
ペネトレーションテスターがおいしいキャンディを見つけるためのツール、by @l0ss および @Sh3r4 ( Twitter: @/mikeloss および @/sh3r4_hax )

Snafflerはペネトレーションテスターやレッドチーム向けのツールで、膨大で退屈な干し草の山(巨大なWindows/AD環境)の中からおいしいキャンディの針(主に認証情報ですが、柔軟に使えます)を見つけるのに役立ちます。
他の目的で使う人にも便利かもしれませんが、これは明示的に「監査」ツールとして設計されたものではありません。
はあ、わかったよ。でも結果については責任取らないからな。俺たちはお前のためにこれら全部を書いたんだが、まあいい。怒ってないよ、ただがっかりしてるだけだ。
snaffler.exe -s -o snaffler.log
大雑把に言えば - Active DirectoryからWindowsコンピュータのリストを取得し、それらすべてにSnafflerの触手を伸ばして、どのコンピュータがファイル共有を持っていて、それを読み取れるかを調べます。
そしてさらに触手を伸ばして、それらの共有内の全ファイルを列挙し、機械のための学習された人工的知能(LEARNED ARTIFACTUAL INTELLIGENCE for MACHINES)を使って、あなたのような汚いハッカーが欲しがるファイルを特定します。
実際には機械学習なんて何もしていません。なぜなら、それを正しくやるには学習データが必要で、それには膨大な時間がかかるからです。その代わり、すべてのまともな「ML」プロジェクトと同様に、大量のif文と正規表現を使っているだけです。
こんな感じです!
もし「ドメイン参加済みのマシンで、ドメインユーザーのコンテキストでEXEをそのまま実行するだけ」(Grouper2で人々に指示された通りに、verbose/debugスイッチを全部オンにして実行し、何百メガバイトものスタックトレースを吐き出させた直後に)なら、基本的には何も起きません。これがREADMEを読まない人へのいたずらTMというわけです。なぜなら私たちはモンスターだからです。
でも…正しい呪文を追加すれば、前述のL.A.I.M.が有効になり、キャンディが見つかるファイルパスが出力されます。
重要な呪文は次のとおりです:
-o 結果をファイルに出力します。-sを使わない場合はこれが必要でしょう。例:-o C:\users\thing\snaffler.log
-s 結果が見つかり次第、標準出力に出力します。-oを使わない場合はこれが必要でしょう。
-v 詳細レベルを制御します。オプションはTrace(最も詳細)、Degub(やや詳細、gubs少なめ)、Info(やや詳細、デフォルト)、Data(結果のみ)です。例:-v debug
-m 検出したファイルをSnafflerが自動的にコピー(または「Snaffle」)するための出力ディレクトリを有効にして割り当てます。
-l Snaffleするファイルの最大サイズ(バイト単位)。デフォルトは10000000で、約10MBです。
-i コンピュータと共有の探索を無効にし、ファイル探索を実行するディレクトリへのパスを要求します。
-n コンピュータの探索を無効にし、共有とファイルの探索を行うホストのカンマ区切りリストまたは入力ファイルを受け取ります。ファイルを指定する場合、入力はパスである必要があるため、例としてC:\targets.txtまたは.\targets.txtを使用します。
-y 出力をTSV形式にします。
-b あまり興味深くないものを検出するLAIMルールをスキップし、0から3の数値で調整します。
-f DFS(分散ファイルシステム)を介したファイル共有のみを検出するようにSnafflerを制限します。これはデフォルトよりもかなりこっそりと動作し、多くの組織で最大のファイル共有をカバーします。
-a ファイルの列挙をスキップし、対象ホスト上のリスト可能な共有のリストのみを出力します。
-u ADからアカウント名のリストを取得し、最も興味深そうなものを選び、それらを検索ルールで使用するようにSnafflerに指示します。
-d ファイルを探すための共有を探すためのコンピュータを検索するドメイン。簡単ですね。
-c ドメインコンピュータのリストを照会するドメインコントローラ。
-r 内部で興味深い文字列を検索するファイルの最大サイズ(バイト単位)。デフォルトは500kです。
-j ファイル内で見つかった文字列の前後に表示するコンテキストのバイト数。例:-j 200
-z 上記のすべてとそれ以上を定義する設定ファイルへのパス。詳細は以下を参照。-z generateを指定すると、.\default.tomlというサンプル設定ファイルが生成されます。
-t 出力したいログのタイプ。現在サポートされているオプションはplainとJSONです。デフォルトはplainです。
-x 使用するスレッドの最大数。4未満に設定すると壊れます。
-p .toml形式のルールファイルが格納されたディレクトリへのパス。Snafflerはデフォルトのルールセットの代わりにこれらをすべて読み込みます。
以下の注釈付きの例が役立つでしょう:
このログエントリは左から右に大まかに解釈すると:
...そして行の残り(灰色部分)は、一致があったファイルのコンテキストの小さなスニペットです。
この例ではASP.NETのvalidationKeyとdecryptionKeyの値が見つかりました。これは、何らかのデシリアライゼーションのハッキングを介してWebアプリにRCEできる可能性があることを意味します。やったー!
注:このスクリーンショットを撮った後、Sh3r4が各行の先頭に現在のユーザーとホスト名を付加する機能を追加しました。でもスクリーンショットを作り直したくありません。
各L.A.I.M.の魔法のファイル発見方法は次のようなことを行います:
正確なファイル拡張子の一致による検索。つまり、関連するワードリストに一致する拡張子を持つファイルがすべて返されます。これは、ほとんどの場合キャンディを含むファイル拡張子を対象としています(例:.kdbx、.vmdk、.ppkなど)。
(大文字小文字を区別しない)正確なファイル名の一致による検索。これは、ほとんどの場合キャンディを含むファイル名を対象としています(例:id_rsa、shadow、NTDS.DITなど)。
正確なファイル拡張子の一致による検索(さらに別のワードリスト)の後に、一致したファイルの内容を特定のキーワード(さらに別のワードリスト)で「grep」する検索。これは、時々キャンディを含むが、ふるいにかけるべき大量の無駄がある可能性が高いファイル拡張子を対象としています。例えば、web.configは時々データベースの資格情報を含みますが、退屈なIIS設定のガラクタやパスワードを含まないこともよくあります。これは(例えば).configで終わるものを見つけ、その中をconnectionString、password、PRIVATE KEYなどの文字列でgrepします。
部分的なファイル名の一致による検索(またまたワードリスト)。これは主にJeff's Password File 2019 (Copy).docxやなどを見つけるためのもので、ファイル名に、、、、などの部分文字列が含まれるものを一致させます。
Snafflerは「分類器」のシステムを使用します。各分類器は共有、フォルダ、ファイル、ファイル内容を検査し、一部の項目を下流の次の分類器に渡し、その他を破棄します。各分類器はルールのセットを使用して、分類した項目をどうするかを決定します。
これらのルールは非常に単純なもの(例:「ファイルの拡張子が.kdbxなら報告する」や、「パスにwindows\sxsが含まれている場合は、そのパス内のサブディレクトリやファイルの調査を停止する」)から、正規表現を使用して比較的高度なパターンマッチングを可能にするものまであります。これはファイル内容の検査時に特に有用ですが、パフォーマンスに大きな影響を与える正規表現には注意が必要です。大規模な環境ではこれらのルールが文字通り数百万回チェックされる可能性があるため、小さなパフォーマンス問題が大幅に増幅される可能性があります。
本当の力は、Snafflerが複数のルールを連鎖させ、さらに分岐する連鎖を作成できる点にあります。これにより、「安価な」ルール(ファイル名や拡張子のチェックなど)を使用して、「高価な」ルール(ファイル内容に対する正規表現の実行、証明書の解析による秘密鍵の有無の確認など)を使用するタイミングを決定できます。これにより、Snafflerは必要な場合にファイルの深い検査を実現しつつ、C#のような高レベル言語で書かれたツールとしては驚くほど高速になります。
例えば、非常に単純なルールセットは次のものを含むかもしれません:
.dmpファイル拡張子を持つファイルを見つけてSnaffleするルール.ps1ファイル拡張子を持つファイルを探し、一致したファイルを2番目と3番目の両方のルールに送る。.batや.cmdファイルに見られるようなcmd.exeコマンド内のハードコードされた資格情報を見つけるために設計された正規表現を使用してファイル内部を調べる。これらはPowerShellスクリプト内でも一般的に使用されるため。このアプローチにより、比較的管理しやすく読みやすいルールセットを維持でき、エンドユーザー(あなた)がデフォルトをカスタマイズしたり、独自のルールセットを開発したりするのも簡単になります。
その通りです。確かに大変でした。
Snafflerにはデフォルトのルールセットが.exeに組み込まれています。./Snaffler/SnaffRules/DefaultRulesで確認できます。
問題ありません、変わり者のあなた。2つのオプションがあります。
DefaultRulesディレクトリ内のルールを編集または置き換え、新しいSnafflerをビルドします。そのディレクトリ内の.tomlファイルはリソースとして.exeに組み込まれ、他のルールを指定しない限り実行時に読み込まれます。-p .\path\to\rulesを指定してSnafflerを実行します。Snafflerはそのディレクトリ内のすべての.tomlファイルを解析し、結果のルールセットを使用します。これらをすべて1つの大きな.tomlファイルにまとめても動作します。以下に、理解を助けるための注釈付きの例を示します。これが非常に難しいと感じたら、私たちのルールを使用すれば、良いものを見つけられるでしょう。
これは、特定の名前を持つディレクトリ以下のすべてのファイルとサブディレクトリを無視するルールの例です。```toml [[ClassifierRules]] EnumerationScope = "DirectoryEnumeration" # This defines which phase of the discovery process we're going to apply the rule. # In this case, we're looking at directories. # Valid values include ShareEnumeration, DirectoryEnumeration, FileEnumeration, ContentsEnumeration RuleName = "DiscardLargeFalsePosDirs" # This can be whatever you want. We've been following a rough naming scheme, but you can call it "Stinky" if you want. ¯_(ツ)_/¯ MatchAction = "Discard"# What to do with things that match the rule. In this case, we want to discard anything that matches this rule. # Valid options include: Snaffle (keep), Discard, Relay (example of this below), and CheckForKeys (example below) Description = "File paths that will be skipped entirely." # Not used in the code, just a place for notes really. MatchLocation = "FilePath" # What part of the file/dir/share to look at to check for a match. In this case we're looking at the whole path. # Valid options include: ShareName, FilePath, FileName, FileExtension, FileContentAsString, FileContentAsBytes, # although obviously not all of these will apply in all EnumerationScopes. WordListType = "Contains" # What matching logic to apply, valid options are: Exact, Contains, EndsWith, StartsWith, or Regex. # Under the hood these all get turned into regexen one way or another. MatchLength = 0 WordList = [
"\\\\puppet\\\\share\\\\doc",
"\\\\lib\\\\ruby",
"\\\\lib\\\\site-packages",
"\\\\usr\\\\share\\\\doc",
"node_modules",
"vendor\\\\bundle",
"vendor\\\\cache",
"\\\\doc\\\\openssl",
"Anaconda3\\\\Lib\\\\test",
"WindowsPowerShell\\\\Modules",
"Python27\\\\Lib"
] Triage = "Green" # If we find a match, what severity rating should we give it. Valid values are Black, Red, Yellow, Green. This value is ignored for Discard MatchActions.
一方、このルールはファイル拡張子を確認し、好ましくないものはすぐに破棄します。この場合、主にフォント、画像、CSSなどを捨てています。```toml
[[ClassifierRules]]
EnumerationScope = "FileEnumeration" # We're looking at the actual files, not the shares or dirs or whatever.
RuleName = "DiscardExtExact" # just a name
MatchAction = "Discard" # We're discarding these
MatchLocation = "FileExtension" # This time we're only looking at the file extension part of the file's name.
WordListType = "Exact" # and we only want exact matches.
WordList = [".bmp", ".eps", ".gif", ".ico", ".jfi", ".jfif", ".jif", ".jpe", ".jpeg", ".jpg", ".png", ".psd", ".svg", ".tif", ".tiff", ".webp", ".xcf", ".ttf", ".otf", ".lock", ".css", ".less"] # list of file extensions.
ここでは、私たちが気に入っていて保持したいものに関する非常にシンプルなルールの例を示します。```toml [[ClassifierRules]] EnumerationScope = "FileEnumeration" # Still looking at files RuleName = "KeepExtExactBlack" # Just a name MatchAction = "Snaffle" # This time we are 'snaffling' these. This usually just means send it to the output, # but if you turn on the appropriate option it will also grab a copy. MatchLocation = "FileExtension" # We're looking at file extensions again WordListType = "Exact" # With Exact Matches WordList = [".kdbx", ".kdb", ".ppk", ".vmdk", ".vhdx", ".ova", ".ovf", ".psafe3", ".cscfg", ".kwallet", ".tblk", ".ovpn", ".mdf", ".sdf", ".sqldump"] # and a bunch of fun file extensions. Triage = "Black" # these are all big wins if we find them, so we're giving them the most severe rating.
これは基本的に同じですが、ファイル名全体を見ているのです。簡単!```toml
[[ClassifierRules]]
EnumerationScope = "FileEnumeration"
RuleName = "KeepFilenameExactBlack"
MatchAction = "Snaffle"
MatchLocation = "FileName"
WordListType = "Exact"
WordList = ["id_rsa", "id_dsa", "NTDS.DIT", "shadow", "pwd.db", "passwd"]
Triage = "Black"
これはちょっと気の利いたものです。見てみてください...```toml [[ClassifierRules]] EnumerationScope = "FileEnumeration" # we're looking for files... RuleName = "KeepCertContainsPrivKeyRed" MatchLocation = "FileExtension" # specifically, ones with certain file extensions... WordListType = "Exact" WordList = [".der", ".pfx"] # specifically these ones... MatchAction = "CheckForKeys" # and any that we find, we're going to parse them as x509 certs, and see if the file includes a private key! Triage = "Red" # cert files aren't very sexy, and you'll get huge numbers of them in most wintel environments, but this check gives us a way better SNR!
さて、ここからが本領発揮です。ここでは、連鎖する2つのルールがあります。
最初のルールに一致する拡張子を持つファイルは、2番目のルールに送られ、そこで特定の単語リストに対して"grep"(つまりString.Contains())で検索されます。
これらは好きなだけ連鎖させることができますが、あまりに入れ子にしすぎるとパフォーマンスの問題が発生し始めるでしょう。```toml
[[ClassifierRules]]
EnumerationScope = "FileEnumeration" # this one looks at files...
RuleName = "ConfigGrepExtExact"
MatchLocation = "FileExtension" # specifically the extensions...
WordListType = "Exact"
WordList = [".yaml", ".xml", ".json", ".config", ".ini", ".inf", ".cnf", ".conf"] # these ones.
MatchAction = "Relay" # Then any files that match are handed downstream...
RelayTargets = ["KeepConfigGrepContainsRed"] # To the rule with this RuleName! This can also be an array of RuleNames if you want to get real wild and start writing branching rulesets.
[[ClassifierRules]]
RuleName = "KeepConfigGrepContainsRed" # Anyway, this is the target rule. Following a naming convention really helps to make sure you're using the right targets.
EnumerationScope = "ContentsEnumeration" # this one looks at file content!
MatchAction = "Snaffle" # it keeps files that match
MatchLocation = "FileContentAsString" # it's looking at the contents as a string (rather than a byte array)
WordListType = "Contains" # it's using simple matching
WordList = ["password=", " connectionString=\"", "sqlConnectionString=\"", "validationKey=", "decryptionKey=", "NVRAM config last updated"]
Triage = "Red"
これでアイデアが伝われば幸いです。いくつかのデフォルトルールを試してみて、慣れるまで調整することをお勧めします。
多くの人が、Word文書やPDF、.emlなど、単なるプレーンテキストではないファイル形式の中身を確認できる機能を求めていました。残念ながら、その機能を実装するための最も簡単なライブラリを使用すると、Snaffler.exeの最終的なファイルサイズが約1200%も増加してしまい、これは、使用できるファイルサイズに上限がある一般的なインメモリ実行手法の多くにとって問題となりました。
解決策はUltraSnafflerです。これは単に、必要なライブラリと関連コードを有効にするための2つ目の.slnファイルです。UltraSnaffler.slnをビルドすると、UltraSnafflerが得られます。
警告: Snafflerのデフォルトルールには、Office文書やPDFの中身を確認するものは含まれていません。というのも、一般的な企業環境で実行を完了するのに何年もかからないようなルールを作成するのは非常に困難だったからです。注意: これらの文書の中身を確認するのは、従来のテキストファイルを確認するよりもはるかに遅く、一般的な環境には価値の低いOffice文書やPDFが大量に存在します。
これは実際かなり便利だと思います。
Snafflerのコマンドラインの末尾に-z generateを追加すると、Snafflerは設定オブジェクト(引数で設定した設定のあらゆる側面を含む)を.toml設定ファイルにシリアライズし、それを手動で簡単に編集(あるいは編集せずに)して、後で自由に再利用できます。
例えば、次のように実行すると:
Snaffler.exe -s -o C:\mydir\snaffler.log -v trace -i \\host.lol.domain\share -p C:\users\someguy\myrules -z generate
Snafflerはあなたの多くの引数を解析し、それらを設定オブジェクトに変換し、その設定オブジェクトを以下の.toml設定ファイルにシリアライズします:```toml
PathTargets = ["\\host.lol.domain\share"]
ComputerTargetsLdapFilter = "(objectClass=computer)"
ScanSysvol = true
ScanNetlogon = true
ScanFoundShares = true
InterestLevel = 0
DfsOnly = false
DfsShareDiscovery = false
DfsNamespacePaths = []
CurrentUser = "l0sslab\l0ss"
RuleDir = "C:\users\someguy\myrules"
MaxThreads = 60
ShareThreads = 20
TreeThreads = 20
FileThreads = 20
MaxFileQueue = 200000
MaxTreeQueue = 0
MaxShareQueue = 0
LogToFile = true
LogFilePath = "C:\mydir\snaffler.log"
LogType = "Plain"
LogTSV = false
Separator = 32
LogToConsole = true
LogLevelString = "trace"
ShareFinderEnabled = false
LogDeniedShares = false
DomainUserRules = false
DomainUserMinLen = 6
DomainUserNameFormats = ["sAMAccountName"]
DomainUserMatchStrings = ["sql", "svc", "service", "backup", "ccm", "scom", "opsmgr", "adm", "adcs", "MSOL", "adsync", "thycotic", "secretserver", "cyberark", "configmgr"]
DomainUsersWordlistRules = ["KeepConfigRegexRed"]
MaxSizeToGrep = 1000000
Snaffle = false
MaxSizeToSnaffle = 10000000
MatchContextBytes = 200
引数で何も指定していない項目がたくさんあることに気付くかもしれません。それらはデフォルトの設定項目であり、一部はソースコードや設定ファイルでのみ簡単に編集できます。多くの場合、それらのために引数を追加する価値はないと判断したためです。
## これってクソじゃない? 改善の予定はある?
いや、クソなのはお前だ。
でも、改善の予定はある。
以下のような改善を進めるつもりだ:
- アーカイブファイルの解析を追加し、理想的にはそれらをただの別のディレクトリとして扱い、良いものを探して歩き回れるようにする。
- ルールと正規表現を改良し続ける。**単語リストにもっと単語を! `string[]` を `string` 玉座に捧げよ!**

## コードをパクったのは誰?
共有列挙の部分は SharpShares から拝借した(わかるだろ?)。これは非常に役立つ Dwight Hohnstein 氏が書いたものだ。(https://github.com/djhohnstein/SharpShares/)
Dwight の GitHub プロフィールは、あたかもホームセンターの奥にある素晴らしい通路のようなもので、そこには「ああ、これは本番で試すのが待ちきれない」と思わせるツールがたくさんある。ぜひチェックしてみてほしい。
コード自体は拝借していないが(主に Ruby だからな)、`plunder2` (http://joshstone.us/plunder2/) からは多くの素晴らしいアイデアを盗用した。
単語リストも、trufflehog、shhgit、gitrobber、graudit といった類似ツールのものから厳選した。
## OPSEC 的に安全なのか?(何を意味しているにせよ)
プッ、そんなわけない。めちゃくちゃ騒がしい。
こう言っておこう… もし BloodHound をデフォルトモードで自信を持って実行できるような環境なら、まあ、そうだな… 本当にステルスだ。
## お前、これレッドチームの仕事で使ったんじゃなかったっけ?
*はあ*
わかった、本当のことを言おう。
デフォルトモードでは、Snaffler は多くの点で SharpHound と非常によく似ている。AD に大量の LDAP トラフィックを送り、その後ドメイン内のすべての Windows マシンに SMB で話しかけようとする。こういう振る舞いは、体制が多少なりとも整っている組織ではほぼ確実にバレる。
だがしかし…
Snaffler のよりターゲットを絞ったオプション(特に `-i`)は、検知を誘発する可能性が*かなり*低い。
私は特に、新しく侵害したサーバーやワークステーションで `Snaffler.exe -s -i C:\` を実行するのが好きだが、今のところこの挙動が検知されたことはない。
まだな。
## どうやって助けたり、助けを得たりすればいい?
Slack で議論したい場合は、BloodHound Slack で @l0ss または @Sh3r4 に ping を送ってください。Slack には https://bloodhoundgang.herokuapp.com/ から参加できます。また、#snaffler チャンネルで貢献者のグループとチャットすることもできます。
Twitter でも ping を送れます - @mikeloss と @sh3r4_hax
それ以外の場合は、Issue を立ててください。何とかします。
Privileged Access Management System Design - As-Built.docxpasswhandoversecretsecureas-built特定の拡張子を持つファイルや、パスに特定の文字列を含むファイルをすべてスキップするスキップリストもあります。