目錄
前置知識
字符串逃逸-減少
字符串逃逸-增多
前置知識
1.PHP 在反序列化時,語法是以 ; 作為字段的分隔,以 } 作為結尾,在結束符}之后的任何內容不會影響反序列化的后的結果
class people{
? ? public $name='lili';
? ? public $age='20';
}var_dump(unserialize('O:6:"people":2:{s:4:"name";s:4:"lil"";s:3:"age";s:2:"20";}123245dasdsf'));
2.根據長度判斷內容
'O:6:"people":2:{s:4:"name";s:4:"lil"";s:3:"age";s:2:"20";}其中lil"是name的值
3.其中字符串必須以雙引號包裹,不能不寫或以單引號包裹;?
注意點,很容易以為序列化后的字符串是;},但對象序列化是直接}結尾
php反序列化字符逃逸,就是通過這個結尾符實現的當長度不對應的時候會出現報錯無法完成反序列化
4.什么事字符串逃逸?
就是開發者使用先將對象序列化,然后將序列化后的敏感字符進行過濾或替換,最后再進行反序列化。這個時候就有可能會產生PHP反序列化字符逃逸的漏洞。分為兩種情況:
1.字符串減少
O:4:"user":1:{s:8:"username";s:5:"admin";} -->O:4:"user":1:{s:8:"username";s:5:“hack";}
2.字符串增多
O:4:"user":1:{s:8:"username";s:5:"admin";}-->O:4:"user":1:{s:8:"username";s:5:“hacker";}
開發者可能通過這些方法來達到過濾非法字符的目的,但是卻會造成其他的問題:字符串逃逸
字符串逃逸-減少
<?php
class a{public $name = "abcp";public $number = "1234";
}$data = serialize(new a());
echo '序列化:'.$data."\n";
$data = str_replace("p","",$data);//注意這個str_replace,它把p都替換為空
echo '序列化:'.$data."\n";
var_dump(unserialize($data));
也就是說它過濾了一個p之后,會吞噬一個字符,但是吞噬之后序列化字符串不符合序列化的語法規范,所以使其無法反序列化成功
這樣某種程度起到了安全的作用 ,但是如果我們使其吞噬更多內容會不會達到結果合法的目的??
解釋:原acbp中p被替換了,abc”成了一個整體,本質上就是把?”?給吞噬掉了 ,所以有多少個p就好吞噬掉多少個字符
字符串逃逸就事利用吞噬更多的字符,使序列化的字符串與后面的引號形成閉合,從而構造惡意代碼,那么吞噬多少算合適呢?一直吞噬到下一個可控點的值之前,使得下一個可控制點的第一個引號充當上一個值的結束引號,可以吞噬”,也可以不吞,后面會說。如上面例子有兩個變量,通過number值構造惡意代碼,就需要吞噬到number的值前面,讓該值的第一個引號充當name的結束引號。:
O:1:"a":2:{s:4:"name";s:4:"abc";s:6:"number";s:4:"1234";}
或
O:1:"a":2:{s:4:"name";s:4:"abc";s:6:"number";s:4:"1234";}
需要注意的是要構造的字符串通常長度是2位數的,所以應該多逃逸一個字符
O:1:"a":2:{s:4:"name";s:?:"abc";s:6:"number";s:xx:"要構造的字符串";}
?
所以要寫19+1?個p來逃逸字符
屬性之間用一個分號分隔,就可以構造逃逸出來的惡意代碼了,如下:
O:1:"a":2:{s:4:"name";s:23:"abc";s:6:"number";s:19:";s:3:"age";i:25;}";}
構造了一個age屬性,值為25
而上面提到可以把值前面的引號也吞噬掉,其實是一樣的,吞噬掉了,我們在構造的是補回來就可以了?
O:1:"a":2:{s:4:"name";s:4:"abc";s:6:"number";s:4:"1234";}
如果它過濾的時候不止吞噬一個字符怎么辦?那樣就可能出現不會正好能吞噬到下一個可控變量引號之前的情況 ,如一次吞噬6個
class a{public $name = "abchacker";public $number = '123';
}$data = serialize(new a());
echo '序列化:'.$data."\n";
$data = str_replace("hacker","",$data);
echo '序列化:'.$data."\n";
var_dump(unserialize($data));
這種情況需要把引號吞掉,因為需要在可控變量里補字符,那么通過上面吞一個的例子,我們知道需要吞21個字符,但是21/6也是除不盡的,這時候可以直接往多了吞就好,吞噬24個是可以被出盡的,也就是4個hacker,那么多出來的,在惡意代碼中補上3個任意字符給他吞噬就可以了
字符串逃逸-增多
經過替換后,字符串增多,目標使isadmin=1
<?php
class a{public $name = "php123";public $number = '1234';public $isadmin = '0';
}$data = serialize(new a());
echo '序列化:'.$data."\n";
$data = str_replace("php","hack",$data);
echo '序列化:'.$data."\n";
var_dump(unserialize($data));
字符串增多并需要其他的可控的,使吐出的字符結合到一起最終構造成合法的序列化字符串即可
O:1:"a":3:{s:4:"name";s:xx:"hackxxx";s:6:"number";s:4:"1234";s:7:“isadmin";s:1:"1";}
";s:6:"number";s:4:"1234";s:7:"isadmin";s:1:"0";}
xxx表示還不知需要多少個hack
";s:6:"number";s:4:"1234";s:7:“isadmin";s:1:"1";}如果我們使得這串字符被吐出來,那么這樣就最終形成了一個合法的序列化字符串:
O:1:"a":3:{s:4:"name";s:xx:"hackxxx ";s:6:"number";s:4:"1234";s:7:“isadmin";s:1:"1";}
因為}結束后,后面的";s:6:"number";s:4:"1234";s:7:"isadmin";s:1:"0";}會被丟棄
一個php會被替換成hack,一個php吐出一個字符。 “;s:6:”number“;s:4:”1234“;s:7:”isadmin“;s:1:”1“;}有 49個字符,所以需要輸入49個php?
假設一個php會被替換成hacker,一個php吐出三個字符。 “;s:6:”number“;s:4:”1234“;s:7:”isadmin“;s:1:”1“;}有 49個字符,49除以3=16余1,所以需要輸入16個php,同時將“;s:6:”number“;s:4:”1234“;s:7:”isadmin“;s:1:”1“;}改為“;s:6:”number“;s:4:”123“;s:7:”isadmin“;s:1:”1“;}這樣就是48個字符,輸入16個php剛剛好
吐出的多了就在“;s:6:”number“;s:4:”1234“;s:7:”isadmin“;s:1:”1“;}紅色部分增加字符即可,如17個php等于吐出51個字符,原49位多出2位,在number補2位即可