Как стать автором
Обновить

Создание и тестирование Firewall в Linux, Часть 2.3. Завершаем Firewall. Обрабатываем трафик в userspace

Время на прочтение5 мин
Количество просмотров7.8K
Содержание первой части:


Содержание второй части:

2.1 — Введение во вторую часть. Смотрим на сеть и протоколы. Wireshark.
2.2 — Таблицы Firewall. Transport Layer. Структуры TCP, UDP. Расширяем Firewall.
2.3 — Расширяем функциональность. Обрабатываем данные в user space. libnetfilter_queue.
2.4 — Бонус. Изучаем реальную Buffer Overflow атаку и предотвращаем с помощью нашего Firewall'а.

Часть 2.3 – Введение


Мы закончили прошлую часть на

if(dest_port == HTTP_PORT || src_port == HTTP_PORT) {
	printk("HTTP packet\n");
}

В этой части мы посмотрим, как легко послать пакеты из kernel space в user space и обратно. Для примера мы возьмем именно HTTP соединение и добавим блокировку отдельных сайтов. В бонусной части мы рассмотрим существующую уязвимость и, в качестве примера, как с ней бороться.

libnetfilter_queue.


К нашему везенью, есть уже готовое решение – низкоуровневая библиотека, написанная на C, для простого взаимодействия с netfilter из userspacelibnetfilter_queue. Как можно догадаться по названию, когда мы получаем очередной пакет и вызывается установленная нами hook_function, есть возможность принять пакет (return NF_ACCEPT), выкинуть (return NF_DROP), а есть возможность поместить его в специальную очередь (queue), просто выполнив:

#define HTTP_QUEUE_NUMBER 1
return NF_QUEUE_NR(HTTP_QUEUE_NUMBER);

HTTP_QUEUE_NUMBER – это определенный мною номер очереди, куда послать пакет (может быть несколько очередей, что удобно для простого разделения пакетов по типам)
NF_QUEUE_NR – это макрос, который необходимо использовать (он делает побитовые сдвиги, чтобы вернуть нужное число).
По умолчанию можно также написать:

return NF_QUEUE

и тогда все пакеты будут посланы в очередь с номером ноль. То есть единственное, что необходимо нам поменять в коде, чтобы послать все пакеты связанные с http протоколом, это добавить эту строчку:

if(dest_port == HTTP_PORT || src_port == HTTP_PORT) {
	printk("HTTP packet\n");
return NF_QUEUE_NR(HTTP_QUEUE_NUMBER);
}

Теперь необходимо эти пакеты получить в user space. И вот тут нам поможет libnetfilter_queue.

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

Внутри также есть простой пример, а возможные проблемы при установке решаются при помощи поисковика (у меня нужно было установить еще один пакет). Давайте посмотрим на самые важные моменты из примера. В функции main() (напоминаю, что мы сейчас в user space!) происходят разные инициализации

int main(int argc, char **argv)
{
…
	printf("opening library handle\n");
	h = nfq_open();
	if (!h) {	exit(1);	}
…
	qh = nfq_create_queue(h,  0, &cb, NULL);
	if (!qh) { exit(1); }
…
	for (;;) {
		if ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) {
			printf("pkt received\n");
			nfq_handle_packet(h, buf, rv);
			continue;
		}
…

Тут важно указать правильный номер QUEUE. В примере это ноль, но мы его меняем на наш qh = nfq_create_queue(h, HTTP_QUEUE_NUMBER, &cb, NULL);
Если все инициализации прошли успешно, то мы попадем в бесконечный цикл, где будем «ждать» поступления новых пакетов из kernel. Когда такой пакет поступает, вызывается функция cb, указатель на которую мы также передали в nfw_create_queue:

static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,
	      struct nfq_data *nfa, void *data)
{
	u_int32_t id = print_pkt(nfa);
	printf("entering callback\n");
	return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}

Функция же в свою очередь вызывает print_pkt, которой передают указатель на структуру, содержащую пакет, который к нам пришел. Каждый пакет, получает в соответствии с порядком поступления – номер id, который возвращает print_pkt (на которую мы посмотрим вот вот), и в конце вызывается функция вердикта данному пакету – в данном случае NF_ACCEPT – принять.

Теперь к «сердцу» примера – print_pkt:

static u_int32_t print_pkt(struct nfq_data *tb)

В оригинальном примере есть множество функций для доступа к различным данным через nfq_data *tb, мы же рассмотрим доступ к данным на тех уровнях, которые нам хорошо знакомы из предыдущих частей – Application, Transport, Network

Вот полный код функции:

static u_int32_t print_pkt(struct nfq_data *tb)
{
	int i = 0;
	int packet_len = 0;
	unsigned char *data = NULL;
	int ip_src_array[4] = {0};
	int ip_dst_array[4] = {0};

	packet_len = nfq_get_payload(tb, &data);

	ip_dst_array[3] = data[16];
	ip_dst_array[2] = data[17];
	ip_dst_array[1] = data[18];
	ip_dst_array[0] = data[19];

	ip_src_array[3] = data[12];
	ip_src_array[2] = data[13];
	ip_src_array[1] = data[14];
	ip_src_array[0] = data[15];
	
	char ip_dst_str[20] = {0};
	char ip_src_str[20] = {0};
	
	ip_hl_to_str(ip_dst_array, ip_dst_str);
	ip_hl_to_str(ip_src_array, ip_src_str);
	
	printf("src_ip = %s, dst_ip = %s", ip_src_str, ip_dst_str);
	
	printf("\n");
	if( packet_len >= 0x34 ) {
		for(i = 0x34; i < packet_len; ++i){
			if(data[i] >= ' ' && data[i] <= '}')
				printf("%c", (int)data[i]);
		}
              }

	printf("End of packet checking\n");
	return NF_ACCEPT;
}

Ну, тут уже не должно быть вопросов.

Компилируем, проверяем:

Так выглядят все части, http – сервер который будет слушать http трафик, interface – управление устройством, module – наш firewall. Компилируем http сервер, запускаем firewall, запускаем http сервер:



Отдельно заходим с host2 и в этот раз для интереса подключаемся к host1 на порт 80 и посылаем команду GET, чтобы получить главную страницу:



Смотрим результаты, что пишет http сервер. Видно посланную нами команду GET, а после нее и всю страницу, которую нам послал host1 в ответ на просьбу.



Теперь уже совершенно очевидно, как, например, заблокировать доступ к отдельным сайтам.

Завершение первой и второй части


Мы начали первую часть с того, что построили простую сеть из трех компьютеров с операционной системой linux. Дальше мы написали простое устройство, которое умело считать количество пакетов, которые проходят через сетевые карты компьютера, а также разрежать и запрещать их дальнейшую передачу. Все это происходило на уровне ядра операционной системы. Для того, чтобы нам было удобно управлять устройством и получать от него информацию, мы добавили user interface, которые умел посылать команды нашему устройству и принимать от него данные. Так мы получали статистику в удобной нам форме.

Вторую часть мы начали с изучения сетей и протоколов, исследовав детали некоторых из них. И дальше применили наши знания, существенно расширив возможности firewall и получив доступ ко всей необходимой информации, которая содержится в пакете на интересующих нас OSI уровнях. Все это происходит в kernel space.

В этой части мы послали некоторые из пакетов в user space, тем самым намного расширив удобство написания кода и его функциональность, открыв для себя возможность использования множества доступных готовых С\С++ библиотек. Надо отметить, что, сделав это, мы «заплатили» в производительности, но при этом не стали нагружать ядро операционной системы и переложили анализ трафика на пользовательскую аппликацию. Теперь работа firewall стала намного медленней, но в нашем случае это не принципиально.

Современные же firewall – это очень сложный hardware-software комплекс, где все заточено под скорость и качество, и который устанавливается на главный «вход» в большие сети и обрабатывает гигабайты в секунду. Можно представить, что даже незначительное ухудшение производительности (например, обработки пакета) в гигабайтах будет обходиться очень дорого. Поэтому в них постоянно ищут слабые звенья и работают над улучшением их производительности. Кроме того, в задачу современных firewall входит не только проверка правил, но и намного более сложные задачи: отражение разных типов DOS атак, вирусов, предотвращение утечки информации, попыток взлома компьютеров, доступа к подозрительным сайтам и скачивания подозрительного контента. Простейший пример в качестве бонусной статьи мы рассмотрим в последней части – завершающей.
Теги:
Хабы:
Всего голосов 13: ↑13 и ↓0+13
Комментарии0

Публикации

Истории

Работа

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань