When I started off discussing where I would take the Security Runtime Engine with the Developer Security MVPs Raffaele Rialdi asked if there would be a way to inspect raw requests and responses. Whilst I can’t do requests, as I don’t see them until ASP.NET has parsed them I can do responses, via ASP.NET’s filter mechanisms so, despite him tagging someone else as me on Facebook I started to look at how best to do this and came up with  IResponseInspector. The response inspector works slightly differently to the other inspectors – by the time it’s called there is no longer a Request or Response object, instead there’s a byte[] stream, so the interface looks like this:

/// <summary>
/// Defines methods that must be implemented for response inspection.
/// </summary>
public interface IResponseInspector : ISecurityRuntimePlugIn
{
    /// <summary>
    /// Inspects an HTTP response for potential problems.
    /// </summary>
    /// <param name="response">A byte array containing the response to inspect.</param>
    /// <returns>An <see cref="IInspectionResult"/> containing the results of the inspection.</returns>
    /// <remarks>
    /// The response is writable - any changes made to it will be sent to the client.
    /// </remarks>
    IInspectionResult Inspect(ref byte[] response);
}

You’ll notice the response is passed in by reference, meaning you can change the contents of the response parameter and it will change the response before it is finally sent on to the requesting client. This opens up some interesting possibilities; when I was implementing this the technical news was reporting how Blippy, a social media site where you share what you’re buying (why on earth you would want to do that I have no idea) had in fact published credit cards which were then indexed by Google. It’s a pretty rare scenario where you would legitimately publish full credit card numbers, so implementing a Blippy proof response inspector seemed like a good example, so ….

using System;
using System.ComponentModel.Composition;
using System.Text;
using System.Text.RegularExpressions;

using SecurityRuntimeEngine;

/// <summary>
/// A response inspector to halt any response which contains a credit card number.
/// </summary>
[Export(typeof(IResponseInspector))]
public sealed class CreditCardResponseInspector : IResponseInspector, IConfigurablePlugIn
{
    /// <summary>
    /// The configuration section name,
    /// </summary>
    private const string ConfigSectionName = "sreCCInspectorSettings";

    /// <summary>
    /// A regular expression for credit card number detection.
    /// </summary>
    /// <remarks>
    /// Original regex sourced from http://www.regexlib.com/REDetails.aspx?regexp_id=1288
    /// </remarks>
    private readonly Regex cardRegex = new Regex(@"/*(^|[^0-9])((4[0-9]|5[12345]|6[0245])[0-9]{2}[^0-9]?[0-9]{4}[^0-9]?[0-9]{4}[^0-9]?[0-9]{4}|3[47][0-9]{2}[^0-9]?[0-9]{6}[^0-9]?[0-9]{5})([^0-9]|$)/*", 
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Multiline); /// <summary> /// The encoding for the response. /// </summary> private readonly UTF8Encoding encoding = new UTF8Encoding(); /// <summary> /// Internal, strongly typed settings. /// </summary> private CreditCardInspectorSettings internalSettings = new CreditCardInspectorSettings(); /// <summary> /// Gets the list of excluded paths for the plugin. /// </summary> /// <value>The list of excluded paths for the plugin.</value> public ExcludedPathCollection ExcludedPaths { get { return this.internalSettings == null ? null : this.internalSettings.ExcludedPaths; } } /// <summary> /// Gets the configuration section name for the plug-in. /// </summary> /// <value>The configuration section name for the plug-in.</value> public string ConfigurationSectionName { get { return ConfigSectionName; } } /// <summary> /// Gets or sets the settings for the plug-in. /// </summary> /// <value>The settings for the plug-in.</value> public BasePlugInConfiguration Settings { get { return this.internalSettings; } set { this.internalSettings = value as CreditCardInspectorSettings; } } /// <summary> /// Inspects an HTTP response for potential problems. /// </summary> /// <param name="response">A byte array containing the response to inspect.</param> /// <returns> /// An <see cref="IInspectionResult"/> containing the results of the inspection. /// </returns> /// <remarks> /// The response is writable - any changes made to it will be sent to the client. /// </remarks> public IInspectionResult Inspect(ref byte[] response) { try { string responseAsString = this.encoding.GetString(response); Match match = this.cardRegex.Match(responseAsString); if (match.Success) { return new ResponseInspectionResult(); } } catch (DecoderFallbackException) { } catch (ArgumentException) { } return new ResponseInspectionResult(InspectionResultSeverity.Continue); } }

What we’re doing is taking the response byte array in our Inspect method, converting it to a string and then running a regular expression over it to look for patterns that may be a credit card. The regular expression took some work, and lots of emails on one of the internal security mailing lists and raised eyebrows and questions of “Exactly why are you wanting this?” (note to self “For evil” is not a good response to that question. The response needs to be converted into a string because regular expressions don’t run over byte arrays. If the regular expression finds a match a ResponseInspectionResult() is created, which has a default InspectResultSeverity of halt. When the SRE detects that return value it stops processing by throwing an exception and the page is never rendered. Of course this may be too severe for your needs, instead you could use the regular expression to replace credit card strings with XXXX-XXXX-XXXX-XXXX or some such and then use the SRE’s logging to log what happened, assuming of course you check your logs …

You’ll see this in the next code drop, along with a major change to AntiXSS which will allow you to safe list languages tables from the Unicode UTF-8 specification. Safe listing the languages your web site expects means that the characters in the language ranges will not be converted into their hex equivalents, speeding up encoding. Dangerous characters such as < > " and & will always be encoded, as you would expect.

In the next code drop you’ll see a bunch of sample inspectors which may give you some ideas of your own … and I have a couple of interesting ones to come myself …