한때 함께 일했던 조운영씨가 가끔 Microsoft SQL Server 2000에 관해 물어보신다. SQL Injection와 관련된 쿼리를 다루시는 것 같다. 오늘은 시스템 저장프로시저 sp_MSdropretry에 대해 알아봤다. 서비스팩 3 이전의 sp_MSdropretry의 코드는 다음과 같다.
CREATE PROCEDURE sp_MSdropretry ( @tname sysname , @pname sysname) as declare @retcode int /* ** To public */ exec ('drop table ' + @tname) if @@ERROR <> 0 return(1) exec ('drop procedure ' + @pname) if @@ERROR <> 0 return(1) return (0) GO
눈치 빠른 사람은 벌써 뭐가 문제인지 알았을 것이다. 다음과 같은 쿼리를 실행시키면, 첫번재 매개변수 뒤의 select * from master.dbo.sysxlogins
가 실행되어 버린다.
exec sp_MSdropretry 'mytable select * from master.dbo.sysxlogins' , 'mytable'
서비스팩 3 이후에는 이같은 문제를 해결하기 위해 sp_MSdropretry의 구현이 다음과 같이 바뀌었다.
CREATE PROCEDURE sp_MSdropretry ( @tname sysname , @pname sysname) as declare @retcode int declare @quotedtname sysname declare @quotedpname sysname /* ** To public */ if not exists (select * from dbo.sysmergepublications where 1 = {fn ISPALUSER(pubid)}) begin RAISERROR (14126, 11, -1) return (1) end select @quotedtname = quotename(@tname) select @quotedpname = quotename(@pname) exec ('drop table ' + @quotedtname) if @@ERROR <> 0 return(1) exec ('drop procedure ' + @quotedpname) if @@ERROR <> 0 return(1) return (0)
첫번째 if 문은 어떤 의미인지 정확하게 모르겠다. ISPALUSER은 sp_MSdropretry와 마찬가지로 문서화되어 있지 않은데다가, 복제와 관련해서는 경험이 부족하다.
내가 중요하게 보는 부분은 quotename
함수이다. 종전에는 첫번째 매개변수 값이 'mytable select * from master.dbo.sysxlogins'
였을 때, 실행되는 쿼리는 drop table mytable select * from master.dbo.sysxlogins
였다. quotename
함수가 추가됨으로써 이제는 drop table [mytable select * from master.dbo.sysxlogins]
가 실행된다. 당연히 문법 에러가 발생하고 쿼리는 실행되지 않는다.
여기서 소개한 것은 마이크로소프트에서 기본적으로 제공하는 시스템 저장프로시저의 취약점이었다. 하지만 많은 개발자들이 잘 모르고 이런 식의 동적 쿼리를 많이 작성한다. 나 역시 마찬가지고, 조금 더 세심하게 작업해야겠다.