Записи с меткой «nginx»

ЭВМ/ Как написать эксплойт для nginx

29.09.2010

В конце прошлого года стало известно сразу о нескольких серьезных уязвимостях в популярном HTTP прокси-сервере nginx. Одна из них теоретически позволяла удаленно выполнить произвольный код на машине с работающим сервером. Насколько мне известно, только недавно был опубликован эксплойт, демонстрирующий возможность выполнения кода, но для его работы требуется нестандартная конфигурация. Поэтому я решил поделиться своими изысканиями на эту тему. Времени с тех пор прошло уже достаточно, поэтому вреда от подобной статьи не будет. Тем более что в ней будут продемонстрированы некоторые интересные подходы. (далее…)

, ,

ЭВМ/ Мирная ртуть

17.05.2010

Поднял на днях для личных нужд сервер с Mercurial репозиториями. С авторизацией и шифрованием, как и положено личным нуждам. Почему не Git? Да кто его знает. Git’а мне и на работе хватает, кроме того, уж очень инопланетянский у него интерфейс. Mercurial в этом плане мне более симпатичен. А почему не CVS/SVN? Мне нравится возможность иметь локально репозиторий со всей историей. Кроме того в DVCS работа с ветками сделана поприятнее.

В общем-то в процессе поднимания ничего особенного нет, вся информация доступна в Интернете. Но для интересующихся приведу последовательность команд с краткими комментариями. Установка происходит на Debian Lenny с nginx и без Apache.

Ставим пакеты mercurial и apache2-utils. Последний понадобится для создания файла с паролями для авторизации.

apt-get install mercurial apache2-utils

Создаем пользователя, из под которого будет работать сервер.

useradd -c "Mercurial Server" -d /var/hg -r -s /bin/false -U hg

Создаем структуру директорий.

mkdir -p /var/hg/{conf,logs,repos}

Ставим правильные права.

chown hg:hg /var/hg/{logs,repos}
chmod o-rwx /var/hg/{logs,repos}

Должно получиться как-то так:

# ls -l /var/hg
total 12
drwxr-xr-x 2 root root 4096 2010-05-14 15:53 conf
drwxr-x--- 2 hg   hg   4096 2010-05-14 16:19 logs
drwxr-x--- 3 hg   hg   4096 2010-05-14 16:14 repos

Создаем пробный репозиторий.

su -s /usr/bin/hg hg init /var/hg/repos/test

Проверяем.

# ls -l /var/hg/repos
total 4
drwxr-xr-x 3 hg hg 4096 2010-05-17 10:06 test

Теперь создаем файл /var/hg/conf/webdir.conf следующего содержания:

[collections]
/var/hg/repos = /var/hg/repos

Такой конфиг позволяет не описывать репозитории по одному, а указать все сразу в виде коллекции. Еще нам понадобится файл /var/hg/conf/hgrc, который мы будем копировать в каждый новый репозиторий. В этом файле отключается встроенный в Mercurial SSL (потому что мы будем использовать SSL в nginx) и разрешается всем push (потому что мы будем использовать авторизацию в nginx).

# cat /var/hg/conf/hgrc
[web]
push_ssl = false
allow_push = *
# cp /var/hg/conf/hgrc /var/hg/repos/test/.hg/

Все готово для запуска сервера. Чтобы было совсем хорошо, создаем скрипт /etc/init.d/hg-serve с таким содержимым:

#!/bin/sh
### BEGIN INIT INFO
# Provides:          hg-serve
# Required-Start:    $remote_fs
# Required-Stop:     $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Mercurial server
### END INIT INFO

PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Mercurial server"
NAME=hg
HOME=/var/$NAME
DAEMON=/usr/bin/$NAME
PIDFILE=$HOME/logs/$NAME.pid
DAEMON_ARGS="serve -d -A $HOME/logs/access.log -E $HOME/logs/error.log \
	     -p 9001 -a 127.0.0.1 --webdir-conf $HOME/conf/webdir.conf \
	     --pid-file $PIDFILE"
SCRIPTNAME=/etc/init.d/$NAME

[ -x "$DAEMON" ] || exit 0
. /lib/init/vars.sh
. /lib/lsb/init-functions

do_start()
{
	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
		--test > /dev/null || return 1
	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
		--chuid hg:hg -- $DAEMON_ARGS || return 2
}

do_stop()
{
	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 \
		--pidfile $PIDFILE --name $NAME
	RETVAL="$?"
	[ "$RETVAL" = 2 ] && return 2
	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 \
		--exec $DAEMON
	[ "$?" = 2 ] && return 2
	rm -f $PIDFILE
	return "$RETVAL"
}

case "$1" in
  start)
	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
	do_start
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  stop)
	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
	do_stop
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  restart|force-reload)
	log_daemon_msg "Restarting $DESC" "$NAME"
	do_stop
	case "$?" in
	  0|1)
		do_start
		case "$?" in
			0) log_end_msg 0 ;;
			1) log_end_msg 1 ;; # Old process is still running
			*) log_end_msg 1 ;; # Failed to start
		esac
		;;
	  *)
	  	# Failed to stop
		log_end_msg 1
		;;
	esac
	;;
  *)
	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
	exit 3
	;;
esac

:

Добавляем в автозагрузку.

update-rc.d hg-serve defaults

И запускаем.

/etc/init.d/hg-serve start

Проверяем, что все работает.

# hg clone http://localhost:9001/test
destination directory: test
no changes found
updating working directory
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
# cd test
# touch test
# hg add test
# hg commit -m test
No username found, using 'root@example.com' instead
# hg push
pushing to http://localhost:9001/test
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
# cd .. && rm -rf test

Теперь настраиваем nginx. Сначала заводим пользователя с паролем для авторизации.

htpasswd -c /var/hg/conf/htpasswd user

Далее создаем самоподписанный (а для личных нужд другого и не требуется) SSL сертификат. Привожу только команды, подробнее о процессе можно почитать, например, тут.

cd /var/hg/conf
openssl genrsa -des3 -out server.key 1024
openssl req -new -key server.key -out server.csr
cp server.key server.key.org
openssl rsa -in server.key.org -out server.key
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

Исправляем права для секретных файлов.

chgrp www-data /var/hg/conf/{htpasswd,server.*}
chmod o-rwx /var/hg/conf/{htpasswd,server.*}

Проверяем.

# ls -l /var/hg/conf
total 28
-rw-r--r-- 1 root root       38 2010-05-14 15:41 hgrc
-rw-r----- 1 root www-data   19 2010-05-17 10:30 htpasswd
-rw-r----- 1 root www-data 1001 2010-05-14 15:45 server.crt
-rw-r----- 1 root www-data  725 2010-05-14 15:45 server.csr
-rw-r----- 1 root www-data  891 2010-05-14 15:45 server.key
-rw-r----- 1 root www-data  963 2010-05-14 15:45 server.key.org
-rw-r--r-- 1 root root       44 2010-05-14 15:53 webdir.conf

Создаем конфиг для nginx /etc/nginx/sites-available/hg.example.com:

server {
	listen 443;
	server_name hg.example.com;
	access_log /var/log/nginx/hg.example.com.access.log;

	ssl on;
	ssl_certificate /var/hg/conf/server.crt;
	ssl_certificate_key /var/hg/conf/server.key;

	ssl_session_timeout 5m;

	ssl_protocols SSLv2 SSLv3 TLSv1;
	ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
	ssl_prefer_server_ciphers on;

	location / {
		auth_basic "Mercurial Repositories";
		auth_basic_user_file /var/hg/conf/htpasswd;

		proxy_pass http://localhost:9001;
	}
}

Включаем конфиг и перезапускаем nginx.

cd /etc/nginx/sites-enabled
ln -s /etc/nginx/sites-available/hg.example.com
/etc/init.d/nginx restart

Финальная проверка с удаленной машины.

$ hg clone https://hg.example.com/test
http authorization required
realm: Mercurial Repositories
user: user
password:
destination directory: test
requesting all changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
updating working directory
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cd test
$ ls
test
$ echo >>test
$ hg commit -m test
$ hg push
http authorization required
realm: Mercurial Repositories
user: user
password:
pushing to https://hg.example.com/test
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
,

ЭВМ/ непорядок v6

12.05.2010

Рад сообщить, что «непорядок» теперь доступен по IPv6. Те, кто используют IPv6, увидят маленький синий значок «v6» рядом с названием сайта.

К сожалению Linode не предоставляет собственной IPv6 связности, но дает подробные инструкции, как получить оную через туннель от Hurricane Electric, он же Tunnel Broker. Ширина полосы в бесплатном туннеле конечно регулируется, но на времени отклика это никак не сказывается. Одна из точек присутствия Hurricane Electric находится в Лондоне, там же расположен ДЦ Linode, где у меня VPS. Поэтому вход в туннель оказывается очень близко:

li153-251:~# ip link show 6in4
11: 6in4@NONE:
 mtu 1280 qdisc noqueue state UNKNOWN
    link/sit 0.0.0.0 peer 216.66.80.26
li153-251:~# traceroute 216.66.80.26
traceroute to 216.66.80.26 (216.66.80.26), 30 hops max, 40 byte packets
 1  109.74.192.2 (109.74.192.2)  0.437 ms  0.462 ms  0.526 ms
 2  te3-1-border76-01.lon2.telecity.net (217.20.44.217)  0.744 ms * *
 3  217.20.44.194 (217.20.44.194)  0.687 ms * *
 4  10gigabitethernet1-1.core1.lon1.he.net (195.66.224.21)  8.024 ms  7.974 ms  7.829 ms
 5  tserv5.lon1.ipv6.he.net (216.66.80.26)  0.938 ms  0.779 ms  0.812 ms
li153-251:~# ping -c3 216.66.80.26
PING 216.66.80.26 (216.66.80.26) 56(84) bytes of data.
64 bytes from 216.66.80.26: icmp_seq=1 ttl=60 time=1.02 ms
64 bytes from 216.66.80.26: icmp_seq=2 ttl=60 time=3.39 ms
64 bytes from 216.66.80.26: icmp_seq=3 ttl=60 time=2.98 ms

--- 216.66.80.26 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 1.021/2.466/3.398/1.036 ms

Непосредственная связность с внешним миром тоже очень хорошая, наличие туннеля совсем незаметно:

li153-251:~# ping6 -c3 ipv6.google.com
PING ipv6.google.com(2a00:1450:8006::68) 56 data bytes
64 bytes from 2a00:1450:8006::68: icmp_seq=1 ttl=57 time=8.25 ms
64 bytes from 2a00:1450:8006::68: icmp_seq=2 ttl=57 time=8.35 ms
64 bytes from 2a00:1450:8006::68: icmp_seq=3 ttl=57 time=8.75 ms

--- ipv6.google.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 8.259/8.458/8.758/0.240 ms

Для того, чтобы nginx начал обрабатывать IPv6 запросы, пришлось обновить его до версии 0.7.65. В стандартном репозитории Debian Lenny есть только версия 0.6.32, поэтому пришлось подключить репозиторий Backports и ставить из него.

Еще один интересный момент связан с конфигурацией. nginx по умолчанию открывает порт только для IPv4 соединений, это задает директива listen в файле /etc/nginx/sites-available/default:

listen 80 default;

Чтобы принимать IPv6 соединения, нужно добавить еще одну директиву listen для IPv6:

listen [::] default;

Специальный адрес [::] является так называемым wildcard и означает «все IPv6 адреса». Однако с такой конфигурацией nginx не запускается и выдает ошибку:

[emerg]: bind() to [::]:80 failed (98: Address already in use)

Дело в том, что в IPv6 существует такое понятие как IPv4-mapped адрес. Суть его в том, что пространство IPv4 адресов отображается в пространство IPv6 адресов с помощью специального префикса ::ffff. Сделано это, видимо, для облегчения процесса перехода на новый протокол. Покойный itojun говорил, что это возможность является потенциальной дырой в безопасности, поэтому, например, в OpenBSD она отключена. В других системах, более терпимых к дырам, наличие этой возможности контролируется специальным параметром для сокетов IPV6_V6ONLY. И в Linux по умолчанию IPv4-mapped адреса включены. Это приводит к тому, что вызов bind(2) с IPv6 wildcard в качестве адреса пытается помимо IPv6 адресов захватить и IPv4 адреса, которые уже заняты предыдущей директивой listen. Чтобы решить проблему, необходимо отключить IPv4-mapped адреса с помощью параметра ipv6only в конфиге nginx:

listen [::] default ipv6only=on;
, , , , ,

ЭВМ/ IT-хозяйство: веб-хостинг

31.03.2010

Осталось рассказать, какой софт я развернул на своей VPS. Как я уже говорил, Linode делает свои виртуалки на базе Xen, причем в режиме паравиртуализации. А это, в свою очередь, означает, что выбор ОС сильно ограничен: или Linux, или какой-то другой Linux. Нет, конечно есть вполне рабочий порт NetBSD на Xen, есть порт FreeBSD, были даже попытки запустить OpenBSD на этой архитектуре. Но, во-первых, я не слышал, чтобы кто-то делал веб-хостинг на NetBSD, а FreeBSD на мой взгляд ничем не лучше Linux.

Вообще я не очень люблю Linux, но и какой-то патологической ненависти к нему не испытываю. Поэтому к идее развернуть свое хозяйство на этой ОС отношусь спокойно. Кроме того, все последние годы деньги мне платили именно за работу с Linux. Так что опыт кое-какой имеется. Сам я слабо разбираюсь в тонкостях современных дистрибутивов Linux и в том, как устроен веб-хостинг. Но многие коллеги на нынешней работе имеют в этой области хороший опыт, поэтому я все делал, основываясь на их рекомендациях.

После выбора тарифного плана на Linode необходимо решить, какую операционную систему развернуть. Из всего списка я остановился на (точнее мне посоветовали) Debian 5.0 Lenny. Забегая наперед скажу, что система мне понравилась: удобный менеджер пакетов, сами пакеты сделаны хорошо, избавляют от большого количества рутиной работы по первичной настройке. Через несколько секунд новая виртуальная машина готова к работе. Зайти на нее можно либо через AJAX консоль, либо по ssh. Консоль также доступна через ssh-интерфейс (что-то вроде serial console по сети), они ее называют красивым именем Lish — Linode Shell, хотя на самом деле это обычная Xen console. Итак, залогинившись можно приступать к настройке.

В качестве веб-платформы я решил (опять-таки послушав умных людей) использовать связку nginxPHP-FPM + MySQL. nginx — потому что монстр Apache мне не нужен, PHP-FPM — толковая реализация FastCGI для PHP, которая кушает мало памяти (а память на моем VPS сильно ограничена), а MySQL — он и в Африке MySQL, не PostgreSQL же ставить в самом деле.

С nginx и MySQL никаких проблем не возникло, оба пакета нашлись в стандартном репозитории Debian. А вот с PHP-FPM не все просто. PHP-FPM — суть сторонний патч для PHP, и только совсем недавно было принято решение включить его в следующую версию PHP. А до сих все прикладывали его вручную, а самые продвинутые делали собственный пакет. Оба этих варианта мне не годились, потому что у меня совсем нет времени делать и поддерживать наколенные поделки, свои или чужие. Легкое гугление показало, что есть добрые люди, которые не только собрали нужный мне пакет, но и создали для него репозиторий и готовы пакет поддерживать. Но на практике оказалось, что их пакеты уже остали от обновлений Debian Security, поэтому эту затею пришлось оставить.

Дальнейшее гугление привело меня на сайт Dotdeb. И там наконец обнаружился добротный пакет PHP-FPM, но только для PHP 5.3, тогда как в Lenny по умолчанию идет 5.2. Но так как мне было все равно, какую версию PHP ставить, я его и взял. Впоследствии правда оказалось, что есть небольшие проблемы этой версии с некоторыми плагинами для WordPress, но в остальном все отлично.

Теперь осталось только выбрать движок для блога. Так как я этой темой никогда особо не интересовался, то взял самый популярный — WordPress. И, надо сказать, не жалею. Интерфейс приятный, удобный, работает быстро. За пол дня подточил стандартную тему — и новый непорядок готов!

, , , , , , , , , , ,