]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/ilm0/hxp36c3.md
ilm0 - second submission deadline + updated readme
[pub/jan/ctf-seminar.git] / writeups / ilm0 / hxp36c3.md
1 # <u>hxp 36C3 CTF</u>
2
3 ## xmas_future
4
5 **category**: rev, (web)
6
7 **description**:
8
9 Most people just give you a present for christmas, hxp gives you a glorious future.
10
11 If you’re confused, simply extract the flag from this 山葵 and you shall understand. :)
12
13 ___
14
15 ### Recon
16
17 As previous experience shows, it is always best to read the description very carefully and look for clues! Of course, the Asian characters stand out: Google Translate quickly tells us that they mean "wasabi" (in Japanese), the Japanese hot sauce/paste. If we search for "wasabi web framework" on Google, the first site is a Kotlin based HTTP framework [https://github.com/wasabifx/wasabi], for now that does not seem particularly interesting or valuable. On the other hand, the second page takes us to [https://github.com/danleh/wasabi], an analysis framework for the inspection of WebAssembly files. This sounds more relevant as we are in fact given a .wasm file. More on this later. 
18
19 We are given the skeleton of a php web server. Among others we are presented with:
20
21 - a JavaScript
22
23 - a WebAssembly file
24
25 - and a html file.
26
27 With the provided shell script (run.sh) we are quickly up and running our PHP based web server. 
28
29 ![page.png](hxp36c3/page.png)
30
31 We have a funky Matrix inspired flag checker tool. As the page advertises, it is supposed to be running Rust and WebAssembly. Let's check the underlying structure and how all this comes together!
32
33 The interesting part of the HTML source looks like this: 
34
35 ```html
36  <div id="container">
37       <div id="inner-container">
38         <h1>High-speed flag checking</h1>
39         <h3>Powered by Rust <img src="./ferris.svg" /> and WASM <img src="./wasm.svg" /></h3>
40
41         <form id="form">
42           <input type="text" id="flag" placeholder="hxp{...}" size="50" /><br />
43           <button type="submit">Check</button>
44         </form>
45       </div>
46     </div>
47
48     <script type="module">
49       import init, { check } from './hxp2019.js';
50
51       async function run() {
52         await init();
53
54         document.getElementById('form').addEventListener('submit', function(e) {
55           e.preventDefault();
56           const flag = document.getElementById('flag').value;
57           if (check(flag)) {
58             alert('Yes, you found the flag!');
59           } else {
60             alert('Nope.');
61           }
62         });
63       }
64
65       run();
66     </script>
67 ```
68
69 We see that there's a button submitting information that is checked by an other JavaScript function defined in the file `hxp2019.js` (reformatted for easier reading):
70
71 ```javascript
72 let wasm;
73
74 let WASM_VECTOR_LEN = 0;
75
76 let cachegetUint8Memory0 = null;
77 function getUint8Memory0() 
78 {
79     if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) 
80     {
81         cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
82     }
83     
84     return cachegetUint8Memory0;
85 }
86
87 let cachedTextEncoder = new TextEncoder('utf-8');
88
89 const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
90     ? function (arg, view) 
91 {
92     return cachedTextEncoder.encodeInto(arg, view);
93 }
94 : function (arg, view) 
95 {
96     const buf = cachedTextEncoder.encode(arg);
97     view.set(buf);
98     return {
99         read: arg.length,
100         written: buf.length
101 };
102 });
103
104 function passStringToWasm0(arg, malloc, realloc)
105 {
106
107 if (realloc === undefined) 
108 {
109     const buf = cachedTextEncoder.encode(arg);
110     const ptr = malloc(buf.length);
111     getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
112     WASM_VECTOR_LEN = buf.length;
113     return ptr;
114 }
115
116 let len = arg.length;
117 let ptr = malloc(len);
118
119 const mem = getUint8Memory0();
120
121 let offset = 0;
122
123 for (; offset < len; offset++)
124 {
125     const code = arg.charCodeAt(offset);
126     if (code > 0x7F) break;
127     mem[ptr + offset] = code;
128 }
129
130 if (offset !== len) 
131 {
132     if (offset !== 0)
133     {
134         arg = arg.slice(offset);
135     }
136     ptr = realloc(ptr, len, len = offset + arg.length * 3);
137     const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
138     const ret = encodeString(arg, view);
139
140     offset += ret.written;
141 }
142
143 WASM_VECTOR_LEN = offset;
144 return ptr;
145
146 }
147 /**
148
149 * @param {string} pwd
150 * @returns {bool}
151   */
152 export function check(pwd)
153 {
154     var ptr0 = passStringToWasm0(pwd, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
155     var len0 = WASM_VECTOR_LEN;
156     var ret = wasm.check(ptr0, len0);
157     return ret !== 0;
158 }
159
160 function init(module)
161 {
162     if (typeof module === 'undefined')
163     {
164         module = import.meta.url.replace(/\.js$/, '_bg.wasm');
165     }
166     let result;
167     const imports = {};
168
169 if ((typeof URL === 'function' && module instanceof URL) || typeof module === 'string' || (typeof Request === 'function' && module instanceof Request))
170 {
171
172     const response = fetch(module);
173     if (typeof WebAssembly.instantiateStreaming === 'function') {
174         result = WebAssembly.instantiateStreaming(response, imports)
175         .catch(e => {
176             return response
177             .then(r => {
178                 if (r.headers.get('Content-Type') != 'application/wasm') {
179                     console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
180                     return r.arrayBuffer();
181                 } else {
182                     throw e;
183                 }
184             })
185             .then(bytes => WebAssembly.instantiate(bytes, imports));
186         });
187     } else
188     {
189         result = response
190         .then(r => r.arrayBuffer())
191         .then(bytes => WebAssembly.instantiate(bytes, imports));
192     }
193 } else 
194 {
195
196     result = WebAssembly.instantiate(module, imports)
197     .then(result => {
198         if (result instanceof WebAssembly.Instance) {
199             return { instance: result, module };
200         } else {
201             return result;
202         }
203     });
204 }
205 return result.then(({instance, module}) => {
206     wasm = instance.exports;
207     init.__wbindgen_wasm_module = module;
208
209     return wasm;
210 });
211
212 }
213
214 export default init; 
215 ```
216
217 If we take a closer look at the definition of the function "check", we see that it passes a string to the function "passStringToWasm0", then it passes this processed string to the .wasm binary. The string-processing function takes a string and copies it to a prepared area of memory. Probably this area is used for communication between the binary and the JavaScript function.
218
219 Let's take a look at the binary with our new tool! I set up wasabi and completed all the steps on their website, but as to how to actually use their software, I found no description. After trying around for a few hours and finally giving up, I moved on to wabt[https://github.com/WebAssembly/wabt].
220
221 I managed to disassemble the .wasm file with the wabt module wasm2c. It returned me a very strange looking 6000 lines long C-file and a short but unusual header file. After some searching around I have found the definition of the check function called by the JavaScript file:
222
223 ```c
224 static u32 check(u32 p0, u32 p1) {
225   u32 l2 = 0, l3 = 0;
226   FUNC_PROLOGUE;
227   u32 i0, i1, i2, i3;
228   i0 = g0;
229   i1 = 32u;
230   i0 -= i1;
231   l2 = i0;
232   g0 = i0;
233   i0 = l2;
234   i1 = 16u;
235   i0 += i1;
236   i1 = p0;
237   i2 = p1;
238   i3 = p1;
239   alloc__vec__Vec_T___from_raw_parts__h6aeafb6342a4f3ed(i0, i1, i2, i3);
240   i0 = l2;
241   i1 = 8u;
242   i0 += i1;
243   i1 = l2;
244   i2 = 16u;
245   i1 += i2;
246   alloc__vec__Vec_T___into_boxed_slice__h0afc7190c9c73a6d(i0, i1);
247   i0 = l2;
248   i0 = i32_load((&memory), (u64)(i0 + 8));
249   l3 = i0;
250   i1 = l2;
251   i1 = i32_load((&memory), (u64)(i1 + 12));
252   p1 = i1;
253   i0 = hxp2019__check__h578f31d490e10a31(i0, i1);
254   p0 = i0;
255   i0 = p1;
256   i0 = !(i0);
257   if (i0) {goto B0;}
258   i0 = l3;
259   i1 = p1;
260   i2 = 1u;
261   __rust_dealloc(i0, i1, i2);
262   B0:;
263   i0 = l2;
264   i1 = 32u;
265   i0 += i1;
266   g0 = i0;
267   i0 = p0;
268   FUNC_EPILOGUE;
269   return i0;
270 }
271 ```
272
273 I was not able to guess what the code does exactly, but there is a suspicious looking function being called: `hxp2019__check__h578f31d490e10a31`. It's definition is similarly fuzzy, I did not include it due to its size 300+ lines. It can be found in the folder hxp36c3 under the filename: `hxp2019__check__h578f31d490e10a31.c`.
274
275 ### Technical details
276
277 WebAssembly was a new technology for me:
278
279 > WebAssembly is a new type of code that can be run in modern web browsers and provides new features and major gains in performance. It is not primarily intended to be written by hand, rather it is designed to be an effective compilation target for low-level source languages like C,  C++, Rust, etc.
280
281 [https://developer.mozilla.org/en-US/docs/WebAssembly/Concepts]
282
283 So it is a binary, that either resides server- or client-side, and provides "near native speed" functionality to JavaScript applications. After some research on the security implications of WebAssembly, I have found the following potential issues:
284
285 The sandboxed environment it runs in has complete control over what the binary can access.
286
287 > There currently is no way to do integrity checking on Wasm applications. This means that there is no process for verifying that a Wasm application has not been tampered with. 
288
289 [https://www.forcepoint.com/blog/x-labs/webassembly-potentials-and-pitfalls]
290
291 The lack of integrity checking could open up a lot of ways for various MITM attacks.
292
293 ### Lessons (to be) learned
294
295 - Tools for unconventional SQL injection
296
297 ## bacon
298
299 **category**: crypto
300
301 **description**:
302
303 Please hack.
304
305 ___
306
307 ### Recon
308
309 We get a netcat service, we connect to it, we are presented with a 12 character hex value. After it, we have approximately 2 minutes to provide the correct "answer".
310
311 We are also presented with python file that :
312
313 **vuln.py**:
314
315 ```python
316 import os, signal
317
318 def Speck(key, blk):
319     assert tuple(map(len, (key, blk))) == (9,6)
320     S = lambda j,v: (v << j | (v&0xffffff) >> 24-j)
321     ws = blk[:3],blk[3:], key[:3],key[3:6],key[6:]
322     x,y, l1,l0,k0 = (int.from_bytes(w,'big') for w in ws)
323     l, k = [l0,l1], [k0]
324     for i in range(21):
325         l.append(S(16,l[i]) + k[i] ^ i)
326         k.append(S( 3,k[i])        ^ l[-1])
327     for i in range(22):
328         x = S(16,x) + y ^ k[i]
329         y = S( 3,y)     ^ x
330     x,y = (z&0xffffff for z in (x,y))
331     return b''.join(z.to_bytes(3,'big') for z in (x,y))
332
333 # did I implement this correctly?
334 assert Speck(*map(bytes.fromhex, ('1211100a0908020100', '20796c6c6172'))) == b'\xc0\x49\xa5\x38\x5a\xdc'
335
336 def H(m):
337     s = bytes(6)
338     v = m + bytes(-len(m) % 9) + len(m).to_bytes(9,'big')
339     for i in range(0,len(v),9):
340         s = Speck(v[i:i+9], s)
341     return s
342
343
344 signal.alarm(100)
345
346 h = os.urandom(6)
347 print(h.hex())
348
349 s = bytes.fromhex(input())
350 if H(s) == h:
351     print('The flag is: {}'.format(open('flag.txt').read().strip()))
352 else:
353     print('Nope.')
354 ```
355
356 We recognize some kind of unusual encryption scheme, which is unidirectional, only the way of encryption is provided to us. The main function used for encryption is called "Speck", which is equivalent of bacon in German. This seems interesting! After some googling around we find the Speck cipher (see TD).
357
358 ```python
359 def Speck(key, blk):
360     assert tuple(map(len, (key, blk))) == (9,6)
361     S = lambda j,v: (v << j | (v&0xffffff) >> 24-j)
362     ws = blk[:3],blk[3:], key[:3],key[3:6],key[6:]
363     x,y, l1,l0,k0 = (int.from_bytes(w,'big') for w in ws)
364     l, k = [l0,l1], [k0]
365     for i in range(21):
366         l.append(S(16,l[i]) + k[i] ^ i)
367         k.append(S( 3,k[i])        ^ l[-1])
368     for i in range(22):
369         x = S(16,x) + y ^ k[i]
370         y = S( 3,y)     ^ x
371     x,y = (z&0xffffff for z in (x,y))
372     return b''.join(z.to_bytes(3,'big') for z in (x,y))
373 ```
374
375 Here is a variant that's a bit easier to read, let's compare!
376 [https://en.wikipedia.org/wiki/Speck_(cipher)]:
377
378 ```c++
379 #define ROR(x, r) ((x >> r) | (x << (64 - r)))
380 #define ROL(x, r) ((x << r) | (x >> (64 - r)))
381 #define R(x, y, k) (x = ROR(x, 8), x += y, x ^= k, y = ROL(y, 3), y ^= x)
382 #define ROUNDS 32
383
384 void encrypt(uint64_t ct[2],
385              uint64_t const pt[2],            
386              uint64_t const K[2])
387 {
388    uint64_t y = pt[0], x = pt[1], b = K[0], a = K[1];
389
390    R(x, y, b);
391    for (int i = 0; i < ROUNDS - 1; i++) {
392       R(a, b, i);
393       R(x, y, b);
394    }
395
396    ct[0] = y;
397    ct[1] = x;
398 }
399 ```
400
401 The first thing that stands out, is the fixed number of rounds in our variant. 22 rounds mean that our key size is 64 or 72 bits. This in turn means that our block size is 32 or 48 bits.
402
403 The assignment `s = bytes(6)` from the function "H" reveals us that we have to do with the variant where the block size is 48 bits.
404
405 `assert tuple(map(len, (key, blk))) == (9,6)` This assertion makes sure that we are using the correct key and block sizes.
406
407 The main loop seems to behave according to the cipher specification and the cor of the example C code:
408
409 ```python
410 for i in range(22):
411         x = S(16,x) + y ^ k[i]
412         y = S( 3,y)     ^ x
413 ```
414
415 The key is XORed into the left word (y) then the left word is XORed into the right word (x).
416
417 I have felt here, that my cryptanalysis skills are not yet sufficient enough, so I looked at the other function, maybe I can find some way to brute force Speck.
418
419 ```python
420 def H(m):
421     s = bytes(6)
422     v = m + bytes(-len(m) % 9) + len(m).to_bytes(9,'big')
423     for i in range(0,len(v),9):
424         s = Speck(v[i:i+9], s)
425     return s
426 ```
427
428 Here I made the revelation that our Speck function is actually only one "step" of the calculation! Speck is a block cipher:
429
430 ```python
431 for i in range(0,len(v),9):
432         s = Speck(v[i:i+9], s)
433 ```
434
435 this loop goes through the message block-by-block. So actually `H` is an implementation of the complete 72/48 Speck encryption scheme. Now we know what precisely the code does, we can try to brute force it!
436
437 After some trying around, I realised that there is no chance, that consumer-grade hardware is able to brute-force this encryption under 2 minutes, not even 2 hours.
438
439 ### Technical details
440
441 > Speck is a family of lightweight block ciphers publicly released by the National Security Agency (NSA) in June 2013. [...] a cipher that would operate well on a diverse collection of Internet of Things devices while maintaining an acceptable level of security.
442
443 [https://en.wikipedia.org/wiki/Speck_(cipher)]
444
445 I think the effectiveness of the cipher comes from the fact that is uses lower key and block sizes when compared to other ciphers, such as AES.
446
447 > As of 2018, no successful attack on full-round Speck of any variant is known.  [...] they [the NSA] found differential attacks to be the limiting attacks, i.e. the type of attack that makes it through the most rounds; they then set the number of rounds to leave a security margin similar to AES-128's at approximately 30%.
448
449 [https://en.wikipedia.org/wiki/Speck_(cipher)]
450
451 So, according to the NSA, the Speck cipher is as resilient against differential attacks as AES-128.
452
453 ### Lessons (to be) learned
454
455 - Applied cryptanalysis?
456 - Brute forcing through the use of clusters?
457
458 # file_magician
459
460 **category**: web
461
462 **description**:
463
464 Finally (again), a minimalistic, open-source file hosting solution.
465
466 ___
467
468 ### Recon
469
470 We get a dockerfile, with which we try to set up as a webserver. No matter how we try, the build command returns an error because the file `flag.txt` is (understandably) not found. After some trying around and understanding the problem, I was able to start the container just by creating the mentioned file in the directory.
471
472 The service provided is a barebones website where we can upload small files, I have noticed (initially) that files over 500kB get rejected. The accepted files are then assigned links with their serial number. The file type is also displayed.
473
474 ![page2.png](hxp36c3/page2.png)
475
476 We grab the webserver banner with `nmap`:
477
478 ```
479 8000/tcp open  http            nginx 1.14.2
480 |_http-server-header: nginx/1.14.2
481 ```
482
483 We have an nginx server running!
484
485 In the provided files, apart from the Dockerfile we have a .php index file too,
486 **index.php**:
487
488 ```php
489 <?php
490 error_reporting(0);
491 ini_set('display_errors', 0);
492 ini_set('display_startup_errors', 0);
493 session_start();
494
495 if( ! isset($_SESSION['id'])) {
496     $_SESSION['id'] = bin2hex(random_bytes(32));
497 }
498
499 $d = '/var/www/html/files/'.$_SESSION['id'] . '/';
500 @mkdir($d, 0700, TRUE);
501 chdir($d) || die('chdir');
502
503 $db = new PDO('sqlite:' . $d . 'db.sqlite3');
504 $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
505 $db->exec('CREATE TABLE IF NOT EXISTS upload(id INTEGER PRIMARY KEY, info TEXT);');
506
507 if (isset($_FILES['file']) && $_FILES['file']['size'] < 10*1024 ){
508     $s = "INSERT INTO upload(info) VALUES ('" .(new finfo)->file($_FILES['file']['tmp_name']). " ');";
509     $db->exec($s);
510     move_uploaded_file( $_FILES['file']['tmp_name'], $d . $db->lastInsertId()) || die('move_upload_file');
511 }
512
513 $uploads = [];
514 $sql = 'SELECT * FROM upload';
515 foreach ($db->query($sql) as $row) {
516     $uploads[] = [$row['id'], $row['info']];
517 }
518 ?>
519 ```
520
521 The server is assigning a unique PHP session variable to each user, files are identified by being placed in a folder with the respective session id. The server uses a sqlite3 database and the connection is handled by the PHP engine. We also discover, that the file size limit is not 500kB but only 10kB! `$_FILES['file']['size'] < 10*1024`
522
523 The queries used seem pretty basic, for now, I do not see any countermeasures against sql-injection. This command seems especially vulnerable:
524
525 ```php
526 "INSERT INTO upload(info) VALUES ('" .(new finfo)->file($_FILES['file']['tmp_name']). " ');"
527 ```
528
529 After some reading around online, it turns out, file upload vulnerabilities are not performed through the filename(what I would have expected), but through image metadata. The sanitization of said metadata makes this possible.
530
531 Apparently, traditional tools, like `sqlmap` are not capable of such attacks.
532
533 Unfortunately, I could not find any tools online, which are able to perform an sql injection attack from such an unusual attack vector.
534
535 ### Technical details
536
537 I have found an explanation for the vulnerability, it is an RCE:
538
539 > I discovered a technique to hide php code in the EXIF data of the image file. When the image is loaded by the page, the php tags located in the headers are interpreted as php code and run by the server. 
540
541 [https://spencerdodd.github.io/2017/03/05/dvwa_file_upload/]
542
543 When PHP handles images, it is possible to escape processing from inside EXIF headers and run instructions. The vulnerability lies in the fact, that PHP tags are recognized in areas where it should not be possible, such as image metadata.
544
545 ### Lessons (to be) learned
546
547 - SQL injection is not only possible through GET and POST parameters
548 - The use of more advanced SQL injection tools