WeiPHP 5.0 sql注入漏洞(bind_follow)浅析

一次漫长的debug分析过程~

0x00 前言

WeiPHP5.0是一个开源,高效,简洁的移动应用系统,它实现一个后台同时管理和运营多个客户端。WeiPHP5以前的版本都是基于ThinkPHP3.2版本开发,5.0以后升级了ThinkPHP5的架构。

WeiPHP在5.0版本存在多个SQL注入漏洞,也被分配了CNVD编号,有一定的影响力,然而网上只流传着一纸poc,没有相关的分析干货,基于学习探究的目的,笔者试着就其中一个SQL注入的漏洞进行分析。

漏洞来源:

WeiPHP5.0 SQL注入漏洞2_N0puple的博客-CSDN博客_weiphp

0x01 poc

  1. POST /weiphp5/public/index.php/home/Index/bind_follow HTTP/1.1
  2. Host: 192.168.249.128:81
  3. Pragma: no-cache
  4. Cache-Control: no-cache
  5. Upgrade-Insecure-Requests: 1
  6. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
  7. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
  8. Accept-Encoding: gzip, deflate
  9. Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en;q=0.7,zh-HK;q=0.6,en-US;q=0.5
  10. Connection: close
  11. Content-Type: application/x-www-form-urlencoded
  12. Content-Length: 80
  13. uid%5b0]=exp&uid[1]=)%20and%20updatexml%0a(1,concat(0x7e,/*!user*/(),0x7e),1)--+

0x02 分析

是一个免登录的前台sql,危害较大

先根据报错信息定位漏洞入口文件

Untitled.png

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

Untitled 1.png

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

来到 application/common.php

Untitled 2.png

继续进入 input 函数

Untitled 3.png

input 首先判断输入的数据是否以 ? 开头,是的话去掉 ? ,并将 $has 赋为 true

接着找数据中是否有 . ,有的话以 . 为界限分割数据

最后看看 has 函数, thinkphp/library/think/facade/Request.php

Untitled 4.png

可见 I 函数对注入没什么过滤作用

接着进到 wp_where 函数

Untitled 5.png

遍历 $map 数组,由于传入的只有两个值,这段代码会进入elseif

如果$value[0]=='exp' ,并且!is_object($value[1] ,进入Db::raw thinkphp/library/think/Db.php

Untitled 6.png

注意到这是静态方法,Db.php有一个魔术方法__callStatic() ,其会在调用没有声明的静态方法时自动调用,作用和原型都类似于 __call() ,并且由于Db.php没有继承任何类,static::connect 在这里调用的是当前类的 connect 方法

Untitled 7.png

打下断点发poc跟进

传入回调的 $method$args

Untitled 8.png

进入 connection 构造函数(\think\db\Query

Untitled 9.png

getConfig 返回数据库前缀

进入raw函数

Untitled 10.png

进入raw函数所在的类 think\db\Expression

初始化处理传入值后,返回到 \think\db\Query::where

Untitled 11.png

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

Untitled 12.png

继续跟进 find() 函数( \think\db\Connection::find ),查找单条记录的函数,分析查询表达式,最后生成查询sql语句

Untitled 13.png

select 函数解析并格式化查询语句

跟进 select 函数,逐个跟进每个 parse 函数

Untitled 14.png

顾名思义,parseTable 函数返回 table名,跟进前面几个函数都是返回一些格式化信息,跟到parseWhere 关键函数

Untitled 15.png

接着到 \think\db\Builder::buildWhere ,遍历 $val 数组的值,传给 $value数组,这里的publicid是默认的

Untitled 16.png

array_shift 函数的作用是删除数组中的第一个元素,并且返回被删除元素的值,这里的作用可以理解成把数组中的值逐个取出做sql解析

Untitled 17.png

$filed 赋值为 publicid 后传入 parseWhereItem 函数

Untitled 18.png

经过array_shift 函数后 $value 也就变成了 {"=", ""}$exp 被赋值为 =

Untitled 19.png

接着进入 \think\db\Query::bind 函数

Untitled 20.png

返回一个 nameThinkBind_1_

Untitled 21.png

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

Untitled 22.png

经过 \think\db\Builder::parseCompare 处理,最终 \think\db\Builder::parseWhereItem 返回 publicid = :ThinkBind_1_

到这里拿到sql语句前半段

Untitled 23.png

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

Untitled 24.png

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

Untitled 25.png

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

Untitled 26.png

最终在 \think\db\Builder::parseExp 处拼接左右括号和 $key ,用 getValue 取值

Untitled 27.png

getValue 函数直接返回 value 的值 (\think\db\Expression::getValue),中间的函数并没有对传入的uid数组进行相关的字符过滤、转义或者检查等操作

Untitled 28.png

value 闭合括号即产生了注入

与上文一样在\think\db\Builder::parseWhereItem 返回解析后的值

Untitled 29.png

接着把两次处理后的值拼接起来

Untitled 30.png

作为\think\db\Builder::parseWhere 的返回值,回到 select 函数完成后续的 parse 函数, 再返回给前文的select语句

Untitled 31.png

最终的 $sql 语句

  1. SELECT * FROM `wp_user_follow` WHERE `publicid` = :ThinkBind_1_ AND ( `uid` ) and updatexml\n(1,concat(0x7e,/*!user*/(),0x7e),1)-- ) LIMIT 1

然后执行查询 $sql

Untitled 32.png
后面初始化、连接数据库等就不赘述了

到这里的堆栈

  1. Connection.php:644, think\db\Connection->query()
  2. Connection.php:844, think\db\Connection->find()
  3. Query.php:3132, think\db\Query->find()
  4. Index.php:161, app\home\controller\Index->bind_follow()
  5. Container.php:395, ReflectionMethod->invokeArgs()
  6. Container.php:395, think\Container->invokeReflectMethod()
  7. Module.php:132, think\route\dispatch\Module->think\route\dispatch\{closure}()
  8. Middleware.php:185, call_user_func_array:{C:\ALLHERE\phpstudy\phpstudy_pro\WWW\weiphp5\thinkphp\library\think\Middleware.php:185}()
  9. Middleware.php:185, think\Middleware->think\{closure}()
  10. Middleware.php:130, call_user_func:{C:\ALLHERE\phpstudy\phpstudy_pro\WWW\weiphp5\thinkphp\library\think\Middleware.php:130}()
  11. Middleware.php:130, think\Middleware->dispatch()
  12. Module.php:137, think\route\dispatch\Module->exec()
  13. Dispatch.php:168, think\route\Dispatch->run()
  14. App.php:432, think\App->think\{closure}()
  15. Middleware.php:185, call_user_func_array:{C:\ALLHERE\phpstudy\phpstudy_pro\WWW\weiphp5\thinkphp\library\think\Middleware.php:185}()
  16. Middleware.php:185, think\Middleware->think\{closure}()
  17. Middleware.php:130, call_user_func:{C:\ALLHERE\phpstudy\phpstudy_pro\WWW\weiphp5\thinkphp\library\think\Middleware.php:130}()
  18. Middleware.php:130, think\Middleware->dispatch()
  19. App.php:435, think\App->run()
  20. index.php:55, {main}()

0x03 利用情况

Untitled 33.png

0x04 总结

看到poc的时候原本以为只是一个简单的未对用户输入进行过滤的SQL注入漏洞,一看代码才发现没那么简单。虽然一样是未过滤,但是形成原因有点复杂,涉及到了ThinkPHP框架原生的SQL操作类(Db.php等)代码以及其操作思路,debug过程很漫长,打了好多断点逐步找到关键函数,整个过程经过了很多的函数处理,文中省略了很多步骤,一路F7十分考验耐心和细心,并且也借此机会多了解了一些ThinkPHP架构的代码思路。最终的闭合居然是在处理解析sql语句的过程中被闭合的,想必发现这个漏洞也一定是对thinkphp代码架构比较熟悉并且比较细心。总的来说,这次分析还是比较具有代表意义的。

  • 发表于 2022-09-19 09:35:51
  • 阅读 ( 8278 )
  • 分类:漏洞分析

0 条评论

3r1cCheng
3r1cCheng

3 篇文章

站长统计