With the recent kerfuffle about sites providing misleading P3P statements, I decided to throw together a quick Fiddler add-on that displays privacy information simply in the Fiddler UI.

Install the new Privacy Scanner add-on and Fiddler will gain a new top-level menu named Privacy.

image

The menu has two options. The Enabled option controls whether the add-on does anything at all; it’s a good practice for Add-ons to offer a simple way to turn them off when they’re not wanted.

When the add-on is enabled, it will add a Privacy Info column to the session list and will flag HTTP/HTTPS responses which set cookies. Evaluation of any P3P statements that come along with those cookies will change the session's background color:

image

Sessions that send a satisfactory P3P policy are shown in green. Sessions that set a cookie without a P3P policy are yellow; in the default IE settings, these cookies will not be sent to a 3rd party context. Sessions that send a P3P policy that does not permit use of the cookie in a 3rd party context are rendered in orange. Sessions that send invalid P3P header tokens will have a red background.

When the menu option Rename P3P header if invalid is checked, if a session presents a P3P statement that is malformed, that P3P header will be renamed to Malformed-P3P to prevent the browser from interpreting it as the P3P 1.0 specification suggested (e.g. ignoring the unknown tokens). You can see the difference when you load this test page; the cookies with bogus compact policies will be dropped in a 3rd party context.

For folks looking to build their own Fiddler extension, the code is provided below.

 

-Eric

 

using System;
using System.Collections;
using System.Globalization;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Text;
using Fiddler;
using System.IO;
using System.Diagnostics;
using Microsoft.Win32;
using System.Reflection;
using System.Text.RegularExpressions;
 
[assembly: Fiddler.RequiredVersion("2.3.9.0")]
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyTitle("PrivacyScanner")]
[assembly: AssemblyDescription("Scans for Cookies and P3P")]
[assembly: AssemblyCompany("Eric Lawrence")]
[assembly: AssemblyProduct("PrivacyScanner")]
 
public class TagCookies : IAutoTamper2
{
    private bool bEnabled = false;
    private bool bEnforceP3PValidity = false;
    private bool bCreatedColumn = false;
    private System.Windows.Forms.MenuItem miEnabled;
    private System.Windows.Forms.MenuItem miEnforceP3PValidity;
    private System.Windows.Forms.MenuItem mnuCookieTag;
 
    public void OnLoad()
    {
        /*
 * NB: You might not get called here until ~after~ one of the AutoTamper methods was called.
 * This is okay for us, because we created our mnuContentBlock in the constructor and its simply not
 * visible anywhere until this method is called and we merge it onto the Fiddler Main menu.
 */
        FiddlerApplication.UI.mnuMain.MenuItems.Add(mnuCookieTag);
    }
 
    public void OnBeforeUnload() {  /*noop*/   }
 
    private void InitializeMenu()
    {
        this.miEnabled = new System.Windows.Forms.MenuItem("&Enabled");
        this.miEnforceP3PValidity = new System.Windows.Forms.MenuItem("&Rename P3P header if invalid");
 
        this.miEnabled.Index = 0;
        this.miEnforceP3PValidity.Index = 1;
 
        this.mnuCookieTag = new System.Windows.Forms.MenuItem();
        this.mnuCookieTag.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.miEnabled, this.miEnforceP3PValidity });
        this.mnuCookieTag.Text = "Privacy";
 
        this.miEnabled.Click += new System.EventHandler(this.miEnabled_Click);
        this.miEnabled.Checked = bEnabled;
 
        this.miEnforceP3PValidity.Click += new System.EventHandler(this.miEnforceP3PValidity_Click);
        this.miEnforceP3PValidity.Checked = bEnforceP3PValidity;
    }
 
    public void miEnabled_Click(object sender, EventArgs e)
    {
        miEnabled.Checked = !miEnabled.Checked;
        bEnabled = miEnabled.Checked;
        this.miEnforceP3PValidity.Enabled = bEnabled;
        if (bEnabled) { EnsureColumn(); }
        FiddlerApplication.Prefs.SetBoolPref("extensions.tagcookies.enabled", bEnabled);
    }
 
    public void miEnforceP3PValidity_Click(object sender, EventArgs e)
    {
        miEnforceP3PValidity.Checked = !miEnforceP3PValidity.Checked;
        bEnforceP3PValidity = miEnforceP3PValidity.Checked;
        FiddlerApplication.Prefs.SetBoolPref("extensions.tagcookies.EnforceP3PValidity", bEnforceP3PValidity);
    }
 
    private void EnsureColumn()
    {
        if (bCreatedColumn) return;
 
        FiddlerApplication.UI.lvSessions.AddBoundColumn("Privacy Info", 1, 120, "X-Privacy");
 
        bCreatedColumn = true;
    }
 
    public TagCookies()
    {
        this.bEnabled = FiddlerApplication.Prefs.GetBoolPref("extensions.tagcookies.enabled", false);
        this.bEnforceP3PValidity = FiddlerApplication.Prefs.GetBoolPref("extensions.tagcookies.EnforceP3PValidity", true);
        InitializeMenu();
 
        if (bEnabled) { EnsureColumn(); } else { this.miEnforceP3PValidity.Enabled = false; }
    }
 
    private void SetP3PStateFromHeader(string sValue, ref P3PState oP3PState)
    {
        if (string.IsNullOrEmpty(sValue))
        {
            return;
        }
 
        string sUnsatCat = String.Empty;
        string sUnsatPurpose = String.Empty;
        sValue = sValue.Replace('\'', '"');
 
        string sCP = null;
 
        Regex r = new Regex("CP\\s?=\\s?[\"]?(?<TokenValue>[^\";]*)");
        Match m = r.Match(sValue);
        if (m.Success && (null != m.Groups["TokenValue"]))
        {
            sCP = m.Groups["TokenValue"].Value;
        }
 
        if (String.IsNullOrEmpty(sCP))
        {
            return;
        }
 
        // Okay, we've got a compact policy token.
 
        oP3PState = P3PState.P3POk;
        string[] sTokens = sCP.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
 
        foreach (string sToken in sTokens)
        {
            // Reject clearly invalid tokens...
            if ((sToken.Length < 3) || (sToken.Length > 4))
            {
                oP3PState = P3PState.P3PMalformed;
                return;
            }
 
            if (",PHY,ONL,GOV,FIN,".IndexOf("," + sToken + ",", StringComparison.OrdinalIgnoreCase) > -1)
            {
                sUnsatCat += (sToken + " ");
                continue;
            }
 
            if (",SAM,OTR,UNR,PUB,IVA,IVD,CON,TEL,OTP,".IndexOf("," + sToken + ",", StringComparison.OrdinalIgnoreCase) > -1)
            {
                sUnsatPurpose += (sToken + " ");
                continue;
            }
 
            // TODO: Look up the token in the complete collection and check validity
        }
 
        // If a cookie contains an unsatisfactory purpose and an unsatisfactory category, mark it
        // http://msdn.microsoft.com/en-us/library/ie/ms537343(v=vs.85).aspx#unsatisfactory_cookies
        if ((sUnsatCat.Length > 0) && (sUnsatPurpose.Length > 0))
        {
            if (oP3PState == P3PState.P3POk)
            {
                oP3PState = P3PState.P3PUnsatisfactory;
            }
        }
    }
 
    private enum P3PState
    {
        NoCookies,
        NoP3PAndSetsCookies,
        P3POk,
        P3PUnsatisfactory,
        P3PMalformed
    }
 
    public void OnPeekAtResponseHeaders(Session oSession) 
    {
        if (!bEnabled) return;
 
        P3PState oP3PState = P3PState.NoCookies;
 
        if (!oSession.oResponse.headers.Exists("Set-Cookie"))
        {
            return;
        }
 
        oP3PState = P3PState.NoP3PAndSetsCookies;
 
        if (oSession.oResponse.headers.Exists("P3P"))
        {
            SetP3PStateFromHeader(oSession.oResponse.headers["P3P"], ref oP3PState);
        }
 
        switch (oP3PState)
        {
            case P3PState.P3POk:
                oSession["ui-backcolor"] = "#ACDC85";
                oSession["X-Privacy"] = "Sets cookies & P3P";
                break;
 
            case P3PState.NoP3PAndSetsCookies:
                oSession["ui-backcolor"] = "#FAFDA4";
                oSession["X-Privacy"] = "Sets cookies without P3P";
                break;
 
            case P3PState.P3PUnsatisfactory:
                oSession["ui-backcolor"] = "#EC921A";
                oSession["X-Privacy"] = "Sets cookies; P3P unsatisfactory for 3rd-party use";
                break;
 
            case P3PState.P3PMalformed:
                oSession["ui-backcolor"] = "#E90A05";
                if (bEnforceP3PValidity)
                {
                    oSession.oResponse.headers["MALFORMED-P3P"] = oSession.oResponse.headers["P3P"];
                    oSession["X-Privacy"] = "MALFORMED P3P: " + oSession.oResponse.headers["P3P"];
                    oSession.oResponse.headers.Remove("P3P");
                }
                break;
        }
    }
    public void AutoTamperRequestBefore(Session oSession) { }
    public void AutoTamperRequestAfter(Session oSession){ /*noop*/ }
    public void AutoTamperResponseAfter(Session oSession) {/*noop*/}
    public void AutoTamperResponseBefore(Session oSession) { /*noop*/ }
    public void OnBeforeReturningError(Session oSession) {/*noop*/}
}