03 Июн, 2025

How an Untamed Filter Parameter Unlocked SQL Injection

Background — When Query Builders Trust User Input

During a penetration test of a multi-tenant ERP platform, our team uncovered a critical SQL-injection vulnerability in the OData $filter parameter used by several REST endpoints. The application dynamically merged user-supplied filters into SQL WHERE clauses without parameterisation, leaving the data layer wide-open while the API presented a façade of strict access control. This is the classic “string-building trap”—security checks live on the client, but the final query assembly happens unchecked on the server.

Discovery — Two Quotes, Full Control

During a penetration test of a multi-tenant ERP platform, our team uncovered a critical SQL-injection vulnerability in the OData $filter parameter used by several REST endpoints. The application dynamically merged user-supplied filters into SQL WHERE clauses without parameterisation, leaving the data layer wide-open while the API presented a façade of strict access control. This is the classic “string-building trap”—security checks live on the client, but the final query assembly happens unchecked on the server.

				
					GET /api/v1/<service>/GenXDatas
    ?$filter=TypeCode eq 'HotKeys'
             and ProductID eq 'Ep'
             and Key1 eq 'manager'''
Authorization: Bearer <JWT>

				
			

Result – The server replied 200 OK with a valid JSON payload, demonstrating that the payload reached the database unfiltered. A follow-up payload such as

				
					Key1 eq 'manager''' or 1=1 --

				
			

returned full record sets, confirming Boolean-based SQL injection.

Technical Root Cause — Dynamic SQL in $filter Handling

				
					// Simplified pseudo-code
var where = $"TypeCode = '{type}' AND ProductID = '{product}' AND Key1 = '{key1}'";
var sql   = $"SELECT * FROM GenXData WHERE {where}";

				
			
  • No prepared statements — variables are spliced directly into SQL.

  • Missing escaping/validation — a single quote ends the literal and appends attacker code.

  • Shared helper — the same builder assembles queries for search, reporting and admin views, multiplying impact.

Real-World Risks — What Attackers Could Do

Bulk data exfiltration – Read sensitive configuration and business records across tenants.

Повышение привилегий – Manipulate auth tables to grant admin roles.

Data tampering – Alter pricing, insert fraudulent defaults, disrupt production schedules.

Denial of service – Run heavy queries (WAITFOR DELAY, expensive joins) to exhaust resources.

Broader Lessons — SQL Injection Isn’t a “Legacy” Issue

Modern APIs (OData, GraphQL, serverless functions) still fall to SQLi when dev teams assume “the framework sanitises it.” Typical patterns include:

• Dynamic string templates for “flexible” reporting.

• Conditional clause assembly instead of parameter arrays.

• Debug toggles that disable ORM parameterisation and later reach production.

If you concatenate anything into SQL, you need a threat-model review—framework or not.

Recommendations — Fix Fast, Prevent Future Slips

Parameterise every query
  • In .NET: SqlParameter, Entity Framework lambdas—never raw string interpolation.

Harden query builders
  • Whitelist permitted fields/operators in $filter.
  • Strip comments (--, /* */) and block stacked queries (;).

Centralised validation layer
  • Reject payloads with unmatched quotes or suspicious keywords.

Least-privilege DB accounts
  • Application credentials need only minimal SELECT/EXEC rights—no ALTER, DROP.

Continuous monitoring
  • Log anomalous $filter lengths and “always true” patterns (or 1=1).
  • Alert on spikes in query time or cardinality.

Conclusion — Query Safety Is a Continuous Discipline

Parameterized queries add a few lines of code but save entire businesses. Here, a single unescaped quote transformed a harmless filter into a master key for the ERP’s data layer. Every time you build a SQL string, an attacker smiles—unless you lock it down first.

Другие Услуги

Готовы к безопасности?

Связаться с нами