-
Notifications
You must be signed in to change notification settings - Fork 337
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
55 changed files
with
473 additions
and
13 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
## 前言 | ||
|
||
大家好,我们是红日安全-代码审计小组。最近我们小组正在做一个PHP代码审计的项目,供大家学习交流,我们给这个项目起了一个名字叫 **PHP-Audit-Labs** 。我们已经发表的系列文章如下: | ||
|
||
[[红日安全]代码审计Day1 - in_array函数缺陷](https://xz.aliyun.com/t/2451) | ||
|
||
[[红日安全]代码审计Day2 - filter_var函数缺陷](https://xz.aliyun.com/t/2457) | ||
|
||
[[红日安全]代码审计Day3 - 实例化任意对象漏洞](https://xz.aliyun.com/t/2459) | ||
|
||
[[红日安全]代码审计Day4 - strpos使用不当引发漏洞](https://xz.aliyun.com/t/2467) | ||
|
||
在每篇文章的最后,我们都留了一道CTF题目,供大家练习。下面是 **Day1-Day4** 的题解: | ||
|
||
## Day1题解:(By 七月火) | ||
|
||
题目如下: | ||
|
||
![1](1.png) | ||
|
||
![2](2.png) | ||
|
||
实际上这道题目考察的是 **in_array** 绕过和不能使用拼接函数的 **updatexml** 注入,我们先来看一下 **in_array** 的绕过。在下图第11~13行处,程序把用户的ID值存储在 **$whitelist** 数组中,然后将用户传入的 **id** 参数先经过stop_hack函数过滤,然后再用 **in_array** 来判断用户传入的 **id** 参数是否在 **$whitelist** 数组中。这里 **in_array** 函数没有使用强匹配,所以是可以绕过的,例如: **id=1'** 是可以成功绕过 **in_array** 函数的。 | ||
|
||
![3](3.png) | ||
|
||
然后在说说 **updatexml** 注入,这题的注入场景也是在真实环境中遇到的。当 **updatexml** 中存在特殊字符或字母时,会出现报错,报错信息为特殊字符、字母及之后的内容,也就是说如果我们想要查询的数据是数字开头,例如 **7701HongRi** ,那么查询结果只会显示 **HongRi** 。所以我们会看到很多 **updatexml** 注入的 **payload** 是长这样的 **and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)** ,在所要查询的数据前面凭借一个特殊符号(这里的 **0x7e** 为符号 **'~'** )。 | ||
|
||
回到题目,我们看一下 **stop_hack** 函数过滤了什么。可以发现该方法过滤了字符串拼接函数(下图第2行),所以我们就要用其他方法来绕过。 | ||
|
||
![4](4.png) | ||
|
||
我们直接来看一下本题的 **payload** : | ||
|
||
```http | ||
http://localhost/index.php?id=4 and (select updatexml(1,make_set(3,'~',(select flag from flag)),1)) | ||
``` | ||
|
||
![5](5.png) | ||
|
||
实际上,绕过的思路还是将特殊字符或字母拼接在我们想要的数据的前面,让数据的第一个字符为字母或符号即可,这里给出我以前写的分析 [**文章**](https://xz.aliyun.com/t/2160) ,供大家参考学习。 | ||
|
||
## Day2题解:(By 七月火) | ||
|
||
题目如下: | ||
|
||
![6](6.png) | ||
|
||
这道CTF题目,实际上考察的是 **filter_var** 函数的绕过与远程命令执行。在题目 **第6行** ,程序使用 **exec** 函数来执行 **curl** 命令,这就很容易让人联系到命令执行。所以我们看看用于拼接命令的 **$site_info['host']** 从何而来。在题目 **第2-4行** ,可以看到 **$site_info** 变量是从用户传来的 **url** 参数经过 **filter_var** 和 **parse_url** 两个函数过滤而来。之后,又规定当 **url** 参数的值以 **sec-redclub.com** 结尾时,才会执行 **exec** 函数。 | ||
|
||
所以让我们先来绕过 **filter_var** 的 **FILTER_VALIDATE_URL** 过滤器,这里提供几个绕过方法,如下: | ||
|
||
```bash | ||
http://localhost/index.php?url=http://[email protected] | ||
http://localhost/index.php?url=http://demo.com&sec-redclub.com | ||
http://localhost/index.php?url=http://demo.com?sec-redclub.com | ||
http://localhost/index.php?url=http://demo.com/sec-redclub.com | ||
http://localhost/index.php?url=demo://demo.com,sec-redclub.com | ||
http://localhost/index.php?url=demo://demo.com:80;sec-redclub.com:80/ | ||
http://localhost/index.php?url=http://demo.com#sec-redclub.com | ||
PS:最后一个payload的#符号,请换成对应的url编码 %23 | ||
``` | ||
|
||
接着要绕过 **parse_url** 函数,并且满足 **$site_info['host']** 的值以 **sec-redclub.com** 结尾,payload如下: | ||
|
||
```bash | ||
http://localhost/index.php?url=demo://%22;ls;%23;sec-redclub.com:80/ | ||
``` | ||
|
||
![7](7.png) | ||
|
||
当我们直接用 **cat f1agi3hEre.php** 命令的时候,过不了 **filter_var** 函数检测,因为包含空格,具体payload如下: | ||
|
||
```bash | ||
http://localhost/index.php?url=demo://%22;cat%20f1agi3hEre.php;%23;sec-redclub.com:80/ | ||
``` | ||
|
||
所以我们可以换成 **cat<f1agi3hEre.php** 命令,即可成功获取flag: | ||
|
||
![8](8.png) | ||
|
||
关于 **filter_var** 函数绕过更多的细节,大家可以参考这篇文章:[SSRF技巧之如何绕过filter_var( )](https://www.anquanke.com/post/id/101058) ,关于命令执行绕过技巧,大家可以参考这篇文章:[浅谈CTF中命令执行与绕过的小技巧](http://www.freebuf.com/articles/web/137923.html) 。 | ||
|
||
## Day3题解:(By 七月火) | ||
|
||
题目如下: | ||
|
||
![9](9.png) | ||
|
||
这道题目考察的是实例化漏洞结合XXE漏洞。我们在上图第18行处可以看到使用了 **class_exists** 函数来判断类是否存在,如果不存在的话,就会调用程序中的 **__autoload** 函数,但是这里没有 **__autoload** 函数,而是用 [**spl_autoload_register**](http://php.net/manual/en/function.spl-autoload-register.php) 注册了一个类似 **__autoload** 作用的函数,即这里输出404信息。 | ||
|
||
我们这里直接利用PHP的内置类,先用 **GlobIterator** 类搜索 **flag文件** 名字,来看一下PHP手册对 **GlobIterator** 类的 构造函数的定义: | ||
|
||
>public **GlobIterator::__construct** ( string `$pattern` [, int `$flags` = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO ] ) | ||
第一个参数为要搜索的文件名,第二个参数为选择文件的哪个信息作为键名,这里我选择用 **FilesystemIterator::CURRENT_AS_FILEINFO** ,其对应的常量值为0,你可以在 [这里](http://php.net/manual/en/globiterator.construct.php) 找到这些常量的值,所以最终搜索文件的 **payload** 如下: | ||
|
||
```bash | ||
http://localhost/CTF/index.php?name=GlobIterator¶m=./*.php¶m2=0 | ||
``` | ||
|
||
![10](10.png) | ||
|
||
我们将会发现flag的文件名为 **f1agi3hEre.php** ,接下来我们使用内置类 **SimpleXMLElement** 读取 **f1agi3hEre.php** 文件的内容,,这里我们要结合使用PHP流的使用,因为当文件中存在: **< > & ' "** 这5个符号时,会导致XML文件解析错误,所以我们这里利用PHP文件流,将要读取的文件内容经过 **base64编码** 后输出即可,具体payload如下: | ||
|
||
```bash | ||
http://localhost/CTF/index.php?name=SimpleXMLElement¶m=<?xml version="1.0"?><!DOCTYPE ANY [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/CTF/f1agi3hEre.php">]><x>%26xxe;</x>¶m2=2 | ||
``` | ||
|
||
![11](11.png) | ||
|
||
上面payload中的param2=2,实际上这里2对应的模式是 **LIBXML_NOENT** ,具体可以参考 [这里](http://php.net/manual/en/simplexmlelement.construct.php) 。 | ||
|
||
## Day4题解:(By 七月火) | ||
|
||
本次题目为QCTF 2018中的一道题目,由于代码太多,这里就不贴出原图片。题目的场景为:一个彩票系统,每位用户初始情况下有20$,由用户输入一个7位数,系统也会随机生成一个7位数。然后逐位数字进行比较,位数相同的个数越多,奖励的前也越多。当你的钱足够买flag的时候,系统就会给你flag。 | ||
|
||
![12](12.png) | ||
|
||
我们来看一下后台代码是如何进行比较的,比较代码在 **buy.php** 文件中: | ||
|
||
![13](13.png) | ||
|
||
在上图中看到表单的部分( **代码4-8行** ),调用了 **js/buy.js** 文件,应该是用来处理上面的表单的,我们具体看一下 **js** 代码: | ||
|
||
![14](14.png) | ||
|
||
在 **第10行** 处看到,程序将表单数据以 **json** 格式提交到服务器端,提交页面为 **api.php** ,我们转到该文件看看。 | ||
|
||
![15](15.png) | ||
|
||
这里主要是对数字进行比较,注意 **第13行** 用的是 **==** 操作符对数据进行比较,这里会引发安全问题。因为用户的数据是以 **json** 格式传上来的,如果我们传一个数组,里面包含7个 **true** 元素,这样在比较的时候也是能相等的。因为 **==** 运算符只会判断两边数据的值是否相等,并不会判断数据的类型。而语言定义,除了 **0、false、null** 以外均为 **true** ,所以使用 **true** 和数字进行比较,返回的值肯定是 **true 。只要后台生成的随机数没有数字0,我们传入的payload就能绕过每位数字的比较。我们发送几次payload后,就可以买到flag了。 | ||
|
||
![16](16.png) | ||
|
||
在看官方WP的时候,还发现另外一种解法,也是一种不错的思路。 | ||
|
||
>另外比赛过程中发现有的选手用了暴力重复注册然后买彩票的方法。考虑了一下这种方法花费的时间并不比直接审计代码短,为了给广大彩民一点希望,可以留作一种备选的非预期解,就没有改题加验证码或者提高flag价格。 | ||
## 总结 | ||
|
||
我们的项目会慢慢完善,如果大家喜欢可以关注 [ **PHP-Audit-Labs** ](https://github.com/hongriSec/PHP-Audit-Labs) 。大家若是有什么更好的解法,可以在文章底下留言,祝大家玩的愉快! |
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
本文由红日安全成员: **水清云影** 编写,如有不当,还望斧正。 | ||
|
||
## 前言 | ||
|
||
大家好,我们是红日安全-代码审计小组。最近我们小组正在做一个PHP代码审计的项目,供大家学习交流,我们给这个项目起了一个名字叫 [**PHP-Audit-Labs**](https://github.com/hongriSec/PHP-Audit-Labs) 。现在大家所看到的系列文章,属于项目 **第一阶段** 的内容,本阶段的内容题目均来自 [PHP SECURITY CALENDAR 2017](https://www.ripstech.com/php-security-calendar-2017/) 。对于每一道题目,我们均给出对应的分析,并结合实际CMS进行解说。在文章的最后,我们还会留一道CTF题目,供大家练习,希望大家喜欢。下面是 **第6篇** 代码审计文章: | ||
|
||
## Day6 - Forst Pattern | ||
|
||
题目叫福斯特模式,代码如下 | ||
|
||
![1](1.png) | ||
|
||
**漏洞解析** : | ||
|
||
这一关考察的内容是由正则表达式不严谨导致的任意文件删除漏洞, 导致这一漏洞的原因在 **第21行** , **preg_replace** 中的 **pattern** 部分 ,该正则表达式并未起到过滤目录路径字符的作用。`[^a-z.-_]` 表示匹配除了 **a** 字符到 **z** 字符、**.** 字符到 **_** 字符之间的所有字符。因此,攻击者还是可以使用点和斜杠符号进行路径穿越,最终删除任意文件,例如使用 **payload** : `action = delete&data = ../../ config.php`,便可删除 **config.php** 文件。 | ||
|
||
>[**preg_replace**](http://php.net/manual/zh/function.preg-replace.php):(PHP 4, PHP 5, PHP 7) | ||
> | ||
>**功能** : 函数执行一个正则表达式的搜索和替换 | ||
> | ||
>**定义** : `mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )` | ||
> | ||
>搜索 **subject** 中匹配 **pattern** 的部分, 如果匹配成功将其替换成 **replacement** 。 | ||
##实例分析 | ||
|
||
本次实例分析,我们选取的是 **WeEngine0.8** 版本。漏洞入口文件为 **web/source/site/category.ctrl.php** ,我们可以看到下图 **14行** 处调用了 **file_delete** 函数,而这是一个文件删除相关操作,我们可以看一下该函数的具体定义。下图是入口文件代码: | ||
|
||
![2](2.png) | ||
|
||
**file_delete** 这一函数可以在 **framework/function/file.func.php** 文件中找到,该方法功能用于检测文件是否存在,如果存在,则删除文件。但是查看上下文发现,程序并没有对文件名 **$file** 变量进行过滤,所以文件名就可以存在类似 **../** 这种字符,这样也就引发任意文件删除漏洞,**file_delete** 函数代码如下: | ||
|
||
![3](3.png) | ||
|
||
现在我们在回溯回去,看看 **$file** 变量从何处来。实际上,上图的 **$file** 变量对应的是 **$row['icon']** 的值,也就是说如果我们可以控制 **$row['icon']** 的值,就可以删除任意文件。那么我们来看看 **$row** 变量从何而来。该变量就在我们刚刚分析的第一张图片中( **web/source/site/category.ctrl.php** 文件),该值为变量 **$navs** 中的元素值,具体代码如下: | ||
|
||
![4](4.png) | ||
|
||
我们再往上看,即可找到 **$navs** 变量的取值情况。可以看到 **$navs** 变量的是是重数据库 **site_nav** 表中取出的,包含了 **icon** 和 **id** 两个字段,具体代码如下: | ||
|
||
```php | ||
$navs = pdo_fetchall("SELECT icon, id FROM ".tablename('site_nav')." WHERE id IN (SELECT nid FROM ".tablename('site_category')." WHERE id = {$id} OR parentid = '$id')", array(), 'id'); | ||
``` | ||
|
||
现在我们要做的,就是找找看数据库中的这两个字段是否可以被用户控制。我们继续往前查找,发现了如下代码:![5](5.png) | ||
|
||
**site_nav** 表中的数据,对应的是 **$nav** 变量。我们继续往上寻找 **$nav** 变量,发现 **$nav['icon']** 变量是从 **$_GPC['iconfile']** 来的,即可被用户控制( 下图 **第21行** )。这里的 **$nav['icon']** 变量,其实就是我们文章开头分析的传入 **file_delete** 函数的参数,具体代码如下: | ||
|
||
![6](6.png) | ||
|
||
由于 **$nav['icon']** 变量可被用户控制,程序有没有对其进行消毒处理,直接就传入了 **file_delete** 函数,最终导致了文件删除漏洞。至此,我们分析完了整个漏洞的发生过程,接下看看如何进行攻击。 | ||
|
||
##漏洞验证 | ||
|
||
访问url:http://xxx.xxx.xxx.xxx/WeEngine/web/index.php?c=account&a=display ,点击管理公众号: | ||
|
||
![](7.png) | ||
|
||
找到分类设置,点击添加文章分类。这里对应的url为:http://xxx.xxx.xxx.xxx/WeEngine/web/index.php?c=site&a=category,实际上表示 **site** 控制器的 **category** 模块,即对应 **category.ctrl.php** 文件。 | ||
|
||
![8](8.png) | ||
|
||
选择对应的内容,进入 **if($isnav)** 判断: | ||
|
||
![9](9.png) | ||
|
||
|
||
在上传图标位置输入要删除文件的路径 | ||
|
||
![10](10.png) | ||
|
||
我们建立 **delete.txt** 文件,用于测试任意文件删除: | ||
|
||
![11](11.png) | ||
|
||
我们点击删除时,就会调用 **file_delete** 函数,同时就会删除掉我们插入到数据库中的图片名: | ||
|
||
![12](12.png) | ||
|
||
![13](13.png) | ||
|
||
这个类型任意文件删除有点类似于二次注入,在添加分类时先把要删除的文件名称插入到数据库中,然后点击删除分类时,会从数据库中取出要删除的文件名。 | ||
|
||
##修复建议 | ||
|
||
漏洞是没有对 `$row['icon']` 参数进行过滤,可以将文件名内容加入目录阶层字符,造成任意文件删除漏洞,所以我们要在传入的参数中过滤"../"等目录阶层字符,避免目录穿越,删除其他文件夹下文件。我们在修复中可以过滤掉 `$row['icon']` 中的目录穿越字符,引入我们自定义的一个函数 `checkstr` 函数。同时 `$row['icon']` 只是文件的名称,并非是一个路径,因此过滤字符并不会影响到实际功能,对此修复意见我们提供如下代码: | ||
|
||
![14](14.png) | ||
|
||
## 结语 | ||
|
||
看完了上述分析,不知道大家是否对 **路径穿越问题** 有了更加深入的理解,文中用到的CMS可以从 [这里](https://pan.baidu.com/s/1dwZgXLBK_7It6qTVkamB_w ) 下载(密码:hgjm) 下载,当然文中若有不当之处,还望各位斧正。如果你对我们的项目感兴趣,欢迎发送邮件到 **[email protected]** 联系我们。**Day6** 的分析文章就到这里,我们最后留了一道CTF题目给大家练手,题目如下: | ||
|
||
```php | ||
// index.php | ||
<?php | ||
include 'flag.php'; | ||
if ("POST" == $_SERVER['REQUEST_METHOD']) | ||
{ | ||
$password = $_POST['password']; | ||
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) | ||
{ | ||
echo 'Wrong Format'; | ||
exit; | ||
} | ||
while (TRUE) | ||
{ | ||
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; | ||
if (6 > preg_match_all($reg, $password, $arr)) | ||
break; | ||
$c = 0; | ||
$ps = array('punct', 'digit', 'upper', 'lower'); | ||
foreach ($ps as $pt) | ||
{ | ||
if (preg_match("/[[:$pt:]]+/", $password)) | ||
$c += 1; | ||
} | ||
if ($c < 3) break; | ||
if ("42" == $password) echo $flag; | ||
else echo 'Wrong password'; | ||
exit; | ||
} | ||
} | ||
highlight_file(__FILE__); | ||
?> | ||
``` | ||
|
||
```php | ||
// flag.php | ||
<?php $flag = "HRCTF{Pr3g_R3plac3_1s_Int3r3sting}";?> | ||
``` | ||
|
||
|
||
|
Binary file not shown.
Binary file not shown.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.