Stored XSS in Roles in osticket/osticket


Reported on

Dec 15th 2022


Stored cross-site scripting vulnerabilities arise when user input is stored and later embedded into the application's responses in an unsafe way. An attacker can use the vulnerability to inject malicious JavaScript code into the application, which will execute within the browser of any user who views the relevant application content.

Following agent authentication, with the need of administrative privileges or the ability to create, edit or delete roles within the application, an attacker can take advantage of insufficient control of the user input on the POST parameter name used while editing an existing role, such as the default one All Access, to inject arbitrary javascript code that will be permanently stored. In this way, the input entered by the attacker will be triggered whenever the list of roles in scp/roles.php is displayed or, in general, whenever the role management form is present, such as while creating a user or editing an existent one.


• Log into the osTicket agent login form at osTicket/scp/login.php using a privileged user.

• Switch to the Admin Panel in the upper right corner.

• Move to the Agents > Roles assignment form at osTicket/scp/roles.php.

• Here, you can both choose to create a new role or edit an existent one. For this PoC we'll be using the default privileged one, namely All Access, so select it to proceed with the role update.

• In the Name label, inject the XSS payload <script>alert(1)</script> right after the All Access string. The input will look like the following All Access<script>alert(1)</script>. Then, saving your changes you'll be notified that the role has been updated successfully.

• Browse again the Agents > Roles assignment form at osTicket/scp/roles.php to see the XSS popping up whenever the list of roles is fetched. It will also trigger in osTicket/scp/staff.php while choosing to add a new agent, since the list of roles is fetched in the Access tab.

Proof of Concept

POST /osTicket/scp/roles.php?id=1 HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 734
Origin: http://<REDACTED>
Connection: close
Referer: http://<REDACTED>/osTicket/scp/roles.php?id=1
Cookie: OSTSESSID=3c90r6qlsn81b655j5jhd9ef37
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
DNT: 1
Sec-GPC: 1



If an attacker can control a script that is executed in the victim's browser, then they can typically fully compromise that user. Amongst other things, the attacker can perform any action within the application that the user can perform, view any information that the user is able to view, modify any information that the user is able to modify or initiate interactions with other application users, including malicious attacks, that will appear to originate from the initial victim user.

Ideally, in this practical example, the victim user of this attack is represented by another osTicket agent, with same administrative privileges or the ability to create, edit or delete roles within the application.

We are processing your report and will contact the osticket team within 24 hours. 3 months ago
We have contacted a member of the osticket team and are waiting to hear back 3 months ago
2 months ago



Please test the below patch and let us know if it fully mitigates the vulnerability:

diff --git a/include/class.role.php b/include/class.role.php
index b4a8723b..f2d07057 100644
--- a/include/class.role.php
+++ b/include/class.role.php
@@ -156,9 +156,10 @@ class Role extends RoleModel {
     function update($vars, &$errors) {
-        if (!$vars['name'])
+        $name = Format::sanitize($vars['name']);
+        if (!$name)
             $errors['name'] = __('Name required');
-        elseif (($r=Role::lookup(array('name'=>$vars['name'])))
+        elseif (($r=Role::lookup(array('name'=>$name)))
                 && $r->getId() != $vars['id'])
             $errors['name'] = __('Name already in use');
         elseif (!$vars['perms'] || !count($vars['perms']))
@@ -167,8 +168,8 @@ class Role extends RoleModel {
         if ($errors)
             return false;
-        $this->name = $vars['name'];
-        $this->notes = $vars['notes'];
+        $this->name = $name;
+        $this->notes = Format::sanitize($vars['notes']);
         $this->updatePerms($vars['perms'], $errors);


2 months ago


Hi @JediKev. Fix applied and I'm not able to reproduce this issue anymore. Previously reported Stored XSS seem to have been fixed correctly, both on osTicket/scp/roles.php and osTicket/scp/staff.php.

JediKev validated this vulnerability 12 days ago
Samuele Gugliotta has been awarded the disclosure bounty
The fix bounty is now up for grabs
The researcher's credibility has increased: +7
JediKev marked this as fixed in v1.16.6 with commit 9fb01b 12 days ago
JediKev has been awarded the fix bounty
This vulnerability has been assigned a CVE
JediKev published this vulnerability 12 days ago
to join this conversation