Attacking web services Pt 1 - SOAP
I often receive testing related questions from AppSec folks new to web services about the techniques used to discover and attack them. Often, web services are seen as difficult to enumerate, interpret, and exploit as well as an arena with only a small arsenal of tools available.
We'd like to bridge that gap a bit by introducing some techniques and offer code, which can be found here. Let's jump right into it. In this article we will cover SOAP specifically. Other popular standards exist such as JSON and REST but for the purpose of this tutorial we will cover SOAP.
Simple Object Access Protocol or "SOAP" leverages an XML structure for messages and typically communicates over HTTP protocol. Web service protocols are a lightweight communication mechanism useful for API driven connectivity and often seen in use with mobile applications.
To follow along with certain portions of the tutorial you will need to install JRuby, Buby, Savon, and Nokogiri gems and have downloaded or purchased a copy of PortSwigger's Burp Suite. The idea is we plan on extending some of Burp's capabilities to make attacking SOAP easier.
As mentioned previously, SOAP communicates messages in an XML structure. This XML structure can be defined in a WSDL or "Web Services Description Language" document. Enumerating this information provides a wealth of data used in formulating attacks and forming requests.
SOAP actions and the parameters they are very useful bits of information and can be extracted from the WSDL. An action might be something like getEmailAddress which is passed a parameter and value such as "profileid=1000". Forming that request and sending it may allow you to discover other user's e-mail addresses simply by changing the profileid value.
Now we could do the enumeration of a WSDL manually by reviewing the response and making sense of the (sometimes) large amounts of data within XML tags but why not create re-usable code to do this quickly and seamlessly?
Install JRuby
$ sudo apt-get install jruby
Install the Savon and Buby gems
$ sudo jruby –S gem install buby $ sudo jruby –S gem install savon
Now it's time to write some code. The Savon gem is a Ruby/JRuby library that allows us to build a SOAP client and interact with the web service. Create a file named attack_soap.rb (click here to get the code) and enter the following code:
Line by Line walk-through
Lines 1-2:
This is a directive to our script that basically says, "Use these libraries".
Line 5:
Next we need to enter code that allows us to hook into Burp Suite and provide a medium to which the tool and our code can meet. We defined a class CustomMenuItem. Line 33 gives a good idea of where this comes into play.
Lines 8-9:
We create a method called enum_wsdl. This method is called on line 27 and passed a URL (rhost object). On line 9 you see that we instantiate the Savon client and pass it the value associated with rhost.
Lines 11- 12:
Both of these are optional values. Line 10 specifies the use of basic authorization which a username and password of "guest". Line 11 sends traffic to our Burp proxy instance.
Lines 13- 18:
Line 13 ensures that client.wsdl and client.wsdl.soap_actions exist prior to being called. Line 14 gives us a pretty purple –{*}- icon and states "List of available action(s):". Lines 15-17 iterate through the array of soap_actions, "puts" each action to the console, and on line 17 we end the loop statement.
Lines 19-21:
Rescue clause in case things go belly-up and if so, we will gracefully handle the error on line 19. On line 20 we will print a red –{-}- marker along with the error ($!) to the screen. Line 21 ends the enum_wsdl method.
Lines 23 -24:
Line 23 is where we define a method called menu_item_clicked. This is extremely important because Burp wants to invoke this method, naming convention specific, on the class we passed it on line 33 when the menu item is clicked (makes sense).
All this means is the class CustomMenuItem must have a method named menu_item_clicked. Take care in noticing we pass it *params. This means multiple objects can be passed to this method.
On line 24 we break up the couple of objects that are passed into two separate objects called menu_item_caption and message_info.
Lines 26 – 31:
The object message_info is an array of messages. Each messages has certain values it is associated with. When we iterate through this array, line 26, we can access these values. On line 27 we do just that by taking the itm object (which is really a message) and access the url value associated with it. We call the enum_wsdl method and pass it this url value. Finally, lines 28, 29, and 31 close off the loop, method, anc class.
Line 33:
Last but not least, we need to create the menu item. We do this by calling Burp's ($burp) registerMenuItem method. We pass the name of the menu item which will be visible to us "enumerate wsdl" and then the class which it can search for the menu_item_clicked method.
The overall workflow will be:
- Click on enumerate wsdl menu item in Burp.
- CustomMenuItem.menu_item_clicked is called and passed two objects.
- From within menu_item_clicked, the enum_wsdl method is called.
- We parse the WSDL for available actions and print them to the console.
Example of running the script:
$ jruby –S buby –i –B ~/Desktop/burp/burp.jar –r ~/Desktop/attack_soap.rb
The next step would be to request the WSDL and intercept the request in Burp. For example:
Intercepted request to http://192.168.1.149/WebGoat/services/WSDLScanning?WSDL
Clicking on "enumerate wsdl" produces:
Now we have an easy to read list of available actions!
We shouldn't stop here. We need to take this a bit farther. Let's add some code here to render available parameters and values. This way we can use these values to form a request.
Now it is important to note that the following code will not parse every WSDL under the sun and "automagically" create the request correctly. This is more of a demonstration on a smaller scale of what is possible with the right code in addition to providing a visual representation of how SOAP requests look.
Now we need to revisit the code we've already written and make some additions. The following photo is the first half of the code base.
Line 3:
We will utilize the Nokogiri library. Hence we "require" it. Ensure you've performed the following prior to running this script:
$ sudo jruby –S gem install nokogiri
Lines 24-27:
On line 24 we create a parse_wsdl method. The method takes in a wsdl XML. Line 25 instantiates and empty hash called wsdl_element_hash. Line 26 creates a doc object that is a Nokogiri parsed wsdl and can be searched easily. Lastly, line 27 searches the XML for the XML tag <wsdl:message> </wsdl:message> .
Lines 28-29:
test_m1 and test_m2 are instantiated so that they can be used outside of the loop (important in the next few lines of code). The ultimate goal of this method is to return a hash with a SOAP Action as a key and the parameter as the value.
As you can see there is an action Request which has a parameter called "id". We need to retrieve the getFirstName, getLoginCount, etc. actions and the parameters it takes to form the SOAP request.
Conceptually, the hash we are creating will look like:
{'getFirstName => 'id',
'getLoginCount => 'id',
'getCreditCard' => 'id',
'getLastName' => 'id'
}
Lines 30-34:
Portions of the XML/WSDL document that contained the <wsdl:message> tags were previously parsed (on line 27) out into an array type structure. The object was then labeled msg. On line 30, we begin iterating through the msg array. Lines 31 & 32 match the "request" SOAP operations (since we are forming a request) and the parameter/type combinations. Lines 33 & 34 strips out the match data (data which matched our regex on lines 31 & 32) unless there was no match. In this case test_m1 and test_m2 are set to "nil".
Lines 36-38:
Provides a sanity check, if test_m1 and test_m2 do NOT return nil, meaning we've successfully matched the XML content against our regular expressions check, we put the SOAP action and its parameter into the wsdl_element_hash.
Lines 39 – 41:
Shown in the picture below, we are ending the loop statement, returning the wsdl_hash_element hash object and closing the parse_wsdl method.
Lines 43-46:
The method called form_request is instantiated and the rhost value (which is a full URL) is accepted as input. Lines 44-46 stand up some of the client settings we will need to correctly build our SOAP client.
Lines 47-48:
Line 47 creates an object called wsdl, which is the WSDL in pure XML representation form. Line 48 passes that XML object to the parse_wsdl method.
Lines 49-53:
On line 49 we iterate through each available soap action discovered in the WSDL. On line 50 we create a conditional setting. If our hash, created by the parse_wsdl method, has the soap_action (itm) action then we need to create a request. On lines 51 to 56 we do this. Lines 52 and 53 form the necessary input. So getCreditCard becomes :get_credit_card and id becomes :id. This is important because this is the format the Savon gem requires requests to be made in.
Lines 54-56:
We create a request on the fly. Every action is passed into the client request along with every parameter.
Lines 57-61:
Closes the loops and the form_request method.
Lines 67-70:
We make a conditional statement depending on which menu item was clicked. If "enumerate wsdl" was clicked, we got to the enum_wsdl method. If "form SOAP request", we divert to the form_request method.
Line 78:
We add another custom menu item called "form SOAP request" which once clicked will not only parse the WSDL but automatically create the SOAP request and send it through to our intercepting proxy for further attack scenarios.
So how does it all look when we are done?
We click on form SOAP request
Four SOAP requests were made (since we had four available actions).
Upon review of the "getCreditCardRequest" wsdl:message we see that we've successfully obtained some poor fellow's credit card number.
So from here we can perform active scans on the formed SOAP requests, send to intruder and fuzz or send to repeater to tweak. The sky is the limit but for now we've coded up an auto parse-to-SOAP-request tool and hacked a credit card number FTW!
Part 2 is now published for the above demonstration...and more.