매개 변수화되지 않은 쿼리가 오류를 반환하도록 만드는 이유는 무엇입니까?


22

SQL 인젝션은 매우 심각한 보안 문제입니다. 잘못 입력하기가 너무 쉽기 때문입니다. 사용자 입력을 통합하여 쿼리를 작성하는 명확하고 직관적 인 방법은 사용자를 취약하게 만들고,이를 완화하는 올바른 방법은 매개 변수화에 대해 알아야합니다. 쿼리 및 SQL 주입을 먼저 수행하십시오.

이 문제를 해결하는 명백한 방법은 명백한 (그러나 잘못된) 옵션을 종료하는 것입니다. 데이터베이스 엔진을 수정하여 매개 변수 대신 WHERE 절에서 하드 코딩 된 값을 사용하는 쿼리가 훌륭하고 설명을 반환하도록하십시오. 대신 매개 변수를 사용하도록 지시하는 오류 메시지. 관리 도구의 임시 쿼리와 같은 항목이 여전히 쉽게 실행되도록하려면 선택 해제 옵션이 있어야하지만 기본적으로 사용하도록 설정해야합니다.

이것이 있으면 거의 밤새 SQL 주입이 차갑게 종료되지만 내가 아는 한 RDBMS는 실제로이 작업을 수행하지 않습니다. 왜 안 좋은 이유가 있습니까?


22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'단일 쿼리에서 하드 코딩 된 값과 매개 변수화 된 값을 모두 가질 수 있습니다. 그런 혼합 쿼리에 유효한 사용 사례가 있다고 생각합니다.
amon

6
오늘부터 레코드를 선택하는 방법SELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
Jaydee

10
@MasonWheeler 죄송합니다.“허용 해보십시오”. 이 매개 변수는 완벽하게 매개 변수화되며 SQL 삽입으로 고통받지 않습니다. 그러나 데이터베이스 드라이버는 리터럴 "bad"이 실제로 리터럴인지 또는 문자열 연결의 결과 인지 알 수 없습니다 . 내가 볼 수있는 두 가지 솔루션은 SQL 및 기타 문자열 포함 DSL을 제거하거나 (그렇습니다) 매개 변수가있는 쿼리를 사용하는 것보다 문자열 연결이 더 성가신 언어를 홍보하는 것입니다 (um, no).
amon

4
RDBMS는이를 수행 할 수있는 방법을 어떻게 감지합니까? 대화식 SQL 프롬프트를 사용하여 밤새 RDBMS에 액세스 할 수 없게됩니다. 더 이상 도구를 사용하여 DDL 또는 DML 명령을 입력 할 수 없습니다.
jwenting

8
어떤 의미에서 당신은 이것을 할 수 있습니다 : 런타임에 SQL 쿼리를 전혀 생성하지 말고 대신 SQL 쿼리를 생성하지 않아도되는 ORM 또는 다른 추상화 계층을 사용하십시오. ORM에 필요한 기능이 없습니까? 그런 다음 SQL은 SQL을 작성하려는 사람들을위한 언어이므로 전체적으로 SQL을 작성할 수 있습니다. 근본적인 문제는 코드를 동적으로 생성하는 것이보기보다 어렵지만 사람들은 어쨌든 그것을 원하고이를 허용하지 않는 제품에 만족하지 않을 것입니다.
Steve Jessop

답변:


45

리터럴을 사용하는 것이 올바른 방법 인 경우가 너무 많습니다.

성능 관점에서 쿼리에 리터럴을 원하는 시간이 있습니다. 일단 성능에 대해 걱정할 정도로 커지면 버그 추적기가 있다고 가정 해 봅시다. 시스템의 버그 중 70 %가 "폐쇄"되고 20 %가 "열림", 5 %가 "활성", 5가 될 것으로 예상합니다 %는 다른 상태에 있습니다. 모든 활성 버그를 반환하는 쿼리를 합리적으로 원할 수도 있습니다.

SELECT *
  FROM bug
 WHERE status = 'active'

status바인드 변수를 전달하는 대신 전달 된 값에 따라 다른 쿼리 계획을 status원합니다. 테이블 스캔을 수행하여 닫힌 버그를 반환하고 인덱스 스캔을 수행하려고합니다.status활성 대출을 반환하는 열입니다. 이제 서로 다른 데이터베이스와 버전에 따라 서로 다른 접근 방식을 사용하여 바인드 변수의 값에 따라 동일한 쿼리가 다른 쿼리 계획을 사용할 수 있습니다. 그러나 이는 쿼리 재분석을 귀찮게 할 것인지 또는 새로운 바인드 변수 값에 기존 계획을 재사용 할 것인지에 대한 결정에 균형을 맞추기 위해 상당한 양의 복잡성을 유발하는 경향이 있습니다. 개발자에게는 이러한 복잡성을 처리하는 것이 좋습니다. 또는 옵티 마이저보다 데이터가 어떻게 보일지에 대한 자세한 정보가있을 때 다른 경로를 강요하는 것이 합리적 일 수 있습니다.

코드 복잡성 관점에서 볼 때 SQL 문에 리터럴을 갖는 것이 완벽한 의미가있는 경우가 많습니다. 예를 들어 zip_code우편 번호가 5 자이고 때로는 4 자리가 추가 된 열이있는 경우 다음과 같은 작업을 수행하는 것이 좋습니다.

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

숫자 값에 대해 4 개의 개별 매개 변수를 전달하는 대신. 이것들은 변하지 않을 변수이므로 바인드 변수를 바인딩하면 코드를 읽기가 더 어려워지고 누군가가 잘못된 순서로 매개 변수를 바인딩하여 버그가 생길 수 있습니다.


12

신뢰할 수없고 검증되지 않은 소스의 텍스트를 쿼리의 다른 부분과 연결하여 쿼리를 작성할 때 SQL 삽입이 발생합니다. 그러한 일은 문자열 리터럴에서 가장 자주 발생하지만 이것이 유일한 방법은 아닙니다. 숫자 값에 대한 쿼리는 사용자가 입력 한 문자열 ( 숫자 만 포함 해야 함)을 사용하고 다른 재료와 연결하여 일반적으로 문자열 리터럴과 관련된 따옴표없이 쿼리를 형성 할 수 있습니다. 클라이언트 측 유효성 검사를 지나치게 신뢰하는 코드에는 필드 이름과 같은 것이 HTML 쿼리 문자열에서 나올 수 있습니다. SQL 쿼리 문자열을 보는 코드가 어떻게 구성되었는지 확인할 수있는 방법은 없습니다.

중요한 것은 SQL 문에 문자열 리터럴이 포함되어 있는지 여부가 아니라 문자열에 신뢰할 수없는 소스의 문자 시퀀스가 포함되어 있는지 여부 이며, 유효성 검사는 쿼리를 작성하는 라이브러리에서 가장 잘 처리됩니다. C #에는 일반적으로 문자열 리터럴을 허용하지만 다른 종류의 문자열 표현은 허용하지 않는 코드를 작성할 수있는 방법이 없지만 쿼리 작성 클래스 대신 쿼리 작성 클래스를 사용하여 쿼리를 작성해야하는 코딩 방법 규칙이있을 수 있습니다. 문자열 연결 및 리터럴이 아닌 문자열을 쿼리 작성기에 전달하는 사람은 그러한 작업을 정당화해야합니다.


1
"문자 그대로"에 대한 근사값으로 문자열이 삽입되었는지 확인할 수 있습니다.
코드 InChaos

1
@CodesInChaos : True, 그리고 런타임에 문자열을 생성 할 이유가있는 사람이 런타임에서 생성 된 문자열을 interning하는 대신 리터럴이 아닌 문자열을 받아들이는 방법을 사용했다면 그러한 테스트는이 목적에 충분히 정확할 수 있습니다. (리터럴 문자열이 아닌 메소드에 다른 이름을 부여하면 코드 검토자가이 메소드의 모든 용도를 쉽게 검사 할 수 있습니다).
supercat

C #에서는이 작업을 수행 할 방법이 없지만 일부 다른 언어에는 가능하게하는 기능이 있습니다 (예 : Perl의 오염 된 문자열 모듈).
Jules

간결하게 말하면 이것은 서버 문제 가 아니라 클라이언트 문제입니다.
Blrfl

7
SELECT count(ID)
FROM posts
WHERE deleted = false

이 결과를 포럼 바닥 글에 넣으려면 매번 거짓을 말하기 위해 더미 매개 변수를 추가해야합니다. 또는 순진한 웹 프로그래머는 해당 경고를 비활성화 한 다음 계속하는 방법을 찾습니다.

이제 열거 형에 예외를 추가한다고 말하면 구멍이 다시 열립니다 (더 작음). 말할 것도없이 사람들은 먼저 사용하지 않도록 교육을 받아야합니다 varchars.

주입의 실제 문제는 프로그래밍 방식으로 쿼리 문자열을 구성하는 것입니다. 이를위한 솔루션은 저장 프로 시저 메커니즘이며 허용되는 쿼리의 사용 또는 화이트리스트를 적용합니다.


2
"파라미터 화 된 쿼리를 사용하기가 너무 쉽게 잊어 버리거나 처음에는 알 수 없음"에 대한 솔루션이 "모든 사람이 저장된 프로 시저를 사용하는 것을 기억하고 (처음에 알도록)"하는 경우 질문의 요점을 모두 잃어 버렸습니다.
메이슨 휠러

5
직장에서 저장 프로 시저를 통해 SQL 삽입을 보았습니다. 모든 것에 대한 저장 프로 시저를 의무화하는 것은 나쁜 것입니다. 항상 동적 쿼리 인 0.5 %가 있습니다 (테이블 조인은 물론 전체 where 절을 매개 변수화 할 수 없음).
Joshua

당신이 대체 할 수있는이 답변의 예에서 deleted = falseNOT deleted리터럴을 피한다. 그러나 요점은 일반적으로 유효합니다.
psmears

5

TL; DR : 절의 문자 만이 아니라 모든 리터럴 을 제한 해야WHERE 합니다. 그렇지 않은 이유 때문에 데이터베이스가 다른 시스템과 분리되어있을 수 있습니다.

첫째, 전제에 결함이 있습니다. WHERE절만 제한하고 싶지만 사용자 입력이 가능한 유일한 장소는 아닙니다. 예를 들어

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

이것은 SQL 주입에 동일하게 취약합니다.

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

따라서 WHERE절 에서 리터럴을 제한 할 수는 없습니다 . 모든 리터럴 을 제한해야합니다 .

이제 "왜 리터럴을 허용합니까?"라는 질문이 남았습니다. 명심하십시오. 관계형 데이터베이스는 다른 언어로 작성된 응용 프로그램 아래에서 많은 시간 을 사용하지만 데이터베이스를 사용하기 위해 응용 프로그램 코드를 사용해야 할 필요 는 없습니다 . 그리고 여기에 답이 있습니다 : 코드를 작성하려면 리터럴이 필요합니다. 다른 대안은 모든 코드를 데이터베이스와 독립적으로 어떤 언어로 작성하도록하는 것입니다. 따라서이를 사용하면 데이터베이스에 직접 "코드"(SQL)를 작성할 수 있습니다. 이것은 귀중한 디커플링이며 리터럴이 없으면 불가능합니다. (언제나 좋아하는 언어로 문자를 쓰십시오. 얼마나 어려운지 상상할 수있을 것입니다.)

일반적인 예로, 리터럴은 종종 값 목록 / 조회 테이블 채우기에 사용됩니다.

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

그것들이 없으면 이 테이블을 채우기 위해 다른 프로그래밍 언어로 코드를 작성해야 합니다. SQL에서 직접 수행 할 수있는 기능은 매우 중요 합니다.

그렇다면 우리는 또 하나의 질문을 남길 것입니다 : 프로그래밍 언어 클라이언트 라이브러리는 왜 그렇게하지 않습니까? 그리고 여기에 우리는 매우 간단한 답변을 제공 합니다 . 지원되는 각 버전의 데이터베이스에 대해 전체 데이터베이스 파서다시 구현 했을 것 입니다. 왜? 모든 리터럴을 찾도록 보장 할 수있는 다른 방법이 없기 때문입니다. 정규 표현식으로는 충분하지 않습니다. 예를 들어 PostgreSQL에는 4 개의 별도 리터럴이 포함되어 있습니다.

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

유효한 구문은 종종 주요 데이터베이스 릴리스 사이에서 변경되므로 유지 관리의 악몽이 될 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.