type
status
date
slug
summary
tags
category
icon
password
最后编辑时间
Apr 20, 2023 02:15 PM
漏洞编号
No.
同步状态
状态
Author
0 前言
WeiPHP5.0是一个开源,高效,简洁的移动应用系统,它实现一个后台同时管理和运营多个客户端。WeiPHP5以前的版本都是基于ThinkPHP3.2版本开发,5.0以后升级了ThinkPHP5的架构。
WeiPHP在5.0版本存在多个SQL注入漏洞,分配了CNVD编号,有一定的影响力,然而网上只流传着poc,没有相关的分析干货,基于学习探究的目的,笔者试着就其中一个SQL注入的漏洞进行分析。
漏洞来源:
1 poc
2 分析
是一个免登录的前台sql,危害较大
先根据报错信息定位漏洞入口文件

进入
/application/home/controller/Index.php

对thinkphp架构熟悉的话,可以知道
I
函数就是一个过滤输入的函数,先ctrl
跟进看看代码来到
application/common.php

继续进入
input
函数
input
首先判断输入的数据是否以 ?
开头,是的话去掉 ?
,并将 $has
赋为 true
接着找数据中是否有
.
,有的话以 .
为界限分割数据最后看看
has
函数, thinkphp/library/think/facade/Request.php

可见
I
函数对注入没什么过滤作用接着进到
wp_where
函数
遍历
$map
数组,由于传入的只有两个值,这段代码会进入elseif
如果
$value[0]=='exp'
,并且!is_object($value[1]
,进入Db::raw
thinkphp/library/think/Db.php

注意到这是静态方法,
Db.php
有一个魔术方法__callStatic()
,其会在调用没有声明的静态方法时自动调用,作用和原型都类似于 __call()
,并且由于Db.php
没有继承任何类,static::connect
在这里调用的是当前类的 connect
方法
打下断点发poc跟进
传入回调的
$method
和 $args

进入
connection
构造函数(\think\db\Query
)
getConfig
返回数据库前缀进入
raw
函数
进入
raw
函数所在的类 think\db\Expression
初始化处理传入值后,返回到
\think\db\Query::where

不断步入到
Index.php
,进入 \think\db\Query::find

继续跟进
find()
函数( \think\db\Connection::find
),查找单条记录的函数,分析查询表达式,最后生成查询sql语句
select
函数解析并格式化查询语句跟进
select
函数,逐个跟进每个 parse
函数
顾名思义,
parseTable
函数返回 table名,跟进前面几个函数都是返回一些格式化信息,跟到parseWhere
关键函数
接着到
\think\db\Builder::buildWhere
,遍历 $val
数组的值,传给 $value
数组,这里的publicid
是默认的
array_shift
函数的作用是删除数组中的第一个元素,并且返回被删除元素的值,这里的作用可以理解成把数组中的值逐个取出做sql解析
$filed
赋值为 publicid
后传入 parseWhereItem
函数
经过
array_shift
函数后 $value
也就变成了 {"=", ""}
,$exp
被赋值为 =

接着进入
\think\db\Query::bind
函数
返回一个
name
值 ThinkBind_1_

\think\db\Builder::parseWhereItem
中赋值 :ThinkBind_1_

经过
\think\db\Builder::parseCompare
处理,最终 \think\db\Builder::parseWhereItem
返回 `publicid` = :ThinkBind_1_
到这里拿到sql语句前半段

接着返回到遍历数组的语句,继续传入
$val
的第二个键值对,也就是poc中post data传入的 uid[0]=exp&uid[1]=)%20and%20updatexml……

这里开始与前面的步骤一样,经过
array_shift
函数处理, $field
赋值为 uid
,并且数组变成 {"exp", think\db\Expression}
,后者即传入的恶意sql语句 ) and updatexml\n(1,concat(0x7e,/*!user*/(),0x7e),1)--

重复上面解析
publicid
的一系列函数处理,一步步将 think\db\Expression
的值解析出来,赋值给value

最终在
\think\db\Builder::parseExp
处拼接左右括号和 $key
,用 getValue
取值
而
getValue
函数直接返回 value
的值 (\think\db\Expression::getValue
),中间的函数并没有对传入的uid
数组进行相关的字符过滤、转义或者检查等操作
value
闭合括号即产生了注入与上文一样在
\think\db\Builder::parseWhereItem
返回解析后的值
接着把两次处理后的值拼接起来

作为
\think\db\Builder::parseWhere
的返回值,回到 select
函数完成后续的 parse
函数, 再返回给前文的select
语句
最终的
$sql
语句然后执行查询
$sql

后面初始化、连接数据库等就不赘述了
到这里的堆栈
3 利用情况

4 总结
看到poc的时候原本以为只是一个简单的未对用户输入进行过滤的SQL注入漏洞,一看代码才发现没那么简单。虽然一样是未过滤,但是形成原因有点复杂,涉及到了ThinkPHP框架原生的SQL操作类(Db.php等)以及其操作思路,debug过程很漫长,打了好多断点逐步找到关键函数,整个过程经过了很多的函数处理,文中省略了很多不关键的,一路F7十分考验耐心和细心,并且也借此机会多了解了一些ThinkPHP架构的代码思路。最终的闭合居然是在处理解析sql语句的过程中被闭合的,想必发现这个漏洞也一定是对thinkphp代码架构比较熟悉并且比较细心,总体来说,这次分析过程获益匪浅。
- 作者:3R1C
- 链接:https://notion-3r1c.vercel.app/article/125
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。