title: SMTP header injection in ASP.NET?
body: <p>My ASP.NET website has a global error handler that sends an email to me (and another developer) when there is any kind of error in the web app.  We recently received an error which contained a CC to an email address that we'd never heard of.  The scary thing is that the list of developers that the error email is sent to is hard coded in compiled ASP.NET code.  We don't see how the CC could have been added.</p>
  
  <p>We're also very suspicious of foul play because the request that caused the error was an attempt to use one of our forms to send spam.  The IP address that sent the request is also listed on <a href="http://www.projecthoneypot.org/" rel="nofollow noreferrer">http://www.projecthoneypot.org/</a>.</p>
  
  <p>Our best guess right now is that the request was malformed in some way that it injected a CC header into the email.  The problem is that we can't figure out how this could be done.  We're using System.Net.Mail to send the emails and it seems to protect against this sort of thing.  The subject of the MailMessage object only accepts a single line so that you don't create a multiline subject with a CC line.  Setting the to and cc addresses in the MailMessage seems pretty robust.  And I can't see how you could add a CC header in the body of the message.  I can't find any information on this and I'd love to know if this is a real problem.</p>
  
  <p>EDIT:  Someone requested the code.  It's a little long, but here it is:</p>
  
  <pre><code>public class Global : System.Web.HttpApplication
  {
      protected void Application_Error(Object sender, EventArgs e)
      {
          // Get the last exception.
          Exception objException = Server.GetLastError();
  
          // Work out the error details based on the exception.
          string ErrorType = "";
          string ErrorDescription = "";
          string ErrorHtml = "";
  
          if (objException == null)
          {
              // This should never occur.
              ErrorType = "Unknown Error";
              ErrorDescription = "Unknown Error";
          }
          else if (objException.GetType() == typeof(HttpException))
          {
              // This will occur when the ASP.NET engine throws a HttpException.
              HttpException objHttpException = objException as HttpException;
              if (objHttpException.GetHttpCode() == 404)
              {
                  string Resource = Globals.GetFullUrl(this.Context);
                  Server.ClearError();
                  Response.Redirect("/ResourceNotFound.aspx?BadUrl=" + Server.UrlEncode(Resource));
                  return;
              }
              else
              {
                  ErrorType = objHttpException.GetHttpCode().ToString();
                  ErrorDescription = objHttpException.Message;
              }
          }
          else if (objException.GetType() == typeof(HttpUnhandledException) &amp;&amp; objException.InnerException != null &amp;&amp; objException.InnerException.GetType() == typeof(HttpException))
          {
              // This will occur when the code throws a HttpException (e.g. a fake 404).
              HttpException objHttpException = objException.InnerException as HttpException;
              if (objHttpException.GetHttpCode() == 404)
              {
                  string Resource = Globals.GetFullUrl(this.Context);
                  Server.ClearError();
                  Response.Redirect("/ResourceNotFound.aspx?BadUrl=" + Server.UrlEncode(Resource));
                  return;
              }
              else
              {
                  ErrorType = objHttpException.GetHttpCode().ToString();
                  ErrorDescription = objHttpException.Message;
              }
          }
          else if (objException.GetType() == typeof(HttpUnhandledException))
          {
              // This will occur when a page throws an error.
              HttpUnhandledException objHttpUnhandledException = (HttpUnhandledException) objException;
              ErrorType = objHttpUnhandledException.GetHttpCode().ToString();
              if (objHttpUnhandledException.InnerException != null)
                  ErrorDescription = objHttpUnhandledException.InnerException.Message;
              else
                  ErrorDescription = objHttpUnhandledException.Message;
              if (objHttpUnhandledException.GetHtmlErrorMessage() != null)
              {
                  ErrorHtml = objHttpUnhandledException.GetHtmlErrorMessage();
              }
          }
          else if (objException.GetType() == typeof(HttpRequestValidationException) &amp;&amp; !Globals.IsTtiUser(this.Context))
          {
              // Do nothing.  This is mostly just spider junk and we don't want to know about it.
          }
          else
          {
              // This will occur when the ASP.NET engine throws any error other than a HttpException.
              ErrorType = objException.GetType().Name;
              ErrorDescription = objException.Message;
          }
  
          // Send an email if there's an error to report.
          if (ErrorType != "" || ErrorDescription != "")
          {
              Globals.SendErrorEmail(this.Context, ErrorType, ErrorDescription, ErrorHtml);
          }
      }
  
      public static void SendErrorEmail (HttpContext context, string errorType, string errorDescription, string errorHtml)
      {
          // Build the email subject.
          string Subject = "EM: " + errorType + ": " + context.Request.ServerVariables["SCRIPT_NAME"];
  
          // Build the email body.
          string Body;
  
          StringBuilder sb = new StringBuilder("");
          sb.Append("Server:\r\n");
          sb.Append(Globals.Server.ToString() + "\r\n");
          sb.Append("\r\n");
          sb.Append("URL:\r\n");
          sb.Append(Globals.GetFullUrl(context) + "\r\n");
          sb.Append("\r\n");
          sb.Append("Error Type" + ":\r\n");
          sb.Append(errorType + "\r\n");
          sb.Append("\r\n");
          sb.Append("Error Description" + ":\r\n");
          sb.Append(errorDescription + "\r\n");
          sb.Append("\r\n");
          sb.Append("Referring Page:\r\n");
          sb.Append(context.Request.ServerVariables["HTTP_REFERER"] + "\r\n");
          sb.Append("\r\n");
          sb.Append("Date/Time:\r\n");
          sb.Append(DateTime.Now.ToString() + "\r\n");
          sb.Append("\r\n");
          sb.Append("Remote IP:\r\n");
          sb.Append(context.Request.ServerVariables["REMOTE_ADDR"] + "\r\n");
          sb.Append("\r\n");
          sb.Append("User Agent:\r\n");
          sb.Append(context.Request.ServerVariables["HTTP_USER_AGENT"] + "\r\n");
          sb.Append("\r\n");
          sb.Append("Crawler:\r\n");
          sb.Append(context.Request.Browser.Crawler.ToString() + "\r\n");
          sb.Append("\r\n");
          sb.Append("Admin User:\r\n");
          sb.Append(context.User.Identity.Name + "\r\n");
          sb.Append("\r\n");
          sb.Append("\r\n");
          Body = sb.ToString();
  
          // If there's HTML to represent the error (usually from HttpUnhandledException),
          // then stuff the body text into the HTML (if possible).
          bool HtmlMessage = false;
  
          if (errorHtml != "")
          {
              Regex r = new Regex("(?&lt;thebodytext&gt;&lt;body.*?&gt;)", RegexOptions.IgnoreCase);
              if (r.IsMatch(errorHtml))
              {
                  Body = Body.Replace("\r\n", "&lt;br&gt;");
                  Body = r.Replace(errorHtml, "${thebodytext}" + Body, 1);
                  HtmlMessage = true;
              }
          }
  
          // Send an email to the TTI developers.
          MailMessage objMail;
          objMail = new MailMessage();
          objMail.From = new MailAddress("from-address");
          objMail.To.Add(new MailAddress("to-address"));
          objMail.CC.Add(new MailAddress("cc-address"));
          objMail.CC.Add(new MailAddress("another-cc-address"));
          if (HtmlMessage)
              objMail.IsBodyHtml = true;
          else
              objMail.IsBodyHtml = false;
          if (errorType == "404")
              objMail.Priority = MailPriority.Low;
          else
              objMail.Priority = MailPriority.High;
          objMail.Subject = Subject;
          objMail.Body = Body;
  
          try
          {
              SmtpClient objSmtpClient = new SmtpClient();
              objSmtpClient.Send(objMail);
          }
          finally
          {
              // Do nothing.
          }
      }
  }
  </code></pre>