a new perspective in empty session and user enumeration

Posted by punzalan at 2020-03-09

A new look at null sessions and user enumeration Hello, TLDR; I think I've found three new ways to enumerate users on a Windows domain controller, and I've also written some scripts for it. Over the years, I have frequently used null session vulnerabilities to enumerate lists of users, groups, shares, and other interesting information in remote Windows systems.

For laymen, windows exposes several managed and hidden shares through SMB by default.

Some of these shares allow users to access the complete storage device on the remote system. For example, C $will allow users to access the C drive. Another share, admin $, allows access to the windows installation directory. However, to install these shares, you need to be an administrator on a remote system.

IPC $is a special part of promoting interprocess communication (IPC). That is, it does not allow access to files or directories like other shares, but rather allows people to communicate with processes running on remote systems. Specifically, IPC $exposes named pipes that can be written or read to communicate with remote processes. These named pipes are created when the application opens the pipe and registers it with the Windows Server Service (SMB), so that it can be exposed through the IPC $share. Any data written to this named pipeline will be sent to the remote process. On the contrary, any output data written by the remote process can be read from the pipeline by the local application. People can use this named pipeline to perform specific functions, usually called remote procedure calls (RPCs) on remote systems.

Some versions of windows allow users to verify and install the IPC $share without providing a user name or password. This connection is often referred to as a null session. Although it has limited permissions, it can be used to perform various RPC calls, and thus obtain useful information about the remote system. It can be said that the most useful information that can be extracted in this way is the list of users and groups, which can be used for violent attacks.

Null sessions are very old. I remember learning it at hacking for dummies in 2004, when it was already well known. After applications like Cain & able and others allow users to take advantage of it, Microsoft has limited it. Starting with Windows XP, you can disable a null session or disable it by default. There are many instructions on how to disable null sessions, and some confusing suggestions from Microsoft are about what settings have been made.

An increasingly popular application for testing null sessions is RPC client, which is used by other tools such as enum4linux and ridenum. The other is the SMB enum user NSE script of nmap. In some tests, I found that when I use RPCC client to deal with known vulnerable systems, error messages are often generated and user information cannot be enumerated. In a penetration test scenario, this behavior may lead to the belief that remote systems are not allowed to access the IPC $share and perform RPC calls, which may be possible. In these tests, I ran the SMB enum users NSE script of rpcclient and nmap against the same vulnerable system and looked at the output.

Next, the output display of the SMB enum users script can enumerate user information:

At the bottom, the SMB enum users script performs a call to querydisplayinfo RPC to enumerate user information. However, when I use rpcclient to make a call to querydisplayinfo RPC, it cannot enumerate user information and produce the following output:

To understand why this happens, let's take a look at the Wireshark trace for each connection. Starting from capturing SMB enum users, this user can enumerate the list of users on the system:

The SMB enum users script goes through different stages, as highlighted by different boxes. It first establishes an anonymous session with the SMB server and then accesses the IPC $share. Then it opens the samr named pipeline and runs several RPC calls, including connect4, enumdomains, lookupdomain, opendomain, and querydisplayinfo, each of which completes successfully. The first few RPC calls extract information about the system's local domain, which is used by querydisplayinfo to generate a list of all users in the domain. Now let's look at the rpcclient connection:

In this capture, we can see that rpcclient has gone through four stages, and finally reached the error state. The first phase, as shown in the first box, indicates that rpcclient can connect to and verify the server with an empty user name and password. The second stage shows that rpcclient can access IPC $share, and the third stage shows that it can open lsarpc named pipeline. Only in the fourth phase, when rpcclient attempts to make an lsaonpolicy RPC call, will the remote server respond to an access denied error message. Highlighting this behavior is the default behavior of rpcclient and it is important to run it before executing any of the supplied RPC commands, such as querydisplayinfo.

By looking at these network traces, we can see that the authentication and authorization steps of the connection are performed separately. That is, we first authenticate to the SMB server with a blank username and password. After this validation is successful, authorization is performed in three different locations: when we try to open the IPC $share, when we try to open the pipeline, and finally when we try to make RPC calls available through the pipeline. As we can see in lsaonpolicy, the system may allow most of these operations, but prevent RPC function calls from finally executing. However, this does not mean that the system will block null session authentication. It just means that the system does not allow anonymous users to make specific RPC function calls.

By default, rpcclient first opens the lsarpc pipeline, and then requests the remote system to execute lsaonpolicy and lsaqueryinformationpolicy functions. If anonymous users are not allowed to perform these functions, rpcclient exits. However, most RPC calls do not require the information provided by these functions. For example, in our test, the SMB enum users script could still execute querydisplayinfo and enumerate user information without accessing these functions.

Because of this behavior, when using rpcclient to determine whether a null session is allowed by the system and whether the session can be used to provide useful information, an erroneous negation may be obtained. After playing with rpcclient and Wireshark, I began to wonder which RPC function calls, if any, could be executed on the default Windows system using a null session. With this in mind, I consulted MSDN, which contains details of the various named pipes, as well as the functionality exposed by each named pipe. I also started testing various versions of windows. I paid particular attention to domain controllers, which, as I pointed out in several tests, often allow null sessions to connect and then enumerate all users in the domain.

Tests show that not only can the default Windows 2012 domain controller be authenticated without providing credentials, but also can open IPC $shares and multiple different pipes. In particular, as long as the windows 2012 system is upgraded to a domain controller, its security configuration will be modified to allow anonymous access to samr, lsarpc, and Netlogon pipes. In the default configuration, most of the functions in the samr and lsarpc pipelines cannot be performed.

However, some functions in the Netlogon pipeline can be performed anonymously, including getdcname, dsrgetdcname, dsrgetdcnameex and dsrgetdcnameex2. These functions allow the user to request the remote system to find a domain controller for any specified domain name. Note that the domain does not need to be the same as the domain joined by the remote system.

You can see that the dsrgetdcnameex2 function allows you to request a remote system to find a domain controller to find a specified domain name that contains a specific user. If a remote system can locate a domain controller for such a domain, and the specified user exists, the remote system returns a successful response with details about the domain controller. If the remote system can find a domain controller for the specified domain, but the user does not exist, the remote system will return an error ﹣ no ﹣ attach ﹣ user error message.

The dsrgetdcnameex2 function needs to specify an allowableaccountcontrolbits field. The field is 32-bit, six of which represent a specific property that a user account should have. In most cases, we are interested in the general domain account chosen by number 10. This becomes 0000000000000000000000000000001000000000, or simply 1000000000, 512 when converted to decimal. With this information, you can use rpcclient to execute the dsrgetdcnameex2 function. If the supplied user name exists in the domain, the response is as follows:

If someone provides a user name that does not exist, rpcclient returns the following error message:

So you can determine if a user exists on the remote domain. Although this type of user enumeration needs to provide a user name (that is, Oracle) and can only verify the existence of the user name, it is still useful in several cases.

For example, if you have established a naming convention for a specific domain, you can generate all possible variations and check for variations that have already been created. For example, if a user has a user name of M1000, you can generate all users between M1000 and m9999 and check if there are users in the domain.

Alternatively, you can use a list of common domain name users that typically bypass password complexity rules. We keep an internal account, but if you withdraw an account from several gal or hash accounts of several DCS, you will be able to create your own account. Considering the rpcclient problem mentioned earlier, I decided that it might be a better idea to write a custom application that can call dsrgetdcnameex2 on a remote system.

In this regard, I used the impacket framework, which can call various RPC functions through SMB. The application accepts the list of potential user names and attempts to determine if each user exists by calling dsrgetdcnameex2 on a remote system. You can get it here. I've tested it in the field, and although it's reported to run on all domain controllers, it's going to take a while to complete the enumeration. Tests also show that it appears to work only on domain controllers.

To speed up enumeration, I investigated how dsrgetdcnameex2 works. The getdcname, dsrgetdcname, dsrgetdcnameex, and dsrgetdcnameex2 functions can locate domain controllers for any provided domain name. To do this, they can use DNS and LDAP or NetBIOS.

But in short, it works as follows:

1. The system obtains the IP address of the domain controller related to the provided domain name by using DNS lookup or by sending NetBIOS broadcast requests for the domain name.

2. If the system uses DNS to obtain the IP address, it will send an LDAP Ping packet to the address, and if it obtains the address through NetBIOS, it will send a mailslot Ping packet to it. Both LDAP and NetBIOS packets are UDP based and contain a list of requirements that a domain controller should have.

3. The domain controller checks whether it has the required properties and responds to the system's request.

4. The system processes the response sent by the remote system to the LDAP Ping or mailslot Ping packets.

The interesting thing about these methods is that they don't use any designed authentication. Since the implementation of dsrgetdcnameex2 can use any of these methods, namely DNS and LDAP or NetBIOS, we can assume that we can use both methods to enumerate users.

Let's take a look at DNS and LDAP methods first. Since we don't really want to use this method to find domain controllers, we'll skip the DNS section and focus only on LDAP packets.

Specifically, this method sends LDAP search queries to remote systems. This is often referred to as a cldap packet, or a connectionless LDAP packet, because it uses UDP instead of TCP. In addition, the structure of cldap packets is almost the same as that of ordinary LDAP search packets, because they all use ASN. 1.

As shown in the figure above, the cldap packet requests the LDAP server to search for objects matching various filters. These include:

Domain name provided (black. Com)

Requested user name (administrator)

The name of the host requesting the search in NetBIOS and DNS (DC01. Labs. Com)

Guid of the searched domain, which can be left blank.

The request also specifies that only the Netlogon property of the object is returned. In the above case, user administrator exists in the domain, so the server will reply using the following structure:

When sending the same packet, for the non-existent users, we get a 25 operation code and logo Sam user unknown ex structure, as shown in the following user name John:

Therefore, we can send such a packet to a remote domain controller and determine whether the user exists by checking whether it responds with an opcode of 23 or 25.

To do this, we need to make a cldap packet. The samba source code contains a sample Perl script that creates cldap packets.

Based on this example, I created a python script that will get a list of user names, create a cldap packet for each user, send it to the remote domain controller, and determine whether the user exists according to the response. Specifically, I used the ASN1 tools Python module to create the data package, which is completely composed of the ANS.1 structure. The server only needs dnsdomain, ntver, user and AAC fields to respond. You can be there.

I tested this script on a remote domain controller and it ran quite fast, checking about 3500 user names in 40 seconds. When executing the script, I wonder if I can change the LDAP search filter to use wildcards in the search. This will allow users to enumerate users without fully knowing their user names. If you can search for all user names that match the pattern, you can increase the pattern until you find the exact match. For example, you can start with B *, then Bo *, and finally Bob. In this regard, I changed the script to use a substring filter instead of a match filter. However, this does not work because the server refuses to reply. I will continue to play around with the request, but at this point, it seems that only one known user name exists.

After completing the cldap method, I want to see if I can implement the NetBIOS mail slot ping method, and consult Wireshark again. I connected to the system ( and issued the dsrgetdcnameex2 command, requesting to find a domain controller for the domain name Black ( 20) and a user named administrator. In the following trace, we can see the packets sent directly after executing the dsrgetdcnameex2 command. system first sends a query broadcast of NBNS name, trying to find the domain name black of any domain controller < 1C >. At 148, we saw that the black domain controller responded to the NBNS query broadcast and informed of its IP address. In 150, the DC01 system starts processing the response and issues an ARP request to connect to the black domain controller.

In 149, the system broadcasts UDP SMB ﹣ Netlogon packets, and then uses its IP address in 153 to specifically send the same packets to the black domain controller.

When the black domain controller processes SMB ﹣ Netlogon packets, it will try to establish the IP address of DC01 through NBNS lookup in 152, and obtain NBNS response in 153.

Finally, in 155, the black domain controller sends its response to the SMB ﹣ Netlogon packet.

From this trace, we can see that we can cause the system that we execute the dsrgetdcnameex2 function and the domain controller that responds to the SMB ﹣ Netlogon request to execute the NBNS lookup request. We also control the names found by the two systems through NBNS, and can respond to these requests with any IP address. This effectively allows a window domain controller to connect to any provided IP address through NetBIOS without providing any authentication. I wonder if I can use a method like responder to get the hash value.

But since all packets generated from dsrgetdcnameex2 are UDP based and no SMB TCP connection is established, it doesn't seem to matter. I'm still playing with responder to see what I can do with this, but at least it seems that I can actively poison the system's cache with NetBIOS name and corresponding IP address in this way. For enumerating users, let's take a look at the SMB 65123; Netlogon package. Although Wireshark classifies this packet as an SMB ﹣ Netlogon, it is actually a mail slot write packet, just like

The structure of data package is very complex, including various fields. In Wireshark, the request sent as 153 looks like this:

Most of the content of the packet is only the preamble, which is working in the direction of Wireshark in the "Microsoft Windows Login Protocol" part, while MSDN calls the NETLOGON_SAM_LOGON_REQUEST structure in the Databytes field. The exception is the requested domain name, in this case black, which is sent at the beginning of the packet. Looking at the Netlogon Sam logon request structure, we can see three fields of interest: Unicode computername, Unicode username and allowableaccountcontrolbits.

The allowableaccountcontrolbits value is the same as before, i.e. 0x00000010 used when constructing cldap packets. The computer name field contains the NetBIOS host name of the system that made the request. As discussed earlier, the domain controller will use the NBNS request to find this value. This is not ideal because it requires the NBNS response packet to be implemented to get the mailbox Ping response. This behavior can be avoided by simply setting the value to an IP address. The MSDN documentation states that the computer name and user name fields should be utf-16 encoded.

Looking at the response provided for this packet, we see an almost identical structure, except for the data bytes field:

Wireshark mistakenly interpreted the response code as "unknown to the user.". As mentioned earlier, MSDN indicates that 0x17 in response code 23 or hex is reserved as "logo? Sam? Logo? Response? Ex". However, response code 25 or 0x19 is reserved for "logo? Sam? User? Unknown? Ex".

When sending a mailslot Ping request with a nonexistent user name, the server did reply with a 25 response code, but Wireshark was unable to interpret the response structure:

In any case, we can send a mailslot Ping packet to determine whether the user exists and check whether the response code is 23 or 25. Making these packets is a bit complicated, so I look at scapy for help. I have to make some changes to the existing scapy structure, and I also have to implement the Netlogon Sam logo request structure. In addition, both the user name and the source host name need to be encoded with utf-16. Another consideration highlighted in the above capture is that the length and size fields change with different user names and host names. MSDN and RFC 1001 and RFC 1002 help explain the calculations required for these fields. You can do this. I can create a script to enumerate users using this method. You can view these scripts here.

Comparing three different methods, I found that cldap technology is the fastest, mailbox method is the second, and dsrgetdcnameex2 is the third. With a remote connection, I can test about 3500 user names in about 40 seconds using cldap technology. Note that the script at this stage is POC only and is not really optimized for speed. The use of multithreading, etc. may result in an increase.

With regard to the logs generated by these technologies, it seems that neither cldap nor the mail slot ping method will generate entries, while the dsrgetdcnameex2 method does generate an anonymous login entry containing the IP address of the system executing the script.

The script you want to provide helps you list domain users. I don't know any existing scripts that use these technologies, but in either way, I learned a lot when implementing them.

Thanks for reading, happy hacking.