Jump to content

Recommended Posts

Исправление SQL-инъекции в AccountServer

 

1.png

 

MAC-адрес — уникальный идентификатор, присваиваемый каждой единице активного оборудования или некоторым их интерфейсам в компьютерных сетях Ethernet.

Википедия - Свободная энциклопедия

 

Привет! :smile:

 

В данной статье я хочу поговорить об уязвимости в AccountServer.exe, которая позволяет злоумышленникам проводить внедрение вредоносного SQL-кода и редактировать базу данных AccountServer по их усмотрению. Таким образом, злоумышленник может, например, заблокировать все аккаунты на сервере, из-за чего игроки не смогут попасть в игровой мир, либо вообще удалить все учетные записи пользователей.

 

 

1. Суть и причины уязвимости

 

После того, как игрок успешно вошел в свою учетную запись используя логин и пароль, AccountServer.exe выполняет SQL-запрос к одноименной базе данных AccountServer, чтобы обновить IP- и MAC-адреса клиента, время входа пользователя, название экземпляра GroupServer, от которого был получен пакет аутентификации, и установить флаг по которому можно определить авторизован ли пользователь в данный момент времени или нет:

UPDATE account_login SET login_status = %d, login_group = '%s', enable_login_time = getdate(), 
		last_login_time = getdate(), last_login_mac = '%s', last_login_ip = '%s' WHERE id = %d

 

Уязвимость заключена в параметре last_login_mac, которому с помощью строкового маркера %s может присваивается любая текстовая строка:

last_login_mac = '%s'

 

В базе данных AccountServer в таблице аккаунтов account_login хранятся MAC-адреса всех клиентов. Поскольку через сеть Интернет нельзя узнать MAC-адрес сетевого адаптера пользователя, в отличие от IP-адреса, то, по идее разработчиков, игровой клиент, запущенный на пользовательском компьютере, должен получить MAC-адрес сетевого интерфейса и записать его в пакет аутентификации (ID: 431), после чего отравить MAC вместе с пакетом аутентификации на сервер. Далее адрес передается в AccountServer.exe и сохраняется в базе данных. Проблема в том, что в AccountServer.exe принятая строка с MAC-адресом никак не проверяется на соответствие формату MAC (XX-XX-XX-XX-XX-XX-XX-XX, где X символы  0 - 1 или A - F), то есть на сервер можно отправить любую строку и она будет вставлена в вышеприведенный SQL-запрос вместо маркера %s и сохранена в базе данных, что, в свою очередь, предоставляет возможность внедрения произвольного SQL-кода в запрос.

 

В качестве примера внедрим в SQL-запрос вредоносный код, который заблокирует все учетные записи пользователей. Для этого вместо MAC-адреса необходимо отправить на сервер следующую строку:

1'; update account_login set ban=1;--

 

Далее она будет подставлена в изучаемый SQL-запрос и в результате будет сформирован новый запрос:

UPDATE account_login SET login_status = %d, login_group = '%s', enable_login_time = getdate(), 
	last_login_time = getdate(), last_login_mac = '1'; update account_login set ban=1;--', last_login_ip = '%s' WHERE id = %d

 

Проанализируем что делает с базой данных SQL-запрос с внедренным вредоносным кодом:

1) Обновляет поля login_status. login_group, enable_login_time, last_login_time для ВСЕХ пользователей, что некорректно, так как эти поля должны обновляться только для текущего пользователя;

2) Присваивает значение "1" MAC-адресам ВСЕХ пользователей, что во-первых, так же некорректно в соответствии с пунктом (1), во-вторых, значение "1" не отражает какой-либо реальный MAC-адрес;

3) Присваивает значение "1" полям ban ВСЕХ пользователей, что означает блокировку всех учетных записей;

4) Поле last_login_ip не обновляется, так как часть запроса, в том числе с условием обновления данных только текущего пользователя, была закомментирована и не обрабатывается SQL Server.

 

В итоге, после исполнения такого SQL-запроса, базе данных аккаунтов будет нанесен определенный ущерб. Последствия рассмотренного примера вполне обратимы, но злоумышленнику ничего не мешает использовать во внедряемом SQL-коде оператор DELETE, который служит для удаления записей из таблиц. В таком случае Вы можете безвозвратно потерять все учетные записи пользователей.

 

 

2. Эксплойт

 

2.png

 

Для проверки наличия уязвимости в AccountServer.exe я разработал специальную программу-эксплойт, с помощью которой можно провести атаку на базу данных AccountServer. Также эксплойт понадобится для тестирования защиты, которую мы разработаем в будущем.

 

Скачать эксплойт

Исходный код эксплойта (в архиве идет проект Visual Studio 2019 Community)

 

ВНИМАНИЕ! Данная программа не предназначена для взлома игровых серверов и должна использоваться только в учебных и испытательных целях. Автор не несет никакой ответственности за совершенные Вами действия и понесенный кем-либо ущерб. Используйте на свой страх и риск!

 

Чтобы провести попытку внедрения SQL-инъекции запустите программу accountexploit.exe со следующими параметрами:

accountexploit ip:<IP-адрес сервера> port:<Порт сервера> login:<Логин> password:<Пароль> version:<Версия сервера>

 

Например:

accountexploit ip:127.0.0.1 port:1973 login:V3ct0r password:112233 version:136

 

Эксплойт запустится и, при условии корректности информации, необходимой для подключения к серверу, начнется процесс тестирования Вашего сервера на уязвимость. В результате Вы должны увидеть одно из двух сообщений.

1. SQL INJECTION SUCCESFULLY DONE! ALL ACCOUNTS ARE BANNED. YOU NEED TO TAKE CARE OF THE SECURITY OF YOUR ACCOUNTSERVER! - В Вашем AccountServer.exe обнаружена уязвимость и Вам необходимо предпринять меры для её устранения;

2. SQL INJECTION FAILED! YOUR ACCOUNTSERVER IS SECURED! - Уязвимость не обнаружена. Ваш AccountServer.exe защищен.

 

Если уязвимость была обнаружена, то нужно её незамедлительно исправить. Как это сделать будет рассмотрено далее.

 

 

3. Исправление уязвимости в AccountServer.exe

 

3.png

 

Существует несколько методов исправления уязвимости:

1) Использовать специальные программы-посредники между GateServer и GroupServer, например, Gemini.X.FailSQLGuardFilterserver, которые анализируют пакеты аутентификации на наличие SQL-инъекций;

2) Написать заплатку ("патч") и внедрить её в AccountServer.exe;

3) Разработать библиотеку динамической компоновки (.dll), исправляющую уязвимость, и подключить её к AccountServer.exe.

 

В рамках данной статьи я выберу вариант (2) с написанием и внедрением заплатки как самый оптимальный ввиду относительной простоты исправления уязвимости. Вариант (1) требует разработки и применения дополнительного серверного программного обеспечения, а вариант (3) порождает дополнительные зависимости для AccountServer.exe. По моему мнению, оба последних варианта избыточны.

 

Для заплатки потребуется разработать функцию, которая будет проверять корректность полученной от клиента строки с MAC-адресом. На вход функции будет подаваться строка, MAC-адрес для проверки соответственно, а на выходе функция будет выдавать логическое значение true в случае, если строка корректна, или false, если строка не прошла проверку и вероятно содержит вредоносный код.

 

Чтобы разработать такую функцию, необходимо определить правила валидации. Поскольку в базе данных MAC-адреса хранятся в формате XX-XX-XX-XX-XX-XX-XX-XX, где X символы  0 - 1 или A - F, то будем использовать такой же формат для проверки:

1) Длина строки должна быть ровно 23 символа;

2) Каждый третий символ должен быть дефисом (-);

3) Каждый байт адреса должен быть представлен шестнадцатеричным значением (символы 0 - 1 и A - F).

 

Теперь можно написать функцию проверки MAC-адресов на соответствие установленным правилам. Я применю язык C++:

/*
Функция для проверки MAC-адреса
1) Длина строки должна быть ровно 23 символа;
2) Каждый третий символ должен быть дефисом(-);
3) Каждый байт адреса должен быть представлен шестнадцатеричным значением(символы 0 - 1 и A - F).
*/
bool check_mac_address(const char* mac)
{
    // Длина строки MAC-адреса
    unsigned int length = 0;

    // Скопируем указатель на строку
    const char* p = mac;

    // Получим длину строки MAC-адреса
    while (*p++) 
    { 
        // Увеличиваем счетчик символов
        length++;

        // Проверим длину строки
        if (length > 23)
        {
            // Слишком длинная строка
            return false;
        }
    }
    
    // Проверим длину строки
    if (length != 23)
    {
        // Слишком короткая строка
        return false;
    }
  
    // Проверим каждый символ в строке
    for (unsigned int i = 0; i < length; i++)
    {
        // Берем текущий символ
        unsigned char c = static_cast<unsigned char>(mac[i]);

        // Каждый 3-й символ должен быть дефисом (-)
        if (((i + 1) % 3 == 0) && c != '-')
        {
            // Некорретный формат MAC!
            return false;
        }

        // Остальные символы могут принимать значения
        // 0 - 1
        // A - F
        if ( ((i + 1) % 3 != 0) && ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) == false)
        {
            // Некорретный формат MAC!
            return false;
        }
    }
    
    // Строка прошла проверку
    return true;
}

 

Скомпилируем разработанную функцию с помощью Microsoft Visual Studio 2019 Community и получим код на языке ассемблера, который можно внедрить в AccountServer.exe:

00F51000 >/$ 55             PUSH EBP
00F51001  |. 8BEC           MOV EBP,ESP
00F51003  |. 83EC 18        SUB ESP,18
00F51006  |. B8 CCCCCCCC    MOV EAX,CCCCCCCC
00F5100B  |. 8945 E8        MOV DWORD PTR SS:[EBP-18],EAX
00F5100E  |. 8945 EC        MOV DWORD PTR SS:[EBP-14],EAX
00F51011  |. 8945 F0        MOV DWORD PTR SS:[EBP-10],EAX
00F51014  |. 8945 F4        MOV DWORD PTR SS:[EBP-C],EAX
00F51017  |. 8945 F8        MOV DWORD PTR SS:[EBP-8],EAX
00F5101A  |. 8945 FC        MOV DWORD PTR SS:[EBP-4],EAX
00F5101D  |. C745 FC 000000>MOV DWORD PTR SS:[EBP-4],0
00F51024  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
00F51027  |. 8945 F8        MOV DWORD PTR SS:[EBP-8],EAX
00F5102A  |> 8B4D F8        /MOV ECX,DWORD PTR SS:[EBP-8]
00F5102D  |. 0FBE11         |MOVSX EDX,BYTE PTR DS:[ECX]
00F51030  |. 8955 EC        |MOV DWORD PTR SS:[EBP-14],EDX
00F51033  |. 8B45 F8        |MOV EAX,DWORD PTR SS:[EBP-8]
00F51036  |. 83C0 01        |ADD EAX,1
00F51039  |. 8945 F8        |MOV DWORD PTR SS:[EBP-8],EAX
00F5103C  |. 837D EC 00     |CMP DWORD PTR SS:[EBP-14],0
00F51040  |. 74 18          |JE SHORT 00F5105A
00F51042  |. 8B4D FC        |MOV ECX,DWORD PTR SS:[EBP-4]
00F51045  |. 83C1 01        |ADD ECX,1
00F51048  |. 894D FC        |MOV DWORD PTR SS:[EBP-4],ECX
00F5104B  |. 837D FC 17     |CMP DWORD PTR SS:[EBP-4],17
00F5104F  |. 76 07          |JBE SHORT 00F51058
00F51051  |. 32C0           |XOR AL,AL
00F51053  |. E9 B0000000    |JMP 00F51108
00F51058  |>^EB D0          \JMP SHORT 00F5102A
00F5105A  |> 837D FC 17     CMP DWORD PTR SS:[EBP-4],17
00F5105E  |. 74 07          JE SHORT 00F51067
00F51060  |. 32C0           XOR AL,AL
00F51062  |. E9 A1000000    JMP 00F51108
00F51067  |> C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0
00F5106E  |. EB 09          JMP SHORT 00F51079
00F51070  |> 8B55 F4        /MOV EDX,DWORD PTR SS:[EBP-C]
00F51073  |. 83C2 01        |ADD EDX,1
00F51076  |. 8955 F4        |MOV DWORD PTR SS:[EBP-C],EDX
00F51079  |> 8B45 F4         MOV EAX,DWORD PTR SS:[EBP-C]
00F5107C  |. 3B45 FC        |CMP EAX,DWORD PTR SS:[EBP-4]
00F5107F  |. 0F83 81000000  |JNB 00F51106
00F51085  |. 8B4D 08        |MOV ECX,DWORD PTR SS:[EBP+8]
00F51088  |. 034D F4        |ADD ECX,DWORD PTR SS:[EBP-C]
00F5108B  |. 8A11           |MOV DL,BYTE PTR DS:[ECX]
00F5108D  |. 8855 F3        |MOV BYTE PTR SS:[EBP-D],DL
00F51090  |. 8B45 F4        |MOV EAX,DWORD PTR SS:[EBP-C]
00F51093  |. 83C0 01        |ADD EAX,1
00F51096  |. 33D2           |XOR EDX,EDX
00F51098  |. B9 03000000    |MOV ECX,3
00F5109D  |. F7F1           |DIV ECX
00F5109F  |. 85D2           |TEST EDX,EDX
00F510A1  |. 75 0D          |JNZ SHORT 00F510B0
00F510A3  |. 0FB655 F3      |MOVZX EDX,BYTE PTR SS:[EBP-D]
00F510A7  |. 83FA 2D        |CMP EDX,2D
00F510AA  |. 74 04          |JE SHORT 00F510B0
00F510AC  |. 32C0           |XOR AL,AL
00F510AE  |. EB 58          |JMP SHORT 00F51108
00F510B0  |> 8B45 F4        |MOV EAX,DWORD PTR SS:[EBP-C]
00F510B3  |. 83C0 01        |ADD EAX,1
00F510B6  |. 33D2           |XOR EDX,EDX
00F510B8  |. B9 03000000    |MOV ECX,3
00F510BD  |. F7F1           |DIV ECX
00F510BF  |. 85D2           |TEST EDX,EDX
00F510C1  |. 74 3E          |JE SHORT 00F51101
00F510C3  |. 0FB655 F3      |MOVZX EDX,BYTE PTR SS:[EBP-D]
00F510C7  |. 83FA 30        |CMP EDX,30
00F510CA  |. 7C 09          |JL SHORT 00F510D5
00F510CC  |. 0FB645 F3      |MOVZX EAX,BYTE PTR SS:[EBP-D]
00F510D0  |. 83F8 39        |CMP EAX,39
00F510D3  |. 7E 1B          |JLE SHORT 00F510F0
00F510D5  |> 0FB64D F3      |MOVZX ECX,BYTE PTR SS:[EBP-D]
00F510D9  |. 83F9 41        |CMP ECX,41
00F510DC  |. 7C 09          |JL SHORT 00F510E7
00F510DE  |. 0FB655 F3      |MOVZX EDX,BYTE PTR SS:[EBP-D]
00F510E2  |. 83FA 46        |CMP EDX,46
00F510E5  |. 7E 09          |JLE SHORT 00F510F0
00F510E7  |> C745 E8 000000>|MOV DWORD PTR SS:[EBP-18],0
00F510EE  |. EB 07          |JMP SHORT 00F510F7
00F510F0  |> C745 E8 010000>|MOV DWORD PTR SS:[EBP-18],1
00F510F7  |> 837D E8 00     |CMP DWORD PTR SS:[EBP-18],0
00F510FB  |. 75 04          |JNZ SHORT 00F51101
00F510FD  |. 32C0           |XOR AL,AL
00F510FF  |. EB 07          |JMP SHORT 00F51108
00F51101  |>^E9 6AFFFFFF    \JMP 00F51070
00F51106  |> B0 01          MOV AL,1
00F51108  |> 8BE5           MOV ESP,EBP
00F5110A  |. 5D             POP EBP
00F5110B  \. C3             RETN

Далее открываем AccountServer.exe в OllyDbg v1.10, переходим в модуль AccountServer.exe и ищем участок нулей в памяти приложения, который никогда не используется им во время исполнения - так называемый Code Cave. В AccountServer.exe из сборки Pirate King Online 1.38 (на его примере я рассмотрю процесс внедрения заплатки) Code Cave начинается с адреса 0x00452B8B.

 

4.png

 

Скопируйте байты функции check_mac_address() и вставьте их в Code Cave.

55 8B EC 83 EC 18 B8 CC CC CC CC 89 45 E8 89 45 EC 89 45 F0 89 45 F4 89 45 F8 89 45 FC C7 45 FC
00 00 00 00 8B 45 08 89 45 F8 8B 4D F8 0F BE 11 89 55 EC 8B 45 F8 83 C0 01 89 45 F8 83 7D EC 00
74 18 8B 4D FC 83 C1 01 89 4D FC 83 7D FC 17 76 07 32 C0 E9 B0 00 00 00 EB D0 83 7D FC 17 74 07
32 C0 E9 A1 00 00 00 C7 45 F4 00 00 00 00 EB 09 8B 55 F4 83 C2 01 89 55 F4 8B 45 F4 3B 45 FC 0F
83 81 00 00 00 8B 4D 08 03 4D F4 8A 11 88 55 F3 8B 45 F4 83 C0 01 33 D2 B9 03 00 00 00 F7 F1 85
D2 75 0D 0F B6 55 F3 83 FA 2D 74 04 32 C0 EB 58 8B 45 F4 83 C0 01 33 D2 B9 03 00 00 00 F7 F1 85
D2 74 3E 0F B6 55 F3 83 FA 30 7C 09 0F B6 45 F3 83 F8 39 7E 1B 0F B6 4D F3 83 F9 41 7C 09 0F B6
55 F3 83 FA 46 7E 09 C7 45 E8 00 00 00 00 EB 07 C7 45 E8 01 00 00 00 83 7D E8 00 75 04 32 C0 EB
07 E9 6A FF FF FF B0 01 8B E5 5D C3

Запомните адрес, начиная с которого Вы вставили байты. Это будет адрес функции check_mac_address(). В моем случае это адрес 0x00452B90 (1).

 

5.png

 

Задействуем внедренную функцию для проверки MAC-адресов. Найдите последовательность байт:

8B 8D C4 FC FF FF 89 81 9C 00 00 00 6A 00 8D 4D 08

Вы должны увидеть следующие инструкции:

MOV ECX,DWORD PTR SS:[EBP-33C]
MOV DWORD PTR DS:[ECX+9C],EAX
PUSH 0
LEA ECX,DWORD PTR SS:[EBP+8]
CALL 0041C410
PUSH EAX
MOV ECX,DWORD PTR SS:[EBP-33C]
ADD ECX,0A4

6.png

 

Инструкцию PUSH EAX (1) необходимо заменить на инструкцию безусловного перехода JMP <Адрес Code Cave> в Code Cave, например, после внедренной функции check_mac_address() (1). В моем случае это адрес 0x00452C9E. Также запомните адрес инструкции ADD ECX, 0A4 (2) - программа будет возвращаться сюда при успешной проверке MAC-адреса. В примере это адрес 0x00403EB3.

 

7.png

 

8.png

 

Далее нужно найти адрес инструкций (1), на которые программа будет переходить при некорректном MAC-адресе. Найдите последовательность байт:

8B 85 C4 FC FF FF C6 40 14 00 C7 45 FC FF FF FF FF 8D 4D 08

В AccountServer.exe из серверных файлов Pirate King Online 1.38 они будут записаны по адресу 0x00403E4E (1).

 

9.png

 

В результате мы получили 5 адресов:

1. Адрес функции check_mac_address() в Code Cave (0x00452B90);

2. Адрес инструкций проверки MAC-адреса в Code Cave (0x00452C9E);

3. Адрес перехода на инструкции проверки MAC-адреса в Code Cave из обработчика пакета аутентификации (0x00403EAC);

4. Адрес перехода из Code Cave на инструкции продолжения процесса обработки пакета аутентификации (0x00403EB3);

5. Адрес перехода из Code Cave на инструкции завершения процесса обработки пакета аутентификации (0x00403E4E);

 

Осталось записать в Code Cave инструкции для проверки MAC-адреса и подставить в них требуемые адреса:

PUSHAD
PUSH EAX
CALL <Адрес функции check_mac_address()>
ADD ESP,4
TEST AL,AL
JE SHORT <FAIL>
POPAD
PUSH EAX
MOV ECX,DWORD PTR SS:[EBP-33C]
JMP <Адрес перехода при успешной проверке MAC-адреса>
<FAIL> POPAD
JMP <Адрес перехода при неккоректном MAC-адресе>

10.png

 

Сохраните проделанные изменения в файле AccountServer.exe. На этом внедрение заплатки и исправление уязвимости в AccountServer.exe завершено. Перейдем к процессу тестирования.

 

Скачать исправленный AccountServer.exe из серверных файлов Pirate King Online 1.38

 

 

4. Проверка AccountServer.exe

 

После того, как Вы внедрили заплатку в AccountServer.exe, необходимо убедиться что она работает, а уязвимость к внедрению вредоносного SQL-кода исчезла. Чтобы это проверить примените эксплойт к исправленному AccountServer.exe по инструкции, приведенной в разделе "Эксплойт". Вы должны увидеть сообщение: SQL INJECTION FAILED! YOUR ACCOUNTSERVER IS SECURED!

 

11.png

 

 

Благодарю за внимание!

  • Like 3

Share this post


Link to post
Share on other sites

Эх, а я помню времена когда мы фиксили это добро через last_login_mac = '!s', олды вспомнят.))

 

А так отличный пример для внедрения своего кода.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×
×
  • Create New...