php特性和缺陷
php特性和缺陷
前言:今天对php由于本身特性所产生的一些过滤绕过机制进行讲解,也算是之前漏洞练习博客的理
论支撑
1.1 ==
和===
的区别
==
为等于,===
为全等
这个区别之前也在其他博客里写过,php中==只比对值是否相同,不管其类型,比如我们让其来判断
两个数字是否相等,无论是整型,还是字符,还是浮点型,只要值相同,判断结果就会正确。
在php中,除了判断值外还要判断类型的比较运算符为===
全等和!===
不全等两种,其余比较运算
符都是只根据其值来进行判断
<?php
header("Content-Type: text/html; charset=utf-8");
if ('1 admin' == 1) {
echo "正确";
}
对于上述情况,输出结果为正确,原因:在PHP中,当一个字符串与整数进行比较时,PHP会尝试将
字符串转换为整数进行比较。 首先使用==,可以将字符串和数字进行比较,对字符串先进
行解析,从左往右解析到的一个数字是1,停止,将整个字符串转换为数字1再参与比较,所以我们
得到的值是正确。
同样的道理,我们这样
<?php
header("Content-Type: text/html; charset=utf-8");
if ("+1" == 1) {
echo "正确";
}
得到的结果也是正确,原因: php检测到字符串以+开头,这是一个正号,表示接下来的数字是正数
字符串+1的剩余部分是有效正数1,所以php会将字符串+1转换为数字1再进行比较,最后得到结果
正确。
对于===严格比较运算符来说,在判断相等时不仅需要值相同,还需要类型相同,所以像上面的字符
串与整形进行比较或者浮点与整形进行比较就不可取了
注意 通过url中查询字符串传入的数据都会被保存为字符串类型
通过php此特性,我们就可以对某些使用==进行过滤的程序进行绕过,从而传入我们想要传入的数据
1.1 MD5函数缺陷绕过
MD5函数是对数据进行md5加密的,在php中,md5加密格式位
我们在通过md5值进行判断时,如果这样写
<?php
header("Content-Type: text/html; charset=utf-8");
if ($_GET["username"] != $_GET["password"]) {
if (MD5($_GET["username"]) == MD5($_GET["password"])) {
echo "ok";
}
}
我们先判断内容不相等后,再编码成md5进行判断,判断相等 才会输出ok
情况1:
当我们username 和password分别传入 240610708 QNKCDZO 输出结果是ok,为什么会有这种情
况呢?我们先对传入的username进行md5编码,编码结果为
0e462097431906509019562988736854
再对传入的password进行编码,编码结果为 0e830400451993494058024219903391 针对两字符
串进行比较 ,检测到以0e开头,且后续无数字,是科学计数法的表达方式,直接转换为数字0,最后实
际判断就是0=0,因此返回true,当后面的值中出现字符时,即使是0e开头也不会被解析为数字,而
是直接以字符串形式进行比较。如果不是0e,如果后面的数值过大超过intmax,则会被解析为无穷
大,所以比较结果也是相等。
情况2
首先我们要先名吧用get方式这样传入数据是什么情况
http://example.com/script.php?name[]=1
当我们这样传入时,php将name解析为一个第一个值为1的一个数组,所以我们像这样输出
echo $_GET[];
是无法正常输出的,由于传入的是数组,所以需要这样输出
echo $_GET["name"][0];
对于md5函数来说,他是无法实现对数组进行加密的,所以当我们传入的数据为数组时,加密后的值
为null,对于此特性,我们再看我们上面的代码,当我们传入 username[]=1 password[]=2 时,会
输出ok,原因就是md5无法对数组进行加密,所以两个数组的加密结果都为null.因此判断相等,我
们还需要注意的是
1.2 intval()函数缺陷及其绕过
首先我们来认识以下intval函数
intval函数用于获得整数值,常用于强制类型转换,用法为
intval( $var, $base )
var为需要转换成整形的变量,base为所使用的进制,注意,第二个参数只有在传入并转换字符串时才
会起作用,也就是我们传入非字符串类型的数据时设置第二个参数是没有作用的
第二个参数base允许为空,其获取整数是直接将小数部分省去,并非四
舍五入型,如果var以0开头,则为8进制,如果以0x开头,则为16进制。默认为10进制
在日常使用过程中,该函数用于过滤掉sql注入过程,因为注入内容有字符,所以无法转化为整数。
无法顺利转化时,返回值为0
当我们使用intval转换数组类型时,不关心数组中的内容,值判断数组中有没有元素,有就返回1,空
数组就返回0;
当我们转化字符串时,如果第二个参数设置为0,则会自动根据你输入数据的格式判断进制,比如0开
头就是八进制,0x开头就是16进制。
当我们转换字符串时,且第二个参数不设置值时
如果以数字开头,则会取第一个字符前的数字,如果是以字符开头,则会按照字符处理
如果是以0开头,则会忽略0取取数字,如果是0x开头,直接返回值0;
intval也支持取反操作,所以当某个数字被过滤时,可以两次取反来绕过
总体绕过思路:
(1)当某个数字被过滤时,可以使用它的 8进制/16进制来绕过;比如过滤10,就用012(八进制)或
0xA(十六进制)。
(2)对于弱比较(a==b),可以给a、b两个参数传入空数组,使弱比较为true。
(3)当某个数字被过滤时,可以给它增加小数位来绕过;比如过滤3,就用3.1。
(4)当某个数字被过滤时,可以给它拼接字符串来绕过;比如过滤3,就用3ab。(GET请求的参数会自动拼接单引号)
(5)当某个数字被过滤时,可以两次取反来绕过;比如过滤10,就用~~10。
(6)当某个数字被过滤时,可以使用算数运算符绕过;比如过滤10,就用 5+5 或 2*5。
1.3 strpos() 缺陷及其绕过
strops函数用于确定一字符串在另一字符串中第一次出现的位置 ,返回的值是所查询的字符串第一个
字母的位置。
使用语法为:strpos(string,find,start)
前两个参数为 必须参数,分别为规定要搜索的字符串和规定要查找的字符串,第三个参数可选,规定
在何处开始搜索
1.4 in_array()缺陷及其绕过
is_array()可以判断一个值是否在数组中。
语法如下:in_array(value,array,type)
value :要搜索的值
array : 被搜索的数组
type : 类型,true全等 ,false非全等(默认)
该函数在使用过程中的绕过点就在第三个参数上,如果未设置true,我们可以通过传入其他形式的数
据进行绕过。
比如我们判断传入的值是否在一个数组数组中,未设置true的话我们传入数字后加字符完全可以绕过
判断,从而进行sql注入等操作。
1.5 preg_match()的数组绕过
<?php
$a = $_GET['x'];
if (preg_match("/[0-9]/", $a)) {
echo "no";
} elseif (intval($a)) {
echo "yes";
}
对于这种情况先判断是否有数字,如果没有数字再判断是否能转换为整数,这种情况我们应该如何绕
过呢,考虑使用数组绕过,传入 x[]=1 即可,原因就是preg_match函数无法处理数组,intval函数传
入有值的数组会返回1,从而进行绕过。
1.6 str_replace()函数绕过
该函数用于查找字符串中的对应部分并进行替换,注意,替换的是所有找到的,并不是第一个找到的
具体用法如下
str_replace("查询的字符串","替换成的字符串","传入的字符串")
给出下面这个例子
<?php
$a = $_GET['x'];
$sql = str_replace("select", "", $a);
echo $sql;
这种过滤方式存在的缺陷就是更替无法进行迭代,即我们替换后的字符串中如果存在需要再次替换的
值,就不会进行二次替换。
举个例子
我们访问该urllocalhost/file/test.php?x=select1
输出的结果为1.但如果我们这样传入 seselect 111
我们输出的内容为111;原因为第一次进行替换
后,虽然将字符串中存在的select替换为空,但替换后的字符串却不会再次进行替换,因此就构造出
select从而成功绕过
1.7 相关练习
我们了解了这么多函数以及其缺陷,现在去实际的练习一下吧。
先看ctfshow web89 发现就是我们上面学习preg_match的练习代码,直接秒了。
再看下一题,ctfshow web90
该题的过滤代码为
include("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);
}
}
很明显,我们这时候需要用进制转化进行绕过,8进制下4476为10574,而且intval第二个参数为0,所以我们直接传入010574该值即可。
我们继续看ctfshow web91 ,过滤代码如下
show_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';
}
我们看到了我们熟悉的朋友,正则表达式,我先对这两个正则表达式进行解释,第二个正则表达式中
的^php
表示匹配以php开头的字符串,而php$
表示匹配以php结尾的字符串,两个结合起来,就只
会匹配字符串php,后面的i,表示不区分大小写,至于第一个正则 表达式中的im则表示不区分大小
写,进行多行匹配。明白了正则表达式,我们看下怎么绕过,绕过的关键就在于这两个表达式的区
别,即是否进行多行匹配,我们直接传入%0aphp
即可顺利绕过,%0a为url编码下的换行符,第一行
为空,第二行为php,顺利通过第一个正则表达式,第二个正则表达式不能跨行匹配,只能匹配第一
个空行,自然匹配不过,所以会输出flag.
再看ctfshow web 92
include("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);
}
}
和先前基本没有区别,只不过变成了===
过滤还更弱了一些,之前的payload直接能用,而且还有了很
多新的绕过方式比如 4476.4等。
我们再来看ctfshow web93
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
和之前没有什么区别,使用八进制,传入010574或者4476.4等浮点数便可成功绕过,
再看ctfshow web 94
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
本题的过滤和之前稍微有了点区别,他过滤掉了放在第一位的0,而且需要保证其他位上至少存在一个
0 ,那咋办吗,加个空格或者加号即可,即%20010574
用其占掉第一位,就实现了绕过
ctfshow web95
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
还是大同小异,只不过把小数点给过滤掉了罢了,用上一题的payload秒了
ctfshow web 96
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
熟悉的过滤,或许会第一时间想到使用通配符,但直接访问文件路径是无法使用通配符的,通配符只
能结合linux中的一些命令一起使用,这里直接./flag.php
即可 秒了。
再看ctfshow web 97
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>
是我们前面讲过的md5缺陷及其绕过,按那个来即可,因为使用post提交,用hackbar交一下即可。