PHP审计之BEESCMS审计案例
审计流程
任意文件包含
文件存在即包含该文件,而包含的这个文件名可控,则需要找这个文件创建的地方
function creat_inc($fl,$str){
if(file_exists($fl)){@unlink($fl);}
if(!$fp=@fopen($fl,'w')){
msg('文件打开失败,请检查是否有足够的权限操作文件');
}
flock($fp,LOCK_EX);
if(!fwrite($fp,$str)){
msg('写入文件失败,请检查是否有足够的权限操作文件');
}
flock($fp,LOCK_UN);
unset($fp);
}
lang是request接受过来的值,可控。
跟踪到这只要$cate_list可控,那么就是一个妥妥的任意文件包含漏洞。而这里的实际代码是$cate_list =.var_export($rel,true)
发现这个$rel这个参数大部分都是数据库查询获取的值。
找了几个目录可控的,继续来看代码
adminadmin_db.php
adminadmin_index.php
adminadmin_info.php
adminadmin_sq_code.php
admin_db.php文件
elseif($action=='save_back'){
if(!check_purview('data_backup')){msg('<span style="color:red">操作失败,你的权限不足!</span>');}
$db = $_POST['db'];
$init = isset($_POST['init'])?$_POST['init']:0;
$sql_size = 1048;
$dir = isset($_GET['dir'])?$_GET['dir']:'';
//缓存所有表
if($init){
if(empty($db)){msg('请选择要备份的表');}
$str="<?php
$table_arr=".var_export($db,true).";
?>";
$file=DATA_PATH.'cache/db_cache.php';
creat_inc($file,$str);
//创建备份目录
$dir = 'db'.date(YmdHms,time());
@mkdir(DATA_PATH.'backup/'.$dir);
}
@include(DATA_PATH.'cache/db_cache.php');
发现转义单引号,代码中并没有发现有过滤函数,看到包含过来的init.php文件
if (!get_magic_quotes_gpc())
{
if (isset($_REQUEST))
{
$_REQUEST = addsl($_REQUEST);
}
$_COOKIE = addsl($_COOKIE);
$_POST = addsl($_POST);
$_GET = addsl($_GET);
}
检测没开启魔术引号,则启用自己的过滤方法进行全局的过滤。
/*
*转义函数
*
*@param $value array || string
*@return array || string
*/
function addsl($value)
{
if (empty($value))
{
return $value;
}
else
{
return is_array($value) ? array_map('addsl', $value) : addslashes($value);
}
}
过滤操作
注入
login.php
elseif($action=='ck_login'){
global $submit,$user,$password,$_sys,$code;
$submit=$_POST['submit'];
$user=fl_html(fl_value($_POST['user']));
$password=fl_html(fl_value($_POST['password']));
$code=$_POST['code'];
.....
check_login($user,$password);
fl_html跟踪查看发现是过滤xss的,也就是实例化html。fl_value方法是一个正则过滤sql的方法。
function fl_value($str){
if(empty($str)){return;}
return preg_replace('/select|insert | update | and | in | on | left | joins | delete |\%|=|/*|*|../|./| union | from | where | group | into |load_file
|outfile/i','',$str);
}
unction check_login($user,$password){
$rel=$GLOBALS['mysql']->fetch_asc("select id,admin_name,admin_password,admin_purview,is_disable from ".DB_PRE."admin where admin_name='".$user."' limit 0,1");
$rel=empty($rel)?'':$rel[0];
updatexml没被过滤,可以使用updatexml。
session覆盖
Extract()
该函数使用数组键名作为变量名,使用数组键值作为变量值。但是当变量中有同名的元素时,该函数默认将原有的值给覆盖掉。这就造成了变量覆盖漏洞。
如果能覆盖(添加)这几个$_SESSION值 就能绕过这个检查
$_SESSION
覆盖有个必须前提,session_start()
必须出现在覆盖之前,不然就算覆盖了$_SESSION
变量,一旦session_start() 变量就会被初始化掉。没有使用EXTR_SKIP
参数导致任意变量覆盖,又由于执行的时候已经session_start()了
所以可以覆盖(添加)任意$_SESSION值
if(!is_login()){header('location:login.php');exit;}
查看is_login的方法
function is_login(){
if($_SESSION['login_in']==1&&$_SESSION['admin']){
if(time()-$_SESSION['login_time']>3600){
login_out();
}else{
$_SESSION['login_time']=time();
@session_regenerate_id();
}
发现验证了三个值,主要伪造着三个值即可登录。
if (!get_magic_quotes_gpc())
{
if (isset($_REQUEST))
{
$_REQUEST = addsl($_REQUEST);
}
$_COOKIE = addsl($_COOKIE);
$_POST = addsl($_POST);
$_GET = addsl($_GET);
}
if (isset($_REQUEST)){$_REQUEST = fl_value($_REQUEST);}
$_COOKIE = fl_value($_COOKIE);
$_GET = fl_value($_GET);
@extract($_POST);
@extract($_GET);
@extract($_COOKIE);
而在这里,init.php代码中,使用Extract方法未使用EXTR_SKIP
参数,并且未过滤_session
字符,导致可以同名变量覆盖。
_SESSION[login_in]=1&_SESSION[admin]=1&_SESSION[login_time]=99999999999
结尾
类似这种非MVC的架构,可以省略看路由这步骤,直接定位危险函数/方法,还有的就是鉴权实现。