Wednesday, September 18, 2013

ASP.Net Trace.axd, a gold mine of information !

What is Trace.axd ?

Trace.axd is an HTTP Handler that allows access to trace information of current requests. Some sort of phpinfo() with focus on requests details.

This is not a new issue and has been known for a very long time, you can find it mentioned in the excellent article about ASP.Net assessment in HITB number 009.

I'm writing this post however to highlight the impact access to this page could have.

How to discover the vulnerability ?

Simply accessing Trace.axd?id=0, the id is the number of the buffered request, the number of stored requests depends on the server configuration.

If you access a very high number, you'll simply be redirected to a page referencing all buffered requests.

The less known fact is that tracing is not limited to Trace.axd, but can be enabled per page and accessed using the following parameter: page.aspx?displayTraceInfo=true

I didn't find any mention of this parameter in the MSDN, but you could find references of it in stackoverflow. It is also worth mentioning that I'm not aware of any vulnerability scanner that checks for this parameter, while most checks for Trace.axd.

Per page tracing is enabled using the following attribute:
<%@ Page Trace="true" %>

Global tracing is enabled using the following parameter in web.config configuration file:

     < trace enabled="true" />

What's the impact ? 

The real risk of Trace.axd compared to phpinfo() for instance, is that you can access the trace of other user's requests, this means accessing session information, query parameters, like username and password.

Another impact is accessing internal information on the application, like file paths, and SQL queries, which could help in discovering hidden content or assist in the identification and exploitation of tricky injection attacks.

Using some very simple goolg-fu, you can find several vulnerable web application, allowing session hijacking, clear-text password retrieval. An attacker can even write a simple script that would keep polling the Trace.axd?id=0  to retrieve session identifier in real time.

How to correct the issue ?

 By simply changing this setting in web.config:

     < trace enabled="false" />

and by removing the trace attributes in all pages or setting it to false:
<%@ Page Trace="false" %>

Automation ?

Because none wants to have to test each request manually, here is a small Burp plugin to add the parameter to each request that goes through the Proxy.

The plugin is in Python so you can easily modify it to suit your needs:

from burp import IBurpExtender
from burp import IHttpListener
from burp import IParameter

from import URL

import re

class BurpExtender(IBurpExtender, IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName("ASP.Net tracer scanner")

    def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        #list mods to apply processing for, 4 is for proxy mode
        if toolFlag in [4,]:
            if messageIsRequest:
                print "[*] Received a new request ..."
                httpService = messageInfo.getHttpService()
                host = httpService.getHost()
                print "[*] ... from host: %s" % host

                #extractint the URL of the request, analyzeRequest(byte []) doesn't work,
                #must use analyzeRequest(IHTTPRequestResponse ) instead
                infoRequest = self._helpers.analyzeRequest(messageInfo)

                #url is a object
                url = infoRequest.url

                #jython allows accessing getters and setters through parametr name only
                #construction of the new query with 'displayTraceInfo' added
                if url.path:
                    _path = url.path
                    _path = '/'

                if url.query:
                    _query = '?' + url.query + '&displayTraceInfo=true'
                    _query = '?displayTraceInfo=true'

                newUri = _path+_query
                print "[*] New URI: %s" % newUri

                #extracting complete request, to show request in text format,
                #must use strToByte
                request = messageInfo.getRequest()
                strRequest = self.byteToStr(request)
                print "[*] Request: %s" % strRequest

                #replacing the URI with the new one
                #It is not clean to use and a regex to handle the same data,
                #but is useful for demo purposes
                oldUri ="\w+ (.*) HTTP",strRequest).group(1)
                strRequest = strRequest.replace(oldUri,newUri,1)
                print "[*] New Request: %s" % strRequest

                #setting the new request, must be of type byte []
                #modified request won't show in Burp,
                #you can confirm modification using a sniffer and another intance of Burp
                print "[*] Sending new request ..."


    def byteToStr(self, byte):
        return ''.join([chr(c) for c in byte])

    def strToByte(self, string):
        import jarray
        seq = [ord(d) for d in string]

        return jarray.array(seq,'b')


  1. Thanks, great post. I find your opinion quite interesting, but the other day I stumbled upon a completely different advice from another blogger, I need to think that one through, thanks for posting.
    earn money online without investment

  2. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a .Net developer learn from Dot Net Training in Chennai. or learn thru ASP.NET Essential Training Online . Nowadays Dot Net has tons of job opportunities on various vertical industry.