2020-06-07
1k
PHP代码审计学习——chinaz
0x01 审计入口
首先chinaz没有后台数据库,并且是一个单入口类,不同于上一篇Yixuncms是一个MVC的架构模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php require_once("library/common.php"); require_once("library/view.php"); $view_class = new View(); $data = array(); if (isset($_GET['page'])) { $data['page'] = filter($_GET['page']); } else{ $data['page'] = 'js'; } $view_class->echoContent($data['page'], $data); ?>
|
在index.php文件中可以看到首先包含了公共文件和渲染文件进来,然后new了一个View类的对象。
接着检测是否以get方式传入page变量,如果有则调用filter方法过滤变量中的数据,最后再调用echoContent
方法加载页面。
那么网站的目录结构也就大概清楚了,library
中包含公共文件和渲染文件,logs
中记录日志,static
是一些静态资源文件,views
类似视图文件,文件中包含了HTML代码,各个模块通过action.php
POST参数跳转到相应功能模块的PHP文件。
0x02 工具扫描
首先上工具,用Seay和rips扫一遍:
rips结果:

Seay结果:

这两个工具都提到了action.php
中的require_once()
函数,那就从这个地方出发。
0x03 审计过程
action.php
文件包含
1 2 3 4 5 6 7 8 9 10 11
| <?php require_once("library/common.php"); require_once("library/view.php"); $page = filter($_POST['page']).'.php'; $post_data = array(); foreach ($_POST as $key => $value) { $post_data[$key] = $value; } @require_once($page); ?>
|
POST一个page
变量时,经过filter
函数处理,最后利用require_once()
包含进来。
filter()
函数了过滤.
,并且限定了文件后缀名为.php
,那么这个点的利用思路就比较清晰了:使用绝对路径的方式传入$page
参数读取本地文件,另外,如果PHP版本小于5.4.3,还可以使用%00
截断的方式读取任意后缀名的文件。
利用方式:

normaliz.php
变量覆盖
根据工具扫描结果审计normaliz.php
文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?php require_once("library/common.php"); require_once("library/view.php"); function action($post_data, $ip_replacement, $mail_replacement){ foreach ($post_data as $key => $value) { $$key = $value; } try{ if ($method == '/\\d+\\.\\d+\\.\\d+\\.\\d+/') { $res = preg_replace($method, $ip_replacement, $source); } else { $res = preg_replace($method, $mail_replacement, $source); } } catch(Exception $e) { write_log($e->getMessage()); $res=$source; } return $res; } $view_class = new View(); $data = array(); $data['page'] = 'normaliz'; $ip_replacement = '222.222.222.222'; $mail_replacement = '[email protected]'; $data['res'] = action($post_data, $ip_replacement, $mail_replacement); $view_class->echoContent($data['page'], $data); ?>
|
第12行和16行代码看到一个比较敏感的函数preg_replace()
,在PHP7.0版本之前可以使用/e
模式执行PHP代码。
其中都需要传入三个参数:$method
、$ip_replacement
或$mail_repalcement
、$source
,这三个参数都可以通过第7行的$$
操作符进行变量覆盖。而post_data
参数是在action.php
通过POST来赋值,那么利用方式就比较清楚了:

common.php
任意文件写入
首先看到file_put_contents()
函数
1 2 3 4 5 6
| function write_log($input) { global $cfg_logfile; file_put_contents($cfg_logfile, $input, FILE_APPEND); }
|
全局查找$cfg_logfile
参数
回溯$input
参数,发现load_file()
中调用了write_log()
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function loadFile($filePath) { global $cfg_basedir; if(!file_exists($filePath)){ write_log('Try to open Null file:'.$filePath); return file_get_contents($cfg_basedir.'/error.php'); } $fp = @fopen($filePath,'r'); $sourceString = @fread($fp,filesize($filePath)); @fclose($fp); return $sourceString; }
|
回溯$file_path
参数,发现在view.php
中的echo_content()
调用了loadfile()
函数:
1 2 3 4 5 6 7 8 9 10
| function echoContent($vId, $data) { $this->data = $data; $content = loadFile("views/".$vId.".php"); $content = $this->parseHeadAndFoot($content); $content = $this->parseVal($content); $content = $this->parseIf($content); echo $content; }
|
接着回溯$vId
参数,查看echoContent()
函数的调用情况:
主要是分两大类:
phpcom.php
、md5.php
和normaliz.php
都是在action.php
中通过传入page变量,从而包含进来的,故不可控;
index.php
下传入的是page变量
那个就可以通过GET方式给page
变量赋值,写入文件内容,然后通过write_log()
函数写入logfile.php
文件中,利用方式:
先写入<?php phpinfo(); ?>

接着通过action.php
文件包含来执行命令:

其实这里用php文件去存储日志就很离谱。
view.php
代码执行
这个点还是比较有难度的



