CTFshow系列——PHP特性Web89-92
好了,今天开始给大家带来php特性系列的文章,毕竟这部分内容我也没接触过,所以也是想着边学习边了解。
文章目录
- Web89
- 代码解释
- 漏洞利用
- payload
- Web90(新方法)
- 其他WP方法
- Web91
- 代码分析
- payload解释
- Web92(类似Web90)
- 漏洞利用方法
- 总结
Web89
老样子,开篇先看代码:
<?phpinclude("flag.php");
highlight_file(__FILE__);if(isset($_GET['num'])){$num = $_GET['num'];if(preg_match("/[0-9]/", $num)){die("no no no!");}if(intval($num)){echo $flag;}
}
代码解释
if(preg_match("/[0-9]/", $num))
: 这是关键的过滤器。它使用正则表达式/[0-9]/
来检查$num
变量中是否包含任何数字。如果包含,die("no no no!");
会被执行,程序终止,你无法获取flag
。if(intval($num))
: 这是触发漏洞的关键点。intval()
函数的作用是将变量转换为整数。- 如果
$num
变量是一个可以被转换为非零整数的值,那么intval($num)
就会返回一个非零值,在布尔上下文中被认为是true
。 - 当这个条件成立时,
echo $flag;
就会被执行,flag
的值就会被打印到页面上。
- 如果
漏洞利用
要成功获取 flag
,你需要找到一个既不包含数字,又能在经过 intval()
转换后得到非零整数的值。
因此,payload 必须是一个字符串,且不包含 0-9
任何一个数字,但被 intval()转换时能得到一个非零的值。
payload
- 一个常见的绕过方法是利用 科学计数法。例如,
intval("0x1")
会得到1
,但是0
是数字。- 最简单的方法是利用布尔值。在 PHP 中,
true
转换为整数是1
,false
转换为整数是0
。
但很遗憾,这两个并不行;
于是经过我测试,又找到了一种方法:
- 数组参数绕过(最通用、无版本依赖)
- 原理:preg_match的数组处理特性:preg_match(“/[0-9]/”, $num)仅能处理字符串,
- 若num是数组,函数会直接返回false(不匹配任何内容),因此不会触发die(“no no no!”);
- intval数组处理规则:PHP 官方规定
「数组转换为整数时固定返回 1」
,intval中输入数组,返回的值只看数组是否为空,为空返回0,否则返回1 ,恰好满足intval($num)的条件,最终输出flag。
# 常见可用 Payload:?num[]=(空数组值)
?num[]=a(任意非数字字符)
?num[]=xyz(任意字符串)
根据上述特性,即可得到flag。
–
Web90(新方法)
<?phpinclude("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){$num = $_GET['num'];if($num==="4476"){die("no no no!");}if(intval($num,0)===4476){echo $flag;}else{echo intval($num,0);}
}
代码解释:
if($num==="4476")
: 这是第一个关键点。=== 是一个严格比较运算符。它不仅会比较两个变量的值,还会比较它们的类型。- 因此,你不能直接输入字符串4476
if(intval($num, 0)===4476)
: 这是第二个关键点,也是漏洞所在- intval($num, 0) 函数会尝试将 $num 转换为一个整数
- 0x 开头会被识别为十六进制。
- 0b 开头会被识别为二进制。
- 0 开头会被识别为八进制。
否则,会被识别为十进制。
思路:用其他进制来表示4776:
因此,你的 payload 可以是:
# 八进制
?num=010574 # 十六进制
?num=0x117c
也是成功得到flag
其他WP方法
-
科学计数法:
- 小数点:intval(‘4476.0’)===4476
- 正负号:intval(‘+4476.0’)===4476
- 科学计数法:intval(‘4476e1’)===4476
-
有关这个函数
intval(str,int)
的讲解:- PHP 的 intval 函数会从字符串的开始部分提取有效数值,直到遇到非数字字符就停止了
- 所以,只要在其后面加入特殊符号即可绕过
# payload
?num=4476.0
?num=+4476
?num=4476e1# 此处以’为例,也可尝试其他特殊字符
?num=4476'
–
Web91
<?phpshow_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){if(preg_match('/^php$/i', $a)){echo 'hacker';}else{echo $flag;}
}
else{echo 'nonononono';
}
代码分析
我们发现代码与之前又有一点不一样,多了preg_match('/^php$/im', $a)
部分代码分析:
-
/^php$/im)
:关键的正则匹配^
:匹配字符串的开头。$
:匹配字符串的结尾。- php:匹配字面量 “php”。
- i:忽略大小写。
- m:多行模式。
-
作用:在多行模式下,^ 和 $ 不仅匹配整个字符串的开头和结尾,还匹配每一行的开头和结尾。
- 区别:这两个正则表达式的区别在于多行模式(m)
- 要获取 flag,你需要找到一个输入 a,它能满足第一个 if 语句(
/^php/im
),但不能满足第二个 if 语句(/^php$/i
)
- 要获取 flag,你需要找到一个输入 a,它能满足第一个 if 语句(
- 多行模式允许 ^ 和 $ 匹配字符串内部的换行符。
- 非多行模式下,^ 和 $ 只匹配整个字符串的开头和结尾。
payload解释
因此,我们可以利用换行符(%0a
,URL 编码)来构造 Payload:
# payload
?cmd=php%0aphp
为什么这个 Payload 能成功?
- 第一个
if:preg_match('/^php$/im', 'php\nphp')
- 在多行模式下,^php$ 会匹配每一行,所以它会匹配第一行的 php 和第二行的 php。这个条件为真。
- 第二个
if:preg_match('/^php$/i', 'php\nphp')
- 在非多行模式下,^ 和 $ 只匹配整个字符串的开头和结尾。整个字符串是 “php\nphp”,它不以 “php” 结尾,也不以 “php” 开头 。这个条件为假。
当然,php%0a
后面跟着的字符串不固定,可以是任意字符串:
?cmd=php%0aflag
?cmd=php%0afdaf
–
Web92(类似Web90)
<?phpinclude("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){$num = $_GET['num'];if($num==4476){die("no no no!");}if(intval($num,0)==4476){echo $flag;}else{echo intval($num,0);}
}
这里我们乍一看代码,咦?怎么和Web90的有点眼熟:
其实还是不一样的:
- 第一个关键点:
==
是一个弱类型比较运算符。在进行比较之前,PHP 会尝试将两个值转换为相同的类型。- 这个比较的漏洞在于,PHP 会将以数字开头的字符串(如 “4476abc”)也转换为数字 4476
- 第二个关键点:
if(intval($num, 0)===4476)
:也是漏洞所在- intval($num, 0) 函数会尝试将 $num 转换为一个整数
- 0x 开头会被识别为十六进制。
- 0b 开头会被识别为二进制。
- 0 开头会被识别为八进制。
(第二个解析与Web90一模一样)
漏洞利用方法
你的目标是找到一个值,它经过弱类型比较后不等于 4476(以绕过第一个 if),但经过 intval() 转换后等于 4476。
你可以利用 intval() 的类型转换特性来构造 Payload:
- 利用
intval()
的解析规则:intval("4476abc")
结果为4476
。
- 尝试:
"4476abc" == 4476
在弱类型比较中会转换为4476 == 4476
,因此第一个 if 语句仍然会执行。所以这种方法行不通。
-
利用科学计数法或特殊字符串:
- intval(“4476e1”) 结果为 44760。
- -intval(“4476.1”) 结果为 4476。
- 尝试:
"4476.1" == 4476
会转换为4476.1 == 4476
,结果为 false。因此,?num=4476.1 可以绕过第一个 if,并满足第二个 if。
- 利用十六进制或八进制:
intval("0x117c", 0)
结果为4476
(十六进制 117c 转换为十进制是 4476)。- 而
"0x117c" == 4476
会转换为0 == 4476
,结果为 false,成功绕过第一个 if。
所以,payload为:
?num=4476.1
?num=4476e1
?num=010574 # 八进制
?num=0x117c # 十六进制
总结
好了,从今天开始PHP特性也是开始进行了解。可能有的人会问,昨天不是说做SQL注入的专题嘛,你个骗子。
好吧,其实是PHP特性我没了解过,所以想拓展知识面,所以才临时做的决定。骗子就骗子吧~