mirror of
https://github.com/cisagov/snafflepy
synced 2026-09-24 18:22:23 +00:00
prepare classifiers implemented, now for actual classification...
This commit is contained in:
@@ -13,16 +13,24 @@ Tested with Python 3.10.6
|
||||
|
||||
## Usage
|
||||
|
||||
`usage: snaffler.py [-h] [-u username] [-p password] [-d domain] [-v] [-i] [-n] targets [targets ...] `
|
||||
`usage: snaffler.py [-h] [-u USERNAME] [-p PASSWORD] [-d DOMAIN] [-H HASH] [-v] [--go-loud] [-i] [-n] targets [targets ...]`
|
||||
|
||||
## Options
|
||||
1. targets IPs, hostnames, CIDR ranges, or files contains targets to snaffle
|
||||
|
||||
2. -h, --help show help message and exit
|
||||
3. -u username, --username username username for LDAP login and SMB
|
||||
4. -p password, --password password password for LDAP login and SMB
|
||||
5. -d domain, --domain domain Domain to authenticate to
|
||||
6. -H hash, --hash hash NT hash for authentication
|
||||
7. -v, --verbose Show debugging information
|
||||
8. -i, --no-discovery Disables computer and share discovery (more stealthy, maybe)
|
||||
9. -n, --disable-computer-discovery Disable computer discovery, requires a single host or list of hosts to do discovery on
|
||||
~~~
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-u USERNAME, --username USERNAME
|
||||
domain username
|
||||
-p PASSWORD, --password PASSWORD
|
||||
password for domain user
|
||||
-d DOMAIN, --domain DOMAIN
|
||||
FQDN domain to authenticate to, if this option is not provided, SnafflePy will attempt to automatically discover the domain for you
|
||||
-H HASH, --hash HASH NT hash for authentication
|
||||
-v, --verbose Show more info
|
||||
--go-loud Don't try to find anything interesting, literally just go through every computer and every share and print out as many files as possible. Use at your own
|
||||
risk
|
||||
-i, --no-share-discovery
|
||||
Disables share discovery (more stealthy)
|
||||
-n, --disable-computer-discovery
|
||||
Disable computer discovery, requires a list of hosts to do discovery on
|
||||
~~~
|
||||
+30
-4
@@ -1,9 +1,17 @@
|
||||
import re
|
||||
import toml
|
||||
from impacket.smbconnection import SessionError, SMBConnection
|
||||
import os
|
||||
import logging
|
||||
import pprint
|
||||
|
||||
from impacket.smbconnection import SessionError, SMBConnection
|
||||
|
||||
|
||||
log = logging.getLogger('snafflepy.classifier')
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
class Rules:
|
||||
def __init__(self) -> None:
|
||||
self.classifier_rules = []
|
||||
@@ -13,7 +21,6 @@ class Rules:
|
||||
self.contents_classifiers = []
|
||||
self.postmatch_classifiers = []
|
||||
|
||||
|
||||
def prepare_classifiers(self):
|
||||
share_path = "./snaffcore/DefaultRules/"
|
||||
|
||||
@@ -21,11 +28,31 @@ class Rules:
|
||||
for name in files:
|
||||
# print(os.path.join(root,name))
|
||||
with open(os.path.join(root, name), 'r') as tfile:
|
||||
yield toml.load(tfile)
|
||||
toml_loaded = toml.load(tfile)
|
||||
for dict_rule in toml_loaded['ClassifierRules']:
|
||||
if dict_rule['EnumerationScope'] == "ShareEnumeration":
|
||||
self.share_classifiers.append(dict_rule)
|
||||
elif dict_rule['EnumerationScope'] == "FileEnumeration":
|
||||
self.file_classifiers.append(dict_rule)
|
||||
elif dict_rule['EnumerationScope'] == "DirectoryEnumeration":
|
||||
self.directory_classifiers.append(dict_rule)
|
||||
elif dict_rule['EnumerationScope'] == "PostMatch":
|
||||
self.postmatch_classifiers.append(dict_rule)
|
||||
elif dict_rule['EnumerationScope'] == "ContentsEnumeration":
|
||||
self.contents_classifiers.append(dict_rule)
|
||||
else:
|
||||
log.warning(
|
||||
f"{dict_rule['RuleName']} is invalid, please check your syntax!")
|
||||
|
||||
# pprint.pprint(self.share_classifiers)
|
||||
# pprint.pprint(self.directory_classifiers)
|
||||
# pprint.pprint(self.file_classifiers)
|
||||
# pprint.pprint(self.contents_classifiers)
|
||||
# pprint.pprint(self.postmatch_classifiers)
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
def is_interest(file):
|
||||
# massive_wordlist = prepare_classifiers()
|
||||
# print(massive_wordlist)
|
||||
@@ -34,7 +61,6 @@ def is_interest(file):
|
||||
# with open(os.path.join(root, name), 'rb') as tfile:
|
||||
# print(toml.loads(tfile))
|
||||
|
||||
|
||||
interest_names = ["Creds.txt"]
|
||||
if file.get_shortname() in interest_names:
|
||||
return True
|
||||
|
||||
+22
-22
@@ -18,28 +18,28 @@ def begin_snaffle(options):
|
||||
|
||||
# Prepare classifiers for use in naive_classify()
|
||||
snaff_rules = Rules()
|
||||
# prepped_rules = snaff_rules.prepare_classifiers()
|
||||
prepped_rules = snaff_rules.prepare_classifiers()
|
||||
# for dict_rules in prepped_rules:
|
||||
# for actual_rule in dict_rules['ClassifierRules']:
|
||||
# pprint.pprint(actual_rule['Triage'])
|
||||
|
||||
|
||||
print("Beginning the snaffle...")
|
||||
sleep(0.2)
|
||||
|
||||
if(not options.domain):
|
||||
if not options.domain:
|
||||
log.info("Domain not provided, retrieving automatically.")
|
||||
s = Server(options.targets[0], get_info = ALL)
|
||||
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)
|
||||
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)
|
||||
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()
|
||||
|
||||
domain_names = []
|
||||
@@ -58,7 +58,7 @@ def begin_snaffle(options):
|
||||
f"Found{target}, adding to targets to snaffle...")
|
||||
sleep(0.5)
|
||||
try:
|
||||
# TODO: Try to fix this? - How to resolve local IP address from Hostname
|
||||
# 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)
|
||||
@@ -79,8 +79,8 @@ def begin_snaffle(options):
|
||||
except:
|
||||
log.error(f"Error logging in to SMB on {options.targets[0]}")
|
||||
|
||||
else:
|
||||
log.info("Enumerating shares for files...")
|
||||
if options.go_loud:
|
||||
log.warning("[GO LOUD ACTIVATED] Enumerating all shares for all files...")
|
||||
for target in options.targets:
|
||||
try:
|
||||
smb_client = SMBClient(
|
||||
@@ -93,8 +93,8 @@ def begin_snaffle(options):
|
||||
for file in files:
|
||||
# filelist.append(file)
|
||||
# Ask do they want file sizes?
|
||||
# log.info(f"{target} Found file in {share}: {file.get_longname()}")
|
||||
naive_classify(share, file, prepped_rules)
|
||||
log.info(f"{target} Found file in {share}: {file.get_longname()}")
|
||||
# naive_classify(share, file, prepped_rules)
|
||||
# log.info(f"{target} Found file in {share}: {file}")
|
||||
except FileListError:
|
||||
log.error(
|
||||
@@ -103,9 +103,8 @@ def begin_snaffle(options):
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"Error creating SMBClient object, {e}")
|
||||
|
||||
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
def access_ldap_server(ip, username, password):
|
||||
log.info("Accessing LDAP Server")
|
||||
@@ -166,10 +165,11 @@ def list_computers(connection: Connection, domain):
|
||||
return None
|
||||
|
||||
# TODO
|
||||
def naive_classify(share, file, rules:Rules):
|
||||
log.info(f"{share}: {file.get_longname()}")
|
||||
|
||||
|
||||
def naive_classify(share, file, rules: Rules):
|
||||
log.info(f"{share}: {file.get_longname()}")
|
||||
|
||||
if is_interest(file, rules):
|
||||
log.info(f"Found interesting file: {share}/{file}")
|
||||
|
||||
|
||||
+7
-5
@@ -19,16 +19,18 @@ def parse_arguments():
|
||||
add_help=True, prog='snaffler.py', description='A "port" of Snaffler in python')
|
||||
parser.add_argument("targets", nargs='+', type=make_targets,
|
||||
help="IPs, hostnames, CIDR ranges, or files contains targets to snaffle. If you are providing more than one target, the -n option must be used.")
|
||||
parser.add_argument("-u", "--username", metavar='username',
|
||||
parser.add_argument("-u", "--username",
|
||||
type=str, help="domain username")
|
||||
parser.add_argument("-p", "--password", metavar='password',
|
||||
parser.add_argument("-p", "--password",
|
||||
type=str, help="password for domain user")
|
||||
parser.add_argument("-d", "--domain", metavar='domain',
|
||||
default="", help="FQDN domain to authenticate to")
|
||||
parser.add_argument("-H", "--hash", metavar='hash',
|
||||
parser.add_argument("-d", "--domain",
|
||||
default="", help="FQDN domain to authenticate to, if this option is not provided, SnafflePy will attempt to automatically discover the domain for you")
|
||||
parser.add_argument("-H", "--hash",
|
||||
default="", help="NT hash for authentication")
|
||||
parser.add_argument("-v", "--verbose",
|
||||
action='store_true', help="Show more info")
|
||||
parser.add_argument("--go-loud", action='store_true',
|
||||
help="Don't try to find anything interesting, literally just go through every computer and every share and print out as many files as possible. Use at your own risk")
|
||||
# parser.add_argument("-e", "--exclude", )
|
||||
|
||||
# TODO
|
||||
|
||||
Reference in New Issue
Block a user