backdoorctf – practice challenge – sequel

Here’s another writeup for a practice challenge on the backdoorCTF site. (WARNING….i went on a bit with this post, so it’s massively over-detailed, but..hopefully it explains everything properly).

They just had (April 2015) a full CTF which i took part in, but didn’t have much time to spend on challenges, so didn’t solve anything….but (as they do with all their CTF’s) they then make the challenges available in their “Practice Arena” for you to work on anytime.   So i’ll be hitting some of those soon 🙂

OK, on with the writeup.  This is a 120 point challenge called “sequel”.

sequel

OK, so we have a link to a web application and a link to some of it’s source code too.

When we click on the link to the web application we get the following:

login

Looking at the source code we see the following:

sourcecodeOK, so looks like a basic web authentication page, using a SQL database…but this is an SQLite database, as opposed to MySQL, PostGres etc, but it seems theres not much difference between statements compared to MySQL.

So i could fully test and see whats going on with this web application…and SQLite, i built a “test” system on a VM, so i could work through the code, and see what happens etc.

I built a simple Ubuntu 14.02.2 server, installed Apache, PHP and SQLite, built a simple database containing what i “thought” maybe in the actual one, along with some info etc.

I could see from the source code, that it queried a table in the database called users, also, there was at least 3 columns i was aware of (usernamepassword_hash and name)

query rows

We could also assume that there would be an id column for each entry, so 4 columns all up.

It looked like the password that was entered, was converted to a sha256 hash, then checked (so the passwords were in this format in the database).

And finally there is reference to a user named “sdslabs“, so i made sure to create a user with that username……..remember this for later.

So my database ended up looking like this:

(The hashed passwords i created were p@ssw0rdbackdoor and football respectively.)

database

Next i created the index.html page, same as on the web application, and copied the source code into submit.php, and made a few “tweaks”.

webdir

Into submit.php, i changed the name of the database to match the name of the one i created.

dbname

And also added the following at the bottom:

debug

This allows me to see the value of various variables after i’ve entered them into the web login form, and submit.php has done it’s stuff.

For example.   If i entered the username john and password monday into the form, the returned page looks like:

johntest

Obviously john doesn’t exist in my database, so it fails, but i can see the entered username and password, the hashed password, and the SQL query that’s being run.

If i logged in with the sdslabs user i created i get:

sdslabstest

So obviously, this time the user exists, and it’s the one that the script is looking for, so it would print the flag.  It also shows the same info as before, but also the value of the $name variable, as well as the results of the query against each row.

Cool!   So i have a working web application, with similar code (albeit with extra debug info) and a database which i THINK (note the underline) matches what the actual database is.

For those familiar with SQL, PHP and specifically SQL Injection, this might seem like a lot of overkill, and effort…but as I’m still learning, and want to fully understand what’s happening, and the processes involved…then this helps me 🙂

OK, lets start working through the script.  The first line connects to the SQLite database.

dbname

Next, it sets the variable $username to the entered username, and also the variable $password to the entered password from the login form., but for the password it also hashes it using sha256.  So entered the password “p@ssw0rd” would become “a075d17f3d453073853f813838c15b8023b8c487038436354fe599c3942e1f95

userpass

Next the variable $query is set to the SQL query to be run.

queryfull

This is a fairly generic SQL statement…which doesn’t look or work much differently than one for MySQL.

Next it sets the variable $result to the result of the SQL query that was run.  If the query returns nothing (e.g the user doesn’t exist, or the password is incorrect, the query would fail, and no result would be returned)  But if it was successful it would return the results (as an array) into the variable $result.

It then performs an IF statement, for each returned row in the result.

result

If there isn’t a row in the result (e.g….no user found, or incorrect password) it jumps down to the else statement, where it prints out “User not found or incorrect password” then the script ends

.notfound

But if there is a result it continues on…

And first sets the variable $name to the value in the row, in position “1“. and also the variable $username to the value in the row, in position “2.

setvars

To understand this a bit more, lets login with the account i created in the database with username “bob” and password “football“.  On my test setup, the SQL query would look like this:

bobquery

If we run this query directly within the database itself (and tag on a “;” on the end) it would look like this:

bobquery2

So the resulting row looks like below, which is an array.

3|Bob Smith|bob|6382deaf1f5dc6e792b76db4a4a7bf2ba468884e000b25e7928e621e27fb23cb

And within PHP, if we were to display the value of each item in the $row variable, it would look like this:bobquery3

So the value of $row[1] is “Bob Smith” which corresponds to the value of name in the database, and $row[2] is “bob” which corresponds to the value of username in the database.

OK…back to the script….there is another IF statement, which checks to see if the value in $username is equal to “sdslabs” and if so….tells us we are successful and displays the flag.

sdslabscheck

And if the value in $username isn’t equal, then it just prints the value in $row[1] which is the value name in the database.

elsesdslabs

OK…so thats the script…..now….what criteria must we meet for this script, which will make the LIVE system display the flag.  Well:

  • We must return a valid result from the SQL query.  If it returns nothing, it will simply say “User not found or incorrect password“.
  • We must make sure the value of $username, which is the value in $row[2] from the SQL query result is “sdslabs“.

So….first things first.  The password.   As this gets hashed up, whatever value we enter into this field is going to be turned into gobbledygook for us, so we can safely ignore this completely.

So lets pretend we don’t know a valid username for our test system, how can we get it to return a result, lets try the good ole “‘ OR 1=1 — ” trick.

OR1demo

OR1demoresult

OK, this at least returned a result, which as expected, returned the first row from the database.

How about if we just specify the username “sdslabs” then comment out the rest of the statement.

sdslabsonly

sdslabsonlyresult

Woah!   It worked….wow that was easy.  Lets try that on the “LIVE” system…the actual challenge on backdoorctf…..yeah, you forgot we were actually trying to solve that didn’t you? 😉

sdslabsonlylive

Huh?   Why?  How come that works on my demo system, but not on the LIVE system?

I checked various things in my script, and also my database at this stage, and was confused. “How come the LIVE system doesn’t log me in with the user sdslabs when my demo system does???”……”I know the user exists, as it checks to see if the username equals that in the script!!!”…..”Wait!….it checks to see if the username equals “sdslabs” in the script……but that DOESN’T mean it’s actually a valid user in the database!”

Aha!  Just because it checks that value….doesn’t mean that user exists in the database….sneaky!

I then tried the “‘ OR 1=1 —  trick on the LIVE system and this returns.

OR1live

OK….so obviously on the LIVE system, there is a user with the name “Dhaval Kupil” in the first row in their database.  So using that trick does get us a result at least. Now onto how we can make sure the returned value from the query has “sdslabs” in position $row[2].

Back to our old friend UNION SELECT.  If you are unfamilar with this function, it allows you to add one SQL statement onto another.  And in this case, we want to add our OWN values to the result of the query.

So….on my test system, i have a database, with a table with 4 columns:

  • id
  • name
  • username
  • password_hash

Using UNION SELECT, i can pass my own values into the query….so it returns these values in the results of the query.

Here’s an example.  If we jump into the SQLite database and enter the following query.

SELECT * FROM users WHERE username = 'john';

But as this user doesn’t exist in my database, no row would be returned.

john

But if i use UNION SELECT i can specify what i want to be returned.  So if the query is changed to look something like this:

SELECT * FROM users WHERE username = 'john' UNION SELECT "9", "John Smith", "john", "whateverpassword";

Despite the user not existing, it would return a row, with the values i specified.

johnunion

So….using this, lets try importing our own values using UNION SELECT onto the LIVE system.  As the only value the PHP code checks for is the username field, we’ll only pass that in the values, and use null for the rest.

Our new string we’ll enter into the username field is:

' OR 1=1 UNION SELECT null, null, "sdslabs", null --

This works on my test system:

finaldemo

But doesn’t on the LIVE system 🙁  But i don’t get an error, saying the user wasn’t found, which makes me think there’s something wrong in my SQL statement. But again, why?  When it works on my test system?

Thinking what could be different again…i KNOW there are at least 4 columns in the database….what if there’s more?

So I modified my query to add a new column AFTER my “sdslabs” (as we know this one must be in the 3rd position (row[2]).  So new query is:

' OR 1=1 UNION SELECT null, null, "sdslabs", null, null --

flag

YESSSS!!!!!  So obviously there was another column in the database 🙂

I had heaps of fun figuring this…kind fairly simple challenge out, but i guess the more you understand the simple challenges, the more it helps in more detailed ones.

Again, i highly recommend people who are interested in challenges like this to head over to https://backdoor.sdslabs.co/ and have a go!  They’ve got heaps available and as i mentioned earlier have all the ones from the recent BackDoorCTF2015.

If you’ve got this far…nice effort, and if i made a mistake, or you liked this post….drop a comment below 🙂

-dook

backdoorctf – practice challenge – 2013-web-150

Hi folks

Next week BackdoorCTF 2015 kicks off, and i found out they have a “practice arena” with several challenges from past CTF’s made available.

One of these challenges was one titled “2013-web-150“, which i really enjoyed doing, and allowed me to further increase my SQLi skills, so i figured i’d do a little writeup for my own reference, and maybe for anyone else learning SQLi.

Please remember this and a lot of other challenges are always available at backdoorCTF’s webpage https://backdoor.sdslabs.co so i’m not going to show the flag in this writeup, but explain how i got it.

So…heres the challenge.

bctf1

The flag has to be the sha256 hash of the admin’s password (which is in md5 format) taken from a “news site”.  When you click the link to the website, you are taken to the following page.

bctf2

So, we have to enter a “Notice ID”, first off i just hit “Submit” with nothing in the field.

bctf3

OK, so the output looks like some news about the site….and it seems like there are 2 there, both with their own title and “data” or whatever they want to say.

Going back to the first page, if i enter “1” as the notice ID, i get.

bctf4

OK…so “Notice ID 1” gives us the item with the title “New Version Launched!”, which must mean if we enter the Notice ID of “2” we get…

bctf5

The item with the title “Happy New Year”.   So what about Notice ID “3”.

bctf6

Ahh…nothing…not even an error.  At this point i try a few more (4,5,6,7,8,9,10,100,1000) and get the same result, so it’s safe to assume there are probably only 2 news notices on this site.

At this point, i’m suspecting this website uses a SQL database on the backend to hold all the notices in, but we can’t be sure yet, so lets try and confirm this.

First up, the good ole “” trick, lets throw that into the Notice ID field, see what happens.

bctf19

bctf7

Hmmmn, OK, no SQL related error, but an error about an “Invalid ID”.  So how about putting in “1′“….the same result.

We know there is a notice with id of “1”, but it’s not liking it when we add a “” after it…..how after adding a SQL comment character after it…..incase theres anything been added onto the end of the query (such as another “” to close the SQL statement)

Lets first try a “#” which is the comment character for a MySQL database.

bctf8

Nope…same error, ok, now lets try a “” comment, which is for a PostGreSQL or a MSSQL database.

bctf9

 

bctf4

 

 

Aha!  Success, so it’s safe to assume there is a database in the backend serving the content for this site.  But what sort of database?  Well we’ve ruled out MySQL as the “#” comment character doesn’t work, but it could be MSSQL, PostGreSQL or even an Oracle database…..we’ll come back to this later.

Next, lets try and find out how many columns are likely in the SQL database table this query is being run against.  For this, i’m going to use the following:

1' ORDER BY 1--

The ORDER BY statement is used to sort the results by one or more of the columns.

Entering this into the “Notice ID” field returns a the 1st notice OK, so we know theres at least 1 column in this table….now, change the ORDER BY number to 2…same result.

3….same result.  4…”Invalid ID”!  Aha!

We can now assume that the table this statement queries has 3 columns in it.

Another good way to identify the number of columns is using a UNION SELECT statement.  This statement is used to add a new SQL query onto the end of the existing one, and for this example i’m going to use the following:

1′ UNION SELECT “column1″,”column2″,”column3”–

What this is going todo, is to return the Notice with ID 1 and also return a second result with the values of “column1”, “column2” and “column3”.

bctf10

bctf11

Nice…it worked, the result here tells us the following:

  • It’s DEFINITELY using a SQL database on the backend, as the UNION SELECT statement worked.
  • There is definitely 3 columns in the table it is querying, as if we add/remove a “columnX” from the above statement we get the “Invalid ID” error.
  • The PHP page only returns the values in the 2nd and 3rd column of the table (“column1” isn’t shown)

So gathered with all this info, we can now get an idea of what the SQL table that is being queried looks like:

bctf12

At this stage i wanted to confirm what sort of database i was dealing with, so i had a bit of a Google search on ways to “fingerprint a database” and came across this OWASP page on the subject, and particularly the section on “String Concatenation”, which is the process of joining 2 strings together.

Say we have 2 strings of “abd” and “def” and we want to join them together to form “abcdef”.

With MSSQL you would use:

"abc" + "def"

And with PostGreSQL you would use:

"abc" || "def"

So…we can test this using a variation on the previous UNION SELECT statement.

1' UNION SELECT "column1","column2","abc" + "def"--

The result of this statement gives:

bctf13

Which isn’t right….it looks like it’s tried to ADD the 2 small strings together, so lets now try it using the PostGreSQL way on concatenation.

1' UNION SELECT "column1","column2","abc" || "def"--

bctf14

AHA!  It worked, it joined the 2 strings together….ladies an gentleman…we have a PostGreSQL database for our backend.

 

WOW….this is a long post already, hope you’re still with me 🙂   Now onto the fun stuff….getting the admins password.

Now usually at this stage we would try and get various other info out of the database using commands like:

version() – Gives us further info on the SQL database in use.

SELECT datname FROM pg_database – Lists the databases running on the SQL server.

But none of these seemed to work, same for similar commands to output the table names, or column names.   So i thought that the system had been configured to disallow or conceal this info, so i went with the next best solution….guessing!

As i mentioned previously, we have an “idea” as to how the table that displays all the news notices looks like, maybe we can “guess” how the table that contains users looks like, maybe something like this:

bctf15

Again, i have NO idea, as i’m unable to retrieve info on the name of the tables or columns from the database, so we have to go with guesswork.

First lets try and get the table name, using the following statement:

1' UNION SELECT "column1","column2","column3" FROM members--

This is going to try and query a table named members, if successful, it will simply just display the “column2” and “column3” info like before…but if it works, it means there is a database table called “members” in the database.

bctf7

Invalid ID again….so that table name was wrong, OK, how about “usernames”

1' UNION SELECT "column1","column2","column3" FROM usernames--

Same result “Invalid ID”…..how about “users”.

1' UNION SELECT "column1","column2","column3" FROM users--

bctf11

Aha!  It returned as we expected….so there IS a table named “users“.  Now we need to try and return the information from columns, again by guessing the column names, first starting with “username“.

1' UNION SELECT "column1","column2",username FROM users--

Nope…”Invalid ID” again…how about “user

1' UNION SELECT "column1","column2",user FROM users--

Nope same again……err….”name“?

1' UNION SELECT "column1","column2",name FROM users--

bctf16

WooHoo!  We are getting somewhere.  We’ve just queried the users table asking for the column name and it’s outputted the usernames, which are “john” and….”admin”.

At this stage, i just want to stop (just before the good bit) and perform some “housekeeping” on the SQL query we are using.   First off, lets ditch that first news notice, as we don’t care anymore….so the “1” at the beginning goes.

' UNION SELECT "column1","column2",name FROM users--

Next we know that the 1st column doesn’t get returned, so instead of “column1” we can just use the “null” character to return nothing.

' UNION SELECT null,"column2",name FROM users--

And finally, as we guessed that maybe the username was in the 2nd column, lets move “name” into there, and write what we want for the 3rd column.

' UNION SELECT null,name,"Almost there!!!" FROM users--

bctf17

Much better…..so the only thing todo now….is get it to guess the password column name, and get it to output that too.   Hmmnn….a column name for a password….what could that be? 😉

' UNION SELECT null,name,password FROM users--

bctf18

SUCCESS!!!! (With the flags removed)

All we do now, is generate the SHA256 hash of the admins password, and enter it into the backdoorctf page.

 

I really enjoyed this challenge, and really learnt a lot more about SQL Injection.  I encourage everyone to take part in backdoorCTF 2015 on April 2nd 2015.

And if i made a mistake….or you know how to get the version, table names or column names out of this challenge….please let me know 🙂

-dook

Boston Key Party CTF – Symphony Writeup

The Boston Key Party CTF happened on the weekend, and i didn’t realize till the last minute.

But i managed to get a couple of challenges done, and here’s a nice quick writeup for the Symphony challenge, a huge 25 pointer 🙂

The challenge was:

Challenge

You click the link, and get taken to a web page a password:

Prompt

If you click the Level 2 title, it takes you to the html/php code running in the background.  The main part, being the PHP code, which was:

<?php
require 'flag.php';

if (isset($_GET['password'])) {
     if (is_numeric($_GET['password'])){
          if (strlen($_GET['password']) < 4){
               if ($_GET['password'] > 999)
                    die('Flag: '.$flag);
               else
                    print '<p class="alert">Too little</p>';
          } else
               print '<p class="alert">Too long</p>';
     } else
          print '<p class="alert">Password is not numeric</p>';
}
?>



So lets break down the PHP code.

  • First it checks to see if something has been entered into the password field.
  • Then it uses the PHP function called is_numeric which checks to see if the value entered into the password field is a numeric one.
  • Next to checks to see if the entered value is less that 4 characters in length.
  • Then finally it checks to see if the entered value is greater than 999

So we need to enter a numeric password, greater than 999, but less than 4 characters.  As i’m still learning PHP, i wanted to know more about the function is_numeric

Finds whether the given variable is numeric. Numeric strings consist of optional sign, any number of digits, optional decimal part and optional exponential part. Thus +0123.45e6 is a valid numeric value. Hexadecimal (e.g. 0xf4c3b00c), Binary (e.g. 0b10100111001), Octal (e.g. 0777) notation is allowed too but only without sign, decimal and exponential part.

Oohhh, so as well as decimal, it also works with binary, octal and….hex! 🙂

Time to fire up the super secret hacker tool….Windows Calculator 🙂

Make sure it’s in “Programmer” mode, enter in a number greater than 999, such as….1000.

Then hit the “Hex” button

Calc

And we end up with 3E8….which is a number greater than 999, and has less than 4 characters in it 🙂

UPDATE (03/03/2015)

Looks like i screwed by hex up!, to be an actual hex value it needs to begin with 0x, so the value should be 0x3E8 as pointed out by Zirkonix below in the comments.  Lesson learned 🙂

Attempt

Result

Woohoo!! 🙂   Yeah, i know, a nice basic one, but still fun 🙂

-dook