SQL 주입은 공격자가 사용자 입력을 통해 유해한 SQL 코드를 삽입하는 웹 애플리케이션의 보안 결함입니다. 이를 통해 민감한 데이터 변경 데이터베이스 내용에 액세스하거나 시스템을 제어할 수도 있습니다. 웹 애플리케이션을 안전하게 유지하려면 SQL 주입에 대해 아는 것이 중요합니다.
SQL 주입(SQLi)은 공격자가 사용자 입력 필드에 악성 SQL 코드를 삽입하여 웹 애플리케이션의 데이터베이스 쿼리를 조작할 수 있을 때 발생하는 보안 취약점입니다. 이렇게 삽입된 쿼리는 기본 데이터베이스를 조작하여 중요한 데이터를 수정하거나 삭제할 수 있습니다. 어떤 경우에는 공격자가 데이터베이스나 서버에 대한 완전한 제어권을 얻기 위해 권한을 상승시킬 수도 있습니다.

실제 사례:
2019년에 Capital One 데이터 유출은 공격자가 SQL 주입 취약점을 악용할 수 있도록 허용하는 잘못 구성된 웹 애플리케이션으로 인해 발생했습니다. 이로 인해 이름 주소, 신용 점수를 포함하여 1억 명이 넘는 고객의 개인 데이터가 유출되었습니다.
SQL 주입 보안 수준
DVWA는 학습자가 다양한 보호가 공격에 어떤 영향을 미치는지 확인할 수 있도록 SQL 주입에 대한 네 가지 보안 수준을 제공합니다.
1. 낮은 보안
앱은 입력을 받아 필터링 없이 SQL 쿼리에 직접 입력합니다.
$id = $_GET['id'];$query = 'SELECT first_name last_name FROM users WHERE user_id = '$id';';- 입장
':쿼리를 중단하고 데이터베이스가 취약하다는 오류를 발생시킵니다. - 입장
1' OR '1'='1:모든 사용자가 반환되도록 쿼리를 항상 true로 속입니다. - 입장
1' UNION SELECT user password FROM users--:사용자 이름 및 비밀번호와 같은 숨겨진 데이터를 가져오기 위해 다른 쿼리를 결합합니다.
2. 중간 보안
앱은 다음과 같은 기능을 사용하여 기본 입력 삭제를 적용합니다.addslashes()탈출하다'.
$id = addslashes($_GET['id']);$query = 'SELECT first_name last_name FROM users WHERE user_id = '$id';';공격 방법:
간단한'주사가 더 이상 작동하지 않습니다(왜냐하면').
그러나 공격자는 숫자 삽입을 사용하여 우회할 수 있습니다(숫자는 따옴표가 필요하지 않기 때문).
예:
객체를 jsonobject java로
1 OR 1=1여전히 모든 레코드가 반환됩니다.
3. 높은 보안
앱은 준비된 문(매개변수화된 쿼리)을 사용하여 사용자 입력을 안전하게 처리합니다.
$stmt = $pdo->prepare('SELECT first_name last_name FROM users WHERE user_id = ?');$stmt->execute([$id]);공격:
다음과 같은 시도' OR 1=1또는UNION SELECT더 이상 작동하지 않습니다.
쿼리는 모든 입력을 SQL 코드가 아닌 데이터로 처리합니다.
SQL 주입 유형
SQL 인젝션에는 다양한 유형이 있습니다.
1. 오류 기반 SQL 인젝션
오류 기반 SQL 주입은 공격자가 의도적으로 데이터베이스에서 오류 메시지를 생성하도록 하는 일종의 대역 내 SQL 주입입니다. 그런 다음 공격자는 이 오류 메시지를 분석하여 더욱 정확한 공격을 만드는 데 사용할 수 있는 테이블 이름 및 열 이름과 같은 데이터베이스 구조에 대한 귀중한 정보를 얻습니다.
작동 방식
이 공격은 일반 메시지를 표시하는 대신 원시 데이터베이스 오류를 표시하는 애플리케이션을 대상으로 합니다. SQL 구문을 깨뜨리는 악의적인 입력을 삽입함으로써 공격자는 이러한 오류를 유발하고 데이터베이스 구조에 대한 귀중한 단서를 얻습니다.
UDP 프로토콜
- 취약한 입력 식별: 공격자는 적절한 입력 삭제 없이 데이터베이스와 직접 상호작용하는 검색창이나 URL 매개변수와 같은 입력 필드를 찾습니다.
- 악성 페이로드를 주입합니다: 공격자는 작은따옴표와 같은 특수 문자를 삽입합니다.
') 또는 데이터베이스 오류를 일으키는 것으로 알려진 함수입니다. - 오류 분석: 잘못된 쿼리를 처리할 수 없는 데이터베이스에서 자세한 오류 메시지가 반환됩니다. 이 메시지는 다음과 같은 중요한 정보를 공개할 수 있습니다.
- 데이터베이스 시스템(예: MySQL Oracle SQL Server)
- 데이터베이스의 버전입니다.
- 실행 중인 전체 SQL 쿼리입니다.
- 테이블 또는 열 이름을 이해하는 데 사용할 수 있는 특정 구문 오류입니다.
- 공격 개선: 공격자는 오류 메시지에서 수집된 정보를 사용하여 페이로드를 정제하여 사용자 이름 및 비밀번호와 같은 더 많은 데이터를 추출할 수 있습니다.
예:
1단계: 환경 설정
- DVWA를 실행합니다. 일반적으로 다음과 같은 URL로 이동하여 액세스합니다.
http://localhost/dvwa귀하의 브라우저에서.
- 기본 자격 증명을 사용하여 DVWA에 로그인합니다.
admin/password.
- DVWA 보안 탭으로 이동하여 보안 수준을 낮음으로 설정하세요. 이렇게 하면 취약점을 쉽게 악용할 수 있습니다.
2단계: 취약점 식별
SQL 주입 페이지에는 사용자 ID를 입력할 수 있는 간단한 입력 상자가 있습니다. 백엔드 쿼리는 다음과 같습니다.SELECT * FROM users WHERE id = 'user_input'
- 다음과 같은 유효한 ID를 입력하세요.
1입력란에 입력하고 '제출'을 클릭하세요. ID 1을 가진 사용자에 대한 세부정보가 표시됩니다.
SQL 주입 소스
PHP $id = $_REQUEST[ 'id' ]; switch ($_DVWA['SQLI_DB']) { case MYSQL: // Check database $query = 'SELECT first_name last_name FROM users WHERE user_id = '$id';'; $result = mysqli_query($GLOBALS['___mysqli_ston'] $query ) or die( ''
. ((is_object($GLOBALS['___mysqli_ston'])) ? mysqli_error($GLOBALS['___mysqli_ston']) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Get values $first = $row['first_name']; $last = $row['last_name']; // Feedback for end user echo 'ID:
{$id}
First name: {$first}
Surname: {$last}'; } mysqli_close($GLOBALS['___mysqli_ston']); break; case SQLITE: global $sqlite_db_connection; #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']); #$sqlite_db_connection->enableExceptions(true); $query = 'SELECT first_name last_name FROM users WHERE user_id = '$id';'; #print $query; try { $results = $sqlite_db_connection->query($query); } catch (Exception $e) { echo 'Caught exception: ' . $e->getMessage(); exit(); } if ($results) { while ($row = $results->fetchArray()) { // Get values $first = $row['first_name']; $last = $row['last_name']; // Feedback for end user echo 'ID:
{$id}
First name: {$first}
Surname: {$last}'; } } else { echo 'Error in fetch '.$sqlite_db->lastErrorMsg(); } break; } } ode ?> - 이제 쿼리를 중단해 보세요. 작은따옴표를 입력하세요.
'입력란에 입력하고 제출하세요.
쿼리는 다음과 같습니다.
SELECT * FROM users WHERE id = ''';여기서 데이터베이스는 추가 견적을 보고 쿼리를 완료하는 방법을 모릅니다.
.06을 분수로
사용자 세부 정보를 표시하는 대신 응용 프로그램은 SQL 오류(예: 'SQL 구문에 오류가 있습니다…')를 반환합니다.
이를 오류 기반 SQL 주입이라고 합니다. 그 이유는 다음과 같습니다.
- 공격자가 잘못된 입력(
') - 데이터베이스에서 오류가 발생했습니다.
- 해당 오류는 데이터베이스에 대한 유용한 정보(예: DB 유형 열 구조 수 등)를 유출합니다.
2. Union 기반 SQL 인젝션
Union 기반 SQL 주입은 공격자가 다음을 사용하는 기술입니다.UNION둘 이상의 결과를 결합하는 연산자SELECT명령문을 단일 결과 세트로 만듭니다. 이를 통해 데이터베이스의 다른 테이블에서 정보를 추출할 수 있습니다. 그만큼UNION연산자는 다음과 같은 경우에만 사용할 수 있습니다.
- 두 쿼리 모두 동일한 수의 열을 갖습니다.
- 열의 데이터 유형이 비슷합니다.
- 열의 순서는 동일합니다.
UNION 운영자 :UNION연산자는 둘 이상의 결과 집합을 결합하는 데 사용됩니다.SELECT진술.
- 각
SELECT내의 진술UNION동일한 개수의 열이 있어야 합니다. - 열의 데이터 유형은 유사해야 합니다.
- 열의 순서는 동일해야 합니다.
SELECT column_name(s) FROM table1UNIONSELECT column_name(s) FROM table2예:
1단계: 먼저 UNION 기반 SQL 주입을 주입하려면 웹 사이트에 있는 기존 테이블의 열 수를 찾아야 합니다.
SQL 주입 페이지에는 사용자 ID를 입력할 수 있는 간단한 입력 상자가 있습니다. 백엔드 쿼리는 다음과 같습니다.
SELECT * FROM users WHERE id = 'user_input'이제 쿼리를 중단해 보세요. 작은따옴표를 입력하세요.'입력란에 입력하고 제출하세요.
애플리케이션이 취약한 경우 자세한 오류 메시지가 표시됩니다. 다음과 같이 보일 수 있습니다.
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1
2단계: 사용UNION열 수를 알아내는 키워드
사용하려면UNION키워드(일반적인 다음 단계)는 원래 쿼리의 열 수를 알아야 합니다. 이는 다음을 사용하여 확인할 수 있습니다.ORDER BY절
숫자로 된 'abc'
- 결과를 열별로 정렬해 보세요.
1:1 ORDER BY 1.
- 제출하다. 작동해야합니다.
SQL 주입 소스
PHP if( isset( $_REQUEST[ 'Submit' ] ) ) { // Get input $id = $_REQUEST[ 'id' ]; switch ($_DVWA['SQLI_DB']) { case MYSQL: // Check database $query = 'SELECT first_name last_name FROM users WHERE user_id = '$id';'; $result = mysqli_query($GLOBALS['___mysqli_ston'] $query ) or die( ''
. ((is_object($GLOBALS['___mysqli_ston'])) ? mysqli_error($GLOBALS['___mysqli_ston']) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Get values $first = $row['first_name']; $last = $row['last_name']; // Feedback for end user echo 'ID:
{$id}
First name: {$first}
Surname: {$last}'; } mysqli_close($GLOBALS['___mysqli_ston']); break; case SQLITE: global $sqlite_db_connection; #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']); #$sqlite_db_connection->enableExceptions(true); $query = 'SELECT first_name last_name FROM users WHERE user_id = '$id';'; #print $query; try { $results = $sqlite_db_connection->query($query); } catch (Exception $e) { echo 'Caught exception: ' . $e->getMessage(); exit(); } if ($results) { while ($row = $results->fetchArray()) { // Get values $first = $row['first_name']; $last = $row['last_name']; // Feedback for end user echo 'ID:
{$id}
First name: {$first}
Surname: {$last}'; } } else { echo 'Error in fetch '.$sqlite_db->lastErrorMsg(); } break; } } ?> - 숫자를 증가시킵니다:
1 ORDER BY 2. 제출하다. 작동해야합니다.
- 오류가 발생할 때까지 계속 증가합니다. 예를 들어
1 ORDER BY 4다음을 제공할 수 있습니다.Unknown column '4' in 'order clause' - 이는 쿼리에 3개의 열이 있음을 의미합니다.
3. 블라인드 기반 SQL 인젝션
블라인드 SQL 인젝션 공격자가 웹페이지에서 쿼리 결과를 직접 볼 수 없을 때 발생합니다. 대신 애플리케이션 동작이나 응답 시간의 미묘한 변화로부터 정보를 추론합니다. 기존 SQLi보다 느리고 지루하지만 똑같이 효과적일 수 있습니다.
공격자는 데이터를 다시 가져오는 대신 웹 페이지의 동작을 관찰하여 정보를 추론합니다. 이는 일반적으로 다음 두 가지 방법 중 하나로 수행됩니다.
- 부울 기반 블라인드 SQLi: 공격자는 다음을 반환하는 SQL 쿼리를 삽입합니다. 진실 또는 거짓 결과. 쿼리가 true인지 false인지에 따라 웹 애플리케이션의 응답이 변경됩니다. 예를 들어 페이지에 다른 메시지가 표시되거나 다른 레이아웃이 렌더링될 수 있습니다.
- 시간 기반 블라인드 SQLi: 공격자는 데이터베이스가 시간이 많이 걸리는 작업(예:
SLEEP()기능) 조건이 충족되면. 공격자는 삽입된 조건이 참인지 거짓인지 확인하기 위해 페이지가 로드되는 데 걸리는 시간을 관찰합니다.
예:
사용자 이름과 비밀번호를 입력하는 로그인 페이지를 상상해 보세요. 애플리케이션은 다음과 같은 SQL 쿼리를 구성합니다.
SELECT * FROM users WHERE username = 'user_input' AND password = 'password_input'블라인드 SQL 주입에는user_input데이터베이스에 질문을 하는 필드입니다.
직접적인 응답을 얻는 대신 공격자는 다음과 같은 것을 시도할 수 있습니다.
user_input = 'admin' AND 1=1; --페이지가 정상적으로 로드되면 공격자는 이를 알게 됩니다.1=1는 진실 성명.
user_input = 'admin' AND 1=2; --페이지에 오류가 표시되거나 다르게 동작하는 경우 공격자는 이를 알고 있습니다.1=2는 거짓 성명.
일련의 참/거짓 질문을 사용하여 공격자는 체계적으로 한 번에 한 문자씩 정보를 추측하고 추출할 수 있습니다. 테이블 이름부터 사용자 비밀번호까지 모든 것을 추측하도록 프로세스를 자동화할 수 있습니다.
SQL 주입 공격의 영향
- 민감한 데이터에 대한 무단 액세스 : 공격자는 데이터베이스에 저장된 개인 금융 정보나 기밀 정보를 검색할 수 있습니다.
- 데이터 무결성 문제 : 공격자는 애플리케이션 기능에 영향을 미치는 중요한 데이터 삭제를 수정하거나 손상시킬 수 있습니다.
- 권한 승격 : 공격자는 인증 메커니즘을 우회하고 관리 권한을 얻을 수 있습니다.
- 서비스 다운타임 : SQL 주입은 서버에 과부하를 주어 성능 저하나 시스템 충돌을 일으킬 수 있습니다.
- 평판 훼손 : 공격이 성공하면 조직의 평판이 심각하게 훼손되어 고객의 신뢰를 잃을 수 있습니다.
SQL 주입 공격 방지
SQL 주입 공격을 방지하기 위한 몇 가지 모범 사례가 있습니다.
1. 준비된 문과 매개변수화된 쿼리를 사용하세요.
준비된 문과 매개변수화된 쿼리를 사용하면 사용자 입력이 SQL 쿼리의 일부가 아닌 데이터로 처리됩니다. 이 접근 방식은 SQL 주입의 위험을 제거합니다.
트리의 선주문 순회
PHP의 예(MySQLi 사용):
$stmt = $conn->prepare('SELECT * FROM users WHERE username = ? AND password = ?'); $stmt->bind_param('ss' $username $password); $stmt->execute();2. 저장 프로시저 사용
저장 프로시저는 데이터베이스에 저장된 미리 정의된 SQL 쿼리입니다. 이러한 프로시저는 SQL 쿼리를 동적으로 생성하지 않기 때문에 SQL 주입을 방지하는 데 도움이 될 수 있습니다.
예:
CREATE PROCEDURE GetUserByUsername (IN username VARCHAR(50)) BEGIN SELECT * FROM users WHERE username = username; END;3. 화이트리스트 입력 검증
사용자 입력이 SQL 쿼리에 사용되기 전에 유효성이 검사되는지 확인하세요. 사용자 이름이나 이메일 주소와 같은 필드에는 영숫자 입력과 같은 특정 문자와 패턴만 허용합니다.
4. ORM 프레임워크 사용
ORM(객체 관계형 매핑) 프레임워크 최대 절전 모드 또는 엔터티 프레임워크 동적 쿼리 생성을 방지하는 쿼리 생성을 자동으로 처리하여 SQL 주입을 방지하는 데 도움이 됩니다.
5. 데이터베이스 권한 제한
사용자에게 필요한 최소 데이터베이스 권한을 부여합니다. 애플리케이션이 필요한 작업(예: SELECT INSERT)만 수행하고 DROP TABLE 또는 ALTER와 같은 권한을 제한할 수 있는지 확인하세요.
6. 오류 처리
사용자에게 자세한 오류 메시지를 표시하지 않도록 데이터베이스와 애플리케이션을 구성합니다. 대신 내부적으로 오류를 기록하고 최종 사용자에게 일반 오류 메시지를 표시합니다.