Автор : Jinn
%===| Введение |===%
Эта статья является продолжением темы взаимодействия php и socks5 сервера. Первую статью "Организация работы php скрипта через socks5 сервер" ты можешь найти на www.zaeb.us или на форуме www.root-access.ru. В ней рассматривалось взаимодействие php и сокса не обремененного аутентификацией. Но соксовые прокси без аутентификации чаще всего - публичные, а работать через них - сжигание своих нервных клеток, а не жировых, как думаю некоторые -). А свои кровные соксы, неважно, как добытые, очень часто, что не без основательно, устанавливаются с авторизацией. Единственным выходом является написание скриптов с поддержкой некоторых схем авторизации. Чем мы сейчас и займемся.
Для понимания данной статьи нужно обладать некоторыми знаниями(начальными), т.к подробно на этих моментам я останавливаться не буду. Желательно: знание протокола socks5(RFC1928 или знать содержание моей первой статьи), язык php.....
Для тестирования я использовал:
* Win XP SP2,
* Frenzy-lite 1.0 установленая на VMware 4.5.1 =)
* Opera 8.52
* Денвер
* оценочная версия какого-то снифера, подойдет любой, его не долго юзать:-)
Для организации socks5 сервера использовались:
win : bouncer-1.0.rc6-win32, 3proxy-0.5.3i(ЗАРАЗА)
frenzy : bouncer-1.0.rc6-freebsd-intel
Я писал и отлаживал скрипт на локалхосте, причем сначала на bouncer'е под виндой, потом под frenzy, и чтобы удостовериться в совместимости проверил на 3proxy. А когда код был готов, поднял сокс на удаленной FreeBSD :-)
%===| Кратко о протоколе |===%
Более подробно я описывал протокол в свой первой статье [1]. Сейчас же немного повтоюсь. Данные socks серверу передаются в бинарном виде.
Работа начинается с того, что клиент посылает пакет с данными о поддерживаемых им схемах авторизации, сокс отсылает клиенту ответ, в котором содержится выбраный метод авторизации, далее начинается сам процесс аутентификации.
Существует несколько методов:
* 00 аутентификация не требуется (описано в первой моей статье) * 01 GSS-API (см.RFC1961) * 02 USERNAME/PASSWORD (см. RFC1929), как я понял самый распространенный * 03 до 7F зарезервировано IANA * 80 до FE предназначено для частных методов * FF нет применимых методовСледующий шагом после аутентификации(если она успешна) является отправление пакета, где указан адрес и порт для коннекта(рассмотрим только его), после чего при успешном коннекте клиент может начинать отсылать данные, которые пройдут путь от сокса к конечному серверу. Данные должны быть представлены в формате задаваемом данной схемой авторизации, но это мы рассмотрим далее.
%===| Изучаем метод аутентификации |===%
Скажу сразу, что когда я начал изучать методы аутентификации у меня не было RFC, я получал данные снифанием трафика после чего анализировал их. Поэтому свой рассказ я буду строить так как изучал я (одновременно покажу методы изучения без документации). Конечно после все свои выводы я проверил по RFC1929....
Для тестов поднимаем пятый носок с аутентификацией по логину:jinn & пассу:jinn2, конечно 3proxy обладает большими возможностями, но для своих экспериментов я пользовался bouncer'ом - это личное дело каждого.
Нам нужно будет отснифать трафик передаваемый сокс серверу и посмотреть как формируются пакеты. Для этого я решил поработать через сокс с каким-либо шеллом :-)
Вот какие данные передаются в момент аутентификации при методе 02(username/password):
Putty->bouncer 0x0000 01 04 6A 69 6E 6E 05 6A 69 6E 6E 32 bouncer->Putty 0x0000 01 00
Т.е передаютя поля:
* ver это как я понял постоянная переменная, т.к постоянно передается именно 01, после я глянул в RFC - так и есть:-) * len username длина логина * Username логин * len password длина пароля * Password пароль Сервер сверяет полученные данные и отправляет пакет: * ver 01 * status * 00 все в порядке,аутентификация прошла успешно :-) * иное связь должна быть разорвана
Метод аутентификации меня порадовал - очень прост :-) но эта схема аутентификации не требует особого формирования пакетов, т.е в ней не реализована проверка целостности/конфиденциальности данных. Поэтому после того как аутентификация прошла успешно, можно начинать посылать пакеты с connnect'ом, bind'ом - вообщем дальше все идет как будто аутентификации и не было :-)
Как выяснилось далее Bouncer поддерживает только метод username/password(рассмотренный выше). Это было выяснено методом научного тыка: сначала я слал пакеты с методами 00 и 02, bouncer выбирал 02, тогда когда на пакеты 00 01, 00 03 был ответ FF, т.е данный метод не поддерживается. Чтож проверим 3proxy, может быть сокс от ЗАРАЗЫ поддерживает другие методы?!
В файле конфигурации для сокса было выставлено auth strong, и вот как вел себя сервер. Точно также при предложенных ему методах 02 и 00 происходила авторизация по 02, далее отсылались пакеты на коннект, данные http-серверу - все было ok. Но когда я заменял 02 на что-либо иное(01,03), то сокс выбирал метод 00, т.е без авторизации, но на запрос Connect сокс-сервер отвечал ошибкой 04(хост недоступен). Тем самым недопускал трафик неавторизовавшихся, по его мнению, клиентов. Хотя может я что не так делал :-0. Вообщем мне не удалось найти сокс-сервер с поддержкой других методов. Напишем скрипт с поддержкой хотя бы этого метода.
%===| Практика |===%
Я решил, что за основу возьму код из моей первой статьи и добавлю туда поддержку метода username/password. В прошлой статье мы переводили даные в бинарный режим с помощью самописной ф-ции которая возвращала символ по его ascii коду, в этот раз мы воспользуемся ф-цией php pack(). В этот раз данные типа логина, пароля, доменного имени мы не будем переводить в hex, мы будем передавать их вообще не переводя, они ведь представлены в виде символов... Так же мы не будем использовать наши ф-ции len_test,hex. В предыдущей статье я хотел показать, что очень многие ф-ции php можно заменить своими, но в этот раз я покажу другой путь. Но все останется по прежнему в плане распаковки данных, мы будем использовать точно также bin2hex(). Для постоянных экспериментов намного удобнеее сделать вебморду для скрипта, а результат выводить в textarea, но это будет не трудно любому человеку знающему php, но если что - стучи, договоримся :-) Т.к наши методы не требуют особого формирования пакетов, то мы вынесем авторизацию в отдельный блок. После того как она будет пройдена начнется взаимодействие с соксом, применимое в обоих случаях (когда схема 02 и когда 00).
Для того чтобы перевести длину в бинарный режим мы поступим вот как, сначала получим длину переменной с помощью strlen, а потом получим специфический символ с помощью chr. Это избавит нас от многих ненужных преобразований переменных.
Порт для подключения мы будем упаковывать с помощью pack("n"), а не как в тот раз самописной функцией.
Далее будет идти код с подробными коментариями:
<? error_reporting(0); $socks_ip = "127.0.0.1"; // адрес scoks 5 прокси $socks_port = 1080; // порт сокса $socks_login = "jinn"; // логин для доступа к соксу $socks_pass = "jinn2"; // пароль $connect_host = "www.zaeb.us"; // доменное имя или ip сервера которому мы будем посылать данные через сокс-сервер =) $connect_port = 80; // порт $path = "/"; // докумен который мы будем запрашивать у http-сервера,в нашем случае это главная страница $socks_server=fsockopen($socks_ip,$socks_port); // соединяемся с соксом if ($socks_server) { echo'<meta http-equiv="Content-Language" content="ru"><meta http-equiv=Content-Type content="text/html; charset=windows-1251">'; $h=pack("H*",'05020002'); // привествие =) fwrite($socks_server,$h); $result=bin2hex(fread($socks_server,4)); // переводим в hex для дальнейшего сравнения if($result == '0500') // авторизация отсутствует { $auth_ok=1; } elseif($result == '0502') // метод username/password { $len_login = chr(strlen($socks_login)); //получаем спец символ соответсвующий ascii коду $len_pass = chr(strlen($socks_pass)); // получаем спец символ соответсвующий ascii коду $h=pack("H*","01").$len_login.$socks_login.$len_pass.$socks_pass; // формируем пакет с помощью pack() fwrite($socks_server,$h); $result=bin2hex(fread($socks_server,4)); // переводим из бинарного режима в hex // далее сверяем если код ответа 0, то авторизация успешна,выставляем об этом переменую,в ином случа выводим сообщение об ошибке if($result{3}!=0) { echo "<center><font color=red>Ошибка:аутентификация не прошла!</font></center>"; fclose($socks_server); } else { $auth_ok=1; } } else { echo "<center><font color=red>Ошибка:возможно это не socks5 или он не поддерживает данные(00,02) методы аутентификации!</font></center>"; } $list=""; // проверяем если авторизация успешна , то начинаем взаимодействие с соксом if($auth_ok==1) { $len_h=chr(strlen($connect_host)); $h=pack("H*","05010003").$len_h.$connect_host.pack("n",$connect_port); //формируем запрос fwrite($socks_server,$h); $result=bin2hex(fread($socks_server,100)); // сичитываем ответ сокса переводим в hex echo "<center>Ответ socks'а при попытке коннекта на хост ".$connect_host.":<b>".$result."</b></center><br>"; // выводим ответ сервера, иногда нужно при тестировании и изучении протокола // проверяем успешность подключения,выводи ошибки if($result{3} == 0) { $head = "GET $path HTTP/1.0\r\n"; $head .= "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 8.52\r\n"; $head .= "Host: $connect_host\r\n"; $head .= "Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1\r\n"; $head .= "Accept-Language: ru,en;q=0.9\r\n"; $head .= "Accept-Charset: windows-1252, utf-8, utf-16, iso-8859-1;q=0.6, *;q=0.1\r\n"; $head .= "Proxy-Connection: close\r\n\r\n"; fwrite($socks_server,$head); // считываем ответ HTTP-сервера через SOCKS-сервер до конца =) while(!feof($socks_server)) { echo fread($socks_server,1024); } } // определяем ошибку! данные коды ответа взяты из RFC elseif ($result{3} == 1) {echo "<center><font color=red>Ошибка: вина SOCKS-сервера</font></center>";} elseif ($result{3} == 2) {echo "<center><font color=red>Ошибка: соединение запрещено набором правил</font></center>";} elseif ($result{3} == 3) {echo "<center><font color=red>Ошибка: сеть недоступна</font></center>";} elseif ($result{3} == 4) {echo "<center><font color=red>Ошибка: хост недоступен</font></center>";} elseif ($result{3} == 5) {echo "<center><font color=red>Ошибка: отказ в соединении</font></center>";} elseif ($result{3} == 6) {echo "<center><font color=red>Ошибка: истечение TTL</font></center>";} elseif ($result{3} == 7) {echo "<center><font color=red>Ошибка: команда не поддерживается</font></center>";} elseif ($result{3} == 8) {echo "<center><font color=red>Ошибка: тип адреса не поддерживается</font></center>";} else {echo "<center><font color=red>Ошибка: не определено!RFC в панике! :-)</font></center>";} } fclose($socks_server); } else { echo "<center><font color=red>SOCKS сервер недоступен!</font></center>"; } echo '<br><br><table border="0" cellspacing="1" width="100%" align="center" style="background-color:#000000;"><tr><td width="100%" align="center" style="background-color:#E8E8E8;"><font size=1 color="#292929" face="verdana">[ c0ded by <b>Jinn</b> | 304227033 | http://www.zaeb.us ] </font></td></tr></table>'; ?>
Как и в первой статье на эту тему я ставил свой целью только показать основы взаимодействия с соксом, поэтому я не разделял ответ http-сервера на заголовок и контент. А в некоторых случаях даже нужно, чтобы все выводилось как получено, особенно при тестах и изучении проокола.
%===| Итог |===%
Конечно меня самого не устраивает поддержка только одной схемы авторизации, но писать поддержку других методов опираясь на RFC мне не улыбается :-( Хочется еще и практики...Может кто подскажет сокс-сервер с поддержкой других методов?
Еще много работы: нужно реализовать bind, udp associate:-)
%===| Материалы |===%
[1] Организация работы php скрипта через socks5 сервер, ]]>http://www.zaeb.us]]>
[2] RFC1928: SOCKS Protocol Version 5
[3] RFC1929: Username/Password Authentication for SOCKS V5
%-----|- Jinn | ZaeB.us | ]]>http://www.zaeb.us]]> -|-----%