
recon-ng と Maltego に触発された、意見の強い組織中心の OSINT フットプリンティング
お知らせ
このプロジェクトは部分的にしか完成しておらず、以下のブログ記事で説明されている機能の多くはまだ実装されていません:https://penafieljlm.com/2017/07/14/inquisitor/
Inquisitorは、オープンソースインテリジェンス(OSINT)ソースを利用して企業や組織に関する情報を収集するためのシンプルなツールです。Maltegoやrecon-ngの動作から強くインスピレーションを受けており、それらのツールの機能の一部をほぼ再実装しつつ、アセットタイプに意見ベースの意味論の追加レイヤーを重ねることで、使いやすいワークフローを実現しています。
Inquisitorの主な機能は次のとおりです。
Inquisitorのコンセプト全体は、ターゲット組織について既にわかっている情報に基づいてオープンソースから情報を抽出するという考えに基づいています。Inquisitorの文脈では、これらは「トランスフォーム」と呼ばれます。また、whoisやインターネットレジストリなどのオープンソースからも取得可能なメタデータに基づいて、既知のアセットから関連情報を即座に取得することもできます。
これらのコンセプトについては、以下のブログ記事で詳しく説明されています:https://penafieljlm.com/2017/07/14/inquisitor/
Inquisitorをインストールするには、リポジトリをクローンし、そのディレクトリに入り、インストールスクリプトを実行します。``` pip install Cython click git clone [email protected]:penafieljlm/inquisitor.git cd inquisitor python setup.py install
## 使用法
Inquisitor には、`scan`、`status`、`classify`、`dump`、`visualize` の5つの基本コマンドがあります。```
usage: inq [-h] {scan,status,classify,dump,visualize} ...
optional arguments:
-h, --help show this help message and exit
command:
{scan,status,classify,dump,visualize}
The action to perform.
scan Search OSINT sources for intelligence based on known
assets belonging to the target.
status Prints out the current status of the specified
intelligence database.
classify Classifies an existing asset as either belonging or
not belonging to the target. Adds a new asset with the
specified classification if none is present.
dump Dumps the contents of the database into a JSON file
visualize Create a D3.js visualization based on the contents of
the specified intelligence database.
スキャンモードでは、ツールはインテリジェンスデータベース内のすべてのアセットに対して利用可能なすべてのトランスフォームを実行します。以下に示すさまざまなOSINTソースのAPIキーを作成し、スクリプトに提供してください。そうしないと、それらのソースを使用するトランスフォームがスキップされます。また、データベースに所有アセットがないと変換するものが何もないため、最初にclassifyコマンドを使用していくつかの既知の所有ターゲットアセットをインテリジェンスデータベースにシードしてください。```
usage: inq scan [-h] [--google-dev-key GOOGLE_DEV_KEY]
[--google-cse-id GOOGLE_CSE_ID]
[--google-limit GOOGLE_LIMIT]
[--shodan-api-key SHODAN_API_KEY]
[--shodan-limit SHODAN_LIMIT]
DATABASE
positional arguments: DATABASE The path to the intelligence database to use. If specified file does not exist, a new one will be created.
optional arguments: -h, --help show this help message and exit --google-dev-key GOOGLE_DEV_KEY Specifies the developer key to use to query Google Custom Search. Visit the Google APIs Console (http://code.google.com/apis/console) to get an API key. If notspecified, the script will simply skip asset transforms that involve Google Search. --google-cse-id GOOGLE_CSE_ID Specifies the custom search engine to query. Visit the Google Custom Search Console (https://cse.google.com/cse/all) to create your own Google Custom Search Engine. If not specified, the script will simply skip asset transforms that involve Google Search. --google-limit GOOGLE_LIMIT The number of pages to limit Google Search to. This is to avoid exhausting your daily quota. --shodan-api-key SHODAN_API_KEY Specifies the API key to use to query Shodan. Log into your Shodan account (https://www.shodan.io/) and look at the top right corner of the page in order to view your API key. If not specified, the script will simply skip asset transforms that involve Shodan. --shodan-limit SHODAN_LIMIT The number of pages to limit Shodan Search to. This is to avoid exhausting your daily quota.
### ステータス
ステータスモードでは、ツールはスキャンデータベースのステータスの簡単な要約を出力します。```
usage: inq status [-h] [-s] DATABASE
positional arguments:
DATABASE The path to the intelligence database to use. If specified
file does not exist, a new one will be created.
optional arguments:
-h, --help show this help message and exit
-s, --strong Indicates if the status will be based on the strong ownership
classification.
classify モードでは、手動でアセットを追加したり、Intelligence Database 内の既存のアセットを再分類することができます。このコマンドを使用して、既知の所有ターゲットアセットで Intelligence Database をシードする必要があります。``` usage: inq classify [-h] [-ar REGISTRANT [REGISTRANT ...]] [-ur REGISTRANT [REGISTRANT ...]] [-rr REGISTRANT [REGISTRANT ...]] [-ab BLOCK [BLOCK ...]] [-ub BLOCK [BLOCK ...]] [-rb BLOCK [BLOCK ...]] [-ah HOST [HOST ...]] [-uh HOST [HOST ...]] [-rh HOST [HOST ...]] [-ae EMAIL [EMAIL ...]] [-ue EMAIL [EMAIL ...]] [-re EMAIL [EMAIL ...]] [-al LINKEDIN [LINKEDIN ...]] [-ul LINKEDIN [LINKEDIN ...]] [-rl LINKEDIN [LINKEDIN ...]] DATABASE
positional arguments: DATABASE The path to the intelligence database to use. If specified file does not exist, a new one will be created.
optional arguments: -h, --help show this help message and exit -ar REGISTRANT [REGISTRANT ...], --accept-registrant REGISTRANT [REGISTRANT ...] Specifies a registrant to classify as accepted. -ur REGISTRANT [REGISTRANT ...], --unmark-registrant REGISTRANT [REGISTRANT ...] Specifies a registrant to classify as unmarked. -rr REGISTRANT [REGISTRANT ...], --reject-registrant REGISTRANT [REGISTRANT ...] Specifies a registrant to classify as rejected. -ab BLOCK [BLOCK ...], --accept-block BLOCK [BLOCK ...] Specifies a block to classify as accepted. -ub BLOCK [BLOCK ...], --unmark-block BLOCK [BLOCK ...] Specifies a block to classify as unmarked. -rb BLOCK [BLOCK ...], --reject-block BLOCK [BLOCK ...] Specifies a block to classify as rejected. -ah HOST [HOST ...], --accept-host HOST [HOST ...] Specifies a host to classify as accepted. -uh HOST [HOST ...], --unmark-host HOST [HOST ...] Specifies a host to classify as unmarked. -rh HOST [HOST ...], --reject-host HOST [HOST ...] Specifies a host to classify as rejected. -ae EMAIL [EMAIL ...], --accept-email EMAIL [EMAIL ...] Specifies a email to classify as accepted. -ue EMAIL [EMAIL ...], --unmark-email EMAIL [EMAIL ...] Specifies a email to classify as unmarked. -re EMAIL [EMAIL ...], --reject-email EMAIL [EMAIL ...] Specifies a email to classify as rejected. -al LINKEDIN [LINKEDIN ...], --accept-linkedin LINKEDIN [LINKEDIN ...] Specifies a LinkedIn Account to classify as accepted. -ul LINKEDIN [LINKEDIN ...], --unmark-linkedin LINKEDIN [LINKEDIN ...] Specifies a LinkedIn Account to classify as unmarked. -rl LINKEDIN [LINKEDIN ...], --reject-linkedin LINKEDIN [LINKEDIN ...] Specifies a LinkedIn Account to classify as rejected.
### Dump
ダンプモードでは、インテリジェンスデータベースの内容を人間が読めるJSONファイルにダンプできます。```
usage: inq dump [-h] [-j FILE] [-a] DATABASE
positional arguments:
DATABASE The path to the intelligence database to use. If
specified file does not exist, a new one will be
created.
optional arguments:
-h, --help show this help message and exit
-j FILE, --json FILE The path to dump the JSON file to. Overwrites existing
files.
-a, --all Include rejected assets in dump.
可視化モードでは、Intelligence Repository の階層的な可視化を取得できます。``` usage: inq visualize [-h] [-l] DATABASE
positional arguments: DATABASE The path to the intelligence database to use. If specified file does not exist, a new one will be created.
optional arguments: -h, --help show this help message and exit -l, --last Simply open the last visualization generated instead of creating a new one.
## ワークフロー
これでInquisitorの基本機能を理解したところで、*実際の*使い方を学びましょう。Inquisitorは以下の手順を念頭に置いて作成されています。
### シーディング
このステップでは、インテリジェンスデータベースにはまだ何もデータがありません。どこからか始めなければならないので、ターゲット組織に属することがわかっている資産でデータベースをシードしてください。これを行うには、`classify`コマンドを使用します。
### スキャン
データベースにターゲット組織に属することがわかっている資産が含まれるようになりました。次にスキャンを進めることができます。これを行うには、`scan`コマンドを使用します。
インテリジェンスデータベースで`scan`コマンドを呼び出すと、Inquisitorは`accepted`として分類された資産の`transform`メソッドを実行します。スキャンが完了すると、ターゲット組織に潜在的に属する可能性のあるさらなる資産が得られます。
新しい資産が得られなかった場合は、インテリジェンスデータベースに新しい情報をシードするか、レポーティングステップに進んでプロセスを完了することができます。
### 分類
Inquisitorは自動的に資産を分類しますが、実際にはターゲット組織に属するいくつかの資産を見逃す可能性があります。
このような場合、データベースの内容を確認し、手動で資産を分類する必要があります。通常、**Registrant**(登録者)資産に注意を払う必要があります。この資産タイプでは所有権を自動的に判断する方法がないためです。また、他のほとんどの資産タイプは、ターゲットに属するかどうかを判断するためにRegistrant資産の所有権分類に依存しているため、Registrant資産に注意を払うことが重要です。さらに、そもそもRegistrant資産はそれほど多くないため、それらを選別するのはそれほど難しくありません。
### レポーティング
`visualize`コマンドまたは`dump`コマンドを使用して、ターゲット組織に属する資産の可視化を生成できます。
## デモ
ツールの動作を示すビデオデモを以下のリンクで公開しています:https://drive.google.com/open?id=0B_O70BVu38TRclo5dWRBWkdTTWc
ただし、無料の画面録画ソフトが最大10分までしか録画できないため、scanコマンドの実行を完全に録画することができませんでした。
## 開発
Inquisitorプロジェクトは以下の形式で構成されています:```
.
|-- README.md
|-- inquisitor
| |-- __init__.py
| |-- assets
| | |-- __init__.py
| | |-- block.py
| | |-- email.py
| | |-- host.py
| | |-- linkedin.py
| | `-- registrant.py
| |-- extractors
| | |-- __init__.py
| | `-- emails.py
| `-- sources
| |-- __init__.py
| |-- google_search.py
| `-- shodan_search.py
|-- inq
|-- report
| `-- index.html
|-- setup.py
`-- tests
|-- __init__.py
`-- test_inq.py
これは assets、extractors、sources という3つの主要モジュールから構成されています。メインスクリプトは inq です。
開発者としては、主に新しい種類のアセットをシステムに追加することに関心があるでしょう。そのため、この開発者ガイドは主にその点に焦点を当てています。
実際にアセットクラスを実装する前に、まずインテリジェンスデータベースとの対話方法を理解する必要があります。これは、アセットクラスから関連するアセットを導出する際にデータベースとやり取りするためです。
インテリジェンスデータベースのソースコードは inquisitor/__init__.py ファイルに格納されています。インテリジェンスデータベースの論理ラッパーの実際の名前は IntelligenceRepository と呼ばれています。
アセットクラスからは IntelligenceRepository.get_asset_string 関数を呼び出すだけで済みます。新しいアセットをインテリジェンスデータベースに追加するのは、inq スクリプト内の scan モジュールの責任だからです。この関数は主に、アセットのインスタンスを作成したり、存在する場合はデータベースから取得したりするために使用します。この関数は、アセットクラスの related 関数や transform 関数からアセットを返す際に重要です。新しいアセットオブジェクトの作成は、初期化中にネットワークリソースを使用するものもあるため、コストがかかるからです。```
Function
IntelligenceRepository.get_asset_string(asset_type, identifier, create=False, store=False)
Description
Retrieves the primary key and asset object for the asset with the provided
type and identifier.
Parameters
asset_type: class, required
The type of the asset to retrieve from the Intelligence Database. You
will actually have to pass the class object of the asset type you want
to retrieve.
identifier: any, required
The identifier of the asset to retrieve. Consider the identifier as the
unique attribute of an asset object. As for which attribute is to be
used to identify an asset, it depends on the contents of the OBJECT_ID
variable in the asset module.
create: bool, optional, default=False
When no matching asset object is found, a new one will be created and
returned if this parameter is set to True. The new asset will not
necessarily be stored in the Intelligence Database unless specified
using the "store" parameter. However, I suggest you do not do this as
adding assets to the Intelligence Database is the responsibility of
another module.
store: bool, optional, default=False
When a new asset is created when none is found, the new one will be
stored in the Intelligence Database. As said previously, I suggest that
you do not do this as adding assets to the Intelligence Database is the
responsibility of another module.
Returns
A two-element tuple where the first element is the database primary key of
the element returned, and the second element is the deserialized asset
object retrieved from the database.
None if the asset was not found.
If the asset was not found and the create flag was set to True, the primary
key member of the tuple will be set to None.
### アセット
新しいアセットタイプを作成するには、`inquisitor/assets` ディレクトリ内に新しいファイルを作成し、以下のスケルトンコードを貼り付けてください:```python
import inquisitor.assets
class ASSET_NAMEValidateException(Exception):
pass
def canonicalize(ASSET_IDENTIFIER):
return ASSET_IDENTIFIER
def main_classify_args(parser):
parser.add_argument(
'-aASSET_NAME_LETTER', '--accept-ASSET_NAME',
metavar='ASSET_NAME',
type=canonicalize,
nargs='+',
help='Specifies a ASSET_NAME to classify as accepted.',
dest='ASSET_NAMEs_accepted',
default=list(),
)
parser.add_argument(
'-uASSET_NAME_LETTER', '--unmark-ASSET_NAME',
metavar='ASSET_NAME',
type=canonicalize,
nargs='+',
help='Specifies a ASSET_NAME to classify as unmarked.',
dest='ASSET_NAMEs_unmarked',
default=list(),
)
parser.add_argument(
'-rASSET_NAME_LETTER', '--reject-ASSET_NAME',
metavar='ASSET_NAME',
type=canonicalize,
nargs='+',
help='Specifies a ASSET_NAME to classify as rejected.',
dest='ASSET_NAME_rejected',
default=list(),
)
def main_classify_canonicalize(args):
accepted = set(args.ASSET_NAMEs_accepted)
unmarked = set(args.ASSET_NAMEs_unmarked)
rejected = set(args.ASSET_NAME_rejected)
redundant = set.intersection(accepted, unmarked, rejected)
if redundant:
raise ValueError(
('Conflicting classifications for ASSET_NAMEs '
': {}').format(list(redundant))
)
accepted = set([canonicalize(a) for a in accepted])
unmarked = set([canonicalize(a) for a in unmarked])
rejected = set([canonicalize(a) for a in rejected])
return (accepted, unmarked, rejected)
class ASSET_NAME(inquisitor.assets.Asset):
def __init__(self, ASSET_IDENTIFIER, owned=None):
super(self.__class__, self).__init__(owned=owned)
self.ASSET_IDENTIFIER = canonicalize(ASSET_IDENTIFIER)
# TODO: Perform other initialization actions here
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return self.ASSET_IDENTIFIER == other.ASSET_IDENTIFIER
def related(self, repo):
# Prepare the results
results = set()
# TODO: Create related assets here based on the attributes of this asset
# Return the results
return results
def transform(self, repo, sources):
# Prepare the results
assets = set()
# Google Transforms
if sources.get('google'):
subassets = self.cache_transform_get('google', repo)
if not subassets:
# Acquire API
google = sources['google']
# TODO: Perform Google queries here and the results to 'subassets'
# Cache The Transform
self.cache_transform_store('google', subassets)
assets.update(subassets)
# Shodan Transforms
if sources.get('shodan'):
subassets = self.cache_transform_get('shodan', repo)
if not subassets:
# Acquire API
shodan = sources['shodan']
# TODO: Perform Google queries here and the results to 'subassets'
# Cache The Transform
self.cache_transform_store('shodan', subassets)
assets.update(subassets)
# Return the results
return assets
def is_owned(self, repo):
if self.owned:
return True
# TODO: Automatically determine ownership based on repo contents
return False
def parent_asset(self, repo):
# TODO: Return parent asset based on repo contents
return None
REPOSITORY = 'ASSET_REPOSITORY'
ASSET_CLASS = ASSET_NAME
OBJECT_ID = 'ASSET_IDENTIFIER'
以下の文字列を適切な値に置き換えてください
ASSET_NAME : アセットの正式名称(例:Registrant、Hostなど)ASSET_IDENTIFIER : アセットの識別子属性の名前ASSET_NAME_LETTER : アセット名の小文字の最初の文字ASSET_REPOSITORY : アセット名の複数形を小文字にしたもの最後に、inquisitor/__init__.py 内の ASSET_MODULES リストにアセットを登録してください。該当のファイルから新しいアセットをインポートすることを忘れないでください。
おめでとうございます! この時点で、新しい動作するアセットタイプができました!
ただし、アセットが他のアセットタイプと関連付けられるようにするには、以下のメソッドを実装する必要があります。``` Function
related
Description
Returns the set of assets directly related to the asset in question (i.e.
those that can be derived without querying a search engine).
When creating asset objects, make sure you use the
IntelligenceRepository.get_asset_string method instead of instatiating a
new one your self so the asset can be returned from the repository if it
exists.
Set the create flag to True when calling the method in question in order
to return a new object when one isn't found.
Set the store flag to False as appending assets is the job of another
module.
Parameters
repo: IntelligenceRepository
The Intelligence Repository that is being used in the current context.
Returns
Set of assets directly related to the asset in question.
(no input provided - empty translation)```
Function
transform
Description
Returns the set of assets potentially related to the asset in question
(i.e. those that can be derived by querying a search engine).
You may access search engine objects through the provided sources
parameter.
Each search engine object has a transform method which automatically
creates asset objects for you. You just need to provide it the repository
and your query string, and then append the objects it returns to the set
of assets to be returned by your asset's transform method.
Parameters
repo: IntelligenceRepository
The Intelligence Repository that is being used in the current context.
sources: dict
The list of search engine objects that are available for use.
Returns
Set of assets potentially related to the asset in question.
入力:``` Function
is_owned
Description
Determines if there is high confidence that this asset does indeed belong
to the target. Usually checks for any "strong" classification tag first by
looking at the contents of the "owned" variable, before performing
automatic evaluation.
Automatic evaluation depends on what type of asset you're writing. For
example, for a Host asset, the secondary sources of determining ownership
would include looking if its registrant is owned by the target, if it's
parent domain is owned by the target. etc.
Parameters
repo: IntelligenceRepository
The Intelligence Repository that is being used in the current context.
Returns
True it is determined with high confidence that this asset does indeed
belong to the target.
No input provided for translation. Please provide the actual Markdown content to translate.```
Function
parent_asset
Description
Returns the asset object that is considered the parent of this asset
object.
Parameters
repo: IntelligenceRepository
Returns
The asset object that this asset falls under (e.g. a Block is under a
Registrant, a Host is under a Block, a Host is under another Host, an Email
is under a Host, etc. This is primarily used for visualization.
上記の方法を実装した後、アセットのソースコード下部でREPOSITORY、ASSET_CLASS、OBJECT_IDの各変数を必ず設定してください。
スキャンモードは、関連する検索エンジンのクォータ制限により十分にテストされていません。また、このプロジェクトは1週間のハッカソンチャレンジの一環として急いで作成されたため、多くの問題が残っている可能性があります。バグを発見した場合やご質問がある場合は、issueチケットを作成するか、[email protected]までご連絡ください。
この成果物は、Maltegoおよびrecon-ngオープンソースインテリジェンスツールによって実装されたアプローチを基にしています。私はこれらのアプローチに、既に一般的に知られているアイデア(例:whoisはドメインの所有者を教えてくれる、サブドメインは親ドメインを所有する同じ組織によって所有されている(ドメイン名ブルートフォース攻撃から推測される)、組織は自身が所有するドメイン名に対して権限を持つ、など)、または私自身の趣味として個人的な時間に考案した独自のアイデア(例:受容性評価、さまざまなトランスフォーム、分類の継承など)を補足しています。
この成果物のいかなる部分も、過去に私が雇用主のために行った業務から派生したものではありません。プロジェクト全体(概念実証を含む)はゼロから作成され、情報セキュリティコミュニティのアイデアで補強されています。