]> Untitled Git - articles.git/blob
2c6ca64aa4502d2d804a49f04322af14a5fe3737
[articles.git] /
1 ## PowerDNS
2
3 PowerDNS -- DNS сервер для массового обслуживания с хранением информации об обслуживаемых зонах в СУБД.
4 Когда-то в прошлой жизни я его использовал в проекте DNS-хостинга.
5 PowerDNS был выбран пример ПО с ярко выраженной поверхностью атаки: есть сетевое соединение обслуживающие анонимных пользователей посылающих серверу нетривиальные запросы.
6 (Надо отметить что в том DNS-хостиге мы прятали наш PowerDNS за более общепринятым bind9 работающем в режиме трансляции запросов)
7 Условия экзамена по обучающей программе предполагали использование Natch для обнаружение функций лежащих на поверхности атаки и подлежащих первоочередной обработки фаззингом.
8 Когда поверхность атаки ярко выраженная, это сделать проще всего.
9
10 ### План эксперимента   
11
12
13 Предполагается 
14 * выполнить сборку PowerDNS из исходников
15 * настроить тестовую DNS-зону
16 * запустить сервер в режиме авторитативного мастера,
17 * пометить данные приходящие на 53й порт по tcp и udp 
18 * отправить к серверу запросы по tcp и udp протоколу
19 * изучить как помеченные данные перемещались через иерархию вызова функций.
20
21 ### Сборка и установка
22
23 1. Получаем исходники последней версии PowerDNS:
24
25 ```
26 git clone https://github.com/PowerDNS/pdns.git
27 ```
28
29 В моем случае используется ветка master находящаяся на коммите d4ccf7673a от 9 апреля 2025г.
30
31 2. Устанавливаем зависимости необходимые для сборки проекта
32
33 ```
34 apt-get build-dep pdns-server
35 ```
36
37 3. Конфигурируем проект и запускаем сборку
38
39
40 ```
41 ./configure --with-modules="gpgsql"
42
43 make
44
45 sudo make install
46 ```
47
48 Если обратить внимание на финальный вывод скрипта `./configure` то можно заметить, что PowerDNS по умолчанию собирается с отладочной информацией (ключ `-g`) и никаких дополнительных действий для сборки с покрытием предпринимать не надо.
49
50 Сборка осуществляется с модулем `gpgsql` который позволяет хранить информацию о зонах и записях в СУБД PostgreSQL, мне с ней привычнее работать.
51
52 ### Настройка
53
54 #### 1. Устанавливаем PostgreSQL из пакетной системы Debian:
55
56 ```
57 sudo apt-get install postgresql
58 ```
59
60
61 #### 2. Создаем пользователя и базу данных для хранения данных PowerDNS
62
63 ```
64 sudo su postgres
65 createuser -P pdns_user
66 # Вводим пароль
67 createdb pdns -O pdns_user
68 ```
69
70 #### 3. Создание схемы базы данных
71
72 Копируем схему базы данных из [документации](https://doc.powerdns.com/authoritative/backends/generic-postgresql.html#default-schema) в буфер
73 (та же схема есть в исходниках по пути `modules/gpgsqlbackend/schema.pgsql.sql`)
74
75 Подключаемся консолью `psql` к базе `pdns` и переключаемся в пользователя `pdns_user`
76
77 ```
78 psql pdns
79 set role pdns_user;
80
81 ```
82
83 Вставляем из буфера обмена в консоль код схемы скопированный выше из документации, нажимаем Enter.
84
85 В консоли появится много строк начинающихся со слова `CREATE`.
86
87 Выходим из консоли `psql`, выходим из `shell`-консоли пользователя `postgres`
88
89 ```
90 exit
91 exit
92 ```
93
94 ####  4. Создание конфигурации PowerDNS
95
96 Копируем пример файла конфигурации в актуальный конфигурационный файл
97
98 ```
99 cp /usr/local/etc/pdns.conf-dist /usr/local/etc/pdns.conf
100 ```
101
102 Добавляем в файл `/usr/local/etc/pdns.conf` строки
103
104 ```
105 launch=gpgsql
106 gpgsql-host=127.0.0.1
107 gpgsql-user=pdns_user
108 gpgsql-password=[ваш пароль]
109 gpgsql-dbname=pdns
110 ```
111 В значение параметра `gpgsql-password` записываете пароль который вы указали при создании пользователя pdns.
112
113 ### Тестирование
114  
115 #### 1. Проверяем что вообще запускается
116
117 По неизвестной мне причине бинарник `pdns_server` в `/usr/local/bin` при `make install` не установился, но при наличии правильного конфигурационного в ожидаемом месте можно запускать бинарник прямо из директории сборки
118
119 ```
120 sudo pdns/pdns_server
121 ```
122
123 Если все было сделано правильно сервер запустится, сообщит что присоединился  к каким-то портам и перейдет в режим ожидания соединения. 
124 Если была допущена какая-то ошибка надо ее устранить и добиться того чтобы сервер запустился.
125 Сервер пока останавливаем нажав Ctrl+C
126
127 #### 2. Создаем тестовую зону.
128
129 В директории сборки в папке `/psql` есть утилита для управления зонами и записями PowerDNS. Используем ее для создания зоны.
130
131 ```
132 pdns/pdnsutil create-zone example.com
133 pdns/pdnsutil add-record example.com test A '8.8.8.8'
134 ```
135
136 Этими командами мы создаем A-запись, которая указывает, что доменное имя `test.example.com` привязана к IP-адресу `8.8.8.8`
137
138 Можно зайти обратно в `psql` и убедиться, что таблицах `domains` и `records` были созданы соответсвующие записи:
139
140 ```
141 select * from domains;
142 select * from records;
143 ```
144
145 #### 3. Локальное тестирование 
146
147 Запустим сервер как в п.1
148
149 ```
150 sudo pdns/pdns_server
151 ```
152
153 в соседней консоли еще раз зайдем на виртуалку и выполним команды
154
155 ```
156 dig @127.0.0.1 test.example.com
157
158 dig @127.0.0.1 test.example.com +tcp
159 ```
160
161 Эти команды выполнят запросы к нашему DNS-серверу по UDP и TCP протоколам. Правильный ответ должен содержать строку
162
163 ```
164 test.example.com.       3600    IN      A       8.8.8.8
165 ```
166
167 А так же в разделе `flags:` должен присутствовать флаг `aa`, говорящий что это ответ от сервера отвечающего за зону, а не ретранслированный.
168
169 #### 4. Пробрасывание 53 порта наружу
170
171 Теперь нам надо сделать так, чтобы порт по которому PowerDNS обслуживает клиентов, оказался доступен на host-машине.
172
173 Для этого в скрипт запуска виртуалки в опции настройки сети добавим параметры `hostfwd=udp::15353-:53,hostfwd=tcp::15353-:53`, в результате чего в теории 53 порт внутри виртуалки будет замэплен на порт 15353 хост машины и по TCP и по UDP.
174 Проделав это перезапустим виртуалку.
175
176 #### 5. Тестирование извне виртуалки
177
178 Зайдя в виртуалку запускаем DNS-свервер как в п.1
179
180 ```
181 sudo pdns/pdns_server
182 ```
183
184 И на хост-машине делаем TCP и UDP DNS-запросы по порту 15353 локального интерфеса:
185  
186 ```
187 dig @127.0.0.1 -p15353 test.example.com +tcp
188 dig @127.0.0.1 -p15353 test.example.com
189 ```
190
191 Результат должен оказаться такой же как в п.3.
192
193
194 ### Запись трассы
195
196 На основании созданного образа виртуальной машины мы создаем два проекта natch для пометки данных по протоколу TCP и для пометки данных по протоколу UDP.
197 Процесс создания проектов в достаточной мере отражен в документации, обозначу лишь ключевые нюансы:
198
199 * Создаем два отдельных проекта, один для перехвата TCP, другой для перехвата UDP
200 * Для случая TCP в консольном диалоге запрашиваем проброс наружу 53го порта, в `tainted.cfg` в разделе `[Ports]` указываем `ip_protocol=6`, `dst=53`, `rc=53`, и все должно заработать
201 * Для случая UDP, после создания проекта, необходимо руками во всех конфигах заменить `hostfwd=tcp` на `hostfwd=udp`,  в `tainted.cfg` в разделе `[Ports]` указать `ip_protocol=17`, а `src` и `dst` не указывать вовсе. 
202
203 Выяснить, читая конфиги на какой из внешних портов замэплен находящийся внутри виртуалки 53 порт. А потом все более или менее по инструкции:
204
205 * Включаем запись сценария `natch record`
206 * Логинимся в виртуалку, уже через графическое окно qemu
207 * Делаем первый снэпшот
208 * Запускаем сервер, как это делали при тестировании
209 * С хост машины отправляем запрос на TCP или UDP порт на который был замаплен 53 порт виртуалки (значение добывается из конфигов натча), так же как это делали при тестировании, номер порта только надо будет поменять.
210 * Получаем ответ
211 * Делаем второй снепшот
212 * Выходим.
213
214 Далее командой `natch replay` снимаем трассу между первым и вторым снепшотом и загружаем ее в snatch.
215
216 ### Анализ результатов
217
218 Данные пришедшие в систему 53 TCP или UDP порта, были помечены, и при помощи программы визуализации snatch можно посмотреть как они распространялись по системе
219
220 #### Resources
221
222 Посмотрев на вкладку Resources мы можем следать вывод, что помеченные данные проходили через сам процесс pdns_server'а и через процесс ядра (имеют право) и через другие процессы не проходили
223
224 ![...](1. PowerDNS.imgs/resources_0.png)
225
226 заглянув внутрь узла `pdns_server/sockets` мы можем увидеть, что помеченные данные читаются из tcp или udp сокета (в зависимости от того на результаты какого эксперимента мы смотрим) и более ни в какие другие сокеты и файлы не направлялись
227
228 ![...](1. PowerDNS.imgs/resources_1_tcp.png)
229
230 ![...](1. PowerDNS.imgs/resources_1_udp.png)
231
232 Если нажать на подсвеченный голубым сокет, то в появившейся справа панели можно увидеть, что помеченные данные из сокета читаются, и потом обратно в него частично пишутся, и при этом пределы pdns_server'а не покидают, например не участвуют в запросе к базе данных.
233 Интересно, как им это удалось? Вся база записей оказывается загруженной в память и сервер делает `strcmp` с содержимым памяти?
234
235 ![...](1. PowerDNS.imgs/resources_2.png)
236
237 Так же можно увидеть, что сервер по адресу 10.0.2.3 (адрес сетевого интерфейса виртуальной машины) сервер сам себе посылает udp-пакет с неким запросом `security-status`.
238 Это явно должно насторожить безопасников, этот механизм должен быть как минимум исследован, и возможно, если результаты исследования окажутся неудовлетворительными, то отключен.
239
240 ![...](1. PowerDNS.imgs/resources_3.png)
241
242 #### Call Graph
243 ##### TCP
244
245 В графе вызовов снятом для случая передачи данных по TCP соединению хорошо виден алгоритм обработки входящего запроса.
246 После ряда вызовов посвященных получению данных из сокета, вызывается большой метод отвечающий за разбор входящего пакета `DNSPacket::parse`, после чего происходит обработка входящего запроса `AuthPacketCache::get` (не совсем четко понятно что он делает, надо код читать) и `PacketHandler::doQuestion`, после чего происходит отправка ответа: `ResponseStats::submitResponse`.
247 Первичной целью для фаззинга совершенно очевидно следует выбрать функцию `DNSPacket::parse`, которая оказывается первой на острее атаки. Для более глубокого фаззинг исследования следует в качестве цели выбрать всю последовательность обработки запроса включая парсинг и обработку запроса, но не включая отправку ответа.  
248
249 ![...](1. PowerDNS.imgs/call_graph_tcp.png)
250
251
252 ##### UDP
253
254 Для случая UDP соединения мы видим те же самые функции `DNSPacket::parse`, `AuthPacketCache::get`, `PacketHandler::doQuestion` и `ResponseStats::submitResponse` перемежающиеся вызовами обеспечивающими работу с UDP-сетевым соединением, и завернутые в два потока выполнения. 
255 По какой-то причине для работы с UPD-соединением была выбрана многопоточная архитектура. Видимо в этом был какой-то практический смысл.
256 Принципиально новых элементов в поверхность атаки при обработке UDP-соединения обнаружено не было.
257
258 ![...](1. PowerDNS.imgs/call_graph_udp.png)
259
260
261 ### Выводы
262
263 Natch достаточно хорошо показал себя как инструмент для начального знакомства с ранее неизвестным продуктом, а так же позволил быстро проанализировать потенциальную поверхность атаки.
264 Natch должен быть незаменимым инструментом для организаций занимающихся исследованием безопасности большого количества стороннего кода.