STLDoctor ========= STLDoctor is a plain-text protocol service, that allows users to upload STL files and generate reports that include information on the file's.. - model name - binary header - file type (bin vs ascii) - file size - triangle count - bounding box size and position - validity Uploaded models and generated reports are stored in a directory structure. Unregistered user's files are saved in a collective directory, which allows users to search for public models via model name. Registered user's uploads are saved to a private directory. This (theoretically) prevents other users from accessing their files. The service is hosted with ncat, one process per client. Models are periodically checked for removal via their *last modified* date and tracked using index files. For both flagstores the **service returns the flag in plaintext**, which is vulnerable to detection by network filters. However, multiple sessions can be used to somewhat obfuscate the exploit mechanism. RCE Countermeasures =================== It is good practice to take preventitive measures against unintentional RCE, which can be used to wreak havoc on vulnboxes and make services go mumble. For this reason, additional security features are enabled via compilation flags: `CFLAGS = -fPIE -fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2` - `-fPIE`: enable position independent executable section - `-fstack-protector-strong`: enable stack canaries in functions with local variables that are prone to overflow - `-D_FORTIFY_SOURCE=2`: gcc buffer overflow detection - `-O2`: enable level 2 of compiler optimizations (required for `_FORITFY_SOURCE`) `LDFLAGS = -Wl,-z,now -Wl,-z,relro` - `-Wl,-z,now`: tell dynamic linker to resolve symbols ASAP instead of lazy loading - `-Wl,-z,relro`: tell dynamic linker to make `got` section read-only after resolving symbols Checker ======= The checker is **not easily identifiable by the examination of network traffic**, because it uses the regular user interface and chooses random input in a way that appears human. This prevents players from only enabling certain service functions when the checker is detected. The checker **uses unusual, incorrect or pseudomalicious input to detect network filters** by replicating the same type of data sent in an exploit (negative chars) in other context, which avoids filtering. The checker **tests processing of many different valid and invalid inputs** to verify that the service can properly distinguish the two and verifies input as it should. Vuln 1: Value Collision ======================= Discovery --------- This vulnerability can be found by reading the source code. The utility function `void freadstr(FILE *f, char **dst)` reads a null-terminated string from the given file, allocates space for it and saves the pointer to its contents in `dst`. ```C void freadstr(FILE *f, char **dst) { size_t start, len; char c; start = ftell(f); for (len = 0; (c = fgetc(f)) != EOF && c; len++); fseek(f, start, SEEK_SET); *dst = checkp(calloc(1, len + 1)); fread(*dst, len, 1, f); fgetc(f); } ``` To determine whether the end-of-file was reached, the return value of `int fgetc(FILE *f)` is compared to the constant `EOF`, which has a value of `-1`. The problem lies in the fact that this comparison is done following a demotion of the return value to `char` through the assignment, and a subsequent promotion to `int`, which results in an arithmetic extension. As a result, a char with unsigned value `255` is cast to char (`0xff`) and then promoted to `-1` (`0xffffffff`). Since this value corresponds with `EOF`, it prevents the function from reading the complete string. This allows an attacker to cleverly truncate a string before it has ended to manipulate the content of strings which follow it. In this case, the model name is saved before the model hash in the information file. By adding a `0xff` to our uploaded model's name, we can control what value is loaded from the file for the model's hash. Since a `search last` will use the previously loaded models hash to find the file via prefix match, any files uploaded by unregistered users can be accessed by choosing this value accordingly. The flag is saved in the model name. Exploiting ---------- 1. Open a session 2. Run `upload` to upload an STL file and specify a model name ending in `0xff` 3. Open a new session 4. Run `search` with the same model name from **step 1** to load the malicious model hash value 5. Run `search last` to use the cached hash prefix to access the target file See the `exploit` method of the checker in `checker/src/checker.py` for an implementation in python. Patching -------- For an example fix, see the unified format patch `src/patches/flagstore1.diff`. Vuln 2: Invalid Format String ============================= Discovery --------- This vulnerability can be found by reading the source code. The utility function `const char* mhash(const char *str, int len)` is used to generate a fixed-width hash from a string input in a static buffer and return a pointer to that buffer. ```C const char* mhash(const char *str, int len) { static char buf[MHASHLEN + 1]; int i, k, v; char c, *bp; if (len == -1) len = strlen(str) + 1; for (v = 0, i = 0; i < len; i++) v += str[i]; srand(v); for (bp = buf, i = 0; i < MHASHLEN / 2; i++) bp += sprintf(bp, "%02x", str[i % len] ^ (rand() % 256)); return buf; } ``` Although the format specifier suggests otherwise, negative values passed to printf will result in more than 2 characters. This can be used to overflow the static buffer. The buffer is zero-initialized and as such is stored in the `.bss` section. If we inspect this section of the compiled binary we can see that it is adjacent to where the `loggedin` variable is stored: ``` 0x000083c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ ; obj.buf.1 (our static buffer) 0x000083d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x000083e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x000083f0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x00008400 0000 0000 0000 0000 0000 0000 0000 0000 ................ ; obj.loggedin ; obj.echo ; obj.resultdir ``` The arrangement of static variables in `.bss` (atleast using gcc) from different compilation units depends on their linking order.. observe the command used to create the binary and the order of static variables from the respective files. ``` build/stldoctor: build/stlfile.o build/util.o main.c | build $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS) ``` The fact that many compiler flags are set to protect against buffer overflows is misleading to the players in this case, because most of them only detect overflows on the *stack*. Since overwriting the global `loggedin` variable gives you permission to use the `list` command and the `resultdir` has not changed (as is usually the case using `auth`), the attacker can now list the hashes of all registered users. The next step is to find a valid preimage for the hashes obtained previously, to log in as them and query information about their files. To calculate the preimage we repeatedly choose a seed for srand. For each seed, we XOR the values encoded in the hex-encoded hash with calls to `rand()`. If after generating each character the sum of the generated values is less than the seed we used, restart. Otherwise, we append some characters to make the sum of the input characters match the seed, such that the seed for srand mhash uses matches the one we chose. The actual value of these 'extra' chars is irrelevant, since mhash only processes the first 20 chars. See `checker/src/revhash/main.c` for an example implementation in C. Exploiting ---------- 1. Open a session 2. Run `search \xff\xff\xff\xff\xff000000000000000` .. this is internally passed to mhash and overflows `loggedin` 3. Run `list` to get list of account hashes 4. For each hash: - Compute hash preimage and authenticate with it - Run `list` to view info See the `exploit` method of the checker in `checker/src/checker.py` for an implementation in python. Patching -------- For an example fix, see the unified format patch `src/patches/flagstore2.diff`.