'injection'에 해당되는 글 3건

  1. 2008.11.15 DLL Injection
  2. 2008.06.30 Uncommon SQL Injection
  3. 2008.06.29 SQL Injection
Programming/Win32 API2008. 11. 15. 19:20

DLL Injection Howto

 

 

 

 Table Of Contents

 

 

1.  개요

2.  Method1: Using Registry

3.  Method1: Using Windows Hook Function

4.  Method2: VirtualAllocEx & CreateRemoteThread

5.  Method3: CreateRemoteThread & WriteProcessMemory

 

 

  

1. 개요

 

- DLL Injection 이란?

 

‘DLL Injection’ 은 이미 실행되어 있는 프로세스로 DLL Code 를 삽입하는 기법들을 말한다. 이 방법은 말 그대로 특정 프로세스를 실행하기에 앞서, 필요한 코드를 프로세스를 실행하는 지점에서 가로챈 뒤 자신의 코드를 먼저 주입(inject, 실행) 하고 제어권을 다시 돌려주는 방법을 뜻하며, 이러한 방법은 ‘Code Injection’ 이라 불리우기도 한다. 이미 실행되어 있는 Process – 현재 Runtime 에서 동작되고 있는 Process - 를 변경하여 사용하기도 하며, 특정 API 를 실행하고자 할 때 Code 를 먼저 Process 상에 Injection 하여 사용하기도 한다. ‘DLL Injection’ 을 통해 구현할 수 있는 기능들은 다음과 같다. 특정 함수를 실행하기에 앞서 필요한 작업을 하는데 사용하거나, 기존 함수들의 동작을 Monitoring 하고자 할 때 사용하며, 또는 기존의 기능을 확장하고자 할 때도 사용할 수 있을 것이다.

 

이 문서에서는 ‘DLL Injection’ 을 구현하고자 할 때 사용하는 여러 가지 방법들을 소개할 것이다.

 

- DLL Injection 을 위해 필요한 기반 지식

 

 WIN32 API 에 대한 기본 지식, DLL Process, Thread 의 동작원리를 알고 있으면 DLL Injection 을 하는데 큰 어려움이 없다. 이 외에도 Windows 에서 Process 를 실행 할 때 DLL 을 호출하는 원리를 알면 된다.


  

일반적으로 DLL 은 한 프로세스 프로그램 - 이 시작될 때 프로그램의 address 영역으로

Mapping 이 이루어 진다. Mapping 이 이루어 질 경우, DLL explicit 하게 호출이 되는지, implicit 하게 호출이 되는지 여부와 상관없이 entry point 함수인 DllMain 을 호출하며 cleanup, initialize 동작을 수행할 것이다. DLL 자체는 DLL 을 호출한 (프로세스의) 쓰레드 address 공간에 자신의 주소를 가지게 될 것이며, 이로 인해 DLL 코드가 프로세스 상의 내부 영역 프로세스가 사용하는 영역 에서 Dispatch 를 수행한다든지, API 를 가로채는 등의 기능을 할 수 있게 되는 것이다.

 

사용자 삽입 이미지


2.
Method1: Using Registry

- Registry: AppInit_DLLs (http://support.microsoft.com/kb/197571)

: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

 

Registry 를 이용하여 모든 프로세스에서 DLL 을 로드할 수 있다. 위에 언급한 레지스트리 경로에 있는 AppInit_DLLs subkey 에 삽입하고자 하는 DLL 의 경로와 이름을 지정해 놓으면, Windows 에서 USER32.DLL 을 사용하는 모든 프로세스는 이 DLL 을 자동으로 LoadLibrary(…)를 이용하여 실행한다.

 

실제로 이 방법을 사용할 경우는 없을 것이다. 효율적이지 못한 것 – 모든 프로세스가DLL 을 로드하게 되는 부하가 생길 것이며, DLL 코드 내부에서 특정 상태에 따른 처리를 해 주어야 하는 문제가 생기는 것 - 이 첫째 이유라면 그럴 것이고, DLL 코드 내부에서 사용할 수 있는 함수도 KERNEL32.DLL 에서 export 되는 함수에 국한될 것이기 때문이다.

 

만약 테스트를 해 보고자 한다면 Registry 에 값을 쓰고 시스템을 리부팅을 꼭 해야 적용이 될 것이다.

 

 

3. Method2: Using Windows Hook Function

- SetWindowHookEx, UnhookWindowsHookEx

 

HHOOK SetWindowsHookEx(

  int idHook,        // type of hook to install

  HOOKPROC lpfn,     // address of hook procedure

  HINSTANCE hMod,    // handle to application instance

  DWORD dwThreadId   // identity of thread to install hook for

);

 

BOOL UnhookWindowsHookEx(
  HHOOK hhk   // handle to hook procedure to remove
);

 

사용자 삽입 이미지
 

 

 Windows 에서 Hook 함수를 사용하여 DLL Inject 하게 되면, 내부적으로는 Hook Procedure 만이 아니라, Hook Procedure 가 들어 있는 DLL 코드 전체가 프로그램의 코드 영역에 mapping 되기 때문에, DLL 코드가 실행되는 영역이 결국 DLL 을 호출한 프로그램의 내부 영역이 된다. 내부 메시지를 hook 을 걸거나, window procedure hook 을 걸어 필요한 작업을 진행하면 될 것이고 SetWindowLong(…) 을 이용하여 관련 함수를 Dispatch 하는 방법을 사용하면 될 것이다.

 

Windows Hook 을 사용하고자 할 때 코드 작성 방식은 다음과 같다.

 

- A) DLL Shared 영역 지정

: Global 변수들을 Shared 로 지정하여 DLL 을 사용하는 모든 프로그램에 대해 DLL 이 로드되는 시간 동안 DLL 간의 공유 가능한 영역을 지정한다.

 

#pragma data_seg(".hkshared")

HHOOK   gHook   = NULL;        // global hook

HWND    gHwnd   = NULL;        // for subclassing (dll injection window)

UINT    gMsg    = 0;           // hook msg

#pragma data_seg()

#pragma comment(linker, "/SECTION:.hkshared,RWS") // for linker option

 

- B) DLL Code 선언부

: Hook 을 설치, 제거하는 함수와 실제 hook 으로 데이터를 얻었을 때 이를 처리하는 함수를 구현한다.

// install Hook Function

HOOKDLL_API int fnInjectDll(HWND hWnd);

// remove Hook Function

HOOKDLL_API int fnRemoveDll();

 

// Hook Procedure

HOOKDLL_API LRESULT CALLBACK HookProc (

int nCode,

     WPARAM wParam,

     LPARAM lParam

);

 

- C) DLL Main Code

: Dll implicit, explicit 와 상관 없이 항상 호출되는 start code 이다. DLL_PROCESS_ATTACH 일 경우에 Hook procedure 에서 사용할 Message 를 등록하는 코드를 작성한다.

 

BOOL APIENTRY DllMain( HANDLE hModule,

                       DWORD  ul_reason_for_call,

                       LPVOID lpReserved

                                       )

{

        if( ul_reason_for_call == DLL_PROCESS_ATTACH )

        {

               hDll = (HINSTANCE) hModule;   // dll module

               ::DisableThreadLibraryCalls( hDll );

 

               if(gMsg == NULL)

                       gMsg = ::RegisterWindowMessage( "WM_HOOKDLLMSG" );                 

    }

       

    return TRUE;

}

 

- C) Hook Install Code

 

HOOKDLL_API int InjectDll (HWND hWnd)

{

     gHwnd = hWnd;  // get window to hook

     gHook = SetWindowsHookEx (

                  WH_KEYBOARD,   // hook (message) type; others->WH_MOUSE, WH_CBT…

                  HookProc,      // hook procedure

                  hInstance,     // dll instance handle

                  GetWindowThreadProcessId (hWnd, NULL)

);

    

}

 

- D) Hook Uninstall Code

 

HOOKDLL_API void RemoveHook (HWND hWnd)

{

    

     UnHookWindowsHookEx(gHook);

    

}

 

- E) Hook Procedure Code

: Hook Procedure 로서, 기본 Procedure Message 를 처리하기 전에 먼저 호출되는 부분이다. 이곳에서 Hook 메시지를 찾아내어 기존 호출 방법을 변경한다.

 

#define pCW ((CWPSTRUCT*)lParam)

HOOKDLL_API LRESULT CALLBACK HookProc (

int nCode,

     WPARAM wParam,

     LPARAM lParam

)

{

If ((pCW->message == WM_HOOKEX) && pCW->lParam) 

        {

              

               // change dll owner (process)’s procedure

               OldProc = (WNDPROC) SetWindowLong(

gHwnd, GWL_WNDPROC, (LONG)NewProc

);

              

        }

return (CallNextHookEx(gHook,nCode,wParam,lParam));

}

 

 

 

 

- F) New Procedure Code

: 기존 Procedure 를 실행하기에 앞서 실행되는 부분이다. 이 부분은 DLL Injection 되는 영역 상에서 동작한다.  

 

LRESULT CALLBACK NewProc(

HWND hwnd,     

UINT uMsg,

WPARAM wParam, 

LPARAM lParam  

)

{

       

        Switch (uMsg)

{

Case WM_LBUTTONDOWN:

        MessageBox(NULL, “[Injected Code] Left Button”, “MSG”, MB_OK”);

        break;

        }

       

        // call original window procedure

        return CallWindowProc(OldProc,hwnd,uMsg,wParam,lParam);

}

 

 

4. Method3: VirtualAllocEx & CreateRemoteThread

- VirtualAllocEx, CreateRemoteThread

 

 

LPVOID VirtualAllocEx(

  HANDLE hProcess,  // process within which to allocate memory

  LPVOID lpAddress, // desired starting address of allocation

  DWORD dwSize,      // size, in bytes, of region to allocate

  DWORD flAllocationType, // type of allocation

  DWORD flProtect   // type of access protection

);

 

 

HANDLE CreateRemoteThread(

  HANDLE hProcess,        // handle to process to create thread in

  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes

  DWORD dwStackSize,      // initial thread stack size, in bytes

  LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function

  LPVOID lpParameter,     // argument for new thread

  DWORD dwCreationFlags,  // creation flags

  LPDWORD lpThreadId      // pointer to returned thread identifier

);

 

 

이미 존재하는 프로세스 상에 thread 를 외부에서 생성하여, 이 쓰레드가 DLL 코드를 실행하도록 동작하는 방법이다. 앞서 Method1 에서 사용한 DLL 코드를 새로 생성한 thread 에서 load 하기 위해 LoadLibrary 를 실행한 뒤 필요한 작업을 수행하면 된다.

 

 

사용자 삽입 이미지



- A) Open Process (in App)

: OpenProcess 함수를 사용하여 기존 프로세스를 open 한다.(정확한 의미로는 기존 Process handle 을 가져온다)

 

int WINAPI WinMain(

HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow

)

{

       

        // get remote process’s id

GetWindowThreadProcessId (hWnd, &dwProcessId);

if (GetCurrentProcessId() == dwProcessId)

               return;

        // open remote process

        HANDLE hProcess = OpenProcess(         PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|

PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwProcessId

);            

// inject dll

// reject dll

}

 

- B) Dll Injection Code (in App)

: remote thread 를 생성하고 LoadLibrary 를 호출한 뒤, thread 에서 dll 코드가 종료될 때까지 기다린다. Dll 코드는 remote thread, 즉 외부 Process 영역에서 동작하며, 필요한 작업을 한 뒤 return 되는데, return 되고 난 뒤에는 만들어 놓은 thread 를 종료하면 된다.

 

int InjectDll( HANDLE hProcess )

{

       

        // 1: get kernel address, this will be used to launch LoadLibrary

HMODULE hKernel32 = GetModuleHandle("Kernel32");

       

        // 2: get full path of dll

        Char szDllPath[MAX_PATH] = {255, 0};

        if(!GetModuleFileName(hInst, szDllPath, MAX_PATH))

               return 0;

        strcpy(strstr(szDllPath,".EXE"), ".dll");

       

        // 3: allocate dll length in the remote thread

        pLibRemote = VirtualAlloc(hProcess,

NULL, sizeof(szDllPath),

MEM_COMMIT, PAGE_READWRITE

);

        // 4: write dll code to the remote thread

        WriteProcessMemory(hProcess, pLibRemote, (void*)szDllPath,

               sizeof(szDllPath), NULL

               );

 

        // 5: call DLL code in the remote thread (LoadLibrary)

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, “LoadLibraryA”)

        , pLibRemote, 0, NULL

);

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// 6: get loaded module

GetExitCodeThread(hThread, &hLibModule);

 

// Close handle, and free dll

CloseHandle(hThread);

VirtualFreeEx(hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE);

       

}

 

- C) Dll Rejection Code (in App)

: remote thread 를 생성하고 FreeLibrary 를 실행하여 DLL unload 한다.

 

void RejectDll (HANDLE hProcess)

{

       

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, “FreeLibrary”)

        , pLibRemote, 0, NULL

);

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// Close handle, and free dll

CloseHandle(hThread);

}

 

 

 

 

 

5. Method4: CreateRemoteThread & WriteProcessMemory

- CreateRemoteThread, WriteProcessMemory

 

HANDLE CreateRemoteThread(

  HANDLE hProcess,        // handle to process to create thread in

  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes

  DWORD dwStackSize,      // initial thread stack size, in bytes

  LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function

  LPVOID lpParameter,     // argument for new thread

  DWORD dwCreationFlags,  // creation flags

  LPDWORD lpThreadId      // pointer to returned thread identifier

);

 

BOOL WriteProcessMemory(

  HANDLE hProcess,             // handle to process whose memory is written to

  LPVOID lpBaseAddress,        // address to start writing to

  LPVOID lpBuffer,             // pointer to buffer to write data to

  DWORD nSize,                 // number of bytes to write

  LPDWORD lpNumberOfBytesWritten   // actual number of bytes written

);

 

사용자 삽입 이미지



프로세스의 address 영역에 thread 를 외부에서 생성하여, 이 쓰레드가 DLL 코드를 실행하도록 동작하는 방법이다. 이 방법은 Method3 에서 사용한 방법과 비슷한 구조를 가지지만, 앞서의 경우와는 달리 DLL 을 외부 프로세스가 Load 하도록 동작하는 것이 아니라, DLL 에서 사용한 코드 자체를 외부 프로세스의 address 영역에다 Write 하고 이를 호출하여 코드를 실행하는 방법이다.

 

 - A) WinMain

: OpenProcess 함수를 사용하여 기존 프로세스를 open 한다.(정확한 의미로는 기존 Process handle 을 가져온다)

int WINAPI WinMain(

HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow

)

{

       

        // get remote process’s id

GetWindowThreadProcessId (hWnd, &dwProcessId);

 

// inject dll

bRet = InjectCode(hWnd);

MessageBox(NULL, “Code Injected. Press OK to reject!”,

                “Injection Sample”, MB_OK

               );

// reject dll

bRet = RejectCode(hWnd);

}

 

 

- B) INJDATA

: 실제 Injection 을 할 때 사용할 구조체이다. 이 구조체를 remot 프로세스의 address 영역에 write 하여 사용할 것이고, 여기에 저장된 내용은 Dispatch , 사용할 프로시저나 함수의 포인터, 내부 변수등을 저장한다.

 

 

typedef struct __INJDATA {

        // pointer to the function used within injected code

        SETWINDOWLONG  fnSetWindowLong;

        CALLWINDOWPROC fnCallWindowProc;     

        //

        HWND hwnd;

        WNDPROC fnNewProc;

        WNDPROC fnOldProc;    

} INJDATA, *PINJDATA;

 

- C) Remote Code (Inject, Reject)

: Remote thread 영역에서 사용할 코드

DWORD WINAPI InjectFunc (INJDATA *pData)

{

        // change old procedure to the new one

        pData->fnOldProc = (WNDPROC) pData->fnSetWindowLong(

               pData->hwnd, GWL_WNDPROC, (long)pData->fnNewProc

               );     

        return (pData->fnOldProc != NULL);

}

 

DWORD WINAPI EjectFunc (INJDATA *pData)

{

        // restore to the old procedure

        return (pData->fnSetWindowLong(pData->hwnd, GWL_WNDPROC,

               (long)pData->fnOldProc) != NULL);

}

 

- D) InjectCode

: 실제 Injection 에 사용될 코드

 

int InjectCode(HWND hWnd)

{

       

        // 1: get user module address, this will be used with dispatch function

HMODULE hKernel32 = GetModuleHandle("user32");

       

        // 2: open remote process

        hProcess = OpenProcess(               PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|

PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwProcessId

                );

        // 3: allocate space for remote procedure(function) and data

        pDataRemote = (BYTE*) VirtualAllocEx(hProcess, 0, size,

               MEM_COMMIT, PAGE_EXECUTE_READWRITE

               );

        pNewProcRemote = pDataRemote + sizeof(INJDATA);

       

        // 4: write pNewProcRemote to the process memory (address space)

        WriteProcessMemory(hProcess, pNewProcRemote,

               &NewProc, cbNewProc, &dwNumBytesXferred

               );     

 

        // 5: initialize INJDATA

        injData.fnSetWindowLong = (SETWINDOWLONG)

               GetProcAddress(hUser32, “SetWindowLongA”);

        injData.fnCallWndProc   =  (CALLWNDPROC)

               GetProcAddress(hUser32, “CallWndProcA”);

        injData.fnNewProc = (WNDPROC) (pNewProcRemote);

 

        // 6: write injData to the process memory           

        WriteProcessMemory(hProcess, pDataRemote,

               &DataLocal, sizeof(INJDATA), &dwNumBytesXferred

               );

       

        // 7: allocate space for remote function (InjectFunc)

        pCodeRemote = (PDWORD) VirtualAllocEx( hProcess, 0, cbInjectFunc,

               MEM_COMMIT, PAGE_EXECUTE_READWRITE
               );

       

        // 8: write function to the remote thread   

        WriteProcessMemory( hProcess, pCodeRemote,

               &InjectFunc, cbInjectFunc, &dwNumBytesXferred

               );

       

        //  9: call function in the remote thread (pCodeRemote)

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE) pCodeRemote, pDataRemote,

                0 , &dwThreadId);

 

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// 10: get loaded module

GetExitCodeThread(hThread, &hLibModule);

 

// Close handle, and free dll

CloseHandle(hThread);

VirtualFreeEx(hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE);

       

}

 

- D) RejectCode

: 실제 Injection 에 사용될 코드

 

int RejectCode(HWND hWnd)

{

       

        // 1: open remote process

        hProcess = OpenProcess(               PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|

PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwProcessId

                );

       

        // 2: allocate space for remote function (RejectFunc)

        pCodeRemote = (PDWORD) VirtualAllocEx( hProcess, 0, cbRejectFunc,

               MEM_COMMIT, PAGE_EXECUTE_READWRITE
               );

       

        // 3: write function to the remote thread   

        WriteProcessMemory( hProcess, pCodeRemote,

               &RejectFunc, cbRejectFunc, &dwNumBytesXferred

               );

       

        //  4: call function in the remote thread (pCodeRemote)

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE) pCodeRemote, pDataRemote,

                0 , &dwThreadId);

 

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// 5: get loaded module

GetExitCodeThread(hThread, &hLibModule);

 

// Close handle, and free dll

CloseHandle(hThread);

VirtualFreeEx(hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE);

       

}

Posted by skensita

댓글을 달아 주세요

Hacking & Security/Web2008. 6. 30. 08:06


Milw0rm 문서 중 하나입니다.

SQL 인젝션의 기초에 대해 설명하고 있습니다.

내용도 짧고 간단해서 읽기에 좋습니다.

많은 도움 되시길 바랍니다.

Posted by skensita

댓글을 달아 주세요

Hacking & Security/Web2008. 6. 29. 03:45

Web Hacking 1탄 SQL Injection
Web Hacking 2탄 파일조작
Web Hacking 3탄 구멍난 자바스크립트

1. 시작하기
가끔 뉴스나 방송에서 xx 사이트 해킹 당했다 라고들 많이 들어보았을 겁니다
이런 뉴스를 접할때 대체 어떻게 해서 해킹이 당하는걸까? 라고들 많이 생각해 보았을 겁니다
어떻게 해킹이 일어나는지 알아야 방어도 가능하기 때문에 이번 강좌는 웹해킹에 대해 알아볼 것입니다

첫번재 시간으로 SQL Injection을 배울 것이며 그다음 두번째에는 파일조작으로 인한 해킹을,
그리고 마지막 시간에는 자바스크립트 조작에 대해 알아보겠습니다.

사용자 삽입 이미지

도표에서도 알수 있듯이 SQL Injection으로 인한 해킹이 가장 간단하고 쉽기 때문에
이를 가장먼저 알아보겠습니다

SQL Injection 이란 서버나 OS의 구멍을 이용한 해킹방법이 아닌 웹 어플리케이션 자체의 버그를
이용한 새로운 형태의 웹해킹 방법입니다
 
정의를 하자면

SQL Injection은 웹페이지를 통해 입력된 파라미터값을 이용하여 쿼리를 재구성하는 방법이다


라고 할수 있습니다

즉 많은 웹페이지들은 사용자나 프로그램이 생성한 파라미터값을 이용해 쿼리를 만들고 실행하는데,
이때 정상적인 값이 아닌 파라미터값이 입력될 때 비정상적인 쿼리가 실행되며, 따라서
원하지 않는 결과가 나올수 있다는 것입니다
이해를 위해 바로 코드로 들어가 봅시다
아래 코드는 사용자가 입력한 로그인 아이디와 비밀번호로 로그인을 시도하는 로직입니다

// 입력받은 사용자 아이디와 비밀번호
String param1 = request.getParameter("user_id");
String param2 = request.getParameter("user_pw");

rs = stmt.executeQuery("SELECT count(*) FROM user_t WHERE userid = '"+param1+"' AND userpw = '"+param2+"'");
rs.next();
if (rs.getInt(1) == 1) {
   // 로그인 성공로직
} else {
   // 로그인 실패로직
}


무엇이 잘못되었을 까요?
코드상으로 보면 실행하는데 전혀 이상이 없는 코드이지만 쫌 아는사람들에게는 비밀번호 없이
아이디만 안다면 로그인을 성공할 수 있는 로직입니다

사용자 삽입 이미지
 
아아디가 goodbug 이고 비밀번호가 1111 인 계정이 있다고 합시다 ^^;
입력란에 googbug / 1111 을 입력하니 정상적으로 로그인이 true가 되었습니다
하지만 만약 다음과 같이 사용자가 입력한다면 어떻게 될까요?

사용자 삽입 이미지

로그인이 됩니다!
비밀번호에 상관없이 로그인이 되는군요!!
하다 더 보도록 하지요

사용자 삽입 이미지

위의 소스는 MySQL을 사용하고 있습니다
MySQL의 라인 주석처리는 # 입니다 아시죠?

즉 뒷부분 password를 체크하는 로직은 주석처리되어 버린겁니다 뜨아~

같은 의미이지만 아래처럼 여러가지 섞어서도 가능합니다.

사용자 삽입 이미지

이게 만약 관리자 아이디였다면 문제는 더 심각해 집니다

이처럼 사용자의 고의로 인한 SQL을 조작하여 웹 어플리케이션 자체를 공격하는것이 SQL Injection 입니다

그렇다면 위의 소스를 어떻게 변경하는게 좋을까요?

// 입력받은 사용자 아이디와 비밀번호
String param1 = request.getParameter("user_id");
String param2 = request.getParameter("user_pw");

//validate 라는 함수는 아이디와 비밀번호 생성시 생성 로직에 맞게 되었는지 체크하는 함수
//만약 특수문자나 아이디 생성 규칙에 맞지 않는 값이면 exception을 throw 한다
validateID(param1); 
validatePW(param2);

pstmt = conn.prepareStatement("SELECT userid, userpw FROM user_t WHERE userid = ?");
pstmt.setString(1, param1);
rs = pstmt.executeQuery();
if (rs.next()) {
    // 문자를 직접 비교한다 (대소문자 구분)
    if (rs.getString(1).equals(param1) && rs.getString(2).equals(param2)) {
         // 로그인 성공로직
    } else {
        // 로그인 비밀번호 혹은 아이디 오류 로직
    }
} else {
    //로그인 부재 아이디 오류 로직
}

위처럼 하면 어느정도 되겠네요 ^^


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

코드로 바로 봅시다
아래 코드는 게시물번호를 파라미터로 받아 해당 게시물이 존재하면 글번호와 글제목, 글내용을 조회하는 코드입니다

String param1 = request.getParameter("boardno");

rs = stmt.executeQuery("SELECT boardno, boardtitle, boardcontent FROM board_t WHERE boardno = '"+param1+"'");
if (rs.next()) {
    out.println(rs.getString(1)+"<br>");
    out.println(rs.getString(2)+"<br>");
    out.println(rs.getString(3)+"<br>");
}

무엇이 잘못되었을까요?

사용자 삽입 이미지
글번호가 1134896409234 인 게시물은 다음과 같이 URL이 요청되어 조회가 될겁니다
요청 URL

/read.jsp?bno=1134896409234


하지만 위와 같이 추가적으로 UNION SQL Injection이 들어갈 수 있습니다

/read.jsp?bno=1134896409234' UNION SELECT '1', userid, userpw FROM user_t WHERE userid = 'goodbug'  ORDER BY boardno ASC #


그러면 goodbug라는 아이디의 비밀번호가 그만 조회되어 버립니다!!
그럼 user_t 라는 테이블과 ,userid, userpw라는 컬럼명들은 어떻게 알수 있을까요?
오라클 이라면 다음과 같이 알아낼 수 있습니다

/read.jsp?bno=1134896409234' UNION SELECT '1', tname, '' FROM user_tables WHERE like '%user%' ORDER BY boardno ASC --


테이블 명을 알아냈다면 이제 컬럼명을 알아봐야겠죠
오라클이라면 user_tab_columns view를 통해 알아볼 수 있습니다
SELECT * FROM user_tab_columns WHERE table_name = 'user_t'

물론 한번에 알아낼수 없으며 많은 시행착오를 겪어야 하는것은 필수입니다
그래서 제로보드나 Unicorn 같은 공개 게시판인 경우는 타겟이 되기 쉽상입니다

MySQl이나 Oracle JDBC에서는 다행히도 ; 문자를 이상 캐릭터로 보고 에러를 반환합니다
하지만 그렇지 않은 JDBC가 있따면 큰일입니다 아래와 같은 코드가 가능하기 때문이지요

/read.asp?bno=1134896409234';DELETE FROM user_t

/read.asp?bno=1134896409234';UPDATE user_t SET userpw = '1111'


PHP나 ASP인 경우에는 가능한 쿼리 입니다
이제 대강 SQL Injection에 대해 감이 잡히시나요?


4. 에러 메세지를 통한 정보수집

admin_login 이라는 관리자 테이블과 login_name이라는 컬럼을 알아 냈다고 한다면..
M$SQL인 경우를 예를 들겠습니다

/read.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 'goodbug' to a column of data type int.
/read.asp, line 5


라는 메세지가 나옵니다
여기서 무순 정보를 알수 있을까요? 바로 goodbug 라는 관리자 아이디가 있다는 것을 알았습니다
그럼 여참에 비밀번호까지 알아봅시다

/read.asp?id=10 UNION SELECT TOP 1 password FROM admin_login where login_name='goodbug'--


Output:

Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value '1111' to a column of data type int.
/read.asp, line 5


즉 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이 어쩔수 없다면 파라미터 검사를 충분히 해야한다
많은분들이 아시는 내용이지만 처음보시는분에겐 많은 도움이 되실겁니다 ^^
Posted by skensita

댓글을 달아 주세요