PHP特性
本文章基于ctfshow web入门的php特性编写,并结合做题经验进行补充
弱类型与强类型比较
* 代表在 PHP 8.0.0 之前为
true
。
弱等于 |
true |
false |
1 |
0 |
-1 |
"1" |
"0" |
"-1" |
null |
[] |
"php" |
"" |
---|---|---|---|---|---|---|---|---|---|---|---|---|
true |
true |
false |
true |
false |
true |
true |
false |
true |
false |
false |
true |
false |
false |
false |
true |
false |
true |
false |
false |
true |
false |
true |
true |
false |
true |
1 |
true |
false |
true |
false |
false |
true |
false |
false |
false |
false |
false |
false |
0 |
false |
true |
false |
true |
false |
false |
true |
false |
true |
false |
false * |
false * |
-1 |
true |
false |
false |
false |
true |
false |
false |
true |
false |
false |
false |
false |
"1" |
true |
false |
true |
false |
false |
true |
false |
false |
false |
false |
false |
false |
"0" |
false |
true |
false |
true |
false |
false |
true |
false |
false |
false |
false |
false |
"-1" |
true |
false |
false |
false |
true |
false |
false |
true |
false |
false |
false |
false |
null |
false |
true |
false |
true |
false |
false |
false |
false |
true |
true |
false |
true |
[] |
false |
true |
false |
false |
false |
false |
false |
false |
true |
true |
false |
false |
"php" |
true |
false |
false |
false * |
false |
false |
false |
false |
false |
false |
true |
false |
"" |
false |
true |
false |
false * |
false |
false |
false |
false |
true |
false |
false |
true |
强等于 |
true |
false |
1 |
0 |
-1 |
"1" |
"0" |
"-1" |
null |
[] |
"php" |
"" |
---|---|---|---|---|---|---|---|---|---|---|---|---|
true |
true |
false |
false |
false |
false |
false |
false |
false |
false |
false |
false |
false |
false |
false |
true |
false |
false |
false |
false |
false |
false |
false |
false |
false |
false |
1 |
false |
false |
true |
false |
false |
false |
false |
false |
false |
false |
false |
false |
0 |
false |
false |
false |
true |
false |
false |
false |
false |
false |
false |
false |
false |
-1 |
false |
false |
false |
false |
true |
false |
false |
false |
false |
false |
false |
false |
"1" |
false |
false |
false |
false |
false |
true |
false |
false |
false |
false |
false |
false |
"0" |
false |
false |
false |
false |
false |
false |
true |
false |
false |
false |
false |
false |
"-1" |
false |
false |
false |
false |
false |
false |
false |
true |
false |
false |
false |
false |
null |
false |
false |
false |
false |
false |
false |
false |
false |
true |
false |
false |
false |
[] |
false |
false |
false |
false |
false |
false |
false |
false |
false |
true |
false |
false |
"php" |
false |
false |
false |
false |
false |
false |
false |
false |
false |
false |
true |
false |
"" |
false |
false |
false |
false |
false |
false |
false |
false |
false |
false |
false |
true |
== 先将类型统一再进行比较数值
=== 先比较类型,再比较数值
- 当遇到弱类型比较时,可以MD5碰撞
1 | QNKCDZO 0e830400451993494058024219903391 |
当内容为0exxx时会自动转换为科学计数法也是都是等于0了
这样就实现了MD5($a)==MD5($b)
- 当遇到强类型比较时,可以使用数组
例如get一个a=cxk[],b=ikun[];
数组自动转化为NULL,NULL===NULL,>true
这样同样实现MD5($a)===MD5($b)
- md5强碰撞
工具:fastcoll 可以碰撞出两个内容不同但md5相同的文件
变量名规则
在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线
但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了
例:CTF[``SHOW.COM
=>CTF_SHOW.COM
ctf show
=>ctf_show
三元运算符
例题:web98
1 |
|
首先判断是否GET传入了数据,如果传入了则将POST的地址赋值给了GET
payload:url/?1 POST: HTTP_FLAG=flag
数组
例题:web99
1 |
|
array_push
——往数组尾部插入元素
rand(1,$i)
——随机生成1-877之间的数
//所以array_push($allow, rand(1,$i))
就是往数组中插入1-877之间的数字
in_array
——搜索数组中是否存在指定的值:
in_array(search,array,type)
search
为指定搜索的值
array
为指定检索的数组
type
为TRUE则 函数还会检查 search的类型是否和 array中的相同
综上,我们可以发现数组中的值是int,而在弱类型中当php字符串和int比较时,字符串会被转换成int,所以 字符串中数字后面的字符串会被忽略。题目中的in_array没有设置type,我们可以输入字符串5.php(此处数字随意,只要在rand(1,0x36d)之间即可),转换之后也就是5,明显是在题目中生成的数组中的,满足条件,同时进入下一步后,我们就可将一句话木马写入了5.php中,然后蚁剑连接即可查看到flag
反射类
反射类是一个类的映射
1 | namespace News; |
正常我们实例化一个类是这样:$News=new News0;
如果我们要实例化News类的反射类是这样:$News=new \ReflectionClass(‘News’);
那么通过反射类实例化的类和普通类有什么不同呢?
通过反射类实例化的类我们可以获取这个这个类的详细信息,可以对类进行分析
例题:web101
1 | //flag in class ctfshow; |
echo new ReflectionClass
即可把类的内容打印出来
反射类不仅仅可以建立对类的映射,也可以建立对PHP基本方法的映射,并且返回基本方法执行的情况。因此可以通过建立反射类new ReflectionClass(system('cmd'))
来执行命令
例题:web109
1 | eval("echo new $v1($v2());"); |
?v1=ReflectionClass&v2=system("tac ./f*")
变量or文件 覆盖
- 变量覆盖
一般是通过$$触发,当代码中出现双美元符号的变量引用时就要注意
例题:web105
1 | include('flag.php'); |
变量覆盖经常伴随着foreach()的出现,它会把你传的参数和其值分别赋予$key和$value
也就是说我这里如果?suces=flag则会转变为$key=suces,$value=flag
在第一个foreach处就会使得$suces=$flag,此时把flag内容传到了$suces里
POST同理,把flag内容传到了$error中
最后die(error),也就把flag给die出来了
- 文件覆盖
通常由于file_put_contents()函数触发
当file_put_contents()可以利用时,可以直接向index.php内写入木马
$_SERVER[‘argv’]
对于php$_SERVER这个全局变量 ,里面有很多的参数
http://localhost/aaa/index.php?p=222&q=333
结果:
$SERVER[‘QUERY_STRING’] = “p=222&q=333”;
$SERVER[‘REQUEST_URI’] = “/aaa/index.php?p=222&q=333”;
$SERVER[‘SCRIPT_NAME’] = “/aaa/index.php”;
$_SERVER[‘PHP_SELF’] = “/aaa/index.php”;
由实例可知:
$SERVER[“QUERY_STRING”] 获取get查询语句
$SERVER[“REQUEST_URI”] 返回uri的值
$SERVER[“SCRIPT_NAME”] 返回文件路径
$_SERVER[“PHP_SELF”] 当前正在执行的文件路径
preg正则绕过
- 换行符绕过(%0a)
preg_match('/^flag$/m',$c)
/m 多行匹配
当使用%0a时,字符串会被当做两行处理,但是preg_match只能匹配一行,成功绕过
- 反斜杠绕过(%5c)
preg_match('/^[a-z0-9_]*$/isD',$c)
/s匹配任何不可见字符,包括空格、制表符、换页符等等
/D如果使用$限制结尾字符,则不允许结尾有换行
这种情况可以尝试使用%5c绕过
- 正则回溯绕过
- 大量的回溯会长时间地占用CPU,从而带来系统性的开销。PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限
pcre.backtrack_limit
。最大回溯次数默认为100万次,超过一百万次preg_match函数将返回false
表示此次执行失败从而绕过if
1 | import requests |
- 双写绕过
preg_replace('/flag/i','',$name)
遇到替换为空的情况,可以采用双写,例如’fflaglag’,当中间的flag被替换为空时,结果即为剩下的组合’flag’
- /e绕过(在 PHP 5.5 中就被废弃,并在 PHP 7.0 中彻底移除)
preg_replace ( pattern, replacement, subject)
-
pattern:用于匹配的正则表达式。
-
replacement:用于替换匹配项的内容。
-
subject:要进行替换的字符串。
1 | echo preg_replace("/test/e",$_GET["h"],"jutst test"); |
如果我们提交?h=phpinfo(),phpinfo()将会被执行(使用/e修饰符,preg_replace会将 replacement 参数当作 PHP 代码执行)
1 | echo preg_replace($_GET["a"], $_GET["b"], $_GET["c"]); |
payload:?a=/233/e&b=phpinfo()&c=233
- 异或取反绕过
preg_match("/[A-Za-z0-9]+/",$code)
异或取反常用于绕过无数字字母的限制
工具:bashfuck
1 | #异或 |
1 | 取反 |
伪随机数
1 | mt_srand() //播种 Mersenne Twister 随机数生成器。 |
这两个函数之间相互联系,当mt_srand()播种之后,mt_rand()会根据其种子生成随机数
1 |
|
当种子不变时,生成的随机数是一样的,由此可以利用
CTF常见考法是通过获得到的部分随机数使用phpmtseed爆破出种子然后再生成完整的随机数
运算符优先级
- PHP中的逻辑“与”运算有两种形式:and 和 &&,同样“或”运算也有 or 和 || 两种形式。
- 如果是单独两个表达式参加的运算,两种形式的结果完全相同
- 但两种形式的逻辑运算符优先级不同,这四个符号的优先级从高到低分别是: &&、||、AND、OR。
例题:web132
1 | if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){ |
先算前面的&&然后得出的结果与后面进行或运算,那么只要满足$username ==="admin"即可绕过第一个if
然后再给code赋值admin即可得到flag
payload:?username=admin&code=admin&password=1
call_user_func()回调函数漏洞
此函数可以再次调用其他函数
例题:
1 | class ctfshow{ |
ctfshow=ctfshow::getFlag
(标准格式)
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
(数组格式)
命令盲注
有些时候题目过滤的东西太多,或者是使用了exec()这种没有回显的命令执行函数时就可以考虑命令盲注
1 | if [ `ls / -1 | awk 'NR==1' | cut -c 1`== b ];then sleep 3;fi |
ls / -1 一行一个文件名列出根目录
awk ‘NR==1’ 匹配第一个文件名
cut -c 1 切割出第一个字符
正常情况第一个是bin 这里最后的结果是b 满足if条件后会sleep3秒
1 | import requests |
找到flag后就可以把ls /
换成cat /flag
了
eval(“return $1$2$3”)
return有个特性: 任意数字和符号组合依旧可以实现命令执行
1=1&2=-ls /-&3=1
三个变量之间用运算符号相连,建议使用-``*``/
加号可能会被解码
intval()特性
-
intval(获取变量的整数型):如果他的值为一个数组,只要数组里面有值,那么不论值的数量,返回值都为1,空数组则返回0**
-
echo intval(42); // 42 echo intval(4.2); // 4 echo intval('42'); // 42 echo intval('+42'); // 42 echo intval('-42'); // -42 echo intval(042); // 34 echo intval('042'); // 42 echo intval(1e10); // 10000000000 echo intval('1e10'); // 10000000000 echo intval(0x1A); // 26 echo intval(42000000); // 42000000 echo intval(420000000000000000000); // 0 echo intval('420000000000000000000'); // 2147483647 echo intval(42, 8); // 42 echo intval('42', 8); // 34 echo intval(array()); // 0 echo intval(array('foo', 'bar')); // 1
-
intval($num,0):
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);
如果字符串以 “0” 开始,使用 8 进制(octal);否则,将使用 10 进制 (decimal)。
payload:?num=010574 这里以0开始,意思就是后面的数字将被以8进制的形式读取
strpos()/stripos()
i
:加了不区分大小写
strpos() 以数组形式查找0在字符串中出现的位置,找得到就给出位置(真),找不到返回0(假)
若要查找的字符在第一位则返回0
若传入数组则会使strpos()返回null
(注意null不等于false)