- SSHセッションで単一のキー入力時に数百個のパケットが送信される現象が見つかり、その原因を追跡した事例
tcpdump の分析結果、ほとんどのパケットは36バイトの繰り返しメッセージで構成されており、およそ20ms間隔で発生
- 原因は2023年にSSHへ追加された**「キー入力タイミング難読化(keystroke timing obfuscation)」機能で、ユーザーの入力タイミングを隠すため多数の「chaff」パケット(SSH2_MSG_PING)**を送信していたこと
- この機能を無効化するか、サーバーが
[email protected] 拡張を広告しないよう修正すると、CPU使用率と帯域幅が半分以下に減少
- SSHのセキュリティ機能が、**リアルタイム性能が重要なアプリケーション(例: ゲーム)**では大きな負荷として作用しうることを示す事例
問題の発見
- SSH経由で動作する高性能ゲームのTUIをテストしていたところ、単一のキー入力でも270個のパケットが発生する現象を確認
tcpdump の結果、66%が36バイトのメッセージ、33%がTCP ACK、残りは少量のその他データ
- 平均90パケット/秒、約11ms間隔でデータを送信
- テスト中、サーバーが “your screen is too small” メッセージだけを送るよう誤設定されていたが、このときCPUと帯域幅の使用量が半分に減少
- ゲームデータが送信されないならCPU使用率は0%近くであるべきだが、それでも50%程度を維持
- これによりSSH自体の通信オーバーヘッドの可能性が浮上
調査プロセス
tcpdump を使って正常動作時とエラー状態のSSHトラフィックを比較
- エラー状態でも36バイトのパケットが20ms間隔で継続発生
- macOS標準のSSHクライアントでも同じパターンを確認
- Claude Codeを使ってpcapファイルを分析した結果
- 全413,703個のパケットのうち66%が36バイト、34%が0バイトACK
- SSHクライアントが主導してパケットを生成していた
根本原因
解決方法
- クライアント側では
ObscureKeystrokeTiming=no オプションで機能を無効化可能
- 適用後、CPU使用率と帯域幅が大幅に減少し、データ送信も正常に維持
- サーバー側の対応として、GoのSSHライブラリで
[email protected] 拡張の広告を削除
- 関連コミットを取り消してテストした結果
- CPU使用率 29.9% → 11.6% ,
システムコール 3.10s → 0.66s,
暗号化演算 1.6s → 0.11s,
帯域幅 6.5Mbit/s → 3Mbit/s
- 性能が50%以上向上
LLMを活用したデバッグ経験
- Claude Codeを使って
tcpdump 、tshark の分析を自動化し、問題の原因を迅速に絞り込み
- コマンド実行の過程をリアルタイムで観察しながら、メンタルモデルの維持が可能
- ChatGPTはSSHの動作を「正常」と誤って判断するなど、モデル間の違いも経験
- LLMが問題解決の全工程を置き換えるわけではないが、補助分析ツールとして高い効率性を示した
- 人間の推論とLLMの分析を組み合わせて、複雑なネットワーク性能問題を解決した事例
1件のコメント
Hacker Newsのコメント
Goのcryptoライブラリをforkするのは少し怖く感じる。
自分の小さなパッチを安全に保つ方法を考えている。
正直、こういう機能はsshライブラリのオプションとしてupstreamされるべきだと思う。
信頼できない環境ではchaff(ノイズ)パケットを送るのをデフォルトにするのがよいが、帯域幅を節約したい場面も多い。
サーバーがクライアントに「不要だ」というシグナルを送れるオプションを追加し、クライアント側はそれを受け入れるか警告するようにするのが正しい解決策だ。
TTYセッションでのみ適用され、クライアントが無効化できる。
ただ今回は、サーバーがあらかじめ重要ではない接続だと分かっているために生じた例外的なケースだ。
ほとんどの場合、クライアントはObscureKeystrokeTiming設定が守られることを期待する。
cryptoライブラリは非常に意見の強いコードベースで、TLS cipher suiteの順序すら変更できないようになっている。
これはSSHのかなり特殊なユースケースに見える。
あまり広く公開すると、「設定して忘れる」状態になって逆にセキュリティを弱めるかもしれない。
1200bpsモデムで通信していた頃もあり、56Kモデムは実際にはかなり誇張だった。
1994年ごろにはイギリスの軍事大学で働きながらWWWに初めて触れたが、そのときは「いまいちだな」と思った。
今思うと本当に時代の変化には驚かされる。
このobfuscation機能は初めて知ったが興味深かった。
sshの挙動をデバッグするときは、「None」cipherをパッチしてパケット内容を直接見るのもよい方法だ。
セキュリティが重要ではなく性能が重要なターミナルゲームなら、単にtelnetを使うことも検討できる。
SSHがこういうことをしているとは知らなかった。
デフォルトで有効なのは理解できるが、自分の環境では無効にするのがよさそうだ。
だから
ObscureKeystrokeTiming=noに設定しようと思う。これをやるべきでない理由はあるだろうか?(1) 人がいつ秘密を入力しているかを常に見分けるのは難しく、活動全体がパターン分析され得る。
(2) これは大学の研究室レベルでも可能な攻撃だ — USENIX論文 と 研究事例 を参照。
(3) 動画トラフィックが支配的なインターネットで、キーストローク数バイトの節約のためにセキュリティを捨てるのは意味がない。
攻撃者がキーストロークのタイミングを分析すれば、コマンドや暗号化されたパスワードのパターンを推定できるかもしれない。
もちろんセッションキーは毎回異なるので復号は難しいだろうが、可能性はある。
自分もほとんどのパスワードはパスワードマネージャーからコピーして貼り付けている。
ほとんどの人はSSHのセキュリティ機能を無効にしても何の問題もないと感じるが、それは単に運が良かっただけだ。
本当に性能が必要ならTelnet、本当にセキュリティが必要ならContainerSSH + OAuth2の組み合わせを使うのがよい。
2004年にSSHセッションのキーストローク間隔分析でコマンドを推定する研究をしていた。
当時の分析資料 を参照。
2023年のパッチがついにその問題を解決したわけだ。
発表資料
本当に時が経つのは早い。
Claudeがデバッグに実際どれほど役立ったのかはよく分からない。
著者はすでに方向性を分かっていて、Claudeはただ相槌を打っていただけに見えた。
Claudeが「Holy Cow!」みたいなことを言うのは少し鼻につく。
自分もClaudeでシステムの挙動をデバッグするとき、直接の答えはなくても理解の整理とモチベーション維持には役立つ。
Rubber Duck DebuggingのWiki
著者が「holy cow」という反応を気に入ってブログに入れたのを見ると、Claudeは雰囲気をよく読んでいたようだ。
TCP_CORKを使えば、遅延なしでパケット数を減らせる。
TCP_NODELAYを無効にするのも方法だが、その代償として遅延の増加がある。
ソケットをcorkすると、カーネルがデータをバッファリングし、uncorkするかMSSに達した時点で送信する。
つまり、パケットをまとめて送る方式だ。
参考資料
pingはそのまま受けるだろうが、pongの送信回数は減らせそうだ。
TCP_NODELAYはすでに使ったことがあるが、遅延が大きくなって自分のゲームには合わなかった。
以前のHN記事
obfuscationの目的上、**遅延の合体(coalescing)**は不可能そうだ。
「The smoking gun!」という表現が面白かった。
英語ネイティブではないが、Claudeがよく使うので初めて覚えた。
今では本当に流行語のように広まっている。
LLM依存は残念だ。
これは単にWiresharkでパケットキャプチャを見れば、もっと早く解決できたはずだ。
SSH dissectorはかなり成熟している。
tcpdumpで1回のキーストロークだけをキャプチャしても、数百個の暗号化パケットが出てくる。
結局、LLMのおかげで著者が面白いことを学び、共有したのだから意味はあった。
NEWKEYSメッセージ以降はパースできず、
none暗号化にパッチしてもフロー全体を完全には解釈できない。改善の余地はある。
ツールを活用して学ぶことにも十分価値がある。
単純なパケットキャプチャだけでは意味のある情報を得にくい。
それのどこが不幸なことなのか分からない。
2023年にSSHがキーストロークタイミング難読化機能を追加した。
タイピング速度から文字を推定できるため、SSHはchaffパケットを混ぜて攻撃者が区別できないようにする。
だが、これは間違ったアプローチのように思える。
本当に必要なら、すべてのキーストロークを50ms間隔で送ればよい。
現在の実装は20ms単位でまとめつつ、一定時間入力がなければchaff送信を止める方式だ。
SSHの本質はセキュリティだが、セキュリティが不要ならなぜSSHを使うのか疑問だ。
たとえばnetcat(nc) はたいていのプラットフォームに標準で入っている。
SSHには性能や利便性など、他の考慮事項もある。
著者が言っているのは単に**キーストローク難読化(privacy)**機能が不要だということだけだ。
暗号化や完全性保証はなお維持したいのかもしれない。
つまり、SSHのセキュリティ機能の大半はそのままに、一部だけ無効にするという選択だ。