이전 글에서 WAF에 대해 정리하면서, WAF가 탐지하거나 차단할 수 있는 대표적인 웹 공격 중 하나로 SQL Injection을 언급했다.
SQL Injection은 웹 보안에서 자주 등장하는 공격 기법이며, 로그인 우회나 데이터베이스 정보 유출과도 연결될 수 있는 중요한 개념이다.
개발을 공부하면서 SQL을 사용해 데이터를 조회하고 저장하는 과정은 익숙했지만, 사용자의 입력값이 어떻게 공격으로 이어질 수 있는지는 명확히 이해하지 못했다.
그래서 이번 글에서는 SQL Injection이 무엇인지, 왜 발생하는지, 그리고 어떻게 방어할 수 있는지 정리해보려고 한다.
SQL Injection은 공격자가 입력값에 악의적인 SQL 구문을 삽입하여, 데이터베이스에 의도하지 않은 쿼리가 실행되도록 만드는 공격이다.
웹 애플리케이션은 사용자가 입력한 값을 바탕으로 데이터베이스에 질의하는 경우가 많다. 예를 들어 로그인, 검색, 게시글 조회, 회원 정보 수정 같은 기능에서 SQL 쿼리가 사용될 수 있다.
이때 사용자의 입력값이 제대로 검증되지 않고 SQL 문장에 직접 포함된다면, 공격자는 입력값을 조작해 원래 의도와 다른 SQL이 실행되도록 만들 수 있다.
즉, SQL Injection의 핵심은 사용자 입력값이 SQL 쿼리의 일부로 해석되는 문제라고 볼 수 있다.
SQL Injection은 주로 사용자의 입력값을 신뢰하고, 그 값을 SQL 문장에 그대로 연결할 때 발생한다.
예를 들어 로그인 기능에서 사용자가 입력한 아이디와 비밀번호를 이용해 다음과 같은 SQL을 만든다고 가정해보자.
SELECT *
FROM users
WHERE username = '입력한 아이디'
AND password = '입력한 비밀번호';
이 구조 자체가 항상 문제인 것은 아니다.
문제는 사용자가 입력한 값이 검증되지 않은 채 SQL 문자열에 그대로 이어 붙여질 때 발생한다.
공격자가 단순한 아이디나 비밀번호 대신 SQL 문법으로 해석될 수 있는 값을 입력하면, 데이터베이스는 이를 일반 문자열이 아니라 SQL 조건의 일부로 처리할 수 있다.
SQL Injection이 발생하는 대표적인 원인은 다음과 같다.
결국 SQL Injection은 웹 애플리케이션이 사용자 입력값을 안전하게 처리하지 못할 때 발생하는 취약점이다.
다음과 같은 로그인 쿼리가 있다고 가정해보자.
SELECT *
FROM users
WHERE username = 'user'
AND password = '1234';
이 쿼리는 아이디가 user이고, 비밀번호가 1234인 사용자를 찾기 위한 쿼리이다.
그런데 사용자의 입력값이 검증 없이 SQL 문장에 그대로 들어간다면 문제가 발생할 수 있다.
예를 들어 사용자가 다음과 같이 입력했다고 가정해보자.
admin' OR '1'='1이 입력값이 그대로 SQL에 포함되면 다음과 같은 쿼리가 만들어질 수 있다.
SELECT *
FROM users
WHERE username = 'admin'
AND password = '' OR '1'='1';
여기서 '1'='1'은 항상 참이 되는 조건이다.
원래는 아이디와 비밀번호가 모두 일치해야 하지만, 입력값이 SQL 조건식으로 해석되면서 의도하지 않은 결과가 발생할 수 있다.
실제 공격에서는 뒤에 남는 SQL 구문을 무시하기 위해 주석 문법을 함께 사용하는 경우도 있다. 하지만 여기서 중요한 것은 특정 공격 문자열을 외우는 것이 아니라, 사용자 입력값이 SQL 문법으로 해석되면 위험하다는 원리를 이해하는 것이다.
SQL Injection을 통해 발생할 수 있는 피해는 다음과 같다.
따라서 SQL Injection은 단순한 입력값 오류가 아니라, 데이터베이스와 서비스 전체의 보안에 영향을 줄 수 있는 취약점이다.
SQL Injection을 방어하기 위해서는 사용자의 입력값이 SQL 문법으로 해석되지 않도록 처리해야 한다.
가장 기본적이고 중요한 방법은 Prepared Statement 또는 Parameterized Query를 사용하는 것이다.
SELECT *
FROM users
WHERE username = ?
AND password = ?;
Prepared Statement는 SQL 문장의 구조를 먼저 정해두고, 사용자 입력값은 나중에 파라미터로 바인딩하는 방식이다.
즉, 데이터베이스는 ?가 들어간 쿼리의 구조를 먼저 분석하고, 이후 입력값은 SQL 문법이 아니라 단순한 데이터로 처리한다.
따라서 사용자가 ' OR '1'='1 같은 값을 입력하더라도, 데이터베이스는 이를 SQL 조건식이 아니라 하나의 문자열 값으로 인식한다. 입력값이 쿼리의 구조를 바꾸지 못하기 때문에 SQL Injection을 방어할 수 있다.
SQL Injection을 방어하기 위한 주요 방법은 다음과 같다.
여기서 중요한 점은 WAF만으로 SQL Injection을 완전히 막을 수는 없다는 것이다.
WAF는 공격 시도를 탐지하고 차단하는 데 도움을 줄 수 있지만, 근본적으로는 애플리케이션 코드에서 안전한 쿼리 작성 방식이 적용되어야 한다.
즉, SQL Injection 방어의 핵심은 입력값 검증 + 안전한 쿼리 작성 + 최소 권한 원칙이라고 볼 수 있다.
SQL Injection은 사용자의 입력값에 악의적인 SQL 구문이 삽입되어, 데이터베이스에 의도하지 않은 쿼리가 실행되도록 만드는 공격이다.
이 공격은 주로 사용자 입력값을 검증하지 않고 SQL 문자열에 그대로 연결할 때 발생한다.
SQL Injection의 핵심은 사용자의 입력값이 단순한 데이터가 아니라 SQL 문법으로 해석될 수 있다는 점이다.
이번 글을 정리하면서 SQL Injection은 단순히 SQL 문법을 악용하는 공격이 아니라, 입력값을 안전하게 처리하지 못했을 때 발생하는 대표적인 웹 취약점이라는 점을 알 수 있었다.
개발자는 사용자 입력값을 신뢰하지 않고, 입력값이 SQL 문장에 직접 연결되지 않도록 안전한 쿼리 작성 방식을 사용하는 것이 중요하다.