代码审计学习之某shop审计

文章首发于先知社区:https://xz.aliyun.com/t/5077

之前看了phpoop师傅的PbootCMS漏洞合集之审计全过程文章,一直没有真正的审过一套cms,代码审计只是停留在ctf题的层面上。所以接下来准备认真审计一些cms。菜鸡文章,希望大佬们不要喷。

查看网站目录结构确定基本内容

某shop-V3.1.1
├─ cache          缓存目录(自动创建)
├─ data           数据目录
│  ├─ database     数据库文件备份目录
│  ├─ uploads     上传数据目录
├─ framework      Tiny 框架目录
├─ install        html模板
├─ logs           日志目录(自动创建)
├─ protected     应用保护代码目录
│  ├─ classes     自由扩展类目录,可自己配制
│  ├─ config     配制文件目录,可自己指定
│  ├─ controllers     控制器目录
│  ├─ extension     程序扩展目录
│  ├─ widgets     插件目录
│  ├─ views     视图目录
├─ runtime       运行时生成的编译目录(自动创建)
├─ static        共用的静态文件
├─ themes        主题目录
│  ├─ default     默认主题
│  ├─├─skins     皮肤目录
│  ├─├─widgets     专属主题的插件目录
│  ├─├─views     视图目录
├─ index.php      前端入口文件

URL 模式

URL 的访问方式是 index.php?con=Controller&act=Action

Action 有两种,一种是脚本处理类的 Action,此类 Action 是 Controller 的一个 function另一种是视图类的 Action(也就是视图),当 Controller 中不存在此 function 时,系统自 动会加载此 action 对应的视图文件,如果此视图也不存在,系统会跳转 404 页面。

开启伪静态时 URL 访问方式是 /Controller/Action 也可为/index.php/Controller/Action

了解系统参数与底层过滤情况

原生 GET,POST,REQUEST

image.png
image.png

根据var_dump的结果可以看到原生GET,POST,REQUEST变量中的' " <>等字符被转义了,通过debug跟踪代码以及全局搜索$_REQUEST等字符我并没有找到对这些变量进行过滤的地方,可能是在渲染输出的时候对字符串进行了过滤。

系统外部变量获取函数

image.png
可以看到获取外部变量都调用了Req类,跟进/framework/lib/util/request_class.php

<?php
/**
 * Tiny - A PHP Framework For Web Artisans
 * @author Tiny <tinylofty@gmail.com>
 * @copyright Copyright(c) 2010-2014 http://www.tinyrise.com All rights reserved
 * @version 1.0
 */
/**
 * 封装$_GET $_POST 的类,使$_GET $_POST 有一个统一的出入口
 * @class Req 
 * @note  
 */
class Req
{
    //对应处理$_GET
    public static function get()
    {
        $num = func_num_args();
        $args = func_get_args();
        if($num==1)
        {
            if(isset($_GET[$args[0]])){
                if(is_array($_GET[$args[0]]))return $_GET[$args[0]];
                else return trim($_GET[$args[0]]);
            }
            return null;
        }
        else if($num>=2)
        {
            if($args[1]!==null)$_GET[$args[0]] = $args[1];
            else if(isset($_GET[$args[0]])) unset($_GET[$args[0]]);
        }
        else
        {
            return $_GET;
        }
    }
    //对应处理$_POST
    public static function post()
    {
        $num = func_num_args();
        $args = func_get_args();
        if($num==1)
        {
            if(isset( $_POST[$args[0]])){
                if(is_array( $_POST[$args[0]]))return  $_POST[$args[0]];
                else return trim( $_POST[$args[0]]);
            }
            return null;
        }
        else if($num>=2)
        {
            if($args[1]!==null) $_POST[$args[0]] = $args[1];
            else if(isset($_POST[$args[0]])) unset($_POST[$args[0]]);
        }
        else
        {
            return $_POST;
        }
    }
    //同时处理$_GET $_POST
    public static function args()
    {
        $num = func_num_args();
        $args = func_get_args();
        if($num==1)
        {
            if(isset($_POST[$args[0]])){
                if(is_array($_POST[$args[0]]))return $_POST[$args[0]];
                else return trim($_POST[$args[0]]);
            }
            else{
                if(isset($_GET[$args[0]])){
                    if(is_array($_GET[$args[0]]))return $_GET[$args[0]];
                    else return trim($_GET[$args[0]]);
                }
            }
            return null;
        }
        else if($num>=2)
        {
            if($args[1]!==null)
            {
                $_POST[$args[0]] = $args[1];
                $_GET[$args[0]] = $args[1];
            }
            else
            {
                if(isset($_GET[$args[0]])) unset($_GET[$args[0]]);
                if(isset($_POST[$args[0]])) unset($_POST[$args[0]]);
            }
        }
        else
        {
            return $_POST+$_GET;
        }
    }
    public function only()
    {
        $hash= md5(serialize($_POST));
        $safebox = Safebox::getInstance();
        $__hash__ = $safebox->get('__HASH__');
        if($hash != $__hash__)
        {
            $safebox->set('__HASH__',$hash);
            return true;
        }
        else
        {
            return false;
        }
    }
}
?>

GET和POST变量分别对应了Req::get()、Req::post()、Req::args(),且没有任何过滤,每次过滤都会调用Filter类,跟进/framework/lib/util/filter_class.php
image.png
该类的每一个方法都对应着不同的过滤功能。

查看系统DB类,了解数据库底层运行方式

/framework/web/model/module_class.php/framework/web/model/query_class.php
前者用于被控制器调用,后者用于viewAction
image.png
基本上Model类的底层没有任何过滤,只是简单的进行类字符串拼接,所以只要能将'\带入Model类中,且没有被Filter类过滤,即可构成注入。

系统情况初步集合

基本上除了Filter类,底层没有进行过于严格的过滤。
只要调用了Req类获取参数且在渲染赋值的过程中没有使用Filter类进行过滤,那么就很容易造成xss。
sql注入同理,底层Model类没有专门进行过滤。
另外Filter类我只是简单的看了一下,具体在某个情况下调用的函数不排除被绕过的可能。

后台一处注入

前台看了好久,过滤的很严格找不到注入点,并且这个cms大部分功能都是后台的,所以我只好在后台找一下了,虽然没什么卵用,就当学习思路。
/protected/crontrollers/goods.php set_online方法中接收id参数进行商品上下架处理,却没有对id参数进行过滤,直接拼接进sql语句中。
image.png
image.png
构造id=12) and if(1=1,sleep(1),0)#
image.png
可以看到成功注入
image.png
同样的,后台有不少地方都没有进行过滤,就不一一列举。
image.png

后台两处任意文件删除+任意文件读取

第一处比较鸡肋,只能删除install.lock文件
/protected/crontrollers/plugin.php
image.png
可以看到路径前缀从$this->widgetPath中获取,跟进
image.png
APP_CODE_ROOT为应用开发路径
image.png
可以看到name参数没有任何过滤,我们可以利用../../install跳转到安装目录,将install.lock删除然后重新安装。
然后配合MySQL LOAD DATA 任意文件读取,读取服务器上的文件。
任意文件读取
image.png

image.png

第二处可以删除任意文件
image.png
$backs变量没有任何过滤,直接和$database_path拼接然后执行删除操作。所以我们可以利用../删除任意文件
image.png
同样可以删除install.lock配合MySQL LOAD DATA读取任意文件。

  1. 鸽了一个月了

    evoA     回复
    • 嘤嘤嘤

      Smi1e     回复
  2. 图片好像看不了…

    2333     回复
    • 不要挂代理

      Smi1e     回复
 

发表评论

 
发表评论