This commit is contained in:
trietend 2025-06-20 19:57:50 +02:00 committed by GitHub
commit b002b623ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 155 additions and 31 deletions

View file

@ -28,10 +28,21 @@ MQTT = On
; Use "Random" for generating a random challenge for each requests (Default) ; Use "Random" for generating a random challenge for each requests (Default)
Challenge = Random Challenge = Random
; Database Management System
; Use "sqlite" or "psql"
Dbms = sqlite
; SQLite Database file ; SQLite Database file
; Delete this file to re-capture previously captured hashes ; Delete this file to re-capture previously captured hashes
Database = Responder.db Database = Responder.db
; Psql Database
PsqlHost = 127.0.0.1
PsqlPort = 5432
PsqlUser= dbuser
PsqlPassword = dbpass
PsqlDatabase = responder
; Default log file ; Default log file
SessionLog = Responder-Session.log SessionLog = Responder-Session.log

View file

@ -1,2 +1,3 @@
aioquic aioquic
netifaces>=0.10.4 netifaces>=0.10.4
psycopg>=3.2.3

View file

@ -139,9 +139,19 @@ class Settings:
self.Krb_On_Off = self.toBool(config.get('Responder Core', 'Kerberos')) self.Krb_On_Off = self.toBool(config.get('Responder Core', 'Kerberos'))
self.SNMP_On_Off = self.toBool(config.get('Responder Core', 'SNMP')) self.SNMP_On_Off = self.toBool(config.get('Responder Core', 'SNMP'))
# Db File # Db
self.Dbms = config.get('Responder Core', 'Dbms')
# Db File Sqlite
self.DatabaseFile = os.path.join(self.ResponderPATH, config.get('Responder Core', 'Database')) self.DatabaseFile = os.path.join(self.ResponderPATH, config.get('Responder Core', 'Database'))
# Db Psql
self.PsqlHost = config.get('Responder Core', 'PsqlHost')
self.PsqlPort = config.get('Responder Core', 'PsqlPort')
self.PsqlUser = config.get('Responder Core', 'PsqlUser')
self.PsqlPassword = config.get('Responder Core', 'PsqlPassword')
self.PsqlDatabase = config.get('Responder Core', 'PsqlDatabase')
# Log Files # Log Files
self.LogDir = os.path.join(self.ResponderPATH, 'logs') self.LogDir = os.path.join(self.ResponderPATH, 'logs')

162
utils.py
View file

@ -25,6 +25,7 @@ import datetime
import codecs import codecs
import struct import struct
import random import random
import psycopg
try: try:
import netifaces import netifaces
except: except:
@ -318,7 +319,26 @@ def NetworkRecvBufferPython2or3(data):
else: else:
return str(data.decode('latin-1')) return str(data.decode('latin-1'))
def CreateResponderDb(): def PsqlConnect():
conn = psycopg.connect(dbname=settings.Config.PsqlDatabase,
host=settings.Config.PsqlHost,
user=settings.Config.PsqlUser,
password=settings.Config.PsqlPassword,
port=settings.Config.PsqlPort)
return conn
def _CreateResponderDbPsql():
conn = PsqlConnect()
cursor = conn.cursor()
cursor.execute('CREATE TABLE IF NOT EXISTS Poisoned (timestamp TIMESTAMP, Poisoner TEXT, SentToIp CIDR, ForName TEXT, AnalyzeMode TEXT)')
conn.commit()
cursor.execute('CREATE TABLE IF NOT EXISTS responder (timestamp TIMESTAMP, module TEXT, type TEXT, client CIDR, hostname TEXT, username TEXT, cleartext TEXT, hash TEXT, fullhash TEXT)')
conn.commit()
cursor.execute('CREATE TABLE IF NOT EXISTS DHCP (timestamp TIMESTAMP, MAC MACADDR8, IP CIDR, RequestedIP CIDR)')
conn.commit()
conn.close()
def _CreateResponderDbSqlite():
if not os.path.exists(settings.Config.DatabaseFile): if not os.path.exists(settings.Config.DatabaseFile):
cursor = sqlite3.connect(settings.Config.DatabaseFile) cursor = sqlite3.connect(settings.Config.DatabaseFile)
cursor.execute('CREATE TABLE Poisoned (timestamp TEXT, Poisoner TEXT, SentToIp TEXT, ForName TEXT, AnalyzeMode TEXT)') cursor.execute('CREATE TABLE Poisoned (timestamp TEXT, Poisoner TEXT, SentToIp TEXT, ForName TEXT, AnalyzeMode TEXT)')
@ -329,34 +349,84 @@ def CreateResponderDb():
cursor.commit() cursor.commit()
cursor.close() cursor.close()
def CreateResponderDb():
if settings.Config.Dbms.lower() == 'psql':
_CreateResponderDbPsql()
else:
_CreateResponderDbSqlite()
def _SaveToDbPsql(result):
conn = PsqlConnect()
cursor = conn.cursor()
if len(result['cleartext']):
cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=%s AND type=%s AND client=%s AND LOWER(username)=LOWER(%s) AND cleartext=%s", (result['module'], result['type'], result['client'], result['user'], result['cleartext']))
else:
cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=%s AND type=%s AND client=%s AND LOWER(username)=LOWER(%s)", (result['module'], result['type'], result['client'], result['user']))
(count,) = cursor.fetchone()
if not count:
cursor.execute("INSERT INTO responder VALUES(NOW(), %s, %s, %s, %s, %s, %s, %s, %s)", (result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash']))
conn.commit()
if count and not settings.Config.Verbose and not len(result['cleartext']):
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=NOW() WHERE user=%s AND client=%s", (result['user'], result['client']))
conn.commit()
cursor.close()
return count
def _SaveToDbSqlite(result):
cursor = sqlite3.connect(settings.Config.DatabaseFile)
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets
if len(result['cleartext']):
res = cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=? AND type=? AND client=? AND LOWER(user)=LOWER(?) AND cleartext=?", (result['module'], result['type'], result['client'], result['user'], result['cleartext']))
else:
res = cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=? AND type=? AND client=? AND LOWER(user)=LOWER(?)", (result['module'], result['type'], result['client'], result['user']))
(count,) = res.fetchone()
if not count:
cursor.execute("INSERT INTO responder VALUES(datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?)", (result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash']))
cursor.commit()
if count and not settings.Config.Verbose and not len(result['cleartext']):
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()
return count
def SaveToDb(result): def SaveToDb(result):
for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]: for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]:
if not k in result: if not k in result:
result[k] = '' result[k] = ''
if isinstance(result[k], str) and '\x00' in result[k]:
# some strings have a mixed encoding in a single string
# correct decoding is not possible
result[k] = result[k].replace("\x00","")
result['client'] = result['client'].replace("::ffff:","") result['client'] = result['client'].replace("::ffff:","")
if len(result['user']) < 2: if len(result['user']) < 2:
print(color('[*] Skipping one character username: %s' % result['user'], 3, 1)) print(color('[*] Skipping one character username: %s' % result['user'], 3, 1))
text("[*] Skipping one character username: %s" % result['user']) text("[*] Skipping one character username: %s" % result['user'])
return return
cursor = sqlite3.connect(settings.Config.DatabaseFile) if settings.Config.Dbms.lower() == 'psql':
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets count = _SaveToDbPsql(result)
else:
count = _SaveToDbSqlite(result)
if len(result['cleartext']): if len(result['cleartext']):
fname = '%s-%s-ClearText-%s.txt' % (result['module'], result['type'], result['client']) fname = '%s-%s-ClearText-%s.txt' % (result['module'], result['type'], result['client'])
res = cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=? AND type=? AND client=? AND LOWER(user)=LOWER(?) AND cleartext=?", (result['module'], result['type'], result['client'], result['user'], result['cleartext']))
else: else:
fname = '%s-%s-%s.txt' % (result['module'], result['type'], result['client']) fname = '%s-%s-%s.txt' % (result['module'], result['type'], result['client'])
res = cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=? AND type=? AND client=? AND LOWER(user)=LOWER(?)", (result['module'], result['type'], result['client'], result['user']))
(count,) = res.fetchone()
logfile = os.path.join(settings.Config.ResponderPATH, 'logs', fname) logfile = os.path.join(settings.Config.ResponderPATH, 'logs', fname)
if not count:
cursor.execute("INSERT INTO responder VALUES(datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?)", (result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash']))
cursor.commit()
if not count or settings.Config.CaptureMultipleHashFromSameHost: if not count or settings.Config.CaptureMultipleHashFromSameHost:
with open(logfile,"a") as outf: with open(logfile,"a") as outf:
if len(result['cleartext']): # If we obtained cleartext credentials, write them to file if len(result['cleartext']): # If we obtained cleartext credentials, write them to file
@ -392,11 +462,30 @@ def SaveToDb(result):
elif len(result['cleartext']): elif len(result['cleartext']):
print(color('[*] Skipping previously captured cleartext password for %s' % result['user'], 3, 1)) print(color('[*] Skipping previously captured cleartext password for %s' % result['user'], 3, 1))
text('[*] Skipping previously captured cleartext password for %s' % result['user']) text('[*] Skipping previously captured cleartext password for %s' % result['user'])
else:
print(color('[*] Skipping previously captured hash for %s' % result['user'], 3, 1)) def _SavePoisonersToDbPsql(result):
text('[*] Skipping previously captured hash for %s' % result['user']) conn = PsqlConnect()
cursor.execute("UPDATE responder SET timestamp=datetime('now') WHERE user=? AND client=?", (result['user'], result['client'])) cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) AS count FROM Poisoned WHERE Poisoner=%s AND SentToIp=%s AND ForName=%s AND AnalyzeMode=%s", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode']))
(count,) = cursor.fetchone()
if not count:
cursor.execute("INSERT INTO Poisoned VALUES(NOW(), %s, %s, %s, %s)", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode']))
conn.commit()
cursor.close()
def _SavePoisonersToDbSqlite(result):
cursor = sqlite3.connect(settings.Config.DatabaseFile)
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets
res = cursor.execute("SELECT COUNT(*) AS count FROM Poisoned WHERE Poisoner=? AND SentToIp=? AND ForName=? AND AnalyzeMode=?", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode']))
(count,) = res.fetchone()
if not count:
cursor.execute("INSERT INTO Poisoned VALUES(datetime('now'), ?, ?, ?, ?)", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode']))
cursor.commit() cursor.commit()
cursor.close() cursor.close()
def SavePoisonersToDb(result): def SavePoisonersToDb(result):
@ -405,13 +494,32 @@ def SavePoisonersToDb(result):
if not k in result: if not k in result:
result[k] = '' result[k] = ''
result['SentToIp'] = result['SentToIp'].replace("::ffff:","") result['SentToIp'] = result['SentToIp'].replace("::ffff:","")
if settings.Config.Dbms.lower() == 'psql':
_SavePoisonersToDbPsql(result)
else:
_SavePoisonersToDbSqlite(result)
def _SaveDHCPToDbPsql(result):
conn = PsqlConnect()
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) AS count FROM DHCP WHERE MAC=%s AND IP=%s AND RequestedIP=%s", (result['MAC'], result['IP'], result['RequestedIP']))
(count,) = cursor.fetchone()
if not count:
cursor.execute("INSERT INTO DHCP VALUES(NOW(), %s, %s, %s)", (result['MAC'], result['IP'], result['RequestedIP']))
conn.commit()
cursor.close()
def _SaveDHCPToDbSqlite(result):
cursor = sqlite3.connect(settings.Config.DatabaseFile) cursor = sqlite3.connect(settings.Config.DatabaseFile)
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets
res = cursor.execute("SELECT COUNT(*) AS count FROM Poisoned WHERE Poisoner=? AND SentToIp=? AND ForName=? AND AnalyzeMode=?", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode'])) res = cursor.execute("SELECT COUNT(*) AS count FROM DHCP WHERE MAC=? AND IP=? AND RequestedIP=?", (result['MAC'], result['IP'], result['RequestedIP']))
(count,) = res.fetchone() (count,) = res.fetchone()
if not count: if not count:
cursor.execute("INSERT INTO Poisoned VALUES(datetime('now'), ?, ?, ?, ?)", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode'])) cursor.execute("INSERT INTO DHCP VALUES(datetime('now'), ?, ?, ?)", (result['MAC'], result['IP'], result['RequestedIP']))
cursor.commit() cursor.commit()
cursor.close() cursor.close()
@ -421,17 +529,11 @@ def SaveDHCPToDb(result):
if not k in result: if not k in result:
result[k] = '' result[k] = ''
cursor = sqlite3.connect(settings.Config.DatabaseFile) if settings.Config.Dbms.lower() == 'psql':
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets _SaveDHCPToDbPsql(result)
res = cursor.execute("SELECT COUNT(*) AS count FROM DHCP WHERE MAC=? AND IP=? AND RequestedIP=?", (result['MAC'], result['IP'], result['RequestedIP'])) else:
(count,) = res.fetchone() _SaveDHCPToDbSqlite(result)
if not count:
cursor.execute("INSERT INTO DHCP VALUES(datetime('now'), ?, ?, ?)", (result['MAC'], result['IP'], result['RequestedIP']))
cursor.commit()
cursor.close()
def Parse_IPV6_Addr(data): def Parse_IPV6_Addr(data):
if data[len(data)-4:len(data)] == b'\x00\x1c\x00\x01': if data[len(data)-4:len(data)] == b'\x00\x1c\x00\x01':
return 'IPv6' return 'IPv6'