PreparedStatement는 SQL 주입을 어떻게 방지하거나 방지합니까?


122

PreparedStatements가 SQL 주입을 피 / 방지한다는 것을 알고 있습니다. 어떻게하나요? PreparedStatements를 사용하여 생성 된 최종 양식 쿼리는 문자열입니까?


3
기술적으로 JDBC 사양은 SQL 주입 결함이 없다고 주장하지 않습니다. 영향을받는 드라이브가 없습니다.
Tom Hawtin-tackline

3
@Jayesh 여기에 답변으로 블로그 내용을 추가하는 것이 좋습니다. 대부분의 답변은 동적 SQL 쿼리 생성과 준비된 stmt의 차이점을 알려주는 것입니다. 그들은 준비된 진술이 귀하의 블로그보다 더 잘 작동 하는지에 대한 문제를 다루지 않습니다.
Pavan Manjunath 2015

1
답변으로 추가되었으므로 도움이 되었기를 바랍니다.
Jayesh

답변:


78

SQL 삽입의 문제점은 사용자 입력이 SQL 문의 일부로 사용된다는 것입니다. 준비된 명령문을 사용하여 사용자 입력을 매개 변수의 컨텐츠로 (SQL 명령의 일부가 아닌) 처리하도록 강제 할 수 있습니다.

그러나 사용자 입력을 준비된 명령문의 매개 변수로 사용하지 않고 대신 문자열을 결합하여 SQL 명령을 빌드하는 경우 준비된 명령문을 사용할 때에도 여전히 SQL 삽입에 취약 합니다.


1
물론입니다.하지만 여전히 일부 또는 모든 매개 변수를 하드 코딩 할 수 있습니다.
tangens

16
예를 들어주세요-그러나 준비된 명령문에 대한 매개 변수로 사용자 입력을 사용하지 않고 대신 문자열을 결합하여 SQL 명령을 빌드하면 준비된 명령문을 사용할 때에도 여전히 SQL 삽입에 취약합니다.
데이비드 블레인

4
FWIW Prepared 문은 JDBC의 것이 아니라 SQL의 것입니다. SQL 콘솔 내에서 준비된 명령문을 준비하고 실행할 수 있습니다. PreparedStatement는 JDBC 내에서이를 지원합니다.
beldaz 2013 년

198

동일한 작업을 수행하는 두 가지 방법을 고려하십시오.

PreparedStatement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')");
stmt.execute();

또는

PreparedStatement stmt = conn.prepareStatement("INSERT INTO student VALUES(?)");
stmt.setString(1, user);
stmt.execute();

"user"가 사용자 입력에서 왔고 사용자 입력이

Robert'); DROP TABLE students; --

그런 다음 첫 번째 경우에, 당신은 푹 빠질 것입니다. 두 번째로, 당신은 안전하고 Little Bobby Tables가 학교에 등록됩니다.


8
그래서, 내가 맞았다면, 실행될 두 번째 예제의 쿼리는 실제로 : INSERT INTO student VALUES ( "Robert '); DROP TABLE students;-")-또는 적어도 그와 비슷한 것입니다. 이것이 사실입니까?
Max

18
아니, 첫 번째 인스턴스에서 당신은 그 진술을 얻을 것입니다. 두 번째에서는 "Robert '); DROP TABLE students;-"를 사용자 테이블에 삽입합니다.
Paul Tomblin

3
이것이 제가 의미하는 바입니다. 두 번째 예 ( "안전한"것)에서 문자열 Robert '); DROP TABLE 학생; - 학생 테이블의 필드에 저장됩니다. 내가 다른 것을 썼습니까? ;)
Max

7
죄송합니다. 중첩 따옴표는 이와 같은 혼란 때문에 피하려고합니다. 그래서 매개 변수가있는 PreparedStatements를 좋아합니다.
Paul Tomblin

59
리틀 바비 테이블. XD Great reference
Amalgovinus 2011 년

128

PreparedStatement가 SQL 주입을 어떻게 방지하는지 이해하려면 SQL 쿼리 실행 단계를 이해해야합니다.

1. 컴파일 단계. 2. 실행 단계.

SQL 서버 엔진이 쿼리를받을 때마다 아래 단계를 거쳐야합니다.

쿼리 실행 단계

  1. 구문 분석 및 정규화 단계 : 이 단계에서는 구문과 의미에 대해 쿼리를 확인합니다. 쿼리에 사용 된 참조 테이블과 컬럼이 존재하는지 확인합니다. 또한해야 할 다른 많은 작업이 있지만 자세히 설명하지 않겠습니다.

  2. 컴파일 단계 : 이 단계에서는 select, from, where 등과 같은 쿼리에 사용 된 키워드가 기계가 이해할 수있는 형식으로 변환됩니다. 질의를 해석하고 취해야 할 조치를 결정하는 단계입니다. 또한해야 할 다른 많은 작업이 있지만 자세히 설명하지 않겠습니다.

  3. 쿼리 최적화 계획 : 이 단계에서는 쿼리를 실행할 수있는 방법을 찾기 위해 의사 결정 트리가 생성됩니다. 쿼리를 실행할 수있는 방법의 수와 쿼리를 실행하는 각 방법과 관련된 비용을 알아냅니다. 쿼리 실행을위한 최상의 계획을 선택합니다.

  4. 캐시 : 쿼리 최적화 계획에서 선택한 최상의 계획은 캐시에 저장되므로 다음에 동일한 쿼리가 들어올 때마다 1 단계, 2 단계 및 3 단계를 다시 통과 할 필요가 없습니다. 다음에 쿼리가 들어 오면 Cache에서 직접 확인하고 거기에서 선택하여 실행합니다.

  5. 실행 단계 : 이 단계에서는 제공된 쿼리가 실행되고 데이터가 ResultSet객체 로 사용자에게 반환됩니다 .

위 단계에서 PreparedStatement API의 동작

  1. PreparedStatements는 완전한 SQL 쿼리가 아니며 런타임에 실제 사용자가 제공 한 데이터로 대체되는 플레이스 홀더를 포함합니다.

  2. 자리 표시자가 포함 된 PreparedStatment가 SQL Server 엔진으로 전달 될 때마다 아래 단계를 통과합니다.

    1. 구문 분석 및 정규화 단계
    2. 컴파일 단계
    3. 쿼리 최적화 계획
    4. 캐시 (자리 표시자가있는 컴파일 된 쿼리는 캐시에 저장됩니다.)

업데이트 사용자 설정 사용자 이름 =? 및 password =? 어디 id =?

  1. 위의 쿼리는 구문 분석되고 자리 표시 자로 컴파일되고 최적화되고 캐시됩니다. 이 단계의 쿼리는 이미 컴파일되어 컴퓨터에서 이해할 수있는 형식으로 변환됩니다. 따라서 캐시에 저장된 쿼리는 미리 컴파일되고 자리 표시 자만 사용자가 제공 한 데이터로 교체하면된다고 말할 수 있습니다.

  2. 이제 사용자가 제공 한 데이터가 들어오는 런타임에 미리 컴파일 된 쿼리가 캐시에서 선택되고 자리 표시자가 사용자가 제공 한 데이터로 대체됩니다.

PrepareStatementWorking

(자리 표시자가 사용자 데이터로 바뀐 후에는 최종 쿼리가 다시 컴파일 / 해석되지 않으며 SQL Server 엔진은 사용자 데이터를 다시 구문 분석하거나 컴파일해야하는 SQL이 아닌 순수한 데이터로 취급합니다. 이것이 PreparedStatement의 아름다움입니다. )

쿼리가 다시 컴파일 단계를 거치지 않아도되는 경우 자리 표시 자에서 대체 된 모든 데이터는 순수한 데이터로 처리되고 SQL Server 엔진에 의미가 없으며 쿼리를 직접 실행합니다.

참고 : 쿼리 구조를 이해 / 해석하고 의미있는 동작을 제공하는 것은 구문 분석 단계 이후의 컴파일 단계입니다. PreparedStatement의 경우 쿼리는 한 번만 컴파일되고 캐시 된 컴파일 된 쿼리는 항상 선택되어 사용자 데이터를 교체하고 실행합니다.

PreparedStatement의 일회성 컴파일 기능으로 인해 SQL Injection 공격이 없습니다.

https://javabypatel.blogspot.com/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html 에서 예제와 함께 자세한 설명을 얻을 수 있습니다.


3
좋은 설명
Dheeraj 조시

4
그것이 작동하는 방법에 대한 말 그대로 가장 완벽한 답변
jouell

많은 도움이되었습니다. 자세한 설명에 감사드립니다.
Unknown

26

PreparedStatement에서 사용되는 SQL은 드라이버에서 사전 컴파일됩니다. 이 시점부터 매개 변수는 SQL의 실행 가능한 부분이 아닌 리터럴 값으로 드라이버에 전송됩니다. 따라서 매개 변수를 사용하여 SQL을 삽입 할 수 없습니다. PreparedStatements의 또 다른 유익한 부작용 (미리 컴파일 + 매개 변수 만 전송)은 드라이버가 SQL 구문 분석 및 컴파일을 각각 수행 할 필요가 없기 때문에 매개 변수에 대해 다른 값 (드라이버가 PreparedStatements를 지원한다고 가정)을 사용해도 명령문을 여러 번 실행할 때 성능이 향상된다는 것입니다. 매개 변수가 변경되는 시간.


그렇게 구현할 필요는 없으며 종종 그렇지 않다고 생각합니다.
Tom Hawtin-tackline

4
실제로 SQL은 일반적으로 데이터베이스에서 사전 컴파일됩니다. 즉, 데이터베이스에서 실행 계획이 준비됩니다. 쿼리를 실행하면 해당 매개 변수를 사용하여 계획이 실행됩니다. 추가 이점은 쿼리 프로세서가 매번 새 계획을 컴파일 할 필요없이 동일한 명령문을 다른 매개 변수로 실행할 수 있다는 것입니다.
beldaz 2013 년

3

내가 추측 그것이 문자열입니다. 그러나 입력 매개 변수는 데이터베이스로 전송되고 실제 SQL 문을 생성하기 전에 적절한 캐스트 / 변환이 적용됩니다.

예를 들어 CAST / Conversion이 작동하는지 확인하려고 할 수 있습니다.
작동하면 최종 진술을 만들 수 있습니다.

   SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))

숫자 매개 변수를 허용하는 SQL 문으로 예제를 시도하십시오.
이제 문자열 변수 (숫자 매개 변수로 허용되는 숫자 내용 포함)를 전달해보십시오. 오류가 발생합니까?

이제 문자열 변수 (숫자 매개 변수로 허용되지 않는 내용 포함)를 전달해보십시오. 무슨 일이 일어나나요?


3

준비된 진술이 더 안전합니다. 매개 변수를 지정된 유형으로 변환합니다.

예를 들어 매개 변수를 문자열 stmt.setString(1, user);로 변환합니다 user.

매개 변수 에 실행 가능한 명령이 포함 된 SQL 문자열이 있다고 가정합니다. 준비된 명령문을 사용하면이를 허용하지 않습니다.

여기에 메타 문자 (일명 자동 변환)를 추가합니다.

이것은 더 안전합니다.


2

SQL 주입 : 사용자가 SQL 문의 일부가 될 수있는 것을 입력 할 기회가있을 때

예를 들면 :

문자열 쿼리 = "학생 값에 삽입 ( '"+ 사용자 + "')"

사용자가 "Robert"를 입력 할 때); DROP TABLE 학생; –”입력으로 SQL 인젝션 발생

어떻게 준비된 진술이 이것을 방지합니까?

문자열 쿼리 = "학생 값에 삽입 ( '"+ ": 이름"+ "')"

parameters.addValue ( "이름", 사용자);

=> 사용자가 "Robert '를 다시 입력하면); DROP TABLE 학생; –“, 입력 문자열은 리터럴 값으로 드라이버에서 미리 컴파일되며 다음과 같이 캐스팅 될 수 있습니다.

CAST ( '로버트'); DROP TABLE 학생; – 'AS varchar (30))

따라서 마지막에는 문자열이 문자 그대로 테이블에 이름으로 삽입됩니다.

http://blog.linguiming.com/index.php/2018/01/10/why-prepared-statement-avoids-sql-injection/


1
제가 잘못 본게 아니라면, 부분 CAST(‘Robert’);에서 CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))깰 것은, 그것은 그런 경우라면 테이블을 놓기로 진행한다. 주입이 중단되므로 예제가 시나리오를 설명하기에 충분하지 않다고 생각합니다.
Héctor Álvarez

1

PreparedStatement :

1) SQL 문의 사전 컴파일 및 DB 측 캐싱을 통해 전체적으로 더 빠른 실행이 가능하고 동일한 SQL 문을 일괄 적으로 재사용 할 수 있습니다.

2) 따옴표 및 기타 특수 문자의 이스케이프 기능을 내장하여 SQL 주입 공격을 자동으로 방지합니다. 이를 위해서는 PreparedStatement setXxx () 메소드 중 하나를 사용하여 값을 설정해야합니다.


1

에서 설명하고있는 바와 같이 이 게시물PreparedStatement여전히 문자열을 연결하는 경우 단독으로는 도움이되지 않습니다.

예를 들어, 한 명의 악의적 인 공격자는 여전히 다음을 수행 할 수 있습니다.

  • 모든 데이터베이스 연결이 사용 중이 어서 응용 프로그램을 사용할 수 없도록 절전 기능을 호출하십시오.
  • DB에서 민감한 데이터 추출
  • 사용자 인증 우회

바인드 매개 변수를 사용하지 않으면 SQL뿐만 아니라 JPQL 또는 HQL도 손상 될 수 있습니다.

결론적으로, SQL 문을 작성할 때 문자열 연결을 사용해서는 안됩니다. 해당 목적을 위해 전용 API를 사용하십시오.


1
PreparedStatement 만 사용하는 것이 아니라 매개 변수 바인딩 사용의 중요성을 지적 해 주셔서 감사합니다. 그러나 귀하의 답변은 SQL 주입으로부터 보호하기 위해 전용 API를 사용해야 함을 암시하는 것 같습니다. 그렇지 않고 매개 변수 바인딩과 함께 PreparedStatement를 사용하는 것도 작동하므로 재구성 하시겠습니까?
Wild Pottok

-3

Prepared Statements에서 사용자는 데이터를 매개 변수로 입력해야합니다. 사용자가 DROP TABLE 또는 SELECT * FROM USERS와 같은 취약한 문을 입력하면 SQL 문의 매개 변수로 간주되므로 데이터가 영향을받지 않습니다.


정밀도가 낮은 선택한 답변과 동일한 답변입니다.
Julien Maret
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.