开始练习【红日团队】的PHP-Audit-Labs 代码审计 Day16
链接:https://github.com/hongriSec/PHP-Audit-Labs
感兴趣的同学可以去练习练习
预备知识:
内容题目均来自 PHP SECURITY CALENDAR 2017
Day 16 - Poem代码如下:
class FTP {
public $sock;
public function __construct($host, $port, $user, $pass) {
$this->sock = fsockopen($host, $port);
$this->login($user, $pass);
$this->cleanInput();
$this->mode($_REQUEST['mode']);
$this->send($_FILES['file']);
}
private function cleanInput() {
$_GET = array_map('intval', $_GET);
$_POST = array_map('intval', $_POST);
$_COOKIE = array_map('intval', $_COOKIE);
}
public function login($username, $password) {
fwrite($this->sock, "USER " . $username . "\n");
fwrite($this->sock, "PASS " . $password . "\n");
}
public function mode($mode) {
if ($mode == 1 || $mode == 2 || $mode == 3) {
fputs($this->sock, "MODE $mode\n");
}
}
public function send($data) {
fputs($this->sock, $data);
}
}
new FTP('localhost', 21, 'user', 'password');
漏洞解析 :
这道题目包含了两个漏洞
,利用这两个漏洞,我们可以往FTP连接资源
中注入恶意数据,执行FTP命令
。
首先看到 第7行
代码,可以发现程序使用 cleanInput
方法
$this->cleanInput();
过滤 GET 、 POST 、 COOKIE 数。
private function cleanInput() {
$_GET = array_map('intval', $_GET);
$_POST = array_map('intval', $_POST);
$_COOKIE = array_map('intval', $_COOKIE);
}
将他们强制转成整型数据
。然而在 第8行
处。
$this->mode($_REQUEST['mode']);
却传入了一个从REQUEST
方式获取的 mode
变量。我们都知道超全局数组 $_REQUEST
中的数据,是 $_GET 、 $_POST 、 $_COOKIE
的合集,而且数据是复制过去的,并不是引用。我们先来看一个例子,来验证这一观点:
可以发现 REQUEST
数据丝毫不受过滤函数的影响。
回到本例题,例题中的程序过滤函数只对 GET 、 POST 、 COOKIE
数据进行操作,最后拿来用的却是REQUEST
数据,这显然会存在安全隐患。
$_REQUEST :
(PHP 4, PHP 5, PHP 7)
功能:
$_REQUEST — HTTP Request 变量
定义:
默认情况下包含了 $_GET
,$_POST
和$_COOKIE
的数组。
说明:
-
“Superglobal”
也称为自动化的全局变量。这就表示其在脚本的所有作用域中都是可用的。不需要在函数或方法中用global $variable
; 来访问它。 -
以命令行方式运行时,将不包含
argv
和argc
信息;它们将存在于$_SERVER
数组。 -
由于
$_REQUEST
中的变量通过GET
,POST
和COOKIE
输入机制传递给脚本文件,因此可以被远程用户篡改而并不可信。这个数组的项目及其顺序依赖于 PHP 的variables_order
指令的配置。
第二个漏洞的话,在代码 第24行
,这里用了 == 弱比较
。
==
和===
==
为弱相等,也就是说12=="12"
–>true
,而且12=="12cdf"
–>true
,只取字符串中开头的整数部分,但是1e3dgf
这样的字符串在比较时,取的是符合科学计数法的部分:1e3
,也就是1000
.
这里推荐PHP弱类型比较(松散比较)方面的漏洞
至于本次案例的攻击payload
,可以使用: ?mode=1%0a%0dDELETE%20test.file
,这个即可达到删除FTP服务器
文件的效果。
实例分析:
本次实例分析, 我们选取的是
WordPress
的All In One WP Security & Firewall
插件 。
该插件在4.1.4 - 4.1.9
版本中存在反射型XSS
漏洞,漏洞原因和本次案例中的漏洞成因一致,官方也在4.2.0
版本中修复了该漏洞。本次,我们将以4.1.4
版本插件作为案例讲解。
漏洞POC 本站提供安全工具、程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负!
漏洞分析:
将下载下来的插件zip包,通过后台插件管理上传压缩包安装即可。
本次发生问题的文件在于 admin\wp-security-dashboard-menu.php
,为了方便大家理解,我将问题代码抽取出来,简化如下:
我们可以很清晰的看到,问题就出在 第25行
的render_tab3
方法中,这里直接将 REQUEST
方式获取的tab
变量拼接并输出。而实际上,在第20行
已经获取了经过过滤处理的 $tab
变量。我们来看一下get_current_tab
方法:
过滤函数的调用链如下图第1行
,接着 $tab
变量就会经过wp_check_invalid_utf8
方法的检测。
漏洞利用:
下面我们来看看攻击 payload
(向 http://10.211.55.5/phpcode/day16/wordpress/wp-admin/admin.php?page=aiowpsec&tab=tab3
POST数据 tab="><script>alert(1)</script>
):
可以看到成功引发XSS攻击
。我们最后再根据payload
对代码的调用过程进行分析。
首先,我们的 payload
会传入 wp-admin/admin.php
文件中,最后进入 第14行
的 do_action('toplevel_page_aiowpsec');
代码。
在wp-includes/plugin.php
文件中,程序又调用了 WP_Hook
类的 do_action
方法,该方法调用了自身的 apply_filters
方法。
然后apply_filters
方法调用了wp-content\plugins\all-in-one-wp-security-and-firewall\admin\wp-security-admin-init.php
文件的 handle_dashboard_menu_rendering
方法,并实例化了一个 AIOWPSecurity_Dashboard_Menu
对象。
接下来就是开头文章分析的部分,也就是下面这张图片:
整个漏洞的攻击链就如下图所示:
这里还有一个小知识点要提醒大家的是,案例中 $_REQUEST["tab"]
最后取到的是 $_POST["tab"]
的值,而不是 $_GET["tab"]
变量的值。这其实和 php.ini
中的 request_order
对应的值有关。例如在我的环境中, request_order
配置如下:
这里的 "GP"
表示的是 GET
和POST
,且顺序从左往右
。例如我们同时以GET
和 POST
方式传输 tab
变量,那么最终用 $_REQUEST['tab']
获取到的就是 $_POST['tab']
的值。
修复建议:
对于这个漏洞的修复方案,我们只要使用过滤后的 $tab
变量即可,且变量最好经过HTML
实体编码后再输出,例如使用htmlentities
函数等。
结语
再次感谢【红日团队】