php if判断两个条件_回首再看CTF中的那些PHP弱类型

原文来自SecIN社区—作者:湿巾不湿

0x00 无话可说的类型转换

众所周知PHP中各数据类型转整数型与取整函数intval()关系密切,intval()函数就是类型转换产生的PHP弱类型问题的关键因素。

<?php

var_dump("abc1"==0);  //Ture

var_dump("2abc"==2); //Ture

var_dump(intval("123")); //int(123)

var_dump(intval("abc")); //int(0)

var_dump(intval("2abc")); //int(2)

?>

上面的测试代码简单呈现出字符类型转整数型产生问题,除了字符串转整数之外,还有数组转整数:

<?php

var_dump(intval(array())); //int(0)
    
var_dump(intval(array(2,3,4))); //int(1)

var_dump(intval(array('aa','bb','cc'))); //int(1)

?>

另外,还有十六进制:

<?php

var_dump(intval('0x3A')); //int(0)

var_dump(intval(0x3A)); //int(58)

?>

0x01 老生常谈的一些函数

strcmp()

我们知道strcmp()函数的功能是比较两个字符串(区分大小写),如果str1< str2 则返回< 0,如果str1大于str2函数返回>0,如果str1= str2 则函数返回 0。

<?php
    
var_dump(strcmp("str1", "str2")); //int(-1)

var_dump(strcmp("str3", "str2")); //int(1)

var_dump(strcmp("str1", "str1")); //int(0)

var_dump(strcmp(array(123),"str2")); //NULL

?>

但是两个参数中只要有其中一个传入的值为数组,函数就会返回NULL,而NULL又可以利用在PHP松散比较中。看下这个简单的例子:

<?php

include('flag.php');
highlight_file(__FILE__);

$password="aaaaaaaa";
if (isset($_GET['password'])){
	$pw = $_GET['password'];
	if ($pw != $password) {
		if (strcmp($pw, $password) == 0){
    		echo $flag;
		}else{
    		echo "NO,NO,NO";
		}
	}else{
		echo "What are you doing!?";
	}
}

?>

该示例中得到flag的途径是给password传值,通过第二个和第三个if的判断。传入数组既可以使第二个if判断成立,又可以造成strcmp()函数返回NULL,进而利用第三个if中的松散比较满足判断条件。payload: ?password[]=xxxxx

另外附上一张PHP官方文档的松散比较图:

v2-83b52ab46de5d8741698ea84d3abc8a6_b.jpg

in_array()

in_array()的松散性基本可以理解为“==”。

<?php
    
var_dump(in_array('abc', array(0,1,2)));  //bool(true)

var_dump(in_array('abc', array('0',1,2)));  //bool(false)

var_dump(in_array('1abc', array(0,1,2)));  //bool(ture)
?>

array_search()

array_search()的问题与in_array()一样,皆会对类型进行强制转换。绕过同理。

之前看Mrsm1th师傅的博客时见过一道这样的题目:

<?php
if(!is_array($_GET['test'])){exit();}
$test=$_GET['test'];
for($i=0;$i<count($test);$i++){
    if($test[$i]==="admin"){
        echo "error";
        exit();
    }
    $test[$i]=intval($test[$i]);
}
if(array_search("admin",$test)===0){
    echo "flag";
}
else{
    echo "false";
}
?>

三个if条件很是苛刻,前两个if分别要求参数test传入的值必须是数组且数组内不能有“admin”,然后第三个条件就要求通过array_search(“admin”,$test)判断。

而我们知道,array_search()与in_array()一样,会类型进行强制转换,那么当我们传入test[]=0时,array_search("admin",$test)中的判断就相当于"admin"==0,最终等式成立返回匹配成功的数组元素的下标0,满足“===”,得到flag。

switch()

switch()函数常用作条件选择,但函数内的参数与case的类型不同时也会进行类型转换。

<?php
include('flag.php');
highlight_file(__FILE__);

$password = $_GET['password'];
if ($password != 1) {
	switch ($password) {
    case 0:
    	echo "green,green";
    	break;
    case 1:
        echo $flag;
        break;
    default:
    	echo "awsl";
    	break;
	}
}else{
	echo "NO";
}    
?>

上示例的绕过方式还是一样原理,传入?password=1abc经过类型转换与case 1匹配。

0x02 那些年我们一起绕过的MD5

最近的比赛似乎常有PHP弱类型和md5组合的绕过题目,什么$a==md5($a);啊,什么$a!=$b;md5($a)==md5($b);啊,诸如此类,绕过方法除了利用数组外就是利用0e215962017QNKCDZOs878926199a这些md5加密后的值为0ed+(0e开头,0e后全为数字)的字符串。

原理也很简单,就是当php在经行==松散比较时,在等号前后的值都为0ed+时,就会判断为科学计数法0的n次方,结果都为0,等式成立。如下例:

<?php
var_dump(md5('QNKCDZO'));  //string(32) "0e830400451993494058024219903391" 
echo '<br>';
var_dump(md5('QNKCDZO')==0);  // bool(true) 
?>

简单的0e绕过

md5加密后值为0ed+的字符串:

<?php
$a = 'QNKCDZO';
$b = 's878926199a';
$c = 's214587387a';
$d = '0e215962017';
var_dump(md5($a)); //string(32) "0e830400451993494058024219903391" 
echo '<br>';
var_dump(md5($b)); //string(32) "0e545993274517709034328855841020" 
echo '<br>';
var_dump(md5($c)); //string(32) "0e848240448830537924465865611904"
echo '<br>';
var_dump(md5($d)); //string(32) "0e291242476940776845150308577824"
?>

看下面这个简单例子:

<?php
include('flag.php');
highlight_file(__FILE__);
if ($_GET['a']==md5($_GET['a'])) {
	echo "$flag";
}
?>

为了得到flag需要满足传入的值与其自身的MD5值松散比较相等,我们只需要传入一个0ed+并且MD5加密后仍然是0ed+的字符串,使得在进行松散比较时两边的值都被解析为零的n次方即可。传入0e215962017

v2-034e0c594615adeae8079362cc4432a1_b.jpg

常规数组绕过

数组绕过利用的是PHP中的md5()函数的其中一个特性,就是当给md5()传参为数组时会返回NULL

<?php
$a = array('aaa','ccc');
$b = array(1,2,3);

var_dump(md5($a));  // NULL
var_dump(md5($b));  // NULL
?>

而NULL在PHP松散比较中利用简直不要太爽。再看下这个利用特性满足严比较的例子:

<?php
include('flag.php');
highlight_file(__FILE__);
if ($_GET['a']!==$_GET['b'] && md5($_GET['a'])===md5($_GET['b'])) {
	echo "$flag";
}else{
	echo "NONONO";
}
?>

利用php中md5()函数传入数组后返回NULL的特性,我们仅需传入两个不同的数组使其md5()加密后等式两边都为NULL,两个严比较同时成立输出flag。

v2-2a86ecc3bae5ec8c0a758e6886aa792b_b.jpg

强类型绕过

所谓MD5强类型绕过,其实就是MD5强碰撞产生的异类。

先拿上个例子来说,假如修改题目源码后无法再通过传入数组绕过严比较,我们该怎么办?比如这样:

<?php
include('flag.php');
highlight_file(__FILE__);
if ((string)$_GET['a']!==(string)$_GET['b'] && md5($_GET['a'])===md5($_GET['b'])) {
	echo "$flag";
}else{
	echo "NONONO";
}
?>

在严比较两边GET方法传参的位置加了类型强制转换,此时我们再传入数组会发现失败了:

v2-bb81ea4397fbf52aa3bc01daa973d5eb_b.jpg

先看一下测试的代码:

<?php
highlight_file(__FILE__);
var_dump((string)$_GET['a']);
echo '<br>';
var_dump((string)$_GET['b']);
echo '<br>';
var_dump(md5((string)$_GET['a']));
echo '<br>';
var_dump(md5((string)$_GET['b']));
echo '<br>';
if ((string)$_GET['a']!==(string)$_GET['b']) {
	echo "111111";
}else{
	echo "22222";
}
?>

v2-b54b9f1856df177c54ead40ee0ec0600_b.jpg

通过GET方法传入的数组在经过(string)类型转后后都变成了字符串“Array”,所以自然无法满足(string)$_GET['a']!==(string)$_GET['b']这个条件。

那么,我们应该怎样才能同时满足(string)$_GET['a']!==(string)$_GET['b']md5($_GET['a'])===md5($_GET['b'])这两个严判断呢?

v2-fcbb2163bf3329cfab469db167dabe69_b.jpg

嗯,就是强类型绕过。第一次见的时候很懵,是19年安洵杯的一道题,BUU链接:[安洵杯 2019]easy_web,参考该题的Writeup:

$data_1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2

$data_2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

这一对payload并不完全一样,是MD5强碰撞产生的异类。。。。直接测试:

v2-fbb71d22e70334bd173e594e2b0d4897_b.jpg

通过测试输出的结果,我们可以很直观的看到这对payload经过MD5加密后的值一模一样,同时还满足(string)$_GET['a']!==(string)$_GET['b']

成功绕过强类型严比较:

v2-f884520dd28fe6d64074d6beacbc5730_b.jpg

0x03 结语

本篇参考:

https://www.cnblogs.com/Mrsm1th/p/6745532.html

https://www.cnblogs.com/wangtanzhi/p/12244096.html

如若文中有哪里分析的不够到位,还请海涵并指出错误。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值