yon11b

[SK 쉴더스 루키즈] SQL Injection 대응방안 본문

보안/SK 쉴더스 루키즈

[SK 쉴더스 루키즈] SQL Injection 대응방안

yon11b 2026. 6. 25. 21:40
반응형

기존방식: statement

SQL문을 문자열 그대로 만들어서 실행하는 방식

 

대응방안1. Prepared Statement

SQL 구조를 미리 DB에 전달해서 준비해두는 방식
입력이 들어오면 준비된 SQL 구조에 값만 바인딩해서 실행하는 것이다.
이 기능은 원래 보안성 때문에 만들어진 것은 아니고 속도를 빠르게 하기 위해서 나온 기능이다.
바인딩하는 값은 무조건 데이터로만 사용되고 쿼리문으로 해석이 안 된다.

select title, content from board where id=?

 

Q. Prepared Statement가 있으니 SQLi는 완전히 차단되었는가?

아니다.
정확히는 ?로 바인딩된 값 부분은 안전하다.

하지만 ?로 처리하지 못하는 부분이 있다. 여기는 어쩔 수 없이 statement 방식으로 해야 하고, 그럼 SQLI가 발생할 수 있다.

 

?를 쓸 수 없는 부분

Prepared Statement의 ?는 값 자리 전용이다.

 

DB는 SQL을 준비할 때 미리 알아야 한다.

어느 테이블을 조회하는지
어떤 컬럼으로 정렬하는지
컬럼 타입이 뭔지
인덱스를 쓸 수 있는지
실행 계획을 어떻게 만들지
 

그런데 FROM ?이면 DB는 준비 단계에서 테이블을 모른다.

또 ORDER BY ?도 컬럼명 정렬이 아니다.

 

가능한 예:

  • WHERE title LIKE ?
  • WHERE id = ?
  • WHERE name = ?

여기서 ?는 나중에 'test', 'admin', 123 같은 데이터 값으로 들어간다.

 

불가능한 예

1. FROM ? (불가능)

SELECT title, content FROM ? WHERE title LIKE ?

여기서 첫 번째 ?를 qna로 바인딩한다고 해서

SELECT title, content FROM qna WHERE title LIKE ?
 

처럼 해석되지 않는다. DB는 FROM ? 자체를 테이블명 자리로 사용할 수 없기 때문이다.

그래서 코드를 이렇게 짤 수밖에 없다.

board = request.args.get("board")
keyword = request.args.get("keyword")

query = f"""
SELECT title, content
FROM {board}
WHERE title LIKE ?
"""

cursor.execute(query, (f"%{keyword}%",))

 

이 코드는 Prepared Statement를 사용한다. 하지만 안전한 부분과 취약한 부분이 나뉜다.

안전한 부분: WHERE title LIKE ?

취약한 부분: FROM {board}

 

공격예시

board = qna WHERE 1=1 --

SELECT title, content
FROM qna WHERE 1=1 -- 
WHERE title LIKE ?

 

2. ORDER BY ? (불가능)

SELECT title, content FROM board WHERE title LIKE ? ORDER BY ?

여기서 두 번째 ?에 REG_DT를 바인딩해도

WHERE title LIKE ? ORDER BY REG_DT
 

로 해석되는 것이 아니라, DB는 으로 처리한다. 즉 컬럼명이 아니라 'REG_DT'라는 리터럴 값으로 취급된다.

 

그래서 얘도 이런식으로 코드를 짤 수밖에 없다. (order by 부분은 prepared 안 됨)

sorting = request.args.get("sorting")
sorting_ad = request.args.get("sortingAd")
keyword = request.args.get("keyword")

query = f"""
SELECT *
FROM free
WHERE title LIKE ?
ORDER BY {sorting} {sorting_ad}
"""

cursor.execute(query, (f"%{keyword}%",))

 

안전한 부분: WHERE title LIKE ?

취약한 부분: ORDER BY {sorting} {sorting_ad}

-> 공격자가 ORDER BY 뒤에 SQL 표현식을 넣을 수 있다.

 

공격예시

입력:

sorting=CASE WHEN 1=1 THEN REG_DT ELSE 1/0 END&sortingAd=ASC

 

결과:

SELECT *
FROM free
WHERE title LIKE ?
ORDER BY CASE WHEN 1=1 THEN REG_DT ELSE 1/0 END ASC

 

조건(1=1)이 참이니까 REG_DT가 결과로 나온다.

그런데 만약 조건을 1=2 과 같은 거짓으로 두면 1/0이 실행되어 에러가 발생한다. -> error based sqli 실행 가능

 

좀 더 실제적인 예시

조건: 첫 번째 테이블명의 길이가 4인가?

입력:

sorting=CASE WHEN (SELECT LENGTH(table_name) 
FROM (SELECT table_name, ROWNUM r FROM user_tables) WHERE r=1)=4 
THEN REG_DT ELSE 1/0 END&sortingAd=ASC

 

 

결과:

SELECT *
FROM free
WHERE title LIKE ?
ORDER BY CASE WHEN (SELECT LENGTH(table_name) 
FROM (SELECT table_name, ROWNUM r FROM user_tables) WHERE r=1)=4 
THEN REG_DT ELSE 1/0 END ASC

 

CASE WHEN 이란?

CASE WHEN은 조건에 따라 값을 선택하는 SQL 표현식이다.

 

형태:

CASE WHEN 조건 THEN 참일때값 ELSE 거짓일때값 END

 

Oracle에서 에러를 일부러 만들려면 이런 식으로 쓸 수 있다.

CASE WHEN 조건 THEN REG_DT ELSE 1/0 END

 

조건이 참이면 REG_DT이 정상 실행된다.

조건이 거짓이면 1/0 -> Oracle에서 에러 발생 -> ORA-01476: 제수가 0 입니다

즉 응답이 정상인지 에러인지 보고 한 글자씩 알아내는 방식이다.

ORDER BY CASE
  WHEN [내가 확인하고 싶은 조건]
  THEN 1
  ELSE 1/0
END
 

이 방식은 Error-based Blind SQLi에 가깝다.
데이터가 바로 출력되는 게 아니라 에러 발생 여부로 참/거짓을 판단하기 때문이다.

 

즉 ?는 값(Value) 이 들어가는 자리에서만 사용할 수 있다

ex) SELECT * FROM board WHERE id = ?

 

대응방안2. 필터링

', ", 등을 공백처리 하는 식

 

대응방안3. 형변환

query=  select * from board where id=

query += int(input_id)

int로 자료형을 고정시켜 주면 됨

728x90