代码审计学习之某shop任意文件删除

2019-05-10 约 2084 字 预计阅读 5 分钟

声明:本文 【代码审计学习之某shop任意文件删除】 由作者 Smi1e 于 2019-05-10 09:10:00 首发 先知社区 曾经 浏览数 158 次

感谢 Smi1e 的辛苦付出!

之前看了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


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

系统外部变量获取函数


可以看到获取外部变量都调用了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

该类的每一个方法都对应着不同的过滤功能。

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

/framework/web/model/module_class.php/framework/web/model/query_class.php
前者用于被控制器调用,后者用于viewAction

基本上Model类的底层没有任何过滤,只是简单的进行类字符串拼接,所以只要能将'\带入Model类中,且没有被Filter类过滤,即可构成注入。

系统情况初步集合

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

后台一处注入

前台看了好久,过滤的很严格找不到注入点,并且这个cms大部分功能都是后台的,所以我只好在后台找一下了,虽然没什么卵用,就当学习思路。
/protected/crontrollers/goods.php set_online方法中接收id参数进行商品上下架处理,却没有对id参数进行过滤,直接拼接进sql语句中。


构造id=12) and if(1=1,sleep(1),0)#

可以看到成功注入

同样的,后台有不少地方都没有进行过滤,就不一一列举。

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

第一处比较鸡肋,只能删除install.lock文件
/protected/crontrollers/plugin.php

可以看到路径前缀从$this->widgetPath中获取,跟进

APP_CODE_ROOT为应用开发路径

可以看到name参数没有任何过滤,我们可以利用../../install跳转到安装目录,将install.lock删除然后重新安装。
然后配合MySQL LOAD DATA 任意文件读取,读取服务器上的文件。
任意文件读取

第二处可以删除任意文件

$backs变量没有任何过滤,直接和$database_path拼接然后执行删除操作。所以我们可以利用../删除任意文件

同样可以删除install.lock配合MySQL LOAD DATA读取任意文件。

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now