We're only a few days into 2016, and it didn't take long for me to see a web application vulnerability that has been documented for over 10 years: HTTP Verb Tampering. This vulnerability occurs when a web application responds to more HTTP verbs than necessary for the application to properly function. Clever attackers can exploit this vulnerability by sending unexpected HTTP verbs in a request (e.g. HEAD or a FAKE verb). In some cases, the application may bypass authorization rules and allow the attacker to access protected resources. In others, the application may display detailed stack traces in the browser and provide the attacker with internal information about the system.
This issue has been recognized in the J2EE development stack for many years, which is well supported by most static analysis tools. But, this particular application wasn't using J2EE. I've been doing static code analysis for many years, and I must admit — this is the first time I've seen this issue in an ASP.NET web application. Let's explore this verb tampering scenario and see what the vulnerability looks like in ASP.NET.
Consider the following example. A web page named "DeleteUser.aspx" accepts one URL parameter called "user". Logging in as an "Admin", the following snippet shows a simple GET request to delete the user account for "bob". In this example, the application correctly returns a 200 OK response code telling me the request was successful.
GET /account/deleteuser.aspx?user=bob HTTP/1.1 Host: localhost:3060 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Cookie: .ASPXAUTH=0BD6E325C92D9E7A6FF307BBE3B4BA7E97FB49B44B518D9391A43837DE983F6E7C5EC42CD6AB5 Connection: keep-alive
Obviously, this is an important piece of functionally that should be restricted to privileged users. Let's test the same request again, this time without the Cookie header. This makes an anonymous (i.e. unauthenticated request) to delete the user account for "bob". As expected, the application correctly returns a 302 response code to the login page, which tells me the request was denied.
GET /account/deleteuser.aspx?user=bob HTTP/1.1 Host: localhost:3060 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive
Similarly, I logged in as a non-admin user and tried the same request again with the non-admin's cookie. As expected, the application responds with a 302 response code to the login page. At this point, the access control rules seem to be working perfectly.
Hunt the Bug
During the security assessment, I noticed something very interesting in the application's web.config file. The following code example shows the authorization rule for the delete user page. Can you find the vulnerability in this configuration?
<location path="DeleteUser.aspx"> <system.web> <authorization> <allow roles="Admin" verbs="GET"> <deny users="*" verbs="GET"> </deny></allow></authorization> </system.web>
Recognizing this vulnerability requires an understanding of how the ASP.NET authorization rules work. The framework starts at the top of the list and checks each rule until the first "true" condition is met. Any rules after the first match are ignored. If no matching rules are found, the framework's default allow all rule is used (e.g. <allow users="*" />).
In the example above, the development team intended to configure the following rules:
- Allow users in the "Admin" role access to the "DeleteUser.aspx" page
- Deny all users access to the "DeleteUser.aspx" page
- Otherwise, the default rule allows all users
But, the "verbs" attribute accidentally creates a verb tampering issue. The "verbs" attribute is a rarely used feature in the ASP.NET framework that restricts each rule to a set of HTTP verbs. The configuration above actually enforces the following rules:
- If the verb is a GET, then allow users in the "Admin" role access to the "DeleteUser.aspx" page
- If the verb is a GET, then deny all users access to the "DeleteUser.aspx" page
- Otherwise, the default rule allows all users
Now that we know what the authorization rules actually enforce, are you wondering the same thing I am? Will this web site really allow me to delete a user if I submit a verb other than GET? Let's find out by submitting the following unauthenticated request using the HEAD verb. Sure enough, the server responds with a 200 OK response code and deletes Bob's account.
HEAD /account/deleteuser.aspx?user=bob HTTP/1.1 Host: localhost:3060 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive
For what it's worth, we could also submit the following POST request with any parameter and achieve the same result. Both verbs bypass the broken authorization rules above.
POST /account/deleteuser.aspx?user=bob HTTP/1.1 Host: localhost:3060 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Content-Length: 3 x=1
Luckily for our development team, the HTTP verb tampering issue is very simple to correct. By removing the "verb" attributes in the new configuration shown below, ASP.NET enforces the access control constraints for all HTTP verbs (e.g. GET, POST, PUT, DELETE, HEAD, TRACE, DEBUG).
<location path="DeleteUser.aspx"> <system.web> <authorization> <allow roles="Admin"> <deny users="*"> </deny></allow></authorization> </system.web>
It is also a best practice to also disable HTTP verbs that are not needed for the application to function. For example, if the application does not need PUT or DELETE methods, configure the web server to return a 405 Method Not Allowed status code. Doing so will reduce the likelihood of HTTP verb tampering vulnerabilities being exploited.
It should be noted that the sample tests in this blog were run against ASP.NET 4.5 and IIS 8.0. Older versions may react differently, but the fundamental problem remains the same.
To learn more about securing your .NET applications, sign up for DEV544: Secure Coding in .NET!
Eric Johnson (Twitter: @emjohn20) is a Senior Security Consultant at Cypress Data Defense, Application Security Curriculum Product Manager at SANS, and a certified SANS instructor. He is the lead author and instructor for DEV544 Secure Coding in .NET, as well as an instructor for DEV541 Secure Coding in Java/JEE. Eric serves on the advisory board for the SANS Securing the Human Developer awareness training program and is a contributing author for the developer security awareness modules. Eric's previous experience includes web and mobile application penetration testing, secure code review, risk assessment, static source code analysis, security research, and developing security tools. He completed a bachelor of science in computer engineering and a master of science in information assurance at Iowa State University, and currently holds the CISSP, GWAPT, GSSP-.NET, and GSSP-Java certifications.