开始练习【红日团队】的PHP-Audit-Labs 代码审计 Day1
链接:https://github.com/hongriSec/PHP-Audit-Labs
感兴趣的同学可以去练习练习
预备知识:
内容题目均来自 PHP SECURITY CALENDAR 2017
Day 1 - Wish List 代码如下:
class Challenge {
const UPLOAD_DIRECTORY = './solutions/';
private $file;
private $whitelist;
public function __construct($file) {
$this->file = $file;
$this->whitelist = range(1, 24);
}
public function __destruct() {
if (in_array($this->file['name'], $this->whitelist)) {
move_uploaded_file(
$this->file['tmp_name'],
self::UPLOAD_DIRECTORY . $this->file['name']
);
}
}
}
$challenge = new Challenge($_FILES['solution']);
漏洞解析 :
这一关卡考察的是一个任意文件上传漏洞,而导致这一漏洞的发生则是不安全的使用 in_array() 函数来检测上传的文件名,即
if (in_array($this->file['name'], $this->whitelist)) {
由于该函数并未将第三个参数设置为 true
,这导致攻击者可以通过构造的文件名来绕过服务端的检测,例如文件名为 7shell.php
。因为PHP在使用 in_array()
函数判断时,会将 7shell.php
强制转换成数字7
,而数字7
在 range(1,24)
数组中,最终绕过 in_array()
函数判断,导致任意文件上传漏洞。(ps:这里之所以会发生强制类型转换,是因为目标数组中的元素为数字类型
)
in_array() 函数的定义:
(PHP 4, PHP 5, PHP 7)
功能:in_array — 检查数组中是否存在某个值
说明:
in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) : bool
大海捞针,在大海(haystack)中搜索针( needle),如果没有设置 strict 则使用宽松的比较。
另一种解释:
在 $haystack 中搜索 $needle ,如果第三个参数 $strict 的值为 TRUE ,则 in_array() 函数会进行强检查,检查 $needle 的类型是否和 $haystack 中的相同。如果找到 $haystack ,则返回 TRUE,否则返回 FALSE。
参数 | 描述 |
---|---|
needle | 必需。规定要在数组搜索的值。 |
haystack | 必需。规定要搜索的数组。 |
strict | 可选。如果该参数设置为 TRUE,则 in_array() 函数检查搜索的数据与数组的值的类型是否相同。 |
技术细节:
返回值 | 如果在数组中找到值则返回 TRUE,否则返回 FALSE。 |
---|---|
PHP 版本 | 4+ |
更新日志 | 自 PHP 4.2 起,search 参数可以是一个数组。 |
实例分析:
本次实例分析,我们选取的是 piwigo2.7.1 版本。该版本由于SQL语句直接拼接 $rate 变量,而 $rate 变量也仅是用
in_array() 函数简单处理,并未使用第三个参数进行严格匹配,最终导致sql注入漏洞发生。
漏洞详情:
Piwigo是世界上最著名的免费开源相册系统之一,由PHP+MySQL架构。由于该框架搭建方便,受到国内外的开发者青睐,近日,Piwigo
<= v2.6.0爆出重要0day漏洞。漏洞成因,Piwigo相册系统的/piwigo/picture.php页面,没有完整的验证jQuery参数。攻击者成功利用该漏洞,可以获取数据库的全部信息,漏洞及其简单暴力。
漏洞影响范围,Piwigo <= v2.6.0。
漏洞POC 本站提供安全工具、程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负!
漏洞分析:
漏洞的分析看起来比较简单,是由于functions_rate.inc.php
文件中的rate_picture
函数没有对传入的$rate
变量进行过滤,直接拼接到SQL中执行:
代码如下:
pwg_query($query);
$query = '
INSERT
INTO '.RATE_TABLE.'
(user_id,anonymous_id,element_id,rate,date)
VALUES
('
.$user['id'].','
.'\''.$anonymous_id.'\','
.$image_id.','
.$rate
.',NOW())
;';
pwg_query($query);
关键在与rate_picture
函数开头其实是有个对$rate变量的过滤的。如下
function rate_picture($image_id, $rate)
{
global $conf, $user;
if (!isset($rate) or !$conf['rate'] or !in_array($rate, $conf['rate_items']))
{
return false;
}
判断$rate
是否是$conf['rate_items']
的项。而后面这个数组的值是配置文件里写死的。
$conf['rate_items']
的内容可以在 include\config_default.inc.php
中找到,为
$conf['rate_items'] = array(0,1,2,3,4,5);
看起来这句的功能是设置了一个rate
变量的白名单。只能是0,1,2,3,4,5
其中之一。
这样子应该很安全才对。当然事实证明这样子写是不安全的。当$rate = "5'aaaaaaaaaaaaaaaaa "
时,
in_array($rate, $conf['rate_items'])
这个判断是返回True
的。这是php里不同类型变量比较时候的一个特性。
所以,利用这个特性,相当于完全的bypass in_array
的过滤。可以输入任意的数据拼接到SQL语句中,
只要以数组中的数字开头就可以了。此外php中的switch也存在类似的特性。
由于 if (!isset($rate) or !$conf['rate'] or !in_array($rate, $conf['rate_items']))
并没有将 in_array()
函数的第三个参数设置为 true
所以会进行弱比较,可以绕过。比如我们将 $rate
的值设置成
1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));
# 那么SQL语句就变成:
INSERT INTO piwigo_rate (user_id,anonymous_id,element_id,rate,date) VALUES (2,'192.168.2',1,1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));#,NOW()) ;
这样就可以进行盲注了,如果上面的代码你看的比较乱的话,可以看下面简化后的代码:
漏洞利用:
接下来我们直接用sqlmap进行验证, payload 如下:
这里我用物理机的sqlmap一直扫不动。。换了Kali的sqlmap就可以了
ps:这里需要自己上传一个图片
sqlmap -u "http://192.168.1.139/PHPcode/piwigo/picture.php?/1/category/1&action=rate" --data "rate=1" --dbs --batch
修复建议:
可以看到这个漏洞的原因是弱类型比较问题,那么我们就可以使用强匹配进行修复。例如将 in_array() 函数的第三个参数设置为 true ,或者使用 intval() 函数将变量强转成数字,又或者使用正则匹配来处理变量。这里我将 in_array() 函数的第三个参数设置为 true ,代码及防护效果如下:
结语
看完了上述分析,不知道大家是否对 in_array() 函数有了更加深入的理解
这里感谢一下【红日团队】