Back to Research blog

Loose Types Sink Ships: Pre-Authentication SQL Injection in Halo ITSM

Share on social

April 2, 2025

Lorem ipsum

Table of Contents


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'


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


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.

About Assetnote

Searchlight Cyber's ASM solution, Assetnote, provides industry-leading attack surface management and adversarial exposure validation solutions, helping organizations identify and remediate security vulnerabilities before they can be exploited. Customers receive security alerts and recommended mitigations simultaneously with any disclosures made to third-party vendors. Visit our attack surface management page to learn more about our platform and the research we do.

Adam Kues

Author

Adam Kues

Security Researcher at Searchlight Cyber

Shubham Shah

Author

Shubham Shah

Chief Security Research Officer at Searchlight Cyber

Shubham Shah is Chief Security Research Officer, having joined Searchlight Cyber following the acquisition of Assetnote, where he was Co-Founder and CTO. Shubham leads the global security research team whose findings feed directly into Searchlight Exposure – surfacing zero-day vulnerabilities in the tools organisations rely on, often months ahead of public disclosure. He remains a prolific bug bounty hunter ranked in the top 50 hackers on HackerOne, and has presented at various industry events including QCon London, Kiwicon, AusCert, BSides Canberra, and CrikeyCon.

Dylan Pindur

Author

Dylan Pindur

Security Researcher at Searchlight Cyber

Explore related Content

Research

Exploit brokers pay $500,000 for a WordPress RCE. I found one with GPT5.6 Sol Ultra and $25

July 20, 2026

Research

wp2shell: Pre Authentication RCE in WordPress Core

July 17, 2026

Research

Smashing the ServiceNow Sandbox – Pre Authentication RCE

July 14, 2026

Research

CargoWise WebTracker – The Keys Were in the Cargo

June 25, 2026

Research

Two Bypasses for Chrome's Sanitizer API

May 22, 2026

Research

Keys to the Kingdom: Anonymous SQL Injection in Drupal Core (CVE-2026-9082)

May 21, 2026