Task | Production |
---|---|
Competition | Teaser Dragon CTF 2018 |
Location | Online |
Category | Software exploitation |
Platform | Linux x64 |
Scoring | 343 pts (easy/medium) |
Number of solves | 16 out of 233 teams |
Can you spot any problems with the production-level quality of our lyrics explorer code?
Click to expand
The task is a x64 ELF file with all mitigations enabled (NX, PIE, RELRO etc.), and the teams are provided the original source code of the program. It allows the user to browse the lyrics of some rock bands' songs. The exploitation process is as follows:
- List the contents of the task's cwd through a one-level path traversal, thanks to a bug in the "songs" operation ("songs .."). This lets you see that the flag can be found in the "flag" file.
- The intended bug is that when you try to read a file which contains the string "DrgnS", the
assert(close(globals::records[idx]) == 0);
code is executed, which looks like it closes the file descriptor, but because it is compiled in release mode (see the-DNDEBUG
flag), the whole expression inside ofassert()
is not included in the executable, resulting in a concealed fd leak. - In
set_limits()
, we set the maximum number of open fds in the process to 32. This means that any potential fd leak can quickly exhaust the fd quota for the process. - The "flag" file cannot be used for the fd leak, because the "flag" word is filtered in the paths of opened lyrics files. However, the "lyrics" task executable also contains the "DrgnS" string, which makes it feasible to use through a "open .. lyrics" command followed by a few reads from the file.
- Thanks to the leak, we can cause the first
open()
inopen_lyrics()
to succeed, but the second one to fail. This enables us to skip the check for the "flag" word in the file path, making it possible to plant an fd to the flag file in the records list. - We still cannot read the flag directly from the file because of the logic in
read_lyrics()
, but we can load the flag into the static buffer. - At the end, we try to read the contents of a lyrics file which was already fully read before (i.e. is currently at EOF), which results in
read_lyrics()
printing out the leftover string in the static buffer, which happens to be the flag.