keac's Bolg.

SSRF服务器端请求伪造

字数统计: 2.2k阅读时长: 8 min
2020/02/27 Share

定义

SSRF(Server-side Request Forgery,服务器端请求伪造)

是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统。

攻击利用了可访问Web服务器(A)的特定功能构造恶意payload

攻击者在访问A时,利用A的特定功能构造特殊payload,由A发起对内部网络中系统B(内网隔离,外部不可访问)的请求,从而获取敏感信息。

此时A被作为中间人(跳板)进行利用。

原因

SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。

简单来说,比如有个网站 www.xxx.com ,他提供了一个服务可以远程下载图片 www.xxx.com/a.php?url=http://www.xxx.com/a.jpg

网站接受到url => 解析这个url => 校验是否合法 => 获取回传数据 =>接受显示

那么产生SSRF漏洞的环节在哪里呢?目标网站接受请求后在服务器端验证请求是否合法

产生的原因:服务器端的验证并没有对其请求获取图片的参数(url=)做出严格的过滤以及限制,导致可以从其他服务器的获取一定量的数据

比如我们把 http://www.xxx.com/a.jpg 换为与该服务器相连的内网服务器地址会产生什么效果呢?

如果存在该内网地址就会返回1xx 2xx 之类的状态码,不存在就会其他的状态码

终极简析

SSRF漏洞就是通过篡改获取资源的请求发送给服务器,但是服务器并没有发现在这个请求是合法的,然后服务器以他的身份来访问其他服务器的资源。

举个例子

1
2
3
4
5
6
7
8
9
10
<?php
// 漏洞代码ssrf.php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET['url']);
#curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
#curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_exec($ch);
curl_close($ch);
?>

这段代码没有对请求目标进行任何校验,我们可以直接探测信息

curl -v 'http://a.com/ssrf.php?url=dict://172.0.0.1:22/info'

同时 file_get_contents()fopen()fsocksopen() 均可能造成SSRF漏洞

SSRF能干什么

  1. 内外网的端口和服务扫描

  2. 主机本地敏感数据的读取

  3. 内外网主机应用程序漏洞的利用

  4. 内外网Web站点漏洞的利用

  5. ……

漏洞寻找点(应用场景)

  • 从指定URL地址获取网页文本内容 (网站翻译)
  • 适配手机屏幕大小,通过URL地址进行图片转码 (转码服务)
  • 对用户提供的远端图片添加水印 (在线编辑器、加水印功能)
  • 下载用户提供的文件 (下载用户提供的图片保存到云端服务器)
  • 判断网站是否存活,状态码等 (广告平台获取广告主网站信息)
  • 获取网站的title等功能 (分享网站URL时,输出网站标题)
  • 其他加载URL的功能
  • XXE漏洞点
  • 未公开的api实现以及其他调用URL的功能
  • 从URL关键字中寻找
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    share
    wap
    url
    link
    src
    source
    target
    u
    3g
    display
    sourceURl
    imageURL
    domain
    ...

攻击方式

更改IP地址写法

可以通过进制绕过:
192.168.0.1的各种进制

  • 8进制格式:0300.0250.0.1
  • 16进制格式:0xC0.0xA8.0.1
  • 10进制整数格式:3232235521
  • 16进制整数格式:0xC0A80001
  • 利用解析URL所出现的问题

如/api/get_content/?url=用户输入的url(不可信)

如果用户直接输入了一个内网网址如192.168.0.1 而你也不采取措施判断的话 … 漏洞就这样发生了

URL重定向

你可能会对用户输入的url使用ip正则校验的方式去判断是不是内网ip对不对?就像下面这样子.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 内网ip黑名单  
// 常见内网网段:192.168.0.0/16; 10.0.0.0/8; 127.0.0.0/8; 172.16.0.0/12
var internal_ip_blacklist = {
'reg_ip_local': /^127.*$/,
'reg_ip_10': /^10\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
'reg_ip_192': /^192\.168\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
'reg_ip_172': /^172\.(1[6-9]|2[0-9]|3[01])\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
}
var http = require('http')
var options = {
host: 'tmxk.org',
port: 80,
path: '/',
method: 'GET'
}

var req = http.request(options, function(res) {
res.on('data', () => { console.log(111) })
})

req.end(function(xxx) {
console.log(req.socket.remoteAddress)
req.abort()
})

那你可能知道个好东西了 谷歌短网址
轻易地重定向到一个内网网址 就这样绕过了你的检验
而且这种重定向可以做多次的 比如这个短网址就可以在上述短网址的基础上再做一次(网络慢的话可能打不开)

另外,在网络上存在一个很神奇的服务,http://xip.io当我们访问这个网站的子域名的时候,例如192.168.0.1.xip.io,就会自动重定向到192.168.0.1。

通过各种非HTTP协议

File协议:File协议主要用于访问本地计算机中的文件
使用file协议可以避免服务端程序对于所访问的IP进行的过滤。

例如可利用file://qq.com/../../etc/hosts 获取文件

DNS Rebinding

对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就干掉该请求并返回

但是在整个过程中,一般会有两次请求
第一次去请求DNS服务进行域名解析
第二次服务端去请求用户输入的URL

这两次请求之间存在一个时间查,利用这个时间差,我们可以进行DNS重绑定攻击,说白了就是前一秒我还是外网,通过了你的代码check,下一秒域名的真正ip就变成了内网,不幸的是,这时候你的服务端发送了真正的请求

要完成DNS重绑定攻击,需要一个域名,并且将这个域名的解析指定到自己的DNS Server,在可控的DNS Server上编写解析服务,设置TTL时间为0。这样就可以进行攻击了

具体可以参考这篇文章 dns rebinding bypass

防御措施

限制协议

  • 禁用不需要的协议,仅仅允许http和https请求,可以防止类似于file://, gopher://, ftp:// 等引起的问题。
  • 服务端需要认证交互,禁止非正常用户访问服务;
  • 过滤输入信息,永远不要相信用户的输入,判断用户的输入是否是一个合理的URL地址
  • 过滤返回信息,验证远程服务器对请求的响应是比较容易的方法,如果web应用是去获取某一种类型的文件。那么在把返* 回结果展示给用户之前先验证返回的信息是否符合标准。
  • 统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。
  • 禁止30x跳转
  • 设置URL白名单或限制内网IP

目标域名解析ip

  • 不考虑dns rebinding的话,nodejs提供的域名解析服务足够了即dns.resolve()
  • 处理dns rebinding的话, 则需要在请求的时候同步检测ip,也就是服务端真正发送请求的同时检测ip,不可如上文中dns检测一次请求,真正请求是第二次请求,让其利用时间差攻击
  • 幸运的是,nodejs中有个字段req.socket.remoteAddress可以判断请求的远端地址的真实ip,这种情况下神马redirect都不需要考虑了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 代码如下 判断req.socket.remoteAddress这个ip值是不是在黑名单就好了
var http = require('http')
var options = {
host: 'tmxk.org',
port: 80,
path: '/',
method: 'GET'
}

var req = http.request(options, function(res) {
res.on('data', () => { console.log(111) })
})

req.end(function(xxx) {
console.log(req.socket.remoteAddress)
req.abort()
})

参考资料

SSRF Tips , safebuff , 2016-07-03

SSRF绕过方法总结 , 瓦都剋 , 2017-11-22

CATALOG
  1. 1. 定义
  2. 2. 原因
    1. 2.1. 终极简析
    2. 2.2. 举个例子
  3. 3. SSRF能干什么
  4. 4. 漏洞寻找点(应用场景)
  5. 5. 攻击方式
    1. 5.1. 更改IP地址写法
    2. 5.2. 利用解析URL所出现的问题
    3. 5.3. URL重定向
    4. 5.4. 通过各种非HTTP协议
    5. 5.5. DNS Rebinding
  6. 6. 防御措施
    1. 6.1. 限制协议
    2. 6.2. 目标域名解析ip
  7. 7. 参考资料