Web Service Hacking

Tips and Tricks: 10 Tests of a Web Service Login you should always do

The most common Web Service Request must be The Login, many of the web services we produce are used by an identified user. This leads to us often having a Login TestStep as the the starting point for all our Web Service testing a typical TestCase will look Like this: Log In, Get a Session ID and use that ID in all subsequent requests, and finally use that session id to Log out.

We have a long tradition of doing security Testing of Login functionality for "Regular" Web Pages as are we very conscious about intrusion mechanisms for web pages when we build them, but still both Security and security testing is quite often left out of Web Service Testing.

In this tip and tricks article we will produce some simple tests you can perform when doing your Web Service Testing and that we feel you should always do. Create the tests in your own project, save them as a template and use them in all your tests all the time.

Before we look into the tests, we have to be aware of what we're looking for, so first let's state this; large part of hacking often is not about actually gaining access to a system, but rather exposing system behavior in order to be able to get access to it later. This means large parts of our testing is not about cracking the system, but rather expose behavior in your web service that exposes how it works. Our first Tip is an example of this.

 


Tip 1) SQL Injection Tests

Date: July 9, 2009

SQL Injection the art of sending in SQL Statements in forms and data to the target system to be executed by the back end database. The result we're looking for is will either for the system to allow you access or to display information that will move us closer to getting access. In the infancy of The Web, this used to be a large problem, but is largely handled today at least on a basic level. Unfortunately with in the realm of SOA development we've taken a step back and the database is exposed surprisingly often.

What we'll be looking at here is using several small steps to see if the base security is fine in regards to Data Injection.

Step 1: Random SQL
We'll start of with a simple test, we insert a SQL Statement in any field and monitor the return response.

<login>
  <username><User>SELECT * from userstable</username>
  <password>*</password>
</login>

This might seem way to simple, but look at this message:

Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft]
[ODBC SQL Server Driver][SQL Server]Syntax error Invalid string or buffer length.

We have already gained information about what what the database is, we can probably guess what the platform used to create the Web Services are and can use that information in further attacks.

Step 2: Wildcards
Next we enter a SQL WildCard

<login>
  <username>*</username>
  <password>*</password>
</login>

Both Step 1 and 2 are similar and should really not result in any errors, but although it shouldn't doesn't mean it doesn't and it's wise to try it: you might get an SQL error back. Step 3 is more complicated

Step 3: The Classic
This test is the most common SQl injection test using the following:

          
<login>
  <username> ' or 1=1--</username>
  <password>' or 1=1--</password>
</login>

"Why?", you might ask. Well, if the SQL used to check the login is:

SELECT * FROM users WHERE username = '[username]' AND password ='[password]';

This results in the following if the contents of the elements aren't checked:

SELECT * FROM users WHERE username = '' or 1=1 - -' AND password ='[password]';

Which might actually cause the SQL Server to exclude everything after ?--" (since it's TransactionSQL) and just return the first user in the database. With some (bad)luck, we might even be able to log in.

Step 4: Empty Strings; The Classic updated
Step 4 is a variation of step 3:

<login>
  <username> ' or ''='</username>
  <password>' or ''='</password>
</login>

Which results in the following SQL:

SELECT * FROM users WHERE username ='' or ''='' and Password = '' or ''=''

Returning all records in the database and possibly logging us in.

Step 5: Type Conversions
We can also try exposing the database by trying sending in type conversions that surely will fail in the database.

<login>
  <username>CAST('eviware' AS SIGNED INTEGER)</username>
  <password>yesitdoes!</password>
</login>

The goal here is -as with the above- to make the database give us any info by sending an error message that exposes the database. As we said earlier, anything that exposes what the database or the application platform is using is helpful, it can help us look up specific vulnerabilities for that environment.

Database hacking is a chapter in itself and you should be learning it from the pro's themselves: The Database Hacker's Handbook: Defending Database Servers

This tip was quite long, the next will be considerably shorter.


Tip 2) Log In and Log In again

Date: July 10, 2009

The fact that this even is a test is of note. Really? Log in and Log in again, why should we test this?
Well, the premise for this test is kind of similar to Tip 1. Although session security is well handled in most applications on the web, when it comes to Web Services it's not. This test fails surprisingly often and that's why it should be tested.

See it as kind of making sure your network cable is in your computer when you don't have net access... it feels stupid and degrading to do, but it's a good first step and it does prove to be a problem half the time. That's why this test should be in everybody's toolbox.

1) The base test

The test itself is is a very simple test.

Do a standard Login and then do a standard Login again with the same user without doing a log out. Like this:

  • login
  • login

If the Login succeeds you are looking at a potential security risk. Also, we might want to look into the response message, is the double login properly handed? Do we get a raw exception that has been thrown propagated up through the system, which exposes the application server? This might be a bit to security conscious, but at least it should be identified and discussed.

2) Deepen the test

That was the base test and our starting point, now it's time develop the scenario and deepen the test, try this:

  • login
  • logout
  • login
  • logout
  • login
  • login

The result is not likely to change from the base test, but we never know what might turn up, and at least after, we know. The time invested is almost NULL since all we have to do is clone the TestCase and in the new TestCase, clone the TestSteps.

Don't stop there; do tests with long chains of logins and out before testing it. We never know what behavior might show up, and since it's so fast in soapUI to develop new tests, you can almost do it on the fly. Also try interspersing regular requests using correct, expired, and faulty sessionid's.

3) Correct id

This is your base test for further exploration and should succeed. We need this as a control test for the tests that should fail later and well use this as a master for creating the next tests.
Login

<login>
  <username>eviware</username>
  <password> s0ApU1R0ck5</password>
</login>

Response

< loginResponse>
  <sessionid>0646305218268376</sessionid>
</ loginResponse>

New Request

<getcustomer>
  <sessionid>0646305218268376</sessionid>
  <customerid>vipcustomers_ 23957</ customerid >
</getcustomer>

As we said, this a base request and should succeed, but we'll use that to build on. Of course we don't actually send the session id in the example, we transfer the sessionid from the loginresponse to the getCustomer Request, like this is you use PropertyExpansion;

<getcustomer>
  <sessionid>${Test Request: Login#Response#//sam:loginResponse[1]/sessionid[1]}</sessionid>
  <customerid>vipcustomers_ 23957</ customerid >
</getcustomer>

4) Request with Expired sessionid

Now, let's build on it. Let's see what happens if we try to do a getCustomer after logging out.
Login

<login>
  <username>eviware</username>
  <password> s0ApU1R0ck5</password>
</login>

Response

<loginResponse>
  <sessionid>0646305218268376</sessionid>
</ loginResponse>

Logout

<logout>
  <sessionid>0646305218268376</sessionid>
</logout>

Request while logged out

<getcustomer>
  <sessionid>0646305218268376</sessionid>
  <customerid>vipcustomers_ 23957</ customerid >
</getcustomer>

Request with expired id

<getcustomer>
<sessionid>0646305218268376</sessionid>
<customerid>vipcustomers_ 23957</ customerid >
</getcustomer>

5) Request with Faulty SessionID

Now for the final test; what happens if we do a GetCustomer with a faulty id straight after logging out. Login

<login>
  <username>eviware</username>
  <password> s0ApU1R0ck5</password>
</login>

Response

< loginResponse>
  <sessionid>0646305218268376</sessionid>
</ loginResponse>

Logout

<logout>
  <sessionid>0646305218268376</sessionid>
</logout>

Request with non existing id

<getcustomer>
  <sessionid>456464564654645</sessionid>
<customerid>vipcustomers_ 23957</ customerid >
</getcustomer>

This should of course render an error message.

Now, build on these tests further. Try different unexpected variations of the tests here, like for example, what happens when two ID's log in simultaneously and sends requests, does the session management work? And remember:Improvise! You'll never know what you find...

 


Tip 3) À la recherche du Users perdu

Date: July 10, 2009

Now, for a simple tip, this is a continuation of the tip above. It's very simple, and as such it need to be in your bag of tricks.

Let's start by iterating; We're looking for any information that might learn us more about system behavior, set up, or data. Anything that helps us getting closer to getting into the target system is what we want. What we're looking for her is even more common than previous scenarios, and this is worrying, because in this case ther target gives up very useful information.

This is what we do, enter what you know is a non-existing user name: Say that you have a user name and password combination like this:

  • User: eviware
  • Password: s0ApU1R0ck5

Use a login like this:

<login>
  <username> emery bear</username>
  <password> s0ApU1R0ck5</password>
</login>

And look for a response with the following meaning:

<loginresponse>
  <error>That user does not exist</error>
</loginresponse>

This will allow you to work through a number of user names until find you one that is working.


Tip 4) À la recherche du Users perdu. Deux

Date: July 14, 2009

Now let's do it the other way around, what happens if we enter a correct user name and a faulty password?

<login>
  <username> eviware</username>
  <password>yesitdoes!</password>
</login>

If we get a response with the meaning

<loginresponse>
  <errror>Wrong user name for the password</error>
</loginresponse>

We know that the Web Service we're testing will reveal if you enter a valid password, which is a good start for trying to find the correct password.

As with previous tips you will be surprise how often this works. You should also try out several combinations and... Improvise!


Tip 5) The Lockout

Date: July 15, 2009

This security flaw is extra common in Web Services and one that if handled correctly offers very good protection. Web Services aren't as public as web pages and basic security measurements aren't implemented, we probably think that ?Well, the Web Service won't be public so it's a good bet we're not going to be noticed".

A short unscientific study showed that there are two more reasons why; with web services, we let the prototype go live without actually industrializing it, or the web service is created by rightclicking a method or class in your favorite IDE and chossing "Publish as Web Service".

What we do to test it is, basically make an loop with a login request that automatically updates the faulty password. If you haven't been locked out after a certain number of tries (how many depends on business requirements, but three should be a good target), you have a potential security risk.
First Request

<login>
  <username> eviware</username>
  <password>yesitdoes!1</password>
</login>

Second Request

<login>
  <username> eviware</username>
  <password>yesitdoes!2</password>
</login>

And so on...

So what lockout do we choose? Well the usual is after three failed attempts we get locked out for a certain time, like 6-24 hours. One that is very interesting is the Geometrically Increased penalty; for each try you lockout time doubles; the first failed attempt gives you a 1 second delay, the second, 2, the third 4 and so on. This makes the penalty for an honest mistake very slight, and not very deterring you might think, but look at what happens later; after 25 failed attempts the lock out time is 225 seconds or as it is more commonly know; more than a year!. This makes robots or scripts unusable!

 


Tip 6) Element Duplication

Date: July 16, 2009

Sometimes we might not be able to hack a Web Service directly, but we can deduce how the Web Service behaves by sending it unexpected XML. One way is sending double elements, like this:

<login>
  <username> eviware</username>
  <password> s0ApU1R0ck5</password>
  <password> s0ApU1R0ck5</password>
</login>

You might get a response like this

<loginresponse>
<error>password is allowed only once and must be at least 6 characters and at most 20 characters.</error>
</loginresponse>

Also try that in several permutations:

<login>
  <username> eviware</username>
  <username> eviware</username>
  <password> s0ApU1R0ck5</password>
  <password> s0ApU1R0ck5</password>
</login>

Or:

<login>
  <username> eviware</username>
  <username> eviware</username>
  <username> eviware</username>
  <password> s0ApU1R0ck5</password>
</login>

Don't stop there! It is just a matter of cloning a TestStep and then changing it to be a new test. Try the unexpected. And Improvise!

Next step is flipping this test...

 


Tip 7) Element Omission

Date: July 17, 2009

lement Omission is quite similar to Element Duplication, but the opposite. Instead of having extra elements, we enter less elements in the request:

<login>
  <username> eviware</username>
</login>

To your surprise, you might be getting:

<loginresponse>
  <errror>element password is expected.</error>
</loginresponse>

You should do clone and change here as well, we'll try the orther way around:

<login>
  <password>s0ApU1R0ck5</password>
</login>

and without any elements at all:

<login>
</login>

 

Tip 8) Malformed XML

Date: July 20, 2009

This one is fun; try different variations of the elements in the request:

<login>
  <user_name> eviware</username>
  <pass_word> s0ApU1R0ck5</password>
</login>

or like this:

<login>
  <user> eviware</username>
  <pass> s0ApU1R0ck5</password>
</login>

You might be surprised by the answer:

<loginresponse>
  <errror>element username is expected.</error>
</loginresponse>

also, send requests where the end elements afre missing

<login>
  <username>eviware<username>
  <pass> s0ApU1R0ck5</password>
</login>

and the opposite; requests with missing start elements:

<login>
<user> eviware</username>
  s0ApU1R0ck5</password>
</login>

Something to also malform is the namespaces. Let's look at how the pseudo code we've been using earlier actually would look:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:demo="http://demo.eviware.com">
  <soapenv:Header/>
    <soapenv:Body>
    <demo :login>
      <demo:username> eviware</demo:username>
      <demo:password> s0ApU1R0ck5</demo:password>
    <demo :/login>  
  </soapenv:Body>
</soapenv:Envelope>

Now, let's change omit one of the name spaces:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:demo="http://demo.eviware.com">
  <soapenv:Header/>
    <soapenv:Body>
    <demo :login>
      <username> eviware</demo:username>
      <demo:password> s0ApU1R0ck5</demo:password>
    <demo :/login>  
  </soapenv:Body>
</soapenv:Envelope>

as well as the reference to the namespace and have one quote to many

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"">
  <soapenv:Header/>
    <soapenv:Body>
    <demo :login>
      <username> eviware</demo:username>
      <demo:password> s0ApU1R0ck5</demo:password>
    <demo :/login>  
  </soapenv:Body>
</soapenv:Envelope>

Tip 9) Boom goes the Payload!

Date: July 21, 2009

Let's start with a quote from Steve Jobs: "Boom!".
The basis for this test is simple; "The weirdest things happens with the weirdest content". Basically, what we'll do is simple, we'll fill up the contents of an element with a huge payload. But first do this slightly, let's assume you know that the user name is allowed to be 25 characters. try what happens with 26;

<login>
  <username>eviware eviware eviware e</username>
  <password>s0ApU1R0ck5</password>
</login>

We should also try 24 and 25 just for interest sake, we'll do the usual, clone a test and then change the message.

That really should be handled correctly, but what happens when we enter a huge number of characters, a payload overload?

<login>
  <username>
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware 
  eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware eviware
  </username>
  <password> s0ApU1R0ck5</password>
</login>

For demonstration purposes I kept the payload small, make the content of usernameHUGEand see what happens:

2007-12-03 13:54:21,706 [Servlet.Engine.Transports : 0] FATAL WebService.CustomerService. 
Login  - Description: java.rmi.ServerException: RemoteException occurred in server thread; 
nested exception is: 
java.rmi.RemoteException: Error; nested exception is: 
java.rmi.RemoteException: Problem with Query; nested exception is: 
java.sql.SQLException: Could not insert new row into the table. Context: 
DataBaseRemote.getCusstomerData, customer=456789 Identity: eviware 
Details: java.rmi.ServerException: RemoteException occurred in server thread; nested exception 
is: To Long UserName, must be Maximum 24 Bytes

The above is a slightly modified response in tests from a user in the community (with their permission of course). The actual response contained information about both the database and the application server as well as information about the ERP system built on top of it and the name of the Stored Procedure used. The test also had the nice effect that it ground the application server to a halt, making it vulnerable for attacks.

 


 

Tip 10) XPath injection

Now for the final tip, we're we'll end up where we started; XPath Injection. soapUI users probably knows about XPath since this is what we use for XPath assertions, when we transfer content and more. The reason why we use Xpath is because this the standard (and a very powerful) way to access and and query XML documents, "SQL for XML".

XPath injection then basically is like SQL injection in XML documents. Now, user data, for example, is seldom stored in XML Documents, so you might believe you are safe, but often the system you're testing is communicating with another system over Web Services. And what do we use to communicate, what do we send back and forth? XML documents...

Now, when we know why, let's look at how.

<login>
  string(//user[username/text()='' or '1' = '1' and password/text()='' or '1' = '1'])
</login>

We know that from the SQL Injection example, we're trying to let the system log us in. It might not work, but it is very interesting to see how the error has been handled.

 

We can also try to tease the XPath processor in the target system;

<login>
  string(//user[user_name/text()='' or '1' = '1' and password/text()='' or '1' = '1'])
</login>

What happens when the XPath processor gets a faulty node? Will we get an error message directly from Xalan, Saxon, Microsoft's XPathNavigator?

 

You should of course play around with the XPath expressions some more and try to coax out as much information as possible about the target system.