入门级代码审计实战之某CMS

*本文作者:wnltc0,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。

目标:某cms

环境:Apache+mysql+php5.3

开始审计!先看下目录结构

入门级代码审计实战之某CMS

index.php

入门级代码审计实战之某CMS

index.php 内定义了几个常量,并载入 /Lib/X.php

跟进 /Lib/X.php

入门级代码审计实战之某CMS

依旧是定义常量,载入文件,在最后面调用了 Router::get_instance() 

跟进 /Lib/Config/Common.php

入门级代码审计实战之某CMS

再来看下 /Lib/Config/Router.php 的 get_instance() 方法

入门级代码审计实战之某CMS

get_instance() 将Router类实例化,触发了其中的构造方法在构造方法中,调用 site_config() 并将返回结果赋值给$this->_default,然后调用本类中的 view_controller() 方法

跟进 site_config() ,/Lib/Config/Config.php 26行

入门级代码审计实战之某CMS

跟进 view_controller() ,/Lib/Config/Router.php 71行

入门级代码审计实战之某CMS

view_controller() 方法中调用了本类中的 _fetch_url() 方法,将结果赋值给 $controller_arr 如果 $controller_arr 为空则调用 self::err() ,反之,载入 $controller_arr[‘url’]若方法 $controller_arr[‘method’].’_Action’ 存在于 $controller_arr[‘name’] 中,则调用 Base::insert_fun_array()

跟进 _fetch_url() ,/Lib/Config/Router.php 31行

private function _fetch_url(){   $url = '';   $controller_arr = array();   $url_arr = explode('.', str_replace(SITEPATH, '/', $_SERVER['REQUEST_URI']));       $uri = ($url_arr[0] == '/') ? '/' : substr($url_arr[0], 1);   if (strpos ( $uri, 'poweredByxxx' ) !== false) {    echo "xxx"."<br>/n";    echo "xxx"."<br>/n";    echo "xxx"."<br>/n";       echo "Your Ip : " . ip () . "<br>/n";    echo "Date : " . date ( 'Y-m-d H:i:s' ) . "<br>/n";    echo "UserAgent : " . $_SERVER ['HTTP_USER_AGENT'] . "<br>/n";    exit ();   }   if($uri == '/'){        $controller_arr['name'] = $this->_default['default_controller'];    $controller_arr['url'] = BASEPATH.'Controller/'.$this->_default['default_controller'].EXT;    $controller_arr['method'] = $this->_default['default_function'];   }else{       $uri_arr = explode($this->_default['url'], $uri);    foreach($uri_arr as $key => $val){      if(empty($val))continue;        $file = $url.$val;       $url .= $val.'/';     if(file_exists(BASEPATH.'Controller/'.$file.EXT)){         $controller_arr['name'] = $val;      $controller_arr['url'] = BASEPATH.'Controller/'.$file.EXT;      $fun_url = substr($uri, strlen($file)+1);       $fun_arr = explode($this->_default['url'], $fun_url);        $controller_arr['method'] = empty($fun_arr[0]) ? 'index' : fun_arr[0];      $controller_arr['fun_arr'] = array_splice($fun_arr, 1);           break;     }      }   }   return $controller_arr;  }

url格式如下:http://172.16.92.165/guest/index.html

_fetch_url() 以 . 为分割符将 $_SERVER[‘REQUEST_URI’] 分割成数组并赋值给 $url_arr

将 $url_arr[0] 赋值给 $uri

若 $uri 为 / 则将 $controller_arr[‘name’] , $controller_arr[‘method’] 设为默认值

否则以 $this->_default[‘url’] 为分隔符将 $uri 分割成数组赋值给 $uri_arr 

$this->_default 在当前类构造方法中通过 site_config() 获得

通过 foreach 将 $uri_arr[0] 设为类名,并拼接成完整的文件路径,即 $controller_arr[‘url’]

其余的赋值给 $controller_arr[‘method’] 及 $controller_arr[‘arr’]

最后返回 $controller_arr

再跟进 Base::insert_fun_array() ,/Lib/Config/Base.php 247行

入门级代码审计实战之某CMS

在 Base::insert_fun_array() 中实例化类($controller_arr[‘name’]) ,并将值($controller_arr[‘arr’]) 传入其方法($controller_arr[‘method’]) 中执行

到这里,对整个程序的运行流程也了解的差不多了,接下来要开始挖洞了!

1)储存型XSS

/System/Controller/guest.php  10行

入门级代码审计实战之某CMS

将POST进来的数据传入 $this->_guestObj->insert() 方法

入门级代码审计实战之某CMS

ps:在 /Lib/Model/ 下的所有类都继承了Db_pdo 类,exec_insert() 正是Db_pdo 类中的方法,Db_pdo的父类是Db类

Db_pdo, Db 在 /Lib/Config/ 下

继续跟进 exec_insert() 

入门级代码审计实战之某CMS

调用父类的 get_sql_insert() 将传入的参数通过 addslashes()过滤后,拼接成一条完整的sql语句

入门级代码审计实战之某CMS

然后调用 $this->q_exec() 执行sql语句

入门级代码审计实战之某CMS

一路上也没看见啥过滤 xss的函数,十有八九是存在xss了

入门级代码审计实战之某CMS

入门级代码审计实战之某CMS

再到后台看下

入门级代码审计实战之某CMS

2)越权

/System/Controller/backend/index.php

入门级代码审计实战之某CMS

/Lib/Config/Controllers.php

入门级代码审计实战之某CMS

该构造方法,从cookie中获取 admin_id, admin_name, admin_secret 等

如果 admin_id 为空,或者 admin_secret 不匹配则跳转登录页

既然是从cookie中获取也就意味着admin_id, admin_secret 等都是可控的,其中SITE_KEY 在一开始的X.php 写死了

so,浏览器访问后台首页,然后抓包设置admin_id 等

入门级代码审计实战之某CMS

入门级代码审计实战之某CMS

入门级代码审计实战之某CMS

成功登录后台!

3)文件上传

/System/Cntroller/index.php

入门级代码审计实战之某CMS

跟进 $this->upload() , /Lib/Config/Controllers.php

入门级代码审计实战之某CMS

upload() 将上传文件的内容取出,进行hash加密后带入数据库查询,若存在,返回文件路径

否则调用 $uploadObj->upload_file() 上传文文件

跟进 $uploadObj->upload_file(),/Lib/Helper/upload.php

入门级代码审计实战之某CMS

判断文件是否通过POST上传,且文件类型属于合法类型

入门级代码审计实战之某CMS

然后调用 self::_move_file() 

跟进 self::_move_file()

入门级代码审计实战之某CMS

由于仅验证了文件类型是否合法,可文件类型是可控的啊,最终造成文件文件上传漏洞

入门级代码审计实战之某CMS

入门级代码审计实战之某CMS

入门级代码审计实战之某CMS

上传成功!

4)任意文件读取

/System/Controller/backend/template.php 

问题代码如下:

public function tempview_Action($tempname = ''){

if(empty($tempname)){

exec_script('alert("模版文件不能为空");history.back();');exit;

       }

        .....

$tempname = base64_decode($tempname);

$result = file_get_contents(BASEPATH.'view/template/'.$templateRs['name'].'/'.$tempname);

$str = str_replace(array("/n"), array('<br>'), htmlspecialchars($result));

$temp['str'] = $str;

$this->load_view('backend/template/tempview', $temp);

}

注意!$tempname 经过国base64解码后直接拼接到 BASEPATH.’view/template/’.$templateRs[‘name’].’/’.$tempname 后面去了

来读下index.php 看看

入门级代码审计实战之某CMS

入门级代码审计实战之某CMS

技术有限!审计到此结束!!!!Bye!!!

*本文作者:wnltc0,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。