Philip Young joined NetSPI in 2023 as the Director of Mainframe Penetration Testing to help enterprises better understand their mainframe cybersecurity threat landscape. Prior to joining NetSPI, Philip spent the past 15 years building mainframe penetration testing programs at Fortune 500 companies such as Visa Inc. and Wells Fargo Inc, covering z/OS, z/TPF, RACF, TSO, VTAM, CICS, TopSecret, Web apps and cloud. He has numerous certificates including but not limited to Security+, CISSP and OSCP. Philip has presented on mainframe cybersecurity around the globe at conferences such as BlackHat, SEC-T, GSE, RSA, Hactivity, DEFCON, and SHARE. He also co-created and taught the world’s only mainframe penetration testing class. He has contributed to multiple opensource tools such as Nmap and Metasploit to add support for z/OS, and has developed many opensource mainframe penetration testing tools such as CATMAP, APFCHECK, OMVSEnum, NJELib, XMILib and TN3270lib for Nmap, as well as brute force tools for TSO and CICS.
Mainframes are ever being included in Red Team Engagements to demonstrate impact. If an adversary can access your mainframe environment they could cause material damage to customer data, cause an outage or potentially steal money. However, when an adversary gets on a mainframe, the account they have may not provide enough access to do anything. NetSPI has performed multiple Mainframe Penetration Tests where the base account was locked down enough to prevent them from doing any real damage.
In a vacuum, that’s fine. But adversaries typically don’t operate in a vacuum and will leverage whatever access they have to further develop their target list. In this blog post we’ll outline the risks of allowing all users the ability to run the LISTUSER command against any user.
The LISTUSER command is a RACF command that you use in TSO that allows you to list information about accounts on the system. TSO is the terminal shell for mainframes, similar to something you’d find on a Linux server, but the prompt is “READY” instead of $. Issuing the LISTUSER command without any arguments outputs information about the currently logged in user.
If you have access to list other user information, you can issue the LISTUSER command with the argument for any user you want to see. For example running the “LISTUSER NETSPI” would output information about that specific user.
Allowing anyone to just list anyone else’s username and group membership is dangerous. In fact, once you’ve confirmed you can access other user profiles through the LISTUSER command you can issue the command LISTUSER * to list all users in RACF! From an adversarial perspective this makes enumerating users and conducted password sprays or targeted attacks very easy.
Although, that’s not 100% true. For a typical user, if you’re allowed to list other users it will return user information, but if the user profile you tried to list is an administrative user (i.e. that user has system SPECIAL in RACF) you get an error message that your access is denied. Once we know this, enumerating administrative accounts is trivial.
Typically, mainframe shops will have username conventions. For this example (and the sake of time) we’ll assume a z/OS userid starts with a letter followed by five numbers. With the ability to list user IDs we can use some REXX to identify every account on the system and all the privileged accounts:
/* REXX */
parse arg len
SAY "LISTUSER RACF User Enumeration Tool"
SAY "VER 1"
/* License: GPLv3 */
ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
NUM = '0123456789'
DO I=1 to 26
S1 = RIGHT(LEFT(ALPHA,I),1)
DO J=1 to 10
S2 = RIGHT(LEFT(NUM,J),1)
DO K=1 to 10
S3 = RIGHT(LEFT(NUM,K),1)
DO L=1 to 10
S4 = RIGHT(LEFT(NUM,L),1)
DO M=1 to 10
S5 = RIGHT(LEFT(NUM,M),1)
DO N=1 to 10
S6 = RIGHT(LEFT(NUM,N),1)
L = OUTTRAP('LUO.')
LU S1||S2||S3||S4||S5||S6
L = OUTTRAP('OFF')
if POS('USER=',LUO.1) > 0 THEN DO
PARSE VAR LUO.1 USER .
PARSE VAR LUO.3 ATTR .
SAY USER ATTR
END
END
END
END
END
END
After this script completes, I have a list of five users who are privileged accounts — now I know to target those users either in a phishing campaign or stealing their credentials using other means.
We know now that allowing access to LISTUSER is unsafe. Fortunately IBM explains how to limit who should be able to run the LISTUSER command in its z/OS Security Server RACF Security Administrator's Guide. In summary, to prevent enumeration quickly you can limit access to the IRR.LISTUSER profile in the FACILITY class.
This, however, only prevents an attacker from listing user details. Due to a quirk in the way IBM implemented the LISTUSER command, we can still enumerate all the accounts on the system. If you enter the LISTUSER command and don’t have access to perform that function RACF returns an access denied error message. Conversely, if we enter a user that doesn’t exist, we get the message “that user doesn’t exist.”
Therefore, using the same REXX code as above, we can still enumerate every user on the system; we just can’t get details about that user. A few changes to the code left up to the reader would still allow you to enumerate all users. Additionally, for a sophisticated attacker it wouldn’t be hard to link mainframe IDs to domain user IDs and hone their target selection for either spear phishing or password spray attacks.
If you were hoping to be able to catch this type of attack in SMF, that is currently not possible. According to IBM, you cannot detect this type of attack even if you’ve sufficiently locked down who can issue the LISTUSER command. From IBM: “RACF does not log failed access attempts to IRR.LU resources. Successful accesses to IRR.LU resources are logged at the installation's discretion.”
Despite these limitation NetSPI strongly recommends you limit who is able to run the LISTUSER command against other users, limiting the information available to attackers make it much harder to profile the system and other users. NetSPI offers multiple different types of Mainframe Penetration Tests to help you better understand the threats and mitigations to your enterprise mainframes, identifying the misconfiguration is just one item we look for when conducting penetration tests or red team activities.
[post_title] => Enumerating Users on z/OS with LISTUSER
[post_excerpt] => Enhance mainframe security by learning about the risks of allowing all users the ability to run the LISTUSER command against any user.
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => enumerating-users-on-z-os-with-listuser
[to_ping] =>
[pinged] =>
[post_modified] => 2023-10-11 14:32:47
[post_modified_gmt] => 2023-10-11 19:32:47
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://www.netspi.com/?p=31202
[menu_order] => 16
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
)
[post_count] => 1
[current_post] => -1
[before_loop] => 1
[in_the_loop] =>
[post] => WP_Post Object
(
[ID] => 31202
[post_author] => 161
[post_date] => 2023-10-12 09:00:00
[post_date_gmt] => 2023-10-12 14:00:00
[post_content] =>
Mainframes are ever being included in Red Team Engagements to demonstrate impact. If an adversary can access your mainframe environment they could cause material damage to customer data, cause an outage or potentially steal money. However, when an adversary gets on a mainframe, the account they have may not provide enough access to do anything. NetSPI has performed multiple Mainframe Penetration Tests where the base account was locked down enough to prevent them from doing any real damage.
In a vacuum, that’s fine. But adversaries typically don’t operate in a vacuum and will leverage whatever access they have to further develop their target list. In this blog post we’ll outline the risks of allowing all users the ability to run the LISTUSER command against any user.
The LISTUSER command is a RACF command that you use in TSO that allows you to list information about accounts on the system. TSO is the terminal shell for mainframes, similar to something you’d find on a Linux server, but the prompt is “READY” instead of $. Issuing the LISTUSER command without any arguments outputs information about the currently logged in user.
If you have access to list other user information, you can issue the LISTUSER command with the argument for any user you want to see. For example running the “LISTUSER NETSPI” would output information about that specific user.
Allowing anyone to just list anyone else’s username and group membership is dangerous. In fact, once you’ve confirmed you can access other user profiles through the LISTUSER command you can issue the command LISTUSER * to list all users in RACF! From an adversarial perspective this makes enumerating users and conducted password sprays or targeted attacks very easy.
Although, that’s not 100% true. For a typical user, if you’re allowed to list other users it will return user information, but if the user profile you tried to list is an administrative user (i.e. that user has system SPECIAL in RACF) you get an error message that your access is denied. Once we know this, enumerating administrative accounts is trivial.
Typically, mainframe shops will have username conventions. For this example (and the sake of time) we’ll assume a z/OS userid starts with a letter followed by five numbers. With the ability to list user IDs we can use some REXX to identify every account on the system and all the privileged accounts:
/* REXX */
parse arg len
SAY "LISTUSER RACF User Enumeration Tool"
SAY "VER 1"
/* License: GPLv3 */
ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
NUM = '0123456789'
DO I=1 to 26
S1 = RIGHT(LEFT(ALPHA,I),1)
DO J=1 to 10
S2 = RIGHT(LEFT(NUM,J),1)
DO K=1 to 10
S3 = RIGHT(LEFT(NUM,K),1)
DO L=1 to 10
S4 = RIGHT(LEFT(NUM,L),1)
DO M=1 to 10
S5 = RIGHT(LEFT(NUM,M),1)
DO N=1 to 10
S6 = RIGHT(LEFT(NUM,N),1)
L = OUTTRAP('LUO.')
LU S1||S2||S3||S4||S5||S6
L = OUTTRAP('OFF')
if POS('USER=',LUO.1) > 0 THEN DO
PARSE VAR LUO.1 USER .
PARSE VAR LUO.3 ATTR .
SAY USER ATTR
END
END
END
END
END
END
After this script completes, I have a list of five users who are privileged accounts — now I know to target those users either in a phishing campaign or stealing their credentials using other means.
We know now that allowing access to LISTUSER is unsafe. Fortunately IBM explains how to limit who should be able to run the LISTUSER command in its z/OS Security Server RACF Security Administrator's Guide. In summary, to prevent enumeration quickly you can limit access to the IRR.LISTUSER profile in the FACILITY class.
This, however, only prevents an attacker from listing user details. Due to a quirk in the way IBM implemented the LISTUSER command, we can still enumerate all the accounts on the system. If you enter the LISTUSER command and don’t have access to perform that function RACF returns an access denied error message. Conversely, if we enter a user that doesn’t exist, we get the message “that user doesn’t exist.”
Therefore, using the same REXX code as above, we can still enumerate every user on the system; we just can’t get details about that user. A few changes to the code left up to the reader would still allow you to enumerate all users. Additionally, for a sophisticated attacker it wouldn’t be hard to link mainframe IDs to domain user IDs and hone their target selection for either spear phishing or password spray attacks.
If you were hoping to be able to catch this type of attack in SMF, that is currently not possible. According to IBM, you cannot detect this type of attack even if you’ve sufficiently locked down who can issue the LISTUSER command. From IBM: “RACF does not log failed access attempts to IRR.LU resources. Successful accesses to IRR.LU resources are logged at the installation's discretion.”
Despite these limitation NetSPI strongly recommends you limit who is able to run the LISTUSER command against other users, limiting the information available to attackers make it much harder to profile the system and other users. NetSPI offers multiple different types of Mainframe Penetration Tests to help you better understand the threats and mitigations to your enterprise mainframes, identifying the misconfiguration is just one item we look for when conducting penetration tests or red team activities.
Necessary cookies help make a website usable by enabling basic functions like page navigation and access to secure areas of the website. The website cannot function properly without these cookies.
Name
Domain
Purpose
Expiry
Type
YSC
youtube.com
YouTube session cookie.
52 years
HTTP
Marketing cookies are used to track visitors across websites. The intention is to display ads that are relevant and engaging for the individual user and thereby more valuable for publishers and third party advertisers.
Name
Domain
Purpose
Expiry
Type
VISITOR_INFO1_LIVE
youtube.com
YouTube cookie.
6 months
HTTP
Analytics cookies help website owners to understand how visitors interact with websites by collecting and reporting information anonymously.
We do not use cookies of this type.
Preference cookies enable a website to remember information that changes the way the website behaves or looks, like your preferred language or the region that you are in.
We do not use cookies of this type.
Unclassified cookies are cookies that we are in the process of classifying, together with the providers of individual cookies.
We do not use cookies of this type.
Cookies are small text files that can be used by websites to make a user's experience more efficient. The law states that we can store cookies on your device if they are strictly necessary for the operation of this site. For all other types of cookies we need your permission. This site uses different types of cookies. Some cookies are placed by third party services that appear on our pages.
Cookie Settings
Discover why security operations teams choose NetSPI.