
Субъективная организация-центричная OSINT разведка, вдохновлённая recon-ng и Maltego
Уведомление
Этот проект завершён лишь частично, и многие функции, описанные в следующей статье моего блога, ещё предстоит реализовать: 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`.```
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.
В режиме сканирования инструмент запускает все доступные трансформации для всех активов, имеющихся в вашей базе данных разведки. Убедитесь, что вы создали ключи API для различных источников OSINT, указанных ниже, и передали их скрипту, чтобы трансформации, использующие эти источники, не были пропущены. Кроме того, убедитесь, что вы первоначально заполнили свою базу данных разведки некоторыми известными принадлежащими вам целевыми активами с помощью команды 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.
В режиме классификации вы сможете вручную добавлять активы и переклассифицировать уже существующие активы в базе данных разведданных. Вам следует использовать эту команду, чтобы заполнить свою базу данных разведданных известными собственными целевыми активами.``` 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
В режиме дампа вы сможете выгрузить содержимое Intelligence Database в удобочитаемый 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.
## Workflow
Теперь, когда вы знаете основные возможности Inquisitor, пришло время узнать, как *на самом деле* его использовать. Inquisitor был написан с учётом следующих этапов:
### Seeding
На этом этапе ваша база данных разведданных ещё ничего не содержит. Нам нужно с чего-то начать, так что внесите в базу активы, которые, как вы знаете, принадлежат вашей целевой организации. Это можно сделать с помощью команды `classify`.
### Scanning
Теперь, когда в базе данных есть активы, которые, как известно, принадлежат вашей целевой организации, можно переходить к сканированию. Это делается с помощью команды `scan`.
Когда вы вызываете команду `scan` для своей базы данных разведданных, Inquisitor запускает методы `transform` активов, классифицированных как `accepted`. После завершения сканирования вы получите дополнительные активы, которые потенциально могут принадлежать вашей целевой организации.
Если новых активов не появилось, вы можете либо внести в базу данных разведданных новую информацию, либо просто перейти к завершению процесса, приступив к этапу отчётности.
### Classifying
Хотя Inquisitor выполняет автоматическую классификацию активов за вас, он может пропустить некоторые активы, которые на самом деле принадлежат вашей целевой организации.
В таком случае вам придётся проверить содержимое базы данных и вручную классифицировать активы. Обычно следует обращать внимание на активы типа **Registrant**, поскольку для этого типа активов невозможно автоматически определить принадлежность. Кроме того, большинство других типов активов полагаются на классификацию принадлежности активов Registrant, чтобы определить, принадлежат ли они вашей цели, так что определённо лучше всего уделить внимание активам Registrant. К тому же изначально активов Registrant не так много, так что их сортировка не будет слишком сложной.
### Reporting
Вы можете создать визуализацию активов, принадлежащих вашей целевой организации, с помощью команды `visualize` или команды `dump`.
## Demo
У меня есть видео-демонстрации работы инструмента по следующей ссылке: https://drive.google.com/open?id=0B_O70BVu38TRclo5dWRBWkdTTWc
Мне не удалось полностью записать выполнение команды scan, так как мой бесплатный рекордер экрана записывает только до 10 минут.
## Development
Проект 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. Основной скрипт называется inq.
Как разработчик, вы, скорее всего, будете заинтересованы в добавлении новых типов активов в систему, поэтому руководство разработчика будет в основном сосредоточено на этом.
Прежде чем перейти к реализации классов активов, нам сначала нужно понять, как взаимодействовать с Intelligence Database, так как мы будем взаимодействовать с ней при получении связанных активов из наших классов активов.
Исходный код Intelligence Database хранится в файле inquisitor/__init__.py. Фактическое имя логической обёртки Intelligence Database — IntelligenceRepository.
Вам нужно только вызывать функцию IntelligenceRepository.get_asset_string из классов активов, так как добавление новых активов в Intelligence Database — это ответственность модуля scan в скрипте inq. Вы будете использовать эту функцию в основном для создания экземпляров активов или их получения из базы данных, если они существуют. Эта функция важна при возврате активов из функций 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]```
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.
Мы должны проверить целостность наших данных. Используйте sha256sum для проверки файла контрольных сумм:
sha256sum -c checksums.txt
Если там написано "OK", данные целы. В противном случае что-то не так.
Проверьте GPG-подпись файла с помощью следующей команды:
gpg --verify file.tar.gz.asc file.tar.gz
Убедитесь, что ключ соответствует ожидаемому отпечатку.
Вы также можете создать отдельную подпись для ваших собственных файлов:
gpg --output file.tar.gz.sig --detach-sign file.tar.gz
Затем распространите как файл, так и файл .sig.
Для получения дополнительной информации обратитесь к официальной документации.``` 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.
Лицензия```
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 в нижней части исходного кода вашего актива.
Режим сканирования не полностью протестирован из-за квот, связанных с используемыми поисковыми системами. Кроме того, этот проект был создан в спешке в рамках недельного хакатона, поэтому может быть много проблем. Пожалуйста, создайте тикет или свяжитесь со мной по адресу [email protected], если вы найдете ошибку или у вас есть вопросы.
Эта работа основана на подходах, реализованных в инструментах разведки на основе открытых источников Maltego и recon-ng. Я дополнил эти подходы идеями, которые либо уже являются общеизвестными (например, whois сообщает, кто является владельцем домена, поддомены принадлежат той же организации, что и их родительский домен — как следует из атак перебором доменных имен, организации имеют авторитет в отношении принадлежащих им доменных имен и т.д.), либо являются оригинальными и были придуманы мной в личное время в рамках моего хобби (например, рейтинги приемлемости, различные преобразования, наследование классификации и т.д.).
Ни один компонент этой работы не был заимствован из какой-либо работы, которую я выполнял для какого-либо работодателя в прошлом. Весь проект, включая прототип, был написан с нуля и дополнен идеями из сообщества специалистов по информационной безопасности.