セキュリティを楽しく学ぶ、触れる。セキュリティごった煮ブログ

ネットエージェント
セキュリティごった煮ブログ

 コース:元祖こってり

「元祖こってり」記事はネットエージェント旧ブログ[netagent-blog.jp]に掲載されていた記事であり、現在ネットエージェントに在籍していないライターの記事も含みます。

DNSSEC応用の一例「Phreebird Suite」の紹介

山口尋久

 こんにちは、ネットエージェント株式会社、大阪の山口です。
 今回は、先日の Black Hat Abu Dhabi 2010 で Dan Kaminsky の発表 において使用されたツール群 Phreebird Suite について紹介させていただきます。
 Dan Kaminsky は Black Hat での発表(スライド)にて、まず問題提起として「異なる組織間の認証」が困難であることを挙げています。組織内での認証は設定が容易であるのに対し、組織外から来た人を認証する仕組みは、まだ必要に応えるに十分整備されてるとは言い難いでしょう。そのため、政治的に介入が難しく技術的にも制約されており、既に十分に普及していて、なおかつ、それなりに信頼されている DNS に、DNSSEC の機能が備わったら認証として使えるのではないか、というのが彼の提案であり、そのコンセプトコードが Phreebird Suite というツール群です。
 「異なる組織間の認証」にDNSを使うという彼の提案内容もなかなか興味深いものですが、発表にもその一部しか記述されていないこともあり今後の発表を待つとして、今回は公開されたツール群 Phreebird Suite について紹介したいと思います(興味がある方は彼のスライド資料もご参照ください)。

-----

 Phreebird Suite の中でも特に DNSSEC の導入を容易にできるのか、という問いに対する答えとして提示されているのが、phreebird コマンドです。phreebird コマンドは権威サーバとして設定された DNS サーバの前段に設置して、DNSSEC 署名を代行するプロキシとして動作します。既存の DNS コンテンツサーバでの署名が何らかの理由でできない場合に、当面の回避策として導入できます。
 以下に phreebird コマンドを試した経過を記します。テストには FreeBSD のインストールされた PC を使用しました。本稿執筆時点での最新バージョンは 1.02 です。
 アーカイブを展開したら、各ファイルがCRLF(改行)になっていたので、行末のCRを削除しました。また、依存ライブラリ類のtar ballが配布アーカイブに含まれていますが、今回は ports システムを使ってインストールしました。

# portmaster dns/ldns devel/libevent devel/libghthash dns/unbound

 さらに、検証の都合上、ポート番号を指定できた方が都合がよかったのでコマンドラインオプションを追加したり、(libevent のドキュメントによると event_free() より event_del() がふさわしいようだったので)コードを若干書き変えたり、コンパイルエラーが出ないように修正するなどして、以下のようなパッチをあて、ビルドしました。

(※)編注:以下、1行を折り返す場合は[折返し]と表示しています。

--- ./phreebird.c.orig  2010-11-19 14:55:00.000000000 +0900
+++ ./phreebird.c 2010-11-19 14:55:00.000000000 +0900
@@ -61,6 +61,7 @@ struct phreebird_opts_struct
unsigned short http_port;
// for TCP
int listensock;
+ int listenport;

};
typedef struct phreebird_opts_struct phreebird_opts;
@@ -163,7 +164,7 @@ int main(int argc, char **argv){

set_defaults(opts);

- while ((c = getopt(argc, argv, "k:gdb:?m:")) != -1) {
+ while ((c = getopt(argc, argv, "k:gdb:?l:m:")) != -1) {
switch (c){
case 'k':
LDNS_FREE(opts->dnskey_fname);
@@ -186,6 +187,9 @@ int main(int argc, char **argv){
//opts->backend_ip = strdup(optarg);
//if(p){opts->backend_port = atoi(p+1);}
break;
+ case 'l':
+ opts->listenport = atoi(optarg);
+ break;
case 'm':
nsec3_rater.max = atoi(optarg);
break;
@@ -332,9 +338,9 @@ int init_udp_socket(unsigned short port)

int init_sockets(phreebird_opts *opts){

- opts->stubsock = init_udp_socket(53);
+ opts->stubsock = init_udp_socket(opts->listenport);
opts->backsock = init_udp_socket(0);
- opts->listensock = init_tcp_socket(53);
+ opts->listensock = init_tcp_socket(opts->listenport);
return 1;
}

@@ -460,7 +466,7 @@ void clientcallback(int clientfd, short
amount_read = read(clientfd, &to_read, 2);
if (amount_read < 2) {
close(clientfd);
- event_free(store_cache->clientevent); // FREE2
+ event_del(store_cache->clientevent); // FREE2
return; // MIDRET, ok because no alloc before here
}
buffer = calloc(to_read, 1); //ALLOC3
@@ -1157,7 +1163,7 @@ int do_sign(ldns_rr_list *dest, ldns_rr_
bool rate_exceeded=false;


- if(ldns_rr_list_rr_count(src)==0){ return; }
+ if(ldns_rr_list_rr_count(src)==0){ return 0; } // make compiler happy

// XXX *must* allow cache size to be specified, [折返し]
and allow records to expire!
if(rrsig_cache == NULL){
@@ -1342,6 +1348,7 @@ void do_help(){
fprintf(stdout, "Options:
");
fprintf(stdout, " -k : Filename of private key (default: dns.key)
");
fprintf(stdout, " -d : Activate debugging
");
+ fprintf(stdout, " -l : [折返し]
Use the given port for listen instead of port 53
");
fprintf(stdout, " -m : Set max [折返し]
# of unique NSEC3 responses to sign a second (Default: 200)
");
fprintf(stdout, "Dangerous Options:
");
fprintf(stdout, " -g : Generate private key
");

 ビルド後、-?オプションを付けて phreebird コマンドを実行すると、使い方が表示されます。

$ bin/phreebird -?
Phreebird 1.02: DNSSEC Supplementation Engine.
Author: Dan Kaminsky, dan@doxpara.com
WARNING: THIS IS EXPERIMENTAL CODE THAT SHOULD NOT BE.
DEPLOYED ON PRODUCTION NETWORKS. Yet.
Options:
-k : Filename of private key (default: dns.key)
-d : Activate debugging
-l : Use the given port for listen instead of port 53
-m : Set max # of unique NSEC3 responses to sign a second (Default: 200)
Dangerous Options:
-g : Generate private key
-b backend_ip:backend_port : Declare the location of the backend to proxy
(DO NOT ALLOW INTERNET TO SPOOF BACKEND TO PHREEBIRD.)

 表示にしたがって -g オプションを付けて実行すると、DNSSEC のためのキーデータが作成されます。



$ bin/phreebird -g
Generated key: dns.key. Restart without -g.
$ cat dns.key
Private-key-format: v1.2
Algorithm: 7 (RSASHA1_NSEC3)
Modulus: viLKSDOnIjGY52is9XyTJhY4LG2FCHP7bA8
VWY5g+ChoHI9omMmChln7vQXJfEL0VyWjmbHE2xWzkhx
lS8IJvnly0W0I2iyavofFXCzM3BeRvKcgLHdP7f8bNXA
lDUoPu2Gz7pd+S7EoRmVI/zxKjgJfsyUeawemLdAhUlw
kfaM=
PublicExponent: AQAB
PrivateExponent: Rjb+0I8Sp5P9TWfgh3+Lr8MA15d
SS37ZWFxxm/LyaHIzkGh9Tf8MjqToTDO45oSrSwuBUR7
O/cET4V9PIRz1D5k3VAphD+ok7KHzsbZVMPkAo/Sc5Gs
wIO/XfMM7RJQorrxUKN1ynILfZXc0ynEQm0FQf913Rgn
RQ9Q1WgTs4YE=
Prime1: 4eM+pW6qHxEEUpxZBkWP5MEjzupU582Sxcst
GIfUbhtSEM8JS3Kii8QR23EeiOtWysL01HFwt3wSjVzP
RY1HAw==
Prime2: 13t2ad9tAncElJEpzQg5qhelwdcZCQpcv1Oq
Nv4NQ6UBlLrIuG34QEff7p/R6icsBuTvFBMvJcFpqvdN
sHxc4Q==
Exponent1: d0YTtSy6/Y5xtuFBjLM8aLCnJMHNNVzyL
Ci9Vh+axsz8R03a/ZC5TY2pVDLlyaxidsv8lRSVTP1hm
m0wMOyJWw==
Exponent2: GIm3r1DBEiHJhL2PHAkOv/7XYl6DPFNQw
nzdikud6REWQACRMOdc+Lz2lC7g8aAqVFKnowqYON1wk
gZ9c1aGIQ==
Coefficient: TWr93R5sESCkQBN6lkgjc9ulRbPFsUF
kkhUnTjJeFNSizgRq5liiEcHsxmNWwU1TSRq2wUlPfuM
OhFtWNvFB1A==

 これを使って、phreebird が DNSSEC プロキシとして動作する様子を確認します。テスト用にゾーン情報を以下のように作成しました。

$ cat /usr/local/etc/nsd/netagent.example.zone
$ORIGIN netagent.example.
$TTL 86400

@ IN SOA ns.netagent.example. root.netagent.example. (
2010111902 ; serial number
8h ; refresh
2h ; retry
10d ; expire
1d ; min ttl
)

NS ns.netagent.example.
MX 10 mail.netagent.example.

ns IN A 192.0.2.1
mail IN A 192.0.2.1

 これを、適当なコンテンツサーバに登録し、50053番ポートで待ちうけます。

$ dig +noquestion +norec +multiline [折返し]
-p 50053 @localhost netagent.example ns
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49693
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; ANSWER SECTION:
netagent.example. 86400 IN NS ns.netagent.example.

;; ADDITIONAL SECTION:
ns.netagent.example. 86400 IN A 192.0.2.1

;; Query time: 0 msec
;; SERVER: 127.0.0.1#50053(127.0.0.1)
;; WHEN: Fri Nov 19 15:56:22 2010
;; MSG SIZE rcvd: 83

 phreebird コマンドを起動します。-b 127.0.0.1:50053 はデフォルト値なので、省略可能です。

$ bin/phreebird -b 127.0.0.1:50053 -l 10053

 動作を確認します。

$ dig +nocmd +noquestion +norec +multiline [折返し]
-p 10053 @localhost netagent.example ns
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34472
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; ANSWER SECTION:
netagent.example. 86400 IN NS ns.netagent.example.

;; ADDITIONAL SECTION:
ns.netagent.example. 86400 IN A 192.0.2.1

;; Query time: 3 msec
;; SERVER: 127.0.0.1#10053(127.0.0.1)
;; WHEN: Fri Nov 19 16:09:33 2010
;; MSG SIZE rcvd: 118

$ dig +nocmd +noquestion +norec +multiline [折返し]
+dnssec -p 10053 @localhost netagent.example ns
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56532
;; flags: qr aa; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 2

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; ANSWER SECTION:
netagent.example. 86400 IN NS ns.netagent.example.
netagent.example. 86400 IN RRSIG NS 7 2 86400 20101217071059 (
20101119071059 10970 netagent.example.
odQyPOP5s1b0dIeVr9SrFs7fZn0YQ2iMlxKLmaMwagZB
J8z3MK4PkK8kBXJKN6brMB77VtXlwTgYpkgWn+y/oUpb
W7yKm6R7xZVxaBKE45E/DL4A7FkTS06yPf8p0peB0nQ7
F3+QycFArFSC/nEMnsQWBza7eL59sjoW0/2eJg8= )

;; ADDITIONAL SECTION:
ns.netagent.example. 86400 IN A 192.0.2.1

;; Query time: 3 msec
;; SERVER: 127.0.0.1#10053(127.0.0.1)
;; WHEN: Fri Nov 19 16:10:59 2010
;; MSG SIZE rcvd: 321

 +dnssec で問い合わせると RRSIG RR が付加されています。
 存在しない名前を引くと、NSEC3 で不存在を示す応答が返ってきます。

dig +nocmd +noquestion +norec +multiline [折返し]
+dnssec -p 10053 @localhost [折返し]
nonexistent.netagent.example
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 16472
;; flags: qr aa; QUERY: 1, ANSWER: 0, AUTHORITY: 8, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; AUTHORITY SECTION:
netagent.example. 86400 IN SOA ns.netagent.example. root.netagent.example. (
2010111902 ; serial
28800 ; refresh (8 hours)
7200 ; retry (2 hours)
864000 ; expire (1 week 3 days)
86400 ; minimum (1 day)
)
netagent.example. 86400 IN RRSIG SOA 7 2 86400 20101217071342 (
20101119071342 10970 netagent.example.
LbDxWtMJSwbhP/zlLKs15SYZ7SCu8XRE3hnEzYjIp/HT
fqhw4wU/Bp+GUs4hqAJdm8C5dM/e8TE/OkzF8frMvRZO
AyCgKpE61TO2dPfQ6JCrBlXPgIELXN1PFJKHatGBPgzy
C02NCszJiSEE2vB44TymiGN2pAz0eUaZPUr8z5U= )
4oj9jsupshjq974ju9ojat1n4f89b6eq.netagent.example. 0 [折返し]
IN NSEC3 1 0 1 1290 4OJ9JSUPSHJQ974JU9OJAT1N4F89B6ES A RRSIG
4oj9jsupshjq974ju9ojat1n4f89b6eq.netagent.example. 0 [折返し]
IN RRSIG NSEC3 7 3 0 20101217071342 (
20101119071342 10970 netagent.example.
U3f/cjjOHqswTClgxVyOkKugpGwNHYGnFCR3i10mHHjj
kH9QVVTp/5lJZhPABOkhMByk1lO1xZMZo9Va5LA3oEGD
hc2lmRNUtU/1eEjFL9mEH3Ig55U6TH0WauTivyxCQfYv
EcdTWaE7O6znudNHhYGGowvSm2jMD3C0w0g+MTo= )
oj4tc3knsrbm09idouc2872s5u50jplu.netagent.example. 0 [折返し]
IN NSEC3 1 0 1 1290 OJ4TC3KNSRBM09IDOUC2872S5U50JPLV [折返し]
RESERVED0 A NS CNAME SOA NULL WKS PTR HINFO MX TXT AAAA [折返し]
LOC SRV NAPTR CERT DS SSHFP IPSECKEY RRSIG NSEC DNSKEY [折返し]
DHCID NSEC3 NSEC3PARAM SPF
oj4tc3knsrbm09idouc2872s5u50jplu.netagent.example. [折返し]
0 IN RRSIG NSEC3 7 3 0 20101217071342 (
20101119071342 10970 netagent.example.
Uz9CdmXwfpXw0gumfl/Q2+jL7EQ2oA55sG0e2R/doYeH
tkuJvPauyPfharlsEBfeREBIvVdU87PTeb7T5Foo8Qf/
LG3TLYQIXPQtw3lX2FM2SLEGRiQ5pClwADROCs/c4KcM
vK1q1pAeDeKHwt8Ii5Lc98NfZHkLab6+QXeI6gg= )
0udor1jnobkp2u1olrmj3ot86aejv62c.netagent.example. 0 [折返し]
IN NSEC3 1 0 1 1290 0UDOR1JNOBKP2U1OLRMJ3OT86AEJV62E A RRSIG
0udor1jnobkp2u1olrmj3ot86aejv62c.netagent.example. 0 [折返し]
IN RRSIG NSEC3 7 3 0 20101217071342 (
20101119071342 10970 netagent.example.
vDszM6GVvRK3eqUMJZ3L81U9jt0ZlyHi9Mh43gZIS2Cz
c+IQMKFHdjyo2dFT5sCIyaLWpOK09qm+roEiwg8ZNfdk
hMPxqR+gmd4yxuTXuNe22JFo1dKXO88U1ykotA6MokC9
cjdRjmBeOASzsyFgx71vJoOE/X7hdEq48lunX9I= )

;; Query time: 5 msec
;; SERVER: 127.0.0.1#10053(127.0.0.1)
;; WHEN: Fri Nov 19 16:13:42 2010
;; MSG SIZE rcvd: 1318

 DNSSEC の署名確認をします。phreebird が生成するDS RRを上位ドメインのサーバに登録してもらう必要がありますが、本稿ではトラストアンカーとしてリゾルバに設定します。まず、DS レコードを取得します。

dig +nocmd +noquestion +nostats +norec +dnssec [折返し]
-p 10053 @localhost netagent.example ds
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33236
;; flags: qr aa; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; ANSWER SECTION:
netagent.example. 3600 IN DS 10970 7 1 [折返し]
4CEA2D546BA9100B59FFBA3683FFCF3B9AE0FCB0

netagent.example. 3600 IN RRSIG DS 7 2 3600 [折返し]
20101217073730 20101119073730 10970 netagent.example. [折返し]
u0xLahDWHEuLvvvBEtYfnE/kQgcL1HTLUgK9uLM67CGbSJt7Dc9p8NTp[折返し]
Ejza0etHJLPNpUqDMkhL0N2BEU9b2DPL8Z/pONP8/e8Kjj1uP31x+gp3[折返し]
v+G9omBQ8uL0BMT/s506a8SfPq5BK/kFJMNO7MBl01rzlO4pNQk5Gc1O dSo=

 今回はリゾルバに unbound を使ったので、unbond.conf を変更します。

$ diff unbound.conf.sample unbound.conf
40a41
> interface: 0.0.0.0
296a298
> do-not-query-localhost: no
314c316
< # auto-trust-anchor-file: [折返し]
"/usr/local/etc/unbound/root.key"
---
> auto-trust-anchor-file: [折返し]
"/usr/local/etc/unbound/root.key"
332a335
> trust-anchor: "netagent.example. 3600 IN DS [折返し]
10970 7 1 4CEA2D546BA9100B59FFBA3683FFCF3B9AE0FCB0
"
498a502,505
>
> stub-zone:
> name: "netagent.example"
> stub-addr: 127.0.0.1@10053

 これで、自前ゾーンもDNSSECに(なんとなく)対応できました。

dig +nocmd +noquestion +multiline +dnssec [折返し]
@localhost netagent.example ns
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, [折返し]
status: NOERROR, id: 20876
;; flags: qr rd ra ad; QUERY: 1, [折返し]
ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; ANSWER SECTION:
netagent.example. 86385 IN NS ns.netagent.example.
netagent.example. 86385 IN RRSIG NS 7 2 86400 20101217090304 (
20101119090304 10970 netagent.example.
SbZ/YFhPJwlz2Bnz/Zc1tS1YQjQZc8z5ktgrXRuAHW66
Sxn/3wRLc+0b3GcJA7/E1rSQJqOj2b1mgGvDroWScwj0
aB1khnxWfrJqbkDa2vzvr9TOXRI6tQKKI7aSfLBjjX+W
CXDYnZ5uPtq/4FO3l31eG2MDSYnWt3/oB2mUyGQ= )

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Fri Nov 19 18:03:19 2010
;; MSG SIZE rcvd: 238

 プロキシとして署名をその場で付加することで、DNSSEC の導入は容易になるでしょう。RR不存在を示すNSEC3 RRはその都度生成され、実際の実在レコードとは関係ない名前を使いますが、これも実用上問題ないという割り切りかと思われます。
 DNSSEC プロキシとしての phreebird コマンドは、コンセプト提示用に最低限の実装しかなされておらず、署名済のドメインにも署名を付加したり、NSEC3 のハッシュが SHA1 だったり、レコードの存在するドメイン名に対する存在しないRRTYPEについての問合せへの応答がおかしかったり、NSEC3PARAM のような DNSSEC 特有の RR を返さなかったりします。既知の問題点や今後の展開については、アーカイブに付属の README.txt や HACKING.txt に書かれています。
 最後に Phreebird Suite の他のコマンドも簡単に紹介します。

-phreeload
 phreeload は LD_PRELOAD を使って OpenSSL の X509_validate_cert() を乗っ取り、DNSSEC でドメイン名が正しければ OK とします。

-unbound_trace
 unbound_trace は、DNSSEC チェックをします。unbound-host と比べてとくに利点があるというわけではなさそうです。

-ldns_chase
 ldns_chase は DNSSEC を上位ドメインまでさかのぼって確認し、ツリー表示します。dig +sigchase に比べて表示がコンパクトです。手許の環境では頻繁に bus error で異常終了しましたが...。

-phreeshell
 phreeshell は、OpenSSH の ssh(1) にパッチをあてたもので、DNS経由で認証を行なう拡張がなされています。Black Hat の発表スライドの6枚目から紹介されているものです。

-ldns-hvc
 ldns-hvc は、DNS トラフィックを http トンネルに通す拡張がなされています。具体的には、問合せ先のホストに対して、http://_host_name_/.well-known/dns-http?v=1&q=_query_ 形式で、_query_ 部分に、DNS 問合せパケットを BASE64 変換したものを渡します。phreebird コマンドはこの機能のデモのために 80 番ポートを listen します。

 実際に運用してみなければ本当のところは判断できませんが、これらのツール群で示されたように、DNSSEC によって組織間の認証が行えるようになるとすると、それは悪いものではないように思えます。

メルマガ読者募集 採用情報 2020年卒向けインターンシップ

月別