From a6a3e8184df146cb8f8c9888c6e99a78abe62a2f Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Thu, 11 Aug 2022 20:57:41 -0400 Subject: [PATCH 1/8] Switch recommonmark to MyST-Parser --- requirements.txt | 4 ++-- server.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0a150ec99..43dcaf5ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,10 +8,10 @@ pyyaml>=5.1 cryptography>=3.2,<37.0.0; python_version <= '3.7' cryptography>=3.2; python_version > '3.7' websockets>=10.3 -Sphinx==3.0.4 +Sphinx==5.1.1 docutils==0.16 # Broken bullet lists in sphinx_rtd_theme https://github.com/readthedocs/sphinx_rtd_theme/issues/1115 sphinx_rtd_theme==0.4.3 -recommonmark==0.6.0 +myst-parser==0.18.0 marshmallow==3.5.1 dirhash==0.2.0 docker==4.2.0 diff --git a/server.py b/server.py index 0b9113a14..8389a32e2 100755 --- a/server.py +++ b/server.py @@ -40,6 +40,7 @@ def setup_logger(level=logging.DEBUG): continue else: logging.getLogger(logger_name).setLevel(100) + logging.getLogger("markdown_it").setLevel(logging.WARNING) logging.captureWarnings(True) From c04b5ad2b162c03ed6ef3947ec8b7de05471f3f7 Mon Sep 17 00:00:00 2001 From: Adam Gaudreau Date: Wed, 17 Aug 2022 09:04:31 -0400 Subject: [PATCH 2/8] Sanitize inputs to toast fn to mitigate XSS attacks --- static/js/shared.js | 70 ++++------------------------------- templates/configurations.html | 1 + templates/operations.html | 4 +- 3 files changed, 11 insertions(+), 64 deletions(-) diff --git a/static/js/shared.js b/static/js/shared.js index 46b33ae3c..d8ef4591e 100644 --- a/static/js/shared.js +++ b/static/js/shared.js @@ -192,9 +192,15 @@ function sortAlphabetically(list) { }) } +function sanitize(unsafeMsg) { + const parser = new DOMParser(); + let doc = parser.parseFromString(unsafeMsg, 'text/html'); + return doc.body.innerText; +} + function toast(message, success) { bulmaToast.toast({ - message: ` ${message}`, + message: ` ${sanitize(message)}`, type: `toast ${success ? 'is-success' : 'is-danger'}`, position: 'bottom-right', duration: '3000', @@ -250,41 +256,6 @@ function uuidv4() { }); } -// TODO: JQuery functions - -function showHide(show, hide) { - $(show).each(function () { - $(this).prop('disabled', false).css('opacity', 1.0) - }); - $(hide).each(function () { - $(this).prop('disabled', true).css('opacity', 0.5) - }); -} - -function validateFormState(conditions, selector) { - (conditions) ? - updateButtonState(selector, 'valid') : - updateButtonState(selector, 'invalid'); -} - -function updateButtonState(selector, state) { - (state === 'valid') ? - $(selector).attr('class', 'button-success atomic-button') : - $(selector).attr('class', 'button-notready atomic-button'); -} - -function stream(msg, speak = false) { - let streamer = $('#streamer'); - if (streamer.text() != msg) { - streamer.fadeOut(function () { - if (speak) { - window.speechSynthesis.speak(new SpeechSynthesisUtterance(msg)); - } - $(this).text(msg).fadeIn(1000); - }); - } -} - /* SECTIONS */ // Alternative to JQuery parseHTML(keepScripts=true) @@ -308,33 +279,6 @@ function removeSection(identifier) { $('#' + identifier).remove(); } -/* AUTOMATIC functions for all pages */ - -$(document).ready(function () { - $(document).find("select").each(function () { - if (!$(this).hasClass('avoid-alphabetizing')) { - alphabetize_dropdown($(this)); - let observer = new MutationObserver(function (mutations, obs) { - obs.disconnect(); - alphabetize_dropdown($(mutations[0].target)); - obs.observe(mutations[0].target, {childList: true}); - }); - observer.observe(this, {childList: true}); - } - }); -}); - -function alphabetize_dropdown(obj) { - let selected_val = $(obj).children("option:selected").val(); - let disabled = $(obj).find('option:disabled'); - let opts_list = $(obj).find('option:enabled').clone(true); - opts_list.sort(function (a, b) { - return a.text.toLowerCase() == b.text.toLowerCase() ? 0 : a.text.toLowerCase() < b.text.toLowerCase() ? -1 : 1; - }); - $(obj).empty().append(opts_list).prepend(disabled); - obj.val(selected_val); -} - function b64DecodeUnicode(str) { //https://stackoverflow.com/a/30106551 if (str != null) { // An error check is needed in case the wrong codec (i.e. not UTF-8) was used at source diff --git a/templates/configurations.html b/templates/configurations.html index f88fe3e0a..bf20c64ee 100644 --- a/templates/configurations.html +++ b/templates/configurations.html @@ -114,6 +114,7 @@

Configuration

}, enablePlugin(pluginName) { + pluginName = sanitize(pluginName); let requestBody = { value: pluginName, prop: 'plugin' diff --git a/templates/operations.html b/templates/operations.html index 4d08d44f9..b79a4e3fe 100644 --- a/templates/operations.html +++ b/templates/operations.html @@ -1435,6 +1435,7 @@

Operations

} else if (!this.isJitterValid()) { toast('Jitter values are invalid', false); } else { + this.operationToStart.name = sanitize(this.operationToStart.name); this.operationToStart.autonomous = Number(this.operationToStart.autonomous); this.operationToStart.use_learning_parsers = parseInt(this.operationToStart.use_learning_parsers, 10) === 1; this.operationToStart.auto_close = parseInt(this.operationToStart.auto_close, 10) === 1; @@ -1458,7 +1459,8 @@

Operations

let stringToReplace = (dateMatches && dateMatches.length) ? dateMatches[dateMatches.length - 1] : ''; let date = `(${new Date().toLocaleString()})`; newOp.name = stringToReplace ? (newOp.name.replace(stringToReplace, date)) : (`${newOp.name} ${date}`); - + newOp.name = sanitize(newOp.name); + apiV2('POST', this.ENDPOINT, newOp).then((newOperation) => { toast(`Started operation ${newOperation.name}!`, true); this.operations.push(newOperation); From 6faf9a83a4d869f39b65438e0fe47af42ab158f2 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Fri, 19 Aug 2022 10:08:56 -0400 Subject: [PATCH 3/8] Repin fieldmanual documentation --- plugins/fieldmanual | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/fieldmanual b/plugins/fieldmanual index 4ac00e405..510d0b9d4 160000 --- a/plugins/fieldmanual +++ b/plugins/fieldmanual @@ -1 +1 @@ -Subproject commit 4ac00e405b3bb8aeb87bb226ac0f08d249adb93d +Subproject commit 510d0b9d48817e508cdbfc0260f088356f8543d5 From 845d3cab37a1db90acfc94d851e3a93978173f3e Mon Sep 17 00:00:00 2001 From: Adam Gaudreau Date: Tue, 23 Aug 2022 10:25:49 -0400 Subject: [PATCH 4/8] Update README's social links --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a779c3ed3..8478dcf66 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ # CALDERA™ -*Full documentation, training and use-cases can be found [here](https://caldera.readthedocs.io/en/latest/).* - CALDERA™ is a cyber security platform designed to easily automate adversary emulation, assist manual red-teams, and automate incident response. It is built on the [MITRE ATT&CK™ framework](https://attack.mitre.org/) and is an active research project at MITRE. @@ -18,6 +16,11 @@ The framework consists of two components: an asynchronous command-and-control (C2) server with a REST API and a web interface. 2) **Plugins**. These repositories expand the core framework capabilities and providing additional functionality. Examples include agents, reporting, collections of TTPs and more. +## Resources and Socials +* 📜 [Documentation, training, and use-cases](https://caldera.readthedocs.io/en/latest/) +* ✍️ [CALDERA's blog](https://medium.com/@mitrecaldera/welcome-to-the-official-mitre-caldera-blog-page-f34c2cdfef09) +* 🌐 [Homepage](https://caldera.mitre.org) + ## Plugins :star: Create your own plugin! Plugin generator: **[Skeleton](https://github.com/mitre/skeleton)** :star: @@ -26,7 +29,6 @@ an asynchronous command-and-control (C2) server with a REST API and a web interf - **[Access](https://github.com/mitre/access)** (red team initial access tools and techniques) - **[Atomic](https://github.com/mitre/atomic)** (Atomic Red Team project TTPs) - **[Builder](https://github.com/mitre/builder)** (dynamically compile payloads) -- **[CalTack](https://github.com/mitre/caltack.git)** (embedded ATT&CK website) - **[Compass](https://github.com/mitre/compass)** (ATT&CK visualizations) - **[Debrief](https://github.com/mitre/debrief)** (operations insights) - **[Emu](https://github.com/mitre/emu)** (CTID emulation plans) @@ -45,6 +47,7 @@ an asynchronous command-and-control (C2) server with a REST API and a web interf These plugins are ready to use but are not included by default: - **[Pathfinder](https://github.com/center-for-threat-informed-defense/caldera_pathfinder)** (vulnerability scanning) - **[SAML](https://github.com/mitre/saml)** (SAML authentication) +- **[CalTack](https://github.com/mitre/caltack.git)** (embedded ATT&CK website) ## Requirements From 51250a0b5e526214622619b598fd22d2e7e3b23c Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Wed, 24 Aug 2022 17:09:03 -0400 Subject: [PATCH 5/8] Set permissions, pin Github Actions by hash --- .github/workflows/greetings.yml | 8 +++++++- .github/workflows/quality.yml | 12 +++++++++--- .github/workflows/security.yml | 7 +++++-- .github/workflows/stale.yml | 9 ++++++++- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml index 9eff78d11..f6ea700ae 100644 --- a/.github/workflows/greetings.yml +++ b/.github/workflows/greetings.yml @@ -2,11 +2,17 @@ name: Greetings on: [pull_request, issues] +permissions: + contents: read + jobs: greeting: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write steps: - - uses: actions/first-interaction@v1 + - uses: actions/first-interaction@bd33205aa5c96838e10fd65df0d01efd613677c1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} issue-message: 'Looks like your first issue -- we aim to respond to issues as quickly as possible. In the meantime, check out our documentation here: http://caldera.readthedocs.io/' diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 91707f2c3..b38501819 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -8,9 +8,15 @@ on: types: [opened, synchronize, reopened, ready_for_review] workflow_dispatch: +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read strategy: fail-fast: false matrix: @@ -25,12 +31,12 @@ jobs: toxenv: py310,style,coverage-ci steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@7884fcad6b5d53d10323aee724dc68d8b9096a2e with: submodules: recursive fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -44,7 +50,7 @@ jobs: - name: Override Coverage Source Path for Sonar run: sed -i "s/\/home\/runner\/work\/caldera\/caldera/\/github\/workspace/g" /home/runner/work/caldera/caldera/coverage.xml - name: SonarCloud Scan - uses: SonarSource/sonarcloud-github-action@master + uses: SonarSource/sonarcloud-github-action@156db6fef3e168e4972abb76de0b32bbce8ec77a env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 062f5200b..56d358062 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -2,6 +2,9 @@ name: Security Checks on: [push] +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest @@ -19,11 +22,11 @@ jobs: toxenv: safety steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@7884fcad6b5d53d10323aee724dc68d8b9096a2e with: submodules: recursive - name: Setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 336fd994c..2d6ad2be7 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -4,13 +4,20 @@ on: schedule: - cron: "0 0 * * *" +permissions: + contents: read + jobs: stale: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: - - uses: actions/stale@v1 + - uses: actions/stale@9c1b1c6e115ca2af09755448e0dbba24e5061cc8 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-label: 'no-issue-activity' From ddcd96c8a86062056bfbd0bb13f74a1b00f47ed5 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 25 Aug 2022 10:31:02 -0400 Subject: [PATCH 6/8] Add workflow for pushing docker image --- .github/workflows/publish_docker_image.yml | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/publish_docker_image.yml diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml new file mode 100644 index 000000000..71063ea55 --- /dev/null +++ b/.github/workflows/publish_docker_image.yml @@ -0,0 +1,41 @@ +name: Create and publish a Docker image + +on: + push: + branches: ['release'] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file From 7d4673ab39139d502aac14fabc8acb06b60288e3 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Sun, 28 Aug 2022 23:00:34 -0400 Subject: [PATCH 7/8] Update docker workflow - Fix trigger - Set top-level permission - Pin checkout action by hash --- .github/workflows/publish_docker_image.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml index 71063ea55..772c09b17 100644 --- a/.github/workflows/publish_docker_image.yml +++ b/.github/workflows/publish_docker_image.yml @@ -1,8 +1,12 @@ name: Create and publish a Docker image on: - push: - branches: ['release'] + release: + types: [published] + workflow_dispatch: + +permissions: + contents: read env: REGISTRY: ghcr.io @@ -17,7 +21,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@7884fcad6b5d53d10323aee724dc68d8b9096a2e - name: Log in to the Container registry uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 @@ -38,4 +42,4 @@ jobs: context: . push: true tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file + labels: ${{ steps.meta.outputs.labels }} From 6eaeb74f16bfd0e726318f973dbeac56f0835049 Mon Sep 17 00:00:00 2001 From: Maria Shkolnik <75180384+mshkolnik22@users.noreply.github.com> Date: Mon, 12 Sep 2022 16:05:32 -0400 Subject: [PATCH 8/8] Displaying un-encoded commands when using obfuscation (#2614) * Separate plaintext and obfuscated command in reports * Fix coverage tests * Add fact_store to gitignore * Add sonar exception to c_link * Edit sonar-project properties Co-authored-by: Zach Palmer Co-authored-by: Adam Gaudreau Co-authored-by: Chris Lenk --- .gitignore | 1 + app/objects/c_operation.py | 2 ++ app/objects/secondclass/c_link.py | 5 +++- app/utility/base_planning_svc.py | 1 + sonar-project.properties | 5 ++++ templates/operations.html | 45 +++++++++++++++++-------------- tests/objects/test_operation.py | 14 ++++++---- 7 files changed, 47 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 21ef2a3bc..7c31b9aac 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ calderaenv/ conf/*.yml !conf/default.yml data/object_store +data/fact_store data/results/* !data/results/.gitkeep data/payloads/* diff --git a/app/objects/c_operation.py b/app/objects/c_operation.py index 493d43e1a..1bfa15a73 100644 --- a/app/objects/c_operation.py +++ b/app/objects/c_operation.py @@ -297,6 +297,7 @@ async def report(self, file_svc, data_svc, output=False): step_report = dict(link_id=step.id, ability_id=step.ability.ability_id, command=step.command, + plaintext_command=step.plaintext_command, delegated=step.decide.strftime(self.TIME_FORMAT), run=step.finish, status=step.status, @@ -361,6 +362,7 @@ async def _load_objective(self, data_svc): async def _convert_link_to_event_log(self, link, file_svc, data_svc, output=False): event_dict = dict(command=link.command, + plaintext_command=link.plaintext_command, delegated_timestamp=link.decide.strftime(self.TIME_FORMAT), collected_timestamp=link.collect.strftime(self.TIME_FORMAT) if link.collect else None, finished_timestamp=link.finish, diff --git a/app/objects/secondclass/c_link.py b/app/objects/secondclass/c_link.py index d7f2d4bac..4f6438c97 100644 --- a/app/objects/secondclass/c_link.py +++ b/app/objects/secondclass/c_link.py @@ -28,6 +28,7 @@ class Meta: id = ma.fields.String(missing='') paw = ma.fields.String() command = ma.fields.String() + plaintext_command = ma.fields.String() status = ma.fields.Integer(missing=-3) score = ma.fields.Integer(missing=0) jitter = ma.fields.Integer(missing=0) @@ -151,11 +152,12 @@ def status(self, value): def is_global_variable(cls, variable): return variable in cls.RESERVED - def __init__(self, command='', paw='', ability=None, executor=None, status=-3, score=0, jitter=0, cleanup=0, id='', + def __init__(self, command='', plaintext_command='', paw='', ability=None, executor=None, status=-3, score=0, jitter=0, cleanup=0, id='', pin=0, host=None, deadman=False, used=None, relationships=None, agent_reported_time=None): super().__init__() self.id = str(id) self.command = command + self.plaintext_command = plaintext_command self.command_hash = None self.paw = paw self.host = host @@ -222,6 +224,7 @@ def is_valid_status(self, status): def replace_origin_link_id(self): decoded_cmd = self.decode_bytes(self.command) self.command = self.encode_string(decoded_cmd.replace(self.RESERVED['origin_link_id'], self.id)) + self.plaintext_command = decoded_cmd def _emit_status_change_event(self, from_status, to_status): event_svc = BaseService.get_service('event_svc') diff --git a/app/utility/base_planning_svc.py b/app/utility/base_planning_svc.py index 264c1560a..50b500e64 100644 --- a/app/utility/base_planning_svc.py +++ b/app/utility/base_planning_svc.py @@ -186,6 +186,7 @@ async def obfuscate_commands(self, agent, obfuscator, links): o = (await self.get_service('data_svc').locate('obfuscators', match=dict(name=obfuscator)))[0] mod = o.load(agent) for s_link in links: + s_link.plaintext_command = s_link.command s_link.command = self.encode_string(mod.run(s_link)) return links diff --git a/sonar-project.properties b/sonar-project.properties index f0fe91ae4..48852ae9f 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -13,3 +13,8 @@ sonar.sources=./app sonar.python.version=3.7,3.8,3.9,3.10 sonar.python.coverage.reportPaths=coverage.xml + +# Make an exception to Link's constructor, since it requires a refactor to pass +sonar.issue.ignore.multicriteria=e1 +sonar.issue.ignore.multicriteria.e1.ruleKey=python:S107 +sonar.issue.ignore.multicriteria.e1.resourceKey=app/objects/secondclass/c_link.py diff --git a/templates/operations.html b/templates/operations.html index b79a4e3fe..ca6b6fccf 100644 --- a/templates/operations.html +++ b/templates/operations.html @@ -462,7 +462,19 @@

Operations

@@ -1204,6 +1216,7 @@

Operations

selectedLink: null, selectedLinkResults: null, selectedLinkFacts: null, + selectedLinkPlaintextCommand: null, selectedLinkCommand: null, selectedReportType: 'full-report', isAgentOutputSelected: false, @@ -1675,12 +1688,14 @@

Operations

this.selectedLinkFacts = Array.from(new Set(res.facts)).sort((a, b) => b.score - a.score); } this.selectedLinkCommand = b64DecodeUnicode(res.command); + this.selectedLinkPlaintextCommand = b64DecodeUnicode(res.plaintext_command); this.editableCommand = b64DecodeUnicode(res.command); }) .catch(() => { this.selectedLinkResults = null; this.selectedLinkFacts = null; this.selectedLinkCommand = null; + this.selectedLinkPlaintextCommand = null; toast('Error getting link results.'); }); }, @@ -1760,27 +1775,17 @@

Operations

}, downloadCSV() { - const csv = []; - const rows = document.querySelector('#operationsPage').querySelectorAll('table tr:not(.csv-exclude)'); + let csv = [ 'Decide,Link/Ability Name,Agent #paw,Host,pid,Command,Plaintext Command' ]; - rows.forEach((row, i) => { - const cols = row.querySelectorAll('td:not(.csv-exclude), th:not(.csv-exclude)'); + this.selectedOperation.chain.forEach(async (link) => { const rowItems = []; - cols.forEach((col) => { - const link = this.selectedOperation.chain[i - 1]; - if (col.className.includes('agentDetails')) { - let agentDetails = `Agent #${link.paw}`; - if (link.host) agentDetails += `\nHost: ${link.host}`; - if (link.pid) agentDetails += `\nPid: ${link.pid}`; - rowItems.push(`"${agentDetails}"`); - } else if (col.className.includes('commandDetails')) { - rowItems.push(`"${b64DecodeUnicode(link.command)}"`); - } else if (col.className.includes('outputDetails')) { - rowItems.push(`"${link.facts}"`); - } else { - rowItems.push(`"${col.innerText}"`); - } - }); + rowItems.push(link.decide); + rowItems.push(link.ability.name); + rowItems.push(link.paw); + rowItems.push(link.host); + rowItems.push(link.pid); + rowItems.push(b64DecodeUnicode(link.command)); + rowItems.push(b64DecodeUnicode(link.plaintext_command)); csv.push(rowItems.join(',')); }); this.createDownloadReport(new Blob([csv.join('\n')], { type: 'text/csv' }), this.selectedOperation.name, true); diff --git a/tests/objects/test_operation.py b/tests/objects/test_operation.py index 364e2a439..b933f44c6 100644 --- a/tests/objects/test_operation.py +++ b/tests/objects/test_operation.py @@ -54,8 +54,8 @@ def operation_adversary(adversary): @pytest.fixture def operation_link(): - def _generate_link(command, paw, ability, executor, pid=0, decide=None, collect=None, finish=None, **kwargs): - generated_link = Link(command, paw, ability, executor, **kwargs) + def _generate_link(command, plaintext_command, paw, ability, executor, pid=0, decide=None, collect=None, finish=None, **kwargs): + generated_link = Link(command, plaintext_command, paw, ability, executor, **kwargs) generated_link.pid = pid if decide: generated_link.decide = decide @@ -88,17 +88,17 @@ def op_for_event_logs(operation_agent, operation_adversary, executor, ability, o ability_2 = ability(ability_id='456', tactic='test tactic', technique_id='T0000', technique_name='test technique', name='test ability 2', description='test ability 2 desc', executors=[executor_2]) link_1 = operation_link(ability=ability_1, paw=operation_agent.paw, executor=executor_1, - command=encoded_command(command_1), status=0, host=operation_agent.host, pid=789, + command=encoded_command(command_1), plaintext_command=encoded_command(command_1), status=0, host=operation_agent.host, pid=789, decide=parse_datestring(LINK1_DECIDE_TIME), collect=parse_datestring(LINK1_COLLECT_TIME), finish=LINK1_FINISH_TIME) link_2 = operation_link(ability=ability_2, paw=operation_agent.paw, executor=executor_2, - command=encoded_command(command_2), status=0, host=operation_agent.host, pid=7890, + command=encoded_command(command_2), plaintext_command=encoded_command(command_2), status=0, host=operation_agent.host, pid=7890, decide=parse_datestring(LINK2_DECIDE_TIME), collect=parse_datestring(LINK2_COLLECT_TIME), finish=LINK2_FINISH_TIME) discarded_link = operation_link(ability=ability_2, paw=operation_agent.paw, executor=executor_2, - command=encoded_command(command_2), status=-2, host=operation_agent.host, pid=7891, + command=encoded_command(command_2), plaintext_command=encoded_command(command_2), status=-2, host=operation_agent.host, pid=7891, decide=parse_datestring('2021-01-01T10:00:00Z')) op.chain = [link_1, link_2, discarded_link] return op @@ -223,6 +223,7 @@ def test_event_logs(self, event_loop, op_for_event_logs, operation_agent, file_s want = [ dict( command='d2hvYW1p', + plaintext_command='d2hvYW1p', delegated_timestamp=LINK1_DECIDE_TIME, collected_timestamp=LINK1_COLLECT_TIME, finished_timestamp=LINK1_FINISH_TIME, @@ -241,6 +242,7 @@ def test_event_logs(self, event_loop, op_for_event_logs, operation_agent, file_s ), dict( command='aG9zdG5hbWU=', + plaintext_command='aG9zdG5hbWU=', delegated_timestamp=LINK2_DECIDE_TIME, collected_timestamp=LINK2_COLLECT_TIME, finished_timestamp=LINK2_FINISH_TIME, @@ -292,6 +294,7 @@ def test_writing_event_logs_to_disk(self, event_loop, op_for_event_logs, operati want = [ dict( command='d2hvYW1p', + plaintext_command='d2hvYW1p', delegated_timestamp=LINK1_DECIDE_TIME, collected_timestamp=LINK1_COLLECT_TIME, finished_timestamp=LINK1_FINISH_TIME, @@ -310,6 +313,7 @@ def test_writing_event_logs_to_disk(self, event_loop, op_for_event_logs, operati ), dict( command='aG9zdG5hbWU=', + plaintext_command='aG9zdG5hbWU=', delegated_timestamp=LINK2_DECIDE_TIME, collected_timestamp=LINK2_COLLECT_TIME, finished_timestamp=LINK2_FINISH_TIME,