URLにおけるIPv6ゾーン指定は『設計ミス』ではないか?
IPv6 zones in URLs are a mistake
IPv6 zones in URLs are a mistake
URLの仕様としてIPv6のゾーンIDを含めるのは、結局のところ避けるべき誤りだったのではないかという議論です。ネットワークインターフェースを指定するための仕組みとはいえ、パーサーやパーマリンク実装に不要な複雑さを持ち込み、セキュリティ上の懸念や表記ゆれを招いています。皆さんはこの実装についてどう考えますか?
ホストとポートを区別するために、IPv6アドレスは通常角括弧で囲むことになってるんだ。つまり、ポート80のfe80::4ならこうなる。
素直にドットのままにしておけばよかったのに。いや、どうしても変える必要があるなら、ポート番号を分けるための記号をいっそのこと変えて統一してほしかったよ。
正しいスコープを付けるとこうなるんだよね。
で、ここにURLエンコーディングまで混ざってくると……。
この辺りで心拍数が上がってきて、過呼吸になりそうだったよ。
もう、見た目通りの悪夢だって受け入れるしかないな。
「IPv6は奇妙だ。規格の中で最も奇妙な部分の一つは、全てのインターフェースのリンクローカルアドレスがfe80::なんとかであることだ」
これがどうしてIPv6特有の奇妙さなんだ?IPv4でも全く同じことだろう。ネットワークインターフェースが2つあれば、専用のIP範囲を割り当てるか、何らかの識別子を追加して、どちらがどちらかを識別しなきゃいけないんだから。
アドレスの一部にゾーンを入れる仕様自体が間違いだったというのは同意するけど、ユーザーが任意のインデックスを選べる場合にアドレスの競合が起きるのは、IPv6の設計欠陥ではないよ。
URLエンコーディングで文句言ってるの?UNCエンコーディングを見てみなよ……。
https://devblogs.microsoft.com/oldnewthing/20100915-00/?p=12... (https://devblogs.microsoft.com/oldnewthing/20100915-00/?p=12863)
\fe80--1ff-fe23-4567-890as3.ipv6-literal.net\share
ライブラリでIPv6のバリデーションを実装していた時に、似たような問題にぶち当たったよ。inet_ptonみたいなシステム関数をそのまま呼ぶと、有効なゾーン識別子がOS依存で制限されるんだよね!それは理想的じゃないから、かなり緩いゾーンID生成器を持つIPv4/IPv6パーサーを自作することにした。そのライブラリはURLもサポートしてたんだけど、このエッジケースでIPv6リテラルをパーセントエンコーディングとしてパースする実装はしなかった。でも結果的に両方のパターンでうまく動くようになったんだ。これが良いことかって?たぶん違うね。厳格なサブセットを選ぶ方が良かったかもしれない。ただ、どちらが良いかはユースケースによるし、残念ながら完璧な答えがない時もあるよね。
それはちょっと飛躍しすぎじゃないかな。第一、IPv4ではこのシナリオ自体が全く扱えないんだ。これはIPv6の機能なんだから、存在することに感謝しよう。アーメン。
それに、もしインターフェースIDを使いたくないなら、ネットワークでULAを有効にすれば、ルーティングが正しいインターフェースに導いてくれるよ。
話はもっとひどいことになるよ。
Pythonのipaddressライブラリには、渡された文字列が有効なIPv4/IPv6アドレスなら対応するオブジェクトを返し、無効ならValueErrorを投げるip_address関数がある。
ユーザー入力の文字列をコマンドラインに渡す前に、それが有効なIPかどうかを調べるためにこの関数を使っているコードをよく見かける。一見問題なさそうだけど、IPv6のゾーンIDにはシェルで使えるメタ文字が有効な場合があるんだ。
fe80::1%a;whoami>${PATH:0:1}tmp${PATH:0:1}pwned は有効なIPv6アドレスとして扱われる。だからもし ping fe80::1%a;whoami>${PATH:0:1}tmp${PATH:0:1}pwned を実行すれば、whoamiの結果が /tmp/pwned に書き出されてしまう。
もちろん、適切な実行方法(subprocess.Popenでshell=Falseにするなど)を使わずにユーザー入力をシェルコマンドに入れるようなコードを書くべきじゃない。でも、「バリデーションしたから大丈夫」と思い込んで、実はバリデーションが不十分でハックされるケースは多いんだよ。
追記:念のため補足すると、攻撃用ペイロードで ${PATH:0:1} を使っているのは、ゾーンIDで / が使えないからだ。${PATH:0:1} は、PATH環境変数の先頭文字を使って / を生成するトリッキーな方法だよ。
fe80::なんとかってリンクローカルだけで、リンクローカルはSLAACでルーターと1対1通信するためのものだと思ってたんだけど。
ユニークローカル(ULA)を取得した後に、通常のルーティング用に使われるんだよね?
認識が間違ってるかな?
「じゃあ、fe80::4宛のパケットがある場合、どうやって区別するのか?」
ルーティングテーブルで宛先までは辿り着けるけど、この質問の意図は「どの送信元アドレスを使うか」、つまりソースとしてどのネットワークカードやインターフェースを使うべきか、ってことだろう。結局どれもfe80::なわけだし。
fe80::宛の通信では、OSが適切なインターフェースを選んでくれる(事実上のARPのIPv6版だ)。
fe80::をリンクローカル以外のネットワーク宛の送信元にすることはないよ。もし明確なルートがなくて送信元アドレスを(自分のアイデンティティとして)設定していないなら、デフォルトゲートウェイやGoLRなんかに送ることになる。送信元アドレスには、複数あるうちのどれかを使うことになるだろうね。
とにかく、ここでの問題はこれだよ。
「でも、GoでこれをURLとしてパースしようとするとエラーになる」
Goを修正しないとだね!