Jawaban langsung: Craft CMS 4.0.0 sampai 4.18.8 dan 5.0.0 sampai 5.11.1 punya SQL injection tanpa login, CVE-2026-123488. Penyerang bisa menjadikan akun mana pun admin. Hari ini, cek versi, lalu update ke 5.11.5.1 atau 4.18.9.

VulnCheck memberi CVSS 3.1 9.8 dan CVSS 4.0 9.3. Patch sudah keluar 17 September 2026, 50 hari sebelum catatan CVE terbit.

Kondisi utama: situs Craft 4 atau 5 yang rentan bisa diakses dari internet. Batas: artikel ini tidak memuat payload, kode serangan, atau urutan serangan.

Kami membaca catatan CVE-2026-123488, catatan CVE-2026-123491, catatan CVE-2026-123490, catatan CVE-2026-123489, advisory GitHub GHSA-qcvp-ggx3-97r6, advisory GitHub GHSA-mjj9-pjh4-r48g, daftar advisory keamanan craftcms/cms, advisory VulnCheck, riwayat rilis di Packagist, dan CISA KEV pada 7 November 2026, pukul 13.30 sampai 14.10 WIB. Kami menguji 3 skrip cek dan 2 langkah sementara di pod podman dengan image resmi craftcms/cli 8.2 dan MariaDB 11.8, di Craft 5.11.1 dan 5.11.5.1. Kami tidak menguji PostgreSQL, Craft 4 di container, dan serangan.

Masalah: 1 request tanpa login bisa membuat admin baru

Craft CMS memegang konten, akun editor, dan sering juga data pelanggan. Jadi 1 celah di database membuka seluruh situs.

Craft CMS adalah CMS PHP buatan PixelPixelPotongan kode di halaman web yang memberi tahu platform iklan bahwa seseorang membuka halaman atau melakukan tindakan.Apa itu Meta Pixel? & Tonic. Agensi dan tim marketing memakai Craft untuk situs perusahaan, katalog produk, dan toko Craft Commerce. Database Craft menyimpan akun admin, hash password, dan isi formulir.

Catatan CVE terbit 6 November 2026 pukul 15.00 UTC. Advisory GitHub sudah publik sejak 22 Oktober, dan patch sudah ada sejak 17 September.

Linimasa: rilis 4.18.9 dan 5.11.2 pada 17 September, 5.11.5.1 pada 8 Oktober, advisory publik 22 dan 23 Oktober, advisory baru 2 November, catatan CVE 6 November.
Titik teal: rilis patch. Titik merah: publikasi detail celah. Titik amber: status hari ini. Patch keluar 50 hari sebelum catatan CVE. Sumber: catatan CVE, advisory GitHub craftcms/cms, rilis Packagist, CISA KEV, dan uji Rama Digital.

Artinya, situs yang update rutin sudah aman. Situs yang tidak update sejak awal September masih rentan, dan detail teknis celah sudah publik selama 16 hari.

Cara kerja: ringkasan advisory

Bagian ini memakai ringkasan resmi dari catatan CVE. Tujuannya membantu Anda memilih langkah yang tepat, bukan menjelaskan cara menyerang.

Ringkasan CVE-2026-123488

Menurut catatan CVE-2026-123488, endpoint pencarian elemen menyambung parameter attribute ke query SQL tanpa pembersihan. Prepared statement emulasi PDO mengizinkan beberapa perintah SQL dalam 1 query. Akibatnya, penyerang tanpa akun bisa membaca dan menulis database, lalu menjadi admin.

Advisory advisory GitHub GHSA-qcvp-ggx3-97r6 memberi judul "SQL injection leading to privilege escalation". Advisory tidak menulis syarat lain selain situs bisa diakses.

3 CVE lain di rilis yang sama

CVERingkasanCVSS 3.1CVSS 4.0
CVE-2026-123488SQL injection di endpoint pencarian elemen. Tanpa login, penyerang bisa mengubah tabel users9.89.3
CVE-2026-123491SQL injection lewat GraphQL eager-load. Tanpa login, penyerang bisa membaca hash password dan token sesi8.28.8
CVE-2026-123490Hak admin naik lewat getOwners(). Butuh akun control panel, hanya 5.8.17 sampai 5.11.18.88.7
CVE-2026-123489Blind SQL injection lewat getNext() dan getPrev(). Butuh akun control panel7.17.1
Empat kartu CVE Craft CMS: 2 SQL injection tanpa login dan 2 celah dengan akun CP. Grafik advisory per versi perbaikan. Aman: 4.18.9 dan 5.11.2.
(1) Kartu merah: celah tanpa login. (2) Kartu amber: celah yang butuh akun control panel. (3) Batang: jumlah advisory per versi perbaikan. (4) Kotak teal: versi aman. Sumber: catatan CVE, advisory GitHub craftcms/cms, rilis Packagist, dan pengelompokan Rama Digital.

Rilis 4.18.9 dan 5.11.2 menutup 12 advisory GitHub. 3 di antaranya berujung RCE, tetapi butuh akun control panel. CVE-2026-123490 hanya ada di 5.8.17 sampai 5.11.1.

Status eksploitasi per 7 November 2026

Kami tidak menemukan laporan serangan atau kode serangan publik. Keempat CVE belum masuk CISA KEV. CISA menulis "Exploitation: none" untuk CVE-2026-123489 dan CVE-2026-123491, dan "Automatable: yes" untuk CVE-2026-123491.

Analisa Rama Digital: 4 CVE Craft lain sudah masuk CISA KEV, yaitu CVE-2024-56145, CVE-2025-23209, CVE-2025-35939, dan CVE-2025-32432. Yang terakhir masuk 20 Maret 2026. Craft sudah berulang kali menjadi target, jadi anggap celah ini mendesak.

Siapa yang paling berisiko

Semua situs Craft 4 dan 5 yang rentan terpapar CVE-2026-123488. Kondisi di bawah menambah dampak.

Kondisi CraftRisikoAlasan
Situs rentan yang bisa diakses publikTertinggiCVE-2026-123488: tanpa login, akun bisa diubah jadi admin
Database MySQL atau MariaDBTinggiAdvisory: query bertumpuk bisa jalan, jadi data bisa ditulis
Skema GraphQL publik aktifTinggiCVE-2026-123491: tanpa login, hash password dan token sesi bisa dibaca
Banyak akun control panel (editor, penulis)PeriksaCVE-2026-123489 dan CVE-2026-123490: akun CP bisa naik jadi admin
Sudah 5.11.2 sampai 5.11.4Periksa6 advisory 2 November, 2 berisiko tinggi
Sudah 5.11.5.1 atau 4.18.9RendahTetap cari tanda dari waktu sebelum update
Enam kartu risiko: situs publik, MySQL atau MariaDB, dan GraphQL publik merah; banyak akun CP dan 5.11.2 sampai 5.11.4 amber; 5.11.5.1 atau 4.18.9 abu-abu.
Kartu merah: risiko tertinggi. Kartu amber: periksa. Kartu abu-abu: risiko rendah. Kotak bawah: status CISA KEV per 7 November 2026. Sumber: catatan CVE, advisory GitHub craftcms/cms, CISA KEV, dan analisa Rama Digital.

Skema GraphQL publik mati secara bawaan. Menurut dokumentasi GraphQL Craft, admin harus menyalakan token publik dan memberi izin baca. Situs headless dan aplikasi mobile sering menyalakannya.

Versi rentan dan versi aman

Yang menentukan adalah versi Craft yang berjalan di vendor/, bukan versi di composer.json.

VersiStatus per 7 November 2026Tindakan
5.8.17 sampai 5.11.1Rentan, 4 CVEUpdate ke 5.11.5.1 hari ini
5.0.0 sampai 5.8.16Rentan, 3 CVEUpdate ke 5.11.5.1 hari ini
4.0.0 sampai 4.18.8Rentan, 3 CVEUpdate ke 4.18.9 hari ini
5.11.2 sampai 5.11.4Aman untuk 4 CVE. Kena advisory 2 NovemberUpdate ke 5.11.5.1 minggu ini
3.x dan lebih lamaDi luar advisory. Tanpa patch keamananRencanakan naik ke Craft 5
5.11.5.1 dan 4.18.9Aman untuk 4 CVEJalankan langkah 5
Tabel 6 baris: Craft 5.0 sampai 5.11.1 dan 4.0 sampai 4.18.8 rentan, 5.11.2 sampai 5.11.4 kena advisory 2 November, 3.x di luar advisory, 5.11.5.1 aman.
Merah: rentan. Amber: aman untuk 4 CVE, tetapi ada advisory baru. Abu-abu: di luar advisory. Teal: aman. Kotak bawah: cara cek versi yang berjalan. Sumber: catatan CVE, advisory GitHub craftcms/cms, rilis Packagist, dan uji Rama Digital.

Menurut riwayat rilis di Packagist, 4.18.9 dan 5.11.2 terbit 17 September 2026. 5.11.4 terbit 1 Oktober, 5.11.5 terbit 7 Oktober, dan 5.11.5.1 terbit 8 Oktober. Kami menyarankan 5.11.5.1, karena versi itu juga menutup 6 advisory 2 November.

Analisa Rama Digital: composer audit membaca database advisory Packagist. Di uji kami pada 7 November 2026, perintah itu tidak menemukan 4 CVE ini di 5.11.1. Jangan pakai hasil kosong itu sebagai bukti aman.

Prasyarat sebelum mulai

  • Akses SSH ke server Craft, atau akses ke repo bila situs deploy dari git.
  • PHP CLI dan Composer di server atau di mesin kerja.
  • bash, curl, dan klien mysql atau mariadb untuk skrip cek.
  • Ruang disk untuk backup database dan salinan folder proyek.
  • Daftar plugin Craft dan versinya. Plugin bisa menahan update Composer.
  • Jendela update 15 sampai 30 menit. Situs bisa tampil error sebentar saat migrasi.

Langkah 1: Cek versi Craft

Simpan skrip ini sebagai cek-versi-craft.sh. Skrip membaca versi di folder vendor/ dan di composer.lock, atau versi yang Anda ketik.

#!/usr/bin/env bash
# Cek versi Craft CMS untuk CVE-2026-123488 (SQL injection tanpa login) dan 3 CVE lain di 4.18.9 dan 5.11.2.
# Skrip hanya membaca. Butuh bash, grep, dan sort.
# Pakai: bash cek-versi-craft.sh /var/www/craft    (folder proyek Craft)
#   atau ketik versi manual: bash cek-versi-craft.sh 5.11.1
set -u
AMAN4=4.18.9
AMAN5=5.11.2
TERBARU5=5.11.5.1

# lebih_kecil A B: benar bila A < B
lebih_kecil() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n 1)" = "$1" ]; }

nilai() {
  local v
  v=$(printf '%s' "$1" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n 1)
  [ -z "$v" ] && { echo "PERIKSA: versi ${1:-kosong} tidak dikenal"; return; }
  case "$v" in
    5.*)
      if lebih_kecil "$v" 5.8.17; then echo "RENTAN: 3 CVE, termasuk CVE-2026-123488. Update ke $TERBARU5"
      elif lebih_kecil "$v" $AMAN5; then echo "RENTAN: 4 CVE, termasuk CVE-2026-123488. Update ke $TERBARU5"
      elif lebih_kecil "$v" 5.11.5; then echo "AMAN untuk 4 CVE ini. Update ke $TERBARU5 untuk advisory 2 November"
      else echo "AMAN untuk 4 CVE ini"; fi ;;
    4.*)
      if lebih_kecil "$v" $AMAN4; then echo "RENTAN: 3 CVE, termasuk CVE-2026-123488. Update ke $AMAN4"
      else echo "AMAN untuk 4 CVE ini"; fi ;;
    3.*|2.*) echo "DI LUAR ADVISORY: advisory hanya menyebut 4.x dan 5.x. Rencanakan naik ke $TERBARU5" ;;
    *) echo "PERIKSA: versi $v di luar cabang 4 dan 5" ;;
  esac
}

ARG=${1:-.}
if printf '%s' "$ARG" | grep -qE '^[0-9]+\.[0-9]+'; then
  echo "Craft $ARG: $(nilai "$ARG")"; exit 0
fi
[ -d "$ARG" ] || { echo "Folder $ARG tidak ada"; exit 1; }
DIR=$(cd "$ARG" && pwd)
echo "== Folder: $DIR"
# Versi kode yang berjalan ada di vendor. composer.lock bisa berbeda bila composer install belum jalan.
RUN=$(grep -oE "'version' => '[^']+'" "$DIR/vendor/craftcms/cms/src/config/app.php" 2>/dev/null | head -n 1 | cut -d"'" -f4)
LOCK=$(grep -A 1 '"name": "craftcms/cms"' "$DIR/composer.lock" 2>/dev/null | grep -oE '"version": "[^"]+"' | cut -d'"' -f4)
[ -z "$RUN$LOCK" ] && { echo "Folder ini bukan proyek Craft (tidak ada vendor/craftcms/cms atau composer.lock)"; exit 1; }
echo "vendor/craftcms/cms: ${RUN:-tidak ada}"
echo "composer.lock: ${LOCK:-tidak ada}"
[ -n "$RUN" ] && [ -n "$LOCK" ] && [ "$RUN" != "$LOCK" ] && echo "PERIKSA: vendor dan composer.lock berbeda. Jalankan composer install, lalu cek lagi"
echo "Craft ${RUN:-$LOCK}: $(nilai "${RUN:-$LOCK}")"
# Folder proyek Craft (berisi berkas craft, composer.lock, dan vendor/)
bash cek-versi-craft.sh /var/www/craft
# Versi yang Anda salin dari Utilities > System Report di control panel
bash cek-versi-craft.sh 4.18.8
# composer audit belum mengenal 4 CVE ini. Di uji kami, hasilnya kosong untuk 5.11.1.
composer audit --locked
Terminal: skrip cek versi menilai 5.11.1 dan 4.18.8 sebagai RENTAN, serta 4.18.9, 5.11.2, dan 5.11.5.1 sebagai AMAN. composer audit tidak menemukan advisory.
Skrip membaca versi di vendor dan composer.lock, lalu menilai 4 versi yang diketik. composer audit diam untuk 5.11.1, jadi jangan pakai hasil itu sebagai bukti aman. Sumber: uji Rama Digital di pod podman dengan image resmi craftcms/cli 8.2 dan mariadb 11.8, Craft 5.11.1 dan 5.11.5.1, 7 November 2026.

Cek hasil: "RENTAN" berarti lanjut ke langkah 2 dan 3 hari ini. "AMAN ... Update ke 5.11.5.1" berarti rencanakan update minggu ini. "AMAN" tetap berarti jalankan langkah 5, karena celah ada sebelum Anda update.

Langkah 2: Cek bagian yang terpapar

Langkah ini menjawab 4 pertanyaan: apakah situs bisa diakses, database apa yang dipakai, apakah GraphQL menjawab tanpa token, dan berapa akun admin yang ada.

Simpan skrip ini sebagai cek-paparan-craft.sh. Skrip hanya membaca .env dan mengirim 2 query {ping} ke GraphQL. Skrip tidak mengirim payload.

#!/usr/bin/env bash
# Cek bagian Craft CMS yang terpapar 4 CVE di 4.18.9 dan 5.11.2. Skrip hanya membaca. Butuh bash, curl, dan php.
# Pakai: bash cek-paparan-craft.sh <folder proyek Craft> <URL publik situs>
# Contoh: bash cek-paparan-craft.sh /var/www/craft https://www.contoh.id
set -u
DIR=${1:?folder proyek Craft, contoh /var/www/craft}
URL=${2:?URL publik situs, contoh https://www.contoh.id}
URL=${URL%/}
TERPAPAR=0; PERIKSA=0
terpapar() { echo "  TERPAPAR: $1"; TERPAPAR=$((TERPAPAR + 1)); }
periksa() { echo "  PERIKSA: $1"; PERIKSA=$((PERIKSA + 1)); }
info() { echo "  INFO: $1"; }
# Baca 1 nilai dari .env tanpa menjalankan isi berkas.
envval() { grep -E "^$1=" "$DIR/.env" 2>/dev/null | tail -n 1 | cut -d= -f2- | tr -d '"'"'"; }
[ -f "$DIR/craft" ] || { echo "Folder $DIR bukan proyek Craft (tidak ada berkas craft)"; exit 1; }

echo "== 1. Pencarian elemen (CVE-2026-123488, tanpa login)"
terpapar "semua versi rentan. Advisory tidak memberi syarat lain selain situs bisa diakses"

echo "== 2. Database"
DRIVER=$(envval CRAFT_DB_DRIVER); DRIVER=${DRIVER:-mysql}
info "driver: $DRIVER"
if [ "$DRIVER" = mysql ]; then
  if grep -qs 'MYSQL_ATTR_MULTI_STATEMENTS' "$DIR/config/db.php"; then
    info "config/db.php menolak beberapa perintah SQL dalam 1 query (langkah 4a terpasang)"
  else
    periksa "MySQL atau MariaDB tanpa langkah 4a. Advisory menulis query bertumpuk bisa jalan"
  fi
fi

echo "== 3. GraphQL publik (CVE-2026-123491, tanpa login)"
GQL=$(envval CRAFT_ENABLE_GQL)
[ "$GQL" = false ] && info "CRAFT_ENABLE_GQL=false di .env"
for P in /actions/graphql/api /api; do
  BODY=$(curl -s -m 15 -X POST "$URL$P" -H 'Content-Type: application/json' --data '{"query":"{ping}"}' 2>/dev/null | head -c 300)
  case "$BODY" in
    *pong*) terpapar "$URL$P menjawab query tanpa token. Cek izin skema publik di langkah 4b" ;;
    *) info "$URL$P tidak menjawab query tanpa token" ;;
  esac
done

echo "== 4. Control panel (CVE-2026-123489 dan CVE-2026-123490, butuh akun CP)"
CP=$(envval CRAFT_CP_TRIGGER); CP=${CP:-admin}
CODE=$(curl -s -o /dev/null -m 15 -w '%{http_code}' "$URL/$CP/login" 2>/dev/null)
if [ "$CODE" = 200 ]; then periksa "halaman login $URL/$CP/login terbuka dari mesin ini (HTTP 200)"
else info "halaman login $URL/$CP/login menjawab HTTP ${CODE:-000}"; fi
ADMINS=$(cd "$DIR" && php craft users/list-admins 2>/dev/null | grep -cE '^ *- ')
info "jumlah akun admin: ${ADMINS:-tidak terbaca}"

echo "== Hasil: TERPAPAR $TERPAPAR, PERIKSA $PERIKSA"
exit 0
# Jalankan di server Craft. Skrip membaca .env dan memanggil URL publik situs.
bash cek-paparan-craft.sh /var/www/craft https://www.contoh.id
Terminal: skrip paparan menulis TERPAPAR 2 (pencarian elemen dan GraphQL publik) dan PERIKSA 2 (MySQL tanpa langkah 4a, halaman login terbuka). Ada 2 akun admin.
Endpoint GraphQL menjawab query ping tanpa token, jadi skrip menulis TERPAPAR. Di uji ini, kami menyalakan skema publik dengan sengaja. Sumber: uji Rama Digital di pod podman dengan image resmi craftcms/cli 8.2 dan mariadb 11.8, Craft 5.11.1 dan 5.11.5.1, 7 November 2026.

Cek hasil: catat setiap baris "TERPAPAR" dan "PERIKSA". Bila update belum bisa jalan hari ini, pasang langkah 4a untuk baris database dan langkah 4b untuk baris GraphQL.

Langkah 3: Backup, lalu update ke 5.11.5.1 atau 4.18.9

Update adalah satu-satunya langkah yang menutup 4 CVE ini. Baca panduan update Craft bila Anda melewati banyak versi.

# 3a. Backup database. Simpan juga salinan folder proyek dan .env.
php craft db/backup
# 3b. Craft 5: update ke 5.11.5.1 (diuji)
composer require craftcms/cms:5.11.5.1 -W
# Craft 4: update ke 4.18.9 (tidak diuji)
composer require craftcms/cms:4.18.9 -W
# 3c. Jalankan migrasi dan kosongkan cache
php craft up --interactive=0
# 3d. Cek lagi versi yang berjalan
bash cek-versi-craft.sh /var/www/craft
Terminal: php craft db/backup membuat berkas backup, composer require menaikkan craftcms/cms dari 5.11.1 ke 5.11.5.1, lalu php craft up tidak menemukan migrasi baru.
Backup dulu, lalu update. Di uji ini, update dari 5.11.1 tidak membawa migrasi database baru. Sumber: uji Rama Digital di pod podman dengan image resmi craftcms/cli 8.2 dan mariadb 11.8, Craft 5.11.1 dan 5.11.5.1, 7 November 2026.

Di uji kami, update dari 5.11.1 ke 5.11.5.1 selesai tanpa migrasi database baru, dan halaman depan tetap menjawab 200. Opsi -W mengizinkan Composer menaikkan dependensi lain. Bila Composer menolak, cek batas versi plugin di pesan error.

Situs yang deploy dari git

Banyak agensi menjalankan Composer di mesin kerja, lalu deploy lewat git. Di alur ini, versi Craft di server mengikuti composer.lock di repo.

# Situs yang deploy dari git: update di mesin kerja, lalu kirim composer.lock
git add composer.json composer.lock
git commit -m "Update Craft CMS ke 5.11.5.1 untuk CVE-2026-123488"
# Sesudah deploy, di server:
composer install --no-dev && php craft up --interactive=0
Terminal: sesudah update, skrip versi membaca 5.11.5.1 di vendor dan composer.lock, lalu menulis AMAN. Halaman depan situs menjawab HTTP 200.
Vendor dan composer.lock sama-sama 5.11.5.1. Bila keduanya berbeda, skrip menulis PERIKSA. Sumber: uji Rama Digital di pod podman dengan image resmi craftcms/cli 8.2 dan mariadb 11.8, Craft 5.11.1 dan 5.11.5.1, 7 November 2026.

Cek hasil: skrip versi menulis "AMAN untuk 4 CVE ini", vendor dan composer.lock sama, dan halaman depan menjawab 200. Buka juga control panel dan 1 halaman dengan formulir.

Langkah 4: Kurangi dampak sampai update selesai

Langkah ini mengurangi dampak, tetapi tidak membuat situs aman. CVE tetap terbuka sampai Anda update.

Tangga 6 baris: cek versi, cek paparan, backup dan update ke 5.11.5.1 atau 4.18.9, 2 langkah sementara untuk database dan GraphQL, lalu cari tanda.
(1) sampai (3): urutan perbaikan. (4a) dan (4b): langkah sementara. Kotak bawah: Analisa Rama Digital untuk langkah 4a, dengan kondisi dan batasnya. Sumber: advisory GitHub craftcms/cms, panduan update Craft, uji Rama Digital, dan analisa Rama Digital.

4a. Tolak query bertumpuk di koneksi MySQL atau MariaDB

Analisa Rama Digital: advisory menulis prepared statement emulasi mengizinkan beberapa perintah dalam 1 query. Kami mematikan opsi PDO itu lewat pdoAttributes di kode sumber DbConfig. Kondisi: update belum bisa jalan hari ini, dan database memakai MySQL atau MariaDB. Batas: injeksi di 1 perintah SELECT bisa tetap membaca data.

Simpan berkas ini sebagai db-craft.php. Bila config/db.php sudah ada, tambah baris ->pdoAttributes(...) ke DbConfig::create() yang ada. Kami hanya menguji berkas di bawah, di Craft 5.11.1.

<?php
// config/db.php: langkah 4a, sementara sampai update selesai (MySQL dan MariaDB).
// Koneksi menolak beberapa perintah SQL dalam 1 query. Nilai CRAFT_DB_* di .env tetap dipakai.
return craft\config\DbConfig::create()
    ->pdoAttributes([PDO::MYSQL_ATTR_MULTI_STATEMENTS => false]);
# 4a. Uji sebelum dan sesudah. Hasil "1" berarti query bertumpuk masih jalan.
php craft exec 'count(Craft::$app->getDb()->createCommand("SELECT 1; SELECT 2")->queryAll())'
# Pasang berkas di atas sebagai config/db.php. Hasil yang benar: Error SQLSTATE[42000] ... 1064.
cp db-craft.php config/db.php
# Query biasa harus tetap jalan
php craft exec 'Craft::$app->getDb()->createCommand("SELECT COUNT(*) FROM {{%users}}")->queryScalar()'
Terminal: query uji bertumpuk berhasil sebelum config/db.php, lalu gagal dengan SQLSTATE 42000 1064 sesudahnya. Query biasa tetap jalan dan situs menjawab HTTP 200.
Sesudah langkah 4a, koneksi menolak 2 perintah SQL dalam 1 query. Query biasa dan halaman situs tetap jalan. Sumber: uji Rama Digital di pod podman dengan image resmi craftcms/cli 8.2 dan mariadb 11.8, Craft 5.11.1 dan 5.11.5.1, 7 November 2026.

Di uji kami, query uji bertumpuk mendapat hasil 1 sebelum berkas ini, lalu SQLSTATE[42000] 1064 sesudahnya. Query biasa tetap mendapat 2, dan halaman depan tetap 200. Hapus langkah ini sesudah update bila Anda tidak butuh lagi.

4b. Matikan GraphQL bila situs tidak memakainya

# 4b. Situs tidak memakai GraphQL: matikan endpoint GraphQL
echo CRAFT_ENABLE_GQL=false >> .env
# Cek lagi. Hasil yang benar: 2 baris "tidak menjawab query tanpa token".
bash cek-paparan-craft.sh /var/www/craft https://www.contoh.id | sed -n '/== 3/,/== 4/p'
Terminal: CRAFT_ENABLE_GQL=false masuk ke .env. Skrip paparan menulis kedua endpoint GraphQL tidak menjawab query tanpa token.
Baris TERPAPAR untuk GraphQL hilang sesudah langkah 4b. Sumber: uji Rama Digital di pod podman dengan image resmi craftcms/cli 8.2 dan mariadb 11.8, Craft 5.11.1 dan 5.11.5.1, 7 November 2026.

Menurut referensi general config Craft, enableGql mengatur endpoint GraphQL, dan Craft membaca nilai itu dari env CRAFT_ENABLE_GQL. Situs headless tidak boleh memakai langkah ini. Matikan token publik di menu GraphQL > Schemas, lalu pakai token privat. Kami tidak menguji jalur menu itu.

4c. Kurangi akun control panel

CVE-2026-123489 dan CVE-2026-123490 butuh akun control panel. Suspend akun editor yang tidak aktif, dan nyalakan 2FA untuk semua admin. Langkah ini tidak menyentuh CVE-2026-123488.

Cek hasil: skrip paparan menulis "langkah 4a terpasang" dan 2 baris GraphQL "tidak menjawab query tanpa token".

Langkah 5: Cari tanda pemakaian

Celah ini ada sebelum Anda update, jadi cari tanda dari waktu sebelum update juga. Mulai minimal dari 17 September 2026, atau lebih awal bila log masih ada.

Simpan skrip ini sebagai cek-tanda-craft.sh. Skrip membaca akun admin, user baru, dan perubahan password di database Craft. Skrip juga mencari kata SQL dan titik koma di query string log akses.

#!/usr/bin/env bash
# Cari tanda penyalahgunaan CVE-2026-123488 dan 3 CVE lain di Craft CMS. Skrip hanya membaca.
# Butuh bash, awk, zgrep, dan klien mysql atau mariadb. Hanya untuk database MySQL atau MariaDB.
# Pakai: bash cek-tanda-craft.sh <folder proyek Craft> <tanggal YYYY-MM-DD> [folder log akses]
# Log akses: berkas access.log* (termasuk .gz) dari nginx atau Apache, format combined.
set -u
DIR=${1:?folder proyek Craft, contoh /var/www/craft}
SINCE=${2:?tanggal mulai, contoh 2026-09-01}
LOGDIR=${3:-}
printf '%s' "$SINCE" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' || { echo "Tanggal harus YYYY-MM-DD"; exit 1; }
TANDA=0; PERIKSA=0; BUTA=0
tanda() { echo "  TANDA: $1"; TANDA=$((TANDA + 1)); }
periksa() { echo "  PERIKSA: $1"; PERIKSA=$((PERIKSA + 1)); }
buta() { echo "  TIDAK BISA DIPASTIKAN: $1"; BUTA=$((BUTA + 1)); }
# Buang karakter kontrol dan potong baris, agar isi log tidak bisa mengubah tampilan terminal.
bersih() { LC_ALL=C tr -d '\000-\010\013-\037\177' | cut -c1-160; }
envval() { grep -E "^$1=" "$DIR/.env" 2>/dev/null | tail -n 1 | cut -d= -f2- | tr -d '"'"'"; }

echo "== 1. Akun di database (mulai $SINCE)"
CLI=$(command -v mariadb || command -v mysql || true)
DRIVER=$(envval CRAFT_DB_DRIVER)
if [ -n "$DRIVER" ] && [ "$DRIVER" != mysql ]; then
  buta "driver $DRIVER. Skrip ini hanya membaca MySQL atau MariaDB"
elif [ -z "$CLI" ]; then
  buta "klien mysql atau mariadb tidak ada"
else
  P=$(envval CRAFT_DB_TABLE_PREFIX); PORT=$(envval CRAFT_DB_PORT); PORT=${PORT:-3306}
  # Password dikirim lewat env MYSQL_PWD, bukan argumen. Argumen tampil di daftar proses (ps).
  q() { MYSQL_PWD="$(envval CRAFT_DB_PASSWORD)" "$CLI" -N -B -h "$(envval CRAFT_DB_SERVER)" -P "$PORT" \
    -u "$(envval CRAFT_DB_USER)" "$(envval CRAFT_DB_DATABASE)" -e "$1" 2>/dev/null; }
  if ! q "SELECT 1" >/dev/null; then
    buta "login database gagal. Cek CRAFT_DB_* di .env"
  else
    while IFS=$'\t' read -r id user mail login ip; do
      [ "$login" = NULL ] && login=""; [ "$ip" = NULL ] && ip=""
      periksa "admin id $id $user <$mail>, login terakhir ${login:-belum ada}, IP ${ip:-tidak tercatat}. Pastikan Anda kenal akun ini"
    done < <(q "SELECT id, username, email, lastLoginDate, lastLoginAttemptIp FROM ${P}users WHERE admin = 1 ORDER BY id" | bersih)
    while IFS=$'\t' read -r id user tgl adm; do
      [ "$adm" = 1 ] && tanda "admin baru id $id $user dibuat $tgl" || periksa "user baru id $id $user dibuat $tgl"
    done < <(q "SELECT id, username, dateCreated, admin FROM ${P}users WHERE dateCreated >= '$SINCE' ORDER BY dateCreated" | bersih)
    while IFS=$'\t' read -r id user tgl; do
      periksa "password id $id $user berubah $tgl"
    done < <(q "SELECT id, username, lastPasswordChangeDate FROM ${P}users WHERE lastPasswordChangeDate >= '$SINCE' ORDER BY lastPasswordChangeDate" | bersih)
    info=$(q "SELECT COUNT(*) FROM ${P}users WHERE admin = 1")
    echo "  INFO: $info akun admin"
  fi
fi

echo "== 2. Log akses (${LOGDIR:-tidak diberi folder}, mulai $SINCE)"
if [ -n "$LOGDIR" ]; then
  FILES=$(find "$LOGDIR" -type f -name 'access.log*' 2>/dev/null | sort)
  echo "  $(printf '%s' "$FILES" | grep -c .) berkas access.log"
  # awk membaca tanggal [07/Nov/2026:10:00:00 +0700]. Baris tanpa tanggal yang terbaca dihitung terpisah.
  HASIL=$(printf '%s\n' "$FILES" | xargs -r zcat -f 2>/dev/null | awk -v since="${SINCE//-/}" '
    BEGIN { split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", m, " "); for (i = 1; i <= 12; i++) mon[m[i]] = sprintf("%02d", i) }
    {
      if (!match($0, /\[[0-9][0-9]\/[A-Z][a-z][a-z]\/[0-9][0-9][0-9][0-9]:/)) { print "BUTA"; next }
      d = substr($0, RSTART + 1, 11); tgl = substr(d, 8, 4) mon[substr(d, 4, 3)] substr(d, 1, 2)
      if (tgl < since) next
      if (!match($0, /"[A-Z]+ [^ "]+/)) next
      req = substr($0, RSTART + 1, RLENGTH - 1); low = tolower(req)
      if (low ~ /graphql|\/api/ && req ~ /^POST/) { print "GQL " $1; next }
      q = index(low, "?"); if (q == 0) next
      qs = substr(low, q)
      if (qs ~ /(union|select|sleep|benchmark|information_schema|extractvalue|updatexml)(\+|%20|%09|%0a|%28|\(|\/\*|%2f\*)/ || qs ~ /%3b|;/)
        print "SQL " $1 " " substr(req, 1, 120)
    }')
  N_BUTA=$(printf '%s\n' "$HASIL" | grep -c '^BUTA')
  [ "$N_BUTA" -gt 0 ] && buta "$N_BUTA baris log tanpa tanggal yang terbaca. Baca baris itu manual"
  while IFS= read -r l; do tanda "pola SQL di query: ${l#SQL }"; done < <(printf '%s\n' "$HASIL" | grep '^SQL ' | bersih | head -n 10)
  echo "  POST GraphQL per IP (5 teratas):"
  printf '%s\n' "$HASIL" | grep '^GQL ' | awk '{print $2}' | sort | uniq -c | sort -rn | head -n 5 | bersih | sed 's/^ */  INFO: /'
fi

echo "== Hasil: TANDA $TANDA, PERIKSA $PERIKSA, TIDAK BISA DIPASTIKAN $BUTA"
[ "$BUTA" -gt 0 ] && echo "Hasil belum lengkap. Hasil 0 tanda belum berarti bersih."
[ "$TANDA" -gt 0 ] && echo "Simpan log dan dump database sebagai bukti, lalu ikuti langkah 5 di artikel."
exit 0
# Database Craft dan log akses nginx, mulai 1 September 2026
bash cek-tanda-craft.sh /var/www/craft 2026-09-01 /var/log/nginx
Terminal: skrip tanda menemukan 1 admin baru dummy dan 3 query string dengan pola SQL di 2 berkas log, termasuk 1 berkas .gz. Hasil: TANDA 4, PERIKSA 3.
Akun dan log di uji ini adalah data dummy dengan alamat dan IP contoh dokumentasi. Skrip juga membaca berkas .gz, jadi log lama ikut dicek. Sumber: uji Rama Digital di pod podman dengan image resmi craftcms/cli 8.2 dan mariadb 11.8, Craft 5.11.1 dan 5.11.5.1, 7 November 2026.

Di uji ini, skrip menemukan 4 TANDA: 1 admin baru dummy dan 3 query string dengan pola SQL dari 2 IP dummy. 1 dari 3 baris itu ada di berkas .gz. Skrip juga menulis 3 PERIKSA untuk 2 admin dan 1 perubahan password.

Dua kolom tanda: di log akses (kata SQL atau titik koma di query string, lonjakan POST GraphQL) dan di database Craft (admin baru, password berubah, login dari IP asing).
Kolom amber: tanda di log akses. Kolom merah: tanda di database Craft. Kotak bawah: langkah bila Anda menemukan 1 tanda saja. Sumber: catatan CVE-2026-123488 dan CVE-2026-123491, uji Rama Digital, dan analisa Rama Digital.

Analisa Rama Digital: SQL injection lewat POST GraphQL tidak tampil di log akses biasa, karena isi body tidak tercatat. Jadi skrip hanya menghitung POST GraphQL per IP. Lonjakan dari 1 IP asing perlu Anda periksa.

Cek hasil: skrip menulis "TANDA 0", dan setiap PERIKSA punya penjelasan dari tim Anda. Log bisa dihapus penyerang, jadi 0 tanda belum berarti aman.

Bila ada 1 tanda saja, simpan log dan dump database sebagai bukti. Lalu hapus admin yang tidak Anda kenal, ganti password semua akun CP, ganti password database dan kunci APIAPIPintu resmi yang dipakai 2 sistem untuk saling mengirim data, tanpa orang menyalin data secara manual.Buka glosarium di .env, dan update ke 5.11.5.1.

Bila situs menyimpan data pelanggan, UU 27/2022 tentang Pelindungan Data Pribadi mewajibkan pemberitahuan tertulis paling lambat 3 kali 24 jam sesudah kebocoran data pribadi. Baca juga panduan security, privacy, dan UU PDP untuk perusahaan.

Contoh: 1 siang respons untuk 3 situs Craft agensi

Simulasi ini memakai data dummy. Nama situs, waktu, dan hasil bukan data pelanggan.

Waktu (WIB)Situs dummyYang dicekHasilTindakan
13.00situs-profil (Craft 5.11.1, MySQL)Langkah 1 dan 2RENTAN, TERPAPAR 1, PERIKSA 2Langkah 3 sekarang
13.30katalog-headless (Craft 5.9.3, GraphQL publik)Langkah 1 dan 2RENTAN, TERPAPAR 2Langkah 4a sekarang, update sore ini
14.00toko-lama (Craft 4.16.2)Langkah 1RENTAN, 3 CVEUpdate ke 4.18.9
16.00Ketiga situsLangkah 3 dan 5AMAN, 1 admin tidak dikenal di toko-lamaSimpan bukti, ganti password

Pelajaran dari simulasi: situs headless tidak bisa memakai langkah 4b, jadi update harus jalan hari itu juga.

Siapa yang memasang patch

Craft adalah paket Composer di proyek Anda, jadi penyedia hosting biasanya tidak memasang patch ini.

Jenis layananPemegang patchYang Anda kerjakan
Craft di VPS atau shared hostingTim AndaLangkah 1 sampai 5
Craft yang deploy dari git (termasuk Craft Cloud)Tim Anda, lewat composer.lockUpdate di mesin kerja, deploy, lalu langkah 5
Situs buatan agensi dengan kontrak maintenanceAgensi, sesuai kontrakMinta versi Craft tertulis dan hasil langkah 5
Situs tanpa kontrak maintenancePemilik situsCari developer hari ini, atau pakai langkah 4 dulu

Rekomendasi Rama Digital: pantau rilis keamanan semua CMS PHP Anda dengan jadwal tetap. Minggu ini kami juga menulis peringatan WordPress CVE-2026-119977, peringatan PolyShell Magento CVE-2026-48356, peringatan PHP2Shell CVE-2026-109618, dan peringatan Rails Active Storage CVE-2026-122006.

Checklist hari ini

  1. Buat daftar semua situs Craft, termasuk staging. Pemilik: tim IT atau agensi. Bukti: daftar nama dan versi.
  2. Cek versi dengan langkah 1. Pemilik: developer. Bukti: output skrip.
  3. Cek paparan dengan langkah 2. Pemilik: developer. Bukti: output skrip.
  4. Pasang langkah 4 di setiap situs yang belum bisa update hari ini. Pemilik: developer. Bukti: output skrip paparan.
  5. Backup, lalu update ke 5.11.5.1 atau 4.18.9. Pemilik: developer. Bukti: output skrip versi sesudah update.
  6. Cari tanda dengan langkah 5. Pemilik: developer dan tim keamanan. Bukti: output skrip.
  7. Suspend akun CP yang tidak aktif dan nyalakan 2FA admin. Pemilik: admin situs. Bukti: daftar akun.

Tanya jawab

Apakah celah ini sudah dipakai untuk menyerang?

Per 7 November 2026, kami tidak menemukan laporan serangan, dan 4 CVE ini belum masuk CISA KEV. Tetapi detail advisory sudah publik sejak 22 Oktober, jadi update hari ini.

Situs saya tidak memakai GraphQL. Apakah saya aman?

Tidak. CVE-2026-123488 ada di endpoint pencarian elemen, bukan di GraphQL. Mematikan GraphQL hanya menutup jalur CVE-2026-123491.

Saya memakai PostgreSQL. Apakah langkah 4a berlaku?

Tidak. Opsi di langkah 4a hanya untuk MySQL dan MariaDB, dan kami tidak menguji PostgreSQL. Update ke 5.11.5.1 atau 4.18.9 adalah jalan Anda.

composer audit bilang tidak ada masalah. Kenapa saya tetap rentan?

Di uji kami pada 7 November 2026, composer audit tidak menemukan 4 CVE ini di 5.11.1. Pakai skrip langkah 1, karena skrip itu membaca versi yang berjalan.

Saya masih di Craft 3. Apa yang harus saya kerjakan?

Advisory hanya menyebut Craft 4 dan 5, dan Craft 3 tidak lagi mendapat patch keamanan. Rencanakan naik ke Craft 5, dan batasi akses ke situs sampai migrasi selesai.

Apakah update menghapus konten atau plugin saya?

Tidak, bila Anda memakai database dan folder yang sama. Di uji kami, update 5.11.1 ke 5.11.5.1 tidak membawa migrasi baru. Tetap backup dulu.

Apakah skrip cek mengubah sesuatu di server saya?

Tidak. Ketiga skrip hanya membaca. Perintah yang mengubah sesuatu hanya ada di langkah 3 dan 4, dan setiap perintah ditulis terpisah.

Langkah berikutnya

Batas yang tetap berlaku: langkah 4 tidak menggantikan update, dan update tidak membatalkan akses yang sudah terjadi. Bila situs Craft Anda menunjukkan tanda di langkah 5, atau Anda butuh bantuan update dan audit, pakai Vibe Code Rescue. Untuk 1 masalah mendesak hari ini, jadwalkan sesi 30 menit.

Sumber