RSS

Monthly Archives: November 2015

How to hide WebMethods unhandled exception stacktrace using Response.Filter

If you’re interested in the code shown in this post, right-click here, click Save As and rename to zip.

Here’s a nice trick how to manipulate outgoing ASP.NET Response. Specifically, in situations that you would like to provide global handling of errors, removing the stacktrace.

In case you are wondering why you would remove the stacktrace – it is considered a security issue for attackers to be able to review the stacktrace (https://www.owasp.org/index.php/Missing_Error_Handling).

Consider this code, a web method raising an exception.

[WebMethod]
public static void RaiseException()
{
	throw new ApplicationException("test");
}

And the page code:

<asp:ScriptManager runat="server" EnablePageMethods="true" />
<input type="button" value="Go" id="btn" />
<script>
	window.onload = function () {
		var btn = document.getElementById('btn');
		btn.onclick = function () {
			PageMethods.RaiseException(function () { }, function (error) {
				alert(error.get_stackTrace());
			});
		}
	}
</script>

Running this code as is and clicking the button would result with a JSON response that includes the error message, exception type and stacktrace:

exception1

To remove the stacktrace it is possible to use ASP.NET’s Response.Filter. The Filter property is actually a Stream that allows us to override the default stream behavior. In this particular case the custom stream is simply a wrapper. All the stream methods except the method Write() will simply invoke the original stream’s methods.

public class ExceptionFilterStream : Stream
{
    private Stream stream;
    private HttpResponse response;
    public ExceptionFilterStream(HttpResponse response)
    {
        this.response = response;
        this.stream = this.response.Filter;
    }

    public override bool CanRead { get { return this.stream.CanRead; } }
    public override bool CanSeek { get { return this.stream.CanSeek; } }
    public override bool CanWrite { get { return this.stream.CanWrite; } }
    public override long Length { get { return this.stream.Length; } }
    public override long Position { get { return this.stream.Position; } set { this.stream.Position = value; } }
    public override void Flush() { this.stream.Flush(); }
    public override int Read(byte[] buffer, int offset, int count) { return this.stream.Read(buffer, offset, count); }
    public override long Seek(long offset, SeekOrigin origin) { return this.stream.Seek(offset, origin); }
    public override void SetLength(long value) { this.stream.SetLength(value); }

    public override void Write(byte[] buffer, int offset, int count)
    {
        if (this.response.StatusCode == 500 && this.response.ContentType.StartsWith("application/json"))
        {
            string response = System.Text.Encoding.UTF8.GetString(buffer);

            var serializer = new JavaScriptSerializer();
            var map = serializer.Deserialize<Dictionary<string, object>>(response);
            if (map != null && map.ContainsKey("StackTrace"))
            {
                map["StackTrace"] = "Forbidden";

                response = serializer.Serialize(map);
                buffer = System.Text.Encoding.UTF8.GetBytes(response);
                this.stream.Write(buffer, 0, buffer.Length);

                return;
            }
        }

        this.stream.Write(buffer, offset, count);
    }
}

Note that all the methods simply call the original stream’s methods. Points of interest:

  • Line 8: Apparently if you don’t call the original Filter property “on time” you will run into an HttpException: “Exception Details: System.Web.HttpException: Response filter is not valid”.
  • Line 23: From this point on the customization of the Write() is up to you. As can be seen, I selected to intercept error codes of 500 (“internal server errors”) where the response are application/json. The code simply reads the text, deserializes it and if the key “StackTrace” appears, replace its value with the word “Forbidden”. Note that there’s a return statement following this replacement so the code does not proceed to line 41. In all other cases, the original stream’s Write() kicks-in (Line 41).

Finally, we need to actually use the custom Stream. I used Application_BeginRequest in Global.asax like so:

<script runat="server">

    protected void Application_BeginRequest(Object sender, EventArgs e)
    {
        Response.Filter = new ExceptionFilterStream(Response);
    }

</script>

The result:
exception2

Obviously you can use this technique to manipulate outgoing responses for totally different scenarios.

 
Leave a comment

Posted by on 29/11/2015 in Software Development

 

Tags: , , ,