SQL Injection은 공격자가 쿼리 파라미터에 악성 SQL 코드를 주입하여 데이터에 대한 무단 액세스나 데이터 수정 능력을 얻는 데이터베이스 공격 유형입니다. OWASP(2025)에 따르면, SQL Injection은 데이터베이스를 완전히 손상시킬 수 있는 중요한 취약점 중 하나로 남아 있습니다. SQL 코드 인젝션을 통해 공격자는 레코드를 읽고, 수정하고, 삭제할 수 있으며, 경우에 따라 서버의 운영 체제에 액세스할 수 있습니다.
핵심 사항
SQL Injection은 애플리케이션이 사용자 데이터와 문자열 연결을 통해 SQL 쿼리를 구성할 때 발생하는 취약점입니다. 공격자는 특수하게 조작된 문자열을 쿼리 파라미터에 전달하여 SQL 명령의 구조를 변경합니다. 주입된 문자열은 데이터가 아닌 SQL 코드의 일부가 되어, 공격자가 데이터베이스에 대해 임의의 쿼리를 실행할 수 있게 합니다. Verizon 데이터 유출 보고서(2025)에 따르면, SQL Injection은 조사된 모든 데이터 유출의 8%에서 발견됩니다.
잘 알려진 취약점임에도 불구하고(1990년대 후반에 처음 언급됨), SQL Injection은 현대 애플리케이션에서 여전히 발견됩니다. 그 이유는 인적 오류입니다. 개발자가 문자열 연결을 사용하는 코드를 작성하고, 레거시 코드가 리팩토링되지 않으며, ORM 프레임워크가 부적절하게 사용됩니다(예: 문자열 보간을 사용한 raw 쿼리). Veracode(2026)에 따르면, 스캔된 모든 애플리케이션의 약 14%에 최소 하나의 SQLi 취약점이 포함되어 있습니다.
성공적인 SQL Injection은 공격자에게 광범위한 능력을 제공합니다: 비밀번호 해시와 사용자 개인 데이터를 포함한 모든 데이터베이스 테이블 읽기, 레코드 수정 및 삭제, 관리 작업(DROP TABLE, TRUNCATE) 실행, 일부 구성에서는 xp_cmdshell(MSSQL) 또는 INTO OUTFILE(MySQL)을 통한 원격 명령 실행. 결과는 사용자 데이터 유출부터 시스템 제어권 완전 상실까지 다양합니다.
SQL 인젝션은 데이터베이스에서 데이터를 추출하는 방법에 따라 분류됩니다. 방법 선택은 애플리케이션이 쿼리 결과와 오류 메시지를 처리하는 방식에 따라 달라집니다. OWASP 분류는 세 가지 주요 유형을 식별합니다: In-band(동일한 채널을 통한 데이터 추출), Inferential/Blind(논리적 추론), Out-of-band(다른 채널을 통한 데이터 전송).
| 유형 | 데이터 추출 방법 | 복잡성 | 빈도 |
|---|---|---|---|
| In-band(클래식) | 쿼리 결과를 직접 통해 | 낮음 | 높음 |
| Blind SQLi | 서버 응답에서 논리적 추론 | 높음 | 중간 |
| Out-of-band | 외부 채널(DNS, HTTP)을 통해 | 중간 | 낮음 |
가장 일반적인 유형입니다. 공격자는 쿼리 파라미터에 SQL 코드를 주입하고, 그 결과가 서버 응답에 직접 표시됩니다. 두 가지 하위 유형: Error-based(DB 오류 메시지를 통해)와 UNION-based(UNION SELECT 연산자를 통해). Error-based는 테이블 이름이나 쿼리 구조를 드러낼 수 있는 MySQL 구문 오류와 같은 오류 메시지의 정보를 활용합니다. UNION-based는 합법적인 쿼리 결과를 데이터베이스의 다른 테이블 데이터와 결합할 수 있게 합니다.
애플리케이션이 쿼리 결과나 오류 메시지를 표시하지 않을 때 사용됩니다. 공격자는 논리적 조건이 포함된 쿼리를 보내고 서버 응답의 차이(응답 시간 또는 페이지 내용 등)를 분석하여 예/아니오 질문을 합니다. Time-based Blind SQLi는 조건을 확인하기 위해 지연 함수(SLEEP, WAITFOR DELAY)를 사용합니다. 조건이 참이면 페이지 로딩 시간이 더 오래 걸립니다. 이 방법은 매우 느려서 하나의 레코드를 추출하는 데 몇 시간이 걸릴 수 있습니다.
# Blind SQL Injection 예제(시간 기반)
# SQLi에 취약한 경우, 조건이 충족되면 SLEEP(2)이 실행됨
import requests
import time
payload = "' OR IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',SLEEP(2),0) -- "
url = "http://example.com/user?id=1" + payload
start = time.time()
response = requests.get(url)
elapsed = time.time() - start
# 응답이 >2초 후에 도착하면 — 비밀번호의 첫 글자는 'a'
print(f"Time: {elapsed:.2f}s - First letter is 'a'" if elapsed > 2 else "First letter is not 'a'")
데이터는 HTTP 응답이 아닌 대체 채널(DNS 쿼리, 외부 서버로의 HTTP 요청, SMTP)을 통해 전송됩니다. 애플리케이션이 쿼리 결과를 반환하지 않거나 오류를 표시하지 않을 때 사용됩니다. MySQL은 DNS 쿼리를 트리거할 수 있는 LOAD_FILE() 함수를 지원하며, MSSQL에는 원격 SMB 서버로 데이터를 보내는 xp_dirtree가 있습니다. Out-of-band SQL Injection은 효과적이지만 추가적인 공격자 서버 설정과 특정 DB 함수가 필요합니다.
SQL Injection 메커니즘은 SQL이 문자열 리터럴에 따옴표를 사용한다는 점에 기반합니다. 애플리케이션이 사용자 입력을 이스케이프 없이 SQL 쿼리에 직접 삽입하는 경우, 공격자는 문자열을 “닫고” 임의의 SQL 코드를 추가할 수 있습니다. 예를 들어, 쿼리 SELECT * FROM users WHERE name = '$input'에 ' OR '1'='1을 삽입하면 SELECT * FROM users WHERE name = '' OR '1'='1'로 변환되어 모든 사용자를 반환합니다.
쿼리 SELECT * FROM users WHERE username = '$user' AND password = '$pass'를 사용하는 로그인 폼을 생각해보세요. 공격자가 사용자 이름 필드에 admin' -- 을 입력하고 비밀번호를 비워두면, 결과 쿼리는 SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''이 됩니다. -- 문자는 나머지 쿼리를 주석 처리하여 비밀번호 확인을 비활성화합니다. 서버는 admin 사용자 레코드를 반환하고, 공격자는 비밀번호를 모르고 로그인합니다.
# SQL Injection 예제 — 인증 우회
# 취약한 코드: 직접 문자열 연결
def login_vulnerable(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
return cursor.fetchone() is not None
# username = "admin' --"이 비밀번호 확인을 무효화
# 안전한 버전: 파라미터화된 쿼리
def login_secure(username, password):
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
return cursor.fetchone() is not None
UNION SELECT 연산자는 두 SELECT 쿼리의 결과를 결합할 수 있습니다. 공격자가 취약한 파라미터를 찾으면 다른 테이블의 쿼리와 함께 UNION SELECT를 추가합니다. 예: ' UNION SELECT username, password FROM admins --. 성공 조건은 두 쿼리의 컬럼 수가 일치해야 한다는 것입니다. 컬럼 수는 ORDER BY를 통해 결정됩니다(' ORDER BY 1--, 그 다음 2, 3... 오류가 발생할 때까지). 컬럼 수를 알면 공격자는 동일한 필드 수로 UNION SELECT를 삽입합니다.
모바일 애플리케이션은 서버 측(API)과 클라이언트 측(로컬 데이터베이스: SQLite, Realm) 모두에서 SQL Injection에 직면합니다. 모바일 API의 서버 측 SQLi는 웹 애플리케이션과 유사하지만, 로컬 데이터베이스는 추가적인 벡터를 만듭니다. 애플리케이션이 SQLite에 데이터를 저장하고 문자열 연결로 쿼리를 실행하는 경우, API를 통해 로컬 데이터베이스에 유입된 악성 데이터가 후속 처리 중에 SQLi를 트리거할 수 있습니다.
디바이스의 로컬 SQLite 데이터베이스도 애플리케이션이 문자열 연결로 쿼리를 구성하는 경우 SQL Injection에 취약합니다. Android의 Content Providers와 iOS의 Core Data는 기본적으로 파라미터화를 사용하지만, raw 쿼리는 개발자의 주의가 필요합니다. SQLite는 세미콜론으로 구분된 여러 쿼리를 지원하지 않아 공격자의 옵션을 제한하지만 WHERE 조건을 통한 데이터 읽기를 막지는 못합니다. Android에서는 항상 selectionArgs를, iOS에서는 파라미터와 함께 NSPredicate를 사용하세요.
모바일 애플리케이션이 통신하는 API는 다른 웹 서버와 마찬가지로 취약합니다. 모바일 개발자는 종종 SQLi를 백엔드 문제로만 생각하지만, 취약점은 클라이언트의 파라미터를 수락하는 API 엔드포인트에서 발생합니다. 책임 분리는 보호가 되지 않습니다. 백엔드 개발자가 쿼리 파라미터화를 잊은 경우, 사용자의 모바일 앱이 공격 벡터가 됩니다. 백엔드에 ORM 또는 prepared statements 사용을 요구하세요.
// Android의 로컬 SQLite에서 SQL Injection 예제
// 취약한 코드: 직접 연결
fun getUserVulnerable(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = $userId", null
)
}
// 안전한 버전: selectionArgs를 통한 파라미터화
fun getUserSecure(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = ?",
arrayOf(userId)
)
}
SQL Injection으로부터의 보호는 간단한 원칙에 기반합니다: SQL 쿼리에서 사용자 입력을 절대 신뢰하지 마십시오. 유일하게 신뢰할 수 있는 방법은 SQL 코드와 데이터를 별도로 전달하는 쿼리 파라미터화(prepared statements)입니다. 이스케이프, 검증, WAF 등 다른 모든 방법은 추가적인 보안 계층이지만 파라미터화를 대체하지는 않습니다. OWASP(2025)에 따르면, 파라미터화는 SQL Injection 공격의 100%를 방지합니다.
Prepared statements를 사용하면 SQL 쿼리가 먼저 DB 서버에 의해 데이터 없이 컴파일된 후, 파라미터 값이 별도로 전달됩니다. 데이터베이스는 파라미터를 실행 가능한 코드가 아닌 데이터로 취급합니다. 공격자가 ' OR '1'='1을 전달하더라도 데이터베이스는 이를 SQL 코드가 아닌 문자열 값으로 해석합니다. Prepared statements는 PHP의 PDO, Java의 PreparedStatement, Python의 cursor.execute 등 모든 최신 언어와 프레임워크에서 지원됩니다.
최신 ORM(Hibernate, Entity Framework, SQLAlchemy, Room)은 개발자가 raw 쿼리로 전환하지 않는 한 쿼리 실행 시 자동으로 파라미터화를 사용합니다. 그러나 ORM이 완전히 보호하지는 않습니다. JPA의 @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true)와 같은 구조는 연결이 아닌 명명된 파라미터를 통해 파라미터를 전달해야 합니다. 쿼리 빌더(Knex, jOOQ)도 raw 메서드를 사용하지 않으면 기본적으로 쿼리를 파라미터화합니다.
특수 문자 이스케이프(mysql_real_escape_string)는 모든 유형의 SQL Injection으로부터 보호하지 않는 구식 방법입니다. 문제는 이스케이프가 인코딩에 의존하며 멀티바이트 인코딩(예: 아시아 시스템의 GBK)을 사용할 때 우회될 수 있다는 점입니다. 파라미터화가 불가능한 레거시 코드에서만 이스케이프를 사용하고, 항상 엄격한 입력 유형 검증과 함께 사용하세요.
| 방법 | 효과 | 권장 |
|---|---|---|
| Prepared Statements | 100% | 모든 쿼리에 필수 |
| ORM(올바른 사용) | 99% | 권장 |
| 문자열 이스케이프 | 70%(인코딩에 따라 다름) | 레거시만 |
| 입력 검증(화이트리스트) | 50%(숫자만) | 추가 |
| WAF(Web Application Firewall) | 60% | 추가 |
애플리케이션 계정에는 최소한의 필요한 권한(SELECT, INSERT, UPDATE, DELETE — 애플리케이션이 실제로 필요로 하는 테이블만)이 있어야 합니다. 애플리케이션 계정의 DROP, TRUNCATE, CREATE 사용을 금지하세요. 이는 SQL Injection이 성공하더라도 피해를 제한합니다. 공격자는 테이블을 삭제하거나 관리 작업을 수행할 수 없습니다.
정기적인 SQL Injection 테스트는 보안 개발 파이프라인의 일부여야 합니다. 정적 분석, 동적 스캔 및 수동 침투 테스트의 조합이 최상의 결과를 제공합니다. Synopsys 사이버 보안 보고서(2025)에 따르면, 자동화된 스캐너는 SQLi 취약점의 최대 70%를 찾지만, 복잡한 Blind 공격은 수동 테스트가 필요합니다.
모바일 애플리케이션의 경우 로컬 SQLite 분석도 중요합니다: 모든 rawQuery 호출, ContentProvider 쿼리 및 rawQuery를 사용한 Room 쿼리를 확인하세요. 도구: Android Studio Lint(SQLite에서 SQLi 탐지), APK/IPA의 자동 정적 및 동적 분석을 위한 MobSF(Mobile Security Framework). 또한 모바일 애플리케이션 트래픽의 프록시 인터셉션과 함께 sqlmap을 통해 API 엔드포인트를 테스트하는 것이 좋습니다.
자주 묻는 질문
SQL Injection은 SQL 쿼리를 통해 관계형 데이터베이스를 공격합니다. NoSQL Injection은 쿼리 연산자($gte, $ne, $where)를 통해 비관계형 데이터베이스(MongoDB, Couchbase)에 영향을 줍니다. MongoDB에서는 애플리케이션이 JSON 문자열에서 BSON 문서를 구성하는 경우 인젝션이 가능합니다. 보호 메커니즘은 유사합니다: 파라미터화와 유형 검증입니다.
사용자 데이터와 문자열 연결을 통해 SQL 쿼리가 형성되는 모든 위치를 찾으세요. "SELECT ... WHERE id = " + userId 또는 f"UPDATE ... SET name = '{name}'"과 같은 패턴을 찾으십시오. 이러한 각 줄은 잠재적인 SQL 인젝션입니다. 모두 파라미터화된 쿼리 또는 prepared statements로 교체하세요.
ORM 프레임워크는 쿼리 빌더 메서드와 명명된 파라미터를 사용하는 경우에만 자동으로 보호합니다. raw 쿼리(JPA의 nativeQuery, Room의 rawQuery)를 사용하는 경우 보호가 작동하지 않습니다. 문자열 연결이 아닌 준비된 표현식을 통해 파라미터를 전달해야 합니다.
Second-Order SQL Injection은 악성 데이터가 데이터베이스에 안전하게 저장된 후, 이스케이프 없이 다른 쿼리에서 사용되는 공격입니다. 예를 들어, 공격자가 ' OR '1'='1과 같은 사용자 이름으로 등록합니다. 데이터는 문자열로 저장되며, 등록 시에는 공격이 없습니다. 그러나 다른 쿼리가 파라미터화 없이 SQL에서 해당 사용자 이름을 사용하면 인젝션이 트리거됩니다.
NoSQL Injection은 개발자 인식 부족으로 인해 잠재적으로 더 위험할 수 있습니다. 개발자는 SQL Injection에 대해 알고 있고 대부분 ORM을 사용하지만, NoSQL Injection에 대해 아는 사람은 거의 없습니다. MongoDB에서 부적절하게 형성된 쿼리는 컬렉션의 모든 문서를 반환할 수 있습니다. 보호는 동일합니다: prepared statements(BSON 파라미터화)와 엄격한 입력 검증입니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.