Merge pull request #2093 from guardicore/1965-credentials-report

1965 credentials report
This commit is contained in:
Mike Salvatore 2022-07-19 13:50:55 -04:00 committed by GitHub
commit e93455031a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 321 additions and 301 deletions

View File

@ -7,6 +7,5 @@ from .monkey import Monkey
from .monkey_ttl import MonkeyTtl
from .pba_results import PbaResults
from monkey_island.cc.models.report.report import Report
from .stolen_credentials import StolenCredentials
from .simulation import Simulation, SimulationSchema, IslandMode
from .user_credentials import UserCredentials

View File

@ -1,21 +0,0 @@
from __future__ import annotations
from mongoengine import Document, ListField, ReferenceField
from monkey_island.cc.models import Monkey
class StolenCredentials(Document):
"""
This class has 2 main section:
* The schema section defines the DB fields in the document. This is the
data of the object.
* The logic section defines complex questions we can ask about a single document
which are asked multiple times, somewhat like an API.
"""
# SCHEMA
monkey = ReferenceField(Monkey)
identities = ListField()
secrets = ListField()

View File

@ -1,7 +1,7 @@
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.models import StolenCredentials
from monkey_island.cc.repository import ICredentialsRepository
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.reporting.stolen_credentials import get_stolen_creds
from monkey_island.cc.services.reporting import format_creds_for_reporting
class T1003(AttackTechnique):
@ -16,8 +16,21 @@ class T1003(AttackTechnique):
@staticmethod
def get_report_data():
raise NotImplementedError
class T1003GetReportData:
"""
Class to patch the T1003 attack technique which
needs stolen credentials from db.
"""
def __init__(self, credentials_repository: ICredentialsRepository):
self._credentials_repository = credentials_repository
def __call__(self):
def get_technique_status_and_data():
if list(StolenCredentials.objects()):
if list(self._credentials_repository.get_stolen_credentials()):
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
@ -28,5 +41,7 @@ class T1003(AttackTechnique):
data.update(T1003.get_message_and_status(status))
data.update(T1003.get_mitigation_by_status(status))
data["stolen_creds"] = get_stolen_creds()
data["stolen_creds"] = format_creds_for_reporting(
self._credentials_repository.get_stolen_credentials()
)
return data

View File

@ -33,6 +33,7 @@ from monkey_island.cc.repository import (
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.server_utils.encryption import ILockableEncryptor, RepositoryEncryptor
from monkey_island.cc.services import AWSService, IslandModeService, RepositoryService
from monkey_island.cc.services.attack.technique_reports.T1003 import T1003, T1003GetReportData
from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
from monkey_island.cc.services.telemetry.processing.credentials.credentials_parser import (
CredentialsParser,
@ -64,9 +65,7 @@ def initialize_services(data_dir: Path) -> DIContainer:
_register_repositories(container, data_dir)
_register_services(container)
# Note: A hack to resolve credentials parser
# It changes telemetry processing function, this will be refactored!
_patch_credentials_parser(container)
_dirty_hacks(container)
# This is temporary until we get DI all worked out.
ReportService.initialize(
@ -153,7 +152,16 @@ def _register_services(container: DIContainer):
container.register_instance(RepositoryService, container.resolve(RepositoryService))
def _patch_credentials_parser(container: DIContainer):
def _dirty_hacks(container: DIContainer):
# A dirty hacks function that patches some of the things that
# are needed at the current point
# Patches attack technique T1003 which is a static class
# but it needs stolen credentials from the database
T1003.get_report_data = container.resolve(T1003GetReportData)
# Note: A hack to resolve credentials parser
# It changes telemetry processing function, this will be refactored!
TELEMETRY_CATEGORY_TO_PROCESSING_FUNC[TelemCategoryEnum.CREDENTIALS] = container.resolve(
CredentialsParser
)

View File

@ -0,0 +1 @@
from .format_credentials import format_creds_for_reporting

View File

@ -0,0 +1,38 @@
import logging
from typing import Mapping, Sequence
from common.credentials import CredentialComponentType, Credentials
logger = logging.getLogger(__name__)
def format_creds_for_reporting(credentials: Sequence[Credentials]) -> Sequence[Mapping]:
logger.info("Stolen creds generated for reporting")
formatted_creds = []
cred_type_dict = {
CredentialComponentType.PASSWORD: "Clear Password",
CredentialComponentType.LM_HASH: "LM hash",
CredentialComponentType.NT_HASH: "NTLM hash",
CredentialComponentType.SSH_KEYPAIR: "Clear SSH private key",
}
for cred in credentials:
secret = cred.secret
if secret is None:
continue
if secret.credential_type not in cred_type_dict:
continue
username = _get_username(cred)
cred_row = {
"username": username,
"_type": secret.credential_type.name,
"type": cred_type_dict[secret.credential_type],
}
if cred_row not in formatted_creds:
formatted_creds.append(cred_row)
return formatted_creds
def _get_username(credentials: Credentials) -> str:
return credentials.identity.username if credentials.identity else ""

View File

@ -4,7 +4,6 @@ import logging
from itertools import chain, product
from typing import List
from common.credentials import CredentialComponentType
from common.network.network_range import NetworkRange
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
from monkey_island.cc.database import mongo
@ -20,16 +19,11 @@ from monkey_island.cc.services.reporting.pth_report import PTHReportService
from monkey_island.cc.services.reporting.report_generation_synchronisation import (
safe_generate_regular_report,
)
from monkey_island.cc.services.reporting.stolen_credentials import (
extract_ssh_keys,
get_stolen_creds,
)
from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses
from .. import AWSService
from . import aws_exporter
from .issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum
from .issue_processing.exploit_processing.processors.cred_exploit import CredentialType
from .issue_processing.exploit_processing.processors.exploit import ExploiterReportInfo
logger = logging.getLogger(__name__)
@ -42,8 +36,6 @@ class ReportService:
_credentials_repository = None
class DerivedIssueEnum:
WEAK_PASSWORD = "weak_password"
STOLEN_CREDS = "stolen_creds"
ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed"
@classmethod
@ -382,37 +374,6 @@ class ReportService:
def get_manual_monkey_hostnames():
return [monkey["hostname"] for monkey in get_manual_monkeys()]
@classmethod
def get_config_users(cls):
usernames = []
configured_credentials = cls._credentials_repository.get_configured_credentials()
for credentials in configured_credentials:
usernames = chain(
usernames,
(
identity
for identity in credentials.identities
if identity.credential_type == CredentialComponentType.USERNAME
),
)
return [u.username for u in usernames]
@classmethod
def get_config_passwords(cls):
passwords = []
configured_credentials = cls._credentials_repository.get_configured_credentials()
for credentials in configured_credentials:
passwords = chain(
passwords,
(
secret
for secret in credentials.secrets
if secret.credential_type == CredentialComponentType.PASSWORD
),
)
return [p.password for p in passwords]
@classmethod
def get_config_exploits(cls):
agent_configuration = cls._agent_configuration_repository.get_configuration()
@ -438,42 +399,18 @@ class ReportService:
return agent_configuration.propagation.network_scan.targets.local_network_scan
@staticmethod
def get_issue_set(issues, config_users, config_passwords):
def get_issue_set(issues):
issue_set = set()
for machine in issues:
for issue in issues[machine]:
if ReportService._is_weak_credential_issue(issue, config_users, config_passwords):
issue_set.add(ReportService.DerivedIssueEnum.WEAK_PASSWORD)
elif ReportService._is_stolen_credential_issue(issue):
issue_set.add(ReportService.DerivedIssueEnum.STOLEN_CREDS)
elif ReportService._is_zerologon_pass_restore_failed(issue):
if ReportService._is_zerologon_pass_restore_failed(issue):
issue_set.add(ReportService.DerivedIssueEnum.ZEROLOGON_PASS_RESTORE_FAILED)
issue_set.add(issue["type"])
return issue_set
@staticmethod
def _is_weak_credential_issue(
issue: dict, config_usernames: List[str], config_passwords: List[str]
) -> bool:
# Only credential exploiter issues have 'credential_type'
return (
"credential_type" in issue
and issue["credential_type"] == CredentialType.PASSWORD.value
and issue["password"] in config_passwords
and issue["username"] in config_usernames
)
@staticmethod
def _is_stolen_credential_issue(issue: dict) -> bool:
# Only credential exploiter issues have 'credential_type'
return "credential_type" in issue and (
issue["credential_type"] == CredentialType.PASSWORD.value
or issue["credential_type"] == CredentialType.HASH.value
)
@staticmethod
def _is_zerologon_pass_restore_failed(issue: dict):
return (
@ -490,12 +427,9 @@ class ReportService:
def generate_report():
domain_issues = ReportService.get_domain_issues()
issues = ReportService.get_issues()
config_users = ReportService.get_config_users()
config_passwords = ReportService.get_config_passwords()
issue_set = ReportService.get_issue_set(issues, config_users, config_passwords)
issue_set = ReportService.get_issue_set(issues)
cross_segment_issues = ReportService.get_cross_segment_issues()
monkey_latest_modify_time = Monkey.get_latest_modifytime()
stolen_creds = get_stolen_creds()
scanned_nodes = ReportService.get_scanned()
exploited_cnt = len(get_monkey_exploited())
@ -515,8 +449,6 @@ class ReportService:
"glance": {
"scanned": scanned_nodes,
"exploited_cnt": exploited_cnt,
"stolen_creds": stolen_creds,
"ssh_keys": extract_ssh_keys(stolen_creds),
"strong_users": PTHReportService.get_strong_users_on_crit_details(),
},
"recommendations": {"issues": issues, "domain_issues": domain_issues},

View File

@ -1,52 +0,0 @@
import logging
from typing import Mapping, Sequence
from common.credentials import CredentialComponentType
from monkey_island.cc.models import StolenCredentials
logger = logging.getLogger(__name__)
def get_stolen_creds() -> Sequence[Mapping]:
stolen_creds = _fetch_from_db()
stolen_creds = _format_creds_for_reporting(stolen_creds)
logger.info("Stolen creds generated for reporting")
return stolen_creds
def extract_ssh_keys(credentials: Sequence[Mapping]) -> Sequence[Mapping]:
return [c for c in credentials if c["_type"] == CredentialComponentType.SSH_KEYPAIR.name]
def _fetch_from_db() -> Sequence[StolenCredentials]:
return list(StolenCredentials.objects())
def _format_creds_for_reporting(credentials: Sequence[StolenCredentials]):
formatted_creds = []
cred_type_dict = {
CredentialComponentType.PASSWORD.name: "Clear Password",
CredentialComponentType.LM_HASH.name: "LM hash",
CredentialComponentType.NT_HASH.name: "NTLM hash",
CredentialComponentType.SSH_KEYPAIR.name: "Clear SSH private key",
}
for cred in credentials:
for secret_type in cred.secrets:
if secret_type not in cred_type_dict:
continue
username = _get_username(cred)
cred_row = {
"username": username,
"_type": secret_type,
"type": cred_type_dict[secret_type],
"origin": cred.monkey.hostname,
}
if cred_row not in formatted_creds:
formatted_creds.append(cred_row)
return formatted_creds
def _get_username(credentials: StolenCredentials) -> str:
return credentials.identities[0]["username"] if credentials.identities else ""

View File

@ -15,10 +15,10 @@ class CredentialsParser:
def __init__(self, credentials_repository: ICredentialsRepository):
self._credentials_repository = credentials_repository
def __call__(self, telemetry_dict):
self._parse_credentials(telemetry_dict)
def __call__(self, telemetry_dict, _agent_configuration):
self._parse_credentials(telemetry_dict, _agent_configuration)
def _parse_credentials(self, telemetry_dict: Mapping):
def _parse_credentials(self, telemetry_dict: Mapping, _agent_configuration):
credentials = [
Credentials.from_mapping(credential) for credential in telemetry_dict["data"]
]

View File

@ -18,7 +18,9 @@ class T1003 extends React.Component {
<br/>
{this.props.data.status === ScanStatus.USED ?
<StolenPasswordsComponent
data={this.props.data.stolen_creds}/>
data={this.props.data.stolen_creds}
format={false}
/>
: ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div>

View File

@ -37,7 +37,6 @@ import {
} from './security/issues/SharedPasswordsIssue';
import {tunnelIssueReport, tunnelIssueOverview} from './security/issues/TunnelIssue';
import {stolenCredsIssueOverview} from './security/issues/StolenCredsIssue';
import {weakPasswordIssueOverview} from './security/issues/WeakPasswordIssue';
import {strongUsersOnCritIssueReport} from './security/issues/StrongUsersOnCritIssue';
import {
zerologonIssueOverview,
@ -45,6 +44,7 @@ import {
zerologonOverviewWithFailedPassResetWarning
} from './security/issues/ZerologonIssue';
import {powershellIssueOverview, powershellIssueReport} from './security/issues/PowershellIssue';
import UsedCredentials from './security/UsedCredentials';
class ReportPageComponent extends AuthComponent {
@ -146,10 +146,8 @@ class ReportPageComponent extends AuthComponent {
[this.issueContentTypes.REPORT]: strongUsersOnCritIssueReport,
[this.issueContentTypes.TYPE]: this.issueTypes.DANGER
},
'weak_password': {
[this.issueContentTypes.OVERVIEW]: weakPasswordIssueOverview,
[this.issueContentTypes.TYPE]: this.issueTypes.DANGER
},
// TODO: Add used_password issue: configured password that were
// successfull exploiting a machine, previously called 'weak_password'
'stolen_creds': {
[this.issueContentTypes.OVERVIEW]: stolenCredsIssueOverview,
[this.issueContentTypes.TYPE]: this.issueTypes.DANGER
@ -161,15 +159,32 @@ class ReportPageComponent extends AuthComponent {
this.state = {
report: props.report,
graph: {nodes: [], edges: []},
nodeStateList: []
nodeStateList: [],
stolenCredentials: [],
configuredCredentials: [],
issues: []
};
}
componentDidMount() {
this.getNodeStateListFromServer();
this.getCredentialsFromServer();
this.updateMapFromServer();
}
getCredentialsFromServer = () => {
this.authFetch('/api/propagation-credentials/stolen-credentials')
.then(res => res.json())
.then(creds => {
this.setState({stolenCredentials: creds});
})
this.authFetch('/api/propagation-credentials/configured-credentials')
.then(res => res.json())
.then(creds => {
this.setState({configuredCredentials: creds});
})
}
getNodeStateListFromServer = () => {
this.authFetch('/api/netmap/node-states')
.then(res => res.json())
@ -184,7 +199,8 @@ class ReportPageComponent extends AuthComponent {
componentDidUpdate(prevProps) {
if (this.props.report !== prevProps.report) {
this.setState({report: this.props.report})
this.setState({report: this.props.report});
this.addIssuesToOverviewIssues();
}
}
@ -273,39 +289,20 @@ class ReportPageComponent extends AuthComponent {
<p>
The monkeys were run with the following configuration:
</p>
{
this.state.report.overview.config_users.length > 0 ?
<>
<p>
Usernames used for brute-forcing:
</p>
<ul>
{this.state.report.overview.config_users.map(x => <li key={x}>{x}</li>)}
</ul>
<p>
Passwords used for brute-forcing:
</p>
<ul>
{this.state.report.overview.config_passwords.map(x => <li key={x}>{x.substr(0, 3) + '******'}</li>)}
</ul>
</>
:
<p>
Brute forcing uses stolen credentials only. No credentials were supplied during Monkeys
configuration.
</p>
}
<UsedCredentials stolen={this.state.stolenCredentials} configured={this.state.configuredCredentials}/>
{
this.state.report.overview.config_exploits.length > 0 ?
<p>
The Monkey uses the following exploit methods:
<ul>
{this.state.report.overview.config_exploits.map(x => <li key={x}>{x}</li>)}
</ul>
</p>
(
<p>
The Monkey attempted the following exploitation methods:
<ul>
{this.state.report.overview.config_exploits.map(x => <li key={x}>{x}</li>)}
</ul>
</p>
)
:
<p>
No exploits are used by the Monkey.
No exploiters were enabled.
</p>
}
{
@ -379,6 +376,9 @@ class ReportPageComponent extends AuthComponent {
getPotentialSecurityIssuesOverviews() {
let overviews = [];
let issues = this.state.report.overview.issues;
let state_issues = this.state.issues;
issues.push(...state_issues);
issues = [...new Set(issues)];
for(let i=0; i < issues.length; i++) {
if (this.isIssuePotentialSecurityIssue(issues[i])) {
@ -420,6 +420,10 @@ class ReportPageComponent extends AuthComponent {
getImmediateThreatCount() {
let threatCount = 0;
let issues = this.state.report.overview.issues;
let state_issues = this.state.issues;
issues.push(...state_issues);
issues = [...new Set(issues)];
for(let i=0; i < issues.length; i++) {
if(this.isIssueImmediateThreat(issues[i])) {
@ -439,6 +443,10 @@ class ReportPageComponent extends AuthComponent {
getImmediateThreatsOverviews() {
let overviews = [];
let issues = this.state.report.overview.issues;
let state_issues = this.state.issues;
issues.push(...state_issues);
issues = [...new Set(issues)];
for(let i=0; i < issues.length; i++) {
if (this.isIssueImmediateThreat(issues[i])) {
@ -535,7 +543,10 @@ class ReportPageComponent extends AuthComponent {
</div>
<div style={{marginBottom: '20px'}}>
<StolenPasswords data={this.state.report.glance.stolen_creds}/>
<StolenPasswords
data={this.state.stolenCredentials}
format={true}
/>
</div>
<div>
<StrongUsers data={this.state.report.glance.strong_users}/>
@ -582,6 +593,23 @@ class ReportPageComponent extends AuthComponent {
}
return <ul>{issuesDivArray}</ul>;
};
addIssuesToOverviewIssues() {
let overview_issues = this.state.issues;
if (this.shouldAddStolenCredentialsIssue()) {
overview_issues.push('stolen_creds');
}
this.setState({
issues: overview_issues
});
}
shouldAddStolenCredentialsIssue() {
// TODO: This should check if any stolen credentials are used to
// exploit a machine
return ( this.state.stolenCredentials.length > 0 )
}
}
export default ReportPageComponent;

View File

@ -0,0 +1,57 @@
export function getAllUsernames(stolen, configured){
let usernames = [];
usernames.push(...getCredentialsUsernames(stolen));
usernames.push(...getCredentialsUsernames(configured));
return usernames;
}
export function getCredentialsUsernames(credentials) {
let usernames = [];
for(let i = 0; i < credentials.length; i++){
usernames.push(credentials[i]['identity']['username']);
}
return usernames;
}
export function getAllSecrets(stolen, configured){
let secrets = [];
for(let i = 0; i < stolen.length; i++){
secrets.push(getSecretsFromCredential(stolen[i]['secret']));
}
for(let i = 0; i < configured.length; i++){
secrets.push(getSecretsFromCredential(configured[i]['secret']));
}
return secrets;
}
function getSecretsFromCredential(credential) {
if(credential['credential_type'] === 'SSH_KEYPAIR'){
return {'type': 'SSH keypair', 'content': credential['private_key']}
}
if(credential['credential_type'] === 'NT_HASH'){
return {'type': 'NT hash', 'content': credential['nt_hash']}
}
if(credential['credential_type'] === 'LM_HASH'){
return {'type': 'LM hash', 'content': credential['lm_hash']}
}
if(credential['credential_type'] === 'PASSWORD'){
return {'type': 'Password', 'content': credential['password']}
}
}
export function getCredentialsTableData(credentials) {
let table_data = [];
let identites = getCredentialsUsernames(credentials);
let secrets = getAllSecrets(credentials, [])
for(let i=0; i<credentials.length; i++) {
let row_data = {};
row_data['username'] = identites[i];
row_data['type'] = secrets[i]['type'];
table_data.push(row_data);
}
return table_data;
}

View File

@ -1,13 +1,13 @@
import React from 'react';
import ReactTable from 'react-table'
import ReactTable from 'react-table'
import {getCredentialsTableData} from '../credentialParsing.js';
const columns = [
{
Header: 'Stolen Credentials',
columns: [
{Header: 'Username', accessor: 'username'},
{Header: 'Type', accessor: 'type'},
{Header: 'Stolen From', accessor: 'origin'}
{Header: 'Type', accessor: 'type'}
]
}
];
@ -22,11 +22,18 @@ class StolenPasswordsComponent extends React.Component {
render() {
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
let showPagination = this.props.data.length > pageSize;
let table_data = this.props.data;
if(this.props.format) {
// Note: This formatting is needed because StolenPasswords
// is used in Security and Attack report with different data
table_data = getCredentialsTableData(this.props.data);
}
return (
<div className="data-table-container">
<ReactTable
columns={columns}
data={this.props.data}
data={table_data}
showPagination={showPagination}
defaultPageSize={defaultPageSize}
/>

View File

@ -0,0 +1,40 @@
import {getAllUsernames, getAllSecrets} from '../credentialParsing';
import React from 'react';
class UsedCredentials extends React.Component {
constructor(props) {
super(props);
}
render() {
let allUsernames = getAllUsernames(this.props.stolen, this.props.configured);
let allSecrets = getAllSecrets(this.props.stolen, this.props.configured);
return (
allUsernames.length > 0 ?
<>
<p>
Usernames used for brute-forcing:
</p>
<ul>
{allUsernames.map(x => <li key={x}>{x}</li>)}
</ul>
<p>
Credentials used for brute-forcing:
</p>
<ul>
{allSecrets.map((x, index) => <li
key={index}>{x['type']}: {x['content'].substr(0, 3) + '******'}</li>)}
</ul>
</>
:
<p>
No credentials were used.
</p>
)
}
}
export default UsedCredentials;

View File

@ -1,6 +0,0 @@
import React from 'react';
export function weakPasswordIssueOverview() {
return (<li>Machines are accessible using passwords supplied by the user during the Monkeys
configuration.</li>)
}

View File

@ -0,0 +1,69 @@
from common.credentials import (
CredentialComponentType,
Credentials,
LMHash,
NTHash,
Password,
SSHKeypair,
Username,
)
from monkey_island.cc.services.reporting import format_creds_for_reporting
monkey_hostname = "fake_hostname"
fake_monkey_guid = "abc"
fake_username = Username("m0nk3y_user")
fake_nt_hash = NTHash("AEBD4DE384C7EC43AAD3B435B51404EE")
fake_lm_hash = LMHash("7A21990FCD3D759941E45C490F143D5F")
fake_password = Password("trytostealthis")
fake_ssh_public_key = "RSA_public_key"
fake_ssh_private_key = "RSA_private_key"
fake_ssh_key = SSHKeypair(fake_ssh_private_key, fake_ssh_public_key)
identities = (fake_username,)
secrets = (fake_nt_hash, fake_lm_hash, fake_password, fake_ssh_key)
fake_credentials = [
Credentials(fake_username, fake_nt_hash),
Credentials(fake_username, fake_lm_hash),
Credentials(fake_username, fake_password),
Credentials(fake_username, fake_ssh_key),
Credentials(None, fake_ssh_key),
Credentials(fake_username, None),
]
def test_formatting_credentials_for_report():
credentials = format_creds_for_reporting(fake_credentials)
result1 = {
"_type": CredentialComponentType.NT_HASH.name,
"type": "NTLM hash",
"username": fake_username.username,
}
result2 = {
"_type": CredentialComponentType.LM_HASH.name,
"type": "LM hash",
"username": fake_username.username,
}
result3 = {
"_type": CredentialComponentType.PASSWORD.name,
"type": "Clear Password",
"username": fake_username.username,
}
result4 = {
"_type": CredentialComponentType.SSH_KEYPAIR.name,
"type": "Clear SSH private key",
"username": fake_username.username,
}
result5 = {
"_type": CredentialComponentType.SSH_KEYPAIR.name,
"type": "Clear SSH private key",
"username": "",
}
assert result1 in credentials
assert result2 in credentials
assert result3 in credentials
assert result4 in credentials
assert result5 in credentials

View File

@ -1,97 +0,0 @@
import pytest
from common.credentials import CredentialComponentType
from monkey_island.cc.models import Monkey, StolenCredentials
from monkey_island.cc.services.reporting.stolen_credentials import (
extract_ssh_keys,
get_stolen_creds,
)
monkey_hostname = "fake_hostname"
fake_monkey_guid = "abc"
fake_username = "m0nk3y_user"
fake_nt_hash = "c1c58f96cdf212b50837bc11a00be47c"
fake_lm_hash = "299BD128C1101FD6"
fake_password = "trytostealthis"
fake_ssh_key = "RSA_fake_key"
fake_credentials = {
"identities": [{"username": fake_username, "credential_type": "USERNAME"}],
"secrets": [
CredentialComponentType.NT_HASH.name,
CredentialComponentType.LM_HASH.name,
CredentialComponentType.PASSWORD.name,
CredentialComponentType.SSH_KEYPAIR.name,
],
}
@pytest.fixture
def fake_monkey():
monkey = Monkey()
monkey.guid = fake_monkey_guid
monkey.hostname = monkey_hostname
monkey.save()
return monkey.id
@pytest.mark.usefixtures("uses_database")
def test_get_credentials(fake_monkey):
StolenCredentials(
identities=fake_credentials["identities"],
secrets=fake_credentials["secrets"],
monkey=fake_monkey,
).save()
credentials = get_stolen_creds()
result1 = {
"origin": monkey_hostname,
"_type": CredentialComponentType.NT_HASH.name,
"type": "NTLM hash",
"username": fake_username,
}
result2 = {
"origin": monkey_hostname,
"_type": CredentialComponentType.LM_HASH.name,
"type": "LM hash",
"username": fake_username,
}
result3 = {
"origin": monkey_hostname,
"_type": CredentialComponentType.PASSWORD.name,
"type": "Clear Password",
"username": fake_username,
}
result4 = {
"origin": monkey_hostname,
"_type": CredentialComponentType.SSH_KEYPAIR.name,
"type": "Clear SSH private key",
"username": fake_username,
}
assert result1 in credentials
assert result2 in credentials
assert result3 in credentials
assert result4 in credentials
@pytest.mark.usefixtures("uses_database")
def test_extract_ssh_keys(fake_monkey):
StolenCredentials(
identities=fake_credentials["identities"],
secrets=fake_credentials["secrets"],
monkey=fake_monkey,
).save()
credentials = get_stolen_creds()
keys = extract_ssh_keys(credentials)
assert len(keys) == 1
result = {
"origin": monkey_hostname,
"_type": CredentialComponentType.SSH_KEYPAIR.name,
"type": "Clear SSH private key",
"username": fake_username,
}
assert result in keys