mirror of
https://github.com/cisagov/snafflepy
synced 2026-09-24 18:22:23 +00:00
snafflepy now looks through directories and subdirectories too! (only in go-loud mode)
This commit is contained in:
+1
-1
@@ -59,4 +59,4 @@ class RemoteFile():
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return f'{self.target}\\{self.share}\\{self.name}'
|
||||
return f'\\\\{self.target}\\{self.share}\\{self.name}'
|
||||
|
||||
+20
-57
@@ -1,15 +1,10 @@
|
||||
import sys
|
||||
# import socket
|
||||
# import urllib.parse
|
||||
# import dns.resolver
|
||||
|
||||
from ldap3 import ALL_ATTRIBUTES, Server, Connection, DSA, ALL, SUBTREE
|
||||
from time import sleep
|
||||
from .smb import *
|
||||
from .utilities import *
|
||||
from .file import *
|
||||
from .classifier import *
|
||||
# import pprint
|
||||
|
||||
log = logging.getLogger('snafflepy')
|
||||
|
||||
@@ -22,23 +17,11 @@ def begin_snaffle(options):
|
||||
|
||||
# Automatically get domain from target if not provided
|
||||
if not options.domain:
|
||||
log.info("Domain not provided, retrieving automatically.")
|
||||
s = Server(options.targets[0], get_info=ALL)
|
||||
c = Connection(s)
|
||||
if not c.bind():
|
||||
log.error("Could not get domain automatically")
|
||||
sys.exit(1)
|
||||
else:
|
||||
try:
|
||||
options.domain = str(
|
||||
s.info.other["ldapServiceName"][0].split("@")[1]).lower()
|
||||
except Exception as e:
|
||||
log.error("Could not get domain automatically")
|
||||
sys.exit(1)
|
||||
c.unbind()
|
||||
options.domain = get_domain(options.targets[0])
|
||||
if options.domain == "":
|
||||
sys.exit(2)
|
||||
|
||||
domain_names = []
|
||||
# TODO: Talk to AD via LDAP to get list of computers with file shares
|
||||
if options.disable_computer_discovery:
|
||||
log.info(
|
||||
"Computer discovery is turned off. Snaffling will only occur on the host(s) specified.")
|
||||
@@ -47,27 +30,15 @@ def begin_snaffle(options):
|
||||
login = access_ldap_server(
|
||||
options.targets[0], options.username, options.password)
|
||||
domain_names = list_computers(login, options.domain)
|
||||
# list_computers() returns list so need to individually add entry
|
||||
for target in domain_names:
|
||||
log.debug(
|
||||
f"Found{target}, adding to targets to snaffle...")
|
||||
sleep(0.5)
|
||||
try:
|
||||
# TODO: Try to fix this? - How to resolve internal IP address from Hostname
|
||||
# Supposedly SMBConnection should be able to take a hostname but not working as intended on the HTB enviroment I am using for testing
|
||||
# ip = resolve(options.domain, target)
|
||||
options.targets.append(target)
|
||||
except Exception as e:
|
||||
log.debug(f"Exception: {e}")
|
||||
log.warning(f"Unable to add{target} to targets to snaffle")
|
||||
continue
|
||||
|
||||
# log.debug(f"Targets that will be snaffled: {options.targets}")
|
||||
|
||||
# Login via SMB
|
||||
# log.info("Preparing classifiers...")
|
||||
# prepare_classifiers()
|
||||
|
||||
try:
|
||||
smb_client = SMBClient(
|
||||
options.targets[0], options.username, options.password, options.domain, options.hash)
|
||||
@@ -81,15 +52,12 @@ def begin_snaffle(options):
|
||||
smb_client = SMBClient(
|
||||
target, options.username, options.password, options.domain, options.hash)
|
||||
if not smb_client.login():
|
||||
log.error(f" Unable to login to{target}")
|
||||
log.error(f"Unable to login to{target}")
|
||||
continue
|
||||
for share in smb_client.shares:
|
||||
try:
|
||||
if not options.go_loud:
|
||||
classify_share(share, snaff_rules)
|
||||
# else:
|
||||
# log.info(f"Found share: {share}")
|
||||
|
||||
is_interest_share(share, snaff_rules)
|
||||
files = smb_client.ls(share, "")
|
||||
|
||||
for file in files:
|
||||
@@ -98,27 +66,30 @@ def begin_snaffle(options):
|
||||
file = RemoteFile(name, share, target, size)
|
||||
|
||||
if options.go_loud:
|
||||
# Dont care about empty files
|
||||
if size == 0:
|
||||
continue
|
||||
try:
|
||||
file_text = termcolor.colored("[File]", 'green')
|
||||
file.get(smb_client)
|
||||
log.info(f"{target}: {share}\\{name}")
|
||||
except FileRetrievalError:
|
||||
log.debug(f"Unable to download ({target}\\\\{share}\\{name})")
|
||||
print(file_text, f"\\\\{target}\\{share}\\{name}")
|
||||
except FileRetrievalError as e:
|
||||
dir_path = file.name
|
||||
# Check if its a directory, and try to list files/more directories here
|
||||
smb_client.handle_download_error(share, dir_path, e)
|
||||
if str(e).find("ACCESS_DENIED"):
|
||||
log.debug(f"Access Denied \\\\{target}\\{share}\\{file}")
|
||||
continue
|
||||
else:
|
||||
if size >= options.max_file_snaffle:
|
||||
pass
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
classify_file(file, snaff_rules, smb_client)
|
||||
is_interest_file(file, snaff_rules, smb_client)
|
||||
except FileRetrievalError as e:
|
||||
log.debug(f"{e}")
|
||||
if str(e).find("ACCESS_DENIED"):
|
||||
log.debug(f"Access Denied, cannot download \\\\{target}\\{share}\\ {file}")
|
||||
continue
|
||||
|
||||
except FileListError:
|
||||
log.error(
|
||||
"Access Denied, cannot list files in %s" % share)
|
||||
except FileListError as e:
|
||||
log.error(f"{share}, {e}")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
@@ -162,7 +133,6 @@ def access_ldap_server(ip, username, password):
|
||||
|
||||
def list_computers(connection: Connection, domain):
|
||||
dn = get_domain_dn(domain)
|
||||
# filter = "(objectCategory=computer)"
|
||||
if connection is None:
|
||||
log.critical("Connection is not established")
|
||||
sys.exit(2)
|
||||
@@ -182,12 +152,5 @@ def list_computers(connection: Connection, domain):
|
||||
log.critical(f"Unable to list computers: {e}")
|
||||
return None
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
def classify_file(file: RemoteFile, rules: Rules, smb_client: SMBClient):
|
||||
is_interest_file(file, rules, smb_client)
|
||||
|
||||
def classify_share(share, rules: Rules):
|
||||
is_interest_share(share, rules)
|
||||
|
||||
|
||||
+27
-1
@@ -4,6 +4,7 @@ import logging
|
||||
import termcolor
|
||||
|
||||
from .errors import *
|
||||
from .file import *
|
||||
from impacket.nmb import NetBIOSError, NetBIOSTimeout
|
||||
from impacket.smbconnection import SessionError, SMBConnection
|
||||
|
||||
@@ -45,7 +46,7 @@ class SMBClient:
|
||||
remarkname = resp[i]['shi1_remark'][:-1]
|
||||
# log.info(f'Found share {sharename} on {self.server}, remark {remarkname}')
|
||||
|
||||
share_text = termcolor.colored("[Share]", 'yellow')
|
||||
share_text = termcolor.colored("[Share]", 'light_yellow')
|
||||
|
||||
print(share_text, termcolor.colored(f"{{Green}} \\\\{self.server}\\{sharename} ({remarkname})", 'green', 'on_white'))
|
||||
# log.info(f'{self.server}: Share: {sharename}')
|
||||
@@ -164,3 +165,28 @@ class SMBClient:
|
||||
log.debug(
|
||||
f'Rebuilding connection to {self.server} after error: {error}')
|
||||
self.login(refresh=True)
|
||||
|
||||
# Handle download errors and recurse into directories
|
||||
def handle_download_error(self, share, dir_path, err):
|
||||
|
||||
while str(err).find("STATUS_FILE_IS_A_DIRECTORY") != -1:
|
||||
try:
|
||||
subfiles = self.ls(share, str(dir_path))
|
||||
for subfile in subfiles:
|
||||
sub_size = subfile.get_filesize()
|
||||
sub_name = str(dir_path + "\\" + subfile.get_longname())
|
||||
subfile = RemoteFile(sub_name, share, self.server, sub_size)
|
||||
subfile.get(self)
|
||||
|
||||
file_text = termcolor.colored("[File]", 'green')
|
||||
print(file_text, f"\\\\{self.server}\\{share}\\{sub_name}")
|
||||
|
||||
self.handle_download_error(share, sub_name, err)
|
||||
except:
|
||||
break
|
||||
finally:
|
||||
break
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+25
-2
@@ -5,10 +5,12 @@ import random
|
||||
import logging
|
||||
import ipaddress
|
||||
from pathlib import Path
|
||||
from ldap3 import ALL_ATTRIBUTES, Server, Connection, DSA, ALL, SUBTREE
|
||||
|
||||
|
||||
# RT: Stolen from manspider - https://github.com/blacklanternsecurity/MANSPIDER
|
||||
|
||||
log = logging.getLogger('snafflerpy.util')
|
||||
log = logging.getLogger('snafflepy.util')
|
||||
|
||||
|
||||
def str_to_list(s):
|
||||
@@ -148,4 +150,25 @@ def get_domain_dn(domain):
|
||||
for i in domain_parts:
|
||||
base_dn += 'DC=%s,' % i
|
||||
base_dn = base_dn[:-1]
|
||||
return base_dn
|
||||
return base_dn
|
||||
|
||||
def get_domain(target):
|
||||
|
||||
log.debug("Domain not provided, retrieving automatically.")
|
||||
s = Server(target, get_info=ALL)
|
||||
c = Connection(s)
|
||||
if not c.bind():
|
||||
log.error("Could not get domain automatically")
|
||||
return ""
|
||||
|
||||
else:
|
||||
try:
|
||||
domain = str(s.info.other["ldapServiceName"][0].split("@")[1]).lower()
|
||||
|
||||
except Exception as e:
|
||||
log.error("Could not get domain automatically")
|
||||
domain = ""
|
||||
|
||||
c.unbind()
|
||||
log.debug(f"Domain:{domain}")
|
||||
return domain
|
||||
Reference in New Issue
Block a user