README.md (8264B)
1STLDoctor 2========= 3 4STLDoctor is a plain-text protocol service, that allows users to upload STL files 5and generate reports that include information on the file's.. 6 7- model name 8- binary header 9- file type (bin vs ascii) 10- file size 11- triangle count 12- bounding box size and position 13- validity 14 15Uploaded models and generated reports are stored in a directory structure. 16 17Unregistered user's files are saved in a collective directory, which 18allows users to search for public models via model name. Registered user's 19uploads are saved to a private directory. This (theoretically) prevents other 20users from accessing their files. 21 22The service is hosted with ncat, one process per client. 23 24Models are periodically checked for removal via their *last modified* date 25and tracked using index files. 26 27For both flagstores the **service returns the flag in plaintext**, which is 28vulnerable to detection by network filters. However, multiple sessions can be 29used to somewhat obfuscate the exploit mechanism. 30 31 32RCE Countermeasures 33=================== 34 35It is good practice to take preventitive measures against unintentional RCE, 36which can be used to wreak havoc on vulnboxes and make services go mumble. 37 38For this reason, additional security features are enabled via compilation flags: 39 40`CFLAGS = -fPIE -fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2` 41 42- `-fPIE`: enable position independent executable section 43- `-fstack-protector-strong`: enable stack canaries in functions with local variables that are prone to overflow 44- `-D_FORTIFY_SOURCE=2`: gcc buffer overflow detection 45- `-O2`: enable level 2 of compiler optimizations (required for `_FORITFY_SOURCE`) 46 47`LDFLAGS = -Wl,-z,now -Wl,-z,relro` 48 49- `-Wl,-z,now`: tell dynamic linker to resolve symbols ASAP instead of lazy loading 50- `-Wl,-z,relro`: tell dynamic linker to make `got` section read-only after resolving symbols 51 52 53Checker 54======= 55 56The checker is **not easily identifiable by the examination of network traffic**, 57because it uses the regular user interface and chooses random input in a way 58that appears human. This prevents players from only enabling certain service 59functions when the checker is detected. 60 61The checker **uses unusual, incorrect or pseudomalicious input to detect network filters** 62by replicating the same type of data sent in an exploit (negative chars) in other 63context, which avoids filtering. 64 65The checker **tests processing of many different valid and invalid inputs** 66to verify that the service can properly distinguish the two and verifies 67input as it should. 68 69 70Vuln 1: Value Collision 71======================= 72 73Discovery 74--------- 75 76This vulnerability can be found by reading the source code. 77 78The utility function `void freadstr(FILE *f, char **dst)` reads a 79null-terminated string from the given file, allocates space for it and saves the 80pointer to its contents in `dst`. 81 82```C 83void 84freadstr(FILE *f, char **dst) 85{ 86 size_t start, len; 87 char c; 88 89 start = ftell(f); 90 for (len = 0; (c = fgetc(f)) != EOF && c; len++); 91 fseek(f, start, SEEK_SET); 92 93 *dst = checkp(calloc(1, len + 1)); 94 fread(*dst, len, 1, f); 95 fgetc(f); 96} 97 98``` 99 100To determine whether the end-of-file was reached, the return value of `int 101fgetc(FILE *f)` is compared to the constant `EOF`, which has a value of `-1`. 102The problem lies in the fact that this comparison is done following a demotion 103of the return value to `char` through the assignment, and a subsequent promotion 104to `int`, which results in an arithmetic extension. As a result, a char with 105unsigned value `255` is cast to char (`0xff`) and then promoted to `-1` 106(`0xffffffff`). Since this value corresponds with `EOF`, it prevents the 107function from reading the complete string. 108 109This allows an attacker to cleverly truncate a string before it has ended to 110manipulate the content of strings which follow it. In this case, the model name 111is saved before the model hash in the information file. By adding a `0xff` to 112our uploaded model's name, we can control what value is loaded from the file for 113the model's hash. Since a `search last` will use the previously loaded models hash 114to find the file via prefix match, any files uploaded by unregistered users can 115be accessed by choosing this value accordingly. 116 117The flag is saved in the model name. 118 119 120Exploiting 121---------- 122 1231. Open a session 1242. Run `upload` to upload an STL file and specify a model name ending in 125 `0xff<target-hash-prefix>` 1263. Open a new session 1274. Run `search` with the same model name from **step 1** to load the malicious 128 model hash value 1295. Run `search last` to use the cached hash prefix to access the target file 130 131See the `exploit` method of the checker in `checker/src/checker.py` 132for an implementation in python. 133 134 135Patching 136-------- 137 138For an example fix, see the unified format patch `src/patches/flagstore1.diff`. 139 140 141Vuln 2: Invalid Format String 142============================= 143 144Discovery 145--------- 146 147This vulnerability can be found by reading the source code. 148 149The utility function `const char* mhash(const char *str, int len)` is used 150to generate a fixed-width hash from a string input in a static buffer 151and return a pointer to that buffer. 152 153```C 154const char* 155mhash(const char *str, int len) 156{ 157 static char buf[MHASHLEN + 1]; 158 int i, k, v; 159 char c, *bp; 160 161 if (len == -1) len = strlen(str) + 1; 162 163 for (v = 0, i = 0; i < len; i++) v += str[i]; 164 srand(v); 165 166 for (bp = buf, i = 0; i < MHASHLEN / 2; i++) 167 bp += sprintf(bp, "%02x", str[i % len] ^ (rand() % 256)); 168 169 return buf; 170} 171``` 172 173Although the format specifier suggests otherwise, negative values 174passed to printf will result in more than 2 characters. This can 175be used to overflow the static buffer. 176 177The buffer is zero-initialized and as such is stored in the `.bss` 178section. If we inspect this section of the compiled binary we can 179see that it is adjacent to where the `loggedin` variable is stored: 180 181``` 1820x000083c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ ; obj.buf.1 (our static buffer) 1830x000083d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 1840x000083e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 1850x000083f0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 1860x00008400 0000 0000 0000 0000 0000 0000 0000 0000 ................ ; obj.loggedin ; obj.echo ; obj.resultdir 187``` 188 189The arrangement of static variables in `.bss` (atleast using gcc) 190from different compilation units depends on their linking order.. 191observe the command used to create the binary and the order of 192static variables from the respective files. 193 194``` 195build/stldoctor: build/stlfile.o build/util.o main.c | build 196 $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS) 197``` 198 199The fact that many compiler flags are set to protect against 200buffer overflows is misleading to the players in this case, 201because most of them only detect overflows on the *stack*. 202 203Since overwriting the global `loggedin` variable gives you 204permission to use the `list` command and the `resultdir` has 205not changed (as is usually the case using `auth`), the attacker 206can now list the hashes of all registered users. 207 208The next step is to find a valid preimage for the hashes obtained 209previously, to log in as them and query information about their files. 210 211To calculate the preimage we repeatedly choose a seed for srand. For 212each seed, we XOR the values encoded in the hex-encoded hash with 213calls to `rand()`. If after generating each character the sum of the 214generated values is less than the seed we used, restart. Otherwise, 215we append some characters to make the sum of the input characters 216match the seed, such that the seed for srand mhash uses matches the 217one we chose. The actual value of these 'extra' chars is irrelevant, 218since mhash only processes the first 20 chars. 219 220See `checker/src/revhash/main.c` for an example implementation in C. 221 222 223Exploiting 224---------- 225 2261. Open a session 2272. Run `search \xff\xff\xff\xff\xff000000000000000` 228 .. this is internally passed to mhash and overflows `loggedin` 2293. Run `list` to get list of account hashes 2304. For each hash: 231 - Compute hash preimage and authenticate with it 232 - Run `list` to view info 233 234 235See the `exploit` method of the checker in `checker/src/checker.py` 236for an implementation in python. 237 238 239Patching 240-------- 241 242For an example fix, see the unified format patch `src/patches/flagstore2.diff`. 243 244