关于php弱类型的小总结
文章最后更新时间为:2019年07月08日 22:19:46
之前的一篇笔记,发到blog上。
PHP,俗称世界上最好的语言,也是CTF中WEB方向最常见的语言,其中利用PHP弱类型特性来绕过限制的情况也比较多,这里结合实例总结一下常见的考察点。
0x01 强制类型转换
先来看题目
<?php
show_source(__FILE__);
$flag = "xxxx";
if(isset($_GET['time'])){
if(!is_numeric($_GET['time'])){
echo 'The time must be number.';
}else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
echo 'This time is too short.';
}else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
echo 'This time is too long.';
}else{
sleep((int)$_GET['time']);
echo $flag;
}
echo '<hr>';
}
?>
乍一看,传入time参数在(5184000,7776000)
范围内不就好了。但是要sleep这么长时间才给flag,那不就真到天荒地老了。
我们输入的GET参数肯定是作为字符串保存了,is_numeric()支持普通数字型字符串、科学记数法型字符串、部分支持十六进制0x型字符串,但强制类型转换int,却不能正确的转换十六进制型字符串、科学计数法型字符串(部分)。
比如:
<?php
echo (int)"0x8888"; //输出为0
echo (int)"8e22"; //输出为8
所以上面我们只要输入科学计数法形式或者16进制形式的字符串就可以绕过漫长的sleep了。0x5b8d80
/6e6
这题涉及到了int强制类型转换。正在进行的ISCC中web题有个小点大概如下:
if (intval($password) < 2333 && intval($password + 1) > 2333){
echo $flag;
}
通过以上分析,很容易可知,可以用科学计数法表示password,绕过限制。
0x02 弱类型比较
弱类型比较最常见了,其实上面的题目有弱类型比较的步骤,$_GET['time'] < 60 * 60 * 24 * 30 * 2
就是字符串和数字对比,当字符串和数字进行对比时,通常将字符串转化为数字,再进行数字之间的对比。
字符串转化为数字时,因为会去掉后面的字母,比如12fda
==12
,我们可以通过这种方式绕过某些限制,比如下面这题:
<?php
error_reporting(0);
include 'flag.php';
highlight_file(__FILE__);
if (!$_COOKIE['admin']) {
exit("\nNot authenticated.\n");
}
if (!preg_match('/^{"hash": [\w\"]+}$/', $_COOKIE['admin'])){
exit("还看不懂正则表达式? 还不快去百度! \n");
}
$session_data = json_decode($_COOKIE['admin'], true);
if ($session_data['hash'] != strtoupper(MD5($flag))) {
echo("给你个提示吧 \n");
for ($i = 0; $i < 32; $i++) {
echo(ord(MD5($flag)[$i]) >> 6);
}
exit("\n");
}
class WHUCTF {
protected $stu;
function __construct() {
$this->stu = new Study();
}
function __destruct() {
$this->stu->action();
}
}
class Study {
function action() {
echo "CTF 真好玩~";
}
}
class Evil {
function action() {
system('cat ./flag.php');
}
}
echo "这么明显了,你懂我意思吧";
unserialize($_GET['whuctf']);
在绕过$session_data['hash'] != strtoupper(MD5($flag)
的时候,我们就可以利用弱类型比较来爆破。
关于字符串还有个Tips,"0e123456"这类的字符串会被识别为科学技术法的数字,而0的无论多少次方都是零。常见的根据这个点来出的题目:
<?php
highlight_file(__FILE__);
include('flag.php');
$str1 = @$_GET['str1'];
$str2 = @$_GET['str2'];
if( $str1 == $str2 ){
die('str1 OR Sstr2 no no no');
}
if( md5($str1) != md5($str2) ){
die('step 1 fail');
}
//这里删去一部分考察其他知识点的内容
echo $flag;
两个字符串不相等,但是md5却相等,我们怎么找这样的字符串呢,但这题只需要使两个字符串的md5都以0e开头即可。在php中如果是0e开头的字符串进行==比较,会认为是科学记数法0e,0的几次方,所以结果自然是0,这样就达到了字符串比较不相等,但是md5值相等的绕过方法。
关于md5绕过的题目基本就三种:
==
比较用0e绕过!==
利用数组绕过(和下面的例子类似)- 找出md5值真正相等的三个不同字符串,可以用文件生成,github已有工具
具体可以学习https://xz.aliyun.com/t/4741#toc-7
0x03 利用不合法(数组)格式绕过限制
<?php
if (isset($_GET['name']) and isset($_GET['password'])) {
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else{
echo '<p>Login first!</p>';
?>
输入的name和password不能一样,之后的sha1比较用了===
,不存在弱类型问题。但sha1不能处理数组,当我们传入name[]=1&password[]=2
时,会造成sha1(Array) === sha1(Array)
,即NULL===NULL
,从而get flag。
其实利用数组来绕过限制的时候有很多,不仅sha1函数不能处理数组,md5函数,token_get_all函数等都不能,程序基本都没有检查数据类型,比如下面这题,前几天校赛中的一题:
<?php
$file = @$_GET["f"];
A:
try{
$tokens = token_get_all($file,TOKEN_PARSE);
foreach ($tokens as $tt) {
if ($tt[0] == 379)
{
$file = str_replace($tt[1],"",$file);
goto A;
}
if($tt[0] == 380)
{
$file = str_replace($tt[1],"",$file);
goto A;
}
if($tt[0] == 381)
{
$file =str_replace($tt[1],"",$file);
goto A;
}
}
}
catch(\Throwable $e){
pass;
}
$filename = md5((string)time().$_SERVER['REMOTE_ADDR'].(string)rand(999,2000));
file_put_contents("/tmp/".$filename,$file);
include '/tmp/'.$filename;
unlink('/tmp/'.$filename);
highlight_file(__FILE__);
?>
总之是要include我们传入的php文件,但是这个A函数很讨厌,利用token_get_all递归过滤了<?php
、 <?=
等关键字,没有这些关键字,include进来也没用啊,最终注意到这题用了try catch结构,那么能不能让上面的A函数直接奔溃,于是传入f参数为数组,token_get_all接收到数组直接报错,绕过了限制。
总之多尝试传入数组,总是没错的。。