Click here to Skip to main content
15,886,873 members
Articles / Web Development / IIS

Server-side fix for the Universal PDF XSS Vulnerability

Rate me:
Please Sign up or sign in to vote.
4.50/5 (5 votes)
24 Apr 20075 min read 39.9K   288   18  
This article describes a server-side fix for the recently discovered vulnerability in the PDF reader plugin by Adobe.
using System;
using System.Web;
using System.Security.Cryptography;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Diagnostics;

public class PDFXSSFilter : System.Web.IHttpHandler
{
    private string _EncryptionKey;

    public PDFXSSFilter()
    {
        //
        // TODO: Add constructor logic here
        //
    }
    #region IHttpHandler Members

    public void ProcessRequest(HttpContext context)
    {
        _EncryptionKey = this.GetEncryptionKey();
        HttpRequest request = context.Request;
        string pdfFileName = request.Url.Segments[request.Url.Segments.Length - 1];
        try
        {
            string tokenvalue = null;
            try
            {
                tokenvalue = request.QueryString["p"];
            }
            catch (System.NullReferenceException nex)
            {
                // This isn't good style - just trying to make this work first.
            }
            // Check if the token value is defined in the URL
            if (tokenvalue == null)
            {
                // If not, calculate the secure value
//                string x = context.Server.UrlEncode(EncryptData(request.ServerVariables["REMOTE_ADDR"]));
                string x1 = EncryptData(request.ServerVariables["REMOTE_ADDR"]);
                this.Trace(context, "pvalue", x1);
                this.Trace(context, "remoteip", request.ServerVariables["REMOTE_ADDR"]);
                string x = context.Server.UrlEncode(x1);

                // Now build the redrect URL
                string redirectURL = request.Url.AbsolutePath + "?p=" + x + "#a";

                // Now redirect them to the calculated URL
                context.Response.Redirect(redirectURL, false);
            }
            else
            {
                // Remove the javascript behind first
                tokenvalue = tokenvalue.Split("#".ToCharArray(), 2)[0];
                // Decode the token next
                tokenvalue = context.Server.UrlDecode(tokenvalue).Replace(' ', '+');
                this.Trace(context, "tokenvalue", tokenvalue);
                this.Trace(context, "remoteip", request.ServerVariables["REMOTE_ADDR"]);
                // Check if token data is valid
                if (ValidateData(tokenvalue, request.ServerVariables["REMOTE_ADDR"], context))
                {
                    // If so, transmit as PDF
                    context.Response.ContentType = "application/pdf";
                }
                else
                {
                    //Otherwise, transmit as an octet-stream (ie, force download)
                    context.Response.ContentType = "application/octet-stream";
                    context.Response.AddHeader("Content-Disposition", "attachment; filename=" + pdfFileName);
                    this.Trace(context, "condition", "Returned false");
                }

                //Transmit the file regardless
                context.Response.TransmitFile(context.Server.MapPath(request.FilePath));
            }
        }
        catch (System.Exception ex)
        {
            // Not the cleanest method, but if there are any exceptions, just pass the file as a forced download.
            context.Response.ContentType = "application/octet-stream";
            context.Response.AddHeader("Content-Disposition", "attachment; filename=" + pdfFileName);
            context.Response.TransmitFile(context.Server.MapPath(request.FilePath));
            this.Trace(context, "final_catch", ex.Message);
        }


    }

    // Method to encrypt the data
    private string EncryptData(string data)
    {
        RijndaelManaged rc = new RijndaelManaged();

        string newdata = System.DateTime.Now.ToString() + "," + data;

        // Build the plain text out of the data and the current DateTime
        byte[] PlainText = System.Text.Encoding.Unicode.GetBytes(newdata);
        byte[] Salt = Encoding.ASCII.GetBytes(_EncryptionKey.Length.ToString());

        // Salt and encrypt the data - calculate a Base64 string
        PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(_EncryptionKey, Salt);
        ICryptoTransform Encryptor = rc.CreateEncryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16));
        MemoryStream ms = new MemoryStream();
        CryptoStream cs = new CryptoStream(ms, Encryptor, CryptoStreamMode.Write);
        cs.Write(PlainText, 0, PlainText.Length);
        cs.FlushFinalBlock();
        byte[] CipherBytes = ms.ToArray();
        ms.Close();
        cs.Close();
        string EncryptedData = Convert.ToBase64String(CipherBytes);

        // Return with a SHA512 hash appended
        return EncryptedData.ToString() + ":" + GetSHA512Hash(newdata);
    }

    // Validate the token data
    private bool ValidateData(string data, string remoteip, HttpContext context)
    {
        // Split into the ciphertext and the SHA512 hash
        string[] components = data.Split(new char[] { ':' });
        string CipherText = components[0];
        string SHA512Hash;
        try
        {
            SHA512Hash = components[1];
        }
        catch (System.ArgumentOutOfRangeException aex)
        {
            this.Trace(context, "exception", "ArgumentOutOfRange");
            // If no hash is found, throw an invalid token exception
            throw new InvalidPDFTokenException();
        }

        // Decrypt the data
        RijndaelManaged RijndaelCipher = new RijndaelManaged();
        byte[] EncryptedData = Convert.FromBase64String(CipherText);
        byte[] Salt = Encoding.ASCII.GetBytes(_EncryptionKey.Length.ToString());

        PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(_EncryptionKey, Salt);

        ICryptoTransform Decryptor = RijndaelCipher.CreateDecryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16));
        MemoryStream memoryStream = new MemoryStream(EncryptedData);

        CryptoStream cryptoStream = new CryptoStream(memoryStream, Decryptor, CryptoStreamMode.Read);
        byte[] PlainText = new byte[EncryptedData.Length];
        int DecryptedCount = cryptoStream.Read(PlainText, 0, PlainText.Length);
        memoryStream.Close();
        cryptoStream.Close();

        string DecryptedData = Encoding.Unicode.GetString(PlainText, 0, DecryptedCount);

        // Check if hash of the Decrypted data matches the stored hash
        if (SHA512Hash != GetSHA512Hash(DecryptedData))
        {
            this.Trace(context, "exception", "hash does not match");
            throw new InvalidPDFTokenException("Token Invalid");
        }

        // Split into IP Address and date time
        string[] values = DecryptedData.Split(new char[] { ',' });
        if (values.Length == 2)
        {
            // Verify that the date time is within x seconds and the IPs match
            int tokentimeout;
            try
            {
                tokentimeout = System.Convert.ToInt32(System.Configuration.ConfigurationSettings.AppSettings["TokenTimeout"]);
            }
            catch (System.Exception ex)
            {
                tokentimeout = 10;
            }
            if (System.DateTime.Now < System.Convert.ToDateTime(values[0]).AddSeconds(tokentimeout) && values[1] == remoteip)
            {
                return true;
            }
        }
        return false;

    }


    // Generate the SHA512 Hash
    private string GetSHA512Hash(string data)
    {
        SHA512Managed _sha512 = new SHA512Managed();
        byte[] PlainText = System.Text.Encoding.Unicode.GetBytes(data);
        byte[] sha512signature = _sha512.ComputeHash(PlainText);

        string sha512Hash = Convert.ToBase64String(sha512signature);

        return sha512Hash;
    }

    // Read the encryption key from the config file (typically an AppSettings entry in web.config)
    private string GetEncryptionKey()
    {
        string key = System.Configuration.ConfigurationSettings.AppSettings
            ["TokenEncryptionKey"];
        if (key == null || key == String.Empty)
            throw new InvalidPDFTokenException
                ("TokenEncryptionKey missing");
        return key;
    }

    // Required by the IHttpHandler interface
    public bool IsReusable
    {
        get
        {
            return true;
        }
    }

    [Conditional("DEBUG")]
    public void Trace(HttpContext context, string name, string msg)
    {
        context.Response.AddHeader("DEBUG_"+name, msg);
    }

    #endregion
}

// Custom exception type
[Serializable]
public class InvalidPDFTokenException : Exception
{
    public InvalidPDFTokenException()
        :
        base("PDF Token data is invalid") { }

    public InvalidPDFTokenException(string message)
        :
        base(message) { }

    public InvalidPDFTokenException(string message,
        Exception inner)
        : base(message, inner) { }

    protected InvalidPDFTokenException(SerializationInfo info,
        StreamingContext context)
        : base(info, context) { }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Singapore Singapore
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions