NTLM Challenge Message Decoder

On premise Exchange, OWA, Skype, and AD FS servers leak information through NTLM challenge messages, including the NetBIOS and DNS domains, computer name, and server version. These services, if exposed to the internet, can aid red teams in information gathering for brute force/password spray attacks. This post will describe discovering these services and using the ntlm_challenger tool to pull relevant information.

The Tool

ntlm_challenger can be found at: https://github.com/b17zr/ntlm_challenger

Example

$ python3 ntlm_challenger.py 'https://autodiscover.hackin.club/autodiscover/autodiscover.xml' 

Target (Domain): HACKIN

Version: Server 2012 / Windows 8 (build 9200)

TargetInfo:
        MsvAvNbDomainName: HACKIN
        MsvAvNbComputerName: EXCH01
        MsvAvDnsDomainName: hackin.club
        MsvAvDnsComputerName: EXCH01.hackin.club
        MsvAvDnsTreeName: hackin.club
        MsvAvTimestamp: Nov 3, 2019 01:07:16.573170

Negotiate Flags:
        NTLMSSP_NEGOTIATE_UNICODE
        NTLMSSP_REQUEST_TARGET
        NTLMSSP_NEGOTIATE_ALWAYS_SIGN
        NTLMSSP_TARGET_TYPE_DOMAIN
        NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
        NTLMSSP_NEGOTIATE_TARGET_INFO
        NTLMSSP_NEGOTIATE_VERSION

Affected Services

Exposed Autodiscover, ActiveSync, OWA, Skype, and AD FS services can leak information through the challenge message. These HTTP services may accept NTLM authentication, so it’s possible to pull information from the NTLM challenge response.

Discovering Hosts

These services are usually on predictable subdomains. Below are some commonly used subdomain names for targeting certain services.

Exchange servers commonly use the following subdomains:

autodiscover
mail
webmail
owa

TrustedSec released a blog post titled Attacking Self-Hosted Skype for Business/Microsoft Lync Installations (by @nyxgeek) which describes a number of subdomains exposed when running Skype for Business:

lyncdiscover
lyncdiscoverinternal
dialin
scheduler
meet
lync-fe

Douglas Bienstock and Austin Baker listed a few subdomains for AD FS endpoints in their “I am AD FS and so can you” talk at Troopers 19 [slides | presentation] (I’d also include federation):

adfs
sts
fs

Discovering Paths

@nyxgeek recently released an automated tool to brute force common NTLM-protected paths given a subdomain: https://github.com/nyxgeek/ntlmscan. The tool provides a solid list for discovery, but may create unwanted noise during an engagement.

It’s possible to send targeted requests if you can identify the service being hosted. I often find Exchange and OWA services listening externally. The TrustedSec blog lists common endpoints for Lync and Skype services. The following table was taken directly from Nate Power’s talk “Hacking Corporate Em@il Systems” [slides | presentation]. The IIS Paths columns lists endpoints used by Autodiscover, ActiveSync, and OWA services.

ServiceVuln HeaderIIS Paths
Autodiscover401 Basic Auth/Autodiscover
/Autodiscover/Autodiscover.xml
ActiveSync401 Basic Auth/Microsoft-Server-ActiveSync
/Microsoft-Server-ActiveSync/default.eas
OWA302 Location
401 Basic Auth
/ECP
/EWS
/EWS/Exchange.asmx
/Exchange
/OWA

AD FS servers configured with Windows Integrated Authentication (WIA) will accept NTLM authentication at the following endpoint:

/adfs/ls/wia

NTLM Over HTTP Protocol

Brief Overview

The NTLM protocol is a three-way handshake used to authenticate a client to a server. In an Active Directory environment, this is commonly used by Windows machines when Kerberos is not an option (as is the case in external services or standalone systems). The steps taken during an authentication attempt are below:

  1. Client sends a NEGOTIATE_MESSAGE
  2. Server sends a CHALLENGE_MESSAGE containing a nonce to prevent replay attacks
  3. Client does some fancy crypto with the nonce and their password hash, then sends the AUTHENTICATE_MESSAGE

After this handshake, the server will verify that the fancy crypto was done with the client’s password hash and the client will be authenticated.

The Challenge Message

The challenge message sent by the server contains all of the information we’re interested in. Specifically, hostname, domain, and OS version information. The message can be requested pre-authentication.

We can coax the server into coughing up this message by sending a negotiate message, shown below:

GET /autodiscover/autodiscover.xml HTTP/1.1
Host: autodiscover.hackin.club
Authorization: NTLM TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=

The server response will look similar to the example:

HTTP/1.1 401 Unauthorized
Server: Microsoft-IIS/8.0
request-id: 5def1285-5b0e-ae5b-8a42-fa90218b0fb9
Set-Cookie: ClientId=CEBKNAAONVXLCNBEQ; expires=Fri, 16-Oct-2020 01:13:09 GMT; path=/; HttpOnly
WWW-Authenticate: NTLM TlRMTVNTUAACAAAADAAMADgAAAAFgokCiw/BR0FOzkIAAAAAAAAAAIwAjABEAAAABgLwIwAAAA9IAEEAQwBLAEkATgACAAwASABBAEMASwBJAE4AAQAMAEUAWABDAEgAMAAxAAQAFgBoAGEAYwBrAGkAbgAuAGMAbAB1AGIAAwAkAEUAWABDAEgAMAAxAC4AaABhAGMAawBpAG4ALgBjAGwAdQBiAAUAFgBoAGEAYwBrAGkAbgAuAGMAbAB1AGIABwAIADL+6wmIhNUBAAAAAA==
WWW-Authenticate: Negotiate
WWW-Authenticate: Basic realm="autodiscover.hackin.club"
X-Powered-By: ASP.NET
X-FEServer: EXCH01
Date: Thu, 17 Oct 2019 01:13:09 GMT
Content-Length: 0

The WWW-Authenticate header will contain the base64-encoded challenge message. Decoding this, we see the following:

$ echo -n 'TlRMTVNTUAACAAAADAAMADgAAAAFgokCiw/BR0FOzkIAAAAAAAAAAIwAjABEAAAABgLwIwAAAA9IAEEAQwBLAEkATgACAAwASABBAEMASwBJAE4AAQAMAEUAWABDAEgAMAAxAAQAFgBoAGEAYwBrAGkAbgAuAGMAbAB1AGIAAwAkAEUAWABDAEgAMAAxAC4AaABhAGMAawBpAG4ALgBjAGwAdQBiAAUAFgBoAGEAYwBrAGkAbgAuAGMAbAB1AGIABwAIADL+6wmIhNUBAAAAAA==' | base64 -d | xxd

00000000: 4e54 4c4d 5353 5000 0200 0000 0c00 0c00  NTLMSSP.........
00000010: 3800 0000 0582 8902 8b0f c147 414e ce42  8..........GAN.B
00000020: 0000 0000 0000 0000 8c00 8c00 4400 0000  ............D...
00000030: 0602 f023 0000 000f 4800 4100 4300 4b00  ...#....H.A.C.K.
00000040: 4900 4e00 0200 0c00 4800 4100 4300 4b00  I.N.....H.A.C.K.
00000050: 4900 4e00 0100 0c00 4500 5800 4300 4800  I.N.....E.X.C.H.
00000060: 3000 3100 0400 1600 6800 6100 6300 6b00  0.1.....h.a.c.k.
00000070: 6900 6e00 2e00 6300 6c00 7500 6200 0300  i.n...c.l.u.b...
00000080: 2400 4500 5800 4300 4800 3000 3100 2e00  $.E.X.C.H.0.1...
00000090: 6800 6100 6300 6b00 6900 6e00 2e00 6300  h.a.c.k.i.n...c.
000000a0: 6c00 7500 6200 0500 1600 6800 6100 6300  l.u.b.....h.a.c.
000000b0: 6b00 6900 6e00 2e00 6300 6c00 7500 6200  k.i.n...c.l.u.b.
000000c0: 0700 0800 32fe eb09 8884 d501 0000 0000  ....2...........

Interpreting the message

Information in the above message can stick out without parsing (ex: hostname EXCH01 and domain hackin.club). The ntlm_challenger tool can decode the full domain, version, target, and flag information from this message without making a request. The following Python script will do just that:

from ntlm_challenger import *

challenge_message = base64.b64decode('TlRMTVNTUAACAAAADAAMADgAAAAFgokCiw/BR0FOzkIAAAAAAAAAAIwAjABEAAAABgLwIwAAAA9IAEEAQwBLAEkATgACAAwASABBAEMASwBJAE4AAQAMAEUAWABDAEgAMAAxAAQAFgBoAGEAYwBrAGkAbgAuAGMAbAB1AGIAAwAkAEUAWABDAEgAMAAxAC4AaABhAGMAawBpAG4ALgBjAGwAdQBiAAUAFgBoAGEAYwBrAGkAbgAuAGMAbAB1AGIABwAIADL+6wmIhNUBAAAAAA==')
challenge = parse_challenge(challenge_message)
print_challenge(challenge)
Target (Domain): HACKIN

Version: Server 2012 / Windows 8 (build 9200)

TargetInfo:
  MsvAvNbDomainName: HACKIN
  MsvAvNbComputerName: EXCH01
  MsvAvDnsDomainName: hackin.club
  MsvAvDnsComputerName: EXCH01.hackin.club
  MsvAvDnsTreeName: hackin.club
  MsvAvTimestamp: Oct 17, 2019 01:13:09.417733

Negotiate Flags:
  NTLMSSP_NEGOTIATE_UNICODE
  NTLMSSP_REQUEST_TARGET
  NTLMSSP_NEGOTIATE_ALWAYS_SIGN
  NTLMSSP_TARGET_TYPE_DOMAIN
  NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
  NTLMSSP_NEGOTIATE_TARGET_INFO
  NTLMSSP_NEGOTIATE_VERSION

For more information, the NT LAN Manager (NTLM) Authentication Protocol Specification lives at https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4.

Prior Art

NTLM Challenge Decoder Burp plugin: https://github.com/PortSwigger/ntlm-challenge-decoder

nmap’s http-ntlm-info script: https://nmap.org/nsedoc/scripts/http-ntlm-info.html

Download Custom Slack Emojis

This post will demonstrate how to use Slack’s emoji.list API method to extract and export custom emojis from Slack using the web client. This API is called by the web client to fetch all emojis when initializing a workspace, and can be called with a user’s xoxc token.

tl;dr

Instructions for downloading the emojis can be found in the Gist at:

https://gist.github.com/b17zr/6a6abf04f52464eec71603b747ffc49a

Details

API Call

When Slack first loads a channel, it fetches custom emojis for the workspace using the emoji.list API. These values do not appear to be stored in a JavaScript variable (although at one point they were…), but it is possible to re-call the API.

The documentation shows that a “token” argument is required for authentication. During normal use, the web client sends the xoxc token, which is saved in localStorage. The following JavaScript will extract this token:

JSON.parse(localStorage.localConfig_v2).teams[slackDebug.activeTeamId].token

The token will follow the format xoxc-11111111111-121212121212-121212121212-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.

The API can be called using the multipart/form-data Content-Type. The following JavaScript will make the API request and dump the response to the console:

// get auth token for API request
var authToken = JSON.parse(localStorage.localConfig_v2).teams[slackDebug.activeTeamId].token;

// setup request
var formData = new FormData();
formData.append('token', authToken);

// make request
(async () => {
  const rawResponse = await fetch('/api/emoji.list', {
    method: 'POST',
    body: formData
  });

  const emojisApi = await rawResponse.json();
  
  // dump to console
  console.log(emojisApi.emoji);
})();

Saving the Data

Due to the restricted (lack of) Access-Control-Allow-Origin header on emoji.slack-edge.com, it is not possible to download the emojis using JavaScript straight from a workspace (located on app.slack.com). Attempting to do so will result in a Cross-Origin Resource Sharing (CORS) error.

Therefore, the download.js script of the Gist saves the results of the API call to a CSV. The CSV will have two columns: one for the image name and the other for the URL. Paste this script into the browser’s console, accept the popup warning, and save the file.

Downloading the Emojis

Since custom emojis do not require authentication, it is possible to download them from outside the browser.

On Linux and Mac, the wget utility can be leveraged to easily download these files. Ensure the terminal is open in the same directory as the emojis.csv file, and run the following script (from download.sh):

for emoji_line in $(cat emojis.csv); do
  name=$(echo "$emoji_line" | cut -d, -f1)
  url=$(echo "$emoji_line" | cut -d, -f2)

  wget -O "$name" "$url"
done

On Windows, PowerShell can be used to download all emojis in the CSV. Run the following commands from the same folder as the emojis.csv file (from download.ps1, requires PowerShell 3):

$emojis = Import-Csv -Path .\emojis.csv -Header 'Name','Url'

foreach ($emoji in $emojis) {
  Invoke-WebRequest -Uri $emoji.Url -OutFile $emoji.Name
}

After running the scripts, all custom emojis from the Slack workspace should be present in the current directory/folder.