В этом посте я хочу рассказать, как эксплуатировать SQL инъекции в веб-приложенях на платформе PHP-MySQL. Как обходить фильтры addslashes и mysql_real_escape_string. И как полностью скомпрометировать хост через SQL инъекцию.

Для демонстрации я использую bWAPP. Это уязвимое PHP приложение. Оно создано бельгийским разработчиком Malik Mesellem для обучения веб-безопасности. Это довольно свежий проект – появился в августе 2013 года. Приложение содержит более 60 различных уязвимостей. Как классические SQL, LDAP, XML инъекции, инъекции команд ОС, XSS, RFI, LFI, так и, например, Slow HTTP POST DoS. Для каждой уязвимости есть три уровня сложности ее эксплуатации (low, medium, high). Сайт проекта находится здесь http://www.mmeit.be/bwapp/, скачать приложение можно здесь http://sourceforge.net/projects/bwapp/

Приложение распространяется в двух вариантах: виртуальная машина - уязвимая Ubuntu с установленным bwapp (вообщем, boot-to-root) или отдельно bwapp. Я выбрал второй вариант.
Конфигурация атакуемой машины: Windows 2008 R2 Server Russian, XAMPP, bWAPP. Для атак я использовал Backtrack 5r3 – еще в процессе перехода на Kali)) 

Мой выбор Windows и XAMPP в качестве платформы для bWAPP связан с тем, что по умолчанию сервис MySQL запущен с правами LocalSystem и эксплуатация SQL инъекции позволяет скомпрометировать весь хост.

Немного о том, что из себя представляет bWAPP с точки зрения SQL инъекций.

bWAPP содержит четыре страницы с SQL инъекцией sqli_1.php, sqli_2.php, sqli_3.php, sqli_4.php. Первая страница sqli_1.php осуществляет поиск в базе по GET-параметру title. Вторая страница sqli_2.php содержит поле select и осуществляет поиск в базе по числовому GET-параметру movie. Третья страница sqli_3.php – это страница аутентификации, POST-параметры login и password используются для поиска нужного пользователя в базе. Четвертая страница sqli_4.php содержит поле для ввода, значение которого передается как POST-параметр title и используется для поиска в базе. На основе передаваемых параметров (GET или POST)  строится SQL-запрос путем конкатенации строк. Для трех первых страниц результаты выполнения SQL-запроса возвращаются сервером и отображаются на странице. Для четвертой страницы результаты запроса не отображаются, но в зависимости от результата выводится сообщение «The movie exists» либо «The movie does not exists».

Перед построением SQL-запроса передаваемые параметры (GET или POST) попадают в функцию sqli, которая в зависимости от уровня сложности выполняет те или иные проверки. 

Вот как выглядит эта функция:
function sqli($data)
{     
    switch($_COOKIE["security_level"])
    {  
        case "0" : 
            $data = no_check($data);           
            break;
        case "1" :
            $data = sqli_check_1($data);
            break;
        case "2" :                      
            $data = sqli_check_2($data);            
            break;
        default : 
            $data = no_check($data);            
            break;   
    }
}    
Все функции проверок содержаться в файле  function_external.php.
function no_check($data)
{    
    return $data;    
}

function sqli_check_1($data)
{
    return addslashes($data);  
}

function sqli_check_2($data)
{ 
    return mysql_real_escape_string($data);
}
Как видно. В случае уровня low, с данным ничего не происходит. В случае уровня medium, применяется функция addslashes, которая экранирует специальные символы (, , \, \x00) при помощи бэкслэша (\). В случае уровня high используется функция mysql_real_escape_string, которая экранирует специальные символы (\x00\n\r\'" и \x1a) также при помощи бэкслэша (\).Еще важный момент. bWAPP работает с MySQL из под пользователя root СУБД.

Понимая, как работает приложение, можно перейти к интересным вещам.

Для эксплуатации SQL-инъекций я использовал тулзу sqlmap (входит в Backtrack и Kali). Кто не знаком, прошу на wiki проекта здесь https://github.com/sqlmapproject/sqlmap/wiki/Usage.

На уровне low эксплуатация SQL-инъекции в моей конфигурации (Windows+XAMPP+bWAPP) для всех страниц приводит к полной компрометации хоста. Атакующий получает доступ к выполнению команд операционной системы с правами LocalSystem.

Как это происходит. SQL-инъекция дает возможность атакующему выполнять SQL-команды с правами пользователя root CУБД. Пользователь root имеет привилегии File_priv, которые позволяют ему читать и писать файлы с правами учетной записи процесса MySQL (т.е. LocalSystem). Для чтения файлов в MySQL используется конструкция load_file. Для записи файлов в MySQL используется конструкция into dumpfile или into outfile. Атакующий просто забрасывает PHP шелл в директорию Web-сервера и получает доступ к операционной системе через шелл. Все просто.

Смотрите первый скринкаст, в котором показано как получить доступ к ОС через SQL-инъекцию в sqli_3.php.

На уровнях medium и hard, когда данные проходят через addslashes или mysql_real_escape_string, а затем попадают в SQL-запрос, полностью скомпрометировать хост уже не получиться. Причина в том, что конструкции into dumpfile (outfile) нужно обязательно передавать имя файла в кавычках, а кавычки экранируются функциями addslashes и mysql_real_escape_string. Записать шелл на сервер уже не получиться(

Чтение файлов возможно, так как load_file может принимать строку в бинарном виде (например, 0x633a2f78616d70702f6874646f63732f62776170702f73716c695f312e706870 то же самое что c:/xampp/htdocs/bwapp/sqli_1.php). В нашей конфигурации (Windows+XAMPP+bWAPP) можно получить доступ на чтение к любым файлам ОС, если конечно известен полный путь к файлам.

Если инъекция в числовом параметре (как в случае с sqli_2.php), мы легко обходим фильтры addslashes и mysql_real_escape_string.

Если инъекция в строковом параметре, тогда необходима кавычка, чтобы закрыть строку и вставить наши SQL-операторы. Казалось бы, addslashes и mysql_real_escape_string экранируют кавычку и у атакующего нет возможности терминировать строку в оригинальном запросе. Но из-за особенностей работы функций addslashes и mysql_real_escape_string в PHP при определенных условиях у атакующего есть возможность обойти фильтрацию одинарной кавычки.

Дело в том, что функция addslashes вообще не различает разные кодировки символов и экранирует символы побайтно. В документации на mysql_real_escape_string написано, что она принимает кодировку соединения, но на практике она работает аналогично addslashes. СУБД MySQL поддерживает разные кодировки. На моей машине, где стоит bWAPP команда show character set показала 39 различных кодировок. Техника обхода как раз основана различии в обработке символов функциями addslashes или mysql_real_escape_string и самой MySQL. Есть такие кодировки, например big5 (китайская кодировка), которая содержит как однобайтные символы (нормальные ascii символы), так и двухбайтные символы (обозначают иероглифы). В таких кодировках есть символы, второй байт которых равен \x5c (бэкслэш в hex). Если MySQL в такой странной кодировке, получается следующее. Если мы передаем веб-приложению что-то вроде %e5%27, функция addslashes или mysql_real_escape_string преобразует это в %e5%5с%27. Далее это передается в MySQL, который интерпретирует это как два символа иероглиф %e5%5c и одинарную кавычку %27. Тада…мы обходим фильтрацию)


Чтобы успешно эксплуатировать эту уязвимость, я изменил кодировку для MySQL  - внес следующие строки в конфиг my.ini:
[mysqld]
init-connect='SET NAMES big5'
character-set-server = big5
Мне стало интересно, какие кодировки и какие символы позволяют обойти addslashes и mysql_real_escape_string. Я написал фаззер на питоне, который проверяет, есть ли в кодировке однобайтовый символ \x27 и ищет в кодировке все двухбайтовые символы, у которых второй байт оканчивается на \x5c.

#!/usr/bin/python

import sys
import binascii
import struct

encoding = sys.argv[1]

sb = b'\x5c' 

print "[-] Valid symbols for %s encoding ended with 5c:"%encoding

try:
        "\x27".decode(encoding)
except Exception,e:
        sys.exit(0)

for i in range(128,255):
        symb = ""
        try:
                symb = (struct.pack("B",i)+sb).decode(encoding)
                if len(symb) > 1:
                        continue
        except Exception,e:
                continue

        s = binascii.hexlify(struct.pack("B",i)+sb)

        print '%'+s[:2]+'%'+s[2:]+",",
Далее я запустил фаззер для все кодировок, поддерживаемых MySQL. В файл encoding.txt я скопировал вывод команды show character set без заголовка таблицы.
0ang3el@ubuntu13:~/sqli-tut# cat encoding.txt | cut -d " " -f2 | xargs -L1 python EncodingFuzzer.py
В итоге получается, что четыре кодировки big5, sjisgbkcp932 удовлетворяют условиям и позволяют обойти фильтры addslashes и mysql_real_escape_string.
[-] Valid symbols for big5 encoding ended with 5c:
%a1%5c, %a2%5c, %a3%5c, %a4%5c, %a5%5c, %a6%5c, %a7%5c, %a8%5c, %a9%5c, %aa%5c, %ab%5c, %ac%5c, %ad%5c, %ae%5c, %af%5c, %b0%5c, %b1%5c, %b2%5c, %b3%5c, %b4%5c, %b5%5c, %b6%5c, %b7%5c, %b8%5c, %b9%5c, %ba%5c, %bb%5c, %bc%5c, %bd%5c, %be%5c, %bf%5c, %c0%5c, %c1%5c, %c2%5c, %c3%5c, %c4%5c, %c5%5c, %c6%5c, %c7%5c, %c9%5c, %ca%5c, %cb%5c, %cc%5c, %cd%5c, %ce%5c, %cf%5c, %d0%5c, %d1%5c, %d2%5c, %d3%5c, %d4%5c, %d5%5c, %d6%5c, %d7%5c, %d8%5c, %d9%5c, %da%5c, %db%5c, %dc%5c, %dd%5c, %de%5c, %df%5c, %e0%5c, %e1%5c, %e2%5c, %e3%5c, %e4%5c, %e5%5c, %e6%5c, %e7%5c, %e8%5c, %e9%5c, %ea%5c, %eb%5c, %ec%5c, %ed%5c, %ee%5c, %ef%5c, %f0%5c, %f1%5c, %f2%5c, %f3%5c, %f4%5c, %f5%5c, %f6%5c, %f7%5c, %f8%5c, %f9%5c,
[-] Valid symbols for dec8 encoding ended with 5c:
[-] Valid symbols for cp850 encoding ended with 5c:
[-] Valid symbols for hp8 encoding ended with 5c:
[-] Valid symbols for koi8r encoding ended with 5c:
[-] Valid symbols for latin1 encoding ended with 5c:
[-] Valid symbols for latin2 encoding ended with 5c:
[-] Valid symbols for swe7 encoding ended with 5c:
[-] Valid symbols for ascii encoding ended with 5c:
[-] Valid symbols for ujis encoding ended with 5c:
[-] Valid symbols for sjis encoding ended with 5c:
%81%5c, %83%5c, %84%5c, %89%5c, %8a%5c, %8b%5c, %8c%5c, %8d%5c, %8e%5c, %8f%5c, %90%5c, %91%5c, %92%5c, %93%5c, %94%5c, %95%5c, %96%5c, %97%5c, %98%5c, %99%5c, %9a%5c, %9b%5c, %9c%5c, %9d%5c, %9e%5c, %9f%5c, %e0%5c, %e1%5c, %e2%5c, %e3%5c, %e4%5c, %e5%5c, %e6%5c, %e7%5c, %e8%5c, %e9%5c, %ea%5c,
[-] Valid symbols for hebrew encoding ended with 5c:
[-] Valid symbols for tis620 encoding ended with 5c:
[-] Valid symbols for euckr encoding ended with 5c:
[-] Valid symbols for koi8u encoding ended with 5c:
[-] Valid symbols for gb2312 encoding ended with 5c:
[-] Valid symbols for greek encoding ended with 5c:
[-] Valid symbols for cp1250 encoding ended with 5c:
[-] Valid symbols for gbk encoding ended with 5c:
%81%5c, %82%5c, %83%5c, %84%5c, %85%5c, %86%5c, %87%5c, %88%5c, %89%5c, %8a%5c, %8b%5c, %8c%5c, %8d%5c, %8e%5c, %8f%5c, %90%5c, %91%5c, %92%5c, %93%5c, %94%5c, %95%5c, %96%5c, %97%5c, %98%5c, %99%5c, %9a%5c, %9b%5c, %9c%5c, %9d%5c, %9e%5c, %9f%5c, %a0%5c, %a8%5c, %a9%5c, %aa%5c, %ab%5c, %ac%5c, %ad%5c, %ae%5c, %af%5c, %b0%5c, %b1%5c, %b2%5c, %b3%5c, %b4%5c, %b5%5c, %b6%5c, %b7%5c, %b8%5c, %b9%5c, %ba%5c, %bb%5c, %bc%5c, %bd%5c, %be%5c, %bf%5c, %c0%5c, %c1%5c, %c2%5c, %c3%5c, %c4%5c, %c5%5c, %c6%5c, %c7%5c, %c8%5c, %c9%5c, %ca%5c, %cb%5c, %cc%5c, %cd%5c, %ce%5c, %cf%5c, %d0%5c, %d1%5c, %d2%5c, %d3%5c, %d4%5c, %d5%5c, %d6%5c, %d7%5c, %d8%5c, %d9%5c, %da%5c, %db%5c, %dc%5c, %dd%5c, %de%5c, %df%5c, %e0%5c, %e1%5c, %e2%5c, %e3%5c, %e4%5c, %e5%5c, %e6%5c, %e7%5c, %e8%5c, %e9%5c, %ea%5c, %eb%5c, %ec%5c, %ed%5c, %ee%5c, %ef%5c, %f0%5c, %f1%5c, %f2%5c, %f3%5c, %f4%5c, %f5%5c, %f6%5c, %f7%5c, %f8%5c, %f9%5c, %fa%5c, %fb%5c, %fc%5c, %fd%5c,
[-] Valid symbols for latin5 encoding ended with 5c:
[-] Valid symbols for armscii8 encoding ended with 5c:
[-] Valid symbols for utf8 encoding ended with 5c:
[-] Valid symbols for ucs2 encoding ended with 5c:
[-] Valid symbols for cp866 encoding ended with 5c:
[-] Valid symbols for keybcs2 encoding ended with 5c:
[-] Valid symbols for macce encoding ended with 5c:
[-] Valid symbols for macroman encoding ended with 5c:
[-] Valid symbols for cp852 encoding ended with 5c:
[-] Valid symbols for latin7 encoding ended with 5c:
[-] Valid symbols for cp1251 encoding ended with 5c:
[-] Valid symbols for cp1256 encoding ended with 5c:
[-] Valid symbols for cp1257 encoding ended with 5c:
[-] Valid symbols for binary encoding ended with 5c:
[-] Valid symbols for geostd8 encoding ended with 5c:
[-] Valid symbols for cp932 encoding ended with 5c:
%81%5c, %83%5c, %84%5c, %87%5c, %89%5c, %8a%5c, %8b%5c, %8c%5c, %8d%5c, %8e%5c, %8f%5c, %90%5c, %91%5c, %92%5c, %93%5c, %94%5c, %95%5c, %96%5c, %97%5c, %98%5c, %99%5c, %9a%5c, %9b%5c, %9c%5c, %9d%5c, %9e%5c, %9f%5c, %e0%5c, %e1%5c, %e2%5c, %e3%5c, %e4%5c, %e5%5c, %e6%5c, %e7%5c, %e8%5c, %e9%5c, %ea%5c, %ed%5c, %ee%5c, %f0%5c, %f1%5c, %f2%5c, %f3%5c, %f4%5c, %f5%5c, %f6%5c, %f7%5c, %f8%5c, %f9%5c, %fa%5c, %fb%5c,
[-] Valid symbols for eucjpms encoding ended with 5c:
Таким образом. Для того, чтобы завершить строку в оригинальном SQL-запросе, вместо одинарной кавычки мы должны передавать символ  %e5%5c, %af%5c или %bf%5c (эти символы валидны для всех четырех кодировок).

Можно подумать, что для обхода функций фильтрации в случае строкового параметра MySQL должен обязательно быть настроен на работу с одной из перечисленных кодировок. Это не всегда так. В PHP есть функция mysql_set_charset или мы можем вызвать mysql_query(‘SET NAMES big5’). Таким образом мы установим нужную нам кодировку. Веб-приложение уязвимо, если содержит вызов указанных функций, позволяя  пользователю задавать кодировку для работы с MySQL.

Еще раз повторюсь, заливать файлы через SQL-инъекцию при использовании addslashes или mysql_real_escape_string не получиться, так как функции не позволяют передать одинарную кавычку в чистом виде.

Смотрите второй скринкасте к этому посту. В нем показано как эксплуатировать слепую SQL инъекцию в sqli_4.php на уровне hard (обходить фильтрацию mysql_real_escape_string). 

Скринкаст 1.



Скринкаст 2.