Настройка VPN-сервера FreeBSD с ведением статистики, шейпингом и фильтрацией
Обновлено: 16.01.2025Для начала: данная статья не претендует на роль полноценного руководства или панацеи на все случаи жизни, посему все вопросы и пожелания по исправлению и дополнению статьи шлем на <shulik at ua.fm>.
Итак - приступим. В работе нам понадобится: FreeBSD 6.0-RELEASE (можно и другую :) ), mpd, freeradius, netams, mysql, поддержка в ядре или в виде модулей IPFW, IPDIVERT, PF, NETGRAPH. Рекомендуется включить поддержку этих компонентов в ядро. Поэтому открываем конфигурационный файл вашего ядра и добавляем, если не хватает, туда следующее:
options NETGRAPH # собственно поддержка netgraph options NETGRAPH_PPP # поддержка PPP в netgraph options NETGRAPH_PPTPGRE # поддержка gre-туннелирования options IPFIREWALL # включаем поддержку ipfw options IPFIREWALL_DEFAULT_TO_ACCEPT # политика по умолчанию - ACCEPT options IPDIVERT # включаем divert-сокеты (понадобится для статистики)
Что касается pf - то я оставил его в виде модуля. Если хотите включить в ядро - /sys/conf/NOTES вам поможет.
Теперь установим такие пакеты, как freeradius, mpd, mysql и netams. Коротко:
cd /usr/ports/net-mgmt/netams make clean make make install make clean
При сборке freeradius обязательно указываем поддержку mysql.
cd /usr/ports/databases/mysql50-server make clean make make install make clean cd /usr/ports/net/freeradius make clean make make install make clean cd /usr/ports/net/mpd make clean make make install make clean
Теперь необходимо отконфигурировать установленный софт. MySQL конфигурим по вкусу - задавая пароли рута и прослушку портов или ее отсутствие. Затем нужно настроить freeradius. Для начала идем в /usr/local/etc/raddb/ и переименовываем все файлики из ‘*.samples’ в ‘*’. Открываем radiusd.conf. В нем правим следующие настройки:
log_stripped_names=yes # Логируем атрибут User-Name если он найден в запросе log_auth=yes # Логируем запросы аутентификации log_auth_badpass=yes # Если пароль неверен - пишем его в Лог log_auth_goodpass=no # верные пароли незачем писать в лог
Проверяем чтобы была раскоментарена строчка:
$INCLUDE ${confdir}/sql.conf
В секции authorize комментарим
files
и удаляем комментарий с
sql
В секции accounting также снимаем комментарий с
sql
Далее смотрим в sql.conf и правим:
driver="rlm_sql_mysql" server="localhost" login="login_k_mysql" password="pass_k_mysql"
Осталось поправить clients.conf, задав для клиента 127.0.0.1 secret:
secret=MyOwnSecret
Кроме того нужно создать в mysql базу radius’a следующего содержания: таблица со словарями:
CREATE TABLE `dictionary` ( `id` int(10) NOT NULL AUTO_INCREMENT, `Type` varchar(30) DEFAULT NULL, `Attribute` varchar(64) DEFAULT NULL, `Value` varchar(64) DEFAULT NULL, `Format` varchar(20) DEFAULT NULL, `Vendor` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
таблица с клиентами самого радиуса (вместо нее пользуем файлы)
CREATE TABLE `nas` ( `id` int(10) NOT NULL AUTO_INCREMENT, `nasname` varchar(128) DEFAULT NULL, `shortname` varchar(32) DEFAULT NULL, `ipaddr` varchar(15) DEFAULT NULL, `type` varchar(30) DEFAULT NULL, `ports` int(5) DEFAULT NULL, `secret` varchar(60) DEFAULT NULL, `community` varchar(50) DEFAULT NULL, `snmp` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
таблица для клиентских сессий:
CREATE TABLE `radacct` ( `RadAcctId` bigint(21) NOT NULL AUTO_INCREMENT, `AcctSessionId` varchar(32) NOT NULL DEFAULT '', `AcctUniqueId` varchar(32) NOT NULL DEFAULT '', `UserName` varchar(64) NOT NULL DEFAULT '', `Realm` varchar(64) DEFAULT '', `NASIPAddress` varchar(15) NOT NULL DEFAULT '', `NASPortId` int(12) DEFAULT NULL, `NASPortType` varchar(32) DEFAULT NULL, `AcctStartTime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `AcctStopTime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `AcctSessionTime` int(12) DEFAULT NULL, `AcctAuthentic` varchar(32) DEFAULT NULL, `ConnectInfo_start` varchar(32) DEFAULT NULL, `ConnectInfo_stop` varchar(32) DEFAULT NULL, `AcctInputOctets` int(12) DEFAULT NULL, `AcctOutputOctets` int(12) DEFAULT NULL, `CalledStationId` varchar(10) NOT NULL DEFAULT '', `CallingStationId` varchar(10) NOT NULL DEFAULT '', `AcctTerminateCause` varchar(32) NOT NULL DEFAULT '', `ServiceType` varchar(32) DEFAULT NULL, `FramedProtocol` varchar(32) DEFAULT NULL, `FramedIPAddress` varchar(15) NOT NULL DEFAULT '', `AcctStartDelay` int(12) DEFAULT NULL, `AcctStopDelay` int(12) DEFAULT NULL, PRIMARY KEY (`RadAcctId`), KEY `UserName` (`UserName`), KEY `FramedIPAddress` (`FramedIPAddress`), KEY `AcctSessionId` (`AcctSessionId`), KEY `AcctUniqueId` (`AcctUniqueId`), KEY `AcctStartTime` (`AcctStartTime`), KEY `AcctStopTime` (`AcctStopTime`), KEY `NASIPAddress` (`NASIPAddress`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
таблица для проверяемых параметров:
CREATE TABLE `radcheck` ( `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `UserName` varchar(64) NOT NULL DEFAULT '', `Attribute` varchar(32) NOT NULL DEFAULT '', `op` char(2) NOT NULL DEFAULT '', `Value` varchar(253) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `UserName` (`UserName`(32)) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
таблица для проверяемых параметров групп:
CREATE TABLE `radgroupcheck` ( `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `GroupName` varchar(64) NOT NULL DEFAULT '', `Attribute` varchar(32) NOT NULL DEFAULT '', `op` char(2) NOT NULL DEFAULT '', `Value` varchar(253) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `GroupName` (`GroupName`(32)) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
таблица для возвращаемых параметров групп:
CREATE TABLE `radgroupreply` ( `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `GroupName` varchar(64) NOT NULL DEFAULT '', `Attribute` varchar(32) NOT NULL DEFAULT '', `op` char(2) NOT NULL DEFAULT '', `Value` varchar(253) NOT NULL DEFAULT '', `prio` int(10) UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `GroupName` (`GroupName`(32)) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
таблица для возвращаемых параметров:
CREATE TABLE `radreply` ( `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `UserName` varchar(64) NOT NULL DEFAULT '', `Attribute` varchar(32) NOT NULL DEFAULT '', `op` char(2) NOT NULL DEFAULT '', `Value` varchar(253) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `UserName` (`UserName`(32)) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
таблица соответствия пользователей и групп:
CREATE TABLE `usergroup` ( `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `UserName` varchar(64) NOT NULL DEFAULT '', `GroupName` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `UserName` (`UserName`(32)) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
И сразу же заведем тестового пользователя:
INSERT INTO `radcheck` VALUES (1,'user_test','Password','==','qwerty'); INSERT INTO `radreply` VALUES (1,'user_test','Framed-IP-Address',':=','192.168.1.3'); INSERT INTO `radreply` VALUES (1,'user_test','Framed-IP-Netmask',':=','255.255.255.255'); INSERT INTO `radreply` VALUES (1,'user_test','Framed-Protocol',':=','PPP');
По сути ААА готова к использованию. Добавляем в /etc/rc.conf строки:
radiusd_enable="YES" mysql_enable="YES"
и стартуем скриптами из /usr/local/etc/rc.d/
Проверим радиус на работоспособность:
radtest user_test qwerty 127.0.0.1 0 MyOwnSecret
В результате должны получить что-то вида:
Sending Access-Request of id 189 to 127.0.0.1:1812 User-Name = "user_test" User-Password = "qwerty" NAS-IP-Address = localhost NAS-Port = 0 rad_recv: Access-Accept packet from host 127.0.0.1:1812, id=189, length=38 Framed-IP-Address = 192.168.1.3 Framed-IP-Netmask = 255.255.255.255 Framed-Protocol = PPP
Это говорит о том, что радиус работает верно. Если нет - смотрим что он кричит в /var/log/radiusd.log
Пришло время настроить mpd. Смело идем в /usr/local/etc/mpd/ и убираем из имен конфигов samples. Открываем mpd.conf и пишем туда:
default: load pptp0 pptp0: new -i ng0 pptp0 pptp0 load pptp_all pptp_all: set ipcp ranges 192.168.1.1/32 10.0.0.0/24 set iface disable on-demand set bundle disable multilink set link yes acfcomp protocomp set link no pap chap set link enable chap set link keep-alive 60 180 set ipcp yes vjcomp set ipcp dns 192.168.1.1 # или любой другой доступный DNS # set bundle enable compression # обязательно проверяем, чтобы это было выключено. # иначе будем долго искать, почему пинги идут, но ничего не грузится set pptp enable incoming set pptp disable originate # если нужно - задаем ip для открытия 1723 порта. по умолчанию - все # set pptp self 172.16.101.2 set radius timeout 10 set radius config /usr/local/etc/mpd/radius.conf set radius retries 3 set bundle enable radius-acct set bundle enable radius-auth set ipcp yes radius-ip
Нужно заметить, что для примера создан всего лишь один pptp линк. В реале их нужно создать столько, сколько вам необходимо одновременных pptp-туннелей. Создаем mpd.links:
pptp0: set link type pptp
И radius.conf:
acct 127.0.0.1 MyOwnSecret auth 127.0.0.1 MyOwnSecret
Добавляем в rc.conf строку:
mpd_enable="YES"
и запускаем mpd.
Теперь нужно его проверить. Чтобы проще было вести отладку - добавим в /etc/syslog.conf:
!mpd *.* /var/log/mpd.log
И передергиваем syslog. Идем за клиентскую машинку и проверяем подключение по впн.
Если все прошло удачно - идем дальше, иначе ищем ошибку.
Далее нужно клиентов из внутренней сети с внутренними ip-адресами выпустить в инет. Для этого у нас есть несколько путей: воспользоваться связками ipfw+natd или ipfw+ng_nat, но мы пойдем по пути использования pf.
В /etc/rc.conf добавляем:
pf_enable="YES" pf_rules="/etc/pf.rules" pf_flags="" pflog_enable="YES" pflog_logfile="/var/log/pflog" pflog_flags=""
Файлик pf.rules:
ext_ip="19х.1хх.хх.хх" # внешний ip-адрес сервера int_if="rl1" # интерфейс смотрящий в локалку int_net_vpn="xx.yy.zz.ff" # внутренняя VPN-сеть local_net="..." # внутрення локалка int_ip="..." # внутренний ип в локалке nat on rl0 from $int_net_vpn to any -> $ext_ip # собсно NAT pass quick on lo0 all # разрешаем все на lo0 pass quick from $ext_ip to any # Разрешаем траф pass quick from any to $ext_ip # на внешнем ip pass quick from $int_net_vpn to any # разрешаем любой траф pass quick from any to $int_net_vpn # по впн-сети # блокируем платные сервисы из локалки block in quick on $int_if proto { tcp udp } from $local_net to $int_ip port { 25 110 143 3128 3306 } pass in quick on $int_if from $local_net to $int_ip block in all # все осальное блокируем по умолчанию
Все. Теперь трафик должен ходить в инет. Осталось организовать логирование трафика через netams. Приведу лишь коротки отрывки конфигов - так как на сайте netams.com есть хорошая документация на русском языке с примерами настройки netams. В rc.conf привычно добавляем:
firewall_enable="YES" firewall_script="/etc/ipfw.sh"
ipfw.sh:
#!/bin/sh fwcmd="/sbin/ipfw" ${fwcmd} -f flush ${fwcmd} -f flush pipe ${fwcmd} -f flush queue ${fwcmd} pipe 1 config bw 512Kbit/s queue 50 # для обжимки трафа пользуемся pipe'ами ${fwcmd} pipe 2 config bw 512Kbit/s queue 50 ${fwcmd} add pipe 1 ip from <vpn_net> to any in # вгоняем траф в пайпы ${fwcmd} add pipe 2 ip from any to <vpn_net> out ${fwcmd} add tee 100 ip from <vpn_net> to any in # копируем траф в divert-socket для netams. ${fwcmd} add tee 100 ip from any to <vpn_net> out
В netams для нас важны разделы:
service data-source 1 type ip-traffic source tee 100 service storage 1 type mysql user mysql_login # с правами создания БД, хотя бы на момент первого запуска password mysql_pass host localhost service monitor 1 monitor to storage 1 monitor unit VPN unit net name VPN ip <vpn-net> acct-policy ip
Теперь видимо все. Жду отзывы и пожелания на указанное выше мыло.
Copyright by Alexander Shulikov <shulik at ua.fm>
Хакеры: Приёмы взлома.
Обновлено: 16.01.2025Согласно недавнему опросу, проведённому в США, обычные граждане боятся хакеров больше, чем обычных преступников. Интересно, что высокие технологии входят в нашу жизнь очень быстро и становятся её неотъемлемой частью. Каждый день наполнен множеством взломов, которые, как правило, не бывают зарегистрированы. Атакуют всех - от обычных пользователей до крупных правительственных организаций. Цели бывают разные, машины обычных пользователей, как правило, становятся «зомби», с которых проводятся атаки или рассылается спам, а у организаций похищается информация.
Введение в Основные моменты взлома и защиты сервера.
Обновлено: 16.01.2025Человек, при любых обстоятельствах помни, что попытки (даже безуспешные) взлома чужих серверов - дело уголовно наказуемое. Даже если ты считаешь, что действуешь с благими намерениями (например, убедить кого-то, что ему следует обновиться). Все нижеизложенное приведено в целях ознакомления с тем, как могут действовать атакующие и от чего и как следует защищать инфраструктуру.
Источники бесперебойного питания. Общие понятия и термины
Обновлено: 16.01.2025Система бесперебойного питания (СБП)
Автоматическое устройство, обеспечивающее нормальное питание нагрузки при полном обесточивании внешней электросети в результате аварии или недопустимо высоком отклонении параметров сетевого напряжения от номинальных значений (см. "Неполадки в сети"). Различают два основных типа СБП: источники бесперебойного питания и генераторные установки.
Удаление стандартных общих ресурсов C$, ADMIN$, IPC$
Обновлено: 16.01.2025Ключевые слова: стандартные общие ресурсы, шары, C$, ADMIN$, IPC$, net share
Большинсто людей, работающих в локальных сетях, даже не подозревают о том, что можно обратиться к их диску C: и посмотреть их личные документы. Большинство людей считают: раз я не ставил общий доступ к ресурсам на моем компьютере, то другие не смогут туда залезть. Как бы не так :). Достаточно вам ввести к командной строке ("Пуск -> Выполнить -> cmd")
net share
чтобы убедиться, что у вашего компьютера есть такие «расшаренные» ресурсы, как c$, admin$, ipc$ и другие в этом же духе.
Если кто-либо в вашей сети обладает правами администратора, то он может запросто посмотреть ваш диск. Например, ваш компьютер в локальном домене организации носит имя “my_pc”. В таком случае для доступа к вашему диску C: достаточно ввести путь в проводнике “\my_pcc$”. Попробуйте сами сделать это. Если у вас есть права администратора, то введите подобную команду, заменив имя своего компьютра на имя компьютера соседа.
Для чего я все это рассказываю? Чтобы вы поняли, насколько вы «открыты» другим по-умолчанию. Конечно, это только один из аспектов вашей безопасности при работе в сети, но им тоже нельзя пренебрегать. Итак, как отключить все это безобразие?
Если удалить эти ресурсы через "Управление компьютером" -> "Общие папки", то после перезагрузки они появятся снова. Полностью отключить скрытые ресурсы можно только с помощью правки реестра. Откройте раздел
HKLMSYSTEMCurrentControlSetServicesLanmanServerParameters
Добавьте или измените следующие значения:
Операционная система | Параметр | Тип | Значение |
Windows 2000 Server | AutoShareServer | REG_DWORD | 0 |
Windows 2000 Professional | AutoShareWks | REG_DWORD | 0 |
Однако, этот метод не уберёт sharing с IPC$. Для того что бы полностью избавиться от всех административных шарингов, создайте BAT или CMD файлик следующего содержания, и вставьте его в автозагрузку.
net share c$ /delete
net share admin$ /delete
net share ipc$ /delete
Если в вашей системе есть дополнительный диск D:, то можно добавить такую строку:
net share d$ /delete
Последние команды можно записать в исполняемый bat-файл (например, del_share.bat) и запускать в автозагрузке или вручную.
Еще раз повторюсь – узнать, какие общие ресурсы открыты на вашем компьютере можно командой “net share” из командной строки windows либо через "Управление компьютером" -> "Общие папки".
Также есть утилита LanSafety, которая сама сделает за вас все это.
Вот и все. Заметочка вышла небольшая, но, надеюсь, полезная.
Как создать голосование на PHP
Обновлено: 16.01.2025Ниже приведена простая для понимания и реализации функция создания интернет-голосования на сайте.
Вызывается функция следующим образом:
// Создаем новую функцию С 3-мя параметрами:
// $name - название опроса;
// $variants - массив вариантов ответа;
// $dir - имя директории для файлов опроса (папка создается скриптом).
function voting($name, $variants, $dir){
GLOBAL $REQUEST_URI;
// Если пользователь уже проголосовал
if(isset($_POST['vote'])){
// Массив ошибок
$errors = array();
// Массив содержимого директории
$indir = array();
$dh = opendir('./');
while($file = readdir($dh)){
if($file == '.' || $file == '..') continue;
$indir[] = $file;
}
closedir($dh);
// --------------------------------------------------------
// Если у вас PHP5, вместо получения содержимого дериктории
// вручную, вы можете воспльзоваться функцией scandir():
// $indir = scandir('./');
// --------------------------------------------------------
// Если директория для файлов опроса отсутсвует,
// Значит еще никто не голосовал, следовательно,
// Нужно создать эту директорию и необходимые файлы,
// Чтобы избежать ошибки
if(in_array($dir, $indir) == FALSE){
mkdir($dir, 0770);
$cr1 = fopen($dir.'/base.txt', 'w');
for($i=0; $i<sizeof($variants); $i++){
fputs($cr1, '0');
}
fclose($cr1);
$cr2 = fopen($dir.'/ip_list.txt', 'w');
fclose($cr2);
}
// Понеслась... Проверяем, голосовал ли пользователь
// Массив уже проголосовавших IP-адресов
$ip_list = file($dir.'/ip_list.txt');
// Если IP юзера совпал с одним из базы, значит юзер
// Уже голосовал
if(in_array($_SERVER['REMOTE_ADDR']." ", $ip_list) == TRUE){
$errors[] = "Вы уже голосовали!";
}else{
$answers = file($dir."/base.txt");
$fh = fopen($dir."/base.txt", "w");
flock($fh, LOCK_EX);
for($i=0; $i<sizeof($answers); $i++){
// Удаляем переводы строк
$answers[$i] = trim($answers[$i]);
// Пишем
if($i == $_POST['vote']){
fputs($fh, ($answers[$i]+1)." ");
}else{
fputs($fh, $answers[$i]." ");
}
}
flock($fh, LOCK_UN);
fclose($fh);
// Записываем IP юзера в базу проголосовавших
$fb = fopen($dir."/ip_list.txt", "a+");
fputs($fb, $_SERVER['REMOTE_ADDR']." ");
fclose($fb);
}
if(isset($errors[0])){
echo "Ваш голос не был учтен, т.к. были обнаружены следующие ошибки: <br>";
for($i=0; $i<sizeof($errors); $i++) echo $errors[$i]."<br>";
}else{
echo "Ваш голос учтен! Результаты:<br>";
// Обновляем базу
$answers = file($dir."/base.txt");
// Выводим результаты, и заодно считаем общее количество
// Для этого складываем существующие голоса
$summ = 0;
for($i=0; $i<sizeof($answers); $i++){
echo $variants[$i].": ".$answers[$i]."<br>";
// Пересчет голосов
$summ += $answers[$i];
}
// Вывод общего количества голосов
echo "Всего: ".$summ;
}
}else{
echo "<b>".$name."</b><br>";
echo "<form action=./ method=POST>";
for($i=0; $i<sizeof($variants); $i++){
echo "<input type=radio name=vote value=".$i;
if($i==0) echo " checked";
echo ">".$variants[$i]."<br>";
}
echo "<input type=submit value=Голосовать>";
echo "</form>";
}
}
?>
Вызывается функция следующим образом:
voting("Выбираем самый популярный антивирус", Array("Kaspersky", "Eset NOD32", "Symantec", "DrWeb", "McAfee"), "vote2");
?>
Ну вот и все.
Автор: Mostom, http://cngroup.ru/
Сессии. Скрипт авторизации. Часть 2
Обновлено: 16.01.2025Принцип защиты, описанный в предыдущей статье, первоначально был написан без использования баз данных mysql. Первоначально в скрипте использовались файлы для хранения паролей, поэтому, переходя на базы данных я упустил возможность mysql injection. Что же, постараюсь в этой статье описать, как защититься от этой самой "инъекции". Кроме того, в статье будет описано, как решить те самые проблемы, что были обозначены в первой части.
Итак, обеспечиваем безопасную запись в базу. Самый простой способ - использование функции addslashes() - она отменит значение символов, которые могут вызывать проблемы при сохранении в базе данных. Чтобы вернуть данные в исходный вид нужно воспользоваться функцией stripslashes().
Вот таким образом это можно сделать в нашем случае:
function check()
{
if (empty($pass) || empty($name)) error("Не указан логин или пароль");
//-------------начало нового кода
$name = addslashes($name);
//-------------конец нового кода
$sql="select * from my_site where login='".$name."'";
if ($show=mysql_query($sql))
{
$a=mysql_fetch_array($show);
if ($a['pass'] != $pass) error("Неверное сочетание логин - пароль");
else session_register("name","pass");
}
else error("Ошибка запроса к базе данных");
}
?>
Второй способ - использование регулярных выражений. С помощью регулярных выражений вы уже сами можете решать - какие символы допускать, а какие нет.
Про регулярные выражения написано уже множество материала, поэтому опустим этот вопрос.
Еще один способ - использовать специальные функции, которые мнемонизируют (делают удобоваримой для mysql_query) строку. Это mysql_real_escape_string() и mysql_escape_string(). Только помните, что они не мнемонизирует символы % и _. Пример использования:
Любой из этих способов защитит вас от mysql injection.
Идем дальше
Для более мощной защиты захешируем наши пароли.
Для этого будем использовать функцию crypt().
"Захешируем" - это значит превратим наш пароль в псевдослучайную строку, причем, такую, что ее нельзя перевести обратно.
"Если нельзя расшифровать пароль, то зачем нужна такая шифровка?", спросите вы. Отвечу. Если скормить нашей функции два одинаковых слова, то на выходе будет два абсолютно одинаковых значения. Теперь понятно как важна эта функция? Мы не можем расшифровать пароль и сравнить его с вводимым, но мы можем зашифровать вводимую строку и сравнить ее с хранящейся в базе.
Т.к. пароль нельзя расшифровать, то даже если злоумышленник каким-то образом просмотрит содержимое нашей таблицы с паролями, то он увидит только захешированные строки, из которых вытянуть настоящие пароли будет крайне затруднительно. Алгоритм шифрования зависит от операционной системы.
Первый аргумент функции - это строка, которую нужно зашифровать. Второй - необязателен, если он не предоставлен, то будет автоматически сгенерирован PHP. Но если вы его не укажите, то на одно и тоже слова функция будет выдавать вам разный результат, поэтому нам все же придется задавать и второй аргумент. Пример использования:
Таким образом, мы хешируем вводимый пользователем пароль и сверяем полученный результат с табличным значением (а там хранятся захешированные с тем же вторым аргументом пароли), если значения совпадают, значит впускаем пользователя.
Идем еще дальше
Решим те задачи, что поставили в предыдущей статье, а именно:
- Возможность входить на сайт под разными именами.
- "Полный выход" т.е. нельзя снова зайти на сайт не авторизировавшись.
В принципе, эти две задачи сводятся к одной. И решение ее очень простое - удаление сессии. Сделаем ссылку "выход", которая будет перебрасывать нас на страницу с формой авторизации (index.php) и удалять нашу сессию.
Вот ссылка:
А этот код разместим на index.php (в самом верху страницы):
session_start();
if (!empty($exit)) session_destroy();
?>
Если определена переменная $exit, то функция session_destroy() уничтожает сессию.
Как я и обещал в прошлой статье, я расскажу, как сделать функцию вывода ошибки, да не простую, а с записью в лог и отправкой отчета об ошибке по почте.
Итак, вот код:
{
Global $name, $pass;
//объявляем глобальные переменные.
$time = date("H:i:s");
//присваиваем переменной $time значение времени.
$ip = $_SERVER['REMOTE_ADDR'];
//в переменную $ip записываем ip пользователя
$logFileName = "logfile.log";
//файл, где будут храниться записи об ошибках
$fp = fopen($logFileName,'a');
//открываем файл с атрибутом на запись в конец файла (атрибут "а")
if (empty($name)) $name="empty";
if (empty($pass)) $pass="empty";
// если имя и пароль не заданы, то присваиваем переменным значения empty.
$message = $time." Ошибка: ".$er." Имя: ".$name." Пароль: ".$pass." ip: ".$ip." Где : ".$_SERVER['PHP_SELF']."n";
//составляем отчет
fputs($fp, $message);
//производим запись в файл
fclose($fp);
//закрываем файл
mail("email@mail.ru", "Error", $message, "From: error_center");
//отправляем отчет по мылу
echo '<center><strong>Ошибка:</strong></center> '.$er;
//выводим сообщение об ошибке пользователю
exit(0)
}
Теперь, когда произойдет ошибка, пользователю откроется страница с сообщением "Ошибка: описание_ошибки", а вам на ящик придет подробный отчет об ошибке, этот же отчет запишется в лог файл.
Теперь сделаем блокировку подбора пароля. Для этого изменим немного функцию error():
{
Global $name, $pass;
//объявляем глобальные переменные.
$time = date("H:i:s");
//присваиваем переменной $time значение времени.
$ip = $_SERVER['REMOTE_ADDR'];
//в переменную $ip записываем ip пользователя
$logFileName = "logfile.log";
//файл, где будут храниться записи об ошибках
$fp = fopen($logFileName,'a');
//открываем файл с атрибутом на запись в конец файла (атрибут "а")
if (empty($name)) $name="empty";
if (empty($pass)) $pass="empty";
// если имя и пароль не заданы, то присваиваем переменным значения empty.
$message = $time." Ошибка: ".$er." Имя: ".$name." Пароль: ".$pass." ip: ".$ip." Где : ".$_SERVER['PHP_SELF']."n";
//составляем отчет
fputs($fp, $message);
//производим запись в файл
fclose($fp);
//закрываем файл
mail("email@mail.ru", "Error", $message, "From: error_center");
//отправляем отчет по мылу
echo '<center><strong>Ошибка:</strong></center> '.$er;
//начало нового кода
if (empty($attempt)) $attempt=1;
else $attempt++;
session_register("attempt");
//конец нового кода
//выводим сообщение об ошибке пользователю
exit(0)
}
Немного поясню новый код: переменная $attempt является счетчиком количеств неудачных попыток попасть на сайт. Каждый раз, когда вызывается функция error(), значение переменной увеличивается на 1. Теперь подправим функцию check():
function check()
{
//начало нового кода
Global $attempt;
$attempt= intval($attempt);
if ($attempt==3)
exit();
//конец нового кода
if (empty($pass) || empty($name)) error("Не указан логин или пароль");
$pass=crypt($pass, "ph");
$sql="select * from my_site where login='".$name."'";
if ($show=mysql_query($sql))
{
$a=mysql_fetch_array($show);
if ($a['pass'] != $pass) error("Неверное сочетание логин - пароль");
else session_register("name","pass");
}
else error("Ошибка запроса к базе данных");
}
?>
Поясню новый код: функция intval получает целочисленное значение переменной, далее если количество попыток равно 3, то мы ничего больше не показываем, на какую бы страницу не заходил пользователь. Эта блокировка будет действовать до тех пор, пока не умрет сессия.
Так же можно внести блокировку и на index.php там мы напишем тоже самое:
$attempt = intval($attempt);
if ($attempt == 3)
exit();
?>
Вот и все. Теперь вряд ли кто-нибудь сможет получить доступ к вашему ресурсу без разрешения.
Автор: Александр, http://cngroup.ru/
Сессии. Скрипт авторизации. Часть 1
Обновлено: 16.01.2025Господа, небольшое объявление: эта статья написана для неопытных веб-программистов. Это не супер-пупер, а просто работающее руководство к действию. Также просматривайте комментарии внизу статьи - они с 2008 года. За этот срок много что поменялось, но основа все равно работает. Обращайте на это внимание!
В статье я опишу, как создать небольшой, но действенный скрипт авторизации, который не позволит непрошенным личностям совать свой длинный нос на ваш сайт. Данный пример не претендует на звание идеального метода защиты, но все же обойти его будет достаточно трудно (я, по крайней мере, не знаю, как это сделать). Представим следующее: у вас есть закрытый сайт "для своих" и вы не хотите, чтобы туда заходили всякие нехорошие личности. В этом нам помогут сессии. Пароли и логины будем хранить в таблице mysql.
На первой странице (index.php) делаем форму для ввода данных:
Думаю здесь пояснять ничего не нужно. Идем дальше:
Сначала краткое пояснение. Мы создаем функцию проверки пользователя check(). У нас есть база данных my_site, содержащая поля id, name и pass, где name и pass - логин и пароль пользователя соответственно. Функция пропускает только тех, кто указал верный логин и пароль в форме ($name, $pass) на входе, потом эти переменные сохраняются в сессию, следовательно, пока сессия жива пользователь может заходить на ваш сайт без авторизации. Даже указав другой верный логин и пароль, он будет авторизирован под указанным ранее логином.
Предполагается, что соединение с базой mysql уже произведено и база данных выбрана.
function check()
{
if (empty($pass) || empty($name)) error("Не указан логин или пароль");
$sql="select * from my_site where login='".$name."'";
if ($show=mysql_query($sql))
{
$a=mysql_fetch_array($show);
if ($a['pass'] != $pass) error("Неверное сочетание логин - пароль");
else session_register("name","pass");
}
else error("Ошибка запроса к базе данных");
}
?>
Начнем пояснения с первой строки.
Проверяем, задан ли пароль и логин. Если одно из полей пустое, то
Здесь error - это любая функция вывода ошибки. В следующей статье я подробно затрону эту тему (вывод ошибки, запись ее в лог, отправка отчета об ошибке и т.д.), а пока мы можем обойтись простой функцией:
function error($er)
{
echo $er;
exit(0);
}
?>
Если поля не пусты, то скрипт продолжает работу.
Пишем запрос к базе mysql, который вернет нам значение поля pass, строки, где login=$name.
Если запрос обработан без ошибок, то
Присваиваем переменной $а (ассоциативный массив) значение поля pass
Если поле pass не соответствует тому, что ввел пользователь или тому, что хранится в сессии, то мы опять через функцию error выводим ему ошибку.
Иначе записываем переменные $name и $pass в сессию и продолжаем выполнять скрипт.
else error("Ошибка запроса к базе данных");
}
Если есть ошибка в запросе к базе mysql, то мы опять же выводим ошибку пользователю.
Итак, вначале каждой страницы, к которой нужно запретить доступ посторонним пишем:
session_start();
// эта функция, открывающая или продолжающая работу с сессиями. Она должна
//быть задана в самом начале страницы до отправки заголовка страницы.
check();
//наша функция проверки.
?>
Теперь проверим логику скрипта. Если человек не заполняет одно из полей и нажимает кнопку "вход", то перейдя на страницу index2.php он получит надпись "Ошибка: Не указан логин или пароль".
Если он указывает неверный логин или пароль, то ему выпадает "Ошибка: Не верное сочетание логин - пароль".
Если же он все указывает верно, то попадет на страницу index2.php, где вы храните свои тайны. За счет того, что логин и пароль записываются в сессию, юзер может бродить по вашему сверх тайному сайту авторизировавшись только один раз.
Возможные проблемы:
Нельзя заходить на сайт под разными именами.
Если человек раз зашел на сайт, то с его компьютера можно посещать этот сайт еще в течении некоторого времени (пока жива сессия), что не очень хорошо, если ты работаешь из Интернет салона.
Решение этих проблем будет описано в следующих статьях. Также будут затронуты вопросы: система обнаружения атак и попыток подбора пароля, с отправкой отчета по почте, функция вывода ошибок, с записью в лог файл.
Автор: Александр, http://cngroup.ru/
Парсер web страниц
Обновлено: 16.01.2025При написании достаточно большого сайта с единым дизайном появляется желание отделить дизайн от содержимого страниц. Как правило над крупным проектом работает несколько человек, минимум двое: программист и дизайнер. Дело программиста - программировать, а дизайнера - работать над дизайном. Как нетрудно заметить, дизайн состоит из нескольких файлов, к тому же в дизайн приходится включать код PHP. Естественно дизайнеру будет неудобно проводить изменение дизайна, а если он не разбирается в PHP, то может что-то испортить. Для программиста возникают аналогичные трудности при написании скриптов - в *.php файлах приходится держать все содержимое страницы.
Решить данную проблему, то есть полностью отделить код от дизайна, позволяет так называемый парсер. Парсер - это некоторый скрипт, который берет готовый дизайн и в специальным способом помеченные места подставляет некоторые значения. Сейчас мы напишем простейший парсер. Его я писал, когда у меня возникло желание сделать на своем сайте несколько разных дизайнов, причем каждый пользователь мог выбрать тот дизайн, который ему больше нравился.
Итак, приступим. Нам необходимо, чтобы на машине (или там где вы собираетесь размещать сайт) были установлены и правильно настроены веб-сервер (например Apache) и интерпритатор языка PHP.
Создайте в директории сайта следующие папки:
- site_funcs - здесь мы будем размещать общие модули сайта
- designs - здесь мы будем держать дизайны
В папке designs создайте файл design.htm будущего дизайна со следующим содержанием:
<title>Наш сайт :: {*title*}</title>
</head><body>
<table cellspacing="0" border="1" cellpadding="1" width="100%" height="100%">
<tr><td height="70" colspan="3">Тут всякие картинки будут</td></tr>
<tr><td height="20" align="center" colspan="3">{*menu*}</td></tr>
<tr valign="top">
<td width="160" align="center">{*left*}</td>
<td style="color:#888888; font-size: 8pt; font-weight: bold;">
::: {*title*}<br><br>{*main*}<br><br></td>
<td width="160" align="center">{*right*}</td>
</tr>
</table>
</body></html>
Наш парсер будет заменять выражения {*имя_переменной*} на некоторые значения, которые будут определяться во время выполнения скрипта.
Теперь приступим к написанию собственно парсера. Создайте в папке site_funcs файл parse_funcs.php следующего содержания:
$Designs=array(); // Элементы подстановки в дизайн
$DesignOut=''; // То, что будем выводить
function LoadDesign ($CurDesign='design.htm'){ // Загружаем выбранный дизайн
global $DesignOut;
//echo 'Start design loading';
$FN='designs/'.$CurDesign;
if (!file_exists($FN)) return false;
$DesignOut=join('',File($FN));
//echo 'Design loaded';
return true;
}
function Parse ($n=1){ // Выполняет глобальную замену переменных {*...*} в дизайне на их значения, парсер одним словом. При необходимости делает замену нужное число раз (Если передать 0, то ничего не делает :) )
global $Designs, $DesignOut;
//echo 'Start parse';
for ($i=0;$i<$n;$i++){
foreach($Designs as $k=>$v){
$DesignOut=str_replace("{*$k*}","$v",$DesignOut);
}
}
}
?>
Разберем работу данного скрипта. Сначала нам понадобятся несколько переменных. В переменной $DesignOut мы будем держать содержимое нашего файла дизайна, а в массиве $Designs будем держать имена переменных, используемых в файле дизайна и их значения. Имена переменных используются в качестве ключей массива, а в значениях массива хранятся значения наших переменных.
Функция LoadDesign загружает в переменную $DesignOut содержимое файла дизайна, по умолчанию это design.htm, но при вызове функции можно указать любой другой.
Теперь самое интересное, функция Parse. Сердце функции состоит всего из одного цикла:
$DesignOut=str_replace("{*$k*}","$v",$DesignOut);
}
Этот цикл заменяет все вхождения переменных {*имя_переменной*} в строке $DesignOut на их значения из массива $Designs, причем имя_переменной это один из ключей массива $Designs. В самой функции есть еще один цикл, который выполняет описанный выше цикл n раз. Это нужно в том случае, если мы захотим в одной из переменных написать "Некоторый текст: {*text*}", а потом на место {*text*} вставить что-то еще. В этом случае нам надо сказать парсеру, чтобы он выполнил подстановку два раза.
Теперь нам надо написать простенький интерфейс для работы с парсером. Интерфейс будет состоять из набора функций, которые будут передавать значение в массив $Designs. Создайте в папке site_funcs файл design_funcs.php следующего содержания:
$CurDes='main'; // Указатель дизайна по умолчанию
function With ($newdes){ // Выбирает дизайн по умолчанию
global$CurDes;
$CurDes=$newdes;
}
function PrClr ($text){ // Пишет в дизайн по умолчанию, предварительно очистив его
global $CurDes,$Designs;
$Designs[$CurDes]=$text;
}
function Pr ($text){ // Пишет в дизайн по умолчанию
global $CurDes,$Designs;
@$Designs[$CurDes].=$text;
}
function PrToClr ($des,$text){ // Пишет в указанный дизайн, предварительно очистив его
global$Designs;
$Designs[$des]=$text;
}
function PrTo ($des,$text){ // Пишет в указанный дизайн
global $Designs;
@$Designs[$des].=$text;
}
function Clr ($des){ // Очищает указанный дизайн
global $Designs;
$Designs[$des]='';
}
?>
В принципе в этом модуле нет ничего хитрого, это всего лишь набор функций облегчающих работу. Все это нужно, чтобы писать не $Designs['main'].='некоторый текст', а просто PrTo('main', 'некоторый текст').
Если мы в элемент дизайна ничего выводить не собираемся, то его нужно просто очистить, т.е. записать в него пустую строку. Если этого не сделать, то парсер эту переменную не заменит и в результате у вас на страничке выскочит {*переменная*}. Функция Clr() как раз и очищает дизайн. Функции PrTo() и PrToClr() пишут в указанный дизайн, отличие в том, что первая просто дописывает текст к тому, что есть, а вторая предварительно очищает выбранный дизайн. Pr() и PrClr() работают аналогично двум последним, с тем лишь отличием, что они пишут в предварительно выбранный элемент дизайна, который можно выбрать с помощью функции With(), в нашем случае по умолчанию это main.
Теперь можно приступить к созданию первой странички. В папке сайта создайте файл index.php следующего содержания:
require_once 'site_funcs/parse_funcs.php';
require_once 'site_funcs/design_funcs.php';
LoadDesign();
PrToClr('menu','Меню, которого нет');
PrToClr('left','Левая часть');
Clr('right'); // В right ничего не пишем
PrToClr('title','Главная страничка');
PrToClr('text','некоторый текст');
Pr('Текст нашей страницы, который содержит в себе подтекст "{*text*}"');
With('left'); //Теперь функция Pr() будет писать в left
Pr('Пишем что-нибудь еще в left');
Parse(2);
echo $DesignOut;
?>
Как это работает думаю пояснять не стоит. Это примитивный пример. Поэкспериментируйте со значением, передаваемым парсеру. По хорошему первые 6 строк скрипта нужно отделить в отдельный файл инициализации, ведь по сути там ничего не меняется, и писать в каждом скрипте все это неразумно. Последние 2 строки нужно поместить в отдельную функцию, которая будет строить дизайн по частям, выводить его и завершать работу скрипта, у нас это будет DoDesign(). Эту функцию тоже не помешало бы положить в отдельный модуль, например в тот же parse_funcs.php. Таким образом последний файл примет вид:
require 'init.php';
PrToClr('title','Главная страничка');
PrToClr('text','некоторый текст');
Pr('Текст нашей страницы, который содержит в себе подтекст "{*text*}"');
With('left'); //Теперь функция Pr() будет писать в left
Pr('Пишем что-нибудь еще в left');
DoDesign(2);
?>
Файл init.php будет содержать в себе:
require_once 'site_funcs/parse_funcs.php';
require_once 'site_funcs/design_funcs.php';
LoadDesign();
PrToClr('menu','Меню, которого нет');
PrToClr('left','Левая часть');
Clr('right'); // В right ничего не пишем
?>
Функция DoDesign будет выглядеть примерно так
Parse($n);
echo $DesignOut;
exit;
}
Вот и все. Для полного разделения содержимого сайта, программного кода и дизайна нужно будет написать функцию загрузки содержимого из некоторого внешнего файла и вывода его в дизайн.
Автор: Gorinich, http://cngroup.ru/
Обжим витой пары
Обновлено: 16.01.2025На рисунке представлены два варианта обжима витой пары - прямое соединение двух компьютеров через сетевые платы (первая часть рисунка), и соединение компьютера и свитча (switch) (на второй, нижней, части рисунка). Соединение компьютера и свитча характерно для локальных сетей офисов, когда несколько компьютеров подключаются к общему коммутатору - свитчу (иначе switch).
Последние комментарии
Популярно:
Разделы статей:
Подскажите. подключение с ПК все работает все ок. Делал по вашей мурзилке.
Но при подключе...