cmseasy的SQL注射漏洞(附分析和exp)

先看 manage_act.php 174行

code 区域

如果 session中没有 from这个的话就设置front类中$from这个为值,我们追追他的$from怎么产生的。

在 front_class.php 312-313

code 区域

看了下,好像没有对 $_SERVER['HTTP_REFERER']做转义处理,系统默认GPC也是不对_SERVER处理的,导致我又可以注射了。

之前发了个已经说到了他session保存在数据库中,有出注入基本就可以控制它整个系统了..( WooYun: cmseasy 的一个高危漏洞(设计缺陷) )

----------

不多说了.上exp,先注册一个号 然后

code 区域

正当我觉得很顺利的时候,发现webscan360拦截了我,

新版本中 白名单已经失效了..白名单是二维数组,原先是用foreach遍历2次,新版本却只遍历一次,所以永远也不能找到对应的白名单。

还有个蛋疼的地方是 post拦截规则加上了 |' 出现单引号就拦截 由于webscan360对referre用的就是post拦截规则,(这样直接拦截单引号,对用户体验也不够好,比如搜索单引号就拦截了,)

单引号不能使用,想了想

code 区域

结构是这样的,哪我 把2222 换成 ";openid|s:1:"2 能不能闭合呢.

结果处理成这样了

code 区域

_sql.jpg

打印了 SESSION 发现闭合失败,15个字符包含其中,那我使用 转义符 "\" 把双引号转义掉,他这个字符就会少一个

提交

code 区域

code 区域

我们继续闭合,我们需要让18:""; 这部分自成一个数据就好了。

经过我一番测试后 终于让他解析成功。

code 区域

分析补充:

标题:php某函数使用不当导致的漏洞

Cmseasy 使用了session_set_save_handler了,其作用是 把session存到数据库中,而代替 文件

而在我研究中 发现,使用session_set_save_handler 不当就很会出现问题,而任意操纵session,很可怕!

session_set_save_handler php官网的介绍

write(string $sessionId, string $data)

在会话保存数据时会调用 write 回调函数。 此回调函数接收当前会话 ID 以及 $_SESSION 中数据序列化之后的字符串作为参数。 序列化会话数据的过程由 PHP 根据 session.serialize_handler 设定值来完成。

序列化后的数据将和会话 ID 关联在一起进行保存。 当调用 read 回调函数获取数据时,所返回的数据必须要和 传入 write 回调函数的数据完全保持一致。

PHP 会在脚本执行完毕或调用 session_write_close() 函数之后调用此回调函数。 注意,在调用完此回调函数之后,PHP 内部会调用 close 回调函数。

//写 将 $_SESSION 中数据序列化 存入数据库中.

read(string $sessionId)

如果会话中有数据,read 回调函数必须返回将会话数据编码(序列化)后的字符串。 如果会话中没有数据,read 回调函数返回空字符串。

在自动开始会话或者通过调用 session_start() 函数手动开始会话之后,PHP 内部调用 read 回调函数来获取会话数据。 在调用 read 之前,PHP 会调用 open 回调函数。

read 回调返回的序列化之后的字符串格式必须与 write 回调函数保存数据时的格式完全一致。 PHP 会自动反序列化返回的字符串并填充 $_SESSION 超级全局变量。 虽然数据看起来和 serialize() 函数很相似, 但是需要提醒的是,它们是不同的。 请参考: session.serialize_handler。

// 反序列化数据库中的 session 然后返回。

Cmseasy中是这样的 :

__construct 构造函数

session_start();

$this->refresh(session_id());

Refresh -> gc //目的就是看时间差来判断 session过期了没有,如果过期了就删除掉这条session数据

读取的时候 会先从数据库中读取出来 然后 return $result ['data']; 然后 反序列化( session_decode() ) $result ['data'];数据并填充 $_SESSION 超级全局变量,

之后在调用 write 用把 $_SESSION数据序列化( SESSION_ENCODE()) ,的数据写入数据库,并更新时间“update_time”(表示自己还在活动中)。

使用出错就出错在 write 没有把数据进行转义处理,而导致的解析出错。

我们来看

(我先自己在他的框架内做的测试

$_SESSION[‘TEST’] = $_POST[‘a’] //我自己测试方便去掉了 实体化。

)

我们提交 一个 “ \ ”他所对应的sql中就是TEST|s:2:"\\"; 插入数据库中 就会变成TEST|s:2:"\"; 因为 \是转义符啊。而它php自己处理的session序列化值却不认这个符号 把他当作普通字符串来序列化。

显然 按照它的流程来的话,读取了这个值就会出现无法反序列化的情况。

数据会变成 TEST|N; 空值,这个一个bug 导致了问题的出现。

现在我们来尝试闭合它,来创建其他的值。

提交 |N;\ 为什么提交这个?因为 |N;来满足他后面的闭合 用 转义符让他的结构出错。

TEST|s:5:"|N;\"; -> null

我们自己加一个值呢?加个 ooo 值吧

code 区域

居然成功解析掉了,

在 archive_act.php中。有一段讲搜索记录存入session中的代码。

256-258

code 区域

我知道cmseasy全局都实体化了。中间测试fuzz费劲,,最后成功了,但是只能使用 int类型的,

code 区域

还有个地方提下,

manage_act.php 174行

code 区域

如果 session中没有 from这个的话就设置front类中$from这个为值,我们追追他的$from怎么产生的。

在 front_class.php 312-313

code 区域

看了下,好像没有对 $_SERVER['HTTP_REFERER']做转义处理,系统默认GPC也是不对_SERVER处理的。

(新版本中 白名单已经失效了..白名单是二维数组,原先是用foreach遍历2次,新版本却只遍历一次,所以永远也不能找到对应的白名单。

还有个蛋疼的地方是 post拦截规则加上了 |' 出现单引号就拦截 由于webscan360对referre用的就是post拦截规则,(这样直接拦截单引号,对用户体验也不够好,比如搜索单引号就拦截了,))

这里的和 上面做了转义处理没做实体化处理的同理

Referer: |N;openid|s:1:\"2\"\ 这样即可

这个有好几个利用 比如 user_act.php 中的 edit_action函数内的userid 任意修改密码,再比如 respond_action 函数中的openid 注册管理员。

最后附上几个 测试的代码

code 区域

code 区域

下了分php的源码但是不怎么明白..怎么执行的。最后都调用来的 php_var_unserialize ,。。

最后说一下 这个也算是php的一个小bug把。我查看php官网上session_set_save_handler 函数 说明 好像并没有看见说明安全性的问题...广大朋友要注意这一点了。。

漏洞证明:

见详细说明

解决方案 :

write 中转义data

漏洞证明:

给个exp:

登录状态

/cmseasy/index.php?case=manage&act=edit&manage=archive&id=1

Referer: |N;\openid|s:1:\"2\"

11_.jpg

22_.jpg

利用方法很多,用到session地方都可以伪造,参考  WooYun: cmseasy 的一个高危漏洞(设计缺陷) ,