SQL Injection (4) - Entity Framework에서의 SQL Injection

[제목] SQL Injection (4) - Entity Framework에서의 SQL Injection

SQL Injection 관련 이전 아티클들은 ADO.NET에서의 Dynamic SQL과 SQL Stored Procedure에서의 Dynamic SQL이 만들어 지는 과정과 그 실행과정에서 발생하는 SQL Injection 문제를 집어 보았다. 그렇다면, 요즘 많은 개발자들이 사용하고 있는 Entity Framework (혹은 LINQ To SQL)과 같은 프레임워크를 사용했을 때, SQL Injection 문제는 없는 것일까?

예를 들어, 간단한 예로 다음과 같이 Entity Framework을 쿼리하는 코드를 살펴보자. 아래 FindBook() 메서드는 문자열 id를 받아들이고, 이를 title_id 컬럼에서 찾아 매칭되는 레코드를 리턴한다. 입력 id가 BU111와 같이 정상적인 값인 경우 해당 레코드를 찾을 수 있다.
하지만 만약 입력 id에 SQL Injection을 시도하는 문자열 예를 들어 ' DELETE FROM Users --을 입력하면 어떻게 될까?

private void FindBook(string id) //ex) id="BU1111"
{            
    pubsEntities pubs = new pubsEntities();
    var book = pubs.titles.SingleOrDefault(p => p.title_id == id);
    Debug.WriteLine(book.title1);
}

Entity Framework (혹은 Linq to SQL)은 SingleOrDefault()과 같은 LINQ 메서드 쿼리를 내부적으로 SQL문을 변경하게 되는데, DB 서버(SQL server로 가정하자)로 어떤 쿼리가 보내지는지 보기 위해 SQL Profiler를 사용할 수 있다. 위의 메서드 호출시 아래와 같은 RPC call 즉 Stored Procedure 호출이 서버로 보내진다.

Profiler에서의 EF SQL문

위의 SQL문을 살펴보면, C#의 p.title_id == id 조건문이 sp_executesql의 Parameterized Query로 자동으로 변경되고 있음을 알 수 있다. 즉, C# 파라미터 id의 값이 sp_executesql의 세번째 argument로 그대로 전달되어 Dynamic SQL 해킹을 방지하고 있다.
(참조: 위의 SQL 쿼리를 자세히 보면, C# 코드에서는 Single을 사용했으나 실제로는 SELECT TOP(2) 와 같이 2개의 ROW를 SELECT하고 있음을 알 수 있다. 이는 Entity Framework에서 최대 2개를 SELECT 해야만, 선별된 ROW가 한 개인지 아니면 복수 개인지를 알 수 있기 때문이다.)

또 다른 하나의 예를 들어 보자. 입력으로 문자열을 받아들이고, 이 문자열이 들어가는 책 제목을 모두 찾고 싶다고 하자. 이런 경우, SQL에서는 LIKE 문을 사용한다. C# LINQ에서는 Contains() 혹은 StartsWith(), EndsWith()를 사용하여 특정문자열이 부분집합으로 들어가는지(Contains), 특정문자열로 시작하는지(StartsWith), 특정 문자열로 끝나는지(EndsWith)를 지정하게 된다.

private void FindTitle(string szTitle) //szTitle="Cooking"
{
    pubsEntities pubs = new pubsEntities();
    // Contains : %Cooking%
    var v1 = pubs.titles.Where(p => p.title1.Contains(szTitle));
    Debug.WriteLine(v1.Count());

    // EndsWith : %Cooking
    var v2 = pubs.titles.Where(p => p.title1.EndsWith(szTitle));
    Debug.WriteLine(v2.Count());

    // StartsWith : Cooking%
    var v3 = pubs.titles.Where(p => p.title1.StartsWith(szTitle));
    Debug.WriteLine(v3.Count());
}

그리고, 이 LINQ 쿼리는 다시 SQL Injection으로부터 안전한 SQL 쿼리로 다시 변경되어 실행되게 된다. 위의 첫번째 Contains()문은 다음과 같은 SQL 쿼리를 만들어 낸다. 아래 결과에 보이듯이, SQL의 LIKE문 다음에 @p__linq__0 라는 파라미터를 지정하고, sp_executesql의 세번째 파라미터에 @p__linq__0='%Cooking% 을 쓰고 있다.

exec sp_executesql N'SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
	COUNT(1) AS [A1]
	FROM [dbo].[titles] AS [Extent1]
	WHERE [Extent1].[title] LIKE @p__linq__0 ESCAPE ''~''
)  AS [GroupBy1]',N'@p__linq__0 varchar(8000)',@p__linq__0='%Cooking%'

이제까지 예에서 보이듯이, Entity Framework이나 Linq To SQL에서 LINQ를 사용할 경우 파라미터들은 자동으로 Parameterized Query로 변경되기 때문에 SQL Injection의 위험이 없다고 할 수 있다. 그러면, Entity Framework이나 Linq To SQL은 항상 SQL Injection이 없다고 보아도 되는가?

물론 그렇지는 않다. Entity Framework이나 Linq To SQL에서도 직접 SQL문을 보낼 수 있는 기능들이 있다. 예를 들어, 아래와 같이 SQL문을 조합하여 Entity Framework의 ExecuteStoreQuery() 메서드를 사용하여 직접 SQL문을 보낸다면, SQL Injection이 물론 가능하다. (참고로 아래 코드는 수정하면 Parameterized Query로 변경되어 보내질 수 있다). 

private void FindAuthors(string name)
{
    string sql = "SELECT * FROM authors WHERE au_fname LIKE '%" + name + "%'";
    pubsEntities pubs = new pubsEntities();
    var results = pubs.ExecuteStoreQuery(sql, null);
    Debug.WriteLine(results.Count());
}

위와 비슷하게 Entity Framework의 Code First 방식을 사용한다면, SqlQuery(), SqlCommand() 등의 메서드를 사용하여 Raw SQL 쿼리를 보내게 되는데, 이때도 Dynamic SQL문을 작성한다면 Injection의 위험이 있다.

따라서 내용을 요약하자면, Entity Framework은 LINQ를 사용할 경우 SQL Injection에 안전하지만, 직접 SQL 문을 만들어서 보내는 경우에는 ADO.NET이나 SP 쿼리에서 처럼 똑같이 Dynamic SQL 쿼리에 주의를 기울여야 한다.

SQL Injection 관련 아티클

해커들이 바라는 프로그래머의 실수 - 해커들이 어떻게 SQL Injection 을 사용해서 해킹을 하는지
SQL Injection 두번째 이야기 - LIKE문에서의 SQL Injection
SQL Injection 세번째 이야기 - Stored Procedure에서의 SQL Injection



본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.