본문 바로가기
드림핵

[드림핵]simple_sqli 풀이

by jwcs 2023. 3. 11.
728x90

https://dreamhack.io/wargame/challenges/24/?writeup_id=1950 

 

simple_sqli

로그인 서비스입니다. SQL INJECTION 취약점을 통해 플래그를 획득하세요. 플래그는 flag.txt, FLAG 변수에 있습니다. Reference Server-side Basic

dreamhack.io

 

간단한 sql injection문제이다. 하지만 끝까지 읽어보길 바란다.

 

/

홈 화면이다. 별 다를 것 없으니 로그인 화면으로 넘어간다.

 

/login

 

로그인 공간이다. 코드를 바로 확인하자.

 

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        userid = request.form.get('userid')
        userpassword = request.form.get('userpassword')
        res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')
        if res:
            userid = res[0]
            if userid == 'admin':
                return f'hello {userid} flag is {FLAG}'
            return f'<script>alert("hello {userid}");history.go(-1);</script>'
        return '<script>alert("wrong");history.go(-1);</script>'

 

쿼리문에 집중해서 보자. userid와 userpassword를 참으로 만들면 res의 첫번째 인자값을 userid로 가져오고 이를 출력해준다. res는 query_db()의 리턴값이므로 query_db()를 보러 가자.

 

def query_db(query, one=True):
    cur = get_db().execute(query)
    rv = cur.fetchall()
    cur.close()
    return (rv[0] if rv else None) if one else rv

 

select 문을 실행하고 이를 cur에 대입한다.

cur를 fetchall()한다. 즉 모두 검색해서 이를 rv에 담는다.

rv[0]을 리턴한다. 즉, 우리가 admin으로 로그인한다고 가정한다면 {"admin":"password"}의 쌍이 리턴된다. 왜 한 쌍이 리턴되는 지는 이 코드를 확인해보자.

db.execute(f'insert into users(userid, userpassword) values ("guest", "guest"), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}");')

 

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        userid = request.form.get('userid')
        userpassword = request.form.get('userpassword')
        res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')
        if res:
            userid = res[0]
            if userid == 'admin':
                return f'hello {userid} flag is {FLAG}'
            return f'<script>alert("hello {userid}");history.go(-1);</script>'
        return '<script>alert("wrong");history.go(-1);</script>'

다시 로그인 페이지로 돌아오자. 현재 res에는 rv[0]이 담겨있다.

만약 res가 정상적으로 리턴되어 값을 받아오면 userid에 res[0]을 담는다. res에는 admin : 비밀번호 쌍이 담겨있었는데, admin이 userid에 담기는 것이다. 그렇게 admin이 담기면 플래그를 보여준다.

 

자 그럼 익스플로잇 도면을 짜보자. select문의 userid가 admin인 상태로 참을 만들면 플래그를 얻을 수 있을 것 같다. 우선 가장 쉬운 방법부터 알아보겠다.

 

 

첫 번째 방법

 

admin"--

userid 란에는 admin"--을 입력하고 패스워드란에는 아무런 패스워드나 치는 것이다.

이렇게 한다면 

select * from users where userid="{userid}" and userpassword="{userpassword}

이 구문에서

select * from users where userid="admin"--   " and userpassword="{userpassword}

이 된다. --뒤에는 주석처리되므로 admin을 찾아 값을 보여줄 것이다.

 

 

두 번째 방법

 

이번에는 필자가 헤맸던 경험과 함께 풀어보겠다.

 

admin

"or"1"=="1

로 풀어보려고 시도했다. 결과는 어땠을 것 같은가?

 

 

 

guest로 인식한다. 그 이유가 뭘까?

 

select 문의 where 이후 부분을 살펴 보겠다.

userid="{userid}" and userpassword="{userpassword}"

필자가 입력한 것을 대입해 보겠다.

 

userid="admin" and userpassword=""or"1"=="1"

이것을 나누어서 생각해보면

userid="admin"       userpassword=""           "1"=="1" 

      참                                거짓                           참이다.

 

앞에서부터 끊어보면 (userid="admin" and userpassword="" ) => 참 and 거짓이므로 거짓이 된다.

이후 거짓 or "1"=="1"(참) 이므로 결국 참이되어 select문을 실행시킬 수 있게된다.

하지만 userid="admin"은 앞서 거짓이 되어 소멸됐으므로 admin으로 검색이되지 않는다.

db.execute(f'insert into users(userid, userpassword) values ("guest", "guest"), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}");')

테이블에는 admin보다 guest가 앞 요소에 있다. rv[0]을 리턴하므로 admin이 아닌 guest가 리턴 되는 것이다.

 

따라서 위 구문으로 인젝션을 시도하면 게스트로 로그인이 된다.

 

그렇다면 어떻게 해야할까?

 

"1"=="1 부분을 userid="admin"으로 만들어주면 된다. 이렇게 하면 마지막 참을 userid="admin"으로 하기 때문에 admin을 찾아준다. 

 

 

세 번째 방법

 

admin

userpassword

userid는 admin으로, password는 userpassword로 입력하면 플래그를 획득할 수 있다. 이 이유를 알아보자.

 

res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')

 

{userpassword}가 무엇으로 감싸져 있는가?

더블쿼터(")이다. sql에서 더블쿼터는 식별자로 쓰인다. 즉 유요한 식별자인 userpassword가 입력되어서 

userpassword = userpassword가 되어 참이 되는 것이다. 이에 관한 링크를 밑에 남겨 두겠다.

 

짜잔

 

https://dreamhack.io/wargame/challenges/24/?writeup_id=1950 

 

simple_sqli

로그인 서비스입니다. SQL INJECTION 취약점을 통해 플래그를 획득하세요. 플래그는 flag.txt, FLAG 변수에 있습니다. Reference Server-side Basic

dreamhack.io

https://sqlite.org/lang_keywords.html

 

SQLite Keywords

The SQL standard specifies a large number of keywords which may not be used as the names of tables, indices, columns, databases, user-defined functions, collations, virtual table modules, or any other named object. The list of keywords is so long that few

sqlite.org

 

728x90
반응형

'드림핵' 카테고리의 다른 글

[드림핵]error based sql injection 풀이  (0) 2023.05.10
[드림핵]blind-command 풀이  (0) 2023.03.12
[드림핵]csrf-2 풀이  (0) 2023.03.11
[드림핵]csrf-1 풀이  (0) 2023.03.11
[드림핵]xss-2 풀이  (0) 2023.03.11