forked from p15670423/monkey
Merge pull request #2093 from guardicore/1965-credentials-report
1965 credentials report
This commit is contained in:
commit
e93455031a
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .format_credentials import format_creds_for_reporting
|
|
@ -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 ""
|
|
@ -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},
|
||||
|
|
|
@ -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 ""
|
|
@ -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"]
|
||||
]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 Monkey’s
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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;
|
|
@ -1,6 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
export function weakPasswordIssueOverview() {
|
||||
return (<li>Machines are accessible using passwords supplied by the user during the Monkey’s
|
||||
configuration.</li>)
|
||||
}
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue