Problem/Motivation
This was originally reported to the security team who decided it could be public
This issue was reported to IBM as a vulnerability in the IBM API Connect Developer Portal, however the actual vulnerability is in the underlying Drupal Core translation modules. The vulnerability allows one Drupal user with administrative permissions to create a page which can force another Drupal user to make a call to a server under the control of an attacker. I have tested this on a vanilla Drupal 10 site and the vulnerability is present there.
Steps to reproduce
1. Set up a HTTP server somewhere, this will be the target of the malicious request (I used "ncat -l -p 80 --keep-open" just to see the request come in, obviously an attacker would use a server that they control) This probably isn't needed, as reviewing browser network traffic is enough to see that a request was made.
2. Enable the four Drupal Core modules Language, Interface Translation, Content Translation and Configuration Translation
3. Add a second language under Manage->Configuration->Region and language->Languages
4. Navigate to Manage->People->Roles and create a new role with a name of
">'><img src="http://127.0.0.1/evil">
5. Navigate to the roles list and from the action menu for our malicious role select "Translate"
6. Choose a language and press the add button
The browser will make a call to the malicious server -- in my example I can clearly see the request come in:

Proposed resolution
Change $value = '<span lang="' . $source_language->getId() . '">' . nl2br($source_config) . '</span>'; to render as plain text string with \Drupal\Component\Utility\Html::escape
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #5 | Screenshot 2025-02-06 at 12.21.45 PM.png | 456.62 KB | akalata |
Issue fork drupal-3440399
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3440399-ssrf-vis-role
changes, plain diff MR !11138
Comments
Comment #3
larowlanComment #4
akalata commentedUpdating steps to reproduce.
Comment #5
akalata commentedComment #7
akalata commentedEdit issue title
Comment #8
akalata commentedComment #9
gregglesThe title is SSRF (server side request forgery) which I understand to mean a request from the server to other machines on the server's network as described in portswigger and owasp.
The description includes:
and
That is not a server-side request.
I can see how this would let an admin gain data about the site visitors, which is not ideal, but on most sites an admin who can enter translations can probably already make other changes to a site that would also gain information about a visitor.
I'm surprised if this is the only place where a user with only the ability to translate could insert an image. I think other strings are similarly not filtered.
Comment #10
smustgrave commentedSeems the test-only failure is passing when would expect to fail so think that needs tweaking https://git.drupalcode.org/issue/drupal-3440399/-/jobs/4273224
If you are another contributor eager to jump in, please allow the previous poster at least 48 hours to respond to feedback first, so they have the opportunity to finish what they started!
Comment #12
prudloff commentedI adjusted the test to fail without the fix.
Comment #13
berdirthis does a strip tags, that seems wrong and the escape then is pointless?
if the source config has some HTML, you want to see that as the translation needs the same.
It should just escape and should then also assert the escaped markup
Comment #14
prudloff commentedI removed the strip_tags() call, I think it was added in order to not break ConfigTranslationUiModulesTest.
And I added an assertion with the escaped string (it needs to be precise because other parts of the page already contain the escaped payload in other places).
Comment #15
smustgrave commentedBoth tests appearing to be failing as expected
New test
Solution matches what's in the summary which seems to be written by security team.
Believe this one is good.
Comment #16
longwaveDo we know why the nl2br() was originally there? Is it possible that there are newlines in the string and we need to care about representing them correctly still?
Comment #17
longwaveTraced this back to an issue in the original config_translation module, where
nl2br()was explicitly added to fix a bug: #1945184: Original value display is brokenTherefore I think we should keep this feature? (and probably add a test for it...) I think
nl2br(Html::escape(...))will work as expected.Comment #18
prudloff commentedI added nl2br back.
Comment #19
longwaveThanks! There was no test for the
nl2br()before, let's not hold this up on adding one.Comment #24
catchJust escaping the HTML looks sensible, it's needed anyway for translators.
Committed/pushed to 11.x and cherry-picked back through to 10.5.x, thanks!