היבטים שונים של פעולת DNS כבר נגעו שוב ושוב על ידי המחבר במספר פורסם במסגרת הבלוג. יחד עם זאת, הדגש העיקרי תמיד היה על שיפור האבטחה של שירות אינטרנט מפתח זה.

עד לאחרונה, למרות הפגיעות הברורה של תעבורת DNS, שעדיין, ברובה, מועברת בבהירות, לפעולות זדוניות מצד ספקים המבקשים להגדיל את הכנסתם על ידי הטמעת פרסום בתוכן, סוכנויות אבטחה ממשלתיות וצנזורה, כמו גם פשוט פושעים, התהליך , למרות נוכחותן של טכנולוגיות שונות כגון DNSSEC/DANE, DNScrypt, DNS-over-TLS ו-DNS-over-HTTPS, נתקעה. ואם פתרונות שרת, וחלקם קיימים כבר די הרבה זמן, ידועים וזמינים, התמיכה שלהם מתוכנת לקוח משאירה הרבה מקום לרצון.
למרבה המזל, המצב משתנה. בפרט, מפתחי הדפדפן הפופולרי פיירפוקס על תוכניות להפעלת מצב תמיכה כברירת מחדל (DoH) בקרוב. זה אמור לעזור להגן על תעבורת ה-DNS של משתמש ה-WWW מהאיומים שלעיל, אך עלול להציג איומים חדשים.
1. בעיות DNS-over-HTTPS
במבט ראשון, ההחדרה ההמונית המתחילה של DNS-over-HTTPS לתוכנת האינטרנט גורמת רק לתגובה חיובית. עם זאת, השטן, כמו שאומרים, נמצא בפרטים.
הבעיה הראשונה שמגבילה את היקף השימוש הנרחב של DoH היא ההתמקדות שלו אך ורק בתעבורת אינטרנט. ואכן, פרוטוקול ה-HTTP והגרסה הנוכחית שלו HTTP/2, שעליה מבוסס DoH, הם הבסיס ל-WWW. אבל האינטרנט הוא לא רק האינטרנט. ישנם הרבה שירותים פופולריים, כמו דואר אלקטרוני, מסרים מידיים שונים, מערכות העברת קבצים, הזרמת מולטימדיה וכו', שאינם משתמשים ב-HTTP. לפיכך, למרות התפיסה על ידי רבים של DoH כתרופה פלאיה, מתברר שזה לא ישים ללא מאמץ נוסף (ומיותר) עבור כל דבר אחר מלבד טכנולוגיות דפדפן. אגב, DNS-over-TLS נראה כמו מועמד הרבה יותר ראוי לתפקיד הזה, שמיישם את הקיבול של תעבורת DNS סטנדרטית בפרוטוקול TLS הסטנדרטי המאובטח.
הבעיה השנייה, שעשויה להיות הרבה יותר משמעותית מהראשונה, היא הנטישה בפועל של הביזור המובנה של DNS על ידי עיצוב לטובת שימוש בשרת DoH יחיד המצוין בהגדרות הדפדפן. במיוחד, מוזילה מציעה להשתמש בשירות של Cloudflare. שירות דומה הושק גם על ידי גורמים בולטים אחרים באינטרנט, בפרט גוגל. מסתבר שהטמעת DNS-over-HTTPS בצורה בה היא מוצעת כיום רק מגבירה את התלות של משתמשי הקצה בשירותים הגדולים ביותר. זה לא סוד שהמידע שניתוח שאילתות DNS יכול לספק יכול לאסוף עוד יותר נתונים לגביו, כמו גם להגביר את הדיוק והרלוונטיות שלו.
בהקשר זה, המחבר היה ונשאר תומך ביישום ההמוני לא של DNS-over-HTTPS, אלא של DNS-over-TLS יחד עם DNSSEC/DANE כאמצעי אוניברסלי, מאובטח ואינו תורם לריכוזיות נוספת של אמצעי האינטרנט. להבטחת אבטחת תעבורת DNS. לרוע המזל, מסיבות ברורות, אי אפשר לצפות להחדרה מהירה של תמיכה המונית בחלופות DoH לתוכנת הלקוח, וזה עדיין נחלתם של חובבי טכנולוגיית אבטחה.
אבל מכיוון שיש לנו כעת DoH, מדוע שלא נשתמש בו לאחר שנמלט ממעקב פוטנציאלי של תאגידים דרך השרתים שלהם לשרת ה-DNS-over-HTTPS שלנו?
2. פרוטוקול DNS-over-HTTPS
אם מסתכלים על הסטנדרט כשמתארים את פרוטוקול ה-DNS-over-HTTPS, אתה יכול לראות שזהו, למעשה, API אינטרנט המאפשר לך לכלול חבילת DNS סטנדרטית בפרוטוקול HTTP/2. זה מיושם באמצעות כותרות HTTP מיוחדות, כמו גם המרה של הפורמט הבינארי של נתוני DNS משודרים (ראה. ומסמכים הבאים) לטופס המאפשר לך לשדר ולקבל אותם, כמו גם לעבוד עם המטא-נתונים הדרושים.
לפי התקן, רק HTTP/2 וחיבור TLS מאובטח נתמכים.
שליחת בקשת DNS יכולה להתבצע באמצעות שיטות GET ו-POST הסטנדרטיות. במקרה הראשון, הבקשה עוברת טרנספורמציה למחרוזת מקודדת base64URL, ובמקרה השני, דרך גוף בקשת ה-POST בצורה בינארית. במקרה זה, נעשה שימוש בסוג נתוני MIME מיוחד במהלך בקשת ה-DNS והתגובה אפליקציה/הודעה-dns.
root@eprove:~ # curl -H 'accept: application/dns-message' 'https://my.domaint/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE' -v
* Trying 2001:100:200:300::400:443...
* TCP_NODELAY set
* Connected to eprove.net (2001:100:200:300::400) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /usr/local/share/certs/ca-root-nss.crt
CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=my.domain
* start date: Jul 22 00:07:13 2019 GMT
* expire date: Oct 20 00:07:13 2019 GMT
* subjectAltName: host "my.domain" matched cert's "my.domain"
* issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x801441000)
> GET /dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE HTTP/2
> Host: eprove.net
> User-Agent: curl/7.65.3
> accept: application/dns-message
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 200
< server: h2o/2.3.0-beta2
< content-type: application/dns-message
< cache-control: max-age=86274
< date: Thu, 12 Sep 2019 13:07:25 GMT
< strict-transport-security: max-age=15768000; includeSubDomains; preload
< content-length: 45
<
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
* Failed writing body (0 != 45)
* stopped the pause stream!
* Connection #0 to host eprove.net left intactשימו לב גם לכותרת בקרת מטמון: בתגובה משרת האינטרנט. בפרמטר גיל מקסימלי מכיל את ערך TTL עבור רשומת ה-DNS המוחזרת (או את הערך המינימלי אם קבוצה מהם מוחזרת).
בהתבסס על האמור לעיל, התפקוד של שרת DoH מורכב ממספר שלבים.
- קבל בקשת HTTP. אם זה GET אז פענח את החבילה מקידוד base64URL.
- שלח חבילה זו לשרת ה-DNS.
- קבל תגובה משרת ה-DNS
- מצא את ערך ה-TTL המינימלי ברשומות שהתקבלו.
- החזר תגובה ללקוח באמצעות HTTP.
3. שרת DNS-over-HTTPS משלך
הדרך הפשוטה, המהירה והיעילה ביותר להפעיל שרת DNS-over-HTTPS משלך היא להשתמש בשרת אינטרנט HTTP/2 , שעליו כבר כתב המחבר בקצרה (ראה "").
בחירה זו נתמכת על ידי העובדה שניתן ליישם את כל הקוד של שרת DoH משלך באמצעות המתורגמן המשולב ב-H2O עצמו . בנוסף לספריות הסטנדרטיות, כדי להחליף נתונים עם שרת ה-DNS, אתה צריך את ספריית (mrbgem) Socket, אשר, למרבה המזל, כבר כלולה בגרסת הפיתוח הנוכחית של H2O 2.3.0-beta2 ביציאות FreeBSD. עם זאת, לא קשה להוסיף אותו לכל גרסה קודמת על ידי שיבוט המאגר לקטלוג /deps לפני הידור.
root@beta:~ # uname -v
FreeBSD 12.0-RELEASE-p10 GENERIC
root@beta:~ # cd /usr/ports/www/h2o
root@beta:/usr/ports/www/h2o # make extract
===> License MIT BSD2CLAUSE accepted by the user
===> h2o-2.2.6 depends on file: /usr/local/sbin/pkg - found
===> Fetching all distfiles required by h2o-2.2.6 for building
===> Extracting for h2o-2.2.6.
=> SHA256 Checksum OK for h2o-h2o-v2.2.6_GH0.tar.gz.
===> h2o-2.2.6 depends on file: /usr/local/bin/ruby26 - found
root@beta:/usr/ports/www/h2o # cd work/h2o-2.2.6/deps/
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # git clone https://github.com/iij/mruby-socket.git
Клонирование в «mruby-socket»…
remote: Enumerating objects: 385, done.
remote: Total 385 (delta 0), reused 0 (delta 0), pack-reused 385
Получение объектов: 100% (385/385), 98.02 KiB | 647.00 KiB/s, готово.
Определение изменений: 100% (208/208), готово.
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # ll
total 181
drwxr-xr-x 9 root wheel 18 12 авг. 16:09 brotli/
drwxr-xr-x 2 root wheel 4 12 авг. 16:09 cloexec/
drwxr-xr-x 2 root wheel 5 12 авг. 16:09 golombset/
drwxr-xr-x 4 root wheel 35 12 авг. 16:09 klib/
drwxr-xr-x 2 root wheel 5 12 авг. 16:09 libgkc/
drwxr-xr-x 4 root wheel 26 12 авг. 16:09 libyrmcds/
drwxr-xr-x 13 root wheel 32 12 авг. 16:09 mruby/
drwxr-xr-x 5 root wheel 11 12 авг. 16:09 mruby-digest/
drwxr-xr-x 5 root wheel 10 12 авг. 16:09 mruby-dir/
drwxr-xr-x 5 root wheel 10 12 авг. 16:09 mruby-env/
drwxr-xr-x 4 root wheel 9 12 авг. 16:09 mruby-errno/
drwxr-xr-x 5 root wheel 14 12 авг. 16:09 mruby-file-stat/
drwxr-xr-x 5 root wheel 10 12 авг. 16:09 mruby-iijson/
drwxr-xr-x 5 root wheel 11 12 авг. 16:09 mruby-input-stream/
drwxr-xr-x 6 root wheel 11 12 авг. 16:09 mruby-io/
drwxr-xr-x 5 root wheel 10 12 авг. 16:09 mruby-onig-regexp/
drwxr-xr-x 4 root wheel 10 12 авг. 16:09 mruby-pack/
drwxr-xr-x 5 root wheel 10 12 авг. 16:09 mruby-require/
drwxr-xr-x 6 root wheel 10 12 сент. 16:10 mruby-socket/
drwxr-xr-x 2 root wheel 9 12 авг. 16:09 neverbleed/
drwxr-xr-x 2 root wheel 13 12 авг. 16:09 picohttpparser/
drwxr-xr-x 2 root wheel 4 12 авг. 16:09 picotest/
drwxr-xr-x 9 root wheel 16 12 авг. 16:09 picotls/
drwxr-xr-x 4 root wheel 8 12 авг. 16:09 ssl-conservatory/
drwxr-xr-x 8 root wheel 18 12 авг. 16:09 yaml/
drwxr-xr-x 2 root wheel 8 12 авг. 16:09 yoml/
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # cd ../../..
root@beta:/usr/ports/www/h2o # make install clean
...תצורת שרת האינטרנט היא בדרך כלל סטנדרטית.
root@beta:/usr/ports/www/h2o # cd /usr/local/etc/h2o/
root@beta:/usr/local/etc/h2o # cat h2o.conf
# this sample config gives you a feel for how h2o can be used
# and a high-security configuration for TLS and HTTP headers
# see https://h2o.examp1e.net/ for detailed documentation
# and h2o --help for command-line options and settings
# v.20180207 (c)2018 by Max Kostikov http://kostikov.co e-mail: max@kostikov.co
user: www
pid-file: /var/run/h2o.pid
access-log:
path: /var/log/h2o/h2o-access.log
format: "%h %v %l %u %t "%r" %s %b "%{Referer}i" "%{User-agent}i""
error-log: /var/log/h2o/h2o-error.log
expires: off
compress: on
file.dirlisting: off
file.send-compressed: on
file.index: [ 'index.html', 'index.php' ]
listen:
port: 80
listen:
port: 443
ssl:
cipher-suite: ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
cipher-preference: server
dh-file: /etc/ssl/dhparams.pem
certificate-file: /usr/local/etc/letsencrypt/live/eprove.net/fullchain.pem
key-file: /usr/local/etc/letsencrypt/live/my.domain/privkey.pem
hosts:
"*.my.domain":
paths: &go_tls
"/":
redirect:
status: 301
url: https://my.domain/
"my.domain:80":
paths: *go_tls
"my.domain:443":
header.add: "Strict-Transport-Security: max-age=15768000; includeSubDomains; preload"
paths:
"/dns-query":
mruby.handler-file: /usr/local/etc/h2o/h2odoh.rbהחריג היחיד הוא המטפל ב-URL /dns-query ששרת ה-DNS-over-HTTPS שלנו, שנכתב ב-mruby ונקרא דרך אפשרות המטפל, אחראי לו למעשה mruby.handler-file.
root@beta:/usr/local/etc/h2o # cat h2odoh.rb
# H2O HTTP/2 web server as DNS-over-HTTP service
# v.20190908 (c)2018-2019 Max Kostikov https://kostikov.co e-mail: max@kostikov.co
proc {|env|
if env['HTTP_ACCEPT'] == "application/dns-message"
case env['REQUEST_METHOD']
when "GET"
req = env['QUERY_STRING'].gsub(/^dns=/,'')
# base64URL decode
req = req.tr("-_", "+/")
if !req.end_with?("=") && req.length % 4 != 0
req = req.ljust((req.length + 3) & ~3, "=")
end
req = req.unpack1("m")
when "POST"
req = env['rack.input'].read
else
req = ""
end
if req.empty?
[400, { 'content-type' => 'text/plain' }, [ "Bad Request" ]]
else
# --- ask DNS server
sock = UDPSocket.new
sock.connect("localhost", 53)
sock.send(req, 0)
str = sock.recv(4096)
sock.close
# --- find lowest TTL in response
nans = str[6, 2].unpack1('n') # number of answers
if nans > 0 # no DNS failure
shift = 12
ttl = 0
while nans > 0
# process domain name compression
if str[shift].unpack1("C") < 192
shift = str.index("x00", shift) + 5
if ttl == 0 # skip question section
next
end
end
shift += 6
curttl = str[shift, 4].unpack1('N')
shift += str[shift + 4, 2].unpack1('n') + 6 # responce data size
if ttl == 0 or ttl > curttl
ttl = curttl
end
nans -= 1
end
cc = 'max-age=' + ttl.to_s
else
cc = 'no-cache'
end
[200, { 'content-type' => 'application/dns-message', 'content-length' => str.size, 'cache-control' => cc }, [ str ] ]
end
else
[415, { 'content-type' => 'text/plain' }, [ "Unsupported Media Type" ]]
end
}שים לב ששרת המטמון המקומי אחראי לעיבוד מנות DNS, במקרה זה מהפצת FreeBSD הסטנדרטית. מבחינה ביטחונית זהו הפתרון האופטימלי. עם זאת, שום דבר לא מונע ממך להחליף localhost לכתובת DNS אחרת שבה אתה מתכוון להשתמש.
root@beta:/usr/local/etc/h2o # local-unbound verison
usage: local-unbound [options]
start unbound daemon DNS resolver.
-h this help
-c file config file to read instead of /var/unbound/unbound.conf
file format is described in unbound.conf(5).
-d do not fork into the background.
-p do not create a pidfile.
-v verbose (more times to increase verbosity)
Version 1.8.1
linked libs: mini-event internal (it uses select), OpenSSL 1.1.1a-freebsd 20 Nov 2018
linked modules: dns64 respip validator iterator
BSD licensed, see LICENSE in source package for details.
Report bugs to unbound-bugs@nlnetlabs.nl
root@eprove:/usr/local/etc/h2o # sockstat -46 | grep unbound
unbound local-unbo 69749 3 udp6 ::1:53 *:*
unbound local-unbo 69749 4 tcp6 ::1:53 *:*
unbound local-unbo 69749 5 udp4 127.0.0.1:53 *:*
unbound local-unbo 69749 6 tcp4 127.0.0.1:53 *:*כל מה שנותר הוא להפעיל מחדש את H2O ולראות מה יוצא מזה.
root@beta:/usr/local/etc/h2o # service h2o restart
Stopping h2o.
Waiting for PIDS: 69871.
Starting h2o.
start_server (pid:70532) starting now...4. בדיקה
אז בואו נבדוק את התוצאות על ידי שליחת בקשת בדיקה שוב ותסתכל על תעבורת הרשת באמצעות כלי השירות tcpdump.
root@beta/usr/local/etc/h2o # curl -H 'accept: application/dns-message' 'https://my.domain/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
...
root@beta:~ # tcpdump -n -i lo0 udp port 53 -xx -XX -vv
tcpdump: listening on lo0, link-type NULL (BSD loopback), capture size 262144 bytes
16:32:40.420831 IP (tos 0x0, ttl 64, id 37575, offset 0, flags [none], proto UDP (17), length 57, bad cksum 0 (->e9ea)!)
127.0.0.1.21070 > 127.0.0.1.53: [bad udp cksum 0xfe38 -> 0x33e3!] 43981+ A? example.com. (29)
0x0000: 0200 0000 4500 0039 92c7 0000 4011 0000 ....E..9....@...
0x0010: 7f00 0001 7f00 0001 524e 0035 0025 fe38 ........RN.5.%.8
0x0020: abcd 0100 0001 0000 0000 0000 0765 7861 .............exa
0x0030: 6d70 6c65 0363 6f6d 0000 0100 01 mple.com.....
16:32:40.796507 IP (tos 0x0, ttl 64, id 37590, offset 0, flags [none], proto UDP (17), length 73, bad cksum 0 (->e9cb)!)
127.0.0.1.53 > 127.0.0.1.21070: [bad udp cksum 0xfe48 -> 0x43fa!] 43981 q: A? example.com. 1/0/0 example.com. A 93.184.216.34 (45)
0x0000: 0200 0000 4500 0049 92d6 0000 4011 0000 ....E..I....@...
0x0010: 7f00 0001 7f00 0001 0035 524e 0035 fe48 .........5RN.5.H
0x0020: abcd 8180 0001 0001 0000 0000 0765 7861 .............exa
0x0030: 6d70 6c65 0363 6f6d 0000 0100 01c0 0c00 mple.com........
0x0040: 0100 0100 0151 8000 045d b8d8 22 .....Q...].."
^C
2 packets captured
23 packets received by filter
0 packets dropped by kernelהפלט מראה כיצד הבקשה לפתור את הכתובת example.com התקבל ועובד בהצלחה על ידי שרת ה-DNS.
כעת כל שנותר הוא להפעיל את השרת שלנו בדפדפן Firefox. לשם כך, עליך לשנות מספר הגדרות בדפי התצורה about: config.

ראשית, זו הכתובת של ה-API שלנו שבה הדפדפן יבקש מידע DNS network.trr.uri. כמו כן, מומלץ לציין את כתובת ה-IP של הדומיין מכתובת אתר זו עבור רזולוציית IP מאובטחת באמצעות הדפדפן עצמו מבלי לגשת ל-DNS ב network.trr.bootstrapAddress. ולבסוף, הפרמטר עצמו network.trr.mode כולל השימוש ב-DoH. הגדרת הערך ל-"3" תאלץ את הדפדפן להשתמש אך ורק ב-DNS-over-HTTPS לפתרון שמות, בעוד שה-"2" האמין והמאובטח יותר ייתן עדיפות ל-DoH, וישאיר את בדיקת ה-DNS הרגילה כאפשרות חזרה.
5. רווח!
האם המאמר היה מועיל? אז נא לא להתבייש ולתמוך בכסף באמצעות טופס התרומה (למטה).
מקור: www.habr.com
