PHP代码审计之wuzhicms

举报
亿人安全 发表于 2023/03/15 17:03:00 2023/03/15
【摘要】 环境搭建漏洞审计路由分析:在根目录index.php文件中包含 coreframe 框架下core.php文件并调用load_class加载application.class.php 文件core.php 框架核心文件然后调用了load_class加载类。然后通过load_class()类实例化wuzhi_application类、通过 load_class() 类实例化 WUZHI_app...

环境搭建

漏洞审计

路由分析:

在根目录index.php文件中包含 coreframe 框架下core.php文件并调用load_class加载

application.class.php 文件

core.php 框架核心文件

然后调用了load_class加载类。然后通过load_class()类实例化wuzhi_application类、

通过 load_class() 类实例化 WUZHI_application 类。既然实例化 WUZHI_application 类就会调用下面构造方法将路由信息定义完成

调用 WUZHI_application 类下 run() 方法获取路由信息

通过注解我们可以发在这里通过传入三个参数m、f、v来定位php文件,下面我们可以通过抓包去查看

他的路由访问规律

1.sql注入

通过上面的路由分析我们就可以通过Burp定位到该文件 /coreframe/app/member/admin/group.php

下的 del() 函数

在代码133行判断是否传入groupid参数且参数是否为空,条件满足在134行代码中判断传入的groupid

是否为数组,也就是判断是否为批量删除操作,这里不管传入的是不是数组都会调用delete()函数去执行

数据库操作。

跟进delete()函数,在db.class.php中找到对应函数,在该函数中又调用了两个函数去处理SQL语句,

array2sql()函数在注解中也可以看到是将组数转化为SQL格式并没有实际操作数据库。

而下面223行的delete()函数才是真正去操作数据库的函数,我们跟进这个delete(),这个函数是

mysqli_class.php 下 WUZHI_mysqli 类下的方法,该类封装了所有增删改查等数据库操作方便后续的代

码调用。在这个过程中我们传入的sql语句并没有进行任何过滤就直接拼接到$sql并通过query()执行,所以这里存

在SQL注入漏洞。

在query()函数中如果出现SQL语句错误会直接爆出SQL语句,所以这里存在报错注入。

漏洞复现:

2.任意文件写入

或者全局搜索file_put_contents函数

通过Seay的审计结果,翻找到一处可能存在任意文件写入的地方,我们去看下该函数的执行流程是怎么

样的。通过注解我们可以大致了解到该函数是通过 file_put_contents() 函数来将数组或字符串写入缓存文

件,如果这里的 $data 内容可控通过翻找发现一处可以直接控制 $data 的文件:

oreframe/app/attachment/admin/index.php

这里的 ueditor() 函数调用了 set_cache() 并且这里的 $GLOBALS['setting'] 参数可控,在代码153

行处只判断了是否设置了 submit 参数,如果设置则在代码155行处,会将 setting 参数也就是上面所

说的 $data 部分写入造成任意文件写入。

如果不设置submit,则会通过 get_cache() 读取缓存文件内容。

漏洞复现:

通过前面路由分析以及上面SQL注入的路由,我们可以构造出调用ueditor()的路由:

/index.php?

m=attachment&f=index&v=ueditor&_su=wuzhicms&submit=XXX&setting=payload

文件已被成功写入到缓存文件中

读取缓存中文件内容

/index.php?m=attachment&f=index&v=ueditor&_su=wuzhicms

3.csrf

在测试功能点: 系统设置 => 权限管理 => 添加管理员 处我们点击提交

通过提示我们了解到必须存在一个前台账户,然后才可以将前台账户绑定为系统管理员,而前台账户是可

以随便注册的。


/index.php?m=core&f=power&v=add&&_su=wuzhicms

通过路由找到对应源码

从最上面的注解就可以看出 add() 函数是用于添加管理员的,35-37行代码判断是否点击提交操作,然

后判断用户名是否为空,不为空则将传入的 username 值赋值给 $username ,在代码38行处根据用户名

从数据库中取出前台账户,如果该前台用户不存在则会提示如下内容,也就是我们上面的提示,在40行

代码判断该用户是否已经是管理员,而44-51行代码是判断添加管理员时是否设置密码,这部分操作在上

面部分我们添加管理员的时候也可以看得出来,在代码54行将我们的内容插入到数据库中。

大家可以看的出这部分代码在操作过程中并没有使用随机token或者其他手段进行校验,所以这里我们

就可以伪造数据包进行CSRF。

/index.php?m=core&f=power&v=add&&_su=wuzhicms

coreframe/app/core/admin/power.php

4.目录遍历

通过全局搜索 glob 函数发现一处功能点处可能存在目录遍历,这里的 glob() 函数中 $dir 参数可控,

但大家仔细去观察18行代码这里存在一些小小的过滤。

glob()

定义和用法:

该函数返回一个包含匹配指定模式的文件名或目录的数组。

语法:

glob(pattern,flags)

  pattern 必需。规定检索模式。

flags 可选。规定特殊的设定。

返回值:

该函数返回一个包含有匹配文件/目录的数组。如果失败则返回 FALSE。上面代码29行通过 template() 函数渲染 listing.tpl.php 文件,将我们目录下的内容遍历出来并显

示在前端页面。

然后添加我们所需要的 dir 参数,由于上面的代码存在过滤且这里仅仅是将传入的内容替换为空而且只替

换一次,所以我们可通过 ...../// 的形式来绕过该处的过滤实现目录遍历

5.任意文件删除

我们通过全局搜索危险函数 unlink() ,发现该处存在一个 my_unlink() 函数调用了 unlink() 函数并

且通过注解也可以大致了解到该函数是用来删除附件的,这里的 $path 参数传递需要删除的文件。

接下来我们的思路是两点:一是看哪些功能点调用了 my_unlink() ,二是看调用处 $path 是否可以控

制。

通过上面的思路,我们 Ctrl+鼠标左键 点击该函数,发现有三处功能点调用了 my_unlink() 函数。

这里我们发现其中一处 del() 函数调用了该功能点进行文件删除,接下来我们只需要看这里

my_unlink() 函数中的参数($path)是否可控就可以了。上面的176、177行代码需要我们传入两个参数id、url,这里需要看看177行代码处的remove_xs()函数中

进行了哪些过滤。

从注释以及代码进行分析这里是对我们传入的内容进行实体编码,下面还过滤了一些js的标签,其实目

的是为了过滤XSS的,在这里其实并没有后对.、/这些特殊字符做限制,所以不影响我们接下来的利用。

通过上面一系列的过滤最终返回我们过滤后的内容。我们再接着看代码,我们如果传入id参数,这里的path参数是从数据库中拿到的,我们不好去控制如果不传入id参数则会走下面的else,在207行代码处判断我们传入的 path 是否在数据库中,如果不在就

会进入208行代码,对我们传入的 $path 进行删除。

通过上面的代码分析,到这里其实我们只需要构造一个不存在的path,就可以实现任意文件删除了。

漏洞复现:

我们看到数据包中的路由其实与我们分析的代码是一样的,在找不到功能点的情况下我们也可以轻松构

造出该路由。

GET /www/index.php?v=del&url=qr_image/mobile.png&m=attachment&f=index&_su=wuzhicms&_menuid=29&_submenuid=52 HTTP/1.1
Host: www.wuzhicms.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://www.wuzhicms.com/www/index.php?dir=.%2Fqr_image&m=attachment&f=index&v=dir&_su=wuzhicms&_menuid=29&_submenuid=52
Cookie: PHPSESSID=k652a136tnltn7n1obr88jo5h6; fbp_uid=QO%2BEiNPcY77ztIj3VwL07Q%3D%3D; fbp_username=qPFUCdQQfkRNinLQXE3qZA%3D%3D; fbp_wz_name=CfApQiRkuXByfw7AqHDG5A%3D%3D; fbp_siteid=MCakL%2FitF2Mz8zst4dyFvQ%3D%3D; fbp_userkeys=u0CzDoL6HLYQwPI%2Ffgo2bg%3D%3D
Upgrade-Insecure-Requests: 1

在更目录下创建一个 ceshi.txt 用于测试

成功将test.txt删除。


总结:

1.上述目录遍历漏洞只是列举了其中的某一处功能点,该系统存在多处目录遍历漏洞,大家可通过代码审计的

方式将其余的漏洞点找出,审计的方法不做限制。

2.以下功能点出存在上述类似的SQL注入问题,请大家结合功能点对应源码进行分析找出该处注入。

第一处 sql注入(可惜是后台的)

先直接全局搜索select

这个函数 展示没有发现有过滤
然后找哪里调用了这个函数

通过全局搜索 在这个地方 发现调用了这个函数
然后查看传递的参数
主要传递的是55行这个$where参数 传到了函数
然后我们看$where参数的组成 里面有两个变量$siteid $keywords
我们是可能可控的
先看$keywords 因为这个没调用函数 但是调用了一个$GLOBALS来获取值
这里就又要介绍下$GLOBALS 因为刚开始我也没懂这个是怎么用来获取值的 知道我百度一阵之后
和代码翻翻之后 在一个文件中发现了通过全局搜索 在这个地方 发现调用了这个函数
然后查看传递的参数
主要传递的是55行这个$where参数 传到了函数
然后我们看$where参数的组成 里面有两个变量$siteid $keywords
我们是可能可控的
先看$keywords 因为这个没调用函数 但是调用了一个$GLOBALS来获取值
这里就又要介绍下$GLOBALS 因为刚开始我也没懂这个是怎么用来获取值的 知道我百度一阵之后
和代码翻翻之后 在一个文件中发现了在这个文件看见了广告管理的注释
那我就去后台找这个功能了


如果现在到过头来看 就一个简单的搜索框的注入没什么花里胡哨的 过滤也没 但审计来看 还是绕了一大圈子

第二处 前台sql注入

还是在搜索select的时候 发现在mysql.class文件下有一个函数里面有select 并且后面的拼接也没有任何的过滤

然后我们搜索哪里调用了这个函数
首先是在api目录下的sms_check文件中发现调用了get_one函数 并且参数是通过前面的$code拼接我们可以看到code 先是通过$GLOBALS来获取参数param的值 从前面的介绍可以知道 $GLOBALS是可以获取post get的值 这个文件前面没有定义param变量 那么 这个param应该就是post 或者get 就是我们可控的 这也是导致注入的点

code还通过strip_tags() 函数 而这个函数的作用是剥去html标签 应该是过滤xss吧大概
之后就直接传入了函数 继续更进函数 因为这个文件前面还引入了db类


这个函数应该是调用的这个文件里面的
来到这个文件可以看到这个get-one函数里面 还调用了一个array2sql函数来处理$where
那先来看看这个函数的作用
可以看到这个函数是用来过滤的
如果是数组 这进入if 把括号 单引号这些过滤掉
不是则走else 过滤 %20 %27
然后返回参数
但也就是这个过滤的地方 没有防护到位
我们传的参数不是数组 所以就没有走if
而else里面过滤的却是 %20 %27
我们传参的时候尽管是经过url编码的 但是web服务器会自动解码一次 所以 我们传到后端代码处的时候是没有进行url编码 相当于
但是二次编码的就不一样了 因为web服务器只解码一次
如果是二次编码这里的else过滤就起效果

return 调用的get_one 则是最开始看见的mysql.class文件里面了

下面就可以开始直接构造payload了 这里通过代码分析可以看到是单引号闭合

第三处 后台sql注入

从前面两个分析 我发现的注入的地方就存在两个函数中get_list get_one
然后直接全局搜索这两个函数 看看什么地方调用

可以看到 在copyfrom.php中listing函数下调用了这个函数
然后我们网上分析 看看什么是可控的
主要传进去的就一个$where 和 $page

可以看到page会被intval()函数 转化为整数 所以我们不考虑它
我们看看where 在if内部 想要进入if 就需要通过GLOBALS获取到keywords
相当于就要传参嘛
然后在看里面 就没有过滤这些 直接拼接
这里也可以看出 闭合方式是百分号单引号 %'我们在来到mysql文件中定义的这个函数 也可以看到 是对where没有过滤处理的
那么 有了前面的基础 直接来构造payload:http://192.168.1.7/wuzhicms/index.php?m=core&f=copyfrom&v=listing&_su=wuzhicms&keywords=%27

rce漏洞

源代码分析:/wuzhicms/coreframe/app/attachment/admin/index.php

当用户传递动态函数sumit时,web程序会引用set_cache()方法,其中M为当前模块名attachment,继续跟进:

$cache_path为web服务器的缓存路径目录,$filename为缓存文件名,然后通过file_put_contents方法,毫无过滤地将$data写入到缓存文件中,那么如果该缓存文件被包含,则可以造成RCE的效果,而且一般来说缓存文件都是会被web程序利用的。按照这个思路继续分析:

当对象创建时会调用魔术方法__construct(),然后会引入get_cache()方法,继续分析:get_cache()方法会包含并返回缓存文件,那么就可以造成RCE的效果了%20POC:/wuzhicms/www/index.php%3Fm=attachment%26f=index%26v=set%26_su=wuzhicms%26submit=1%26setting=<%253fphp%2Bsystem($GLOBALS%5B%27cmd%27%5D)%253b%253f>

敏感信息泄露

逻辑漏洞:

和上次看的zzcms2021类似的利用方法,具体可以参考https://xz.aliyun.com/t/10432
www\api\uc.php中:

通过传参可以调用uc_note类的任意方法:当对象创建时会调用魔术方法__construct(),然后会引入get_cache()方法,继续分析:get_cache()方法会包含并返回缓存文件,那么就可以造成RCE的效果了%20POC:/wuzhicms/www/index.php%3Fm=attachment%26f=index%26v=set%26_su=wuzhicms%26submit=1%26setting=<%253fphp%2Bsystem($GLOBALS%5B%27cmd%27%5D)%253b%253f>

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。