文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 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
// index.php
<?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.phpPOST参数跳转到相应功能模块的PHP文件。

0x02 工具扫描

首先上工具,用Seay和rips扫一遍:

rips结果:

Seay结果:

这两个工具都提到了action.php中的require_once()函数,那就从这个地方出发。

0x03 审计过程

action.php文件包含

1
2
3
4
5
6
7
8
9
10
11
//action.php
<?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
// normaliz.php
<?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
// common.php
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
// common.php
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
// view.php
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()函数的调用情况:

主要是分两大类:

  1. phpcom.phpmd5.phpnormaliz.php都是在action.php中通过传入page变量,从而包含进来的,故不可控;
  2. index.php下传入的是page变量

那个就可以通过GET方式给page变量赋值,写入文件内容,然后通过write_log()函数写入logfile.php文件中,利用方式:

先写入<?php phpinfo(); ?>

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

其实这里用php文件去存储日志就很离谱。

view.php代码执行

这个点还是比较有难度的

Copyright © ca01h 2019-2020 | 本站总访问量