修正版は3本、下限は18通り — GitLab の18件を版数で機械判定する

GitLab が 2026年9月10日、CE / EE のクリティカルパッチリリースを出した。修正版は 19.3.2 / 19.2.6 / 19.1.8 の3本で、ここで塞がれた CVE は 18件ある。

そのうち CVE-2026-85706 は CVSS 3.1 で 10.0、CISA が翌 9月11日に KEV(Known Exploited Vulnerabilities・悪用が確認された脆弱性のカタログ)へ収載し、是正期限を 3日後の 9月14日に設定した。リポジトリの commits API に、認証を経ずにサーバ上の任意のファイルを読めるパストラバーサル(CWE-22)がある。

急ぐ理由は分かる。問題は「自分が該当するのか」である。ここが一行では書けない。

修正版は3本なのに、下限は18通りある

リリースノートの18件を、影響範囲の下限だけ並べてみる。上限はすべて共通で、3系列それぞれの修正版の1つ手前(19.1.7 / 19.2.5 / 19.3.1)である。

CVE CVSS 下限
CVE-2026-82837 5.3 10.1.0
CVE-2026-88765 8.5 12.3
CVE-2024-11222 6.4 13.0
CVE-2026-8030 4.3 13.0
CVE-2026-7514 4.3 13.9
CVE-2026-78252 8.2 15.3
CVE-2026-13210 7.7 15.7
CVE-2026-86341 / CVE-2026-86340 4.4 17.1
CVE-2026-3855 3.1 18.2.7
CVE-2026-87719 9.9 18.3
CVE-2025-14871 / CVE-2026-1168 7.5 18.4.6
CVE-2026-12910 5.4 18.6
CVE-2026-85706 10.0 18.7
CVE-2026-16794 4.3 18.11
CVE-2026-79708 8.5 19.0
CVE-2026-19619 4.7 19.0

下限は 10.1.0 から 19.3 まで散っている。同じパッチで直る18件が、始まった時期は最大で9年ぶんずれている。 だから「18.7 以降が危ない」も「19系が危ない」も、どちらも18件全体の説明にはならない。CVSS 10.0 の1件だけを見れば 18.7 が下限だが、その隣にある CVSS 9.9 の CVE-2026-87719 は 18.3 が下限で、18.5 で止まっている環境は前者に該当せず後者に該当する。

そしてもう一段やっかいなのが、修正版が3本しか無いという点である。19.1 / 19.2 / 19.3 の3系列にしか修正版が存在しない。18系で止まっている環境には、上げるべき「自分の系列の修正版」が無い。系列をまたいで 19.1.8 以降へ上げるしかない。

前に書いた JFrog Artifactory の2件では、系列ごとに修正版が用意されていて、「自分の系列の最新に上げる」が片方のCVEには効き、もう片方には効かなかった。今回は形が違う。そもそも自分の系列に修正版が無い帯がある。

目で追うと必ず間違える。データにして数える。

判定スクリプト

リリースノートの表をそのままデータにして、稼働中の版から該当CVEを数える。依存は無い。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
"use strict";
// 修正版は3本だけ。どのCVEも共通。
const FIXED = ["19.1.8", "19.2.6", "19.3.2"];

// 上限は修正版の1つ手前で全件共通。下限だけがCVEごとに違う。
const ADVISORY = [
{ cve: "CVE-2026-85706", cvss: 10.0, title: "Path Traversal in repository commits API", lower: "18.7" },
{ cve: "CVE-2026-87719", cvss: 9.9, title: "Insecure Deserialization in GraphQL (EE)", lower: "18.3" },
{ cve: "CVE-2026-88765", cvss: 8.5, title: "Buffer Overflow in Unicode conversion (EE)", lower: "12.3" },
{ cve: "CVE-2026-79708", cvss: 8.5, title: "Pipeline Policy CI/CD変数へのアクセス (EE)", lower: "19.0" },
{ cve: "CVE-2026-78252", cvss: 8.2, title: "XSS in Markdown JSON table", lower: "15.3" },
{ cve: "CVE-2026-13210", cvss: 7.7, title: "CI/CD環境変数スコープの不正な認可", lower: "15.7" },
{ cve: "CVE-2025-14871", cvss: 7.5, title: "GraphQL DoS (複雑度制限)", lower: "18.4.6" },
{ cve: "CVE-2026-1168", cvss: 7.5, title: "GraphQL DoS (複雑度制限)", lower: "18.4.6" },
{ cve: "CVE-2024-11222", cvss: 6.4, title: "Merge Request Pipelineの競合状態", lower: "13.0" },
{ cve: "CVE-2026-12910", cvss: 5.4, title: "SAML SSO認証の不正な実装", lower: "18.6" },
{ cve: "CVE-2026-82837", cvss: 5.3, title: "Workhorse認証情報保護不十分", lower: "10.1.0" },
{ cve: "CVE-2026-19619", cvss: 4.7, title: "Content EditorのXSS", lower: "19.0" },
{ cve: "CVE-2026-86341", cvss: 4.4, title: "保護環境承認ルールのアクセス制御 (EE)", lower: "17.1" },
{ cve: "CVE-2026-86340", cvss: 4.4, title: "保護環境承認ルール迂回 (EE)", lower: "17.1" },
{ cve: "CVE-2026-7514", cvss: 4.3, title: "Generic Package Registryの認可欠落", lower: "13.9" },
{ cve: "CVE-2026-8030", cvss: 4.3, title: "Namespace Transferの入力検証不十分", lower: "13.0" },
{ cve: "CVE-2026-16794", cvss: 4.3, title: "Compliance Framework管理の認可欠落 (EE)", lower: "18.11" },
{ cve: "CVE-2026-3855", cvss: 3.1, title: "Terraform State APIの入力検証不十分", lower: "18.2.7" },
];

/** "19.2.6" -> [19,2,6]。欠けた桁は 0 で埋める。文字列比較はしない。 */
function parse(v) {
const p = String(v).split(".").map((n) => Number(n));
if (p.some((n) => !Number.isInteger(n) || n < 0)) throw new Error(`版数として読めません: ${v}`);
while (p.length < 3) p.push(0);
return p.slice(0, 3);
}
/** a<b なら負、a==b なら 0、a>b なら正。 */
function cmp(a, b) {
const x = parse(a), y = parse(b);
for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] - y[i];
return 0;
}

判定の本体は2つに分かれる。1つは「該当するか」、もう1つは「どこへ上げるか」である。後者が今回の肝で、自分の系列に修正版が無い場合を別扱いにする

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** 稼働中の版が属する系列の修正版を返す。系列内に無ければ「今より新しい最小の修正版」へ寄せる。 */
function fixFor(version) {
const [maj, min] = parse(version);
const same = FIXED.find((f) => { const [M, m] = parse(f); return M === maj && m === min; });
if (same) return { target: same, jumpsSeries: false };
const up = FIXED.filter((f) => cmp(f, version) > 0).sort(cmp)[0];
return { target: up || null, jumpsSeries: true };
}

/** 下限(含む)以上で、かつ自系列の修正版に達していなければ該当。 */
function applies(version, lower) {
if (cmp(version, lower) < 0) return false;
const [maj, min] = parse(version);
const same = FIXED.find((f) => { const [M, m] = parse(f); return M === maj && m === min; });
if (same) return cmp(version, same) < 0; // 19.1/19.2/19.3 系は自系列の修正版未満なら該当
return true; // 修正版の無い系列は、下限以上なら全部該当
}

applies の最後の return true が、18系で止まっている環境を拾う行である。上限は「自系列の修正版の1つ手前」なのだから、修正版が存在しない系列では上限が無い、と読む。

走らせる

代表的な版を並べて実行した実際の出力である。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
=== 稼働中: GitLab 19.3.1 ===
該当 18/18 件(最大 CVSS 10)
CVE-2026-85706 CVSS 10 Path Traversal in repository commits API
CVE-2026-87719 CVSS 9.9 Insecure Deserialization in GraphQL (EE)
CVE-2026-88765 CVSS 8.5 Buffer Overflow in Unicode conversion (EE)
CVE-2026-79708 CVSS 8.5 Pipeline Policy CI/CD変数へのアクセス (EE)
… 他 14 件
上げ先: 19.3.2 (同じ系列内の更新)

=== 稼働中: GitLab 19.0.4 ===
該当 18/18 件(最大 CVSS 10)
CVE-2026-85706 CVSS 10 Path Traversal in repository commits API
CVE-2026-87719 CVSS 9.9 Insecure Deserialization in GraphQL (EE)
CVE-2026-88765 CVSS 8.5 Buffer Overflow in Unicode conversion (EE)
CVE-2026-79708 CVSS 8.5 Pipeline Policy CI/CD変数へのアクセス (EE)
… 他 14 件
上げ先: 19.1.8 ← この系列に修正版は無い。系列をまたぐ更新になる

=== 稼働中: GitLab 18.10.2 ===
該当 15/18 件(最大 CVSS 10)
CVE-2026-85706 CVSS 10 Path Traversal in repository commits API
CVE-2026-87719 CVSS 9.9 Insecure Deserialization in GraphQL (EE)
CVE-2026-88765 CVSS 8.5 Buffer Overflow in Unicode conversion (EE)
CVE-2026-78252 CVSS 8.2 XSS in Markdown JSON table
… 他 11 件
上げ先: 19.1.8 ← この系列に修正版は無い。系列をまたぐ更新になる

=== 稼働中: GitLab 17.5.0 ===
該当 9/18 件(最大 CVSS 8.5)
CVE-2026-88765 CVSS 8.5 Buffer Overflow in Unicode conversion (EE)
CVE-2026-78252 CVSS 8.2 XSS in Markdown JSON table
CVE-2026-13210 CVSS 7.7 CI/CD環境変数スコープの不正な認可
CVE-2024-11222 CVSS 6.4 Merge Request Pipelineの競合状態
… 他 5 件
上げ先: 19.1.8 ← この系列に修正版は無い。系列をまたぐ更新になる

=== 稼働中: GitLab 12.2.0 ===
該当 1/18 件(最大 CVSS 5.3)
CVE-2026-82837 CVSS 5.3 Workhorse認証情報保護不十分
上げ先: 19.1.8 ← この系列に修正版は無い。系列をまたぐ更新になる

=== 稼働中: GitLab 19.3.2 ===
該当 0/18 件
更新の必要なし

読み取れることが3つある。

1つ目。19.0.4 と 18.10.2 の差が「CVSS 10.0 に該当するかどうか」ではない。 どちらも CVE-2026-85706 に該当する(18.7 以上だから)。違うのは件数で、19.0.4 は18件全部、18.10.2 は15件である。落ちた3件は下限が 18.11・19.0・19.0 のもので、要するに新しい版のほうが該当件数が多い。「古いほど危ない」という直感はここでは成り立たない。

2つ目。17.5.0 は CVSS 10.0 に該当しない。 下限 18.7 に届いていないからである。ところが該当は9件あり、最大 CVSS は 8.5 である。KEV の是正期限に追われているのは 85706 だが、17系の環境がやるべきことは「85706 は無関係だから急がない」ではなく「9件に該当していて、しかも上げ先が2メジャー先の 19.1.8 しかない」である。判断の材料がまったく違う。

3つ目。12.2.0 の該当は1件だけである。 下限 10.1.0 の CVE-2026-82837(CVSS 5.3)しか当たらない。これは「12.2.0 は安全」という意味ではまったくなく、この18件の対象になるほど新しくないというだけである。12系はとうに保守期間の外で、この表に載っていない過去の脆弱性が積み上がっている。スクリプトが数えているのは「このリリースノートの18件」だけだと、出力の読み手に必ず伝える必要がある。

落とし穴: 版数を文字列で比べる

cmp をわざわざ書いたのは、"19.10.0" < "19.2.6" が JavaScript では true になるからである。実測した。

1
2
3
4
5
6
7
const pairs=[["19.10.0","19.2.6"],["19.3.10","19.3.2"],["18.4.10","18.4.6"]];
for(const [a,b] of pairs){
const str = a < b ? `"${a}" < "${b}"` : (a > b ? `"${a}" > "${b}"` : "等しい");
const num = a.split(".").map(Number), nb = b.split(".").map(Number);
let r=0; for(let i=0;i<3;i++){ if(num[i]!==nb[i]){ r=num[i]-nb[i]; break; } }
console.log(`${a} vs ${b} 文字列比較: ${str} / 数値比較: ${r<0?a+" が古い":a+" が新しい"}`);
}
1
2
3
19.10.0 vs 19.2.6  文字列比較: "19.10.0" < "19.2.6"  /  数値比較: 19.10.0 が新しい
19.3.10 vs 19.3.2 文字列比較: "19.3.10" < "19.3.2" / 数値比較: 19.3.10 が新しい
18.4.10 vs 18.4.6 文字列比較: "18.4.10" < "18.4.6" / 数値比較: 18.4.10 が新しい

文字列比較は先頭から1文字ずつ見るので、"1""2" の比較で決着してしまう。19.10.019.2.6 より 新しいのに、文字列では古いと判定される。

これは机上の話ではない。GitLab の 19.1 系はこのあと 19.1.9、19.1.10 と進む。version < "19.1.8" という文字列比較で「まだ危ない」を判定していると、19.1.10 に上げた環境が「未対応」と表示され続ける。逆向きの事故も起きる。下限 18.4.6 に対して "18.4.10" >= "18.4.6"false なので、該当しているのに該当しないと判定される。

安全側にも危険側にも倒れる。だから桁ごとに数値で比べる。

まとめ

同じパッチリリースで直る複数のCVEを、1つの「影響範囲」で要約しない。 今回の18件は修正版が3本しか無いので、上げ先だけを見れば話は単純に見える。しかし該当範囲の下限は 10.1.0 から 19.3 まで18通りに散っていて、稼働中の版によって該当件数は 0件から18件まで変わる。「18.7 以降が危ない」と一行で覚えると、18.5 の環境で CVSS 9.9 を見落とす。

やることは2つだけである。リリースノートの表をそのままデータにすること。版数を桁ごとに数値で比べること。この2つを守れば、稼働中の版を1つ入れるだけで該当件数と上げ先が出る。目で追う作業をやめられる。

出典: GitLab Critical Patch Release: 19.3.2, 19.2.6, 19.1.8(確認日 2026-09-15)。CVE-2026-85706 の KEV 収載日と是正期限は CISA Known Exploited Vulnerabilities Catalog による。GitLab.com は修正済みで稼働しており、GitLab Dedicated の利用者は対応不要とリリースノートに明記されている。本記事が扱うのは自己管理型の GitLab である。


修正版は3本、下限は18通り — GitLab の18件を版数で機械判定する
https://blog.hashito.biz/2026/09/15/gitlab-2026-09-patch-18-cves-version-range-check/
著者
hashito
作成日
2026年9月15日
著作権