📄 2026.01.30 (Day 67) - Python을 활용한 Blind SQL Injection 자동화 공격
1. 핵심 개념 정리
Blind SQL Injection 공격 기법
| # | 핵심 개념 | 설명 | 실무/보안 관점 |
|---|---|---|---|
| 1 | Blind SQL Injection | 쿼리 결과가 직접 반환되지 않고 참/거짓 반응으로만 데이터를 추출하는 기법 | 일반 SQL Injection보다 탐지가 어렵고 자동화 도구 없이는 공격이 비효율적 |
| 2 | Boolean-based Blind | 조건문의 참/거짓 결과에 따라 페이지 응답이 달라지는 것을 이용 | 정상 응답과 비정상 응답의 패턴 차이로 탐지 가능 |
| 3 | Binary Search(이진탐색) | 1 |
공격 시간을 획기적으로 단축 (127회 -> 7회) |
| 4 | Oracle vs MySQL 차이 | Oracle: user, substr() / MySQL: user(), substring() | DBMS별 함수 차이를 이용한 핑거프린팅 가능 |
| 5 | Brute Force 공격 | 가능한 모든 조합을 시도하여 비밀번호를 찾는 무차별 대입 공격 | 계정 잠금 정책, rate limiting으로 방어 필요 |
Python requests 모듈 활용
| # | 핵심 개념 | 설명 | 실무/보안 관점 |
|---|---|---|---|
| 6 | requests.get() | HTTP GET 요청을 보내고 응답을 받는 메서드 | URL 파라미터를 통한 SQL Injection 자동화에 활용 |
| 7 | requests.post() | HTTP POST 요청으로 데이터를 전송하는 메서드 | 로그인 폼, 검색 등 POST 기반 공격 자동화 |
| 8 | Cookie/Header 설정 | 세션 유지를 위한 쿠키, 요청 헤더 커스터마이징 | 인증이 필요한 페이지 공격 시 필수 |
| 9 | 응답 텍스트 분석 | 응답.text에서 특정 문자열 포함 여부로 참/거짓 판단 | “New MacBook Pro” 같은 정상 응답 마커 활용 |
| 10 | 반복문 자동화 | for, while을 이용한 대량 요청 자동 전송 | 수동으로는 불가능한 속도로 데이터 추출 |
SQL Injection 공격 포인트 탐지
| # | 핵심 개념 | 설명 | 실무/보안 관점 |
|---|---|---|---|
| 11 | 검색창 공격 | like ‘%검색어%’ 구문에서 %’ or ’e’=‘q% 삽입 | 가장 흔한 공격 벡터, 입력값 검증 필수 |
| 12 | 상세페이지 ID 파라미터 | ?id=62 파라미터에 and (공격쿼리) 추가 | 숫자형 파라미터도 문자열 검증 우회 가능 |
| 13 | 로그인 폼 우회 | _csrf 토큰 재사용, 세션 유지로 무차별 대입 | CSRF 토큰도 제대로 검증 안 하면 무용지물 |
| 14 | 에러 메시지 분석 | “권한이 없습니다”, “로그인 실패” 등 응답 차이 활용 | 에러 메시지 통일로 정보 노출 최소화 필요 |
| 15 | 쿼리 구조 유추 | 공격 성공 패턴으로 백엔드 쿼리문 역추적 | 개발자가 작성한 쿼리 구조 파악으로 정밀 공격 |
2. 실습 내용 정리
실습 1: 이진탐색 알고리즘 구현
목표: ASCII 값을 7~8회 만에 찾는 효율적 탐색 알고리즘 이해
이진탐색 로직 (Python):
초기 범위 설정:
- 아스키 = 1 (실제 탐색할 값)
- 시작점 = 1, 끝점 = 127
이진탐색 루프:
- while 시작점 < 끝점:
- 중간점 = int((시작점+끝점)/2)
- 아스키 > 중간점이면: 시작점 = 중간점 + 1 (상위 절반 탐색)
- 아스키 <= 중간점이면: 끝점 = 중간점 (하위 절반 탐색)
- 종료 시 시작점 = 목표 ASCII 값
알고리즘 특성:
- 127회 선형 탐색 -> 7회 이진탐색으로 효율 극대화
- 중간점 계산: (시작+끝)/2를 정수로 변환
- 범위 좁히기: 참이면 시작점 상승, 거짓이면 끝점 하강
- 종료 조건: 시작점 == 끝점일 때 값 확정
보안 인사이트:
- 공격자 입장에서는 시간 효율성이 매우 중요 (빠른 데이터 탈취)
- 방어자 입장에서는 반복 요청 패턴 탐지가 핵심 (7~10회 규칙적 요청)
- 이진탐색 특유의 중간값 테스트 패턴을 WAF에서 시그니처로 활용 가능
실습 2: Blind SQL Injection 참/거짓 테스트
목표: 수동으로 Boolean-based Blind SQL Injection 원리 파악
실습 환경:
- 대상: 쇼핑몰 상품 상세 페이지 (Oracle DB)
- URL 파라미터: ?id=62
- 세션 유지: JSESSIONID 쿠키
공격 구조:
URL 구성: …/detail?id=62 and ({공격쿼리})
쿠키 설정: JSESSIONID = 세션ID값
응답 분석:
- “New MacBook Pro” 포함 시 -> 참!
- 포함 안 될 시 -> 거짓…
Boolean-based Blind 원리:
- 정상 쿼리: SELECT * FROM 상품상세 WHERE 상품번호 = 62 -> “New MacBook Pro” 상품 정보 반환
- 참 조건 삽입: WHERE 상품번호 = 62 and (length(user) > 0) -> 조건이 참이면 동일하게 상품 정보 반환
- 거짓 조건 삽입: WHERE 상품번호 = 62 and (1=2) -> 조건이 거짓이면 아무것도 반환 안 됨
발견 가능한 정보:
- DB 사용자명 길이: length(user) > X
- 사용자명 각 문자: ascii(substr(user,1,1)) > X
- 테이블 개수: (select count(table_name) from user_tables) > X
- 권한 정보: (select 1 from dual where user=‘SYSTEM’)
탐지 패턴:
- 동일 IP에서 동일 URL로 수십~수백 회 반복 요청
- URL 파라미터에 SQL 함수명 포함 (length, substr, ascii)
- 응답 크기는 다르지만 HTTP 상태 코드는 200 OK
실습 3: 자동화 유저명 탈취 스크립트
목표: 이진탐색을 활용한 완전 자동화 데이터 추출
스크립트 구조:
이진탐색 함수:
- def 이진탐색(쿼리): 시작점=1, 끝점=127
- while 시작점 < 끝점: 중간점 계산 후 요청 전송
- “New MacBook Pro” 포함 시: 시작점 = 중간점 + 1
- 미포함 시: 끝점 = 중간점
- return 시작점 (최종 ASCII 값)
유저명 탈취 흐름:
- 글자수 = 이진탐색(“length(user)”) -> 출력: 유저명 글자수
- for 글자순번 in range(1, 글자수+1): 이진탐색(“ascii(substr(user,{글자순번},1))”) -> chr(아스키) 출력
- 테이블수 = 이진탐색(“select count(table_name) from user_tables”) -> 출력: 테이블개수
자동화 효과:
- 수동: 5글자 유저명 추출에 5×127 = 635회 요청 필요
- 자동: 5글자 유저명 추출에 5×7 = 35회 요청 (약 18배 효율)
- 실행 시간: 수동 1시간 -> 자동 10초 이내
보안 인사이트:
- 함수화로 재사용성 극대화 (유저명, 테이블명, 컬럼명 모두 동일 로직)
- 공격자는 이미 완전 자동화 도구 보유 (sqlmap 등)
- 수동 공격만 막는다고 안전한 게 아님, 근본적 취약점 제거 필요
실습 4: Brute Force 로그인 공격
목표: POST 요청 자동화로 비밀번호 무차별 대입
실습 환경:
- 대상: 쇼핑몰 로그인 페이지
- 아이디: admin (고정)
- 비밀번호 범위: 0700~0999 (4자리 숫자)
스크립트 구조:
요청 설정:
- 헤더: Content-Type: application/x-www-form-urlencoded
- 쿠키: JSESSIONID 세션 ID
- 데이터: _csrf 토큰, memberid=admin, password=비밀번호
반복문:
- for i in range(700, 1000): pw = str(i).zfill(4) (0700, 0701, … 형식)
- 데이터[“password”] = pw
- requests.post(url, headers, cookies, data)
- ‘로그인에 실패했습니다.’ 포함 시: 비밀번호 틀림 출력
- 미포함 시: “비밀번호 찾았다! {pw}” 출력 후 break
발견된 취약점:
- CSRF 토큰 재사용 가능 (토큰 갱신 없음)
- 계정 잠금 정책 부재 (무제한 시도 가능)
- Rate Limiting 없음 (초당 수십 회 요청 가능)
- 실패 응답 명확히 노출 (공격자에게 힌트 제공)
탐지 패턴:
- 동일 계정으로 짧은 시간 내 다수 로그인 시도
- 순차적 비밀번호 패턴 (0700, 0701, 0702…)
- 동일 IP/세션에서 CSRF 토큰 재사용
방어 방법:
- 5회 실패 시 계정 임시 잠금 (15분)
- CAPTCHA 추가 (자동화 방지)
- CSRF 토큰 매 요청마다 재생성
- IP 기반 Rate Limiting (1분당 10회)
3. 공격 기법 비교 표
SQL Injection vs Brute Force 비교
| 항목 | Blind SQL Injection | Brute Force | 일반 SQL Injection | 사용 시기/적용 방안 |
|---|---|---|---|---|
| 공격 대상 | DB 쿼리 결과 간접 추출 | 인증 시스템 직접 공격 | DB 직접 결과 조회 | SQL Injection은 데이터 탈취, Brute Force는 계정 탈취 |
| 탐지 난이도 | 높음 (에러 없이 정상 요청) | 중간 (로그 패턴 명확) | 낮음 (에러 메시지 노출) | Blind는 WAF 우회 용이, 일반은 시그니처 탐지 쉬움 |
| 공격 속도 | 느림 (수십~수백 회 요청) | 빠름 (단순 반복) | 빠름 (1~2회 성공) | 시간 제약 없으면 Blind도 충분히 효과적 |
| 필요 조건 | SQL 취약점 존재 | 약한 비밀번호 정책 | SQL 에러 메시지 노출 | 취약점 종류에 따라 기법 선택 |
DBMS별 함수 차이
| 예시 | Oracle | MySQL | SQLite | 보안 영향 |
|---|---|---|---|---|
| 사용자 조회 | user | user() | N/A | DB 핑거프린팅 가능 |
| 문자열 추출 | substr(str,1,1) | substring(str,1,1) | substr(str,1,1) | 함수명 차이로 DBMS 식별 |
| 문자열 길이 | length(str) | length(str) | length(str) | 공통 함수는 범용 공격 가능 |
| 테이블 목록 | user_tables | information_schema.tables | sqlite_master | 메타 정보 위치 차이 |
4. 심화 분석
Python 자동화 스크립트 구조 분석
| 구분 | 이진탐색 함수 | 반복문 로직 | 응답 분석 | 분석/인사이트 |
|---|---|---|---|---|
| 핵심 역할 | ASCII 값 7회 만에 특정 | 글자 수만큼 반복 호출 | 참/거짓 판별 기준 | 세 요소가 유기적으로 결합되어 완전 자동화 |
| 재사용성 | 모든 수치 추출에 활용 | 문자열 길이만큼 동적 | 응답 마커만 변경 | 함수화로 코드 중복 최소화 |
| 성능 | O(log n) 복잡도 | O(n) 글자 수 | O(1) 문자열 검색 | 전체 성능은 O(n log m), n=글자수, m=ASCII범위 |
취약점 시나리오 핵심 코드 설명
Boolean-based Blind SQL Injection 취약한 쿼리:
- query = “SELECT * FROM products WHERE id = " + user_input (직접 삽입)
- 공격: user_input = “62 and ascii(substr(user,1,1)) > 64”
- 결과: DB 사용자명 첫 글자 ASCII 값이 64보다 큰지 확인 가능
Brute Force 로그인 취약한 코드:
- if username == “admin” and password == db_password: login_success() (계정 잠금 없음)
- 공격: for pw in range(0000, 10000): try_login(“admin”, str(pw).zfill(4))
- 결과: 계정 잠금 없으면 모든 조합 시도 가능
방어 기법 (Prepared Statement):
- cursor.execute(“SELECT * FROM products WHERE id = ?”, (user_input,))
- 파라미터 바인딩으로 SQL 구문 분리, 인젝션 불가능
5. 실무/보안 적용
보안 전문가 관점 - 탐지/대응 포인트
| 단계/유형 | 탐지 포인트 | 로그 예시 | 대응 방안 |
|---|---|---|---|
| Blind SQL Injection | 동일 세션 반복 요청 (50+), URL에 SQL 함수 포함, 응답 크기 일정하나 빈도 높음 | GET /detail?id=62 and (length(user))>5 | WAF 룰셋에 SQL 함수 시그니처 추가, 세션별 동일 URL 요청 제한, 개발팀에 파라미터 바인딩 적용 요청 |
| Brute Force | 동일 계정 다수 로그인 실패, 순차적 비밀번호 패턴, 단시간 대량 POST 요청 | POST /login password=0700, 0701, 0702 | 5회 실패 시 15분 계정 잠금, CAPTCHA 또는 2FA 적용, 비밀번호 복잡도 정책 강화 |
| 자동화 도구 사용 | User-Agent: Python-requests, 일정한 요청 간격 (자동화), CSRF 토큰 재사용 패턴 | User-Agent: python-requests/2.28.0 | User-Agent 검증 (비정상 차단), Rate Limiting 적용, CSRF 토큰 매 요청 재생성 |
WAF 룰셋 예시
ModSecurity 룰셋 주요 항목:
- SQL 함수 탐지: length, substr, ascii, substring, concat, user
- 반복 요청 제한: 동일 IP에서 60초 내 30회 이상 요청 차단 (429 응답)
- Python requests User-Agent 차단
웹 애플리케이션 보안 체크리스트
SQL Injection 방어:
- 모든 DB 쿼리를 파라미터 바인딩으로 변경
- ORM 사용 시에도 raw query 금지
- 화이트리스트 기반 검증 (숫자는 isdigit() 확인)
- 프로덕션 환경에서 상세 SQL 에러 비노출, 일반적 에러 메시지로 통일
- DB 계정은 필요한 테이블만 접근 가능, 웹 애플리케이션 계정으로 system 테이블 조회 불가
Brute Force 방어:
- 5회 실패 시 계정 임시 잠금
- CAPTCHA 추가
- CSRF 토큰 매 요청 재생성
- IP 기반 Rate Limiting
6. 배운 점 및 인사이트
새로 알게 된 점
- 이진탐색의 실전 활용: 단순 알고리즘 문제가 아니라 실제 공격 효율성을 127배 향상시키는 핵심 기술임을 체감했습니다.
- Python requests 모듈의 강력함: HTTP 통신을 완전 자동화할 수 있어 수동으로 불가능한 공격도 스크립트로 실현 가능함을 확인했습니다.
- Blind SQL Injection의 정교함: 에러 메시지 없이도 참/거짓 응답 차이만으로 모든 데이터를 추출할 수 있다는 점이 놀라웠습니다.
- DBMS별 함수 차이 활용: Oracle의 user와 MySQL의 user() 차이로 대상 시스템을 핑거프린팅하는 기법을 이해했습니다.
- 자동화 스크립트의 필수성: 현대 공격은 이미 완전 자동화되어 있으며, 보안 전문가도 자동화 도구를 다룰 줄 알아야 함을 깨달았습니다.
이전 학습과의 연결고리
- SQL Injection 기초와 연계: Union-based, Error-based에서 학습한 SQL 함수들을 Blind 공격에서도 그대로 활용했습니다.
- SIEM 로그 분석 확장: 이전에 배운 로그 분석 기법으로 오늘 실습한 공격 패턴들을 탐지하는 룰셋 작성이 가능해졌습니다.
- 웹 취약점 스캐너와 연계: 이진탐색 기반 자동화 스크립트 원리가 SQLMap 등의 자동화 도구와 동일한 메커니즘임을 이해했습니다.
실무 적용 아이디어
보안 전문가 관점:
- 공격 시뮬레이션 자동화: 오늘 배운 스크립트 기법으로 모의해킹 시 반복 작업 자동화 가능
- SIEM 탐지 룰 검증: 직접 공격 스크립트를 돌려보고 Wazuh/Splunk에서 제대로 탐지되는지 테스트
- 취약점 재현 PoC 작성: 발견한 취약점을 개발팀에 전달할 때 Python 스크립트로 명확히 재현 가능
침해사고 대응 관점:
- 로그 패턴 분석 자동화: 침해사고 발생 시 로그에서 이진탐색 패턴, 순차 비밀번호 시도 등을 자동 탐지하는 스크립트 작성
- 공격자 IP 추적: requests 라이브러리 특유의 User-Agent 패턴으로 자동화 공격 IP를 빠르게 식별
7. Quick Reference
Python requests 자동화 명령어 모음
GET 요청 기본:
- 응답 = requests.get(url=“https://example.com”, cookies=쿠키)
POST 요청 (로그인):
- 응답 = requests.post(url=주소, headers=헤더, data=데이터)
응답 분석:
- if “특정문자열” in 응답.text: print(“조건 만족”)
이진탐색 구현:
- while 시작점 < 끝점:
- 중간점 = int((시작점+끝점)/2)
- if 조건 만족: 시작점 = 중간점 + 1
- else: 끝점 = 중간점
반복문 + zfill (숫자 패딩):
- for i in range(700, 1000): pw = str(i).zfill(4) -> 0700, 0701, …
Blind SQL Injection 페이로드 요약표
| 구분 | Oracle | MySQL | 핵심 키워드 | 적용 방법 |
|---|---|---|---|---|
| 사용자명 | length(user) | length(user()) | DB 계정 확인 | and length(user)>5 |
| 문자 추출 | ascii(substr(user,1,1)) | ascii(substring(user(),1,1)) | 한 글자씩 ASCII | and ascii(substr(…))>64 |
| 테이블 수 | count(table_name) from user_tables | count(*) from information_schema.tables | 메타 정보 | and (select count(*))>10 |
| 컬럼 존재 | count(*) from 테이블 where 컬럼 is not null | 동일 | 특정 컬럼 유무 | and (select count(*) from…)>0 |
공격 탐지 체크리스트
WAF 룰셋:
- SQL 함수 시그니처 (length, substr, ascii)
- 반복 요청 패턴 (30회/분 이상)
- Python requests User-Agent 차단
- 파라미터에 괄호/논리 연산자 포함
애플리케이션 방어:
- Prepared Statement 사용
- 입력값 화이트리스트 검증
- 에러 메시지 일반화
- 계정 잠금 정책 (5회 실패)
로그 모니터링:
- 동일 세션 50+ 요청
- SQL 함수명 포함 URL
- 로그인 연속 실패 패턴
- CSRF 토큰 재사용 시도
8. 트러블슈팅
| 문제 | 원인 | 해결 방법 |
|---|---|---|
| “권한이 없습니다” 응답 | 쿼리 구문 오류로 DB 에러 발생 | 괄호 닫기 확인, 쿼리 문법 재검증, 테스트 환경에서 먼저 검증 |
| 이진탐색이 무한루프 | 시작점 < 끝점 조건 미충족 | 초기 범위 확인 (1~127), 중간점 계산 로직 검증, 디버깅 print로 값 추적 |
| 세션 만료 에러 | JSESSIONID 쿠키 유효기간 초과 | 브라우저에서 새 세션 복사, 스크립트에 세션 갱신 로직 추가, 타임아웃 시간 단축 |
| zfill() 적용 안 됨 | str() 변환 누락 | str(i).zfill(4) 순서 확인, int를 먼저 str로 변환 후 zfill |
Today’s Insight:
오늘 실습을 통해 공격자가 얼마나 효율적으로 데이터를 탈취할 수 있는지 몸소 체감했다. 단순해 보이는 이진탐색 알고리즘이 실제 공격에서는 시간을 127배 단축시키는 게임 체인저였고, Python 몇 줄로 수백 번의 요청을 자동화하니 수동 공격과는 차원이 달랐다. 특히 Blind SQL Injection은 에러 메시지조차 없는데도 참/거짓 응답 차이만으로 모든 걸 추출할 수 있다는 점에서, 방어자는 단순히 에러 메시지만 숨긴다고 안전한 게 아니라는 걸 깨달았다. 앞으로 보안 전문가로서 이런 자동화 공격 패턴을 SIEM에서 탐지하고, 개발팀에게는 Prepared Statement의 중요성을 설득할 수 있는 근거를 확보했다는 점에서 매우 값진 하루였다.