본문 바로가기

CTF

[TJCTF 2023]swill-squill

<문제>

 

<풀이>

1. 사이트 기능 확인

처음 페이지에 방문하면 뜨는 페이지이다. Name과 Grade 입력란과 Register 버튼을 확인할 수 있다.

 

값을 입력해서 Register 해보았다.

 

Register를 하고 나니 Note 입력란과 Save Note 버튼이 있는 페이지로 연결된다.

 

노트를 입력해보았다. 같은 페이지의 상단에 내가 입력한 노트가 보여진다.

 

 

 

2. 코드 확인하기

 

def create_db():
    conn = sqlite3.connect(':memory:')
    c = conn.cursor()

    c.execute(
        'CREATE TABLE users (name text, grade text)')

    c.execute(
        'CREATE TABLE notes (description text, owner text)')

    c.execute('INSERT INTO users VALUES (?, ?)',
              ('admin', '12'))
    c.execute('INSERT INTO notes VALUES (?, ?)',
              ('My English class is soooooo hard...', 'admin'))
    c.execute('INSERT INTO notes VALUES (?, ?)',
              ('C- in Calculus LOL', 'admin'))
    c.execute('INSERT INTO notes VALUES (?, ?)',
              ("Saved this flag for safekeeping: "+flag, 'admin'))

    conn.commit()

    return conn

create_db() 함수를 확인해보면 flag는 notes 테이블에 admin을 owner로 하여 description 필드의 값으로 저장되어 있다. notes 테이블을 조회하는 쿼리를 실행하는 부분의 코드를 확인해보았다.

 

@app.route('/api', methods=['GET'])
def api():
    name = load_token(request.cookies.get('jwt_auth'))['name']

    c = conn.cursor()

    string = "SELECT description FROM notes WHERE owner == '" + name + "';"
    c.execute(string)

    return render_template("notes.jinja", notes=[html.escape(a[0]) for a in c.fetchall()])

/api 엔트포인트는 앞서 사이트 기능을 살펴볼 때, note를 입력하고 입력된 note의 내용을 보여주던 페이지에 해당한다. 코드를 살펴보면, jwt_auth 쿠키에 저장되어 있는 name값을 가져와서 notes 테이블에서 description 값을 SELECT 하는 쿼리문의 조건문에 활용하고 있다.

name에 들어있는 값이 admin이라면 flag값이 들어있는 admin이 소유한 note를 조회할 수 있다.

 

@app.route('/register', methods=['POST'])
def post_register():
    name = request.form['name']
    grade = request.form['grade']

    if name == 'admin':
        return make_response(redirect('/'))

    res = make_response(redirect('/api'))
    res.set_cookie("jwt_auth", generate_token(name))

    c = conn.cursor()
    c.execute("SELECT * FROM users WHERE name == '"+name+"';")

    if c.fetchall():
        return res

    c = conn.cursor()
    c.execute('INSERT INTO users VALUES (?, ?)',
              (name, grade))
    conn.commit()

    return res

그러나 위의 코드를 살펴보면 jwt_auth 쿠키에 name을 저장하기 전에 name이 admin이면 사용자 등록이 이루어지지 않고 그대로 return 되고 있는 것을 확인할 수 있다. 따라서 name에 직접 admin 값을 입력하는 방법으로는 사이트를 뚫을 수 없다.

 

하지만 SQL Injection 기법을 활용하여 주석 등을 이용하면 우회하는 방법을 찾을 수 있다.

name 필드에 admin';--을 입력하면 jwt_auth 쿠키의 name값은 admin'--가 되어 name이 admin인 경우를 필터링하는 if문을 통과할 수 있다. 그리고 그렇게 /api 엔트포인트에 접속하면 notes 테이블을 조회하는 쿼리문이

 

SELECT description FROM notes WHERE owner == 'admin';--';

위와 같이 완성되어 admin의 notes 를 조회할 수 있게 된다.

 

 

 

3. 익스플로잇

Name 필드에 admin';-- 값을 입력하고 Grade 필드에는 임의의 값을 입력하여 Register 하였다.

 

/api 엔드포인트로 페이지가 넘어가고 조회된 admin의 note에서 flag를 찾을 수 있었다.

'CTF' 카테고리의 다른 글

[Pointer Overflow CTF] Easy as it Gets  (0) 2023.09.20
[HSpace CTF] HSpace Free Board  (1) 2023.09.01
[BYUCTF 2023] Notes (미해결)  (0) 2023.05.27
[Vishwa CTF] Eeezzy(미해결)  (0) 2023.04.02
[LINE CTF 2023] old pal(미해결)  (0) 2023.03.27