代码审计 TIPS(1)

https://www.chery666.cn/blog/2017/12/11/Code-audit.html

0x01 PHP 敏感配置项

register_globals(php 版本小于 5.4 时存在)

当该配置项为 ON 时,会把用户通过 GET、POST 提交的参数自动注册成全局变量。当代码中存在有未初始化的变量时,可能会导致变量覆盖的问题;

(PS: 其中参数覆盖的顺序受到配置文件中variables_order的参数影响,默认是EGPCS。按顺序,右边的参数来源会覆盖左边的的参数来源)

allow_url_include(php 版本大于 5.2 默认为 off)

当该配置项为 ON 时,可以通过 include、require 等函数进行远程文件包含

其中有个类似的配置项是 allow_url_fopen,这个参数配置为 on 的时候可以函数中例如 file_get_contents 中打开 url。

当两个配置项都为 ON 的时候,可以直接使用 url 进行远程包含,当 include 为 ON,fopen 为 OFF 时,只能通过 php 伪协议进行包含

magic_quato_gpc(php 版本小于 5.4 存在)

此配置项为 ON 的时候会对 GET、POST、COOKIE 变量中的单引号 (')、双引号(")、反斜杠()、空字符(NULL) 前添加反斜杠进行转义,注意:这个配置并不会对 SERVER 变量里的特殊字符进行转义,因此可能会导致 referer、client-ip 存在注入等漏洞

magic_quato_runtime(php 版本小于 5.4 存在)

这个配置和 magic_quato_gpc 的区别就在于 runtime 是对从数据库或者文件中取出的数据进行转义,因此只对例如 file()、fgets()、fread()、mysql_fetch_array()等很多对数据库查询和文件读取的函数产生影响

magic_quato_sybase(php 版本小于 5.4 存在)

这个配置和 magic_quato_gpc 的区别在于,sybase 只会转义空字符,把单引号转为双引号,并且这个配置如果为 ON 会覆盖 gpc 的配置

open_basedir

这个配置用来设置限定 php 程序只能访问哪些目录。在 windows 下,多个目录用分号(;)分割,linux 下用冒号 (:) 进行分割。注意的是配置的目录需要用斜杠(/)进行封尾,否则就变成了前缀匹配。例如,配置 / var/test,那么 / var/test 和 / var/test123 都是可以进行访问的,如果指定一个确定的目录就要写成 / var/test/

0x02 PHP 常见敏感函数

  • 注入: select from 、mysql_connect、mysql_query、mysql_fetch_*、update、insert、delete
  • 宽字节注入: set names gbk、character_set_client=gbk mysql_set_charset('gbk')、iconv
  • 二次编码注入: urldecode、rawurldecode、
  • 文件包含 : include、include_once 、require、require_once
  • 文件上传 : move_upload_file
  • 任意文件删除 : unlink、session_destory
  • 代码注入 : eval assert、preg_replace(/e)、 call_user_func、call_user_func_array、array_map 等
  • 命令执行: system、exec、shell_exec、passthru 、pctnl_exec、popen、proc_exec、``
  • 变量覆盖: extract、parse_str、$$
  • 反序列化: unserialize
  • 随机数: rand、mt_rand

0x03 常见漏洞解析

  • 各种问题可以导致系统重装

    一般程序都是通过判断 install 文件下有没有安装过程中生成的以 lock 为后缀的文件或者 config 配置文件来判断有没有安装。

1. 未对系统是否已经安装进行判断

例如 PHPSHE B2C 商城 1.6(wooyun 2014-062047)


可以看到代码未对是否安装进行任何判断,直接进入安装流程

2. 变量覆盖绕过

例如 frcms (wooyun 2014-073244)

其中主要的漏洞代码是
foreach(Array('_GET','_POST','_COOKIE') as $_request){
    foreach($$_request as $_k => $_v) ${$_k} = _runmagicquotes($_v);
}

他会把你从 GET、POST、COOKIE 中的变量注册为全局变量,因此我们直接通过 GET 参数提交 $insLockfile 变量即可绕过

3. 判断已安装后未 exit() 退出程序

例如 startbbs (wooyun-2013-045143)

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 文件存在后直接使用 js 代码将用户进行重定向,但是并没有 die 程序,直接从前端删除返回的 js 代码即可重装

4. 还可以借助任意文件删除的漏洞来删除 lock 文件,然后进行重装(PS: 这个就留在任意文件删除再分析)

  • 文件包含漏洞(未做过滤 + 截断)

例如: 用 thinkphp 改造的 hdcms (wooyun-2015-092061)
我们先跟着框架走一遍,首先查看入口文件 index.php

首先定义了一些基本的框架目录,然后就直接开始引入框架文件,我们进入框架初始化文件 hdphp.php


前面还是检测一次是否成功初始化常量,然后检测是否进行过编译,否则就载入文件进行首次编译,然后进行 boot 的 run 方法,查看该方法内容。

前面依然是定义了各种常量,我们可以直接跳过,来到最后的应用初始化,查看该方法

可以看到 module_path 常量是通过将 get 形式提交的 var_group 参数进行拼接的,然后又将 module_path 拼接入 module_config_path,最后使用 require 进行了文件包含。(PS: 因为这里后面制定了 config.php,所以需要用到 %00 进行截断)

但是我们分析到目前为止只能说是疑似存在文件包含漏洞,我们还要看 GET 参数接收时有没有进行过滤,于是我们进入之前的解析路由方法 route::parseurl(),代码较长我就不贴图了,里面就是将 url 中的参数进行截取解析,没有进行任何的过滤和检测,因此可以确定此处存在文件包含漏洞。因此我们接下来需要确定输入点,可以发现变量是通过 thinkphp 中获取参数的 C 方法进行获取的,而 C 方法获取的变量在 config.php 中,于是我们查看文件中可以看到 var_group 对应的变量是 g


因此我们可以先上传一个文件然后 index.php?g=../test.php%00
  • 注入漏洞

之前的文件包含漏洞我们是通过 index.php 这个入口文件一步步搞懂 cms 框架然后进行审计。除了这种方法,我们还可以直接定位数据库查询语句或者功能附近,看看传入的数据有没有被进行清洗。

注入漏洞这里我们就用两个有意思的骚操作来分析一下

1.Ecshop 支付宝插件全局转义绕过导致 sql 注入

其实在 ECshop 中的 init.php 中对用户输入的参数进行了全局转义


但是我们来到来到案发现场看看,巧妙的运用 str_replace 的替换功能来帮助单引号进行逃逸

其中的核心漏洞代码是 $order_sn = str_replace($_GET['subject'], '', $_GET['out_trade_no']);其中代码对用户提交的 out_trade_no 参数中将 subject 替换为空,然后送入 check_money 函数中的 sql 查询语句.
这个漏洞的关键在于 str_replace 函数中的替换内容和源字符串都是可控的。
理解这个漏洞之前我们再来回顾一下 php 中 addslashes 中的转义处理机制 [\-->\\,"-->\","-->\",null-->\0]
于是我们可以提交 out_trade_no 参数为 %00'后面再跟上我们的 payload,提交的 subject 参数为 0,我们来看一下数据转换过程.
out_trade_no=%00' ————>经过全局gpc转义————> out_trade_no=\0\'————>送入str_replace函数处理,将0替换————>out_trade_no=\\' 也就是等于了',最终也就成功在 sql 语句中引入了一个单引号,从而可以进行注入

2. 格式化字符串导致的单引号逃逸

再来分析一下前段时间出来的 wordpress 格式化字符导致的注入

具体的代码分析在这就不贴图了,我们直接来分析一下格式化字符串漏洞的核心原理,其中一个关键点就是 sprintf 的 padding 特性

printf()和 sprintf()函数中可以通过使用 % 接一个字符来进行 padding 功能

例如 %10s 字符串会默认在左侧填充空格至长度为 10,还可以 %010s 会使用字符 0 进行填充,但是如果我们想要使用别的字符进行填充,需要使用'单引号进行标识,例如 %’#10s 这个就是使用 #进行填充(百分号不仅会吃掉’单引号,还会吃掉 斜杠)

同时 sprintf()可以使用指定参数位置的写法

% 后面的数字代表第几个参数,$ 后代表格式化类型

于是当我们输入的特殊字符被放到引号中进行转义时,但是又使用了 sprintf 函数进行拼接时, 例如 %1$’%s’ 中的 ‘% 被当成使用 % 进行 padding,导致后一个’逃逸了

还有一种情况就是’被转义成了 \’, 例如输入 %’ and 1=1# 进入,存在 SQL 过滤,’被转成了 \’
于是 sql 语句变成了select * from user where username = '%\' and 1=1#’;
如果这个语句被使用 sprintf 函数进行了拼接,% 后的被吃掉了,导致了’逃逸

"select * from user where username = '%\' and 1=1#';";
$args = "admin";
echo sprintf( $sql, $args ) ;
//result: select * from user where username = '' and 1=1#'
?>

不过这样容易遇到 PHP Warning: sprintf(): Too few arguments 的报错
这个时候我们可以使用 %1$ 来吃掉转移添加的 \

"select * from user where username = '%1$\' and 1=1#' and password='%s';";
$args = "admin";
echo sprintf( $sql, $args) ;
//result: select * from user where username = '' and 1=1#' and password='admin';
?>


你可能感兴趣的:(渗透)