世界を動かす技術を、日本語で。

SSHはなぜキー入力ごとに100パケットを送信するのか?

概要

  • SSHセッション での パケット通信量 の増加原因調査
  • Keystroke timing obfuscation による 大量の“chaff”パケット 発生
  • ゲームサーバー での パフォーマンス低下 とその対処法
  • Goのsshライブラリフォークして拡張機能を無効化 し大幅な改善
  • LLMによるデバッグ支援 の体験と考察

SSHセッションとパケット量の謎

  • tcpdump でSSHセッションを解析した結果、 1回のキーストローク で非常に多くのパケットが送信される現象を確認
  • 36バイトのデータパケット が多数、 0バイトのACKパケット が約3割、 ごく少数の他サイズパケット が観測
  • 1つのキーストローク約90パケット/秒 という高頻度通信を記録

実験環境と問題発見

  • 高性能ゲームssh経由 で動作させ、 bubbleteawish を利用したTUIを実装
  • 数百のbot をssh接続し、 1秒ごとに操作 させてパフォーマンス計測
  • テスト中に 「your screen is too small」 という1回のメッセージのみ送信する状態となり、 CPU・帯域使用量が半減
  • ゲームデータ未送信でもCPUが0%にならない ことに違和感を覚え、詳細調査へ

詳細調査とtcpdumpの分析

  • ゲームデータ送信時送信停止時 のパケットキャプチャを比較
  • 送信停止時でも36バイトパケットが20ms間隔で大量発生 していることが判明
  • MacOSの標準sshクライアント で同様のパターンを再現確認

原因の特定:Keystroke Timing Obfuscation

  • ssh -vvv でログを確認し、 obfuscate_keystroke_timing 機能が 20ms間隔でchaffパケット を送信していることを発見
  • 2023年以降のssh で導入された Keystroke Timing Obfuscation が原因
    • タイピング速度から入力内容を推測されるリスク を低減するため、 ダミーパケット(chaff) を大量送信
  • ゲーム用途 では 大量の無駄な通信とCPU負荷 の原因

対策と効果

  • sshクライアント側ObscureKeystrokeTiming=no を指定するとchaff送信が停止し、 CPU・帯域が大幅削減
  • Goのsshライブラリ (crypto/ssh)で [email protected]拡張の広告を停止 するパッチを作成
    • SSH2_MSG_PING メッセージを送らせないことでchaff通信を根本的に防止
  • パッチ適用後の効果
    • CPU使用率 :約30%→11%
    • システムコール時間 :3.1s→0.66s
    • 暗号処理時間 :1.6s→0.11s
    • 帯域 :6.5Mbit/sec→3Mbit/sec
  • フォーク運用のリスク はあるが、 劇的なパフォーマンス向上 を実現

LLMによるデバッグ体験

  • Claude Code などのLLMを活用し、 tcpdumpやtsharkの出力解釈 を迅速化
  • LLMの提案 と自分の知識を組み合わせて問題解決
  • ChatGPT の誤った提案も経験し、 LLMとの対話力や試行錯誤の重要性 を再認識
  • LLMは万能ではないが、強力な補助ツール として活用できる実感

まとめ

  • SSHのKeystroke Timing Obfuscationセキュリティ向上 には有効だが、 高頻度通信が必要な用途 では 大きなオーバーヘッド
  • Goのsshライブラリの拡張広告を止めることで根本解決 が可能
  • LLMを活用したデバッグ は、知識の補完や効率化に有効
  • 実践的な問題解決力と新しいツールの習熟 が今後ますます重要

Hackerたちの意見

あの20msは決定的な証拠だね。さっき見た謎のパターンとぴったり一致してる!それはさておき、クラウドがその「決定的な証拠」って言葉を使いすぎじゃない?デバッグの質問をすると、バージョン番号とか何かランダムなことを「決定的な証拠」って言う気がする。

決定的な証拠、まさにその通りだね。いい質問だし、エムダッシュ、「fooだけじゃなくて、barもある」って、本当の真実、厳しい真実、問題を浮き彫りにして、掘り下げて、またエムダッシュ… もう気持ち悪いよ。

それについて掘り下げてみたいな。 https://pshapira.net/2024/03/31/delving-into-delve/

LLMのパーソナリティ調整がどうなってるか分からないけど、興奮しやすさ(興奮したフレーズを使う傾向)が強くなってるんじゃないかな。「決定的な証拠」っていう言葉は、興奮度が高い評価を受けてるはず。他にも「素晴らしい!」や「いい発見!」、「その通り!」みたいなフレーズにも当てはまると思う。

ChatGPTもそうだよね。「ぴったり一致してる」って言うけど、実際には何とも一致してないし。

クロードは私との会話でこれを一度も使ったことがないと思う(Claude Desktop、Claude Code、音声会話...)。お世辞、確かに!もしかしたら、あなたのプロフィールや記憶に関係があるのかも?

はい!この投稿は完全に私が書いたけど、昨夜クレードとデバッグにかなりの時間をかけたから、「スモーキングガン」を用意してたとしても驚かないな。

特定のフレーズやmdashesをよく見るかもしれないけど、…これらのプログラムは人間が書いたデータ(またはマイクロソフトのスペル修正)で訓練されてるから、過去数年でそれを使いすぎたんじゃないかな?じゃあ、これらの可哀想なLLMたちは代わりに何を生成すればいいの?

それとも「ユーレカ!それはただのスモーキングガンじゃなくて、LLMspeakの典型的なケースだ。」って感じかな。Grok、ChatGPT、Claudeはみんなこういう癖があって、プロ版でも回答の中でその特徴的なフレーズを何度も使うんだ。これが意図的なのか、AIを検出しやすくするためなのか、ちょっと気になるね。

ちょっとしたSREエージェントを作って、オンコールのためにチケットに情報を事前に入れようとしてるんだけど、もうClaudeが「決定的証拠」を見つけるのに疲れちゃった。

彼らはクリシェが大好きで、同じ言葉を繰り返すのが嫌いだから、「原因」とか言った後に「決定的証拠」って言ったり、また別の何かに変わったりするんだよね。

TCP_CORKを使うと、遅延を増やさずにパケット数を減らせるよ。TCP_NODELAYを無効にするのもパケット数を減らせるし、ポータブルで実装も簡単だけど、遅延のペナルティが発生するね。

えー、TCP_CORKって初めて聞いた!ピングを無効にしなくても、もっとたくさんのパケットを受け取るコストは払うことになるけど、そんなにたくさんのポンを送らなくて済むなら、耐えられるかも。これはめっちゃ便利そうだね。いじってみるのが楽しみ!TCP_NODELAYについては知ってるよ(面白いことに、最近HNにTCP_NODELAYについて投稿したばかりなんだ、ここで書いたゲームのことを考えてた時にね)。でも、これを無効にした時のレイテンシーが自分には合わないと思う。

TCP_CORKって聞いたことなかった、すごく興味深いね。ググりたくない人のために説明すると:1. ソケットをTCP_CORKする 2. データを入れるとカーネルがバッファする 3. ソケットのコルクを抜くか、バッファがMSSに達するとカーネルがパケットを送信する。基本的に、カーネルはフルパケット分のデータが揃うまで待つか、送信するデータがもうないと言うまで待ってから送信する感じ。極端なTCP_YESDELAYってところかな。詳しくは https://catonmat.net/tcp-cork で学んだよ。

いろんなトピックに触れてて、すごく楽しめた!それに、SSHが過去にキーストロークのタイミングに脆弱だったなんて知らなかったよ!

https://news.ycombinator.com/item?id=37307708 ここで2023年の議論があるよ。

明らかに、Goの暗号ライブラリをフォークするのはちょっと怖いし、安全な方法で自分のパッチを維持する方法を考えないといけないね。これ、sshライブラリのオプションとして上流に上げるべきだよ。信頼できない環境では、無駄なデータを送るのがデフォルトでいいけど、帯域幅を節約できる場所もたくさんあるからね。

+1… SSHがコンピューター間通信にどれだけ使われているかを考えると、必要ない時にこれを無効にする方法が本当にあってもいいと思う。

そうだね、でもその変更が却下されても驚かないよ。暗号ライブラリはかなり独自の考え方があって、例えばTLSの暗号スイートの順序を設定することもできないし。

信頼できる環境でも信頼できない環境でも脅威は存在するよね。これってSSHのすごくニッチな使い方に感じる。もっと広く公開すると、設定して放置するような状況になって、結局誰かのセキュリティが下がっちゃうかも。

もうその状態になってるよ。この挙動はTTYのあるセッションにだけ適用されて、クライアントはそれを無効にできるから、これは理にかなったデフォルトだね。この特定のユースケースは明らかに混乱させてるけど、サーバーは接続が重要じゃないって事前にわかってるから、隠す必要がないし、これは典型的なターミナルセッションじゃない。でも、ほとんどの他のシナリオでは、その判断をする方法がなくて、クライアントはObscureKeystrokeTimingが尊重されることを期待してるんだ。

すごく面白い!このオブフスケーションについては知らなかったから、クリックしてよかった。SSHの正確な動作をデバッグするためのもう一つの良い手法は、テスト環境用に「None」暗号化サポートをパッチすることだね。プロキシを設定するのと同じくらいの手間だけど、パケットの生の内容をテレネットのように見ることができる。セキュリティが重要でないターミナルゲームでは、パフォーマンスとスケールが重要だから、最初からテレネットを提供するのも考慮する価値があるかも。

追加されたときにフロントページに載ったよ。 https://news.ycombinator.com/item?id=37307708

2023年にsshがキー入力のタイミングを隠す機能を追加したんだ。これは、異なる文字を打つ速度が、どの文字を入力しているかの情報を漏らすっていうアイデアなんだ。だからsshは、キー入力と一緒にたくさんの「チフ」パケットを送って、攻撃者が実際にキーを入力しているタイミングを特定しにくくしてる。これって問題の解決方法としては間違ってると思う。もし本当にそれを望むなら、入力した文字を50msの間隔で送信して、タイミングの解像度を制限すればいいんじゃないかな。

追加で50msの遅延でタイピングするのはかなり不快だよね。

50ms間隔で入力されたすべての文字を送信する これって、パケットの間隔を20msから50msに変えるだけじゃない?それとも、50ms間隔でパケットの定常的なストリームを意味してるの?今の実装のアイデアは、キー入力が20msの間隔でバッチ処理されて、十分な長さの沈黙があればチフストリームが止まるってことだと思う。だから、キー入力のタイミングが20msの誤差範囲で隠される感じ。

LLMに依存するのは残念だね。この謎はWiresharkでパケットキャプチャを見ればもっと早く解決できたと思うよ。Wiresharkのディセクタはかなり成熟してるし、SSHも結構しっかりカバーされてるから。

その賭けにはどれくらい賭けてるの?

残念ながら、SSHに関してはディセクタがあまり成熟してないんだ。KeXの完了メッセージ(NEWKEYS)までは有効な解析ができるけど、その後はカスタムパッチで暗号化をnoneに設定しても、メッセージの流れは解析されない。セッションキーをダンプするのは全然一般的じゃないからみたい。まあ、努力次第なんだけど、誰かがSSHのディセクタのために改善する時間をかければ、ほとんどの基盤は整ってると思うよ。

ゲートキープしてるね。人々が正確なアプローチを知る代わりに、調査を助けるツールを使うなんて、神様が禁止してるみたいだ。

ほとんどの場合LLMには反対だけど、> この謎はWiresharkでパケットキャプチャを見ればもっと早く解決できたと思う。Wiresharkを使い慣れていて、何を探せばいいか分かっている人には、たぶんそうだろうね。でも、技術者の大多数にはそうじゃないと思う。私の場合、tcpdumpで単一のキー入力のパケットキャプチャをして、それをWiresharkにインポートしたら、200以上の「クライアント:暗号化パケット」と「サーバー:暗号化パケット」のエントリーが出てきた。全然役に立たなかったよ。SSH接続のセットアップ全体をtcpdumpすると、同じくらい役に立たない情報が得られるけど、なぜか私の一回のキー入力でトリガーされたパケットよりも少ない。だから、LLMは全く好きじゃないし、今見られるLLMへの依存も嫌いだけど、このケースでは著者が面白いことをたくさん学んで、それを私たちと共有してくれた。LLMがなければ、ただ肩をすくめて次に進んでいたかもしれない。

OPの経験的かつ分析的な厳密さは素晴らしいね。彼はLLMを最適な方法で使った:不器用なコマンドラインフラグやプロトコル実装でギャップを埋める。そんなことは常に頭に入れておく必要はないから。

LLMにSSHについて聞くと(ヒント:二つのSはセキュリティを意味する)、Wiresharkでパケットキャプチャだけでは何もわからない理由がわかるよ。

Claudeがデバッグにどう役立ったのか全然分からない。著者は何をすべきか知っていたみたいで、Claudeにそれを考えさせているだけのように見えた。私も少しClaudeを使ったけど、そんな風に話しかけてくることはなかったよ、「おお、すごい!」とか。実際の人と話すよりもイライラする感じ。もしかしたらAIは入力テキストから性格を感じ取るのが得意で、私の簡潔なプロンプトにはこういう風には反応しないのかも。

チャットボットがRubber Ducky [1]としてだけ機能しても、それはすでに価値があるよ。システムの挙動をデバッグするためにClaudeを使ったことがあるけど、著者に賛成かな。Claudeはいつも直接的に役立つわけじゃないけど(幻覚が残ったり、古い情報が出たりするし)、1) システムの理解を言葉にする手助けをしてくれるし([1]を見てね)、2) タスクを提供してくれるから momentumを保てるんだ。 [1] https://en.wikipedia.org/wiki/Rubber_duck_debugging

AIはテキストから人の性格を読み取るのが得意だよね。Claudeはここでうまくやったし、著者は「すごい!」ってコメントが嬉しかったみたいで、それをブログ記事に載せてたよ。これはただの冗談じゃなくて、ボットたちは素晴らしいおべっか使いだってことを言いたいんだ。

うーん、セキュリティはSSHの最優先事項だけど、著者がセキュリティを必要としないなら、なんでSSHを使うの?例えば、「nc」(netcat)はSSHがあるすべてのプラットフォームにプリインストールされてるよ。

例えば、「nc」(netcat)はSSHがあるすべてのプラットフォームにプリインストールされてる。これは技術的に間違ってるよ、だってWindowsにも今はSSHが含まれてるから!

SSHとは関係ないけど、eieio.gamesのウェブサイトで他の人のモニターもちらつく?ウェブサイトがフルスクリーンになると何かが圧倒してるみたい。モニターのバックライトが壊れたのかと思ったよ。