Page MenuHomePhabricator

Remote Code Execution on svgtranslate
Closed, ResolvedPublicSecurity

Description

Vulnerability Report: Unauthenticated Remote Code Execution in Wikimedia's SVGTranslate

System Overview

SVGTranslate, a tool developed by Wikimedia, is designed to convert SVG files into PNG images while allowing for language-based text substitutions within the SVG content. This application uses a PHP backend, with specific functionality managed through the ApiController.php and a service layer implemented in Renderer.php. The source code is available at the SVGTranslate GitHub repository.

    1. Component Path
  • API Controller: /src/Controller/ApiController.php
  • Rendering Service: /src/Service/Renderer.php

Vulnerability Description

The application is vulnerable to an unauthenticated remote code execution (RCE) due to improper handling of user input in the language parameter during the PNG generation process. The vulnerability stems from the way shell commands are constructed and executed in the rendering service.

Vulnerability Analysis

Entry Point

The API endpoint defined as:

/**
 * Serve a PNG rendering of the given SVG in the given language (without any user-provided
 * translation strings).
 *
 * @Route("/api/file/{filename}/{lang}.png", name="api_file", methods="GET")
 *
 * @param string $filename
 * @param string $lang
 * @return Response
 */
public function getFile(string $filename, string $lang): Response
{
    $filename = Title::normalize($filename);
    $content = $this->svgRenderer->render($this->cache->getPath($filename), $lang);
    return new Response($content, 200, [
        'Content-Type' => 'image/png',
        'X-File-Hash' => sha1($content),
    ]);
}

This endpoint processes file names and language parameters to serve PNG files. The lang parameter is passed to the renderer without adequate validation.

Renderer Vulnerability

The Renderer.php service processes the language parameter as part of a shell command:

/**
 * Render a SVG file to PNG and either save a file or return the image.
 * @param string $file Full filesystem path to the SVG file to render.
 * @param string $lang Code of the language in which to render the image.
 * @param string $outFile Full filesystem path to the file to write the PNG to.
 * @throws ProcessFailedException If the PNG conversion failed.
 * @return string The PNG image contents, or nothing if an $outFile was provided.
 */
public function render(string $file, string $lang, ?string $outFile = null) : string
{
    $command = $this->rsvgCommand.' "$SVG"';
    if ('fallback' !== $lang) {
        $command .= " --accept-language=$lang";
    }
    if ($outFile) {
        $command .= ' > "$PNG"';
    }
    $process = Process::fromShellCommandline($command);
    $process->mustRun(null, ['SVG' => $file, 'PNG' => $outFile]);
    return $process->getOutput();
}

The vulnerability is in the concatenation of the $lang variable into the shell command without proper sanitization. This allows an attacker to inject additional shell commands.

Proof of Concept

Using the following request, an attacker can execute arbitrary commands:

GET /api/file/SI_base_unit1.svg/fr;id;.png HTTP/2
Host: svgtranslate.toolforge.org

svgtranslate_rce.png (639×1 px, 236 KB)

This results in unauthorized command execution, as demonstrated by the server response showing system user information.

Mitigation Recommendations

  1. Input Validation: Implement strict validation for all input parameters, especially those being incorporated into command line operations.
  2. Secure Command Execution: Use array parameters for command execution to ensure separation between commands and arguments, preventing injection.
  3. Security Audit and Testing: Conduct a thorough security review and penetration testing to identify and fix potential vulnerabilities.

NOTE

In this report, the vulnerability was directly tested on the Wikimedia instance, demonstrating the issue without setting up a separate testing environment. This was done to quickly verify the clear and evident potential for exploitation as indicated by the code analysis. It should be noted that no harmful actions were undertaken during this testing process.

Details

Risk Rating
Low
Author Affiliation
Wikimedia Communities

Event Timeline

At the very least, within Renderer->render(), $lang should be sanitized via escapeshellarg() and/or a limiting regular-expression/allow-list.

At the very least, within Renderer->render(), $lang should be sanitized via escapeshellarg() or a limiting regular-expression/allow-list.

Exactly!

And I have a question, once this vulnerability is patched, may I have permission to discuss the details about the vulnerability on my blog https://chocapikk.com/ for educational purposes? I believe it could provide valuable insights to the community. Thanks

RhinosF1 changed Author Affiliation from WMF Product to Wikimedia Communities.May 22 2024, 9:04 PM
RhinosF1 changed Risk Rating from N/A to High.

Thanks for reporting this! Fixing it now. It looks like it was introduced in 69bb1e8c1b66195efc2e1ea534f9316f2566f0c1 for T358305.

I don't mind if you write it up on your blog.

We will need to cycle your DB and k8s credentials for the tool, and any other secrets you might have (envvars/files).

I stopped the webservice as a preventive measure, after fixing the issue and before re-enabling do:

@dcaro Thanks! The tool doesn't need a database, so should I request that its user be dropped or is it okay to just delete replica.my.cnf?

I've deleted the tool's user certs.

PR to fix the issue: https://github.com/wikimedia/svgtranslate/pull/767

This has been merged and released in version 2.0.2.

@dcaro Thanks! The tool doesn't need a database, so should I request that its user be dropped or is it okay to just delete replica.my.cnf?

You will not be able to delete it (you need root), I will.
Unfortunately, we do create those DB users by default currently, so you get one assigned no matter what (we might stop doing it by default and allow users to self-serve if they need it, but not there yet).

Your DB credentials should be refreshed in a minute:

root@cloudcontrol1007:~# /usr/local/sbin/maintain-dbusers delete tools.svgtranslate --account-type=tool
INFO [root.delete_account:1084] Deleted tool account in 185.15.56.15:3306 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1021.eqiad.wmnet:3311 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1013.eqiad.wmnet:3311 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1017.eqiad.wmnet:3311 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1021.eqiad.wmnet:3312 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1014.eqiad.wmnet:3312 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1018.eqiad.wmnet:3312 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1021.eqiad.wmnet:3313 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1013.eqiad.wmnet:3313 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1017.eqiad.wmnet:3313 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1021.eqiad.wmnet:3314 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1015.eqiad.wmnet:3314 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1019.eqiad.wmnet:3314 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1021.eqiad.wmnet:3315 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1016.eqiad.wmnet:3315 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1020.eqiad.wmnet:3315 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1021.eqiad.wmnet:3316 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1015.eqiad.wmnet:3316 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1019.eqiad.wmnet:3316 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1021.eqiad.wmnet:3317 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1014.eqiad.wmnet:3317 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1018.eqiad.wmnet:3317 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1021.eqiad.wmnet:3318 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1016.eqiad.wmnet:3318 for tools.svgtranslate
INFO [root.delete_account:1084] Deleted tool account in clouddb1020.eqiad.wmnet:3318 for tools.svgtranslate
INFO [root.delete_account:1100] Deleted replica config for tool account tools.svgtranslate

If you fixed the issue, feel free to start your webservice again :)

Great, thanks!

I've installed the new version and restarted the web service, and all seems to be working correctly and the above example of an exploited URL is showing the proper error: https://svgtranslate.toolforge.org/api/file/SI_base_unit1.svg/fr;id;.png (although we could probably improve the error message display!).

@Chocapikk1337 -

I don't mind if you write it up on your blog.

Let's wait until the issue is fully-resolved and double-check with WMF-Legal. Most of the time they are fine with public write-ups by third-parties, but sometimes they're not.

We could also add you to our security hall of fame, if you're interested.

@Chocapikk1337 -

I don't mind if you write it up on your blog.

Let's wait until the issue is fully-resolved and double-check with WMF-Legal. Most of the time they are fine with public write-ups by third-parties, but sometimes they're not.

We could also add you to our security hall of fame, if you're interested.

Hello, yes, so I'll wait, no problem. Yes, the hall of fame would be cool. Thank you very much for your responsiveness in responding to this vulnerability.

I also have another question. I don't think this is necessary. But a CVE ID can be assigned to this bug or not? If yes, how is it going? Do I do the process or do you?

And I want to confirm that the vulnerability is indeed fixed after some tests.

I also have another question. I don't think this is necessary. But a CVE ID can be assigned to this bug or not? If yes, how is it going? Do I do the process or do you?

And I want to confirm that the vulnerability is indeed fixed after some tests.

I'm not sure security team have a process for CVEs for toolforge.

This is hosted on Github so I'd look to @Samwilson to see if he can request one via the Github Security tab on the repo.

We don't normally bother issuing security advisories with tools like this that are only deployed in one place. It's not considered production. There might be other people using it elsewhere of course, but it's unlikely.

Heh, I had typed out a response to this but didn't submit it. Anyhow, @RhinosF1 and @Samwilson are correct. Here's my original response for posterity:

I also have another question. I don't think this is necessary. But a CVE ID can be assigned to this bug or not? If yes, how is it going? Do I do the process or do you?

That's generally up to the maintainers of the software. But basically anybody can request a CVE via this Mitre form. I'd note that Mitre is extremely backlogged right now, and it could take months for them to process a CVE request.

And I want to confirm that the vulnerability is indeed fixed after some tests.

It should definitely be fixed via https://github.com/wikimedia/svgtranslate/pull/767 (Symfony's Process notes related to the fix) and I believe @Samwilson did do some testing? So unless you've found another way to exploit this, it should be fine for now.

I believe @Samwilson did do some testing?

I did (and all seems well), and once this task is un-hidden it can also be tested by CommTech's QA engineers.

I believe @Samwilson did do some testing?

I did (and all seems well), and once this task is un-hidden it can also be tested by CommTech's QA engineers.

Great. I was waiting for a yes/no from WMF Legal on full disclosure for this issue (mainly around the blog write-up). If I don't hear anything back from them by next week, I think we can at least make this task public (especially since the PR was public).

sbassett changed the task status from Open to In Progress.May 28 2024, 4:59 PM
sbassett triaged this task as Low priority.
sbassett moved this task from Incoming to In Progress on the Security-Team board.
sbassett added a project: SecTeam-Processed.

I haven't been able to reproduce the RCE vulnerability in the description.

I also used OWASP ZAP to do an "active scan" against https://svgtranslate.toolforge.org/api/file/100_Years_War_France_1435.svg/de/9f8a89d0e690cb8b9bebf1230d5ccc35.png, but it did not report any "high priority" alerts.

Test environment: https://svgtranslate.toolforge.org version 2.0.2

sbassett moved this task from In Progress to Our Part Is Done on the Security-Team board.

I received approval from WMF-Legal to make this public and for @Chocapikk1337 to write a public blog post (or whatever) about this issue. Also, @Chocapikk1337, let us know if you'd like to be added to the Wikimedia security hall of fame.

sbassett changed the visibility from "Custom Policy" to "Public (No Login Required)".Jun 12 2024, 3:07 PM
sbassett changed the edit policy from "Custom Policy" to "All Users".
sbassett changed Risk Rating from High to Low.

Hello @sbassett, Yes I would like to be added to the Security hall of fame, thank you !

Change #1046784 had a related patch set uploaded (by SBassett; author: SBassett):

[operations/deployment-charts@master] Update security.wikimedia.org helmfile image version

https://gerrit.wikimedia.org/r/1046784

Change #1046784 merged by jenkins-bot:

[operations/deployment-charts@master] Update security.wikimedia.org helmfile image version

https://gerrit.wikimedia.org/r/1046784