| <div x-data="alpineExfilled()" x-init="initPage()"> |
| <div x-ref="headerFiles"> |
| <h2>Exfilled Files</h2> |
| <p> |
| Exfilled files are uploaded from an agent on a host to the server during an operation are by default stored on the server at <span x-text="exfilDir"></span>. |
| <br> |
| If an operation has exfilled files, you can view and download the files here. |
| </p> |
| </div> |
| <hr> |
| <div> |
| <div class="buttons" x-show="numFiles !== 0"> |
| <button class="button is-primary is-small" @click="toggleAllFiles(selectedFiles.length < numFiles)"> |
| <span x-text="selectedFiles.length < numFiles ? 'Select' : 'Unselect'"></span> All Files |
| </button> |
| <button class="button is-primary is-small" x-bind:disabled="!selectedFiles.length" @click="downloadFiles"> |
| <span class="icon"><i class="fas fa-save"></i></span> |
| <span>Download</span> |
| </button> |
| </div> |
| <p x-show="numFiles === 0"> |
| The exfill directory (<span x-text="exfilDir"></span>) is empty. |
| </p> |
| <ul class="tree" x-show="numFiles !== 0"> |
| <li class="root"> |
| <span class="icon is-small"><i class="far fa-folder-open"></i></span> |
| <span x-text="exfilDir"></span> |
| </li> |
| <template x-for="agentName of Object.keys(files)" :key="agentName"> |
| <li class="root-child"> |
| <span class="icon is-small"><i class="far fa-folder-open"></i></span> |
| <span x-text="agentName"></span> |
| <template x-for="operation of Object.keys(files[agentName])" :key="operation"> |
| <ul class="tree"> |
| <li class="agentName-child"> |
| <span class="icon is-small"><i class="fas fa-dragon"></i></span> |
| <span x-text="operation"></span> |
| <ul class="tree"> |
| <template x-for="filename of Object.keys(files[agentName][operation])" :key="filename"> |
| <li class="agent-child"> |
| <label class="checkbox"> |
| <input type="checkbox" class="file-checkbox" x-bind:value="files[agentName][operation][filename]" @click="toggleFile($el, files[agentName][operation][filename])"> |
| <span class="icon is-small"><i class="far fa-file-alt"></i></span> |
| <span x-text="filename"></span> |
| </label> |
| </li> |
| </template> |
| <li class="agent-child" x-show="!Object.keys(files[agentName][operation]).length"> |
| (none) |
| </li> |
| </ul> |
| </li> |
| </ul> |
| </template> |
| </li> |
| </template> |
| </ul> |
| </div> |
| </div> |
| |
| <script> |
| function alpineExfilled() { |
| return { |
| exfilDir: '', |
| operations: [], |
| selectedOperationId: '', |
| files: {}, |
| numFiles: 0, |
| selectedFiles: [], |
| |
| initPage() { |
| apiV2('GET', '/api/v2/config/main').then((config) => { |
| this.exfilDir = config.exfil_dir; |
| return apiV2('GET', '/api/v2/operations'); |
| }).then(async (operations) => { |
| this.operations = operations; |
| this.loadFiles(); |
| |
| // While the agents tab is open, keep checking for new/killed agents |
| while (this.$refs.headerFiles) { |
| await sleep(3000); |
| this.loadFiles(); |
| } |
| }).catch((error) => { |
| toast('Error loading page', false); |
| console.error(error); |
| }); |
| }, |
| |
| loadFiles() { |
| restRequest('POST', { index: 'exfil_files', operation_id: this.selectedOperationId }, (files) => { |
| this.files = JSON.parse(files); |
| this.numFiles = 0; |
| if (this.selectedOperationId) { |
| let validHosts = []; |
| this.operations.filter((operation) => operation.id === this.selectedOperationId).forEach((operation) => { |
| operation.host_group.forEach((host) => { |
| validHosts.push(`${host.host}-${host.paw}`); |
| }); |
| }); |
| |
| Object.keys(this.files).forEach((host) => { |
| if (!validHosts.includes(host)) { |
| this.files[host] = {}; |
| } |
| }); |
| } |
| |
| Object.keys(this.files).forEach((agent) => { |
| Object.keys(this.files[agent]).forEach((filename) => this.numFiles++); |
| }); |
| }); |
| }, |
| |
| toggleFile(el, filePath) { |
| if (el.checked) { |
| this.selectedFiles.push(filePath); |
| } else { |
| let index = this.selectedFiles.indexOf(filePath); |
| this.selectedFiles.splice(index, 1); |
| } |
| }, |
| |
| toggleAllFiles(selectAll) { |
| let inputs = Array.from(document.querySelectorAll('.file-checkbox')); |
| |
| if (inputs.every((input) => input.checked)) { |
| inputs.forEach((input) => { |
| input.checked = false; |
| }); |
| this.selectedFiles = []; |
| } else { |
| inputs.forEach((input) => { |
| input.checked = true; |
| }); |
| this.selectedFiles = inputs.map((input) => input.value); |
| } |
| }, |
| |
| downloadFiles() { |
| this.selectedFiles.forEach((filePath) => { |
| let filename = filePath.split(/[\/\\]/); |
| filename = filename[filename.length - 1]; |
| let uri = `/file/download_exfil?file=${btoa(filePath)}`; |
| let downloadAnchorNode = document.createElement('a'); |
| downloadAnchorNode.setAttribute('href', uri); |
| downloadAnchorNode.setAttribute('download', filename); |
| document.body.appendChild(downloadAnchorNode); |
| downloadAnchorNode.click(); |
| downloadAnchorNode.remove(); |
| }); |
| } |
| }; |
| } |
| |
| // # sourceURL=exfilled_files.js |
| </script> |
| |
| <style scoped> |
| #select-operation { |
| max-width: 800px; |
| margin: 0 auto; |
| } |
| |
| ul.tree { |
| margin: 0px 0px 0px 20px; |
| list-style: none !important; |
| line-height: 2em; |
| font-family: Arial; |
| } |
| ul.tree li { |
| font-size: 16px; |
| position: relative; |
| } |
| ul.tree li:before { |
| position: absolute; |
| left: -15px; |
| top: -10px; |
| content: ''; |
| display: block; |
| border-left: 1px solid #ddd; |
| height: 1.7em; |
| border-bottom: 1px solid #ddd; |
| width: 10px; |
| } |
| ul.tree li:after { |
| position: absolute; |
| left: -15px; |
| bottom: -7px; |
| content: ''; |
| display: block; |
| border-left: 1px solid #ddd; |
| height: 100%; |
| } |
| ul.tree li.root { |
| margin: 0px 0px 0px -20px; |
| } |
| ul.tree li.root:before { |
| display: none; |
| } |
| ul.tree li.root:after { |
| display: none; |
| } |
| ul.tree li.root-child:last-child:after { |
| display: none; |
| } |
| ul.tree li.agentName-child:after{ |
| display: none; |
| } |
| ul.tree li.agent-child:nth-last-child(2):after { |
| display: none; |
| } |
| ul.tree li:last-child:after { |
| display: none; |
| } |
| |
| </style> |