blob: c733b4482784f87d1e05d2a901391758d6459a6d [file]
<div x-data="alpineAgents()" x-init="initPage()" id="main-content">
<!-- PAGE HEADER -->
<div x-ref="header">
<h2>Agents</h2>
<p>
You must deploy at least 1 agent in order to run an operation. Groups are collections of agents so hosts can be compromised simultaneously.
</p>
</div>
<hr>
<!-- AGENTS TABLE AND CONFIGURATION -->
<div>
<div class="columns">
<div class="column is-4 buttons m-0 is-flex is-justify-content-flex-start">
<button class="button is-primary is-small level-item" @click="showDeployModal = true">
<span class="icon"><i class="fas fa-plus"></i></span>
<span>Deploy an agent</span>
</button>
<button class="button is-primary is-small level-item" @click="showConfigModal = true">
<span class="icon"><i class="fas fa-cog"></i></span>
<span>Configuration</span>
</button>
</div>
<div class="column is-4 m-0 is-flex is-justify-content-center">
<span class="level-item tag is-medium m-0" x-on:mouseenter="isHoveringAgentCount = true" x-on:mouseleave="isHoveringAgentCount = false">
<span class="mr-4 has-text-success" x-show="isHoveringAgentCount">
<span class="has-text-weight-bold" x-text="agents.filter((a) => getAgentStatus(a) === 'alive' || getAgentStatus(a) === 'pending kill').length"></span>&nbsp;alive&nbsp;·
<span class="has-text-weight-bold" x-text="agents.filter((a) => a.trusted).length"></span>&nbsp;trusted
</span>
<strong x-text="agents.length"></strong>&nbsp;agent<span x-show="agents.length !== 1">s</span>
<span class="ml-4 has-text-warning" x-show="isHoveringAgentCount">
<span class="has-text-weight-bold" x-text="agents.filter((a) => getAgentStatus(a) === 'dead').length"></span>&nbsp;dead&nbsp;·
<span class="has-text-weight-bold" x-text="agents.filter((a) => !a.trusted).length"></span>&nbsp;untrusted
</span>
</span>
</div>
<div class="column is-4 m-0 is-flex is-justify-content-flex-end">
<div class="dropdown is-right is-hoverable">
<div class="dropdown-trigger">
<button class="button is-small is-primary" aria-haspopup="true" aria-controls="bulk-actions">
<span>Bulk Actions</span>
<span class="icon is-small"><i class="fas fa-angle-down" aria-hidden="true"></i></span>
</button>
</div>
<div class="dropdown-menu" id="bulk-actions" role="menu">
<div class="dropdown-content">
<a href="#" class="dropdown-item" @click="confirmAlt('Are you sure you want to remove all dead agents?', removeDeadAgents)">
Remove dead agents
</a>
<a href="#" class="dropdown-item" @click="confirmAlt('Are you sure you want to remove all agents? Agents that beacon after they are removed will reappear.', removeAllAgents)">
Remove all agents
</a>
<a href="#" class="dropdown-item has-text-danger" @click="confirmAlt('You are about to kill all agents. Are you sure?', killAllAgents)">
Kill all agents
</a>
</div>
</div>
</div>
</div>
</div>
<table class="table is-striped is-fullwidth" x-show="agents.length">
<thead>
<tr>
<th>id (paw)</th>
<th>host</th>
<th>group</th>
<th>platform</th>
<th>contact</th>
<th>pid</th>
<th>privilege</th>
<th>status</th>
<th>last seen</th>
<td></td>
</tr>
</thead>
<tbody>
<template x-for="(agent, index) of agents" :key="agent.paw">
<tr @click="selectedAgent = agent; showDetailsModal = true;" class="pointer">
<td x-text="agent.paw"></td>
<td x-text="agent.host"></td>
<td x-text="agent.group"></td>
<td x-text="agent.platform"></td>
<td x-text="agent.contact"></td>
<td x-text="agent.pid"></td>
<td x-text="agent.privilege"></td>
<td>
<span x-text="getAgentStatus(agent)" x-bind:class="{ 'has-text-warning': getAgentStatus(agent) === 'dead', 'has-text-success': getAgentStatus(agent) === 'alive', 'has-text-info': getAgentStatus(agent) === 'pending kill' }"></span><span>,</span>
<span x-text="agent.trusted ? 'trusted' : 'untrusted'" x-bind:class="{ 'has-text-warning': !agent.trusted, 'has-text-success': agent.trusted }"></span>
</td>
<td x-text="getHumanFriendlyTimeISO8601(agent.last_seen)" x-bind:data-tooltip="agent.last_seen"></td>
<td class="has-text-centered"><button class="delete is-white" @click.stop="removeAgent(agent.paw, index)"></button></td>
</tr>
</template>
</tbody>
</table>
<div class="has-text-centered content" x-show="!agents.length">
<p>No deployed agents</p>
</div>
</div>
<!-- MODALS -->
<div class="modal" x-bind:class="{ 'is-active': showDeployModal }">
<div class="modal-background" @click="closeDeployModal()"></div>
<div class="modal-card wide">
<header class="modal-card-head">
<p class="modal-card-title">Deploy an agent</p>
</header>
<section class="modal-card-body">
<form class="has-text-centered">
<div class="field">
<label class="label">Agent</label>
<div class="control">
<div class="select is-small">
<select x-model="selectedAbilityId" x-on:change="selectAbility()">
<option disabled selected value="">Choose an agent</option>
<template x-for="ability of deploymentAbilities" :key="ability.ability_id">
<option x-bind:value="ability.ability_id" x-text="`${ability.name} | ${ability.description}`"></option>
</template>
</select>
</div>
</div>
</div>
<div class="field" x-show="selectedAbilityId">
<label class="label">Platform</label>
<div class="control is-flex is-flex-direction-row is-justify-content-center">
<div class="has-text-centered platform" x-bind:class="{ 'selected': selectedPlatform === 'all' }" @click="changePlatform('all')" x-show="selectedPlatform">
<span class="icon is-large"><i class="far fa-2x fa-circle"></i></span>
<br> all
</div>
<div class="has-text-centered platform" x-bind:class="{ 'selected': selectedPlatform === 'linux' }" x-show="platforms.includes('linux')" @click="changePlatform('linux')">
<span class="icon is-large"><i class="fab fa-2x fa-linux"></i></span>
<br> linux
</div>
<div class="has-text-centered platform" x-bind:class="{ 'selected': selectedPlatform === 'windows' }" x-show="platforms.includes('windows')" @click="changePlatform('windows')">
<span class="icon is-large"><i class="fab fa-2x fa-windows"></i></span>
<br> windows
</div>
<div class="has-text-centered platform" x-bind:class="{ 'selected': selectedPlatform === 'darwin' }" x-show="platforms.includes('darwin')" @click="changePlatform('darwin')">
<span class="icon is-large"><i class="fab fa-2x fa-apple"></i></span>
<br> darwin
</div>
</div>
</div>
</form>
<form class="command-fields">
<template x-for="field of agentFields" :key="field.name">
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label" x-text="field.name"></label>
</div>
<div class="field-body">
<div class="field has-addons">
<div class="control is-expanded">
<input class="input is-small" type="text" x-model="field.value">
</div>
<div class="control">
<a class="button is-small has-tooltip-arrow" data-tooltip="Reset to default" @click="field.value = agentFieldConfig[field.name]">
<span class="icon"><i class="fas fa-undo"></i></span>
</a>
</div>
</div>
</div>
</div>
</template>
</form>
<hr x-show="filteredCommands.length">
<div>
<template x-for="command of filteredCommands" :key="command.command">
<div class="command-container container">
<div class="tags are-medium has-addons">
<span class="tag is-black">
<span class="icon">
<i class="fab" x-bind:class="{ 'fa-windows': command.platform === 'windows', 'fa-linux': command.platform === 'linux', 'fa-apple': command.platform === 'darwin' }"></i>
</span>
</span>
<span class="tag is-dark" x-text="command.executor"></span>
<span class="tag" x-text="command.description"></span>
</div>
<div class="box container">
<pre class="box" x-text="getCommandField(command.command)"></pre>
<a class="button is-small is-outlined cmd-copy-button" @click="copyCommandToClipboard(getCommandField(command.command))" x-show="window.isSecureContext">
<span class="icon"><i class="far fa-lg fa-copy"></i></span>
<span>Copy</span>
</a>
</div>
<p class="has-text-centered" x-show="command.variations.length"><b>Variations</b></p>
<template x-for="variation of command.variations" :key="variation.command">
<div class="content">
<div class="tags are-medium has-addons">
<span class="tag is-black">
<span class="icon">
<i class="fab" x-bind:class="{ 'fa-windows': command.platform === 'windows', 'fa-linux': command.platform === 'linux', 'fa-apple': command.platform === 'darwin' }"></i>
</span>
</span>
<span class="tag is-dark" x-text="command.executor"></span>
<span class="tag" x-text="variation.description"></span>
</div>
<div class="box container">
<pre class="box" x-text="getCommandField(variation.command).replaceAll(';', ';\n')"></pre>
<a class="button is-small is-outlined cmd-copy-button" @click="copyCommandToClipboard(getCommandField(variation.command))" x-show="window.isSecureContext">
<span class="icon"><i class="far fa-lg fa-copy"></i></span>
<span>Copy</span>
</a>
</div>
</div>
</template>
</div>
</template>
</div>
</section>
<footer class="modal-card-foot">
<nav class="level">
<div class="level-left"></div>
<div class="level-right">
<div class="level-item">
<button class="button is-small" @click="closeDeployModal()">Close</button>
<div>
</div>
</nav>
</footer>
</div>
</div>
<div class="modal" x-bind:class="{ 'is-active': showConfigModal }">
<div class="modal-background" @click="showConfigModal = false"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Agent Configuration</p>
</header>
<section class="modal-card-body">
<table>
<col width="35%">
<col width="65%">
<tbody>
<tr>
<th class="has-text-right pt-3">
<span class="has-tooltip-multiline has-tooltip-bottom" data-tooltip="Set the minimum and maximum seconds the agent will take to beacon home.">Beacon Timers (s)</span>
</th>
<td>
<div class="is-align-items-center is-flex is-align-items-center">
<span class="top-right-anchor">
<label>min</label>
<input class="input is-small is-small-number" type="number" placeholder="30" x-model="agentConfig.sleep_min" x-bind:class="{ 'is-danger': fieldErrorsConfig.includes('sleep_min') }">
</span>
<span class="top-right-anchor">
<label>max</label>
<input class="input is-small is-small-number" type="number" placeholder="60" x-model="agentConfig.sleep_max" x-bind:class="{ 'is-danger': fieldErrorsConfig.includes('sleep_max') }">
</span>
</div>
<p x-show="fieldErrorsConfig.includes('sleep_min') || fieldErrorsConfig.includes('sleep_max')" class="help is-danger">This field is required.</p>
</td>
</tr>
<tr>
<th class="has-text-right pt-3">
<span class="has-tooltip-multiline has-tooltip-bottom" data-tooltip="Set the number of seconds to wait, once the server is unreachable, before killing an agent.">Watchdog Timer (s)</span>
</th>
<td>
<input class="input is-small is-small-number" type="number" placeholder="0" x-model="agentConfig.watchdog" x-bind:class="{ 'is-danger': fieldErrorsConfig.includes('watchdog') }">
<p x-show="fieldErrorsConfig.includes('watchdog')" class="help is-danger">This field is required.</p>
</td>
</tr>
<tr>
<th class="has-text-right pt-3">
<span class="has-tooltip-multiline" data-tooltip="Set the number of seconds to wait before marking a missing agent as untrusted. Operations will not generate new links for untrusted agents.">Untrusted Timer (s)</span> *
</th>
<td>
<input class="input is-small is-small-number" type="number" placeholder="90" x-model="agentConfig.untrusted_timer" x-bind:class="{ 'is-danger': fieldErrorsConfig.includes('untrusted_timer') }">
<p x-show="fieldErrorsConfig.includes('untrusted_timer')" class="help is-danger">This field is required.</p>
</td>
</tr>
<tr>
<th class="has-text-right pt-3">
<span class="has-tooltip-multiline" data-tooltip="The base name of newly-spawned agents. If necessary, an extension will be added when an agent is created.">Implant Name</span>
</th>
<td>
<input class="input is-small" type="text" placeholder="splunkd" x-model="agentConfig.implant_name" x-bind:class="{ 'is-danger': fieldErrorsConfig.includes('implant_name') }">
<p x-show="fieldErrorsConfig.includes('implant_name')" class="help is-danger">This field is required.</p>
</td>
</tr>
<tr>
<th class="has-text-right pt-3">
<span class="has-tooltip-multiline" data-tooltip="A list of ability IDs to be run on a new agent beacon. By default, this is set to run a command which clears command history.">Bootstrap Abilities</span>
</th>
<td>
<div class="field is-grouped is-grouped-multiline" x-show="!isAddingBootstrapAbility || bootstrapAbilities.length">
<template x-for="(abilityId, index) of bootstrapAbilities">
<div class="control">
<div class="tags has-addons">
<a class="tag is-info" x-text="getAbilityName(abilityId)" x-bind:data-tooltip="getAbilityDescription(abilityId)"></a>
<a class="tag is-delete" @click="bootstrapAbilities.splice(index, 1)"></a>
</div>
</div>
</template>
<div class="control">
<div class="tags has-addons">
<a class="tag is-primary" @click="isAddingBootstrapAbility = true; isAddingDeadmanAbility = false" x-show="!isAddingBootstrapAbility">
<span class="icon"><i class="fas fa-plus"></i></span>
</a>
</div>
</div>
</div>
<div class="field has-addons" x-show="isAddingBootstrapAbility">
<div class="control is-expanded auto-complete" x-data="{focusSearchResults: true}" @click.outside="focusSearchResults = false">
<input class="input is-small" placeholder="Search for an ability..." x-model="abilitySearchQuery" x-on:keyup="searchForAbility()" @click="focusSearchResults = true">
<div class="search-results is-size-7" x-show="abilitySearchResults && focusSearchResults">
<template x-for="result of abilitySearchResults" :key="result.ability_id">
<p @click="addBootstrapAbility(result.ability_id)" x-text="result.name"></p>
</template>
</div>
</div>
<div class="control">
<a class="button is-small" @click="isAddingBootstrapAbility = false">Cancel</a>
</div>
</div>
</td>
</tr>
<tr>
<th class="has-text-right pt-3">
<span class="has-tooltip-multiline" data-tooltip="A list of abilities to be run immediately prior to agent termination. The agent must support deadman abilities in order for them to run.">Deadman Abilities</span>
</th>
<td>
<div class="field is-grouped is-grouped-multiline" x-show="!isAddingDeadmanAbility || deadmanAbilities.length">
<template x-for="(abilityId, index) of deadmanAbilities">
<div class="control">
<div class="tags has-addons">
<a class="tag is-info" x-text="getAbilityName(abilityId)" x-bind:data-tooltip="getAbilityDescription(abilityId)"></a>
<a class="tag is-delete" @click="deadmanAbilities.splice(index, 1)"></a>
</div>
</div>
</template>
<div class="control">
<div class="tags has-addons">
<a class="tag is-primary" @click="isAddingDeadmanAbility = true; isAddingBootstrapAbility = false" x-show="!isAddingDeadmanAbility">
<span class="icon"><i class="fas fa-plus"></i></span>
</a>
</div>
</div>
</div>
<div class="field has-addons" x-show="isAddingDeadmanAbility">
<div class="control is-expanded auto-complete" x-data="{focusSearchResults: true}" @click.outside="focusSearchResults = false">
<input class="input is-small" placeholder="Search for an ability..." x-model="abilitySearchQuery" x-on:keyup="searchForAbility()" @click="focusSearchResults = true">
<div class="search-results is-size-7" x-show="abilitySearchResults && focusSearchResults">
<template x-for="result of abilitySearchResults" :key="result.ability_id">
<p @click="addDeadmanAbility(result.ability_id)" x-text="result.name"></p>
</template>
</div>
</div>
<div class="control">
<a class="button is-small" @click="isAddingDeadmanAbility = false">Cancel</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<p class="help">* A global setting that will effect any new or existing agents.</p>
</section>
<footer class="modal-card-foot">
<nav class="level">
<div class="level-left"></div>
<div class="level-right">
<div class="level-item">
<button class="button is-small" @click="showConfigModal = false">Close</button>
</div>
<div class="level-item">
<button class="button is-primary is-small" @click="saveAgentConfig()">
<span class="icon"><i class="fas fa-save"></i></span>
<span>Save</span>
</button>
<div>
</div>
</nav>
</footer>
</div>
</div>
<div class="modal" x-bind:class="{ 'is-active': showDetailsModal }">
<div class="modal-background" @click="showDetailsModal = false"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Agent Details</p>
</header>
<section class="modal-card-body">
<p class="has-text-weight-bold">Settings</p>
<table>
<col width="30%">
<col width="70%">
<tbody>
<tr>
<th class="has-text-right pt-3">Contact</th>
<td>
<div class="select is-small">
<select x-model="selectedAgent.pending_contact">
<template x-for="contact of selectedAgent.available_contacts" :key="contact">
<option x-bind:value="contact" x-text="contact"></option>
</template>
</select>
</div>
</td>
</tr>
<tr>
<th class="has-text-right pt-3">Group</th>
<td>
<input class="input is-small" type="text" x-model="selectedAgent.group" x-bind:class="{ 'is-danger': fieldErrorsAgent.includes('group') }">
<p x-show="fieldErrorsAgent.includes('group')" class="help is-danger">This field is required.</p>
</td>
</tr>
<tr>
<th class="has-text-right pt-3">Sleep Timer</th>
<td>
<div class="is-align-items-center is-flex is-align-items-center">
<span class="top-right-anchor">
<label>min</label>
<input class="input is-small is-small-number" type="number" placeholder="30" x-model="selectedAgent.sleep_min" x-bind:class="{ 'is-danger': fieldErrorsAgent.includes('sleep_min') }">
</span>
<span class="top-right-anchor">
<label>max</label>
<input class="input is-small is-small-number" type="number" placeholder="60" x-model="selectedAgent.sleep_max" x-bind:class="{ 'is-danger': fieldErrorsAgent.includes('sleep_max') }">
</span>
</div>
<p x-show="fieldErrorsAgent.includes('sleep_min') || fieldErrorsAgent.includes('sleep_max')" class="help is-danger">This field is required.</p>
</td>
</tr>
<tr>
<th class="has-text-right pt-3">Watchdog Timer</th>
<td>
<input class="input is-small is-small-number" type="number" x-model="selectedAgent.watchdog" x-bind:class="{ 'is-danger': fieldErrorsAgent.includes('watchdog') }">
<p x-show="fieldErrorsAgent.includes('watchdog')" class="help is-danger">This field is required.</p>
</td>
</tr>
</tbody>
</table>
<button class="button is-small is-fullwidth is-primary" @click="saveAgent()">Save Settings</button>
<hr>
<p class="has-text-weight-bold">Agent Details</p>
<table>
<col width="30%">
<col width="70%">
<tbody>
<tr>
<th class="has-text-right">Status</th>
<td>
<span x-text="getAgentStatus(selectedAgent)" x-bind:class="{ 'has-text-warning': getAgentStatus(selectedAgent) === 'dead', 'has-text-success': getAgentStatus(selectedAgent) === 'alive', 'has-text-info': getAgentStatus(selectedAgent) === 'pending kill' }"></span><span>,</span>
<span x-text="selectedAgent.trusted ? 'trusted' : 'untrusted'" x-bind:class="{ 'has-text-warning': !selectedAgent.trusted, 'has-text-success': selectedAgent.trusted }"></span>
</td>
</tr>
<tr>
<th class="has-text-right">Paw</th>
<td x-text="selectedAgent.paw"></td>
</tr>
<tr>
<th class="has-text-right">Host</th>
<td x-text="`${selectedAgent.host} (${selectedAgent.host_ip_addrs ? selectedAgent.host_ip_addrs.join(', ') : ''})`"></td>
</tr>
<tr>
<th class="has-text-right">Display Name</th>
<td x-text="selectedAgent.display_name"></td>
</tr>
<tr>
<th class="has-text-right">Username</th>
<td x-text="selectedAgent.username"></td>
</tr>
<tr>
<th class="has-text-right">Privilege</th>
<td x-text="selectedAgent.privilege"></td>
</tr>
<tr>
<th class="has-text-right">Last Seen</th>
<td x-text="selectedAgent.last_seen"></td>
</tr>
<tr>
<th class="has-text-right">Created</th>
<td x-text="selectedAgent.created"></td>
</tr>
<tr>
<th class="has-text-right">Architecture</th>
<td x-text="selectedAgent.architecture"></td>
</tr>
<tr>
<th class="has-text-right">Platform</th>
<td x-text="selectedAgent.platform"></td>
</tr>
<tr>
<th class="has-text-right">PID</th>
<td x-text="selectedAgent.pid"></td>
</tr>
<tr>
<th class="has-text-right">PPID</th>
<td x-text="selectedAgent.ppid"></td>
</tr>
<tr>
<th class="has-text-right">Executable Name</th>
<td x-text="selectedAgent.exe_name"></td>
</tr>
<tr>
<th class="has-text-right">Location</th>
<td x-text="selectedAgent.location"></td>
</tr>
<tr>
<th class="has-text-right">Executors</th>
<td x-text="selectedAgent.executors"></td>
</tr>
<tr>
<th class="has-text-right">Host IP Addresses</th>
<td x-text="selectedAgent.host_ip_addrs"></td>
</tr>
<tr>
<th class="has-text-right">Peer-to-Peer Proxy Receivers</th>
<td x-text="(selectedAgent.proxy_receivers && Object.keys(selectedAgent.proxy_receivers).length) ? Object.keys(selectedAgent.proxy_receivers) : 'No local P2P proxy receivers active.'"></td>
</tr>
<tr>
<th class="has-text-right">Peer-to-Peer Proxy Chain</th>
<td x-text="(selectedAgent.proxy_chain && selectedAgent.proxy_chain.length) ? selectedAgent.proxy_chain.join(', ') : 'Not using P2P agents to reach C2.'"></td>
</tr>
</tbody>
</table>
</section>
<footer class="modal-card-foot">
<nav class="level">
<div class="level-left">
<div class="level-item">
<button class="button is-danger is-outlined is-small has-text-white" @click="confirmAlt(`Are you sure you want to kill agent '${selectedAgent.paw}'?`, () => killAgent(selectedAgent))">
<span class="icon"><i class="fas fa-skull-crossbones"></i></span>
<span>Kill Agent</span>
</button>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-small" @click="showDetailsModal = false">Close</button>
<div>
</div>
</nav>
</footer>
</div>
</div>
<div class="modal" x-bind:class="{ 'is-active': showConfirmModal }">
<div class="modal-background" @click="showConfirmModal = false"></div>
<div class="modal-card" style="z-index: 20">
<header class="modal-card-head">
<p class="modal-card-title">Confirm</p>
</header>
<section class="modal-card-body">
<p x-text="confirmText"></p>
</section>
<footer class="modal-card-foot">
<nav class="level">
<div class="level-left">
<div class="level-item">
<button class="button is-small" @click="showConfirmModal = false">Cancel</button>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-primary is-small" @click="confirmFunction(); showConfirmModal = false;">Confirm</button>
<div>
</div>
</nav>
</footer>
</div>
</div>
</div>
<script>
function alpineAgents() {
return {
// Core variables
agents: [],
abilities: [],
deploymentAbilities: [],
selectedAbilityId: '',
selectedAbility: {},
platforms: [],
selectedPlatform: '',
selectedAgent: { available_contacts: [] },
agentConfig: { bootstrap_abilities: [] },
agentFields: [],
agentFieldConfig: {},
deployCommands: [],
filteredCommands: [],
bootstrapAbilities: [],
deadmanAbilities: [],
isAddingBootstrapAbility: false,
isAddingDeadmanAbility: false,
abilitySearchQuery: [],
abilitySearchResults: [],
isHoveringAgentCount: false,
// Modals
showDeployModal: false,
showConfigModal: false,
showDetailsModal: false,
showConfirmModal: false,
confirmText: '',
confirmFunction: () => {},
// Input validation
requiredFieldsConfig: ['sleep_min', 'sleep_max', 'watchdog', 'untrusted_timer', 'implant_name'],
fieldErrorsConfig: [],
requiredFieldsAgent: ['group', 'sleep_min', 'sleep_max', 'watchdog'],
fieldErrorsAgent: [],
initPage() {
apiV2('GET', '/api/v2/agents').then((agents) => {
this.agents = agents;
this.sortAgents();
return apiV2('GET', '/api/v2/config/agents');
}).then((agentConfig) => {
this.agentConfig = agentConfig;
this.bootstrapAbilities = this.agentConfig.bootstrap_abilities || [];
this.deadmanAbilities = this.agentConfig.deadman_abilities || [];
return apiV2('GET', '/api/v2/abilities');
}).then(async (allAbilities) => {
this.abilities = allAbilities;
this.agentConfig.deployments.forEach((abilityId) => {
let match = allAbilities.find((ability) => ability.ability_id === abilityId);
if (match) this.deploymentAbilities.push(match);
});
// While the agents tab is open, keep checking for new/killed agents
while (this.$refs.header) {
await sleep(3000);
this.refreshAgents();
}
}).catch((error) => {
toast('Error initializing page', false);
console.error(`Error initializing page: ${error}`);
});
},
refreshAgents() {
apiV2('GET', '/api/v2/agents').then((agents) => {
this.agents = agents;
this.sortAgents();
}).catch((error) => {
toast('Error refreshing agents', false);
console.error(error);
});
},
sortAgents() {
this.agents.sort((a, b) => new Date(b.last_seen.replace(/-/g, "/")).getTime() - new Date(a.last_seen.replace(/-/g, "/")).getTime());
},
selectAbility() {
this.selectedAbility = this.deploymentAbilities.find((ability) => ability.ability_id === this.selectedAbilityId);
this.platforms = [...new Set(this.selectedAbility.executors.map((executor) => executor.platform))];
if (!this.platforms.includes(this.selectedPlatform)) this.selectedPlatform = '';
apiV2('GET', `/api/v2/deploy_commands/${this.selectedAbilityId}`).then((data) => {
this.deployCommands = data.abilities;
this.agentFieldConfig = data.app_config;
this.filterAbilityPlatforms();
}).catch((error) => {
toast('Error loading the agent.', false);
console.error(`Error loading the agent: ${error}`);
});
},
changePlatform(platform) {
this.selectedPlatform = platform;
this.filterAbilityPlatforms();
},
filterAbilityPlatforms() {
if (this.selectedPlatform === 'all') {
this.filteredCommands = this.deployCommands;
} else {
this.filteredCommands = this.deployCommands.filter((ability) => ability.platform === this.selectedPlatform);
}
let fields = [];
this.agentFields = [];
this.filteredCommands.forEach((command) => {
fields = fields.concat([...command.command.matchAll(/#{(.*?)}/gm)].map((field) => field[1]));
command.variations.forEach((variation) => {
fields = fields.concat([...variation.command.matchAll(/#{(.*?)}/gm)].map((field) => field[1]));
})
});
fields = [...new Set(fields)];
fields.forEach((field) => {
this.agentFields.push({ name: field, value: this.agentFieldConfig.hasOwnProperty(field) ? this.agentFieldConfig[field] : "" });
});
},
closeDeployModal() {
this.showDeployModal = false;
this.selectedAbilityId = '';
this.selectedPlatform = '';
this.filteredCommands = [];
this.agentFields = [];
},
getCommandField(command) {
this.agentFields.forEach((field) => {
command = command.replaceAll(`#{${field.name}}`, field.value);
});
return command;
},
copyCommandToClipboard(command) {
navigator.clipboard.writeText(command.replaceAll('\n', ''));
toast('Copied command to clipboard!', true);
},
getAbilityName(id) {
let abil = this.abilities.find((ability) => ability.ability_id === id);
return (!id || !this.abilities.length || !abil) ? '[unknown]' : abil.name;
},
getAbilityDescription(id) {
let abil = this.abilities.find((ability) => ability.ability_id === id);
return (!id || !this.abilities.length || !abil) ? 'The current user does not have permissions to view this ability.' : abil.description;
},
searchForAbility() {
this.abilitySearchResults = [];
if (!this.abilitySearchQuery) return;
this.abilities.forEach((ability) => {
if (ability.name.toLowerCase().indexOf(this.abilitySearchQuery.toLowerCase()) > -1) {
this.abilitySearchResults.push({
ability_id: ability.ability_id,
name: ability.name
});
}
});
},
addBootstrapAbility(id) {
this.bootstrapAbilities.push(id);
this.abilitySearchQuery = '';
this.abilitySearchResults = [];
this.isAddingBootstrapAbility = false;
},
addDeadmanAbility(id) {
this.deadmanAbilities.push(id);
this.abilitySearchQuery = '';
this.abilitySearchResults = [];
this.isAddingDeadmanAbility = false;
},
getAgentStatus(agent) {
if (!agent.last_seen) return '';
let lastSeen = new Date(agent.last_seen).getTime();
let msSinceSeen = Date.now() - lastSeen;
// Give a buffer of 1 minute to mark an agent dead
let isAlive = (msSinceSeen < (agent.sleep_max * 1000));
if (msSinceSeen <= 60000 && agent.sleep_min === 3 && agent.sleep_max === 3 && agent.watchdog === 1) {
return 'pending kill'
} else {
return msSinceSeen <= 60000 || isAlive ? 'alive' : 'dead';
}
},
saveAgent() {
this.fieldErrorsAgent = validateInputs(this.selectedAgent, this.requiredFieldsAgent);
if (this.fieldErrorsAgent.length) return;
apiV2('PATCH', `/api/v2/agents/${this.selectedAgent.paw}`, this.selectedAgent).then((response) => {
toast('Agent saved', true);
}).catch((error) => {
toast('Error killing the agent', false);
console.error(error);
});
},
saveAgentConfig() {
this.fieldErrorsConfig = validateInputs(this.agentConfig, this.requiredFieldsConfig);
if (this.fieldErrorsConfig.length) return;
let reqBody = {
...this.agentConfig,
bootstrap_abilities: this.bootstrapAbilities,
deadman_abilities: this.deadmanAbilities
};
delete reqBody.deployments;
apiV2('PATCH', '/api/v2/config/agents', reqBody).then((response) => {
this.showConfigModal = false;
toast('Configuration saved.', true);
}).catch((error) => {
toast('Error saving the configuration.', false);
console.error(error);
});
},
removeAgent(paw, index) {
apiV2('DELETE', `/api/v2/agents/${paw}`).then(() => {
this.agents.splice(index, 1);
}).catch((error) => {
toast('Error removing the agent', false);
console.error(error);
});
},
confirmAlt(msg, fn) {
this.confirmText = msg;
this.confirmFunction = fn;
this.showConfirmModal = true;
},
removeDeadAgents() {
this.agents.forEach((agent, index) => {
if (this.getAgentStatus(agent) === 'dead') {
this.removeAgent(agent.paw, index);
}
});
},
removeAllAgents() {
this.agents.forEach((agent, index) => {
this.removeAgent(agent.paw, index);
});
},
killAgent(agent) {
let reqBody = {
watchdog: 1,
sleep_min: 3,
sleep_max: 3
};
apiV2('PATCH', `/api/v2/agents/${agent.paw}`, reqBody).then((response) => {
toast('Agent will be killed after its next beacon.', true);
}).catch((error) => {
toast('Error killing the agent', false);
console.error(error);
});
this.showDetailsModal = false;
},
killAllAgents() {
let reqBody = {
watchdog: 1,
sleep_min: 3,
sleep_max: 3
};
let numKilled = 0;
this.agents.forEach((agent, index) => {
apiV2('PATCH', `/api/v2/agents/${agent.paw}`, reqBody).then((response) => {
numKilled++;
if (numKilled === this.agents.length) {
toast('All agents have been configured to be killed by next beacon.', true);
}
}).catch((error) => {
toast('Error killing the agent', false);
console.error(error);
});
});
}
};
}
// # sourceURL=agents.js
</script>
<style>
pre.box {
margin-bottom: 0;
margin-top: 10px;
scrollbar-color: #3f3f3f #0000;
scrollbar-width: thin;
}
.command-container {
background-color: #262626;
border-radius: 4px;
margin: 30px;
}
.command-fields {
padding: 0 250px;
}
.is-small-number {
width: 80px;
}
.is-white {
background-color: #636363;
}
.is-white:hover {
background-color: #505050 !important;
}
.cmd-copy-button {
position: absolute;
top: 10px;
right: 10px;
}
.platform {
width: 80px;
border-radius: 4px;
padding: 5px;
margin: 5px;
font-family: monospace;
border: 1px solid transparent;
}
.platform:hover {
background-color: #484848;
cursor: pointer;
}
.platform.selected {
border: 1px solid white;
}
.pointer {
cursor: pointer;
}
.tag.blue {
background-color: #191970 !important;
}
.tag.red {
background-color: #8B0000 !important;
}
.top-right-anchor > label {
position: relative;
top: -14px;
left: 75%;
color: #a0a0a0;
z-index: 999;
font-size: .8em;
}
.top-right-anchor {
margin-left: -26px;
padding-right: 5px;
}
</style>