影风博客

代码审计的艺术系列 第十一篇

2019-03-23

0x00 前言

设计缺陷和逻辑相关的漏洞是目前漏洞挖掘者比较关注的。

最近在国外漏洞披露平台hackerone上就有位白帽子挖掘到了Uber的一个修改任意账户密码的逻辑漏洞获得了$10000的丰厚奖励。

所以如何在源码中找逻辑漏洞是安全和开发人员必须熟悉的技能,主要总结了安装问题、找回密码这两篇内容,本篇讲的是代码审计中的安装问题。

0x01 设计缺陷&&逻辑漏洞挖掘的脑图:

0x02 无任何验证 :

程序安装完成后不会自动删除安装文件,也不会生成lock来判断是否安装过导致的重装漏洞,之前出现过的漏洞代码如下:

install/index.php:

<?php
error_reporting(E_ALL ^ E_NOTICE);
date_default_timezone_set('PRC');
header('Content-Type: text/html; charset=utf-8');
//改写不安全的register_global和防sql注入处理
if (@ini_get('register_globals')) {
    foreach($_REQUEST as $name => $value){unset($$name);}
}
$pe['host_root'] = 'http://'.str_ireplace(rtrim(str_replace('\\','/',$_SERVER['DOCUMENT_ROOT']), '/'), $_SERVER['HTTP_HOST'], str_replace('\\', '/', dirname(__FILE__))).'/../';
$pe['path_root'] = str_replace('\\','/',dirname(__FILE__)).'/../';
include("{$pe['path_root']}/include/class/cache.class.php");
include("{$pe['path_root']}/include/function/global.func.php");
if (get_magic_quotes_gpc()) {
    !empty($_GET) && extract(pe_trim(pe_stripslashes($_GET)), EXTR_PREFIX_ALL, '_g');
    !empty($_POST) && extract(pe_trim(pe_stripslashes($_POST)), EXTR_PREFIX_ALL, '_p');
}
else {
    !empty($_GET) && extract(pe_trim($_GET),EXTR_PREFIX_ALL,'_g');
    !empty($_POST) && extract(pe_trim($_POST),EXTR_PREFIX_ALL,'_p');
}
switch ($_g_step) {
    //#####################@ 配置信息 @#####################//
    case 'setting':
        if (isset($_p_pesubmit)) {

install的引导文件里没有判断lock导致可以直接重装

0x03 代码的逻辑问题 :

1.Step1判断lock文件可直接Step2绕过

install/index.php的缺陷代码和分析如下:

if (empty ($step))
{
    $step = 1;//当用户没有提交step的时候 赋值为1
}
require_once ("includes/inc_install.php");
$gototime = 2000;
/*------------------------
显示协议文件
------------------------*/
if ($step == 1) //当1才检测lock
{
    if (file_exists('installed.txt'))
    {
        echo '<html>
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        </head>
        <body>
        你已经安装过该系统,如果想重新安装,请先删除install目录下的 installed.txt 文件,然后再安装。
        </body>
        </html>';
        exit;
    }
    include_once ("./templates/s1.html");
    exit ();
}
/*------------------------
测试环境要求
------------------------*/
else
    if ($step == 2) // 我们直接提交step为2 就不check lock了
    { 
        $phpv = @ phpversion();
        $sp_os = $_ENV["OS"];
        $sp_gd = @ gdversion();
        $sp_server = $_SERVER["SERVER_SOFTWARE"];
        $sp_host = (empty ($_SERVER["SERVER_ADDR"]) ? $_SERVER["SERVER_HOST"] : $_SERVER["SERVER_ADDR"]);
        $sp_name = $_SERVER["SERVER_NAME"];
        $sp_max_execution_time = ini_get('max_execution_time');
        $sp_allow_reference = (ini_get('allow_call_time_pass_reference') ? '<font color=green>[√]On</font>' : '<font color=red>[×]Off</font>');

2.判断lock文件的代码有问题

还有一种是在判断lock文件是否存在的代码上有问题了,我们看下缺陷代码:

<?php
$lockfile=ROOT.'/install.lock';
$pattern_db= '/[0-9a-zA-Z]*$/';
if (!preg_match($pattern_db,$db_name)||!preg_match($pattern_db,$db_user)) {
    echo '1001';exit;
}
if(file_exists($lockfile) && ($_a=='template' || $_a=='setting' || $_a=='check')){
    exit('please delete install.lock');
}
?>

可以看到这里判断了Lock是否存在,但是

if(file_exists($lockfile) && ($_a=='template' || $_a=='setting' || $_a=='check')

这里除了判断lock还判断了$_a,并且使用&&导致$_a为空时就绕过了lock的验证最终导致可继续重装。

0x04 变量覆盖:

install/index.php

header("Content-Type: text/html; charset={$lang}");
foreach(Array('_GET','_POST','_COOKIE') as $_request){  
    foreach($$_request as $_k => $_v) ${$_k} = _runmagicquotes($_v);
}
function _runmagicquotes(&$svar){
    if(!get_magic_quotes_gpc()){
        if( is_array($svar) ){
            foreach($svar as $_k => $_v) $svar[$_k] = _runmagicquotes($_v);
        }else{
            $svar = addslashes($svar);
        }
    }
    return $svar;
}
if(file_exists($insLockfile)){
    exit(" 程序已运行安装,如果你确定要重新安装,请先从FTP中删除 install/install_lock.txt!");
}

file_exists($insLockfile)这里判断lock文件是否存在并退出,但是这行代码

foreach($$_request as $_k => $_v) ${$_k} = _runmagicquotes($_v);

存在变量覆盖,所以可以直接将$insLockfile变量覆盖为1就使得file_exists($insLockfile)的返回为0,从而可以继续重装。

0x05 判断lock后无exit:

缺陷代码如下:

<?php
class Install extends Install_Controller
{
    function __construct ()
    {
        parent::__construct();
        $this->load->library('myclass');
        $file=FCPATH.'install.lock';
        if (file_exists($file)){
            $this->myclass->notice('alert("系统已安装过");window.location.href="'.site_url().'";');
        }
    }
?>

检查是否存在install.lock,然后用javascript的方式告诉用户”系统已安装过”,然后跳转。问题在于这个脚本根本还没有使用exit函数来结束,程序会继续运行,导致可继续重装。

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章