| <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> alive · |
| <span class="has-text-weight-bold" x-text="agents.filter((a) => a.trusted).length"></span> trusted |
| </span> |
| <strong x-text="agents.length"></strong> 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> dead · |
| <span class="has-text-weight-bold" x-text="agents.filter((a) => !a.trusted).length"></span> 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> |