PHP代码审计之dedecms
2023-2-12 22:52:0 Author: xz.aliyun.com(查看原文) 阅读量:32 收藏

框架介绍:

本项目为dede自研系统,其中所有功能点都由dede自开发,访问对应PHP文件即可找到对应功能点。
大部分文件都包含了/dede/config.php,该文件中检测了用户的登录状态,且对于XSS进行了过滤。

1.任意文件写入

通过搜索 fwrite() 函数,发现这里存在可疑写入点,而我们知道得知dede的路由可以通过直接访问

我们可以看到这里代码70行的变量 $configstr 是经过序列化的 $faqs 变量传入的,那么这里的 $faqs
这个数组是怎么传入的。

直接访问该文件发现如下功能点,而这里的验证码设置功能处正是传入参数的地方。

这里抓包我们可以清楚的看到此处参数可控的地方。

但在代码的第60行是存在过滤的,这里当你 question 参数传入单引号时会被正则替换为双引号没办法
进行闭合,而 quswer 参数则并没有任何限制,反而帮我们删除了反斜杠,所以这里 answer 参数处就存在绕过。

漏洞复现:


2.目录遍历

搜索 read() 函数,发现在危险函数中存在参数可控的地方,我们进入 select_images.php 文件。

漏洞复现:

3.数据库操作getshell(1)

在测试功能点模块管理时,发现一处可以操作数据库的地方,我们点击修改。

其实我们可以直接在删除程序处输入我们想要执行的SQL语句进行测试
这里传入了两个参数 action 以及 hash。

这里通过uninstall执行该处分支,然后通过上面传入的hash参数值来定位文件, GetFileLists() 获取

跟进 GetFileLists() 函数。

在该函数中首先包含了 modulescache.php 文件,并通过 $hash 返回xml文件内容。

modulescache.php 文件内容。

找到该xml,查看该xml内容,该文件内容中就是相应模块的配置信息。

回到上面代码的的第425行,又执行了 uninstallok 分支。该分支就是卸载模块的分支,这里最重
要的是通过 GetSystemFile() 获取文件内容,这里传入了另一个参数 delsql ,跟进该函数

通过 GetHashFile() 函数获取文件内容。

这里的delsql内容中正是删除程序处的文件的base64内容

最后在代码518行执行SQL语句,所以我们可以通过Mysql日志文件来getshell,而这里需要网站的绝对
路径,由于存在目录遍历,所以可以轻松获取绝对路径。

漏洞复现:

最后访问cyw.php,获取到phpinfo信息。

4.任意文件写入漏洞(2)

同样的方法,在搜索危险函数 fwrite() 时发现在 article_string_mix.php 中存在可以控制写入内容
的地方。

在 article_string_mix.php 中 $allsource 写入的内容是可控的,但在代码29-34行中存在危险函数
过滤,但在过滤函数中过滤不严格导致绕过,实现代码执行。

漏洞复现:

通过 preg_replace() 绕过危险函数过滤,造成代码执行。

写入downmix.data.php

访问dede/article_template_rand.php文件,执行phpinfo()。

5.任意文件写入漏洞(3)

这里依旧是同样的功能点,所以大家看到这里其实也可以发现,在dedecms中使用 fwrite() 函数写入
配置文件的操作是很多的,在该系统中我们只需要观察两点即可,一写入的位置为可解析的php文件,
二写入的内容我们是可控的即可。

在函数 ReWriteConfig() 中,我们可以发现代码的第38行和第42行都是用了 fwrite() 函数,向上回
溯发现该处的值是从代码31行的 sysconfig 表中拿到的,我们去看看该表中的内容。

存储了网站的配置信息,上面的代码中可以看到type为 number 时会将 varname 字段
的值以及value字段的值写入配置文件,如果类型不为type时,这里会存在一个小小的过滤,这里的单引
号会被替换为空。
1.我们可以直接在type=number类型的字段中直接插入我们要执行的代码
2.通过\反斜杠来转义原本的单引号,然后使我们的代码逃逸出来

在 dopost 参数为 save 时,我们可以发现这里接收了post传入的参数,并且将传入的内容写入到了
sysconfig 表中。在代码74行中执行了上面分析的 ReWriteConfig() 函数。

漏洞复现:

我们访问/dede/sys_info.php文件,发现该处功能点确实是配置系统参数处,所以我们直接在
type=number参数处插入我们的代码。

第二种方法可以通过\反斜杠转义原有的单引号,使我们的代码逃逸出来。

这样的话也可以造成代码执行,只不过方法不同。

6.数据库操作getshell(2)

在测试系统功能点的时候,发现一处可以操作数据库的地方,下面的功能可以执行SQL语句。

漏洞分析:

这里执行到query分支,108-111行这里存在过滤,这里过滤了drop关键字,116行执行我们SQL语句。

漏洞复现:

set global general_log = on;
set global general_log_file = 'D:/phpstudy_pro/WWW/www.dedecms12.com/uploads/shell.php';

7.文件上传漏洞

该处具有前端限制,上传 .jpg 后缀文件,结合brup抓包,发现处理上传功能的文件为dede/archives_do.php

入口文件通过 config.php 会实现权限认证和一些外部参数过滤注册
我们这里上传文件会带有$_FILES参数,上面通过全局分析得知会触发uploadsafe.inc.php的过滤
在经过过滤后,通过AdminUpload()实现最终文件上传

include/helpers/upload.helper.php

最终实现文件上传的AdminUpload()来自upload.helper.php,传入AdminUpload()的$ftype固定为imagelit,则一定会进入对应的检测判断,在检测判断代码中,$sparr定义了一个MIME Type白名单,若上传文件的MIME Type不在白名单中直接退出,MIME Type可控。

梳理一下,该功能点,系统只做了两个限制,MIMI Type为图片类型,可控。但MIME Type为图片类型时会通过getimagesize()检测,这里也可绕过,看是否可以利用保证Content-Type为图片类型,构造图片的文件头,绕过文件上传的限制,并且会返回上传文件名和路径。

漏洞复现:

进入添加文档,该功能可以发布文章,而且具有文件上传的功能。

8.xss漏洞

测试发现还是黑盒好测一点,在dedecms后台还是存在很多xss的,本次是在黑盒测试后,在回头审计代码的问题,其实这样白盒审计意义不大,在全局分析中发现并没有对外部数据做xss全局过滤,另外注意到dedecms具有视图类负责显示输出,封装了很多输出的功能,在平时白盒审计xss漏洞需要注意echo,innerHTML这类输出到前端的关键词,但在dedecms中还需要注意视图类封装的输出函数
qrcode.php及加载的文件都没有做xss过滤,通过common.inc.php会注册全局变量

$id只能为整数类型,$type类型可控
加载模板qrcode.htm,利用视图类格式化输出$id,$type的值,$type可控,这里就存在xss漏洞
可以看到这里的触发点$dtp->SetVar('type',$type);,然而在seay这种代码扫描工具中是不会在意这些点的,同样有些框架对sql操作也做了很好的封装,如果只是依靠seay的结果来做代码审计,可能会忽略掉很多关键点

9.url 重定向漏洞

eay似乎没有 url 重定向漏洞的扫描,不过该漏洞审计也比较简单,主要关注能重定向的一些关键词,再看重定向地址是否可控
plus/download.php
对$link做了base64解码
程序中有一个很奇怪的限制,in_array($linkinfo['host'], $allowed),然而download.php中却没有$linkinfo这个参数dedecms后台也有一些url重定向漏洞,这里就不多关注这个洞了

10.会员中心任意用户密码修改

在用户密码重置功能处,php存在弱类型比较,导致如果用户没有设置密保问题的情况下可以绕过验证密保问题,直接修改密码(管理员账户默认不设置密保问题)。值得注意的是修改的密码是member表中的密码,即使修改了管理员密码也是member表中的管理员密码,仍是无法进入管理。

漏洞代码分析

php弱类型比较问题很常见,在不同类型比较时,如果使用的是==,php会将其中一个数据进行强制转换为另一个,比如'123a'就会被强制转换成123。这样就出现了弱类型比较问题,当然如果使用===判断比较就不会出现问题了。常见比较如下

'' == 0 == false '123' == 123             //'123'强制转换为123 
'abc' == 0         //intval('abc')==0 
'123a' == 123            //intval('123a')==123 
'0x01' == 1             //被识别为十六进制
'0e123456789' == '0e987654321'  //被识别为科学计数法 
[false] == [0] == [NULL] == [''] 
NULL == false == 0 
true == 1

dedecms的/member/resetpassword.php就是用来处理用户密码重置的问题,问题出在75行开始处理验证密保问题处。

这段代码先是从数据库取出相关用户的密保问题及密保答案,在对用户输入做了一些处理后,进行了关键性的判断if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer) ,就在这里用了弱类型判断==。首先我们知道,如果没有设置密保的话safequestion从数据库取出默认为'0',safeanswer为空。根据empty函数特性,'0'会被判断为空,会进入重新将$safequestion赋值为''。而'0' != '' ,所
跟踪sn函数


跟踪newmail。

可见在sn函数中将send参数设置了'N',其实就是生成了暂时密码并插入了数据库中,并进行跳转:

漏洞复现:

在找回密码处,点击通过安全问题取回。

11.会员中心任意用户登陆

dedecms的会员模块的身份认证使用的是客户端session,在Cookie中写入用户ID并且附上
ID__ckMd5

漏洞代码分析
在/member/index.php中会接收uid和action参数。uid为用户名,进入index.php后会验证Cookie中的用户ID与uid(用户名)并确定用户权限。

我们可以看到当uid存在值时就会进入我们现在的代码中,当cookie中的last_vid中不存在值为空时,就会将uid值赋予过去,$last_vid = $uid;,然后PutCookie。
那么这么说,我们控制了$uid就相当于可以返回任意值经过服务器处理的md5值。

现在我们来看看,dedecms会员认证系统是怎么实现的:/include/memberlogin.class.php
$this->M_ID等于Cookie中的DedUserID,我们继续看看GetCookie函数它不但读了cookie还验证了md5值。

这样,由于index.php中我们可以控制返回一个输入值和这个输入值经过服务器处理后的md5值。那么如果我们伪造DedUserID和它对应的MD5就行了。

漏洞复现:

  1. 先从member/index.php中获取伪造的DedeUserID和它对于的md5
  2. 使用它进行登录
    访问member/index.php?uid=0000001并抓包(注意cookie中last_vid值应该为空)。

REF:
https://www.freebuf.com/articles/web/281747.html
https://blog.szfszf.top/article/25/
https://github.com/SukaraLin/php_code_audit_project/blob/master/dedecms/dedecms%20v5.7%20sp2%20%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1.md


文章来源: https://xz.aliyun.com/t/12148
如有侵权请联系:admin#unsafe.sh