CVE-2024-55591の解説の解説
悪用禁止
自身の理解を振り返るメモ
CVE-2024-55591の概要
- Fortinet 製 FortiOS の脆弱性対策について(CVE-2024-55591) | 情報セキュリティ | IPA 独立行政法人 情報処理推進機構
- Authentication bypass in Node.js websocket module | PSIRT | FortiGuard Labs
認証情報が無くとも,管理者権限で操作が可能になる脆弱性.
例えば,FortiOSならブラウザでログインする管理画面でNode.jsが利用されている. 管理画面の認証機能に脆弱性が存在. この脆弱性を悪用することで, ブラウザ管理画面からFortigate CLIと同等の機能を持った「jsonconsole」を利用できてしまう. これによって,管理者ユーザの追加,VPNユーザの追加など設定変更ができてしまう.

この脆弱性の悪用とみられる不正なアクセスが,脆弱性公表前から確認されていたらしい.
CVE-2024-55591の原因,悪用例概説
確認した限りで最初にgithubに最も悪用可能なPoCを公開したwatchtowrlabsの記事を参照しながら,話を進めていく.
結論から注目.
PoC and Conclusion
This has been a grinding journey for us, with twists, turns and misdirections all over the place, slowing our progress, but the fruit is there.
This vulnerability isn’t "just" a simple Authentication Bypass but a chain of issues combined into one critical vulnerability. To summarise this vulnerability in a digestible format, four important things are happening:
- A WebSocket connection can be created from a pre-authenticated HTTP request.
- A special parameter local_access_token can be used to skip session checks.
- A race condition in the WebSocket → Telnet CLI allows us to send authentication before the server does.
- The authentication which is raced by us contains no unique key, password or identifier to establish a user. We can just pick an choose our access profile (super_admin it is!).
では,脆弱性の悪用ステップの部分の日本語訳(kagitranslateを使用)
まずは最初の2ステップから確認.
1. WebSocket接続は、事前認証されたHTTPリクエストから作成することができます。
2. セッションチェックをスキップするために、特別なパラメータlocal_access_tokenを使用することができます。
watchtowrlabsの解説記事の下記の箇所.
Halfway through this function, just before !authParamsFound, which returns us null, is an interesting else if (localToken) that sets authParamsFound to true. There is no value check; it is just a check to see if some kind of value if present.
Fortigateの管理画面に関連するソースコードから,
async _getAdminSession(request, options = {}) {という管理者でログインしてるかどうかチェックしていそうな部分で,
else if (localToken) {
authParams[authParams.length - 1] += `?local_access_token=${localToken}`;
authParamsFound = true;
}
というようにlocal_access_tokenが正しいかどうかを検証しておらず,local_access_tokenに値が入っているか存在するかどうかしか確認していない.
これで管理者機能にアクセスできてしまう部分がある.
この部分が,CVE-2024-55591の主な脆弱性の箇所なのではないかと.
watchtowrlabsの作成したpythonスクリプトを見ると.
upgrade_request = (
f"GET /ws/cli/open?cols=162&rows=100&local_access_token=watchTowr HTTP/1.1\r\n"
f"Host: {host}\r\n"
f"Upgrade: websocket\r\n"
f"Connection: Upgrade\r\n"
f"Sec-WebSocket-Key: {ws_key}\r\n"
f"Sec-WebSocket-Version: 13\r\n\r\n"
)
他の認証リクエストを真似して,同じようなものを作ってしまえばいいというのが最初のステップ.Sec-WebSocket-Keyはフォーマットさえ合っていれば何でもいけそう.
次のステップのlocal_access_tokenはお好きな文字列を.
こうして一部管理画面へのアクセスを認証情報無しにできてしまえていると思われる.
最初の2ステップは,よくあるような認証機能の不備.CWEだと,CWE-287あたりだろうか. 認証機能はあるけど,機能によってはちゃんと使っていない,認証情報の確認を行わない場合があるというような脆弱性.
「そうはならんやろ」
「なっとるやろがい」
3. WebSocket → Telnet CLIにおける競合状態により、サーバーが認証を行う前に認証を送信することが可能です。
watchtowrlabsの解説記事の3ステップ目からは,CVE-2024-55591の悪用方法の例と考えている. 2ステップ目まで辿り着ければ,他にもできそうなことがありそうなので.(これについては後ほど)
「WebSocket → Telnet CLI」というのは解説記事から,管理画面のjsoncosoleがWebsocketを経由して端末のTelnetにアクセスしていることが分かる. これに関して,解説記事で注目したのは以下の部分.
During this initial period, however - when the server is waiting for Connected. - the function to send and receive messages over WebSocket from the client browser to the CLI Process over Telnet has already been established:
ws.on('message', msg => cli.write(msg)); cli.setNoDelay().on('data', data => this.processData(data));Is this the vulnerability in the form of a race condition?
It looks like we're able to send data over the WebSocket to the CLI process, before the server processes it’s initialisation sequence.
WebSocket 経由でメッセージを送受信する機能は既に動いている????ということは手順をすっ飛ばして,jsonconsoleを使える?
よく似た機能を見ていたり,感が良い人は気づいたりするのかもしれないが, WebSocket経由でCLIに先にデータを送信できるってどうやって気づいたのだろうか. 色々試した結果なのだろうか.
実際は異なる部分があるが分かりやすく考えると,
- jsonconsoleを使い始めるというデータを送信し,
- サーバが初期化動作か何かを行い,
- 準備が出来たとサーバ側から返答が来たら,
- こちら側がjsonconsole用の必要情報を持ってサーバ側にアクセスし,
- サーバ側の認証後にメッセージか何かをサーバ側から返し,
- こちらからCLI機能を利用開始するデータを送信する
というようなものが正規の手順とすると,1のデータを投げたら全て飛ばして6のデータを投げ続けることで何か起こりそうという思考?
Subsequently, looking back at the logs as we walk through the process again, the value of
this.loginContextfrom our upgraded request shows the following:"Local_Process_Access" "Local_Process_Access" "root" "" "" "none" [192.168.1.179]:54145 [192.168.142.132]:80Throwing together some Python, we wrote a quick/dirty script to create a WebSocket upgrade request and then instantly send this message to authenticate to see if we could trigger unexpected behaviour in this race, but we were met with a new error we hadn’t seen before:
Upgrade response: HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: qJ180PUWh8TuhgsJK6MyZg9WvkM= Decoded: b'Bad args to CLI process.\\r\\n'
そして手順をすっ飛ばしたデータを投げ続けたら,CLIプロセス?というTelnet接続ぽいのが見えたと.
4. 私たちが競合させる認証には、ユーザーを確立するための一意のキー、パスワード、または識別子は含まれていません。私たちはアクセスプロファイルを自由に選択することができます(もちろんsuper_adminを選びます!)。
これが悪用の最後のステップ.
Telnet接続ぽいのが見えたが,投げるデータが間違っている所為か.上手くいかなかったと. しかしながら,正規の手順でのデータ送受信を確認したところ.
By logging in natively through the browser as the default user admin, the output from
this.logincontextis :"admin" "admin" "root" "super_admin" "root" "none" [192.168.1.179]:53893 [192.168.142.132]:80Ah yes, these values look full of entropy.
送信する必要のある該当のデータを見つけたので,あとはもう投げるだけ.
このデータ,"admin" "admin" "root" "super_admin" "root" "none" [192.168.1.179]:53893 [192.168.142.132]:80は
ユーザ名やユーザプロファイル,接続元IP,接続先端末IPなどが含まれている.
このデータ形式を見ていて思い出したことが. FortiGuard LabsのCVE-2024-55591のページに記載のあった以下の部分.
Please note as well that an attacker needs to know an admin account's username to perform the attack and log in the CLI. Therefore, having a non-standard and non-guessable username for admin accounts does offer some protection, and is, in general, a best practice. Keep in mind however that the targeted websocket not being an authentication point, nothing would prevent an attacker from bruteforcing the username.
つまり,CVE-2024-55591を悪用するためにはユーザ名を知っている必要がある. 大抵の場合,ユーザ名adminを使っていそうな気がするなぁ.
また,最後のステップの説明通りプロファイルを自由に選択できるとのこと. これは,管理画面にログインできるユーザであればどんなプロファイルであっても,super_adminのユーザとしてjsonconsoleを利用できてしまうと.
If you look closely, you can find the answers to questions like:
- Where is the password?
- Where is some kind of token?
それでは,私が代わりに問いに答えます.
Q.パスワードはどこにありますか
A.要らないです!
Q.何らかの認証トークンが必要ですか
A.要りません!
こりゃもうだめだ...
watchtowrlabsのPoCの確認(悪用禁止)
実際にwatchtowrlabsのpythonスクリプトを使用.
左側がjsonconsoleの利用を行った際の記録,右側がpythonスクリプトを実行したもの. ログインせずにjsoncosoleのAdmin login successfulが記録される. 管理画面にログインせずに急に知らないjsonconsoleアクセスがあるのを監視すれば気づけるかもしれない. 悪用の際にはSourceIP,DestinationIPは任意のものに変更できる.Arctic Wolfの悪用痕跡 では,127.0.0.1や8.8.8.8など何かそれっぽく怪しくなさそうなのを装っている? この感じだと通常のログでは,攻撃元IPを追うのがかなり難しそうだ.
ちなみに悪用時と正規利用時のものを比べると次のようになる.

左が攻撃時,右が正しいもの. 正規のjsonconsole記録は,SourceIPがログイン元端末,DestinationIPがForti端末のIPになっている.
また,確認した感じだと管理画面にログインできるユーザであれば どんなユーザでもいけそう.例えば,次のnone_profと定義したユーザもjsonconsole悪用時のユーザとして指定可能であった.

今回の解説は悪用の一例
先に触れた通り,watchtowrlabsの作成したものは,悪用の一例.
他のpocコード を確認すると,jsoncosoleではなく,おそらく他の方法でシステムログを表示しているものがある.
認証情報無しに管理画面にアクセスしてできることは他にもあるのでは.
CVE-2024-55591の対策
IPAのページFortinet 製 FortiOS の脆弱性対策について(CVE-2024-55591) | 情報セキュリティ | IPA 独立行政法人 情報処理推進機構 にあるような対策を行う
脆弱性の解消のためにはアップデート適用.
しかしながら,どうしても直ぐに適用できないのであれば - 管理画面を外部に公開しない - 管理画面にアクセスできるIPアドレスを制限する という一時的な回避策もある.
どうしても上記の全ての対応ができないのであれば,
ブラウザ管理画面のユーザ名を全て推測不可能なものにするというのもありそうだが.
それだけで守れるのは,jsonconsoleへのアクセスだけのように思える.
そもそも,local_access_tokenであったりのjsonconsoleよりも前段階の部分もあるので.
jsonconsole以外の致命的な悪用方法が広まってしまった場合には意味がなさそうだ.
やはり,アップデート適用が一番
