본문 바로가기
C.W.K.
Stream
Lesson 03 of 10 · published

Parameterized Query — SQL Injection 멈춰

~14 min · python, parameters, sql-injection, security

Level 0Scout
0 XP0/80 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

제일 중요한 규칙 하나

유저 입력으로 SQL 문자열을 이어붙이거나 f-string으로 만드는 짓은 절대 하지 마. Placeholder를 쓰고 값은 따로 넘겨. escaping은 드라이버가 알아서 하고, SQL injection은 구조적으로 불가능해져.

SQLite가 받는 placeholder 스타일은 둘이야.

  • Qmark?에 tuple이나 list를 짝지어. 순서대로 들어가.
  • Named:name에 dict를 짝지어. 코드만 봐도 뭐가 뭔지 읽혀.
Warning: conn.execute(f'SELECT * FROM users WHERE name = "{name}"')는 그 자체로 보안 버그야. 똑같은 패턴이 production에서 수도 없이 데이터 유출의 뿌리가 됐어. 언제나 placeholder를 써. '믿을 만한' 입력이라도. 믿을 만하다는 건 손에서 잘 미끄러지는 성질이거든.

placeholder가 못 하는 게 하나 있어. 테이블 이름이나 컬럼 이름 같은 식별자는 parameter로 못 넘겨. 그건 허용 목록으로 검사한 다음에만 SQL 문자열에 끼워 넣어.

Code

Placeholder 두 스타일, 둘 다 안전·python
# Qmark — 순서 기반
conn.execute(
    'SELECT * FROM users WHERE email = ? AND archived = ?',
    ('a@x.com', 0),
)

# Named — dict 기반, self-documenting
conn.execute(
    'SELECT * FROM users WHERE email = :email AND archived = :archived',
    {'email': 'a@x.com', 'archived': 0},
)

# 다중 row — 같은 placeholder, executemany
conn.executemany(
    'INSERT INTO users(email, username) VALUES (?, ?)',
    [('a@x.com', 'alice'), ('b@x.com', 'bob')],
)
Identifier — allowlist 검증·python
ALLOWED_SORT = {'created_at', 'updated_at', 'id'}

def list_messages(conn, sort_by: str):
    if sort_by not in ALLOWED_SORT:
        raise ValueError(f'invalid sort: {sort_by}')
    # 이제 알려진 상수 값이라 format 안전
    return conn.execute(
        f'SELECT * FROM messages ORDER BY {sort_by} DESC LIMIT 100'
    ).fetchall()
SQL injection 시도 — placeholder가 어떻게 죽이나·python
# UNSAFE — 절대 하지 마
name = "alice'; DROP TABLE users; --"
conn.execute(f"SELECT * FROM users WHERE name = '{name}'")
# users 안녕.

# SAFE — placeholder 가 string 전체를 값으로 처리
conn.execute('SELECT * FROM users WHERE name = ?', (name,))
# 그냥 row 0 반환; 테이블 무사.

External links

Exercise

SQL을 문자열로 이어붙이는 취약한 함수를 하나 만들고, 테이블을 날리거나 query를 부풀리는 공격 입력을 짜봐. 그다음 placeholder로 다시 쓰고 같은 입력이 아무 짓도 못 하는지 확인해. 두 버전의 차이를 문서로 남겨. 보너스로, 네 codebase에서 f-string SQL을 검색해봐. 최소 하나는 나올걸.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.