├── .gitignore ├── README.md ├── requirements.txt ├── snaffcore ├── DefaultRules │ ├── FileRules │ │ ├── Discard │ │ │ ├── DiscardByFileExtension.toml │ │ │ └── DiscardByFileName.toml │ │ └── Keep │ │ │ ├── BusinessDocs │ │ │ └── ByPartialName │ │ │ │ └── KeepFilenameContainsPamOrPwdVault.toml │ │ │ ├── Code │ │ │ ├── CSharpAndASP │ │ │ │ ├── KeepCSharpDbConnStrings.toml │ │ │ │ ├── KeepCSharpViewstateKeys.toml │ │ │ │ └── RelayCSharpByExtension.toml │ │ │ ├── Cmd │ │ │ │ ├── KeepCmdCredentials.toml │ │ │ │ └── RelayCmdByExtension.toml │ │ │ ├── GenericConfig │ │ │ │ ├── KeepConfigByName.toml │ │ │ │ └── RelayConfigByExtension.toml │ │ │ ├── Java │ │ │ │ ├── KeepJavaDbConnStrings.toml │ │ │ │ └── RelayJavaByExtension.toml │ │ │ ├── JavaScript │ │ │ │ └── RelayJsByExtension.toml │ │ │ ├── KeepAwsKeysInCode.toml │ │ │ ├── KeepDbConnStringPw.toml │ │ │ ├── KeepInlinePrivateKey.toml │ │ │ ├── KeepPassOrKeyInCode.toml │ │ │ ├── KeepS3UriPrefixInCode.toml │ │ │ ├── KeepSlackTokensInCode.toml │ │ │ ├── KeepSqlAccountCreation.toml │ │ │ ├── PHP │ │ │ │ ├── KeepPhpByName.toml │ │ │ │ ├── KeepPhpDbConnStrings.toml │ │ │ │ └── RelayPhpByExtension.toml │ │ │ ├── Perl │ │ │ │ ├── KeepPerlDbConnStrings.toml │ │ │ │ └── RelayPerlByExtension.toml │ │ │ ├── PowerShell │ │ │ │ ├── KeepPsByName.toml │ │ │ │ ├── KeepPsCredentials.toml │ │ │ │ └── RelayPsByExtension.toml │ │ │ ├── Python │ │ │ │ ├── KeepPyDbConnStrings.toml │ │ │ │ └── RelayPythonByExtension.toml │ │ │ ├── Ruby │ │ │ │ ├── KeepRubyByName.toml │ │ │ │ ├── KeepRubyDbConnStrings.toml │ │ │ │ └── RelayRubyByExtension.toml │ │ │ ├── ShellScript │ │ │ │ └── RelayShellScriptByExtension.toml │ │ │ └── VBScript │ │ │ │ └── RelayVBScriptByExtension.toml │ │ │ ├── Infrastructure │ │ │ ├── Certificates │ │ │ │ └── RelayCertByExtension.toml │ │ │ ├── CiCdStuff │ │ │ │ └── KeepJenkinsByName.toml │ │ │ ├── Databases │ │ │ │ └── KeepDatabaseByExtension.toml │ │ │ ├── DeploymentAutomation │ │ │ │ ├── KeepDefenderConfigByName.toml │ │ │ │ ├── KeepDeployImageByExtension.toml │ │ │ │ ├── KeepDomainJoinCredsByName.toml │ │ │ │ ├── KeepDomainJoinCredsByPath.toml │ │ │ │ ├── KeepSCCMBootVarCredsByPath.toml │ │ │ │ └── KeepUnattendXmlRelay.toml │ │ │ ├── FTPServers │ │ │ │ └── KeepFtpServerConfigByName.toml │ │ │ ├── InfraAsCode │ │ │ │ └── KeepInfraAsCodeConfigByExtension.toml │ │ │ ├── MemDumps │ │ │ │ ├── KeepMemDumpByExtension.toml │ │ │ │ └── KeepMemDumpByName.toml │ │ │ ├── NetworkDevice │ │ │ │ ├── KeepNetConfigCreds.toml │ │ │ │ ├── KeepNetConfigFileByName.toml │ │ │ │ └── RelayNetConfigByName.toml │ │ │ ├── NixLocalHashes │ │ │ │ └── KeepNixLocalHashesByName.toml │ │ │ ├── PAMAndPwVault │ │ │ │ ├── KeepCyberArkConfigsByName.toml │ │ │ │ └── RelayCyberArkByExtension.toml │ │ │ ├── PacketCapture │ │ │ │ └── KeepPcapByExtension.toml │ │ │ ├── RelayInfraConfigByExtension.toml │ │ │ └── WinHashes │ │ │ │ └── KeepWinHashesByName.toml │ │ │ └── UserFiles │ │ │ ├── APIKeys │ │ │ ├── KeepCloudApiKeysByName.toml │ │ │ └── KeepCloudApiKeysByPath.toml │ │ │ ├── BrowserCreds │ │ │ └── KeepFfLoginsJsonRelay.toml │ │ │ ├── DBMgmt │ │ │ └── KeepDbMgtConfigByName.toml │ │ │ ├── DotFiles │ │ │ ├── KeepGitCredsByName.toml │ │ │ ├── KeepShellHistoryByName.toml │ │ │ └── KeepShellRcFilesByName.toml │ │ │ ├── PassMgrs │ │ │ ├── KeepPassMgrsByExtension.toml │ │ │ └── KeepPasswordFilesByName.toml │ │ │ ├── RemoteAccess │ │ │ ├── KeepFtpClientByName.toml │ │ │ ├── KeepRdpPasswords.toml │ │ │ ├── KeepRemoteAccessConfByExtension.toml │ │ │ ├── KeepRemoteAccessConfByName.toml │ │ │ └── RelayRdpByExtension.toml │ │ │ └── SSH │ │ │ ├── KeepSSHFilesByFileName.toml │ │ │ ├── KeepSSHFilesByPath.toml │ │ │ ├── KeepSSHKeysByFileExtension.toml │ │ │ └── RelayPrivKeyByEnding.toml │ ├── PathRules │ │ └── Discard │ │ │ ├── DiscardLargeFalsePosDirs.toml │ │ │ └── DiscardWinSystemDirs.toml │ ├── PostMatchRules │ │ ├── DiscardPostMatchByName.toml │ │ └── DiscardPostMatchByPath.toml │ ├── ShareRules │ │ ├── Discard │ │ │ └── DiscardNonFileShares.toml │ │ └── Keep │ │ │ ├── KeepDollarShares.toml │ │ │ └── KeepSCCMShares.toml │ └── unsorted.txt ├── classifier.py ├── errors.py ├── file_handling.py ├── go_snaffle.py ├── logger.py ├── smb.py └── utilities.py └── snaffler.py /.gitignore: -------------------------------------------------------------------------------- 1 | # created by virtualenv automatically 2 | snaffcore/__pycache__/ 3 | .vscode 4 | snafflepy/ 5 | remotefiles/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SnafflePy 2 | Snaffler reimplementation in Python - https://github.com/SnaffCon/Snaffler 3 | 4 | Thank you to MANSPIDER for the helpful code that I stole: https://github.com/blacklanternsecurity/MANSPIDER 5 | 6 | Tested with Python 3.10.6 7 | 8 | This tool works by first sending a LDAP query to the specified target to discover other domain joined machines, and then attempts to login (authenticated or not) through SMB and retrieve interesting files (currently work in progress). 9 | 10 | ### Current Features: 11 | SnafflePy includes different options and methods of enumeration. It can discover AD joined computers automatically by performing specific LDAP queries to Active Directory and include them in its target list, or if you want to disable this, it can also manually take in a list of IPs, hostnames, or CIDR ranges as its targets. It can also return every share and filename that is readable on the target network, authenticated or unauthenticated. If the credentials provided fail, then SnafflePy will automatically attempt to login via a Guest user, and if that fails it will attempt to login via a “NULL” session. It also supports the original TOML rule formats from Snaffler and uses them to identify interesting share names and return them to the user. 12 | Currently, SnafflePy can identify common password files by extension and name, backup files by extension, and SSN by regex in file content. 13 | 14 | ### Features to Add: 15 | 1. Classifier system from Snaffler to find interesting files 16 | 2. Make it way faster 17 | 3. Output to JSON 18 | 19 | ## Use case: 20 | 21 | Sometimes you do not always have access to a domain joined windows machine when you want to Snaffle. With this tool, you can "snaffle" from a non windows machine! 22 | 23 | ## Installation (Linux): 24 | 25 | 1. Clone this repository 26 | 27 | 2. Optional but encouraged, create a virtual enviroment for this project 28 | 29 | 3. `pip install -r requirements.txt` 30 | 31 | ## Usage and Options 32 | ~~~ 33 | SnafflePy by @robert-todora 34 | usage: snaffler.py [-h] [-u USERNAME] [-p PASSWORD] [-d DOMAIN] [-H HASH] [-v] [--go-loud] [-m size] [-n] [--no-download] targets [targets ...] 35 | 36 | A "port" of Snaffler in python 37 | 38 | positional arguments: 39 | targets IPs, hostnames, CIDR ranges, or files contains targets to snaffle. If you are providing more than one target, the -n option must be used. 40 | 41 | options: 42 | -h, --help show this help message and exit 43 | -u USERNAME, --username USERNAME 44 | domain username 45 | -p PASSWORD, --password PASSWORD 46 | password for domain user 47 | -d DOMAIN, --domain DOMAIN 48 | FQDN domain to authenticate to, if this option is not provided, SnafflePy will attempt to automatically discover the domain for you 49 | -H HASH, --hash HASH NT hash for authentication 50 | -v, --verbose Show more info 51 | --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 52 | -m size, --max-file-snaffle size 53 | Max filesize to snaffle in bytes (any files over this size will be dropped) 54 | -n, --disable-computer-discovery 55 | Disable computer discovery, requires a list of hosts to do discovery on 56 | --no-download Don't download files, just print found file names to stdout - this can only show the top level of files from the share and is unable to recurse into subdirectories. 57 | ~~~ 58 | 59 | ## Examples 60 | 61 | 1. Snaffle all files, directories, and shares and output them to stdout, files will be downloaded to `PATH-TO-PROJECT/remotefiles/` 62 | 63 | `python3 snaffler.py -u -p -d --go-loud` 64 | 65 | 2. Automatically discover the domain name and identify interesting shares and find a limited number of interesting files from them 66 | 67 | `python3 snaffler.py -u -p -v` 68 | 69 | ## Output 70 | 71 | Screenshot 2023-08-15 at 3 40 37 PM 72 | 73 | 74 | ## Author Information 75 | Robert Todora - robert.todora@cisa.dhs.gov 76 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | argcomplete==3.1.1 2 | blinker==1.6.2 3 | cffi==1.15.1 4 | chardet==5.1.0 5 | click==8.1.3 6 | cryptography==41.0.3 7 | dnspython==2.3.0 8 | Flask==2.3.2 9 | future==0.18.3 10 | impacket==0.10.0 11 | itsdangerous==2.1.2 12 | Jinja2==3.1.2 13 | ldap3==2.9.1 14 | ldapdomaindump==0.9.4 15 | MarkupSafe==2.1.3 16 | packaging==23.1 17 | pipx==1.2.0 18 | pyasn1==0.5.0 19 | pycparser==2.21 20 | pycryptodomex==3.18.0 21 | pyOpenSSL==23.2.0 22 | six==1.16.0 23 | termcolor==2.3.0 24 | toml==0.10.2 25 | userpath==1.8.0 26 | Werkzeug==2.3.6 27 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Discard/DiscardByFileExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "DiscardByFileExtension" 4 | MatchAction = "Discard" 5 | Description = "Skip any further scanning for files with these extensions." 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.bmp", 10 | "\\.eps", 11 | "\\.gif", 12 | "\\.ico", 13 | "\\.jfi", 14 | "\\.jfif", 15 | "\\.jif", 16 | "\\.jpe", 17 | "\\.jpeg", 18 | "\\.jpg", 19 | "\\.png", 20 | "\\.psd", 21 | "\\.svg", 22 | "\\.tif", 23 | "\\.tiff", 24 | "\\.webp", 25 | "\\.xcf", 26 | "\\.ttf", 27 | "\\.otf", 28 | "\\.lock", 29 | "\\.css", 30 | "\\.less", 31 | "\\.admx", 32 | "\\.adml", 33 | "\\.xsd", 34 | "\\.nse", 35 | "\\.xsl"] 36 | Triage = "Green" 37 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Discard/DiscardByFileName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "DiscardByFileName" 4 | MatchAction = "Discard" 5 | Description = "Skip any further scanning for files with these names." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ 10 | "jmxremote\\.password\\.template", 11 | "sceregvl\\.inf" 12 | ] 13 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/BusinessDocs/ByPartialName/KeepFilenameContainsPamOrPwdVault.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepNameContainsGreen" 4 | MatchAction = "Snaffle" 5 | Description = "A description of what a rule does." 6 | MatchLocation = "FileName" 7 | WordListType = "Contains" 8 | MatchLength = 0 9 | WordList = ["passw", 10 | "secret", 11 | "credential", 12 | "thycotic", 13 | "cyberark"] 14 | Triage = "Green" 15 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/CSharpAndASP/KeepCSharpDbConnStrings.toml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [[ClassifierRules]] 5 | EnumerationScope = "ContentsEnumeration" 6 | RuleName = "KeepCSharpDbConnStringsYellow" 7 | MatchAction = "Snaffle" 8 | Description = "Match SQL connection strings that appear to use integrated security (so no passwords)." 9 | MatchLocation = "FileContentAsString" 10 | WordListType = "Regex" 11 | MatchLength = 0 12 | WordList = ["Data Source=.+Integrated Security=(SSPI|true)","Integrated Security=(SSPI|true);.*Data Source=.+"] 13 | Triage = "Yellow" 14 | 15 | [[ClassifierRules]] 16 | EnumerationScope = "ContentsEnumeration" 17 | RuleName = "KeepCSharpDbConnStringsRed" 18 | MatchAction = "Snaffle" 19 | Description = "Match SQL connection strings that appear to have a password." 20 | MatchLocation = "FileContentAsString" 21 | WordListType = "Regex" 22 | MatchLength = 0 23 | WordList = ["Data Source=.+(;|)Password=.+(;|)","Password=.+(;|)Data Source=.+(;|)"] # ["(?!.+Integrated Security)Data Source=.+Password=.+"] 24 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/CSharpAndASP/KeepCSharpViewstateKeys.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepCSharpViewstateKeys" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["validationkey\\s*=\\s*[\\'\\\"][^\\'\\\"]....", 10 | "decryptionkey\\s*=\\s*[\\'\\\"][^\\'\\\"]...."] 11 | Triage = "Red" 12 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/CSharpAndASP/RelayCSharpByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayCSharpByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepCSharpDbConnStringsYellow", 6 | "KeepCSharpDbConnStringsRed", 7 | "KeepCSharpViewstateKeys", 8 | "KeepAwsKeysInCode", 9 | "KeepInlinePrivateKey", 10 | "KeepPassOrKeyInCode", 11 | "KeepSlackTokensInCode", 12 | "KeepSqlAccountCreation", 13 | "KeepDbConnStringPw", 14 | "KeepCSharpDbConnStringsRed", 15 | "KeepCSharpDbConnStringsYellow"] 16 | Description = "Files with these extensions will be searched for CSharp and ASP.NET related strings." 17 | MatchLocation = "FileExtension" 18 | WordListType = "Exact" 19 | MatchLength = 0 20 | WordList = ["\\.aspx", 21 | "\\.ashx", 22 | "\\.asmx", 23 | "\\.asp", 24 | "\\.cshtml", 25 | "\\.cs", 26 | "\\.ascx", 27 | "\\.config"] 28 | Triage = "Green" 29 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Cmd/KeepCmdCredentials.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepCmdCredentials" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["passwo?r?d\\s*=\\s*[\\'\\\"][^\\'\\\"]....", 10 | "schtasks.{1,300}(/rp\\s|/p\\s)", 11 | "net user ", 12 | "psexec .{0,100} -p ", 13 | "net use .{0,300} /user:", 14 | "cmdkey " 15 | ] 16 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Cmd/RelayCmdByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayCmdByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepCmdCredentials", 6 | "KeepAwsKeysInCode", 7 | "KeepInlinePrivateKey", 8 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 9 | "KeepSqlAccountCreation"] 10 | Description = "Files with these extensions will be searched for cmd.exe/batch file related strings." 11 | MatchLocation = "FileExtension" 12 | WordListType = "Exact" 13 | MatchLength = 0 14 | WordList = ["\\.bat", 15 | "\\.cmd"] 16 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/GenericConfig/KeepConfigByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepConfigByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.htpasswd"] 10 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/GenericConfig/RelayConfigByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayConfigByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = [ 6 | "KeepAwsKeysInCode", 7 | "KeepInlinePrivateKey", 8 | "KeepPassOrKeyInCode", 9 | "KeepSlackTokensInCode", 10 | "KeepSqlAccountCreation", 11 | "KeepDbConnStringPw"] 12 | Description = "Files with these extensions will be subjected to a generic search for keys and such." 13 | MatchLocation = "FileExtension" 14 | WordListType = "Exact" 15 | MatchLength = 0 16 | WordList = ["\\.yaml", 17 | "\\.yml", 18 | "\\.toml", 19 | "\\.xml", 20 | "\\.json", 21 | "\\.config", 22 | "\\.ini", 23 | "\\.inf", 24 | "\\.cnf", 25 | "\\.conf", 26 | "\\.properties", 27 | "\\.env", 28 | "\\.dist", 29 | "\\.txt", 30 | "\\.sql", 31 | "\\.log", 32 | "\\.sqlite", 33 | "\\.sqlite3", 34 | "\\.fdb", 35 | "\\.tfvars"] 36 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Java/KeepJavaDbConnStrings.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepJavaDbConnStrings" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["\\.getConnection\\(\\\"jdbc\\:", 10 | "passwo?r?d\\s*=\\s*[\\'\\\"][^\\'\\\"]...."] 11 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Java/RelayJavaByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayJavaByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepJavaDbConnStrings", 6 | "KeepAwsKeysInCode", 7 | "KeepInlinePrivateKey", 8 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 9 | "KeepSqlAccountCreation", 10 | "KeepDbConnStringPw" ] 11 | Description = "Files with these extensions will be searched for Java and ColdFusion related strings." 12 | MatchLocation = "FileExtension" 13 | WordListType = "Exact" 14 | MatchLength = 0 15 | WordList = ["\\.jsp", 16 | "\\.do", 17 | "\\.java", 18 | "\\.cfm"] 19 | Triage = "Green" 20 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/JavaScript/RelayJsByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayJsByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepAwsKeysInCode", 6 | "KeepInlinePrivateKey", 7 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 8 | "KeepSqlAccountCreation", 9 | "KeepDbConnStringPw" ] 10 | Description = "Files with these extensions will be searched for JavaScript related strings." 11 | MatchLocation = "FileExtension" 12 | WordListType = "Exact" 13 | MatchLength = 0 14 | WordList = ["\\.js", 15 | "\\.cjs", 16 | "\\.mjs", 17 | "\\.cs", 18 | "\\.ts", 19 | "\\.tsx", 20 | "\\.ls", 21 | "\\.es6", 22 | "\\.es"] 23 | Triage = "Green" 24 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/KeepAwsKeysInCode.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepAwsKeysInCode" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["aws[_\\-\\.]?key", 10 | "(\\s|\\'|\\\"|\\^|=)(A3T[A-Z0-9]|AKIA|AGPA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z2-7]{12,16}(\\s|\\'|\\\"|$)"] 11 | Triage = "Red" 12 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/KeepDbConnStringPw.toml: -------------------------------------------------------------------------------- 1 | 2 | [[ClassifierRules]] 3 | EnumerationScope = "ContentsEnumeration" 4 | RuleName = "KeepDbConnStringPw" 5 | MatchAction = "Snaffle" 6 | Description = "Files with contents matching these regexen are very interesting." 7 | MatchLocation = "FileContentAsString" 8 | WordListType = "Regex" 9 | MatchLength = 0 10 | WordList = ["connectionstring.{1,200}passw"] 11 | Triage = "Yellow" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/KeepInlinePrivateKey.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepInlinePrivateKey" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["-----BEGIN( RSA| OPENSSH| DSA| EC| PGP)? PRIVATE KEY( BLOCK)?-----"] 10 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/KeepPassOrKeyInCode.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepPassOrKeyInCode" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["passw?o?r?d\\s*=\\s*[\\'\\\"][^\\'\\\"]....", 10 | "api[Kk]ey\\s*=\\s*[\\'\\\"][^\\'\\\"]....", 11 | "passw?o?r?d?>\\s*[^\\s<]+\\s*<", 12 | "passw?o?r?d?>.{3,2000}\\s*[^\\s<]+\\s*<", 14 | "[_\\-\\.]oauth\\s*=\\s*[\\'\\\"][^\\'\\\"]....", 15 | "client_secret\\s*=\\s*[\\'\\\"][^\\'\\\"]....", 16 | "ClientAuth" 17 | ] 18 | Triage = "Red" 19 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/KeepS3UriPrefixInCode.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepS3UriPrefixInCode" 4 | MatchAction = "Snaffle" 5 | Description = "Files with content matching an AWS S3 or Apache Hadoop S3A URI Prefix" 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["s3[a]?:\\/\\/[a-zA-Z0-9\\-\\+\\/]{2,16}"] 10 | Triage = "Yellow" 11 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/KeepSlackTokensInCode.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepSlackTokensInCode" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["(xox[pboa]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32})", 10 | "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}"] 11 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/KeepSqlAccountCreation.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepSqlAccountCreation" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["CREATE (USER|LOGIN) .{0,200} (IDENTIFIED BY|WITH PASSWORD)"] 10 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/PHP/KeepPhpByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepPhpByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["LocalSettings\\.php"] 10 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/PHP/KeepPhpDbConnStrings.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepPhpDbConnStrings" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["mysql_connect\\s*\\(.*\\$.*\\)", 10 | "mysql_pconnect\\s*\\(.*\\$.*\\)", 11 | "mysql_change_user\\s*\\(.*\\$.*\\)", 12 | "pg_connect\\s*\\(.*\\$.*\\)", 13 | "pg_pconnect\\s*\\(.*\\$.*\\)"] 14 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/PHP/RelayPhpByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayPhpByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepPhpDbConnStrings", 6 | "KeepAwsKeysInCode", 7 | "KeepInlinePrivateKey", 8 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 9 | "KeepSqlAccountCreation", 10 | "KeepDbConnStringPw" ] 11 | Description = "Files with these extensions will be searched for php related strings." 12 | MatchLocation = "FileExtension" 13 | WordListType = "Exact" 14 | MatchLength = 0 15 | WordList = ["\\.php", 16 | "\\.phtml", 17 | "\\.inc", 18 | "\\.php3", 19 | "\\.php5", 20 | "\\.php7"] 21 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Perl/KeepPerlDbConnStrings.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepPerlDbConnStrings" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["DBI\\-\\>connect\\("] 10 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Perl/RelayPerlByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayPerlByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepPerlDbConnStrings", 6 | "KeepAwsKeysInCode", 7 | "KeepInlinePrivateKey", 8 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 9 | "KeepSqlAccountCreation", 10 | "KeepDbConnStringPw" ] 11 | Description = "Files with these extensions will be searched for Perl related strings." 12 | MatchLocation = "FileExtension" 13 | WordListType = "Exact" 14 | MatchLength = 0 15 | WordList = ["\\.pl"] 16 | Triage = "Green" 17 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/PowerShell/KeepPsByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepPSHistoryByName" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepPsCredentials", 6 | "KeepCmdCredentials", 7 | "KeepAwsKeysInCode", 8 | "KeepInlinePrivateKey", 9 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 10 | "KeepSqlAccountCreation", 11 | "KeepDbConnStringPw"] 12 | Description = "Files with these exact names will be searched for PowerShell related strings." 13 | MatchLocation = "FileName" 14 | WordListType = "Exact" 15 | MatchLength = 0 16 | WordList = ["ConsoleHost_history\\.txt"] 17 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/PowerShell/KeepPsCredentials.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepPsCredentials" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = [ "-SecureString", 10 | "-AsPlainText", 11 | "\\[Net.NetworkCredential\\]::new\\("] 12 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/PowerShell/RelayPsByExtension.toml: -------------------------------------------------------------------------------- 1 | 2 | [[ClassifierRules]] 3 | EnumerationScope = "FileEnumeration" 4 | RuleName = "RelayPsByExtension" 5 | MatchAction = "Relay" 6 | RelayTargets = ["KeepPsCredentials", 7 | "KeepCmdCredentials", 8 | "KeepAwsKeysInCode", 9 | "KeepInlinePrivateKey", 10 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 11 | "KeepSqlAccountCreation", 12 | "KeepDbConnStringPw"] 13 | Description = "Files with these extensions will be searched for PowerShell related strings." 14 | MatchLocation = "FileExtension" 15 | WordListType = "Exact" 16 | MatchLength = 0 17 | WordList = ["\\.psd1", 18 | "\\.psm1", 19 | "\\.ps1"] 20 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Python/KeepPyDbConnStrings.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepPyDbConnStrings" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["mysql\\.connector\\.connect\\(", 10 | "psycopg2\\.connect\\("] 11 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Python/RelayPythonByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayPythonByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepPyDbConnStrings", 6 | "KeepAwsKeysInCode", 7 | "KeepInlinePrivateKey", 8 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 9 | "KeepSqlAccountCreation", 10 | "KeepDbConnStringPw" ] 11 | Description = "Files with these extensions will be searched for python related strings." 12 | MatchLocation = "FileExtension" 13 | WordListType = "Exact" 14 | MatchLength = 0 15 | WordList = ["\\.py"] 16 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Ruby/KeepRubyByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepRubyByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["database\\.yml", 10 | "\\.secret_token\\.rb", 11 | "knife\\.rb", 12 | "carrierwave\\.rb", 13 | "omniauth\\.rb"] 14 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Ruby/KeepRubyDbConnStrings.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepRubyDbConnStrings" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["DBI\\.connect\\("] 10 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/Ruby/RelayRubyByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayRubyByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepRubyDbConnStrings", 6 | "KeepAwsKeysInCode", 7 | "KeepInlinePrivateKey", 8 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 9 | "KeepSqlAccountCreation", 10 | "KeepDbConnStringPw" ] 11 | Description = "Files with these extensions will be searched for Rubby related strings." 12 | MatchLocation = "FileExtension" 13 | WordListType = "Exact" 14 | MatchLength = 0 15 | WordList = ["\\.rb"] 16 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/ShellScript/RelayShellScriptByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayShellScriptByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = [ 6 | #"KeepShellScriptCredentials", 7 | "KeepAwsKeysInCode", 8 | "KeepInlinePrivateKey", 9 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 10 | "KeepSqlAccountCreation" ] 11 | Description = "Files with these extensions will be searched for Bash related strings." 12 | MatchLocation = "FileExtension" 13 | WordListType = "Exact" 14 | MatchLength = 0 15 | WordList = [ "\\.netrc", 16 | "\\.exports", 17 | "\\.functions", 18 | "\\.extra", 19 | "\\.npmrc", 20 | "\\.env", 21 | "\\.bashrc", 22 | "\\.profile", 23 | "\\.zshrc", 24 | "\\.bash_history", 25 | "\\.zsh_history", 26 | "\\.sh_history", 27 | "zhistory", 28 | "\\.irb_history"] 29 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Code/VBScript/RelayVBScriptByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayVBScriptByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepCmdCredentials", 6 | "KeepAwsKeysInCode", 7 | "KeepInlinePrivateKey", 8 | "KeepPassOrKeyInCode", "KeepSlackTokensInCode", 9 | "KeepSqlAccountCreation", 10 | "KeepDbConnStringPw", 11 | "KeepCSharpDbConnStringsRed", 12 | "KeepCSharpDbConnStringsYellow" ] 13 | Description = "Files with these extensions will be searched for cmd.exe/batch file related strings." 14 | MatchLocation = "FileExtension" 15 | WordListType = "Exact" 16 | MatchLength = 0 17 | WordList = ["\\.vbs", 18 | "\\.vbe", 19 | "\\.wsf", 20 | "\\.wsc", 21 | "\\.asp", 22 | "\\.hta"] 23 | Triage = "Green" 24 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/Certificates/RelayCertByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayCertByExtension" 4 | MatchAction = "CheckForKeys" 5 | Description = "Files with these extensions will be parsed as x509 certificates to see if they have private keys." 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.pem", 10 | "\\.der", 11 | "\\.pfx", 12 | "\\.pk12", 13 | "\\.p12", 14 | "\\.pkcs12"] 15 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/CiCdStuff/KeepJenkinsByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepJenkinsByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["jenkins\\.plugins\\.publish_over_ssh\\.BapSshPublisherPlugin\\.xml", 10 | "credentials\\.xml"] 11 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/Databases/KeepDatabaseByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepDatabaseByExtension" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these extensions are a little interesting." 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ 10 | "\\.mdf", 11 | "\\.sdf", 12 | "\\.sqldump", 13 | "\\.bak"] 14 | Triage = "Yellow" 15 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/DeploymentAutomation/KeepDefenderConfigByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepDefenderConfigByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files containing Defender Configs are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["SensorConfiguration.json"] 10 | Triage = "Yellow" 11 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/DeploymentAutomation/KeepDeployImageByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepDeployImageByExtension" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these extensions are a little interesting." 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ 10 | "\\.wim", 11 | "\\.ova", 12 | "\\.ovf"] 13 | Triage = "Yellow" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/DeploymentAutomation/KeepDomainJoinCredsByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepDomainJoinCredsByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files containing Domain Join Credes are quite interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["customsettings.ini"] 10 | Triage = "Yellow" 11 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/DeploymentAutomation/KeepDomainJoinCredsByPath.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepDomainJoinCredsByPath" 4 | MatchAction = "Snaffle" 5 | Description = "Files with a path containing these strings are very interesting." 6 | MatchLocation = "FilePath" 7 | WordListType = "Contains" 8 | MatchLength = 0 9 | WordList = ["control\\\\customsettings.ini"] 10 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/DeploymentAutomation/KeepSCCMBootVarCredsByPath.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepSCCMBootVarCredsByPath" 4 | MatchAction = "Snaffle" 5 | Description = "Files with a path containing these strings are very interesting." 6 | MatchLocation = "FilePath" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["REMINST\\\\SMSTemp\\\\.*\\.var", 10 | "SMS\\\\data\\\\Variables.dat", 11 | "SMS\\\\data\\\\Policy.xml" 12 | ] 13 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/DeploymentAutomation/KeepUnattendXmlRelay.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayUnattendXml" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepUnattendXmlRegexRed"] 6 | Description = "Look inside unattend.xml files for actual values." 7 | MatchLocation = "FileName" 8 | WordListType = "Exact" 9 | MatchLength = 0 10 | WordList = ["unattend\\.xml", 11 | "Autounattend\\.xml"] 12 | Triage = "Green" 13 | 14 | [[ClassifierRules]] 15 | EnumerationScope = "ContentsEnumeration" 16 | RuleName = "KeepUnattendXmlRegexRed" 17 | MatchAction = "Snaffle" 18 | Description = "Files with contents matching these regexen are very interesting." 19 | MatchLocation = "FileContentAsString" 20 | WordListType = "Regex" 21 | MatchLength = 0 22 | WordList = ["(?s).{0,30}.*<\\/Value>", 23 | "(?s).{0,30}.*<\\/Value>"] 24 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/FTPServers/KeepFtpServerConfigByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepFtpServerConfigByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["proftpdpasswd", 10 | "filezilla\\.xml"] 11 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/InfraAsCode/KeepInfraAsCodeConfigByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepInfraAsCodeByExtension" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these extensions are very very interesting." 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ "\\.cscfg", 10 | "\\.tfvars"] 11 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/MemDumps/KeepMemDumpByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepMemDumpByExtension" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these extensions are a little interesting." 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.dmp"] 10 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/MemDumps/KeepMemDumpByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepMemDumpByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ "MEMORY\\.DMP", 10 | "hiberfil\\.sys", 11 | "lsass\\.dmp", 12 | "lsass\\.exe\\.dmp", ] 13 | Triage = "Black" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/NetworkDevice/KeepNetConfigCreds.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepNetConfigCreds" 4 | MatchAction = "Snaffle" 5 | Description = "A description of what a rule does." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["NVRAM config last updated", 10 | "enable password \\.", 11 | "simple-bind authenticated encrypt", 12 | "pac key [0-7] ", 13 | "snmp-server community\\s.+\\sRW"] 14 | Triage = "Red" 15 | 16 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/NetworkDevice/KeepNetConfigFileByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepNetConfigFileByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ "running-config\\.cfg", 10 | "startup-config\\.cfg", 11 | "running-config", 12 | "startup-config"] 13 | Triage = "Black" 14 | 15 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/NetworkDevice/RelayNetConfigByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayNetConfigByName" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepNetConfigCreds"] 6 | Description = "Files with these name patterns will be searched for Cisco bits." 7 | MatchLocation = "FileName" 8 | WordListType = "Contains" 9 | MatchLength = 0 10 | WordList = ["cisco","router","firewall","switch"] 11 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/NixLocalHashes/KeepNixLocalHashesByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepNixLocalHashesByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ "shadow", 10 | "pwd\\.db", 11 | "passwd"] 12 | Triage = "Black" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/PAMAndPwVault/KeepCyberArkConfigsByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepCyberArkConfigsByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ 10 | "Psmapp\\.cred", 11 | "psmgw\\.cred", 12 | "backup\\.key", 13 | "MasterReplicationUser\\.pass", 14 | "RecPrv\\.key", 15 | "ReplicationUser\\.pass", 16 | "Server\\.key", 17 | "VaultEmergency\\.pass", 18 | "VaultUser\\.pass", 19 | "Vault\\.ini", 20 | "PADR\\.ini", 21 | "PARAgent\\.ini", 22 | "CACPMScanner\\.exe\\.config", 23 | "PVConfiguration\\.xml" 24 | ] 25 | Triage = "Black" 26 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/PAMAndPwVault/RelayCyberArkByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepCyberArkByExtension" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these extensions are QUITE interesting." 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ 10 | "\\.cred", 11 | "\\.pass" 12 | ] 13 | Triage = "Red" 14 | 15 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/PacketCapture/KeepPcapByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepPcapByExtension" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these extensions are a little interesting." 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.pcap", 10 | "\\.cap", 11 | "\\.pcapng", 12 | ] 13 | Triage = "Yellow" 14 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/RelayInfraConfigByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayInfraConfigByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = [ 6 | "KeepNetConfigCreds" 7 | ] 8 | Description = "Files with these extensions will be subjected to a generic search for keys and such." 9 | MatchLocation = "FileExtension" 10 | WordListType = "Exact" 11 | MatchLength = 0 12 | WordList = [ 13 | "\\.xml", 14 | "\\.json", 15 | "\\.config", 16 | "\\.ini", 17 | "\\.inf", 18 | "\\.cnf", 19 | "\\.conf", 20 | "\\.txt" 21 | ] 22 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/Infrastructure/WinHashes/KeepWinHashesByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepWinHashesByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ "NTDS.DIT", 10 | "SYSTEM", 11 | "SAM", 12 | "SECURITY"] 13 | Triage = "Black" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/APIKeys/KeepCloudApiKeysByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepCloudApiKeysByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.tugboat"] 10 | Triage = "Black" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/APIKeys/KeepCloudApiKeysByPath.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepCloudApiKeysByPath" 4 | MatchAction = "Snaffle" 5 | Description = "Files with a path containing these strings are very very interesting." 6 | MatchLocation = "FilePath" 7 | WordListType = "Contains" 8 | MatchLength = 0 9 | WordList = ["\\\\\\.aws\\\\", 10 | "doctl\\\\config.yaml"] 11 | Triage = "Black" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/BrowserCreds/KeepFfLoginsJsonRelay.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepFfLoginsJsonRelay" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepFFRegexRed"] 6 | Description = "Files with these extensions will be searched for Firefox/Thunderbird backups related strings." 7 | MatchLocation = "FileName" 8 | WordListType = "Exact" 9 | MatchLength = 0 10 | WordList = ["logins\\.json"] 11 | Triage = "Green" 12 | 13 | [[ClassifierRules]] 14 | EnumerationScope = "ContentsEnumeration" 15 | RuleName = "KeepFFRegexRed" 16 | MatchAction = "Snaffle" 17 | Description = "Files with contents matching these regexes are very interesting." 18 | MatchLocation = "FileContentAsString" 19 | WordListType = "Regex" 20 | MatchLength = 0 21 | WordList = ["\"encryptedPassword\":\"[A-Za-z0-9+/=]+\""] 22 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/DBMgmt/KeepDbMgtConfigByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepDbMgtConfigByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["SqlStudio\\.bin", 10 | "\\.mysql_history", 11 | "\\.psql_history", 12 | "\\.pgpass", 13 | "\\.dbeaver-data-sources\\.xml", 14 | "credentials-config\\.json", 15 | "dbvis\\.xml", 16 | "robomongo\\.json"] 17 | Triage = "Red" 18 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/DotFiles/KeepGitCredsByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepGitCredsByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.git-credentials"] 10 | Triage = "Red" 11 | 12 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/DotFiles/KeepShellHistoryByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepShellHistoryByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.bash_history", 10 | "\\.zsh_history", 11 | "\\.sh_history", 12 | "zhistory", 13 | "\\.irb_history", 14 | "ConsoleHost_History\\.txt"] 15 | Triage = "Green" 16 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/DotFiles/KeepShellRcFilesByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepShellRcFilesByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.netrc", 10 | "_netrc", 11 | "\\.exports", 12 | "\\.functions", 13 | "\\.extra", 14 | "\\.npmrc", 15 | "\\.env", 16 | "\\.bashrc", 17 | "\\.profile", 18 | "\\.zshrc"] 19 | Triage = "Green" 20 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/PassMgrs/KeepPassMgrsByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepPassMgrsByExtension" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these extensions are very very interesting." 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.kdbx", 10 | "\\.kdb", 11 | "\\.psafe3", 12 | "\\.kwallet", 13 | "\\.keychain", 14 | "\\.agilekeychain", 15 | "\\.cred"] 16 | Triage = "Black" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/PassMgrs/KeepPasswordFilesByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepPasswordFilesByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["passwords\\.txt", 10 | "pass\\.txt", 11 | "accounts\\.txt", 12 | "passwords\\.doc", 13 | "pass\\.doc", 14 | "accounts\\.doc", 15 | "passwords\\.xls", 16 | "pass\\.xls", 17 | "accounts\\.xls", 18 | "passwords\\.docx", 19 | "pass\\.docx", 20 | "accounts\\.docx", 21 | "passwords\\.xlsx", 22 | "pass\\.xlsx", 23 | "accounts\\.xlsx", 24 | "secrets\\.txt", 25 | "secrets\\.doc", 26 | "secrets\\.xls", 27 | "secrets\\.docx", 28 | "secrets\\.xlsx"] 29 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/RemoteAccess/KeepFtpClientByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepFtpClientConfigConfigByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["recentservers\\.xml", 10 | "sftp-config\\.json"] 11 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/RemoteAccess/KeepRdpPasswords.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepRdpPasswords" 4 | MatchAction = "Snaffle" 5 | Description = "Files with contents matching these regexen are very interesting." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["password 51\\:b"] 10 | Triage = "Red" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/RemoteAccess/KeepRemoteAccessConfByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepRemoteAccessConfByExtension" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these extensions are a little interesting." 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.rdg","\\.rtsz","\\.rtsx","\\.ovpn","\\.tvopt","\\.sdtid"] 10 | Triage = "Yellow" 11 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/RemoteAccess/KeepRemoteAccessConfByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepRemoteAccessConfByName" 4 | MatchAction = "Snaffle" 5 | Description = "Files with these exact names are very very interesting." 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ "mobaxterm\\.ini", 10 | "mobaxterm backup\\.zip", 11 | "confCons.xml"] 12 | Triage = "Black" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/RemoteAccess/RelayRdpByExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "RelayRdpByExtension" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepRdpPasswords"] 6 | Description = "Look inside .rdp files for actual values." 7 | MatchLocation = "FileExtension" 8 | WordListType = "Exact" 9 | MatchLength = 0 10 | WordList = ["\\.rdp"] 11 | Triage = "Green" 12 | 13 | 14 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/SSH/KeepSSHFilesByFileName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepSSHKeysByFileName" 4 | MatchAction = "Snaffle" 5 | Description = "SSHKeys" 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["id_rsa", 10 | "id_dsa", 11 | "id_ecdsa", 12 | "id_ed25519"] 13 | Triage = "Black" 14 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/SSH/KeepSSHFilesByPath.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepSSHFilesByPath" 4 | MatchAction = "Snaffle" 5 | Description = "Files with a path containing these strings are very very interesting." 6 | MatchLocation = "FilePath" 7 | WordListType = "Contains" 8 | MatchLength = 0 9 | WordList = ["\\\\\\.ssh\\\\"] 10 | Triage = "Black" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/SSH/KeepSSHKeysByFileExtension.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "KeepSSHKeysByFileExtension" 4 | MatchAction = "Snaffle" 5 | Description = "SSHKeys" 6 | MatchLocation = "FileExtension" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\.ppk"] 10 | Triage = "Black" 11 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/FileRules/Keep/UserFiles/SSH/RelayPrivKeyByEnding.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "FileEnumeration" 3 | RuleName = "CertContentByEnding" 4 | MatchAction = "Relay" 5 | RelayTargets = ["KeepInlinePrivateKey"] 6 | Description = "Files ending like this will be grepped for private keys." 7 | MatchLocation = "FileName" 8 | WordListType = "EndsWith" 9 | MatchLength = 0 10 | WordList = ["_rsa", 11 | "_dsa", 12 | "_ed25519", 13 | "_ecdsa"] 14 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/PathRules/Discard/DiscardLargeFalsePosDirs.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "DirectoryEnumeration" 3 | RuleName = "DiscardLargeFalsePosDirs" 4 | MatchAction = "Discard" 5 | Description = "File paths that will be skipped entirely." 6 | MatchLocation = "FilePath" 7 | WordListType = "Contains" 8 | MatchLength = 0 9 | WordList = [ 10 | "\\\\puppet\\\\share\\\\doc", 11 | "\\\\lib\\\\ruby", 12 | "\\\\lib\\\\site-packages", 13 | "\\\\usr\\\\share\\\\doc", 14 | "node_modules", 15 | "vendor\\\\bundle", 16 | "vendor\\\\cache", 17 | "\\\\doc\\\\openssl", 18 | "Anaconda3\\\\Lib\\\\test", 19 | "WindowsPowerShell\\\\Modules", 20 | "Python\\d*\\\\Lib", 21 | "Reference Assemblies\\\\Microsoft\\\\Framework\\\\.NETFramework", 22 | "dotnet\\\\sdk", 23 | "dotnet\\\\shared", 24 | "Modules\\\\Microsoft\\.PowerShell\\.Security", 25 | "Windows\\\\assembly" 26 | ] 27 | Triage = "Green" 28 | 29 | 30 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/PathRules/Discard/DiscardWinSystemDirs.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "DirectoryEnumeration" 3 | RuleName = "DiscardWinSystemDirs" 4 | MatchAction = "Discard" 5 | Description = "File paths that will be skipped entirely." 6 | MatchLocation = "FilePath" 7 | WordListType = "Contains" 8 | MatchLength = 0 9 | WordList = ["\\\\winsxs", 10 | "\\\\syswow64", 11 | "\\\\system32", 12 | "\\\\systemapps", 13 | "\\\\windows\\\\servicing", 14 | "\\\\servicing", 15 | "\\\\Microsoft\\.NET\\\\Framework", 16 | "\\\\windows\\\\immersivecontrolpanel", 17 | "\\\\windows\\\\diagnostics", 18 | "\\\\windows\\\\debug", 19 | "\\\\locale", 20 | "\\\\chocolatey\\\\helpers", 21 | "\\\\sources\\\\sxs", 22 | "\\\\localization", 23 | "\\\\AppData\\\\Local\\\\Microsoft", 24 | "\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows", 25 | "\\\\AppData\\\\Roaming\\\\Microsoft\\\\Teams", 26 | "\\\\wsuscontent", 27 | "\\\\Application Data\\\\Microsoft\\\\CLR Security Config", 28 | "\\\\servicing\\\\LCU"] 29 | Triage = "Green" 30 | -------------------------------------------------------------------------------- /snaffcore/DefaultRules/PostMatchRules/DiscardPostMatchByName.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "PostMatch" 3 | RuleName = "DiscardPostMatchByName" 4 | MatchAction = "Discard" 5 | Description = "Post-match check for specific filenames" 6 | MatchLocation = "FileName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = [ 10 | "credentialprovider\\.idl", 11 | "pspasswd64\\.exe", 12 | "pspasswd\\.exe", 13 | "psexec\\.exe", 14 | "psexec64\\.exe" 15 | ] 16 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/PostMatchRules/DiscardPostMatchByPath.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "PostMatch" 3 | RuleName = "DiscardPostMatchByPath" 4 | MatchAction = "Discard" 5 | Description = "Post-match check for specific path elements" 6 | MatchLocation = "FilePath" 7 | WordListType = "Contains" 8 | MatchLength = 0 9 | WordList = [ 10 | "Windows Kits\\\\10", 11 | "Git\\\\mingw64", 12 | "Git\\\\usr\\\\lib", 13 | "ProgramData\\\\Microsoft\\\\NetFramework\\\\BreadcrumbStore", 14 | "\\.MSSQLSERVER\\\\MSSQL\\\\Binn\\\\Templates" 15 | ] 16 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/ShareRules/Discard/DiscardNonFileShares.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ShareEnumeration" 3 | RuleName = "DiscardNonFileShares" 4 | MatchAction = "Discard" 5 | Description = "Skips scanning inside shares ending with these words." 6 | MatchLocation = "ShareName" 7 | WordListType = "EndsWith" 8 | MatchLength = 0 9 | WordList = ["\\\\print\\$", "\\\\ipc\\$", "PRINT\\$", "IPC\\$"] 10 | Triage = "Green" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/ShareRules/Keep/KeepDollarShares.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ShareEnumeration" 3 | RuleName = "KeepDollarShares" 4 | MatchAction = "Snaffle" 5 | Description = "Notifies the user that C$ or ADMIN$ is visible, but doesn't actually scan inside the share." 6 | MatchLocation = "ShareName" 7 | WordListType = "Exact" 8 | MatchLength = 0 9 | WordList = ["\\\\C\\$", 10 | "\\\\ADMIN\\$", "ADMIN\\$", "C\\$"] 11 | Triage = "Black" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/ShareRules/Keep/KeepSCCMShares.toml: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ShareEnumeration" 3 | RuleName = "KeepSCCMShares" 4 | MatchAction = "Snaffle" 5 | Description = "Notifies the user that they can read SCCMContentLib$ so they can take CMLoot for a spin." 6 | MatchLocation = "ShareName" 7 | WordListType = "EndsWith" 8 | MatchLength = 0 9 | WordList = ["\\\\SCCMContentLib\\$"] 10 | Triage = "Yellow" -------------------------------------------------------------------------------- /snaffcore/DefaultRules/unsorted.txt: -------------------------------------------------------------------------------- 1 | [[ClassifierRules]] 2 | EnumerationScope = "ContentsEnumeration" 3 | RuleName = "KeepCertRegexRed" 4 | MatchAction = "Snaffle" 5 | Description = "A description of what a rule does." 6 | MatchLocation = "FileContentAsString" 7 | WordListType = "Regex" 8 | MatchLength = 0 9 | WordList = ["-----BEGIN( RSA| OPENSSH| DSA| EC| PGP)? PRIVATE KEY( BLOCK)?-----"] 10 | Triage = "Red" 11 | 12 | [[ClassifierRules]] 13 | EnumerationScope = "FileEnumeration" 14 | RuleName = "KeepPathContainsRed" 15 | MatchAction = "Snaffle" 16 | Description = "Files with a path containing these strings are very interesting." 17 | MatchLocation = "FilePath" 18 | WordListType = "Contains" 19 | MatchLength = 0 20 | WordList = ["\\\\.purple\\\\accounts.xml", 21 | "\\\\.gem\\\\credentials", 22 | "config\\\\hub"] 23 | Triage = "Red" 24 | 25 | [[ClassifierRules]] 26 | EnumerationScope = "ContentsEnumeration" 27 | RuleName = "KeepConfigRegexRed" 28 | MatchAction = "Snaffle" 29 | Description = "A description of what a rule does." 30 | MatchLocation = "FileContentAsString" 31 | WordListType = "Regex" 32 | MatchLength = 0 33 | WordList = ["NVRAM config last updated", 34 | "enable password \\.", 35 | "simple-bind authenticated encrypt"] 36 | Triage = "Red" 37 | 38 | [[ClassifierRules]] 39 | EnumerationScope = "FileEnumeration" 40 | RuleName = "KeepExtExactYellow" 41 | MatchAction = "Snaffle" 42 | Description = "Files with these extensions are a little interesting." 43 | MatchLocation = "FileExtension" 44 | WordListType = "Exact" 45 | MatchLength = 0 46 | WordList = ["\\.key", 47 | "\\.keypair", 48 | "\\.jks", ] 49 | Triage = "Yellow" 50 | 51 | [[ClassifierRules]] 52 | EnumerationScope = "FileEnumeration" 53 | RuleName = "KeepFilenameExactRed" 54 | MatchAction = "Snaffle" 55 | Description = "Files with these exact names are very interesting." 56 | MatchLocation = "FileName" 57 | WordListType = "Exact" 58 | MatchLength = 0 59 | WordList = [ 60 | "otr\\.private_key", 61 | "Favorites\\.plist", 62 | "proxy\\.config", 63 | "keystore", 64 | "keyring", 65 | "\\.gitconfig", 66 | "\\.dockercfg", 67 | "key3\\.db", 68 | "key4\\.db", 69 | "Login Data"] 70 | Triage = "Green" 71 | -------------------------------------------------------------------------------- /snaffcore/classifier.py: -------------------------------------------------------------------------------- 1 | import re 2 | import toml 3 | import os 4 | import logging 5 | # import pprint 6 | import termcolor 7 | 8 | from impacket.smbconnection import SessionError, SMBConnection 9 | from .smb import * 10 | from .file_handling import * 11 | 12 | log = logging.getLogger('snafflepy.classifier') 13 | 14 | # TODO 15 | 16 | 17 | class Rules: 18 | 19 | def __init__(self) -> None: 20 | self.classifier_rules = [] 21 | self.share_classifiers = [] 22 | self.directory_classifiers = [] 23 | self.file_classifiers = [] 24 | self.contents_classifiers = [] 25 | self.postmatch_classifiers = [] 26 | 27 | def prepare_classifiers(self): 28 | share_path = "./snaffcore/DefaultRules/" 29 | 30 | for root, dirs, files in os.walk(share_path, topdown=False): 31 | for name in files: 32 | # print(os.path.join(root,name)) 33 | with open(os.path.join(root, name), 'r') as tfile: 34 | toml_loaded = toml.load(tfile) 35 | for dict_rule in toml_loaded['ClassifierRules']: 36 | if dict_rule['EnumerationScope'] == "ShareEnumeration": 37 | self.share_classifiers.append(dict_rule) 38 | elif dict_rule['EnumerationScope'] == "FileEnumeration": 39 | self.file_classifiers.append(dict_rule) 40 | elif dict_rule['EnumerationScope'] == "DirectoryEnumeration": 41 | self.directory_classifiers.append(dict_rule) 42 | elif dict_rule['EnumerationScope'] == "PostMatch": 43 | self.postmatch_classifiers.append(dict_rule) 44 | elif dict_rule['EnumerationScope'] == "ContentsEnumeration": 45 | self.contents_classifiers.append(dict_rule) 46 | else: 47 | log.warning( 48 | f"{dict_rule['RuleName']} is invalid, please check your syntax!") 49 | 50 | # TODO 51 | 52 | 53 | def is_interest_file(file, smb_client, share, no_download: bool): 54 | backup_ext_list = [".bak", ".mdf", ".sqldump", ".sdf", ".dmp"] 55 | cred_list = ["creds", "password", "passw", "credentials", "login", "secret", "account", "pass", 56 | ".kdb", ".psafe3", ".kwallet", ".keychain", ".agilekeychain", ".cred"] 57 | 58 | file_text = termcolor.colored(f"[File]", "green") 59 | ssn_regex = str("^\d{{3}}-\d{{2}}-\d{{4}}$") 60 | is_interest = False 61 | 62 | # Non-file shares 63 | # if str(share).lower().find("ipc") or str(share).lower().find("print"): 64 | # pass 65 | # else: 66 | 67 | # MVP Build only, check for SSN in files 68 | 69 | # MVP Build only, check for backup files 70 | for ext in backup_ext_list: 71 | if re.search(str(ext), str(file.name).lower()): 72 | is_interest = True 73 | file_triage = termcolor.colored( 74 | f"{{Yellow}}\\\\{file.target}\\{share}\\{file.name} ", "light_yellow", "on_white") 75 | try: 76 | file.get(smb_client) 77 | log.info(f"{file_text} {file_triage}") 78 | except FileRetrievalError as e: 79 | file.handle_download_error(file.name, e, False, False) 80 | 81 | # MVP Build only, check for files with possible passwords contained inside 82 | for cred in cred_list: 83 | if re.search(str(cred), str(file.name).lower()): 84 | is_interest = True 85 | 86 | file_triage = termcolor.colored( 87 | f"{{Black}}\\\\{file.target}\\{share}\\{file.name} ", "black", "on_white") 88 | try: 89 | if not no_download: 90 | file.get(smb_client) 91 | log.info(f"{file_text} {file_triage}") 92 | except FileRetrievalError as e: 93 | file.handle_download_error(file.name, e, False, False) 94 | 95 | file_data = "" 96 | try: 97 | if not no_download: 98 | file.get(smb_client) 99 | with open(str(file.tmp_filename), 'rb') as f: 100 | file_data = str(f.read(10000)) 101 | if re.search(ssn_regex, file_data): 102 | file_triage = termcolor.colored( 103 | f"{{Red}}\\\\{file.target}\\{share}\\{file.name} ", "red", "on_white") 104 | log.info(f"{file_text} {file_triage}") 105 | elif not is_interest: 106 | # print(file.name) 107 | os.remove(f"./{file.tmp_filename}") 108 | else: 109 | pass 110 | except FileRetrievalError as e: 111 | os.remove(f"./{file.tmp_filename}") 112 | file.handle_download_error(file.name, e, False, False) 113 | 114 | 115 | # TODO 116 | # Implementing file name classification with snafflers ruleset here 117 | # Going to follow the same steps as is_interest_share 118 | # Call this function after every file has been downloaded in order to avoid problems with nested directories 119 | # Call it within the for targets loop but after the for share loop stage is over so it can still do multiple targets 120 | def classify_file_name(file, rules: Rules): 121 | for rule in rules.file_classifiers: 122 | pass 123 | 124 | def classify_file_content(file, rules:Rules): 125 | for rule in rules.contents_classifiers: 126 | pass 127 | 128 | def classify_directory(dir, rules:Rules): 129 | for rule in rules.directory_classifiers: 130 | regex_rules = [] 131 | dir_text = termcolor.colored("[Share]", 'light_yellow') 132 | default_triage = termcolor.colored(f"{dir}", 'green') 133 | if rule['WordListType'] == "Regex": 134 | regex_rules = rule['WordList'] 135 | for pattern in regex_rules: 136 | if re.search(str(pattern), str(dir)) is not None: 137 | if rule['MatchAction'] == "Snaffle": 138 | color = rule['Triage'] 139 | print(dir_text, termcolor.colored( 140 | f"{{{rule['Triage']}}} {dir} <{rule['RuleName']}>:<{rule['Description']}>", str(color).lower(), 'on_white')) 141 | return True 142 | else: 143 | log.debug( 144 | f"{dir} matched rule {rule['RuleName']}:{rule['Description']}") 145 | if rule['MatchAction'] == "Discard": 146 | return False 147 | 148 | elif rule['WordListType'] == "EndsWith": 149 | regex_rules = rule['WordList'] 150 | for pattern in regex_rules: 151 | if re.search(str(pattern + "$"), str(dir)) is not None: 152 | if rule['MatchAction'] == "Snaffle": 153 | color = rule['Triage'] 154 | print(dir_text, termcolor.colored( 155 | f"{{{rule['Triage']}}} {dir} <{rule['RuleName']}>:<{rule['Description']}>", str(color).lower(), 'on_white')) 156 | return True 157 | 158 | else: 159 | log.debug( 160 | f"{dir} matched rule {rule['RuleName']}:{rule['Description']}") 161 | if rule['MatchAction'] == "Discard": 162 | return False 163 | 164 | elif rule['WordListType'] == "StartsWith": 165 | regex_rules = rule['WordList'] 166 | for pattern in regex_rules: 167 | if re.search(str("^" + pattern), str(dir)) is not None: 168 | if rule['MatchAction'] == "Snaffle": 169 | color = rule['Triage'] 170 | print(dir_text, termcolor.colored( 171 | f"{{{rule['Triage']}}} {dir} <{rule['RuleName']}>:<{rule['Description']}>", str(color).lower(), 'on_white')) 172 | return True 173 | 174 | else: 175 | log.debug( 176 | f"{dir} matched rule {rule['RuleName']}:{rule['Description']}") 177 | if rule['MatchAction'] == "Discard": 178 | return False 179 | 180 | elif rule['WordListType'] == "Contains": 181 | regex_rules = rule['WordList'] 182 | for pattern in regex_rules: 183 | if re.search(str(pattern), str(dir)) is not None: 184 | if rule['MatchAction'] == "Snaffle": 185 | color = rule['Triage'] 186 | print(dir_text, termcolor.colored( 187 | f"{{{rule['Triage']}}} {dir} <{rule['RuleName']}>:<{rule['Description']}>", str(color).lower(), 'on_white')) 188 | return True 189 | else: 190 | log.debug( 191 | f"{rule['MatchAction']} {dir} matched rule {rule['RuleName']}:{rule['Description']}") 192 | if rule['MatchAction'] == "Discard": 193 | return False 194 | 195 | elif rule['WordListType'] == "Exact": 196 | regex_rules = rule['WordList'] 197 | for pattern in regex_rules: 198 | if re.search(str("^" + pattern + "$"), str(dir)) is not None: 199 | if rule['MatchAction'] == "Snaffle": 200 | color = rule['Triage'] 201 | print(dir_text, termcolor.colored( 202 | f"{{{rule['Triage']}}} {dir} <{rule['RuleName']}>:<{rule['Description']}>", str(color).lower(), 'on_white')) 203 | return True 204 | else: 205 | log.debug( 206 | f"{dir} matched rule {rule['RuleName']}:{rule['Description']}") 207 | if rule['MatchAction'] == "Discard": 208 | return False 209 | 210 | else: 211 | log.warning( 212 | f"{rule['RuleName']} has an invalid WordListType - valid values are Regex, EndsWith, StartsWith, Contains, or Exact") 213 | raise Exception("Invalid WordListType") 214 | 215 | 216 | def is_interest_share(share, rules: Rules) -> bool: 217 | 218 | # Tedium City to find match in wordlist. Did not prepare rules beforehand except by putting each MatchLocation in its own list 219 | # so I have to do more work here before I can find the match 220 | 221 | for rule in rules.share_classifiers: 222 | regex_rules = [] 223 | share_text = termcolor.colored("[Share]", 'light_yellow') 224 | default_triage = termcolor.colored(f"{share}", 'green') 225 | if rule['WordListType'] == "Regex": 226 | regex_rules = rule['WordList'] 227 | for pattern in regex_rules: 228 | if re.search(str(pattern), str(share)) is not None: 229 | if rule['MatchAction'] == "Snaffle": 230 | color = rule['Triage'] 231 | print(share_text, termcolor.colored( 232 | f"{{{rule['Triage']}}} {share} <{rule['RuleName']}>:<{rule['Description']}>", str(color).lower(), 'on_white')) 233 | return True 234 | else: 235 | log.debug( 236 | f"{share} matched rule {rule['RuleName']}:{rule['Description']}") 237 | if rule['MatchAction'] == "Discard": 238 | return False 239 | 240 | elif rule['WordListType'] == "EndsWith": 241 | regex_rules = rule['WordList'] 242 | for pattern in regex_rules: 243 | if re.search(str(pattern + "$"), str(share)) is not None: 244 | if rule['MatchAction'] == "Snaffle": 245 | color = rule['Triage'] 246 | print(share_text, termcolor.colored( 247 | f"{{{rule['Triage']}}} {share} <{rule['RuleName']}>:<{rule['Description']}>", str(color).lower(), 'on_white')) 248 | return True 249 | 250 | else: 251 | log.debug( 252 | f"{share} matched rule {rule['RuleName']}:{rule['Description']}") 253 | if rule['MatchAction'] == "Discard": 254 | return False 255 | 256 | elif rule['WordListType'] == "StartsWith": 257 | regex_rules = rule['WordList'] 258 | for pattern in regex_rules: 259 | if re.search(str("^" + pattern), str(share)) is not None: 260 | if rule['MatchAction'] == "Snaffle": 261 | color = rule['Triage'] 262 | print(share_text, termcolor.colored( 263 | f"{{{rule['Triage']}}} {share} <{rule['RuleName']}>:<{rule['Description']}>", str(color).lower(), 'on_white')) 264 | return True 265 | 266 | else: 267 | log.debug( 268 | f"{share} matched rule {rule['RuleName']}:{rule['Description']}") 269 | if rule['MatchAction'] == "Discard": 270 | return False 271 | 272 | elif rule['WordListType'] == "Contains": 273 | regex_rules = rule['WordList'] 274 | for pattern in regex_rules: 275 | if re.search(str(pattern), str(share)) is not None: 276 | if rule['MatchAction'] == "Snaffle": 277 | color = rule['Triage'] 278 | print(share_text, termcolor.colored( 279 | f"{{{rule['Triage']}}} {share} <{rule['RuleName']}>:<{rule['Description']}>", str(color).lower(), 'on_white')) 280 | return True 281 | else: 282 | log.debug( 283 | f"{rule['MatchAction']} {share} matched rule {rule['RuleName']}:{rule['Description']}") 284 | if rule['MatchAction'] == "Discard": 285 | return False 286 | 287 | elif rule['WordListType'] == "Exact": 288 | regex_rules = rule['WordList'] 289 | for pattern in regex_rules: 290 | if re.search(str("^" + pattern + "$"), str(share)) is not None: 291 | if rule['MatchAction'] == "Snaffle": 292 | color = rule['Triage'] 293 | print(share_text, termcolor.colored( 294 | f"{{{rule['Triage']}}} {share} <{rule['RuleName']}>:<{rule['Description']}>", str(color).lower(), 'on_white')) 295 | return True 296 | else: 297 | log.debug( 298 | f"{share} matched rule {rule['RuleName']}:{rule['Description']}") 299 | if rule['MatchAction'] == "Discard": 300 | return False 301 | 302 | else: 303 | log.warning( 304 | f"{rule['RuleName']} has an invalid WordListType - valid values are Regex, EndsWith, StartsWith, Contains, or Exact") 305 | raise Exception("Invalid WordListType") 306 | -------------------------------------------------------------------------------- /snaffcore/errors.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import logging 3 | from impacket.nmb import NetBIOSError, NetBIOSTimeout 4 | from impacket.smb import SessionError, UnsupportedFeature 5 | from impacket.smbconnection import SessionError as CSessionError 6 | 7 | # RT: Stolen from manspider - https://github.com/blacklanternsecurity/MANSPIDER 8 | # set up logging 9 | log = logging.getLogger('snafflepy') 10 | 11 | 12 | class SnafflerError(Exception): 13 | pass 14 | 15 | 16 | class FileRetrievalError(SnafflerError): 17 | pass 18 | 19 | 20 | class ShareListError(SnafflerError): 21 | pass 22 | 23 | 24 | class FileListError(SnafflerError): 25 | pass 26 | 27 | 28 | class LogonFailure(SnafflerError): 29 | pass 30 | 31 | 32 | native_impacket_errors = ( 33 | struct.error, 34 | NetBIOSError, 35 | NetBIOSTimeout, 36 | SessionError, 37 | CSessionError, 38 | UnsupportedFeature, 39 | ) 40 | 41 | 42 | impacket_errors = ( 43 | OSError, 44 | BrokenPipeError, 45 | ) + native_impacket_errors 46 | 47 | 48 | def impacket_error(e): 49 | ''' 50 | Tries to format impacket exceptions nicely 51 | ''' 52 | 53 | if type(e) in (SessionError, CSessionError): 54 | try: 55 | error_str = e.getErrorString()[0] 56 | e.args = (error_str,) 57 | except (IndexError,): 58 | pass 59 | if not e.args: 60 | e.args = ('',) 61 | return e 62 | 63 | 64 | def handle_impacket_error(e, smb_client, share='', filename='', display=False): 65 | ''' 66 | Handle arbitrary Impacket errors 67 | this is needed because the library doesn't implement proper inheritance for its exceptions 68 | ''' 69 | 70 | resource_str = '/'.join([smb_client.server, share, filename]).rstrip('/') 71 | 72 | if type(e) == KeyboardInterrupt: 73 | raise 74 | elif type(e) in (NetBIOSError, NetBIOSTimeout, BrokenPipeError, SessionError, CSessionError): 75 | # the connection may need to be rebuilt 76 | if type(e) in (SessionError, CSessionError): 77 | if any([x in str(e) for x in ('PASSWORD_EXPIRED',)]): 78 | smb_client.rebuild(e) 79 | else: 80 | smb_client.rebuild(e) 81 | if type(e) in native_impacket_errors: 82 | e = impacket_error(e) 83 | if display: 84 | log.debug(f'{resource_str}: {str(e)[:150]}') 85 | 86 | return e 87 | -------------------------------------------------------------------------------- /snaffcore/file_handling.py: -------------------------------------------------------------------------------- 1 | from .utilities import * 2 | from .errors import * 3 | 4 | from pathlib import Path 5 | import os 6 | import termcolor 7 | 8 | 9 | # RT: Stolen from manspider - https://github.com/blacklanternsecurity/MANSPIDER 10 | 11 | 12 | class RemoteFile(): 13 | ''' 14 | Represents a file on an SMB share 15 | Passed from a spiderling up to its parent spider 16 | ''' 17 | 18 | def __init__(self, name, share, target, size=0, smb_client=None): 19 | 20 | self.share = share 21 | self.target = target 22 | self.name = name 23 | self.size = size 24 | self.smb_client = smb_client 25 | 26 | does_exist = os.path.exists("remotefiles") 27 | if not does_exist: 28 | log.info("remotefiles directory not present, creating dir") 29 | os.makedirs("remotefiles") 30 | 31 | # file_suffix = Path(name).suffix.lower() 32 | self.tmp_filename = Path('./remotefiles') / \ 33 | (self.name) 34 | 35 | # self.tmp_filename = Path('/tmp/.snafflepy') / \ 36 | # (random_string(15) + file_suffix) 37 | 38 | def get(self, smb_client=None): 39 | ''' 40 | Downloads file to self.tmp_filename 41 | 42 | NOTE: SMBConnection() can't be passed through a multiprocessing queue 43 | This means that smb_client must be set after the file arrives at Spider() 44 | ''' 45 | 46 | if smb_client is None and self.smb_client is None: 47 | raise FileRetrievalError('Please specify smb_client') 48 | 49 | # memfile = io.BytesIO() 50 | with open(str(self.tmp_filename), 'wb') as f: 51 | 52 | try: 53 | smb_client.conn.getFile(self.share, self.name, f.write) 54 | except Exception as e: 55 | handle_impacket_error(e, smb_client, self.share, self.name) 56 | raise FileRetrievalError( 57 | f'Error retrieving file "{str(self)}": {str(e)[:150]}') 58 | 59 | # reset cursor back to zero so .read() will return the whole file 60 | # memfile.seek(0) 61 | 62 | def __str__(self): 63 | 64 | return f'\\\\{self.target}\\{self.share}\\{self.name}' 65 | 66 | def handle_download_error(self, dir_path, err, is_from_go_loud: bool, add_err: bool): 67 | # subfiles = [] 68 | if str(err).find("DIRECTORY"): 69 | dir_text = termcolor.colored("[Directory]", 'light_blue') 70 | 71 | if is_from_go_loud: 72 | log.info( 73 | f"{dir_text} \\\\{self.target}\\{self.share}\\{dir_path}") 74 | try: 75 | subfiles = self.smb_client.ls(self.share, str(dir_path)) 76 | add_err = False 77 | 78 | 79 | for subfile in subfiles: 80 | sub_size = subfile.get_filesize() 81 | sub_name = str(dir_path + "\\" + subfile.get_longname()) 82 | 83 | try: 84 | subfile = RemoteFile( 85 | sub_name, self.share, self.target, sub_size) 86 | if is_from_go_loud: 87 | subfile.get(self.smb_client) 88 | # else: 89 | # is_interest_file(self, self.smb_client, self.share) 90 | add_err = False 91 | 92 | except FileRetrievalError as e: 93 | # handle_impacket_error(e, subfile.smb_client, subfile.share, sub_name, True) 94 | err = e 95 | add_err = True 96 | 97 | finally: 98 | if add_err: 99 | # print(error) 100 | self.handle_download_error( 101 | sub_name, err, is_from_go_loud, True) 102 | else: 103 | file_text = termcolor.colored("[File]", 'green') 104 | if is_from_go_loud: 105 | log.info( 106 | f"{file_text} \\\\{self.target}\\{self.share}\\{sub_name}") 107 | except FileListError as e: 108 | if is_from_go_loud: 109 | log.error( 110 | f"Access denied, cannot read at {self.target}\\{self.share}\\{dir_path}") 111 | -------------------------------------------------------------------------------- /snaffcore/go_snaffle.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from ldap3 import ALL_ATTRIBUTES, Server, Connection, DSA, ALL, SUBTREE 4 | # from .smb import * 5 | from .utilities import * 6 | # from .file_handling import * 7 | from .classifier import * 8 | from .errors import * 9 | 10 | log = logging.getLogger('snafflepy') 11 | 12 | 13 | def begin_snaffle(options): 14 | 15 | snaff_rules = Rules() 16 | snaff_rules.prepare_classifiers() 17 | 18 | print("Beginning the snaffle...") 19 | 20 | # Automatically get domain from target if not provided 21 | if not options.domain: 22 | options.domain = get_domain(options.targets[0]) 23 | if options.domain == "": 24 | sys.exit(2) 25 | 26 | domain_names = [] 27 | if options.disable_computer_discovery: 28 | log.info( 29 | "Computer discovery is turned off. Snaffling will only occur on the host(s) specified.") 30 | 31 | else: 32 | login = access_ldap_server( 33 | options.targets[0], options.username, options.password) 34 | domain_names = list_computers(login, options.domain) 35 | for target in domain_names: 36 | log.info( 37 | f"Found{target}, adding to targets to snaffle...") 38 | try: 39 | options.targets.append(target) 40 | except Exception as e: 41 | log.debug(f"Exception: {e}") 42 | log.warning(f"Unable to add{target} to targets to snaffle") 43 | continue 44 | 45 | if options.go_loud: 46 | log.warning( 47 | "[GO LOUD ACTIVATED] Enumerating all shares for all files...") 48 | if options.no_download: 49 | log.warning("[no-download] is turned on, skipping SSN check...") 50 | 51 | for target in options.targets: 52 | 53 | smb_client = SMBClient( 54 | target, options.username, options.password, options.domain, options.hash) 55 | if not smb_client.login(): 56 | log.error(f"Unable to login to{target}") 57 | continue 58 | 59 | for share in smb_client.shares: 60 | files = [] 61 | try: 62 | if not options.go_loud: 63 | if is_interest_share(share, snaff_rules) == False: 64 | log.debug(f"{share} matched a Discard rule, skipping files inside of this share...") 65 | continue 66 | 67 | files = smb_client.ls(share, "") 68 | 69 | 70 | 71 | for file in files: 72 | size = file.get_filesize() 73 | name = file.get_longname() 74 | # bad_name = name 75 | file = RemoteFile(name, share, target, size, smb_client) 76 | 77 | if options.go_loud: 78 | try: 79 | file_text = termcolor.colored("[File]", 'green') 80 | if not options.no_download: 81 | file.get(smb_client) 82 | log.info( 83 | f"{file_text} \\\\{target}\\{share}\\{name}") 84 | 85 | except FileRetrievalError as e: 86 | no_add_error = False 87 | keep_dir_name = True 88 | # Check if its a directory, and try to list files/more directories here 89 | file.handle_download_error( 90 | file.name, e, options.go_loud, no_add_error) 91 | 92 | else: 93 | if size >= options.max_file_snaffle: 94 | pass 95 | else: 96 | try: 97 | is_interest_file(file, smb_client, share, options.no_download) 98 | except FileRetrievalError as e: 99 | # Error will trigger if access denied to file, or the file is actually a directory 100 | # File 101 | # keep_dir_name = classify_directory(file.name, snaff_rules) 102 | no_add_error = False 103 | file.handle_download_error( 104 | file.name, e, options.go_loud, no_add_error) 105 | 106 | except FileListError as e: 107 | log.error(f"Cannot list files at {share} {e}") 108 | 109 | 110 | 111 | 112 | def access_ldap_server(ip, username, password): 113 | # log.info("Accessing LDAP Server") 114 | server = Server(ip, get_info=DSA) 115 | try: 116 | conn = Connection(server, username, password) 117 | # log.debug(server.schema) 118 | 119 | if not conn.bind(): 120 | log.critical(f"Unable to bind to {server}") 121 | return None 122 | return conn 123 | 124 | except Exception as e: 125 | log.critical(f'Error logging in to {ip}') 126 | log.info("Trying guest session... ") 127 | 128 | try: 129 | conn = Connection(server, user='Guest', password='') 130 | if not conn.bind(): 131 | log.critical(f"Unable to bind to {server} as {username}") 132 | return None 133 | return conn 134 | 135 | except Exception as e: 136 | log.critical(f'Error logging in to {ip}, as {username}') 137 | log.info("Trying null session... ") 138 | 139 | conn = Connection(server, user='', password='') 140 | if not conn.bind(): 141 | log.critical(f"Unable to bind to {server}") 142 | return None 143 | return conn 144 | 145 | # 2nd snaffle step, finding additional targets from original target via LDAP queries 146 | 147 | 148 | def list_computers(connection: Connection, domain): 149 | dn = get_domain_dn(domain) 150 | if connection is None: 151 | log.critical("Connection is not established") 152 | sys.exit(2) 153 | 154 | try: 155 | connection.search(search_base=dn, search_filter='(&(objectCategory=Computer)(name=*))', 156 | search_scope=SUBTREE, attributes=['dNSHostName'], paged_size=500) 157 | domain_names = [] 158 | 159 | for entry in connection.entries: 160 | sep = str(entry).strip().split(':') 161 | # Sometimes there's no dNSHostName 162 | if len(sep) == 7: 163 | domain_names.append(sep[6]) 164 | 165 | return domain_names 166 | 167 | except Exception as e: 168 | log.critical(f"Unable to list computers: {e}") 169 | return None 170 | -------------------------------------------------------------------------------- /snaffcore/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from copy import copy 3 | from sys import stdout 4 | from pathlib import Path 5 | from datetime import datetime 6 | from multiprocessing import Queue 7 | from logging.handlers import QueueHandler, QueueListener 8 | 9 | 10 | ### PRETTY COLORS ### 11 | 12 | # RT: Stolen from manspider - https://github.com/blacklanternsecurity/MANSPIDER 13 | 14 | 15 | class ColoredFormatter(logging.Formatter): 16 | 17 | color_mapping = { 18 | 'DEBUG': 69, # blue 19 | 'INFO': 118, # green 20 | 'WARNING': 208, # orange 21 | 'ERROR': 196, # red 22 | 'CRITICAL': 196, # red 23 | } 24 | 25 | char_mapping = { 26 | 'DEBUG': '*', 27 | 'INFO': '+', 28 | 'WARNING': '-', 29 | 'ERROR': '!', 30 | 'CRITICAL': '!!!', 31 | } 32 | 33 | prefix = '\033[1;38;5;' 34 | suffix = '\033[0m' 35 | 36 | def __init__(self, pattern): 37 | 38 | super().__init__(pattern) 39 | 40 | def format(self, record): 41 | 42 | colored_record = copy(record) 43 | levelname = colored_record.levelname 44 | levelchar = self.char_mapping.get(levelname, '+') 45 | seq = self.color_mapping.get(levelname, 15) # default white 46 | colored_levelname = f'{self.prefix}{seq}m[{levelchar}]{self.suffix}' 47 | colored_record.levelname = colored_levelname 48 | 49 | return logging.Formatter.format(self, colored_record) 50 | 51 | @classmethod 52 | def green(cls, s): 53 | 54 | return cls.color(s) 55 | 56 | @classmethod 57 | def red(cls, s): 58 | 59 | return cls.color(s, level='ERROR') 60 | 61 | @classmethod 62 | def color(cls, s, level='INFO'): 63 | 64 | color = cls.color_mapping.get(level) 65 | return f'{cls.prefix}{color}m{s}{cls.suffix}' 66 | 67 | 68 | class CustomQueueListener(QueueListener): 69 | ''' 70 | Ignore errors in the monitor thread that result from a race condition when the program exits 71 | ''' 72 | 73 | def _monitor(self): 74 | try: 75 | super()._monitor() 76 | except Exception: 77 | pass 78 | 79 | 80 | ### LOG TO STDERR ### 81 | 82 | console = logging.StreamHandler(stdout) 83 | # tell the handler to use this format 84 | console.setFormatter(ColoredFormatter('%(levelname)s %(message)s')) 85 | 86 | 87 | ### LOG TO FILE ### 88 | 89 | log_queue = Queue() 90 | listener = CustomQueueListener(log_queue, console) 91 | sender = QueueHandler(log_queue) 92 | logging.getLogger('snafflepy').handlers = [sender] 93 | 94 | logdir = Path.home() / '.snafflepy' / 'logs' 95 | logdir.mkdir(parents=True, exist_ok=True) 96 | logfile = f'snafflepy_{datetime.now().strftime("%m-%d-%Y")}.log' 97 | handler = logging.FileHandler(str(logdir / logfile)) 98 | handler.setFormatter(logging.Formatter( 99 | '%(asctime)s %(levelname)s %(message)s')) 100 | 101 | logging.getLogger('snafflepy').addHandler(console) 102 | logging.getLogger('snafflepy').addHandler(handler) 103 | -------------------------------------------------------------------------------- /snaffcore/smb.py: -------------------------------------------------------------------------------- 1 | import ntpath 2 | import struct 3 | import logging 4 | import termcolor 5 | 6 | # from .errors import * 7 | from .file_handling import * 8 | from impacket.nmb import NetBIOSError, NetBIOSTimeout 9 | from impacket.smbconnection import SessionError, SMBConnection 10 | 11 | 12 | # RT: Stolen from manspider - https://github.com/blacklanternsecurity/MANSPIDER 13 | 14 | # set up logging 15 | log = logging.getLogger('snafflepy.smb') 16 | 17 | 18 | class SMBClient: 19 | ''' 20 | Wrapper around impacket's SMBConnection() object 21 | ''' 22 | 23 | def __init__(self, server, username, password, domain, nthash): 24 | 25 | self.server = server 26 | 27 | self.conn = None 28 | 29 | self.username = username 30 | self.password = password 31 | self.domain = domain 32 | self.nthash = nthash 33 | if self.nthash: 34 | # means no password, see https://yougottahackthat.com/blog/339/what-is-aad3b435b51404eeaad3b435b51404ee 35 | self.lmhash = 'aad3b435b51404eeaad3b435b51404ee' 36 | else: 37 | self.lmhash = '' 38 | 39 | @property 40 | def shares(self): 41 | 42 | try: 43 | resp = self.conn.listShares() 44 | for i in range(len(resp)): 45 | sharename = resp[i]['shi1_netname'][:-1] 46 | remarkname = resp[i]['shi1_remark'][:-1] 47 | # log.info(f'Found share {sharename} on {self.server}, remark {remarkname}') 48 | 49 | share_text = termcolor.colored("[Share]", 'light_yellow') 50 | 51 | print(share_text, termcolor.colored( 52 | f"{{Green}} \\\\{self.server}\\{sharename} ({remarkname})", 'green', 'on_white')) 53 | # log.info(f'{self.server}: Share: {sharename}') 54 | 55 | yield sharename 56 | 57 | except Exception as e: 58 | e = handle_impacket_error(e, self) 59 | log.debug(f'{self.server}: Error listing shares: {e}') 60 | 61 | def login(self, refresh=False, first_try=True): 62 | ''' 63 | Create a new SMBConnection object (if there isn't one already or if refresh is True) 64 | Attempt to log in, and switch to null session if logon fails 65 | Return True if logon succeeded 66 | Return False if logon failed 67 | ''' 68 | 69 | if self.conn is None or refresh: 70 | try: 71 | self.conn = SMBConnection( 72 | self.server, self.server, sess_port=445, timeout=10) 73 | except Exception as e: 74 | # log.info(f"Timeout exceeded, unable to connect to {self.server}") 75 | e = handle_impacket_error(e, self, display=True) 76 | # self.conn = SMBConnection( 77 | # self.server, self.server, sess_port=139, timeout=10) 78 | 79 | try: 80 | 81 | if self.username in [None, '', 'Guest'] and first_try: 82 | # skip to guest / null session 83 | assert False 84 | 85 | log.debug( 86 | f'{self.server}: Authenticating as "{self.username}"') 87 | 88 | # pass the hash if requested 89 | if self.nthash and not self.password: 90 | self.conn.login( 91 | self.username, 92 | '', 93 | lmhash=self.lmhash, 94 | nthash=self.nthash, 95 | domain=self.domain, 96 | ) 97 | # otherwise, normal login 98 | else: 99 | self.conn.login( 100 | self.username, 101 | self.password, 102 | domain=self.domain, 103 | ) 104 | 105 | log.debug( 106 | f'{self.server}: Successful login as "{self.username}"') 107 | return True 108 | 109 | except Exception as e: 110 | 111 | if type(e) != AssertionError: 112 | e = handle_impacket_error(e, self, display=False) 113 | 114 | # try guest account, then null session if logon failed 115 | if first_try: 116 | 117 | bad_statuses = [ 118 | 'LOGON_FAIL', 'PASSWORD_EXPIRED', 'LOCKED_OUT', 'SESSION_DELETED'] 119 | for s in bad_statuses: 120 | if s in str(e): 121 | log.warning( 122 | f'{self.server}: {s}: {self.username}') 123 | 124 | log.debug(f'{self.server}: Trying guest session') 125 | self.username = 'Guest' 126 | self.password = '' 127 | self.domain = '' 128 | self.nthash = '' 129 | guest_success = self.login(refresh=True, first_try=False) 130 | if not guest_success: 131 | log.debug(f'{self.server}: Switching to null session') 132 | self.username = '' 133 | self.login(refresh=True, first_try=False) 134 | 135 | return False 136 | 137 | else: 138 | return True 139 | 140 | def ls(self, share, path): 141 | ''' 142 | List files in share/path 143 | Raise FileListError if there's a problem 144 | @byt3bl33d3r it's really not that bad 145 | ''' 146 | 147 | nt_path = ntpath.normpath(f'{path}\\*') 148 | 149 | # for every file/dir in "path" 150 | try: 151 | for f in self.conn.listPath(share, nt_path): 152 | # exclude current and parent directory 153 | if f.get_longname() not in ['', '.', '..']: 154 | yield f 155 | except Exception as e: 156 | e = handle_impacket_error(e, self) 157 | raise FileListError( 158 | f'{e.args}: Error listing files at "{share}{nt_path}"') 159 | 160 | def rebuild(self, error=''): 161 | ''' 162 | Rebuild our SMBConnection() if it gets borked 163 | ''' 164 | 165 | log.debug( 166 | f'Rebuilding connection to {self.server} after error: {error}') 167 | self.login(refresh=True) 168 | 169 | # Handle download errors and recurse into directories, current implementation may not work properly 170 | # I think there needs to be a finally block that goes through the current directory and tries to get any remaining files/dirs because 171 | # it will stop as soon as it finds one subdirectory 172 | # def handle_download_error(self, share, dir_path, err, isFromGoLoud:bool): 173 | # add_err = False 174 | # problem_files = [] 175 | 176 | # if str(err).find("STATUS_FILE_IS_A_DIRECTORY"): 177 | # dir_text = termcolor.colored("[Directory]", 'light_blue') 178 | 179 | # if isFromGoLoud: 180 | # log.info(f"{dir_text}\\\\{self.server}\\{share}\\{dir_path}") 181 | 182 | # subfiles = self.ls(share, str(dir_path)) 183 | 184 | # for subfile in subfiles: 185 | # sub_size = subfile.get_filesize() 186 | # sub_name = str(dir_path + "\\" + subfile.get_longname()) 187 | 188 | # # try: 189 | # subfile = RemoteFile(sub_name, share, self.server, sub_size) 190 | # subfile.get(self) 191 | 192 | # if FileRetrievalError: 193 | # add_Err = True 194 | # problem_files.append(subfile) 195 | # continue 196 | # else: 197 | # file_text = termcolor.colored("[File]", 'green') 198 | # if isFromGoLoud: 199 | # log.info(f"{file_text} \\\\{self.server}\\{share}\\{sub_name}") 200 | 201 | # # except FileRetrievalError as e: 202 | # if str(err).find("STATUS_FILE_IS_A_DIRECTORY"): 203 | # dir_text = termcolor.colored("[Directory]", 'light_blue') 204 | 205 | # if isFromGoLoud: 206 | # log.info(f"{dir_text}\\\\{self.server}\\{share}\\{sub_name}") 207 | # self.handle_download_error(share, sub_name, e, True) 208 | 209 | # else: 210 | # self.handle_download_error(share, sub_name, e, False) 211 | 212 | # elif str(err).find("ACCESS_DENIED"): 213 | # continue 214 | 215 | # ORIGINAL 216 | # if str(err).find("STATUS_FILE_IS_A_DIRECTORY"): 217 | # dir_text = termcolor.colored("[Directory]", 'light_blue') 218 | # if isFromGoLoud: 219 | # log.info(f"{dir_text}\\\\{self.server}\\{share}\\{dir_path}") 220 | # try: 221 | # subfiles = self.ls(share, str(dir_path)) 222 | 223 | # except FileListError as e: 224 | # log.error(f"Access denied, cannot read at \\\\{self.server}\\{share}\\{dir_path}") 225 | 226 | # for subfile in subfiles: 227 | 228 | # sub_size = subfile.get_filesize() 229 | # sub_name = str(dir_path + "\\" + subfile.get_longname()) 230 | 231 | # try: 232 | # subfile = RemoteFile(sub_name, share, self.server, sub_size) 233 | # subfile.get(self) 234 | 235 | # file_text = termcolor.colored("[File]", 'green') 236 | # if isFromGoLoud: 237 | # log.info(f"{file_text} \\\\{self.server}\\{share}\\{sub_name}") 238 | 239 | # # self.handle_download_error(share, sub_name, err) 240 | # except Exception as e: 241 | # if str(err).find("STATUS_FILE_IS_A_DIRECTORY"): 242 | # dir_text = termcolor.colored("[Directory]", 'light_blue') 243 | 244 | # if isFromGoLoud: 245 | # log.info(f"{dir_text}\\\\{self.server}\\{share}\\{sub_name}") 246 | # self.handle_download_error(share, sub_name, e, True) 247 | 248 | # else: 249 | # self.handle_download_error(share, sub_name, e, False) 250 | 251 | # elif str(err).find("ACCESS_DENIED"): 252 | # continue 253 | -------------------------------------------------------------------------------- /snaffcore/utilities.py: -------------------------------------------------------------------------------- 1 | import os 2 | # import magic 3 | import string 4 | import random 5 | import logging 6 | import ipaddress 7 | from pathlib import Path 8 | from ldap3 import ALL_ATTRIBUTES, Server, Connection, DSA, ALL, SUBTREE 9 | 10 | 11 | # RT: Stolen from manspider - https://github.com/blacklanternsecurity/MANSPIDER 12 | 13 | log = logging.getLogger('snafflepy.util') 14 | 15 | 16 | def str_to_list(s): 17 | 18 | l = set() 19 | # try to open as file 20 | try: 21 | with open(s) as f: 22 | lines = set([l.strip() for l in f.readlines()]) 23 | for line in lines: 24 | if line: 25 | l.add(line) 26 | except OSError: 27 | l.add(s) 28 | 29 | return list(l) 30 | 31 | 32 | def make_targets(s): 33 | ''' 34 | Accepts filename, CIDR, IP, hostname, file, or folder 35 | Returns list of targets as IPs, hostnames, or Path() objects 36 | ''' 37 | 38 | targets = set() 39 | 40 | p = Path(s) 41 | if p.is_dir(): 42 | targets.add(p) 43 | 44 | else: 45 | for i in str_to_list(s): 46 | try: 47 | for ip in ipaddress.ip_network(i, strict=False): 48 | targets.add(str(ip)) 49 | except ValueError: 50 | targets.add(i) 51 | 52 | return list(targets) 53 | 54 | 55 | def human_to_int(h): 56 | ''' 57 | converts human-readable number to integer 58 | e.g. 1K --> 1000 59 | ''' 60 | 61 | if type(h) == int: 62 | return h 63 | 64 | units = {'': 1, 'K': 1024, 'M': 1024**2, 'G': 1024**3, 'T': 1024**4} 65 | 66 | try: 67 | h = h.upper().strip() 68 | i = float(''.join(c for c in h if c in string.digits + '.')) 69 | unit = ''.join([c for c in h if c in units.keys()]) 70 | except (ValueError, KeyError): 71 | raise ValueError(f'Invalid filesize "{h}"') 72 | 73 | return int(i * units[unit]) 74 | 75 | 76 | def bytes_to_human(_bytes): 77 | ''' 78 | converts bytes to human-readable filesize 79 | e.g. 1024 --> 1KB 80 | ''' 81 | 82 | sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB'] 83 | units = {} 84 | count = 0 85 | for size in sizes: 86 | units[size] = pow(1024, count) 87 | count += 1 88 | 89 | for size in sizes: 90 | if abs(_bytes) < 1024.0: 91 | if size == sizes[0]: 92 | _bytes = str(int(_bytes)) 93 | else: 94 | _bytes = '{:.2f}'.format(_bytes) 95 | return '{}{}'.format(_bytes, size) 96 | _bytes /= 1024 97 | 98 | raise ValueError 99 | 100 | 101 | ''' 102 | def better_decode(b): 103 | 104 | # detect encoding with libmagic 105 | m = magic.Magic(mime_encoding=True) 106 | encoding = m.from_buffer(b) 107 | 108 | try: 109 | return b.decode(encoding) 110 | except Exception: 111 | return str(b)[2:-1] 112 | ''' 113 | 114 | 115 | def random_string(length): 116 | 117 | return ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for i in range(length)) 118 | 119 | 120 | def list_files(path): 121 | 122 | path = Path(path) 123 | 124 | if path.is_file() and not path.is_symlink(): 125 | yield path 126 | 127 | elif path.is_dir(): 128 | for dir_name, dirnames, filenames in os.walk(path): 129 | for file in filenames: 130 | file = Path(dir_name) / file 131 | if file.is_file() and not file.is_symlink(): 132 | yield file 133 | 134 | 135 | def rmdir(directory): 136 | ''' 137 | Recursively remove directory 138 | ''' 139 | directory = Path(directory) 140 | for item in directory.iterdir(): 141 | if item.is_dir(): 142 | rmdir(item) 143 | else: 144 | item.unlink() 145 | directory.rmdir() 146 | 147 | def get_domain_dn(domain): 148 | base_dn = '' 149 | domain_parts = domain.split('.') 150 | for i in domain_parts: 151 | base_dn += 'DC=%s,' % i 152 | base_dn = base_dn[:-1] 153 | return base_dn 154 | 155 | def get_domain(target): 156 | 157 | log.debug("Domain not provided, retrieving automatically.") 158 | s = Server(target, get_info=ALL) 159 | c = Connection(s) 160 | if not c.bind(): 161 | log.error("Could not get domain automatically") 162 | return "" 163 | 164 | else: 165 | try: 166 | domain = str(s.info.other["ldapServiceName"][0].split("@")[1]).lower() 167 | 168 | except Exception as e: 169 | log.error("Could not get domain automatically") 170 | domain = "" 171 | 172 | c.unbind() 173 | log.debug(f"Domain:{domain}") 174 | return domain -------------------------------------------------------------------------------- /snaffler.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import logging 4 | import os 5 | 6 | from snaffcore.go_snaffle import * 7 | from snaffcore.utilities import * 8 | from snaffcore.logger import * 9 | 10 | log = logging.getLogger('snafflepy') 11 | log.setLevel(logging.INFO) 12 | 13 | 14 | def parse_arguments(): 15 | syntax_error = False 16 | print("SnafflePy by @robert-todora") 17 | 18 | parser = argparse.ArgumentParser( 19 | add_help=True, prog='snaffler.py', description='A "port" of Snaffler in python') 20 | parser.add_argument("targets", nargs='+', type=make_targets, 21 | 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.") 22 | parser.add_argument("-u", "--username", 23 | type=str, help="domain username") 24 | parser.add_argument("-p", "--password", 25 | type=str, help="password for domain user") 26 | parser.add_argument("-d", "--domain", 27 | default="", help="FQDN domain to authenticate to, if this option is not provided, SnafflePy will attempt to automatically discover the domain for you") 28 | parser.add_argument("-H", "--hash", 29 | default="", help="NT hash for authentication") 30 | parser.add_argument("-v", "--verbose", 31 | action='store_true', help="Show more info") 32 | parser.add_argument("--go-loud", action='store_true', 33 | 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") 34 | 35 | parser.add_argument("-m", "--max-file-snaffle", metavar="size", type=int, default=10000, help="Max filesize to snaffle in bytes (any files over this size will be dropped)") 36 | # TODO 37 | # parser.add_argument("-i", "--no-share-discovery", action='store_true', 38 | # help="Disables share discovery (more stealthy)") 39 | parser.add_argument("-n", "--disable-computer-discovery", action='store_true', 40 | help="Disable computer discovery, requires a list of hosts to do discovery on") 41 | 42 | parser.add_argument("--no-download", action='store_true', help="Don't download files, just print found file names to stdout - this can only show the top level of files from the share and is unable to recurse into subdirectories.") 43 | 44 | try: 45 | if len(sys.argv) <= 1: 46 | parser.print_help() 47 | sys.exit(1) 48 | 49 | except argparse.ArgumentError as e: 50 | syntax_error = True 51 | log.error(e) 52 | log.error('Check your syntax') 53 | 54 | finally: 55 | if syntax_error: 56 | parser.print_help() 57 | sys.exit(2) 58 | else: 59 | options = parser.parse_args() 60 | if options.verbose: 61 | log.setLevel('DEBUG') 62 | 63 | targets = set() 64 | [[targets.add(t) for t in g] for g in options.targets] 65 | options.targets = list(targets) 66 | 67 | if len(options.targets) > 1 and not options.disable_computer_discovery: 68 | log.error("If you have more than one target, then the -n option must be specified.") 69 | sys.exit(2) 70 | return options 71 | 72 | 73 | def print_banner(): 74 | print(r''' 75 | O~~ ~~ O~~ O~~ O~~ O~~~~~~~ 76 | O~~ O~~ O~ O~ O~~ O~~ O~~ 77 | O~~ O~~ O~~ O~~ O~O~ O~O~O~ O~ O~~ O~~ O~~ O~~O~~ O~~ 78 | O~~ O~~ O~~ O~~ O~~ O~~ O~~ O~~ O~ O~~ O~~~~~~~ O~~ O~~ 79 | O~~ O~~ O~~O~~ O~~ O~~ O~~ O~~O~~~~~ O~~O~~ O~~~ 80 | O~~ O~~ O~~ O~~O~~ O~~ O~~ O~~ O~~O~ O~~ O~~ 81 | O~~ ~~ O~~~ O~~ O~~ O~~~ O~~ O~~ O~~~ O~~~~ O~~ O~~ 82 | O~~ ''') 83 | 84 | print("") 85 | print("") 86 | 87 | 88 | def main(): 89 | print_banner() 90 | snaffle_options = parse_arguments() 91 | begin_snaffle(snaffle_options) 92 | 93 | print("\nI snaffled 'til the snafflin was done") 94 | print("View log file at ~/.snafflepy/logs/") 95 | print("Files snaffled from targets are available in /remotefiles/") 96 | sys.exit() 97 | 98 | 99 | if __name__ == '__main__': 100 | main() 101 | --------------------------------------------------------------------------------