PHP-Audit-Labs-Day1 - in_array函数缺陷
函数缺陷原理分析
先看一段简单的源代码
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($this->file['name'], $this->whitelist)
再看in_array函数的使用:
in_array :(PHP 4, PHP 5, PHP 7)
功能 :检查数组中是否存在某个值
定义 : bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
在 $haystack 中搜索 $needle ,如果第三个参数 $strict 的值为 TRUE ,
则 in_array() 函数会进行强检查,
检查 $needle 的类型是否和 $haystack 中的相同。如果找到 $haystack ,则返回 TRUE,否则返回 FALSE。
这个函数会在第二个参数中搜索第一个参数。关键点在于第三个参数$strict。这个参数只有True和False。默认的是False,即不开启PHP的强检查,也就是说我们可以成功的上传一个7shell.php文件,因为7shell.php是字符串,在与数字比较前会被类型转换成7,7在白名单的范围之内,成功上传。这个点导致了任意文件上传功能。
修复方法
1、开启第三个参数为True
一道由in_array造成sql报错注入的CTF题
先在本地搭建一下环境。
//index.php
<?php
include 'config.php';
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->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 "<center><table border='1'>";
foreach ($row as $key => $value) {
echo "<tr><td><center>$key</center></td><br>";
echo "<td><center>$value</center></td></tr><br>";
}
echo "</table></center>";
}
else{
die($conn->error);
}
?>
//config.php
<?php
$servername = "localhost";
$username = "fire";
$password = "fire";
$dbname = "day1";
function stop_hack($value){
$pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";
$back_list = explode("|",$pattern);
foreach($back_list as $hack){
if(preg_match("/$hack/i", $value))
die("$hack detected!");
}
return $value;
}
?>
# 搭建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','Lucia@hongri.com',3000);
INSERT INTO users VALUES(2,'Danny','Danny@hongri.com',4500);
INSERT INTO users VALUES(3,'Alina','Alina@hongri.com',2700);
INSERT INTO users VALUES(4,'Jameson','Jameson@hongri.com',10000);
INSERT INTO users VALUES(5,'Allie','Allie@hongri.com',6000);
create table flag(flag varchar(30) not null);
INSERT INTO flag VALUES('HRCTF{1n0rrY_i3_Vu1n3rab13}');
一开始提示我们加一个id参数。
id=1 and 1=1有回显,id=1 and 1=2异常。存在数字型注入。id=5有回显,id=6报错,一共存在5列。接着暴数据,但是从代码里面看到存在过滤字符。updatexml没有被过滤,使用报错注入,但是concat被禁了,无法拼接数据。这里用到了make_set()函数。
我们先看make_set这个函数的定义
make_set()的用法
返回一个设定值(含子字符串分隔字符串。,。字符)由那些在设置位的相应位的字符串。str1对应于位0,str2至位1,以此类推。NULL值在str1,str2,...不添加到结果。
make_set()应用实例
9化成二进制是1001,倒过来也是1001,那么对应四位数,a对应1,取,b和c对应0,不取,d对应1,取,所以结果是a,d。10化成二进制是1010,倒过来就是0101,a对应0,不取,b对应1,取,c对应0,不取,d对应1,取。所以结果是b,d。
再看
1|4表示进行或运算,为0001 | 0100,得0101,倒过来排序,为1010,同上方法结果是a,c。
但是还有一种就是str里面有null的。
如果null对应到了1那么直接跳过。
payload
3化成二进制是11,倒过来也是11,updatexml报错注入的原理就是Xpath语法遇到特殊字符会报错,并且返回非法格式报错的内容,这里对应1,select database()也对应1。所以能把数据暴出来。
?id=1 and updatexml(1,make_set(3,'~',(select flag from flag limit 1)),1)
这里碰到了一个小错误。在删掉limit1 之后报错Subquery returns more than 1 row,加个limit 1就好了。因为是白盒测试就直接读flag了。
漏洞成因
先看整个代码的逻辑,白名单是1到5,对id参数进行stop_hack过滤,然后将id的参数代入到$sql语句中,但是因为
if (!in_array($id, $whitelist)) {
die("id $id is not in whitelist.");
}
in_array没有开启强比较,我们的注入语句可以过去。就像这样
<?php
$id = "1 and updatexml(1,make_set(3,'~',(select flag from flag limit 1)),1)";
$whitelist = range(1,5);
if(!in_array($id,$whitelist)){
echo "True";
}
else{
echo "False";
}
id在比较的时候变成了1,1在$whitelist内得到False。
接着执行我们的sql语句,就可以暴出数据