본 발표는 Ruby on Rails 애플리케이션 개발 시 마주칠 수 있는 SQL Injection 취약점에 대한 경각심을 일깨우고, Active Record 사용 환경에서 안전하게 데이터를 처리하는 방법을 모색합니다. 특히 사용자 입력을 SQL 쿼리에 포함시킬 때 발생할 수 있는 위험을 진단하고, Rails가 제공하는 방어 메커니즘과 개발자가 취해야 할 구체적인 예방 조치에 대해 논의합니다. 이를 통해 개발자들이 SQL Injection의 위협으로부터 벗어나 보다 안전한 코드를 작성할 수 있도록 돕는 것을 목표로 합니다.
SQL Injection은 SQL 구문 조립 방식의 문제로 인해 공격자가 데이터베이스를 비정상적으로 조작할 수 있게 만드는 심각한 웹 취약점입니다. Rails 환경에서 흔히 발생할 수 있는 취약한 코드는 where
메서드에 사용자 입력을 문자열 보간(#{}
)으로 직접 전달하는 경우입니다. 이러한 취약점은 개인 정보 유출, 웹 페이지 변조, 시스템 탈취 등 서비스 지속성을 위협하는 심각한 결과를 초래할 수 있습니다.
SQL Injection의 일반적인 방지법으로는 SQL 구문 조립 시 PreparedStatement를 사용하거나, 문자열 연결 시 Escape 처리를 수행하는 방법이 있습니다. Rails는 Active Record 내부적으로 이러한 방어 기제를 충실히 구현하고 있습니다. find_by
와 같은 메서드는 설정에 따라 PreparedStatement를 사용하거나 Active Record 내에서 리터럴 Escape 처리를 수행하여 안전하게 동작합니다.
그러나 where
메서드에 SQL Fragment를 문자열로 직접 전달하는 방식은 여전히 위험합니다. 이 경우 Active Record는 해당 문자열을 SQL 구문의 일부로 간주하고 Escape 처리를 하지 않기 때문입니다. 안전하게 값을 포함시키려면 위치 지정 핸들러(?
) 또는 명명된 핸들러(:name
)를 사용하여 값을 별도로 전달해야 합니다. Active Record는 이 방식으로 전달된 값에 대해 적절한 Escape 처리를 적용합니다. Query 메서드가 안전한지 판단하는 기준은 SQL 구문의 리터럴과 1:1로 대응하는 값을 전달하는 방식인지 여부입니다.
order
메서드의 경우, Rails 6.1부터는 허용된 문자열 패턴을 만족하지 않으면 예외를 발생시켜 SQL Injection 공격을 방지하고 있습니다. 복잡한 정렬 기준이 필요한 경우 arel_sql
메서드로 래핑하여 사용해야 하지만, 이 경우 개발자가 직접 문자열의 안전성을 보장해야 합니다.
특히 주의해야 할 메서드 중 하나는 execute
입니다. execute
는 인자를 받는 다양한 방식이 있으며, 배열 형태로 받을 경우 첫 번째 요소는 SQL Fragment로 처리되고 이후 요소는 값으로 처리됩니다. 이때 params
와 같이 외부 입력을 배열로 직접 전달하면 임의의 SQL 구문이 실행될 위험이 있습니다. 따라서 execute
와 같이 SQL Fragment를 받는 메서드를 사용할 때는 외부 입력을 절대 부주의하게 전달해서는 안 됩니다.
SQL Injection 방지를 위한 핵심은 다음과 같습니다. 첫째, SQL Fragment를 인자로 받을 수 있는 Active Record 메서드를 정확히 인지해야 합니다. 둘째, 그리고 가장 중요하게, Query 메서드에 외부 입력(특히 params
)을 안전하지 않은 형태로 전달하지 않아야 합니다. 반드시 Escape 처리가 확실히 적용되는 형식을 선택하거나, 전달되는 값에 대한 엄격한 입력 검증을 수행해야 합니다.
이러한 수동적인 노력과 더불어, Breakman과 같은 정적 분석 도구를 활용하는 것이 매우 효과적입니다. Breakman은 Ruby on Rails 코드의 잠재적 취약점을 스캔하여 보고해주며, SQL Injection 취약점도 탐지합니다. Rails 버전에 따라 안전하거나 취약한 메서드 패턴을 카탈로그화하여 검사하므로, 개발자가 모든 SQL Fragment를 받는 메서드를 일일이 기억할 필요가 줄어듭니다. 다만, Breakman은 오탐(False Positive)이 발생할 수 있으므로, 보고서를 맹신하기보다는 검토 후 적절한 설정을 통해 활용하는 것이 좋습니다.
결론적으로, Ruby on Rails는 Active Record를 통해 SQL Injection 방어를 위한 다양한 내부 메커니즘을 제공하고 있습니다. 그러나 개발자가 사용자 입력을 SQL 쿼리에 부주의하게 직접 삽입하는 방식(`where`에 문자열 보간 사용, `execute`에 `params` 배열 전달 등)은 여전히 위험을 초래합니다. 따라서 개발자는 Active Record 메서드의 안전한 사용법을 숙지하고, 외부 입력을 처리할 때 항상 Escape 처리가 보장되는 방식을 선택하거나 엄격한 입력 유효성 검사를 수행해야 합니다. 또한 Breakman과 같은 정적 분석 도구를 개발 파이프라인에 통합하여 잠재적 취약점을 조기에 발견하고 수정하는 노력을 병행한다면, SQL Injection으로부터 안전한 Rails 애플리케이션을 구축할 수 있을 것입니다.