views:

868

answers:

4

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.

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 http://www.projecthoneypot.org/.

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.

EDIT: Someone requested the code. It's a little long, but here it is:

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) && objException.InnerException != null && 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) && !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("(?<thebodytext><body.*?>)", RegexOptions.IgnoreCase);
   if (r.IsMatch(errorHtml))
   {
    Body = Body.Replace("\r\n", "<br>");
    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.
  }
 }
}
A: 

So in your code you leave room for 2 CC addresses. Where do those values get set?

Eppz
They are hard coded email addresses for myself and another developer. I just didn't want to leave real addresses in the code.
Ben Mills
so the CC addresses are hard coded as well?
Eppz
+3  A: 

I could see this being the target of a VERY creative attack.... You are stuffing user controlled data into your message body... At which point, crafty use of binary data COULD result in a BODY that sends the proper data during the SMTP session to get it formatted JUST RIGHT... If I may, I'd suggest either converting the body to all ASCII text, or during your string building, write a string sanitizer that only allows RFC chars in. (Filters the URL's, the REFERRER, Remote Address, and UserAgent). Those are your more likely points of attack.

A second thought might be to construct a basic email in code, and ATTACH the body that you have constructed as a text, or HTML, or PDF file.

Keep in mind, SMTP ENVELOPE data is NOT the same as message data.... If someone was crafty enough to send the correct body that caused a CRLFCRLF.CRLFCRLF to be sent during the body part, that would terminate the sending, and then if they kept sending data, they could send the whole MAIL FROM: RCPT TO:, DATA, etc... (Granted, this is an unlikely scenario...)...

I'd LOVE to see the RAW source of the email you got... (As in the hex dump of the actual SMTP transaction, not what Outlook wants you to see, or whatever).

You may also try encoding your body using QP, or B64 before sending the message.... That might solve your problem...

This is an interesting one, and I'm looking forward to the outcome of it.

LarryF
+1  A: 

Your code looks very secure, so I don't think the problem is on your side.

IMO, either someone intercepted the SMTP message while it was being sent to the mailserver and injected the extra CC: line; or the mailserver has been compromised.

If you can't find an answer I suggest contacting Microsoft directly - you may have uncovered an exploit in the .NET Framework.

Ian Kemp
A: 

As a workaround, why don't you encript the email message using asymeterical encription (for example public key encription)? That way only the intended recepient will be able to read it.

In this way even if the bad guys get a copy of your message (by whatever means) it will be of now use to them.

Lets face it, if you have a high profile website like the FBI or Google, lots of very creative people will spend lots of time and go to great lengths to comprimise it. It is extremely important to protect detailed error messages.

JonnyBoats