프로그래밍 언어/SQL

SQL 기초 실습 노트 6편: SQL Injection 직접 검증하기

gamjadori 2026. 8. 31. 21:55
728x90

SQL 기초 실습 노트 6편: SQL Injection 직접 검증하기


준비: 실습용 테이블과 계정 데이터 만들기
sudo mysql
CREATE DATABASE login_test;
USE login_test;
CREATE TABLE accounts (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
password VARCHAR(50)
);
 
>> accounts라는 이름의 테이블을 생성
id, username, password라는 세 개의 컬럼 생성
id 컬럼은 정수형(INT)으로 저장되며, PRIMARY KEY로 설정되어 각 계정을 구분하는 고유한 값이 된다.
또한 AUTO_INCREMENT 옵션을 사용하여 새로운 계정 데이터가 추가될 때마다 번호가 자동으로 1씩 증가
username 컬럼은 최대 50글자의 문자열을 저장하는 컬럼으로, 사용자의 아이디나 계정 이름을 저장하는 용도로 사용
password 컬럼은 최대 50글자의 문자열을 저장하며, 사용자의 비밀번호 정보를 저장하는 용도로 사용


INSERT INTO accounts (username, password) VALUES (‘admin’, ‘SuperSecret123’);
INSERT INTO accounts (username, password) VALUES (‘testuser’, ‘password1’);


DB 접속용 파이썬 라이브러리 설치
sudo apt install python3-pip -y
pip3 install pymysql –break-system-packages
 


취약한 버전 스크립트 작성 (문자열 이어붙이기)
vi vulnerable_login.py
 
내용: 사용자가 아이디와 비밀번호를 입력하면 MySQL의 login_test 데이터베이스에서 accounts 테이블을 조회해서 로그인 성공/실패를 판단하는 간단한 로그인 검증 프로그램


import pymysql
conn = pymysql.connect(host=‘localhost’, user=‘root’, password=‘본인비밀번호’, database=‘login_test’)
cursor = conn.cursor()
username = input(“아이디: “)
password = input(“비밀번호: “)
query = “SELECT * FROM accounts WHERE username=’” + username + “’ AND password=’” + password + “’”
print(“실행되는 쿼리:”, query)
cursor.execute(query)
result = cursor.fetchone()
if result:
print(“로그인 성공:”, result)
else:
print(“로그인 실패”)
conn.close()


정상적인 값으로 먼저 테스트
python3 vulnerable_login.py
아이디에 admin, 비밀번호에 SuperSecret123 입력


결과: 로그인 성공 메시지와 함께 계정 정보 출력

SQL Injection 페이로드로 테스트
python3 vulnerable_login.py 다시 실행
아이디에 admin’ # 입력, 비밀번호는 아무거나(빈 값이어도 됨) 입력
 

실행되는 쿼리 출력 부분을 보면 SELECT * FROM accounts WHERE username=‘admin’ #’ AND password=’…’ 형태로 바뀐 걸 확인 가능
#은 MySQL/MariaDB에서 한 줄 주석 처리 기호라서, 그 뒤의 비밀번호 조건 전체가 무시됨
결과: 비밀번호를 몰라도 로그인 성공으로 뚫리는 것 확인
 

안전한 버전 스크립트 작성 (Prepared Statement)
vi safe_login.py
아래 내용 작성

import pymysql
conn = pymysql.connect(host=‘localhost’, user=‘root’, password=‘본인비밀번호’, database=‘login_test’)
cursor = conn.cursor()
username = input(“아이디: “)
password = input(“비밀번호: “)
query = “SELECT * FROM accounts WHERE username=’” + username + “’ AND password=’” + password + “’”
print(“실행되는 쿼리:”, query)
안전한 버전에 같은 공격 시도python3 safe_login.py아이디에 똑같이 admin’ # 입력결과: 로그인 실패로 나오는 것 확인


일반 계정 정보로도 테스트
python3 safe_login.py
아이디 1234, 비밀번호 1234처럼 실제 존재하지 않는 계정으로 시도
결과: 로그인 실패 — SQLi 공격이 아니어도 매칭되는 계정이 없으면 정상적으로 실패 처리되는 것 확인, safe_login.py의 기본 로직 자체가 문제없이 동작한다는 걸 보여주는 컷

왜 이렇게 다른가
취약한 버전은 입력값을 SQL 문자열에 직접 이어붙이기 때문에, 입력값 안에 SQL 문법(’, #, OR 등)이 섞이면 쿼리 구조 자체가 바뀜
안전한 버전은 %s 자리에 입력값을 SQL 문법이 아니라 순수한 “데이터”로만 전달함(파라미터 바인딩). 그래서 admin’ #이라는 문자열이 그대로 하나의 아이디 값으로만 취급되고, 매칭되는 계정이 없어서 로그인 실패로 처리