[SEC-004][기초] 입력을 검증하고 SQL을 안전하게 실행하기 > IT 기술 공유

본문 바로가기
사이트 내 전체검색

IT 기술 공유

[SEC-004][기초] 입력을 검증하고 SQL을 안전하게 실행하기

페이지 정보

profile_image
작성자 기술팀장
댓글 0건 조회 219회 작성일 26-08-31 12:34

본문

[이번 수업]
사용자 입력을 SQL 문자열에 그대로 붙이면 데이터가 명령의 일부로 해석될 수 있습니다. 이것이 SQL 인젝션(SQL 명령 삽입) 위험입니다. 이번에는 서버 입력 검증과 매개변수 쿼리를 함께 사용해 로컬 SQLite의 가짜 사용자 한 명만 안전하게 조회합니다.

[선수지식]
SEC-001~003의 위협·인증·권한 기초와 DB-001~003의 SELECT·INSERT를 알면 됩니다. Python 3 표준 라이브러리만 사용합니다.

[학습목표]
1. 구문 검증과 의미 검증을 구분한다.
2. SQL 코드와 사용자 데이터를 분리한다.
3. 검증 실패를 안전하게 처리하고 테스트한다.

[핵심개념]
입력 검증은 데이터가 애플리케이션이 기대한 형태인지 확인하는 일입니다. 구문 검증은 사용자 ID가 ASCII 숫자로만 구성됐는지, 의미 검증은 값이 허용 범위 1~1,000,000 안인지 확인합니다. 브라우저 검증은 우회할 수 있으므로 보안을 위한 검증은 서버에서 다시 해야 합니다. 고정 선택지는 허용 목록과 정확히 비교하고, 숫자는 변환·범위까지 확인합니다.

하지만 입력 검증만으로 SQL 인젝션을 막으려 하면 안 됩니다. 이름의 작은따옴표처럼 정상 문자도 있고 차단 목록은 우회되기 쉽습니다. 핵심 방어는 준비된 문장 또는 매개변수 쿼리입니다. SQL은 `WHERE id = ?`로 고정하고 실제 값은 `(user_id,)`로 드라이버에 따로 전달합니다. 그러면 값은 SQL 문법이 아니라 데이터로 취급됩니다.

테이블명이나 정렬 열처럼 SQL 구조는 일반 값 placeholder로 바꿀 수 없습니다. 사용자가 보낸 문자열을 그대로 넣지 말고 `{'name': 'name', 'created': 'created_at'}`처럼 서버가 정한 허용 목록에서 실제 열 이름으로 변환하세요. DB 계정 권한과 제약 조건도 최소화해 실수의 영향을 줄입니다.

[따라하기]
아래를 safe_query.py로 저장합니다. 데이터베이스는 메모리에만 생기고 프로그램 종료 시 사라집니다.

```python
import sqlite3

def parse_user_id(raw):
    if not raw.isascii() or not raw.isdecimal():
        raise ValueError("user_id must contain ASCII digits only")
    user_id = int(raw)
    if not 1 <= user_id <= 1_000_000:
        raise ValueError("user_id is outside the allowed range")
    return user_id

database = sqlite3.connect(":memory:")
database.execute(
    "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)"
)
database.executemany(
    "INSERT INTO users VALUES (?, ?)", [(1, "민수"), (2, "지수")]
)

def find_user(raw):
    try:
        user_id = parse_user_id(raw)
    except ValueError:
        return None
    return database.execute(
        "SELECT id, name FROM users WHERE id = ?", (user_id,)
    ).fetchone()

for sample in ["1", "1 OR 1=1", "-1", "1000001"]:
    print(repr(sample), "->", find_user(sample))
database.close()
```

macOS/Linux는 `python3 safe_query.py`, Windows는 `py safe_query.py`를 실행합니다. 첫 줄만 `(1, '민수')`를 반환하고 나머지는 `None`이어야 합니다. 운영체제에 따른 코드 차이는 없습니다. 이 예제는 본인 소유의 로컬 메모리 DB에서 방어 동작만 확인합니다.

[흔한 실수]
`f"... {value}"`, `+ value`, `% value`로 SQL을 조립하지 마세요. 입력 검증을 브라우저에만 두거나, 위험해 보이는 글자 몇 개만 제거하는 차단 목록도 부족합니다. placeholder 주변에 직접 따옴표를 넣지 말고 사용하는 DB 드라이버의 문법을 확인하세요.

[보안 주의]
실제 서비스나 다른 사람의 DB에 공격 문자열을 보내지 마세요. 오류 응답에는 SQL 문장·스택·DB 구조를 노출하지 말고 서버 로그에도 비밀번호와 원문 개인정보를 남기지 않습니다. 애플리케이션 DB 계정에는 필요한 SELECT·INSERT 등 최소 권한만 부여하고 관리자 계정으로 웹 요청을 처리하지 마세요. 검증 실패 횟수와 원인은 민감정보 없이 관찰합니다.

[직접 해볼 과제]
1. 입력 `2`가 지수를 반환하는 테스트를 추가하세요.
2. 빈 문자열, 공백, `0001`을 넣고 허용 정책을 정해 코드와 일치시키세요.
3. 정렬 키 `name`, `created`를 실제 열로 바꾸는 허용 목록을 작성하세요.

[확인문제]
1. 구문 검증과 의미 검증은 어떻게 다른가요?
2. 매개변수 쿼리가 문자열 연결보다 안전한 이유는 무엇인가요?
3. 테이블명·열 이름을 사용자가 고르게 할 때 어떤 방식이 필요한가요?

[다음 학습]
다음 순환은 AI-004 학습·검증·테스트와 과적합입니다. 보안 트랙은 SEC-005 XSS와 출력 인코딩으로 이어집니다.

[공식 참고 자료]
https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
https://docs.python.org/3/library/sqlite3.html

댓글목록

등록된 댓글이 없습니다.

회원로그인

회원가입

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

접속자집계

오늘
2,120
어제
5,103
최대
16,772
전체
773,174
Copyright © 소유하신 도메인. All rights reserved.