SQL 인젝션 (SQL Injection)

SQL Injection

설명

SQL 인젝션은 사용자 입력을 문자열 연결, 포매팅, f-string 등으로 그대로 쿼리 문자열에 섞어 넣을 때 발생합니다. 값과 쿼리 구조가 분리되지 않으면 따옴표, 논리 연산자(OR 1=1), 주석(--), UNION, 세미콜론(;) 등을 주입해 원래 의도와 다른 쿼리가 실행될 수 있습니다. SQLAlchemy의 text()에 사용자 입력을 직접 포함하거나, Django의 raw(), pandas.read_sql에 동적 문자열을 넣는 경우도 동일하게 위험합니다. 공격자는 인증 우회, 대량 데이터 덤프, 임의 수정/삭제, DB 함수나 확장을 악용한 RCE까지 시도할 수 있습니다.

잠재적 영향

  • 데이터 유출: WHERE 조건 우회, UNION 주입 등으로 민감 정보 조회/덤프가 가능합니다.

  • 데이터 변조/삭제: 임의의 INSERT/UPDATE/DELETE 실행으로 무결성이 훼손됩니다.

  • 인증/권한 우회: 로그인 쿼리 조작으로 인증 우회 및 권한 상승이 가능합니다.

  • 원격 코드 실행(RCE): DB 확장/프로시저(예: xp_cmdshell, COPY … PROGRAM 등)를 악용해 OS 명령 실행이 가능할 수 있습니다.

  • 서비스 거부(DoS): 복잡한 쿼리 유도나 대량 트랜잭션으로 자원 고갈을 초래할 수 있습니다.

해결 방법

  • 파라미터 바인딩(Prepared Statement) 사용: DB-API execute/executemany에 쿼리와 값 인자를 분리해 전달합니다. 문자열 연결/포매팅/f-string 금지.

  • SQLAlchemy: text()에 사용자 입력을 직접 넣지 말고 바인드 파라미터(:name)와 인자 딕셔너리를 사용합니다. ORM의 query/filter를 우선 사용.

  • Django: raw() 대신 ORM API 사용. 불가피하면 params 인자로 바인딩하고, 동적 식별자(테이블/컬럼명)는 허용 목록으로 매핑.

  • pandas.read_sql: 쿼리 값은 params 인자로 바인딩. 테이블/컬럼명처럼 바인딩 불가한 식별자는 허용 목록(whitelist)에서만 선택.

  • 입력 검증: 타입 변환(int, UUID 등)과 허용 목록 기반 검증을 적용. 패턴 검증만으로 신뢰하지 말 것.

취약한 코드 및 안전한 코드 예시

취약한 코드

안전한 코드

설명:

  • 취약한 코드:

    • f-string으로 사용자 입력(keyword)과 정렬 컬럼(order)을 그대로 쿼리에 포함했습니다.

    • 값 부분은 따옴표/와일드카드/논리식/주석을 주입할 수 있고, 식별자 부분은 임의 컬럼/함수 호출을 유도할 수 있어 SQL 인젝션이 가능합니다.

  • 안전한 코드:

    • 값은 파라미터 바인딩(cur.execute(sql, params))으로 전달하여 쿼리 구조와 데이터를 분리했습니다. 드라이버가 적절히 이스케이프/타입 처리를 수행하므로 주입이 차단됩니다.

    • 식별자(ORDER BY 컬럼)는 허용 목록으로 제한해 미승인 컬럼/함수 호출이 불가능합니다. 필요 시 테이블/컬럼, 정렬 방향까지 모두 매핑해 고정합니다.

참조

Last updated