PHP-Audit-Labs
1 |
|
漏洞解析 :
根据题目意思,这里考察的应该是个 xss漏洞 , 漏洞触发点应该在代码中的 第13-14行 。这两行代码的作用是直接输出一个html的 <a>
标签。代码中的 第3-5行 ,foreach循环 对 $_GET 传入的参数进行了处理,但是这里有个问题。我们看下 第四行 的代码,这行代码针对 $value 进行类型转换,强制变成int类型。但是这部分代码只处理了 $value 变量,没针对 $key 变量进行处理。经过了 第3-5行 的代码处理之后,根据 & 这个符号进行分割,然后拼接到 第13行 的 echo 语句中,在输出的时候又进行了一次 htmlentities 函数处理。 htmlentities 函数主要是会对一些特殊符号进行HTML实体编码。具体定义如下:
htmlentities — 将字符转换为 HTML 转义字符
1
2 > string htmlentities ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode = true ]]] )
>
作用:在写PHP代码时,不能在字符串中直接写实体字符,PHP提供了一个将HTML特殊字符转换成实体字符的函数 htmlentities()。
注:htmlentities() 并不能转换所有的特殊字符,是转换除了空格之外的特殊字符,且单引号和双引号需要单独控制(通过第二个参数)。第2个参数取值有3种,分别如下:
- ENT_COMPAT(默认值):只转换双引号。
- ENT_QUOTES:两种引号都转换。
- ENT_NOQUOTES:两种引号都不转换。
这里附上一个 HTML 中有用的字符实体表
经过上面的分析,我们再回到题目,想想如何构造一下攻击 payload 。我们先梳理一些已知信息:
- 这里的 $query 参数可控
- 且 htmlentities 函数在这里可逃逸单引号
- xss的漏洞触发点在
<a>
标签。
在 <a>
中,我们可以通过 javascript 事件来执行js代码,例如: onclick 这类事件,因此最后的poc构造如下:
1 | /?a'onclick%3dalert(1)%2f%2f=c |
实例分析
本次实例分析选择 DM企业建站系统 v201710 中的 sql注入漏洞 来进行分析。首先,我们可以从cnvd上面看到一些相关信息,如下:
从漏洞通告中可以发现一些有用的信息,漏洞位置在登陆处,搭建的时候提示后台登陆口位置在 admindm-yourname/g.php 文件中,打开这个文件,发现重定向到 admindm-yournamemod_common/login.php 文件中,所以漏洞触发点应该就在这个文件中。
打开 admindm-yournamemod_common/login.php 这个文件,一眼就看到漏洞位置,截取部分相关代码如下:
第15行 很明显存在sql注入漏洞,通过拼接的方式直接插入到select语句中。 第15行 中的 $user 变量是通过 POST 方式提交上来,其值可控。但是上图的 第3行 代码调用 htmlentitiesdm 函数,对 POST 数据进行了处理,我们跟进这个 htmlentitiesdm 函数。该函数位置在 component/dm-config/global.common.php 文件中,截取关键代码如下:
这个函数是调用 htmlentities 函数针对输入的数据进行处理。前面我们已经介绍过了这个函数的用法,这里这个函数的可选参数是 ENT_NOQUOTES ,也就是说两种引号都不转换。下面我们来看个小例子:
这里我猜测开发者应该是考虑到了xss的问题,但是由于 htmlentities 这个函数选择的参数出现了偏差,导致这里我们可以引入单引号造成注入的问题。
我们看看最新版是怎么修复,使用 beyond compare 对比两个版本代码的差别。
新版修复的时候将可选参数修改为 ENT_QUOTES ,这个参数的作用就是过滤单引号加双引号,我们来看看下面这个例子,就很容易明白了这个参数的作用了。
漏洞验证
这里因为没有回显,所以是盲注,下面是验证截图:
漏洞修复
针对 htmlentities 这个函数,我们建议大家在使用的时候,尽量加上可选参数,并且选择 ENT_QUOTES 参数。
我们看看对比的效果
CTF
index.php
1 |
|
sql.sql
1 | # Host: localhost (Version: 5.5.53) |
从代码 第27行 很明显,这道题考查sql注入,但是这里有两个考察点,我们分别来看一下。
第一部分
第23行 和 第24行 针对 GET 方式获取到的 username 和 password 进行了处理,处理函数为 clean 。该函数在 第16-20行 处定义,函数的主要功能就是使用 htmlentities 函数处理变量中带有的特殊字符,而这里加入了 htmlentities 函数的可选参数 ENT_QUOTES ,因此这里会对 单引号 , 双引号 等特殊字符进行转义处理。由于这里的注入是字符型的,需要闭合单引号或者逃逸单引号,因此这里需要绕过这个函数。我们可以通过下面这个例子观察 clean 函数的处理效果:
题目 第36行 是进入数据库查询,并且返回 name 列字段的值。而这里的sql语句是这样的:
1 | $query='SELECT * FROM ctf.users WHERE name=\''.$username.'\' |
那我们如果输入的 username 是 admin , password 是 admin ,自然就构成了正常要执行的sql语句。
这道题的问题就在于可以引入反斜杠,也就是转义符,官方针对 转义符 是这么解释的。
比如,如果你希望匹配一个 "*" 字符,就需要在模式中写为
\*
。 这适用于一个字符在不进行转义会有特殊含义的情况下。
这里我们看个简单的例子理解一下这个转义符号。
转义符号会让当前的特殊符号失去它的作用,这道题由于可以引入反斜杠,也就是转义符号,来让
1 | $query='SELECT * FROM ctf.users WHERE name=\''.$username.'\' |
username后面的 ' 失效,只要这个 ' 失效,就能闭合pass=后面的 '。最后组合的payload就如下图所示
所以实际上目前 name 的值是 admin' AND pass= ,这时候 password 的值是一个可控的输入点,我们可以通过这个值来构造 sql 的 联合查询 ,并且注释掉最后的 单引号 。
最后我们看看在mysql中执行的结果。
第二部分
好了第一部分我们其实已经成功构造好了payload,但是回头来看看题目,题目 第6行 到 第16行 有两个正则表达式,作用就是如果参数中带有 or、and 、union 等数据,就退出,并输出 Attack detected!!!
这里当然我们可以正面硬刚这个正则表达式。但是这里我们来聊一个比较有趣的解法。
我们看到是通过 request 方式传入数据,而php中 REQUEST 变量默认情况下包含了 GET ,POST 和 COOKIE 的数组。在 php.ini 配置文件中,有一个参数 variables_order ,这参数有以下可选项目
1 | ; variables_order |
这些字母分别对应的是 E: Environment ,G:Get,P:Post,C:Cookie,S:Server。这些字母的出现顺序,表明了数据的加载顺序。而 php.ini 中这个参数默认的配置是 GPCS ,也就是说如果以 POST 、 GET 方式传入相同的变量,那么用 REQUEST 获取该变量的值将为 POST 该变量的值。
我们举个简单的例子方便大家理解:
我们可以看到这里的 post 方式传入的数据覆盖了 get 方式传入的数据,因此这里最后的payload如下: