Searchlight Cyber is used by security professionals and leading investigators to surface criminal activity and protect businesses. Book your demo to find out how Searchlight can:
April 2, 2025
Loose Types Sink Ships: Pre-Authentication SQL Injection in Halo ITSM
Halo ITSM is an IT support management software that can be deployed on-premise or in the cloud. Currently, there are around ~1000 cloud deployments of this software under the haloitsm.com domain, not accounting for all the on-premise deployments.
This software is critical, as it houses IT support tickets often containing credentials or internal documentation. When we first noticed the presence of this software on our customers’ attack surfaces, we decided to take a deeper look at its internals to ensure that our customers would not be compromised through the presence of this software on their network.
Halo ITSM’s code base does not do a good job at enforcing security controls, with ORM’s not being consistently used and several areas of the code base being vulnerable to SQL injection. During our audit, we discovered a single pre-authentication SQL injection vulnerability, possible due to the usage of weakly typed objects and string concatenation.
An interesting pattern we noticed in the pre-authentication attack surface was that several locations could have been vulnerable to SQL injection, only being prevented by using strongly typed objects enforcing the integer type. The one that we discovered pre-authentication was the only entry point for SQL with a loose type, where an assumed integer value could be transmitted as a string, which was later concatenated into a SQL query.
Close Calls
As we mentioned earlier, there were a lot of close calls that were prevented by strongly typed objects. An example of what we’re talking about can be found below, found inside `NetHelpDesk.API/Controllers/NotifyController.cs`:
We can see that the input for this controller is taken from the request body, and is typed to a Device42Webhook. This object is typed like so:
namespace HaloClassLibrary.Models;
public class Device42Webhook
{
public string category { get; set; }
public string user { get; set; }
public string action { get; set; }
public Device42WebhookData data { get; set; }
public object resourceObject { get; set; }
}
Where Device42WebhookData is typed like so:
namespace HaloClassLibrary.Models;
public class Device42WebhookData
{
public int? id { get; set; }
public int? type_id { get; set; }
public string name { get; set; }
public string notes { get; set; }
}
Unfortunately, the `data.id` value is typed as an integer, which means that the following code is not vulnerable to SQL injection, despite the concatenation of a user input value inside a SQL query:
if (await _CommonFunctions.getOneFieldInt(this._context, "area", "aarea", "where adevice42id=" + device42Webhook.data.id.ToString(), -1, "", new bool?(false), null, "") > 0)
Finding the pre-authentication injection point
As shown earlier in this blog post, many potential injection points are prevented due to typing despite SQL queries being constructed with user input via concatenation.
There was a single injection point discovered in the pre-authentication attack surface that did not follow this paradigm, the PostNotify controller located in `NetHelpDesk.API/Controllers/NotifyController.cs`.
When following the logic of the controller, we start with its definition:
[HttpPost]
public async Task<IActionResult> PostNotify([FromBody] Dictionary<string, object> obj)
It’s important to note that this controller has no decorators that enforce authentication, and most importantly, this controller takes in a dictionary object that is not typed.
The fact that the controller is taking in an untyped object is essential for exploiting this issue, as many other pre-authentication controllers that could have led to SQL injection often had objects with strict types, where integer typing would prevent SQL injection from being possible.
Following the logic of this controller:
if (obj.ContainsKey("publisherId") && obj["publisherId"].ToString() == "tfs")
{
return await this.PostAzureDevOps(JsonConvert.DeserializeObject<DevOpsWebhook>(JsonConvert.SerializeObject(obj)), false, true, false);
}
if (obj.ContainsKey("webhookEvent") && (obj["webhookEvent"].ToString() == "jira:issue_updated" || obj["webhookEvent"].ToString() == "comment_created" || obj["webhookEvent"].ToString() == "issue_commented" || obj["webhookEvent"].ToString() == "jira:issue_created"))
{
return await this.PostJira(obj);
}
if (obj.ContainsKey("integration") && obj["integration"].ToString() == "PagerDuty")
{
int? rpagerdutywebhooktype = controlobj.rpagerdutywebhooktype;
int num = 0;
if ((rpagerdutywebhooktype.GetValueOrDefault() == num) & (rpagerdutywebhooktype != null))
{
return await this.PostPagerDutyLegacy(obj, controlobj);
}
}
if (obj.ContainsKey("sessionid") && obj.ContainsKey("tracking0"))
{
return await this.PostLogMeIn(obj, controlobj, list);
}
To reach our sink, we need to provide a JSON object that contains `sessionid` and `tracking0` – the SQL injection vulnerability exists in the `PostLogMeIn` function:
private async Task<IActionResult> PostLogMeIn(Dictionary<string, object> obj, Control controlobj, List<ModuleSetup> modules)
... omitted ...
if (obj.ContainsKey("lastactiontime") && !string.IsNullOrWhiteSpace(obj["lastactiontime"].ToString()))
{
int num = await _CommonFunctions.getOneFieldInt(this._context, "uname", "unum", "where ulogmeinid=" + obj["techid"].ToString(), -1, "", new bool?(false), null, "");
if (num > 0)
{
DateTime startdate;
DateTime.TryParse(obj["pickuptime"].ToString(), out startdate);
DateTime enddate;
DateTime.TryParse(obj["lastactiontime"].ToString(), out enddate);
RemoteSessionData remoteSessionData = new RemoteSessionData();
remoteSessionData.rsdthirdpartyid = obj["sessionid"].ToString();
remoteSessionData.rsdunum = new int?(num);
RemoteSessionData remoteSessionData2 = remoteSessionData;
remoteSessionData2.rsduname = await _CommonFunctions.getOneFieldString(this._context, "uname", "uname", "where unum=" + num.ToString());
We need to provide `lastactiontime` in our JSON request, to finally reach the SQLi sink here:
"where ulogmeinid=" + obj["techid"].ToString()
This calls into `_CommonFunctions.getOneFieldInt` where the fourth argument allows for arbitrary SQL to be executed if there is user-controllable input within it:
getOneFieldInt(ApplicationDbContext _context, string tablename, string fieldname, string wheresql = "", int ifnullvalue = -1, string orderbysql = "", bool? setresulttomax = false, DateTime? dateparam1 = null, string withSql = "")
The proof-of-concept has temporarily been removed due to a request from Halo PSA.
Variant Hunting
As with all bugs, we tried to find variants of this issue to see if this mistake had been made again elsewhere. It was surprising to see that no other locations in the code base followed the same pattern. We utilized regexes and a custom Semgrep rule to ensure that similar vulnerabilities did not exist.
Below, you can find some of the regex searches we did across the codebase and their corresponding results:
Conclusion
Halo ITSM has a really large attack surface, with many code patterns that can lead to serious vulnerabilities. Many potential improvements can be made to this codebase, especially with how SQL queries are constructed. This research showed how code hygiene and strict typing could have prevented a pre-authentication entry point.
From our analysis of this code base, even though the vendor has patched the issue we highlighted, there are deeper issues with the code base, especially in its post-authentication attack surface.
As always, customers of our Attack Surface Management platform were the first to know when this vulnerability affected them. We continue to perform original security research to inform our customers about zero-day vulnerabilities in their attack surface.
in this article
Book your demo: Identify cyber threats earlier– before they impact your business
Enhance your security with advanced automated dark web monitoring and investigation tools
Continuously monitor for threats, including ransomware groups targeting your organization
Prevent costly cyber incidents and meet cybersecurity compliance requirements and regulations