MySQL Security: Privilege Leak Triage, GRANT Audit & Least Privilege Enforcement
Identifying over-privileged users, wildcard (%) host grants, and unauthorized SUPER/GRANT ALL permissions via audit logs, safely migrating to MySQL 8.0 role-based least privilege architecture.
Operational Safety & Risk Notice
This runbook contains operational intervention steps; several are write-impacting (KILL, terminate, DROP, RESIZE). Verify in a staging environment first, and apply sequentially in production with verification.
1. Symptom & Signals
- SOC / Güvenlik denetiminde `GRANT ALL ON *.*` veya `SUPER` yetkisine sahip servis kullanıcıları uyarısı.
- `host = '%'` (tüm dünyadan erişilebilir) tanımlı uygulama kullanıcıları tespit edildi.
- Audit loglarında (`mysql.audit_log` veya Percona Audit) yetkisiz `CREATE USER`, `GRANT` veya DDL anomalileri.
2. Business & Technical Impact
P2 - Güvenlik & Uyumluluk Riski. Bir web uygulamasının SQL Injection veya kimlik bilgisi sızıntısına uğraması durumunda saldırgan tüm veritabanını silebilir (`DROP DATABASE`), yeni yönetici ekleyebilir veya kritik verileri sızdırabilir.
3. Diagnosis & Root-Cause Analysis
SELECT user, host, Grant_priv, Super_priv, authentication_string
FROM mysql.user
WHERE host = '%' AND user NOT IN ('app_readonly', 'mysql.sys', 'mysql.session');SELECT user, host, Grant_priv, Super_priv, Create_user_priv, Reload_priv
FROM mysql.user
WHERE Super_priv = 'Y' OR Grant_priv = 'Y';SELECT * FROM information_schema.user_privileges WHERE grantee LIKE "'%app%'";
SELECT * FROM information_schema.schema_privileges WHERE grantee LIKE "'%app%'";-- Enterprise Audit tablosu
SELECT user, host, command_class, sql_text, timestamp
FROM mysql.audit_log
WHERE timestamp > NOW() - INTERVAL 7 DAY
AND command_class IN ('grant', 'create_user', 'drop_user', 'revoke')
ORDER BY timestamp DESC;
-- Percona Audit için log taraması:
-- cat /var/log/mysql/audit.log | grep -i grant4. Remediation & Action Plan
### ALTIN KURAL: ÖNCE YENİ ROLE OLUŞTURUN VE TEST EDİN, SONRA ESKİ YETKİYİ REVOKE EDİN!
Doğrudan canlıda REVOKE çalıştırmak production uygulamalarını anında çökertebilir.
---
#### Adım 1: Mevcut Yetkileri Yedekleyin (Rollback Planı)
mysqldump -u root -p mysql --tables user db tables_priv columns_priv procs_priv default_roles role_edges > /backups/mysql_privs_$(date +%F).sql
mysqlpump -u root -p --users > /backups/grants_$(date +%F).sql
# veya tekil kullanıcı için:
SHOW GRANTS FOR 'app_user'@'10.0.0.%' \G---
#### Adım 2: MySQL 8.0 Rol Tabanlı Yeni Yapı Oluşturun (En Az Yetki)
-- Yeni standart kurumsal roller
CREATE ROLE IF NOT EXISTS app_readonly, app_readwrite, app_dba_read;
-- Readonly: Sadece SELECT
GRANT SELECT ON mydb.* TO app_readonly;
-- ReadWrite: SELECT, INSERT, UPDATE, DELETE (DDL yok!)
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO app_readwrite;
-- DBA Read: Teşhis ve izleme (Tüm DB'lerde SELECT + SHOW)
GRANT SELECT ON *.* TO app_dba_read;
GRANT SHOW DATABASES, PROCESS, REPLICATION CLIENT ON *.* TO app_dba_read;
-- Rolü kullanıcıya atayın ve varsayılan yapın
GRANT app_readwrite TO 'app_backend'@'10.0.1.%';
SET DEFAULT ROLE app_readwrite TO 'app_backend'@'10.0.1.%';
-- Fazla yetkili eski global izinleri kaldırın
REVOKE SUPER, GRANT OPTION ON *.* FROM 'app_backend'@'10.0.1.%';---
#### Adım 3: Wildcard (%) Host Kapatma
-- Kötü: 'deploy'@'%'
-- İyi: 'deploy'@'10.0.1.10', 'deploy'@'10.0.1.11'
-- Yeni host-specific kullanıcı oluşturun
CREATE USER 'deploy'@'10.0.1.10' IDENTIFIED BY 'GucluSifre2026!';
GRANT app_readwrite TO 'deploy'@'10.0.1.10';
SET DEFAULT ROLE app_readwrite TO 'deploy'@'10.0.1.10';
-- Eski % kullanıcısını silmeden önce app testi yapın, ardından kaldırın:
DROP USER 'deploy'@'%';---
#### Adım 4: Anonim ve Test Kullanıcılarını Temizleyin
SELECT user, host FROM mysql.user WHERE user = '' OR user LIKE 'test%';
DROP USER IF EXISTS ''@'localhost', ''@'127.0.0.1';
DROP USER IF EXISTS 'test'@'%';5. Verification & Health Checks
- Yeni yetkilerin atandığını doğrulayın:
SHOW GRANTS FOR 'app_backend'@'10.0.1.%';
SELECT CURRENT_ROLE();- Uygulama sunucusundan uçtan uca test yapın:
mysql -h mysql-primary -u app_backend -p -e "SHOW TABLES; INSERT INTO test_triage VALUES (1); DROP TABLE test_triage;"
# INSERT başarılı olmalı, DROP yetki hatası (Access denied) vermelidir.- Audit log'da REVOKE işlemlerinin kaydedildiğini teyit edin.
6. Rollback & Contingency Plan
- Yetki daraltma sonrası uygulama hata alırsa, alınan SQL dump yedeği ile eski yetkileri geri yükleyin:
mysql -u root -p mysql < /backups/mysql_privs_2026-11-05.sql
FLUSH PRIVILEGES;- **Önleme Checklist — dbaguide Standardı:**
- [ ] `sql_require_primary_key = ON` (yetkisiz tablo oluşturmayı zorlaştırır).
- [ ] `default_authentication_plugin = caching_sha2_password` zorunlu mu?
- [ ] Haftalık cron işi: `pt-show-grants --all > /audit/grants_$(date +%F).sql` alınıp versiyon kontrolünde saklanıyor mu?
- [ ] ProxySQL veya MySQL Enterprise Firewall ile `GRANT ALL` engellendi mi?
- [ ] SOC2 / ISO 27001 gereği 90 günde bir `mysql.user` tablosu audit review yapılıyor mu?
- [ ] Parola politikaları: `password_history = 5` ve `password_reuse_interval = 180`.
Engineering Triad: Diagnostic Scripts
Diagnostic scripts recommended during this runbook triage to pinpoint the root cause:
MySQL InnoDB Buffer Pool Hit Ratio
Compares buffer pool read requests against disk reads to compute the hit ratio. Tells you whether memory sizing is sufficient.
Risky MySQL User Accounts and Excess Privileges
Finds accounts with no password, open host access, or dangerous privileges like SUPER and FILE. A baseline check for security audits.