https://dreamhack.io/wargame/challenges/24/?writeup_id=1950
간단한 sql injection문제이다. 하지만 끝까지 읽어보길 바란다.
홈 화면이다. 별 다를 것 없으니 로그인 화면으로 넘어간다.
로그인 공간이다. 코드를 바로 확인하자.
@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
https://sqlite.org/lang_keywords.html
'웹 해킹 > 드림핵' 카테고리의 다른 글
[드림핵]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 |