Philip Young, Author at NetSPI The Proactive Security Solution Mon, 16 Sep 2024 15:44:34 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.1 https://www.netspi.com/wp-content/uploads/2024/03/favicon.png Philip Young, Author at NetSPI 32 32 Mapping Mainframe Memory Made Easy https://www.netspi.com/blog/technical-blog/mainframe-penetration-testing/mapping-mainframe-memory/ Mon, 16 Sep 2024 15:39:47 +0000 https://www.netspi.com/?p=25510 Explore how NetSPI's own LPAR enhances pentesting efficiency through rapid tool prototyping and deployment.

The post Mapping Mainframe Memory Made Easy appeared first on NetSPI.

]]>
Image of NetSPI testing LPAR

One of the benefits of working for NetSPI is access to our own testing LPAR. This proves handy when we’re mid-engagement and need to quickly create a tool, allowing us to test it before trying it on a live environment. For example, during a recent engagement, I couldn’t run the TSO command PARMLIB or the operator command “DISPLAY IKJTSO”, both of which provide similar information we use when conducting pentests.

Image demonstrating the parmlib command cannot be run by my user

Fortunately, a series of in-memory tables (i.e., IKJEFTE2, IKJEFTE8, IKJEFTAP, and IKJEFTNS) store the information we need. These tables contain the authorized commands, programs, etc., typically defined in the TSO configuration file (IKJTSOxx parmlib member). The TSO/E System Diagnosis: Data Areas manual documents how to locate these tables. 

On z/OS, we refer to these tables as control blocks1. Typically chained and anchored, we can find all control blocks by starting at the base control block (usually the CVT or common vector table) and traversing the pointers and tables until we obtain the desired information. While these tables are sometimes well-documented (as in this case), we sometimes must figure them out on our own.  

If we lack permission to run the tools or commands to dump this information, we can manually retrieve the contents of the in-memory tables. To do this, we need to traverse a series of pointers and offsets in memory:  

  1. We start by looking at the CVT2, which is always located at offset 0x10.  
  2. At offset 0x9C in the CVT we find the address of the TSO vector table (TSVT) 3, labeled CVTTVT 
  3. At offset 0x4C in the TSO vector table (TSVT) is the address of the TSO/E Parameters Vector Table (TPVT), labeled TSVTTPVT 
  4. At offset 0x14 in the TSO/E Parameters Vector Table (TPVT)4 is the address of the TSO/E Command Tables Location Table (CTLT), labeled TPVTCTLT 5 

The Command Tables Location Table (CTLT) contains the address and other information we need to find our target data. 

In essence, we’ve done this:  

Mapping of the control block and their offsets in z/OS memory

Although the contents of the Command Tables Location Table (CTLT) are well documented, I wanted to visualize the table in memory and make it readable. This approach allows me to access this information in future situations, regardless of access to the commands or programs typically used to read it.

Putting together a quick REXX script is my preferred method, though you could just as easily use ISRDDN to do it (which I’ll leave as an exercise to the reader). 

/* REXX */                                
CVT = STORAGE(10,4)                       
TSVT = STORAGE(D2X(C2D(CVT)+X2D(9C)),4)   
TPVT = STORAGE(D2X(C2D(TSVT)+X2D(4C)),4)  
CTLT = STORAGE(D2X(C2D(TPVT)+X2D(14)),4)  
SAY "CVT:" C2X(CTV)                       
SAY "TSVT:" C2X(TSTV)                     
SAY "TPVT:" C2X(TPVT)                     
SAY "CTLT:" C2X(CTLT)                     
SAY "CTLT CONTENTS"                       
CTLTHEX = STORAGE(C2X(CTLT),100)          
OUTPUT = ''                               
DO I=1 TO 60                              
 BYTE = C2X(SUBSTR(CTLTHEX,I,1))          
 OUTPUT = OUTPUT BYTE                     
 IF I // 16 = 0 THEN DO                   
  SAY OUTPUT                              
  OUTPUT = ''                             
 END                                      
IF OUTPUT \= '' THEN SAY OUTPUT

After saving this REXX script to z/OS and executing it, we obtain the following hex dump of the CTLT table: 

Screenshot showing the output of the above REXX script

Let’s analyze the bytes in CTLT CONTENTS and refer to IBM’s documentation in the TSO/E System Diagnosis: Data Areas, under KJCTLT information. (As of this blog post, this information is also available at: https://www.ibm.com/docs/en/zos/3.1.0?topic=information-ikjctlt-mapping).

The first 4 bytes (C3 E3 D3 E3, which is EBCDIC for ‘CTLT’) represent what IBM calls an eye catcher, essentially a road marker indicating you’re in the right spot. The next two bytes show the size of this table (00 3C), which is 60, followed by a version byte (02) and a reserved byte. 

The table becomes particularly interesting after these initial bytes. We find 4 entries, each 12 bytes long, followed by a flag byte and three more reserved bytes. 

Image showing the CTLT memory region with descriptions

Let’s examine these four entries more closely. I’ve separated them out here:

20 37 03 88 00 00 02 A8 00 53 00 08 (AUTHCMD/IKJEFTE2) 
20 37 02 60 00 00 01 28 00 23 00 08 (AUTHPGM/IKJEFTE8) 
20 3B 00 00 00 00 00 42 00 05 00 0A (NOTBKGND/IKJEFTNS) 
20 38 50 58 00 00 00 38 00 05 00 08 (AUTHTSF/IKJEFTAP) 

Referring back to the documentation, we can parse each entry as follows: 

  • Four bytes: memory address 
  • Four bytes: size of the table in bytes 
  • Two bytes: number of entries 
  • Two bytes: size of each entry 

So, for example, AUTHPGM can be broken down as:

AUTHPGM

Now that we understand the structure of each entry in the CTLT, we can create a REXX script to enumerate all four structures:  

/* REXX */  
                                          
SAY 'ENUMERATING CTLT' 

  /*                         */ 
 /* Walk the control blocks */ 
/*                         */           

CVT =  C2X(STORAGE(10,4))                                  
TSVT = _STORAGE(CVT,9C)                                    
TPVT = _STORAGE(TSVT,4C)                                   
CTLT = _STORAGE(TPVT,14)                                   
SAY CVT ": CVT" TSVT ": TSVT" TPVT ": TPVT" CTLT ": CTLT"   

/* Get the CTLT size */ 
CTLT_SIZE = C2D(STORAGE(D2X(X2D(CTLT)+4),2))                

  /*                         */ 
 /* Loop through each entry */ 
/*                         */ 

DO I = 8 TO CTLT_SIZE - 5 BY 12                            
 TABLE   = _STORAGE(CTLT,D2X(I))                           
 SIZE    = C2D(STORAGE(D2X(X2D(CTLT)+I+6),2))              
 ENTRIES = C2D(STORAGE(D2X(X2D(CTLT)+I+8),2))              
 LENGTH  = C2D(STORAGE(D2X(X2D(CTLT)+I+10),2))             
 NAME = STORAGE(TABLE,8)                                   
 SAY ;SAY NAME "ENTRIES:" ENTRIES-1; SAY                     
 OUTPUT = ''      

    /*                                         */ 
   /* Print the entries in the table skipping */ 
  /*  over ' PARMLIB'                        */ 
 /*                                         */ 

 DO J = 2 TO ENTRIES                                       
  ENT = STORAGE(D2X(X2D(TABLE) + (LENGTH*J)),LENGTH)  
  OUTPUT = OUTPUT ENT                                
  IF LENGTH(OUTPUT) > 60 THEN DO                     
   SAY OUTPUT                                        
   OUTPUT = ''                                       
  END                                                
 END                                                 
 IF OUTPUT /= '' THEN SAY OUTPUT                     
END                                                  
RETURN                                                                                                   

_STORAGE:                                            
 PARSE ARG ADDR, DISP                                
 RETURN C2X(STORAGE(D2X(X2D(ADDR)+X2D(DISP)),4))

And after uploading this script to z/OS and executing it, we get: 

Screenshot showing the output from the updated REXX script
Screenshot showing the output from the updated REXX script

Success! We can now easily view the contents of the tables we needed. While there’s room for further refinement, particularly in handling the length of the IKJEFTNS entries, the script provides us with the necessary information to overcome the obstacle we faced. 

This work proved its value during a recent engagement. Using this script, we uncovered a privilege escalation path that might have gone undetected otherwise. 

To streamline future pentests, I’ve integrated this functionality as option “TSOT” in my z/OS enumeration REXX script, available here:  

This REXX script is just one example of how we at NetSPI continuously innovate to enhance our penetration testing capabilities. By developing new tools and techniques, we’re able to provide more comprehensive and effective security assessments, particularly in complex mainframe environments. 

Are you looking to bolster your mainframe security? Our expert team is ready to apply these advanced techniques and more to your systems. Click here to learn about our mainframe penetration testing services and schedule a consultation. Let’s work together to secure your critical mainframe infrastructure. 

The post Mapping Mainframe Memory Made Easy appeared first on NetSPI.

]]>
Enumerating Users on z/OS with LISTUSER https://www.netspi.com/blog/technical-blog/mainframe-penetration-testing/enumerating-users-on-z-os-with-listuser/ Thu, 12 Oct 2023 14:00:00 +0000 https://www.netspi.com/enumerating-users-on-z-os-with-listuser/ Enhance mainframe security by learning about the risks of allowing all users the ability to run the LISTUSER command against any user.

The post Enumerating Users on z/OS with LISTUSER appeared first on NetSPI.

]]>
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.

The post Enumerating Users on z/OS with LISTUSER appeared first on NetSPI.

]]>