Chosen1|安全

PHP代码审计之死亡exit绕过

参考p神的博客
https://www.leavesongs.com/PENETRATION/php-filter-magic.html

引入

e.g.有这样一段代码,我们该如何绕过
<?php

$filename = $_GET['filename'];
$content = $_GET['content'];
file_put_contents($filename,'<?phpexit();'.$content);

还有三种不同的格式,但是原理其实都差不多
file_put_contents($filename,"<?php exit();".$content);

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

file_put_contents($filename,$content . "\nxxxxxx");

php://filter函数

php://filter 最常见的出现地方实在XXE,最主要是因为PHP属于标签的脚本语言,且语法与XML相符,在解析XML时有可能会被误以为是XML,但是代码中也可能含有特殊字符,与XML语法冲突,导致们在读取HTML、PHP等文件时可能会抛出此类错误parser error : StartTag: invalid element name
那么就会引入了我们的php://filter,用它来编码一遍我们带有特殊字符等容易引发冲突的代码,
php://filter 是 PHP 中的一个封装器(wrapper),用于在读写流时应用各种过滤器。它通常用于处理输入和输出的数据,允许在读取或写入数据之前对其进行过滤或转换。

它可以用来:数据过滤和转换;输入过滤;输出过滤;处理不同数据流;以及代码简洁性;
e.g.
<?php
// 通过 php://filter 读取 URL 中的数据并应用 base64 解码过滤器
$filteredData = file_get_contents('php://filter/read=convert.base64-decode/resource=https://example.com/data');

// 处理过滤后的数据
echo $filteredData;
?>
当然也可以用来编码
php://filter/read=convert.base64-encode/resource=./xxe.php

通过一道题来让我们了解

使用编码不光可以帮助我们获取文件,也可以帮我们去除一些“不必要的麻烦”。

记得前段时间三个白帽有个比赛,其中有一部分代码大概类似于以下:
<?php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);

$content在开头增加了exit过程,导致即使我们成功写入一句话,也执行不了(这个过程在实战中十分常见,通常出现在缓存、配置文件等等地方,不允许用户直接访问的文件,都会被加上if(!defined(xxx))exit;之类的限制)。那么这种情况下,如何绕过这个“死亡exit”?

第一种方法(利用base64)

首先说一下总的思路
这里有一些前提知识需要我们了解:
1.什么是base64编码,他的编码范围是什么
2.base64解码时几个字节为一组
Base64编码是一种用于将二进制数据转换成ASCII字符集可打印字符的编码方式。它将每3个字节的二进制数据编码为4个ASCII字符。Base64编码经常用于在文本环境中传输二进制数据,例如在电子邮件中传输二进制附件或在网页中嵌入图像。

Base64编码的字符集包括:
- 26个大写字母(A-Z)
- 26个小写字母(a-z)
- 10个数字(0-9)
- "+" 和 "/" 两个特殊字符

此外,Base64编码通常使用等号("=")作为填充字符,以确保编码后的长度是4的倍数。

编码过程如下:
1. 将输入数据划分为3字节的块。
2. 将每个3字节的块转换为4个Base64字符。
3. 如果输入数据的长度不是3的倍数,使用等号进行填充。

Base64编码的编码范围是由这64个字符组成的,其中每个字符表示6个比特。这意味着Base64编码的编码长度总是4的倍数,而原始数据的长度则可以是任意的。
所以说那两个问题的答案是
编码范围是:26个大写字母(A-Z),26个小写字母(a-z),10个数字(0-9),"+" 和 "/" 两个特殊字符;这里我们可以想到,不包括$content中的< ? ; >,这不在base64编码范围内,所以会被编码为phpexit这七个字符;

但是,我们又从上面一段文字知道base64会以4个字节为一组,为了不影响我们后面的shell,所以我们需要手动在我们的shell前给他补上一个base64可以解码的字符

就用上面的那道题来说,我们的大致思路为,我们在filename位置写入base64解码,后面的txt传入,已经使用base64编码好的shell,注意记得在编码好的前面加上一个base64可以解码的字符我这里选择为z,这样一来前面的phpexit和我们加上的z凑为八个字节,当他解码时就不会使用我们后面shell中的代码进行向前拼接,最后就可以绕过这个死亡exit.

实操

首先我们把准备工作做好:
把shell使用base64编好
<?php phpinfo();  ===>  PD9waHAgcGhwaW5mbygpOw==

并在网站根目录下创建php代码
<?php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);
image-20231127202837779
这里我们可以看出来我们需要使用post传两个参数
一个是txt,一个是filename
txt为: zPD9waHAgcGhwaW5mbygpOw==
filename为: php://filter/convert.base64-decode/resource=shell.php
可以从生成的shell.php发现,前面的exit已经没有了
�^�+s<?php phpinfo();

我们访问一下shell.php看看有没有phpinfo信息
http://192.168.1.187/shell.php
成功

小结

我们可以看出来,这一种方法我们是通过文件名,使用了php://filter这种封装器,并使用了convert.base54-decode过滤器,用来解码shell.php文件中的base64数据,当然其中的< ? ; >,因为解码时,仅将合法的字符组成字符串进行解析,遇到不包含在64个可打印的字符是会自动跳过,所以我们成功让<?php exit(); ?>失效

第二种方法(利用字符串操作)

一个更简单的方法
这里需要引入一个php函数:strip_tags

strip_tags 是 PHP 中的一个函数,用于从字符串中去除HTML和PHP标签。该函数的主要目的是清除用户输入中可能包含的HTML或PHP标记,以防止潜在的安全问题或不良影响。

函数使用方法:
string strip_tags ( string $str [, string $allowable_tags ] )

参数:
- $str: 要处理的字符串,通常是用户输入的文本。
- $allowable_tags(可选):允许保留的标签,其他标签将被移除。这是一个逗号分隔的字符串,包含允许的标签名称。

返回值:
- 返回去除标签后的字符串。

e.g.
$input = "<p>This is a <b>paragraph</b>.</p>";
$output = strip_tags($input);
echo $output; // 输出:This is a paragraph.

在上述示例中,strip_tags 函数将 <b> 和 </b> 标签从输入字符串中移除,只保留文本内容。这有助于防止用户在输入中包含恶意的HTML或PHP代码,从而提高应用程序的安全性。
我们上一种方法说过php代码和xml很像,我们观察<?php exit(); ?> 和 xml的标准格式< >,就差不多,所以我们可以使用strip_tags来去除<?php exit(); ?>这段代码,但是我们的shell也是<?php ?>标签怎么办,我们通过php手册是可以看见php://filter是支持多过滤器的,那么我们的思路就有了,我们先去除<?php exit(); ?>,再进行base64解码

实操

我们的源码还是一样
<?php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);
我们修改我们通过post上传的两个参数
txt:
<?php phpinfo();  ===>  PD9waHAgcGhwaW5mbygpOw==

filename:
php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php
访问我们的shell,成功绕过

总结

这里的方法为使用了string.strip_tags用来去掉<?php exit(); ?>,再用convert.base64-decode/resource=shell.php来解码shell.php中使用base64编码的文件,文件中就只剩<?php phpinfo();了

第三种方法(rot13编码)

原理和上面类似,核心是将“死亡exit”去除。<?php exit; ?>在经过rot13编码后会变成<?cuc rkvg; ?>,在PHP不开启short_open_tag时,php不认识这个字符串,当然也就不会执行了

实操

txt:
txt=<?cuc cucvasb(); ?>

filename:
filename=php://filter/write=string.rot13/resource=shell.php
这种方法需要确认php.ini中short_open_tag为关闭状态

总结

和第二种方法差不多,区别在于,有可能需要修改php配置文件,且shell需要编码

Views: 43

退出移动版