Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

SQL-инъекции

1.2K
19 октября 2006 года
PAVEL BASIC
171 / / 24.07.2006
Я бы хотел обсудить способы защит от SQL-инъекций. Возможно кто-нибудь может мне посоветовать наиболее эфективные методы.:D :D
4
19 октября 2006 года
mike
3.7K / / 01.10.2002
mysql_escape_string
2
19 октября 2006 года
squirL
5.6K / / 13.08.2003
а если у него PostgreSQL или Oracle :) ?
1.2K
19 октября 2006 года
PAVEL BASIC
171 / / 24.07.2006
Нет у меня как раз таки MySQL. Спасибо.

Кстати а при защите от SQL-инъекций достаточно будет только этой функции?
И хотелось бы знать с какой версии она поддерживается.
4
19 октября 2006 года
mike
3.7K / / 01.10.2002
[QUOTE=PAVEL BASIC]Нет у меня как раз таки MySQL. Спасибо.

Кстати а при защите от SQL-инъекций достаточно будет только этой функции?
И хотелось бы знать с какой версии она поддерживается.[/QUOTE]
При прямых руках да. Но очень-очень-очень рекомендую проверять все переменные которые так или иначе используются в SQL запросах.
13
20 октября 2006 года
RussianSpy
3.0K / / 04.07.2006
[QUOTE=PAVEL BASIC]Я бы хотел обсудить способы защит от SQL-инъекций. Возможно кто-нибудь может мне посоветовать наиболее эфективные методы.:D :D[/QUOTE]
Читать faq по безопасности в этом разделе
securitylab.ru


Читать много и по нескольку раз чтобы понять.
mysql_escape_string() недостаточно для предотвращения инъекций
369
19 августа 2010 года
Kesano
451 / / 09.10.2007
Руссик, на секлабе не нашел такого раздела... Да и сам секлаб для меня вместо башорга...

подскажите, вот что:
передают переменную $data
Если её обрабатывать так:
 
Код:
$date=$_POST["data"];
$sql="insert into table values ('".$data."')";
mysql_query($sql);

То будь осторожен, sql-хак возможен...

что-то меняет если данные писать так:
 
Код:
$data=$_POST["data"];
mysql_query("insert into table values('$data')");

???
244
19 августа 2010 года
UAS
2.0K / / 19.07.2006
Ничего не меняет. Сделайте $data = "olo'lo"; и наблюдайте ошибку.
369
20 августа 2010 года
Kesano
451 / / 09.10.2007
УАСЬ, не могу наблюдать ошибку... пишет в базу без проблем...
Пытался olo'lo, olo\'lo... всё равно пишет :(

Код:
<form action="" method="POST">
<input type="text" name="user"> <input type="submit" name="but" value="go!">

</form>
<?
if($_POST["user"])
 {
    echo "POST прошел";
    $user=$_POST["user"];
    if(mysql_query("insert into users (user) values ('$user')")) echo "<br> команда Мускуля прошла";
 }
?>


Просьба (может глупая):
Пожалуйста, напишите мне какую-то инъекцию под этот код (форма, переменные и т.д. даны)... Ну например, чтобы инъекция трункатила таблицу...
13
20 августа 2010 года
RussianSpy
3.0K / / 04.07.2006
На дату посмотрите - в те времена секлаб был вполне приличным сайтом. Сам я его уже пару лет как не читаю потому что секлаб стал какахой.
369
20 августа 2010 года
Kesano
451 / / 09.10.2007
Упс... Посмотрел...
Уважаемый Спай, по поводу моего поста чуть выше есть что сказать???...
244
20 августа 2010 года
UAS
2.0K / / 19.07.2006
Код:
/*
CREATE TABLE `user`(
`user` VARCHAR(40) NOT NULL
)
*/


$user = "olol'o";

$rs = mysql_query("insert into user(user) values ('$user')");

if( $rs === false ) {
    printf("#%d - %s", mysql_errno(), mysql_error());
} else {
    echo("Ok");
}


Ругается как
 
Код:
#1064 - 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 'o')' at line 1

Я полагаю и стоит у вас Денвер какой-нить и включен флаг magic_quotes.

Посчет TRUNCATE в INSERT - мне пока идей не пришло. Зато можно спокойно напосоздавать любых пользователей с любыми правами (и паролями, я полагаю).

А если $user равно 1'), (VERSION()), ('1 - то создаем за раз трех пользователей, имеем доступ к SQL-командам.
13
20 августа 2010 года
RussianSpy
3.0K / / 04.07.2006
Цитата: Kesano
Упс... Посмотрел...
Уважаемый Спай, по поводу моего поста чуть выше есть что сказать???...



Конечно есть - читайте
http://webew.ru/articles/2078.webew

369
20 августа 2010 года
Kesano
451 / / 09.10.2007
"Денвер какой-нить" - вот грубиян :)
Да. Денвер стоит...

2 Спай: Угу.. почитал...
т.е. РЕАЛЬНЫМ спасением (читай - защитой) SQL-запросов будет:
1. Использование проверки переменных регулярками
2. Использование mysql_real_escape_string()
?

Я пока не знаю, как именно работает mysql_real_escape_string(), но вопрос таков:
А что, если нужно передавать данные как есть???
Ну, чтобы в базе можно было хранить html-код и т.п. ???
244
20 августа 2010 года
UAS
2.0K / / 19.07.2006
mysql_escape_string, вроде как, иногда некорректно работает с многобайтными кодировками (т.е. наш любимый UTF-8), в результате чего можно побить строки.

Цитата:
А что, если нужно передавать данные как есть??? Ну, чтобы в базе можно было хранить html-код и т.п. ???


Передавать как есть, только обрабатывать mysql_real_escape_string

Цитата:
"Денвер какой-нить" - вот грубиян


Ну потому что вас издалека видно :) Потому что если бы хоть раз ставили сами бы, то не возникало бы вопросов, почему строка уже экранирована, потому что знали бы надстройки. Так, при включенном magic_quotes все данные, приходящие от клиента автоматически экранируются.
И вообще [QUOTE="php.net"]This feature has been DEPRECATED as of PHP 5.3.0. Relying on this feature is highly discouraged.[/QUOTE]

Да и напишите функцию проверки, общую для всех.
У меня вся проверка данных выполняется в виде:
value_check( $value, array("type"=>"numeric", "min" => 100, "max" => "100K") );
value_check( $value, array("type"=>"email", "empty" => true) );

Ибо плодить в коде кучу регулярок и проверок ненаглядно, а тут сразу все видно. Проверка, так-то, хорошая.

369
21 августа 2010 года
Kesano
451 / / 09.10.2007
ну собственно про реал_эскейп я и писал..
а про вэлью_чек пошел читать
244
21 августа 2010 года
UAS
2.0K / / 19.07.2006
Да это не встроенная функция такая))) Это мною написанная функция, просто я привел как пример как сделать удобнее)
369
21 августа 2010 года
Kesano
451 / / 09.10.2007
что-то вроде такого?
Код:
function value_check($str, $type) {

   switch($type) {
   case "email": $pattern='#...emailpattern...#'; break;
   case "text": $pattern='#...symbolpatern...#'; break;
   case "login": $pattern='#...loginpattern...#'; break;
   default: $pattern='#...symbolpatern...#';
   }

   $valid=preg_match($pattern, $str);

   return $valid;

}

if(value_check($login, "login")==true && value_check($email, "email")==true)
   {
     //Всякая белиберда
   }


Правильно???
244
21 августа 2010 года
UAS
2.0K / / 19.07.2006
Нет, типа
Код:
function value_check( $value, array $parameters ) {

    // устанавливаем проверочные параметры и дополняем пропущенными
    $parameters = array_merge(
        // значения параметров проверки "по-умолчанию"
        array(
            "empty"      => null, // ничего не указано в $value
            "equals"     => null, // равенство чему-либо (равенство проверяется через ===
            "minlength"  => null, // минимальная длина строки
            "maxlength"  => null, // максимальная длина строки
            "min"        => null, // min значение числа
            "max"        => null, // max значение числа
            "enum"       => null, // допустимые значения (допустимое значение - массив типа array(enum1,enum2,...)
            "mask"       => null, // регулярное выражение, к которому должно подходить $value
            "type"       => null, // тип (заранее установленные типы: url, seo_url, email, numeric, bool
            "charset"    => "UTF-8" // кодировка $value (если передана строка)
        ),
        // указанные значения параметров проверки
        $parameters
    );

    // преобразование чисел с суффиксами в числа (преобразование суффиксов K,M,k,m)
    foreach( array("minlength","maxlength", "min", "max") as $item ) {
        if( is_null( $parameters[$item]) ) continue;

        // выдергиваем последний символ и сверяем с позволенными суффиксами
        switch( substr((string)$parameters[$item],-1) ) {
            case "K":
                $parameters[$item] = (int)substr($parameters[$item],0,-1)*1024; break;
            case "k":
                $parameters[$item] = (int)substr($parameters[$item],0,-1)*1000; break;
            case "M":
                $parameters[$item] = (int)substr($parameters[$item],0,-1)*1024*1024; break;
            case "m":
                $parameters[$item] = (int)substr($parameters[$item],0,-1)*1000*1000; break;
            default:
                break;
        }
    }


    if( !is_null($parameters["empty"]) && $parameters["empty"] == true && $value == "" ) {
        return true;
    }

    if( !is_null($parameters["equals"]) && $value != $parameters["equals"] ) {
        return false;
    }

    if( !is_null($parameters["minlength"]) &&
        mb_strlen($value,$parameters["charset"]) < $parameters["minlength"]
    ) {
        return false;
    }

    if( !is_null($parameters["maxlength"]) &&
        mb_strlen($value,$parameters["charset"]) > $parameters["maxlength"]
    ) {
        return false;
    }

    if( !is_null($parameters["min"]) && (int)$value < (int)$parameters["min"] ) {
        return false;
    }

    if( !is_null($parameters["max"]) && (int)$value > (int)$parameters["max"] ) {
        return false;
    }

    if( !is_null($parameters["enum"]) &&
        ( !is_array($parameters["enum"]) || !in_array($value, $parameters["enum"], true) )
    ) {
        return false;
    }

    if( !is_null($parameters["mask"]) && !preg_match($parameters["mask"], $value) ) {
        return false;
    }

    if( !is_null($parameters["type"]) ) {
        $no_errors = 1;
        switch($parameters["type"]) {
            case "url":
                $no_errors &= preg_match("#^((http|https|ftp):\/\/)?(www\.)?[a-z0-9-_]+(\.[a-z]{2,6})+?(/?.*)$#i",$value);
                break;
            case "email":
                $no_errors &= preg_match("/^(([a-z0-9]|[!#$%\*\/\?\|^\{\}`~&'\+=-_])+\.)*([a-z0-9]|[!#$%\*\/\?\|^\{\}`~&'\+=-_])+@([a-z0-9-_]+\.)+([a-z0-9-]){2,6}$/s",$value);
                break;
            case "numeric":
                $no_errors &= is_numeric($value);
                break;
            case "bool":
                $no_errors &= is_bool($value);
                break;
            default:
                $no_errors = 0;
                break;
        }
        if( $no_errors == 0 ) return false;
    }

    return true;
}


Использовать, например, как тут:
 
Код:
value_check(100500, array("min"=>100, "max"=>"200k")); // true
value_check(0, array("enum"=>array(1,2,3,4,5)) ); // false
value_check("", array("empty"=>true, "type"=>"email")); // true
value_check("example@example.com", array("type"=>"email")); // true
value_check("ololo", array("equals"=>"ololo")); //true
value_check("qwer(qw", array("maxlength"=>10, "mask"=> "/^qwe.*/i")); // true
value_check("abc", array("type"=>"bool")); // false


Советы с удовольствие выслушаю, ибо использую эту функцию чуть более, чем везде в своих наработках.

[COLOR="Silver"]За email и url рег.выражения не ругать, ибо мне их хватает.[/COLOR]

Да и что-то от оригинальной темы мы отошли.
253
21 августа 2010 года
Proger_XP
1.5K / / 07.08.2004
Цитата: UAS
Советы с удовольствие выслушаю, ибо использую эту функцию чуть более, чем везде в своих наработках.



Пофлудим немного о твоей функции :)

1. Вместо is_null() лично я использую === null, т.к. работает несколько быстрее, ибо не является функцией, а эффект одинаковый.
2. В substr() обязательно приведение к (string)? Думается мне, PHP это делает автоматически, т.к. ожидает на входе строку.
3. Какой смысл в двух проверках, если можно обойтись одной строгой?

 
Код:
if( !is_null($parameters["empty"]) && $parameters["empty"] == true && $value == "" ) {
  // =>
  if( $parameters["empty"] === true && $value == "" ) {

4. Здесь же кстати снова приведения типов, типа (int)$value - думаю, пхп может сравнивать по <, > только числа, значит, конвертит в них автоматом, а у тебя получаются лишние инструкции.
5. default: break; в switch'е для числовых значений явно лишний. Вообще, этот switch я бы переписал с таким трюком:
 
Код:
$multiplier = 1;
switch( substr($parameters[$item],-1) ) {
  case "M": $multiplier *= 1024;
  case "K": $multiplier *= 1024; break;
  case "m": $multiplier *= 1000;
  case "k": $multiplier *= 1000; break;
}
$parameters[$item] = substr($parameters[$item], 0, -1) * $multiplier;

Главное не пропусти break'и. Кстати, if (is_null()) перед switch'ем можно убрать, если добавить в него case null: continue;
6. in_array() скорее всего возвращает false, если $haystack не массив, так что у тебя снова лишние инструкции.
7. Последний switch я бы переделал немного иначе, убрав условие перед ним и добавив "case null: return true;" - у тебя ж после switch'а всё равно ничего нет, стало бы меньше на 2 return, переменную и пару скобок.
8. Кстати, зачем ты там везде используешь &=?
9. Регулярка для url выглядит странно - ты экранируешь "/" внутри неё, но разделитель-то у тебя не /, а # - так что это не нужно.
9.1. Кстати, в доменных именах, насколько я понимаю, подчёркивание не допускается, а у тебя оно есть. Плюс у тебя там избыточность: (/?.*)$ - совершенно не к чему, ибо все части необязательны (? и *), а затем идёт конец строки.
9.2. Насчёт (\.[a-z]{2,6})+[COLOR="Red"]?[/COLOR] я бы ещё раз подумал, по-моему он тут не к чему.
9.3. Зачем тебе в регулярках скобки, ты же не собираешь их в $matches? :) Если для наглядности, то я бы использовал флаг x и пробелы.
9.4. Регулярка для email какая-то странная (! # * / и т.д.).

Ладно, остановимся. Это совершенно личные впечатления, но может пригодятся :) Функция интересная, возьму на заметку соорудить для себя такую же при случае.
244
22 августа 2010 года
UAS
2.0K / / 19.07.2006
Ну что ж, замечания в основном получил как раз те, что и ожидал =)
Большая часть как раз по-оформлению, а я уж привык порой написать лишнюю конструкцию, чтобы нагляднее мне было.

1) Вместо is_null() лично я использую === null
Хмм, а я как-то и не подумал. Реально, при 100.000 итерациях is_null выполняется в среднем ~0.041с, а === за ~0.015.

2) В substr() обязательно приведение к (string)?
Да, ты прав, php сам делает приведение к стринг, это, скорее, моя привычка всегда явно преобразовывать типы, хоть это и не суть важно для языка.

3) Какой смысл в двух проверках, если можно обойтись одной строгой?
Тут, так-то, всё верно.

4) Здесь же кстати снова приведения типов, типа (int)$value
См. пункт 2

5) Вообще, этот switch я бы переписал с таким трюком
Да, неплохо, надо будет переделать.

6) in_array() скорее всего возвращает false, если $haystack не массив, так что у тебя снова лишние инструкции
Warning: in_array() expects parameter 2 to be array, string given (для примера)

7) Последний switch я бы переделал немного иначе, убрав условие перед ним и добавив "case null: return true;"
Ок. Опять же is_null в if я добавил для наглядности, а то у меня везде выше is_null, потому визуально блок проверки сразу видно.

8) Кстати, зачем ты там везде используешь &=?
Чтобы не вводить внутри case условия if(!cond) return false; Потому для наглядности сделал битовое И, а в конце уже делают вывод.
Хотя с учетом замечаний выше + блок последний, я думаю, имеет смысл $no_error &= сразу заменить на return и всё. Что я, скорее всего, и сделаю.

9) Регулярка для url выглядит странно - ты экранируешь "/" внутри неё, но разделитель-то у тебя не /
:D косяк. Видать, когда-то скопипастил, потом сменил разделители, а внутри забыл поправить.

Над пунктами 9.1-9.4 на досуге пошаманю=)

Ок, спс, сделаю кое-какие поправки.
Если кому-то надо будет - выложу, один фиг полезная функция.
287
22 августа 2010 года
Shiizoo
958 / / 14.03.2004
6) "in_array" с "левым" haystack дает "warning".

9.3) Согласен, захватывающие скобки надо заменить на "(?: ... )".


За юзабельность функции в целом не скажу, т. к. когда в свое время делал для себя, пришел к выводу, что особо время не экономит. А может, просто лень одолела. ^)
253
22 августа 2010 года
Proger_XP
1.5K / / 07.08.2004
Цитата: UAS

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


Ну, как я уже сказал, это дело каждого. Лично я придерживаюсь мнения, что "меньше кода - более читабельно", без экстрима, конечно.

Цитата:

Реально, при 100.000 итерациях is_null выполняется в среднем ~0.041с, а === за ~0.015.


Это известный приём оптимизации, типа как замена $i++ на ++$i - вместо 4х опкодов будет 3. Конечно, ощутимая разница будет видна на огромных потоках данных, но почему бы не писать сразу так, как быстрее, ведь разницы в коде нет, а "платить меньше"?

Ещё один трюк из этой же серии, уже на гране экстрима, им я почти не пользуюсь:

 
Код:
if (strlen($str) < 10) { }
if (!isset($str[10])) { }

Т.к. isset() языковая конструкция, а не функция вроде strlen(), то нет расходов на вызов функции, например, создание её стёка, поиск в таблице функций и т.д.

Можешь погуглить про оптимизацию PHP-скриптов, я как-то находил интересную статью (список из ~40 пунктов), эта похожа на неё, но не та. Если интересно, выложу свои записи по оптимизации, которые я взял из той статьи.

Цитата:
Да, ты прав, php сам делает приведение к стринг


И по-моему с (typecast)'ами код становится менее читабельным.

Цитата:

Warning: in_array() expects parameter 2 to be array, string given (для примера)


Да, верно. Это кстати ещё одна интересная штука. Знаешь про то, что код с @ выполняется во много раз медленнее, поэтому рекомендуется @ избегать, а вместо него включать error_reporting на максимум?
Я как раз придерживаюсь этого правила: на локальной машине error_reporting - максимальный, а на реальном сервере отключен. Тогда получается, что многие проверки, которые ты забиваешь @'ми нужны только для предотвращения вывода этих самых сообщений, когда юзер ввёл что-то неверно. Но у тебя-то на локальной машине ведь таких ситуаций почти не бывает и даже наоборот: рискуешь пропустить какое-то важное сообщение, когда код забит @.

У меня такое бывало не раз, например, когда это вызов какого-то метода @$class->Method(), а $class либо не объект, либо просто не имеет Method() - тогда скрипт вылетает, не сказав ни слова (не отправляя вывода и заголовков), и ищи потом место ошибки...

Поэтому использую @ тогда, когда без него код будет более громозкий. Кстати, проверки типа if (is_array($a)) { extract($a); } работают быстрее, чем @extract($a). in_array() и прочие так же.

Цитата:

Ок. Опять же is_null в if я добавил для наглядности, а то у меня везде выше is_null, потому визуально блок проверки сразу видно.


Ну, тебе же код читать, поэтому смотри сам. Как я уже сказал, для себя придерживаюсь мнения "меньше кода лучше видно".

Цитата:

8) Кстати, зачем ты там везде используешь &=?
Чтобы не вводить внутри case условия if(!cond) return false;


Так может и так, но у тебя ж там после каждого case стоит break, да и не похоже, чтоб $no_error зависел более, чем от одного условия.

А так код функции получается разбитым на два: в первой части (до последнего switch) ты сразу делаешь return false/true, а во второй используешь флаг, и только потом выходишь из функции. Тогда уж либо везде используй флаг + & и | (что кстати я обычно и делаю), либо везде return.

Цитата:

Если кому-то надо будет - выложу, один фиг полезная функция.


Выкладывай, может что ещё найдём интересное :)

Цитата: Shiizoo

9.3) Согласен, захватывающие скобки надо заменить на "(?: ... )".


Это не то, что я имел в виду (кстати, интересно, насколько быстрее работают регулярки с (?: ), чем с ()?). Там не везде скобки вообще используются, например: [COLOR="Blue"](/?.*)$#i",$value);[/COLOR] - здесь никакого модификатора после скобок нет.

Цитата:

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


Я сейчас использую небольшие функции типа

 
Код:
function Bounds(&$var, $lowest, $highest) {
  isset($var) or $var = 0;
  $var = max($lowest, min($highest, $var));
}

вместо одной большой. Раньше, вроде бы, использовал вариант как у UAS. Пока не решил, что лучше.
10
22 августа 2010 года
Freeman
3.2K / / 06.03.2004
Извините, что встреваю.

Вопрос непосредственно по теме. Принято ли использовать mysqli_stmt_bind_param(), и если нет, то почему?

Скажем, под Oracle код, не использующий параметры, считается непрофессиональным. И в учебниках первой заповедью идёт именно использование параметров. В первую очередь это делается для разгрузки сервера: подстановка параметров - процедура намного более простая, чем разбор SQL и построение плана заново.

Поскольку значения параметров передаются отдельно от текста запроса и подставляются уже самим сервером, SQL-инъекция невозможна.
244
22 августа 2010 года
UAS
2.0K / / 19.07.2006
Цитата:
Да, верно. Это кстати ещё одна интересная штука. Знаешь про то, что код с @ выполняется во много раз медленнее, поэтому рекомендуется @ избегать, а вместо него включать error_reporting на максимум?


Ну так-то и я сам всегда поступаю)
Последний раз, где я использовал, так в классе для БД перед вызовом, например, @mysql_query, т.к. потом ниже все равно идет проверка результата с выбросом исключений, а @ нужен, дабы не вылезали нотисы, ворнинги или что он там показывает.

Ну посчет 8 пункта - он точно изменится)

Freeman
Я думаю, принято использовать, по крайней мере когда видел код mysqli, то там это использовали + очень много видел подобного, когда Java баловался. Так что используйте =)

253
22 августа 2010 года
Proger_XP
1.5K / / 07.08.2004
Цитата: UAS

Последний раз, где я использовал, так в классе для БД перед вызовом, например, @mysql_query


mysql_query() выдаёт варнинги? :eek: Сколько использую, ни одного не видел, если конечно был коннект к БД и всё штатно.
Ну, идея понятна :)

Цитата: Freeman

Принято ли использовать mysqli_stmt_bind_param(), и если нет, то почему?


Я не думаю, что в пхп есть "принятое" и нет. Конкретно по подготовленным запросам - я их почти не встречал в чужом коде (может, конечно, мало его читал) кроме какого-то одного популярного движка, да и сам не использую. Отчасти потому, что до сих пор юзаю MySQL, а не MySQLi (shame on me, ага), а отчасти потому, что при выводе одной страницы редко нужно делать несколько похожих запросов, которые можно подставить под шаблон - может быть полезно для форумов и тому подобных вещей.

А подготавливать выражение ради одного вызова слишком много мороки.

244
22 августа 2010 года
UAS
2.0K / / 19.07.2006
Ну если передать некорректный SQL, то вылетает ошибка.
253
22 августа 2010 года
Proger_XP
1.5K / / 07.08.2004
Цитата: UAS
Ну если передать некорректный SQL, то вылетает ошибка.


А, ну это конечно.
Суть не/выода ошибок, которую я пытался описать как раз в том, что во время разработки количество нкорректных запросов минимально - и даже если возникает что-то нештатное это либо баг, который надо пофиксить, либо ожидаемая ошибка как следствие некорректного ввода от юзера (= от программёра, имитирующего такой ввод). В обоих случаях вывод ошибок приветствуется и в обоих же случаях отключение вывода ошибок на реально работающем сервере только на руку и позволяет не задумываться о предупреждениях во время разработки.

По крайней мере я использую такой подход.

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог