Drupal sites are attacked within hours of a highly critical core advisory, and the sites that get compromised are nearly always the ones on an unsupported branch or a slow patch cycle. Drupal powers many government, university and enterprise sites, which is why five Drupal core flaws are in the CISA Known Exploited Vulnerabilities catalog, from the 2018 Drupalgeddon remote code execution bugs to the May 2026 SQL injection in SA-CORE-2026-004. This guide covers how Drupal users, roles and permissions work, how to protect user 1 and administrator roles, API and service credentials, logging and detection, and a hardening baseline built around release support dates. It is for site owners, Drupal developers and web platform and security teams. Facts come from Drupal core source files and drupal.org documentation and advisories; confirm details for your version.
Key takeaways
- Drupal 10 ends on 9 December 2026. drupal.org lists the end of security support for 11.3.x and 10.6.x in the week of 7 December 2026, and Drupal 7 ended on 5 January 2025. Plan the move to the current 11.x minor now.
- Patch on the day for highly critical advisories. SA-CORE-2026-004 (CVE-2026-9082) was exploitable by anonymous users on PostgreSQL sites and was seen exploited in the wild two days after release.
- Permissions marked "restrict access" are admin rights.
administer permissions,administer usersandadminister account settingslet a role escalate itself. Keep them with a small administrator role. - settings.php holds the keys. The database credentials and
hash_saltlive there. Keep the salt out of the database and backups, settrusted_host_patterns, and make code read only to the web server. - Logging must leave the database. Database logging (dblog) is lost or altered when the site is compromised. Send watchdog messages to syslog and your SIEM and watch login failures and role changes.
How identity and access work in Drupal
Drupal stores accounts in its own users tables and authorises with roles that carry permissions. Every visitor has the built-in anonymous role, every logged in user the authenticated role, and further roles (such as Content editor or Administrator) are granted per account. A role can be flagged as the administrator role so it automatically receives every new permission. Content access is then refined by node access modules, text formats and field permissions.
| Identity | What it is | Security notes |
|---|---|---|
| User 1 | The account created during installation (the site maintenance account) | Bypasses all permission checks. Give it an unguessable name, a long unique password, MFA, and use it only for break glass. |
| Administrator role | Role set as the administrator role in Account settings | Receives all permissions, including future ones added by modules. |
| Authenticated role | Every logged in account | Permissions granted here apply to anyone who can register. Check what authenticated holds before allowing open registration. |
| Anonymous role | Every visitor | Most Drupal mass exploitation targets anonymous reachable code paths. |
| External identity (SAML, OpenID Connect) | Contributed modules mapping IdP users and groups to Drupal roles | Moves password and MFA policy to the IdP; keep a local break glass account. |
Login protection in core
- Flood control: core's
user.floodconfiguration defaults to 5 failed attempts per account in 6 hours (user_limit: 5,user_window: 21600) and 50 per IP per hour (ip_limit: 50,ip_window: 3600). Behind a reverse proxy setreverse_proxyandreverse_proxy_addresses, or every request appears to come from the proxy and the IP limit locks everyone out or nobody. - Registration: the user module's default registration mode lets visitors create accounts; most organisational sites should set "Administrators only" or require approval.
- Password reset: one-time login links expire after 24 hours by default (
password_reset_timeout: 86400) and are signed usinghash_salt, so changing the salt invalidates outstanding links. - MFA: core has no multi-factor authentication; use a contributed TFA module or enforce MFA at your identity provider when using SSO.
Admin roles and privileged access
Drupal flags dangerous permissions with "restrict access", shown in the UI as a warning to give them only to trusted roles. Treat each one as privileged.
| Permission (machine name) | Why it is privileged |
|---|---|
administer permissions (Administer roles and permissions) | Can grant any permission to any role, including its own. |
administer users | Can change any account's email and password, so can take over user 1. |
administer account settings | Controls registration, account emails and cancellation methods. |
administer site configuration and administer modules | Change system settings and enable modules that add new code paths. |
administer filters and use of Full HTML text formats | Unfiltered HTML lets an editor store script that runs in an administrator's browser. |
administer views and other Twig template editing | SA-CORE-2026-004 told sites to review which roles can edit Twig templates, for example through Views. |
administer software updates | Grants access to update.php. |
- Separate roles: create Site administrator, User administrator and Content editor roles rather than giving editors the administrator role. Review role assignments monthly.
- Lock configuration in production: deploy configuration through the configuration sync directory (
config_sync_directory, outside the web root) and version control, so permission changes in production are visible as drift. drupal.org also documents the Secure Permissions approach of disabling the role and permission UI in production. - Keep update.php closed:
$settings['update_free_access']must stayFALSE; the comment indefault.settings.phpsays to set it back after any upgrade that needed it. - Server access: anyone with shell access as the deploy user, or with Drush, can do anything user 1 can. Treat SSH keys, Drush aliases and hosting control panels as privileged access.
Integrations, API tokens and service accounts
Drupal increasingly acts as a headless content API and integrates with search, CRMs, CDNs and mail providers. Each integration brings a credential. See the API Key Management Guide.
| Integration | Credential | Hardening |
|---|---|---|
| Database | DSN in settings.php | Dedicated database user with rights on the Drupal schema only; load from environment or a secrets file outside the web root |
| JSON:API | Uses Drupal sessions or an auth provider | Core ships with read_only: true; keep it unless you need writes, and scope what anonymous can read through entity access |
| REST with Basic Auth | Drupal username and password on every request | Use a dedicated service account with only the needed permissions, over HTTPS; prefer OAuth via a contributed module |
| OAuth 2.0 (contributed, for example Simple OAuth) | Client secrets, signing keys | Keep private keys outside the web root and the database, short token lifetimes, scopes mapped to roles |
| Third party API keys (search, mail, CDN, CRM) | Often stored in configuration | Configuration is exported to Git; store secrets with a Key module provider or environment overrides instead of exported config |
| Outbound proxy | http_client_config proxy URLs may include a username and password | Keep in a non-committed settings include |
Configuration export is the commonest leak path: API keys typed into module settings land in YAML in the configuration sync directory and then in the repository. Scan it with your secret scanner and use settings.php overrides for secrets. See the Secrets Management Guide.
Logging and detection
Drupal writes watchdog messages through its logger. The Database Logging module (dblog) stores them in the site database, viewable at Reports, Recent log messages; the core Syslog module sends them to the operating system log. For security monitoring use syslog and forward to the SIEM, because an attacker with database or admin access can clear dblog. Core sets error_level: hide in system.logging so errors are not shown to visitors; keep that in production.
| Signal | Source | Maps to |
|---|---|---|
| "Login attempt failed for %user" or "from %ip" in bursts | user channel in watchdog | Brute force against accounts and user 1 |
| New account with an elevated role, role permission changes | user channel, configuration change events, config drift checks | Account Manipulation, T1098 |
| Module enabled or new code deployed outside a release | system channel, file integrity monitoring | Persistence through code changes |
New PHP files in sites/default/files or the web root | File integrity monitoring | Web Shell, T1505.003 |
| Requests matching a fresh advisory (unusual parameters, PostgreSQL errors, form API abuse) | Web server and WAF logs, php error logs | Exploit Public-Facing Application, T1190 |
| Outbound connections from PHP processes to unknown hosts | Egress firewall | Post exploitation downloads and crypto mining |
Metrics
- Time to patch: hours from a Drupal core security release to production (target same day for critical and highly critical).
- Supported versions: percentage of sites on a Drupal minor with security coverage (target 100%, with a plan for 9 December 2026).
- Privileged accounts: number of accounts holding restricted permissions, with MFA coverage among them.
- Configuration drift: unexpected differences between production configuration and the sync directory per week.
- Contributed module currency: modules with an open security advisory or that are unsupported.
Hardening baseline
- Get onto a supported release: move Drupal 7, 8 and 9 sites off those versions; schedule Drupal 10 sites onto 11.x before 9 December 2026. Use Composer to manage core and contributed modules so updates are repeatable.
- Subscribe and patch: follow Drupal security advisories (core and contributed) and public service announcements, which announce release windows for highly critical fixes in advance. Rehearse a same day patch on staging.
- Set file ownership: following drupal.org, code owned by a deploy user and readable by the web server group (directories 750, files 640), the
filesdirectory writable by the group (770 and 660), and its.htaccessread only and not owned by the web server user. The web server must not be able to change the code it executes. - Configure settings.php: set
trusted_host_patternsfor your hostnames, loadhash_saltfrom a file outside the document root and outside database backups, placefile_private_pathandconfig_sync_directoryoutside the web root, and set reverse proxy settings where relevant. - Lock down accounts: rename and protect user 1, restrict registration, enforce MFA for every role with restricted permissions, and remove the administrator role from anyone who does not need it.
- Restrict text formats: keep Full HTML for trusted administrators only, give editors filtered formats, and never install modules that evaluate PHP from the UI.
- Reduce code: uninstall unused modules, including REST, Basic Auth and JSON:API when not used, and remove abandoned contributed modules.
- Enable syslog and monitoring: enable the Syslog module, forward to the SIEM, and add file integrity monitoring on the web root.
- Front the site: HTTPS everywhere, security headers, and a WAF or CDN rule set that can be updated quickly for new advisories. A WAF buys hours, not a substitute for the update. See the Web Application Firewall Guide.
- Test and roll back: deploy updates through staging with database update tests (
drush updatedb), keep a database backup and the previous code release, and know how to put the site into maintenance mode while you patch.
Small and large estates
A single small site often does best on a managed Drupal host that applies core security updates automatically, with MFA for administrators and a short list of contributed modules. Organisations with dozens of sites (common in universities and government) need a central inventory of core and module versions, a shared upstream or distribution so one Composer update fixes every site, a patch SLA tied to advisory risk scores, and central logging.
Common misconfigurations
- Unsupported cores left running: Drupal 7 and 9 sites with no security coverage, often forgotten microsites. SA-CORE-2026-004 offered only best effort manual patches for 8.9 and 9.
- Slow patching after highly critical advisories: the 2018 Drupalgeddon flaws (CVE-2018-7600, CVE-2018-7602) and CVE-2019-6340 were mass exploited soon after release and are all in the KEV catalog.
- Web server can write code: lets any file upload or code execution flaw become a persistent backdoor. CVE-2020-13671 (KEV) involved file name extension sanitisation in uploads.
- Restricted permissions on broad roles:
administer usersor Full HTML given to the authenticated role or to all editors. - Empty or committed hash_salt: a salt in Git or in a database backup weakens the signing of one-time login links and other tokens derived from it.
- Secrets in exported configuration: API keys committed to the configuration sync directory. The same pattern hurts WordPress sites; see the PHP and WordPress Security Guide.
Framework mapping
| Control area | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | CIS Controls v8.1 |
|---|---|---|---|
| Supported versions and patching | SI-2, SA-22 | PR.PS-02, ID.RA-01 | 2.2, 7.4 |
| Roles, restricted permissions, user 1 | AC-2, AC-6 | PR.AA-05 | 5.4, 6.8 |
| MFA for administrators | IA-2 | PR.AA-03 | 6.5 |
| Secure settings and file permissions | CM-6, CM-7 | PR.PS-01 | 4.1 |
| Logging and integrity monitoring | AU-2, AU-12, SI-7 | PR.PS-04, DE.CM-09 | 8.2 |
Practitioner checklist
- Every site runs a Drupal minor with security coverage, with a dated plan for Drupal 10 end of life on 9 December 2026.
- Highly critical advisories are patched the same day; SA-CORE-2026-004 and all KEV listed Drupal CVEs are confirmed fixed.
- Restricted permissions sit only with a small administrator role, and every such account uses MFA.
- User 1 is a protected break glass account, not a daily login.
trusted_host_patternsis set,hash_saltis loaded from outside the web root, andupdate_free_accessis FALSE.- Code is not writable by the web server; the files directory blocks PHP execution.
- Secrets are kept out of exported configuration and the repository.
- Syslog forwarding and file integrity monitoring are on; login failures and role changes alert.
- Unused modules, including web services modules, are uninstalled.
Standards and references
- Drupal: SA-CORE-2026-004 Drupal core highly critical SQL injection
- Drupal: Core release schedule
- Drupal: Security in Drupal
- Drupal: Securing file permissions and ownership
- Drupal: default.settings.php
- CISA: Known Exploited Vulnerabilities Catalog
- NIST: SP 800-53 Rev. 5
- MITRE ATT&CK: Exploit Public-Facing Application
Related NHI Mgmt Group resources: PHP and WordPress Security Guide · Web Application Firewall Guide · Secrets Management Guide · Service Account Security Guide