"Anemone": The brief tale of a Drupal core security advisory

Image
Pink anemone and orange fish
By xjm, June 20, 2025

A long while back, security researcher Sam Mortenson reported a cross-site scripting vulnerability in Drupal core's Link module. Essentially, the options property on link fields was not being properly sanitized. This meant cross-site scripting was possible under some circumstances -- and, as always for cross-site scripting, we were concerned that the XSS could be combined with other attacks and escalated to more serious exploits.

A fix for this vulnerability was released back in March as SA-CORE-2025-004. It's been more than two months now since that advisory was issued, which means that the embargo on disclosures for it is over, and I can tell you a bit more about it.

The conundrum

The vulnerability (known internally as "Anemone") was fairly straightforward, but we faced a conundrum. Over 60 contributed modules extend core's link field formatter, which meant there were two potential problems:

  1. Any of these modules might have had the same vulnerability as core, if they overrode or reimplemented the vulnerable core code.
  2. Any of these modules could break when we changed the Link module's code to mitigate the vulnerability.

Two fronts 

Over a long period, we tackled this problem on two fronts:

  1. We researched different potential solutions at various levels of the Link module's API. We looked for a fix that would provide the most complete protection against the vulnerability without breaking downstream code. 
  2. Meanwhile, we also audited all the contributed modules hosted on Drupal.org. We checked to see which would be fixed by our core fix, which would remain insecure (and therefore require coordinated security advisories), and which might be disrupted by the various proposed solutions.

The best fix

The best fix we came up with was identified by core release manager longwave and implemented by framework manager larowlan, both members of the Security Team: We would improve core's existing XSS sanitization to properly handle options attributes, and then call that improved sanitization in LinkFormatter::buildUrl() and MenuLinkContent::getUrlObject(). To achieve this, we needed a new API for better attribute sanitization, and the Xss::attributes() method also needed a small improvement to support that new API.

The reason core SAs add duplicated code

We couldn't just make changes to Xss::attributes() in a security release, though. Core's XSS utility is used everywhere in core and contrib, and even offered as part of a standalone utility component that doesn't depend on Drupal at all. Changing such an important API in a security release would have sweeping consequences, and even making backwards-compatible additions to it would violate semantic versioning. Such disruption could introduce regressions for contributed or custom code and thus prevent site owners from updating to the security release -- exactly the opposite of what we wanted.

So, instead of making changes to the XSS utility, we did what (under other circumstances) would be a bad practice: We made a copy of the code for use in the security fix only, and added the changes we needed to that copy. 

This is a strategy we often use for core security releases. We duplicate and update code targeting the fix to avoid making disruptive changes during the security window. Then, following the release and (optionally) an embargo period, we file a followup issue in the public core issue queue to refactor our addition into the proper core API. The refactoring happens in one of core's minor releases, when we can safely include API additions, behavior changes, and new deprecations under our allowed changes policy.

Five contributed modules

Once we identified the safest fix, we were able to finalize the list of contributed modules that would still be vulnerable despite our update. In our audit, we found five contributed modules opted into security coverage that did not call buildUrl() directly (meaning they also would not call the new sanitization we were adding). Of those:

  • One did not use the API in a vulnerable way.
  • One could already be updated in the public issue queue to more properly use buildUrl(), without disclosing anything related to the private security issue.
  • The remaining three needed their own security fixes, and therefore required their own security advisories.

The coordinated release

Drupal practices coordinated disclosure: When we know of a vulnerability affecting one or more projects, we publish the details of that vulnerability only after the maintainers are able to fix it. In practice, this means that when core and contrib share the same vulnerability, we publish advisories for all affected projects during the same security window.

Drupal core security windows are on the third Wednesday of every month from 16:00 to 22:00 UTC. We often collect multiple core security advisories in private and publish them in the same release once they're ready, so that site owners don't have to install as many core updates. 

However, when we have a complex or potentially disruptive security advisory -- like a core security release that needs to be coordinated with multiple contributed projects -- we try to publish it by itself. If the site owner is prevented from updating immediately because of (say) breaking changes in one of the updated contributed modules, we don't want them to also be dealing with other newly disclosed vulnerabilities from other core advisories in the same window. (Plus, every advisory we release in a given window adds complexity for the Security Team and release managers.)

So, on March 19, 2025, we published the following four advisories, all fixing different instances of the same link option attribute sanitization vulnerability:

A team effort

All core security advisories are collaborative efforts by members of the Drupal Security Team, but this one was especially involved! Over a dozen people contributed to fixing this security issue over time: akalataalexpottbenjifisherbramdriesencatcheffulgentsiajenlamptonlarowlanlongwavemcdruidpandaskiphenaproximasamuel.mortenson, and myself.

Thanks to Tag1 Consulting for sponsoring my time on this issue last year through Patreon, to HeroDevsZoocha, and OPTASY for supporting my Security Team work, and to Acquia for funding me and several other contributors to work on it together previously.

I'd also like to give particular acknowledgment to pandaski, whose extensive efforts researching the contrib ecosystem were crucial to picking a solution and ultimately getting the advisory on track for release!

Interested in supporting Drupal's security? You can sponsor me.