From e2147c63e33f0500ffd73d891b9c6548af741cef Mon Sep 17 00:00:00 2001 From: Stephen Tomkinson Date: Mon, 11 Nov 2024 18:01:06 +0000 Subject: [PATCH] Migrate eventstream to use same evidence expansion/styling as cobalt strike for web & PDF --- event_tracker/static/css/event_table.css | 18 +++ event_tracker/static/scripts/event_table.js | 88 +++++++++++++ .../cobalt_strike_monitor/archive_list.html | 119 +++--------------- .../event_tracker/eventstream_list.html | 66 ++++------ event_tracker/views.py | 10 +- pstranscript2eventstream.py | 19 ++- 6 files changed, 157 insertions(+), 163 deletions(-) create mode 100644 event_tracker/static/css/event_table.css create mode 100644 event_tracker/static/scripts/event_table.js diff --git a/event_tracker/static/css/event_table.css b/event_tracker/static/css/event_table.css new file mode 100644 index 0000000..295575f --- /dev/null +++ b/event_tracker/static/css/event_table.css @@ -0,0 +1,18 @@ +.description ~ div, .input ~ div { + margin-top: 0.5rem; + margin-bottom: 0; +} + +.input::before { + content: "\00BB "; +} + +.input, .output { + display: block; + font-family: monospace; + white-space: pre-wrap; +} + +.output { + font-style: italic; +} \ No newline at end of file diff --git a/event_tracker/static/scripts/event_table.js b/event_tracker/static/scripts/event_table.js new file mode 100644 index 0000000..c407398 --- /dev/null +++ b/event_tracker/static/scripts/event_table.js @@ -0,0 +1,88 @@ +pdfMake.preserveLeadingSpaces = true + +function pdfExportCustomize(doc, config, dt) { + pdfMake.fonts = { + RobotoMono: { + normal: eventTableConfig.monospaceFontURL, + }, + Roboto: { + normal: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.66/fonts/Roboto/Roboto-Regular.ttf', + bold: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.66/fonts/Roboto/Roboto-Medium.ttf', + }, + } + + doc.styles['terminal'] = { + font: 'RobotoMono', + bold: false + } + + for (const row of doc.content[1].table.body) { + let description = row[eventTableConfig.descriptionColumn] // column of each row that should be "Description" + const parts = description.text.split("{monospace}") + if (parts.length > 1) { + // Attach 'terminal' style to everything after the first "\n\n" + description.text = [parts[0], parts[0].length ? "\n\n" : "", { + text: parts.slice(1).join("\n\n"), + style: 'terminal', + preserveLeadingSpaces: true + }] + } + } + + // Ensure the main text column doesn't stretch when given long content + doc.content[1].table.widths = Array(eventTableConfig.totalColumns).fill("auto") + doc.content[1].table.widths[eventTableConfig.descriptionColumn] = 400 + // Sprinkle in some corporate branding + doc.footer = function (currentPage, pageCount) { + return [ + { + canvas: [ + {type: 'line', x1: 40, y1: 0, x2: 800, y2: 0, lineWidth: 0.5, lineColor: '#242C7A'} + ] + }, + { + columns: [ + currentPage.toString() + ' / ' + pageCount, + {svg: eventTableConfig.brandingSVG, alignment: 'center'}, + {text: eventTableConfig.brandingText, alignment: 'right'}, + ], + margin: [40, 10], + }, + ] + } +} + +function pdfExportAction (e, dt, node, config, cb) { + let outer_dt = dt; + let outer_config = config; + let orig_len = dt.page.len(); + let outer_cb = cb; + + doExport = function (e, _dt, node, _config, cb) { + // Deregister the event handler + dt.off('draw', doExport); + // Trigger the print action + $.fn.dataTable.ext.buttons.pdfHtml5.action.call(outer_dt.button(), e, outer_dt, node, outer_config, outer_cb); + // Redraw the table at the original page size + dt.page.len(orig_len).draw(); + } + + // Register an event handler to print the table once all the data is loaded + dt.on( 'draw', doExport ) + // Trigger a non-paginated table draw + dt.page.len(-1).draw(); +} + +function descriptionRender (data, type, row) { + if (type === "export") { + // When exporting to PDF, before the HTML is stripped and passed to pdfMake, + // add a double new line to show where the
s end. Used by customize function above. + return data.split(/
/).join("{monospace}") + } else { + return data; + } +} + +function tableDrawCallback (settings) { + $('.output').expander({slicePoint: 200, normalizeWhitespace: false, detailPrefix: '',}); + } \ No newline at end of file diff --git a/event_tracker/templates/cobalt_strike_monitor/archive_list.html b/event_tracker/templates/cobalt_strike_monitor/archive_list.html index 7b411e4..4e8a5a4 100644 --- a/event_tracker/templates/cobalt_strike_monitor/archive_list.html +++ b/event_tracker/templates/cobalt_strike_monitor/archive_list.html @@ -15,6 +15,18 @@ + + + + {% endblock head %} @@ -125,78 +118,10 @@ }, { text: 'Export PDF', - action: function (e, dt, node, config, cb) { - let outer_dt = dt; - let outer_config = config; - let orig_len = dt.page.len(); - let outer_cb = cb; - - doExport = function (e, _dt, node, _config, cb) { - // Deregister the event handler - dt.off('draw', doExport); - // Trigger the print action - $.fn.dataTable.ext.buttons.pdfHtml5.action.call(outer_dt.button(), e, outer_dt, node, outer_config, outer_cb); - // Redraw the table at the original page size - dt.page.len(orig_len).draw(); - } - - // Register an event handler to print the table once all the data is loaded - dt.on( 'draw', doExport ) - // Trigger a non-paginated table draw - dt.page.len(-1).draw(); - }, + action: pdfExportAction, extend: 'pdfHtml5', // Required to pull in default config orientation: "landscape", - customize: function (doc, config, dt) { - pdfMake.fonts = { - RobotoMono: { - normal: '{{request.scheme}}://{{request.META.HTTP_HOST}}{% static "fonts/RobotoMono-Regular.ttf" %}', - }, - Roboto: { - normal: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.66/fonts/Roboto/Roboto-Regular.ttf', - bold: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.66/fonts/Roboto/Roboto-Medium.ttf', - }, - } - - doc.styles['terminal'] = { - font: 'RobotoMono', - bold: false - } - - for (const row of doc.content[1].table.body) { - let description = row[3] // the 4th (i.e. [3]) column of each row should be "Description" - const parts = description.text.split("\n\n") - if (parts.length > 1) { - // Attach 'terminal' style to everything after the first "\n\n" - description.text = [parts[0], "\n\n", { - text: parts.slice(1).join("\n\n"), - style: 'terminal', - preserveLeadingSpaces: true - }] - } - } - - // Ensure the main text column doesn't stretch when given long content - doc.content[1].table.widths=['auto','auto','auto',400,'auto','auto']; - // Sprinkle in some corporate branding - doc.footer = function (currentPage, pageCount) { - return [ - { - canvas: [ - { type: 'line', x1: 40, y1: 0, x2: 800, y2: 0, lineWidth: 0.5, lineColor: '#242C7A'} - ] - }, - { - columns: [ - currentPage.toString() + ' / ' + pageCount, - {svg: '{{REPORT_FOOTER_IMAGE}}', alignment: 'center'}, - {text: '{{REPORT_FOOTER_TEXT}}', alignment: 'right'}, - ], - margin: [40, 10], - }, - ] - } - }, + customize: pdfExportCustomize, title: "Cobalt Strike Logs", exportOptions: { stripNewlines: false, @@ -213,23 +138,11 @@ { searchBuilderType: "moment-{% datetime_format_moment %}" }, { orderable: false }, { orderable: false }, - { - render: function (data, type, row) { - if (type === "export") { - // When exporting to PDF, before the HTML is stripped and passed to pdfMake, - // add a double new line to show where the
s end. Used by customize function above. - return data.split("
").join("
\n\n") - } else { - return data; - } - } - }, + { render: descriptionRender }, null, { orderable: false }, ], - drawCallback: function (settings) { - $('.output').expander({slicePoint: 200, normalizeWhitespace: false, detailPrefix: '',}); - } + drawCallback: tableDrawCallback } ) }) diff --git a/event_tracker/templates/event_tracker/eventstream_list.html b/event_tracker/templates/event_tracker/eventstream_list.html index 347b182..fd2741f 100644 --- a/event_tracker/templates/event_tracker/eventstream_list.html +++ b/event_tracker/templates/event_tracker/eventstream_list.html @@ -13,6 +13,20 @@ {% include "base/external-libs/jquery.html" %} {% include "base/external-libs/datatables-pdfexport.html" %} + + + + + +