Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: c2-event evidence type #1086

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions backend/services/evidence.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ func CreateEvidence(ctx context.Context, db *database.Connection, contentStore c
fallthrough
case "codeblock":
fallthrough
case "c2-event":
fallthrough
case "event":
content = contentstore.NewBlob(i.Content)

Expand Down Expand Up @@ -443,6 +445,8 @@ func UpdateEvidence(ctx context.Context, db *database.Connection, contentStore c
fallthrough
case "codeblock":
fallthrough
case "c2-event":
fallthrough
case "terminal-recording":
content := contentstore.NewBlob(i.Content)
processedKeys, err := content.ProcessPreviewAndUpload(contentStore)
Expand Down
17 changes: 8 additions & 9 deletions frontend/public/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<title>ASHIRT</title>
<link rel="stylesheet" href="/assets/main.css">
</head>
<body>
<script src="/assets/main.js"></script>
</body>
</html>
<head>
<meta charset="utf-8">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the modified version of the file that is generated by webpack after it generates the css and js. We don't want to include this version in the PR.

<title>ASHIRT</title>
<meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="/assets/main-85d25d758e04b71e4c3e.js"></script><link href="/assets/main-b0ad39be34ceaa80dd41.css" rel="stylesheet"></head>
<body>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type EvidenceTypeOption = BulletProps & {
export const supportedEvidenceCount: Array<EvidenceTypeOption> = [
{ name: 'Screenshot', id: 'image' },
{ name: 'Code Block', id: 'codeblock' },
{ name: 'C2Event', id: 'c2-event' },
{ name: 'Terminal Recording', id: 'terminal-recording' },
{ name: 'HTTP Request/Response', id: 'http-request-cycle' },
{ name: 'Events', id: 'event' },
Expand Down
259 changes: 259 additions & 0 deletions frontend/src/components/c2-event/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
// Copyright 2020, Verizon Media
// Licensed under the terms of the MIT. See LICENSE file in project root for terms.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the majority of copyright headers have been removed, and we could probably drop the header. @jrozner or @jkennedyvz can verify.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct @JoelAtDeluxe we no longer use copyright headers in each file.


import * as React from 'react'
import { default as Input, SharedProps} from 'src/components/input'
import { C2Event } from 'src/global_types'
import ComboBox from 'src/components/combobox'
import { useAsyncComponent } from 'src/helpers'
import LoadingSpinner from 'src/components/loading_spinner'
import WithLabel from 'src/components/with_label'
Comment on lines +4 to +7
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably remove unused imports to keep things cleaner.

import classnames from 'classnames/bind'
const cx = classnames.bind(require('./stylesheet'))


export const C2EventTextArea = React.forwardRef((props: SharedProps & {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you decide to create a text area here, rather than reuse the one from components/input ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats a very good question. I missed that component entirely when I was writing this initially. I've gone ahead and removed the custom TextArea.

}, ref: React.RefObject<HTMLTextAreaElement>) => (
<WithLabel className={cx('root', props.className)} label={props.label}>
<textarea
ref={ref}
className={cx('c2-event-textarea')}
disabled={props.disabled}
name={props.name}
onBlur={props.onBlur}
onChange={e => { if (props.onChange) props.onChange(e.target.value) }}
onClick={props.onClick}
onFocus={props.onFocus}
onKeyDown={props.onKeyDown}
placeholder={props.placeholder}
value={props.value}
/>
</WithLabel>
))


export const C2EventViewer = (props: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably combine the EventViewer and eventEditor

disabled?: boolean,
//onChange: (newValue: C2Event) => void,
value: C2Event,
}) => {
return (
<div className={cx('c2-event-grid')}>
<div className={cx('c2framework')}>
<Input
label="C2"
className={cx('c2-event-input')}
value={props.value.c2 || ''}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to account for an undefined/null props.value.c2. The type specifies string, which means not undefined or null. This is just a type hint though -- the compiler will enforce it, but it's still javascript, so if this value gets populated in another way, then we might have a problem.

Except, there should only be one way to get assets into the database (this UI, unless you make a change to the ashirt application), and and it'll force strings there. At worst, if a null did get in there, then we'd see a warning in the console. Not great, but this would be an odd case given the above.

disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('operator')}>
<Input
label="Operator"
className={cx('c2-event-input')}
value={props.value.c2Operator || ''}
disabled={props.disabled}
readOnly
/>
</div>
Comment on lines +26 to +34
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the outer div. className on Input refers to the label, not the input, which would make this the direct child, and I think negate the need for an extra div.

Suggested change
<div className={cx('operator')}>
<Input
label="Operator"
className={cx('c2-event-input')}
value={props.value.c2Operator || ''}
disabled={props.disabled}
readOnly
/>
</div>
<Input
label="Operator"
className={cx('operator', 'c2-event-input')}
value={props.value.c2Operator || ''}
disabled={props.disabled}
readOnly
/>

<div className={cx('user-context')}>
<Input
label="User Context"
className={cx('c2-event-input')}
value={props.value.userContext || ''}
disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('beacon')}>
<Input
label="Beacon"
className={cx('c2-event-input')}
value={props.value.beacon || ''}
disabled={props.disabled}
readOnly
/>
</div>
Comment on lines +35 to +52
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a tabstop issue in your code (tabbing from operator goes to User Context, which then goes to Beacon, then Hostname. The rest are fine after this)
You just need to re-arrange the items to solve it:

Suggested change
<div className={cx('user-context')}>
<Input
label="User Context"
className={cx('c2-event-input')}
value={props.value.userContext || ''}
disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('beacon')}>
<Input
label="Beacon"
className={cx('c2-event-input')}
value={props.value.beacon || ''}
disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('beacon')}>
<Input
label="Beacon"
className={cx('c2-event-input')}
value={props.value.beacon || ''}
disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('user-context')}>
<Input
label="User Context"
className={cx('c2-event-input')}
value={props.value.userContext || ''}
disabled={props.disabled}
readOnly
/>
</div>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd need to do the same thing to the editor below

<div className={cx('hostname')}>
<Input
label="Hostname"
className={cx('c2-event-input')}
value={props.value.hostname || ''}
disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('intIP')}>
<Input
label="Internal IP"
className={cx('c2-event-input')}
value={props.value.internalIP || ''}
disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('extIP')}>
<Input
label="External IP"
className={cx('c2-event-input')}
value={props.value.externalIP || ''}
disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('process')}>
<Input
label="Process"
className={cx('c2-event-input')}
value={props.value.processName || ''}
disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('procID')}>
<Input
label="Process ID"
type="number"
className={cx('c2-event-input')}
value={props.value.processID !== undefined ? props.value.processID.toString() : ''}
disabled={props.disabled}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
disabled={props.disabled}
disabled={props.disabled}
readOnly

/>
</div>
<div className={cx('command')}>
<C2EventTextArea
label="Command" className={cx('c2-event-input', 'resizeable')}
value={props.value.command || ''}
disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('result')}>
<C2EventTextArea
label="Result" className={cx('c2-event-input', 'resizeable')}
value={props.value.result || ''}
disabled={props.disabled}
readOnly
/>
</div>
</div>
)
}

// WIP..
export const C2EventEditor = (props: {
disabled?: boolean,
onChange: (newValue: C2Event) => void,
value: C2Event,
}) => {
return (
<div className={cx('c2-event-grid')}>
<div className={cx('c2framework')}>
<Input
label="C2"
className={cx('c2-event-input')}
value={props.value.c2 || ''}
disabled={props.disabled}
onChange={c2 => props.onChange({...props.value, c2})}
/>
</div>
<div className={cx('operator')}>
<Input
label="Operator"
className={cx('c2-event-input')}
value={props.value.c2Operator || ''}
disabled={props.disabled}
onChange={c2Operator => props.onChange({...props.value, c2Operator})}
/>
</div>
<div className={cx('user-context')}>
<Input
label="User Context"
className={cx('c2-event-input')}
value={props.value.userContext || ''}
disabled={props.disabled}
onChange={userContext => props.onChange({...props.value, userContext})}
/>
</div>
<div className={cx('beacon')}>
<Input
label="Beacon"
className={cx('c2-event-input')}
value={props.value.beacon || ''}
disabled={props.disabled}
onChange={beacon => props.onChange({...props.value, beacon})}
/>
</div>
<div className={cx('hostname')}>
<Input
label="Hostname"
className={cx('c2-event-input')}
value={props.value.hostname || ''}
disabled={props.disabled}
onChange={hostname => props.onChange({...props.value, hostname})}
/>
</div>
<div className={cx('intIP')}>
<Input
label="Internal IP"
className={cx('c2-event-input')}
value={props.value.internalIP || ''}
disabled={props.disabled}
onChange={internalIP => props.onChange({...props.value, internalIP})}
/>
</div>
<div className={cx('extIP')}>
<Input
label="Exeternal IP"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
label="Exeternal IP"
label="External IP"

className={cx('c2-event-input')}
value={props.value.externalIP || ''}
disabled={props.disabled}
onChange={externalIP => props.onChange({...props.value, externalIP})}
/>
</div>
<div className={cx('process')}>
<Input
label="Process"
className={cx('c2-event-input')}
value={props.value.processName || ''}
disabled={props.disabled}
onChange={processName => props.onChange({...props.value, processName})}
/>
</div>
<div className={cx('procID')}>
<Input
label="Process ID"
type="number"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

number type is probably wrong here. a couple of observations:

  1. you can set negative numbers (you could fix this with min=0 or min=1)
  2. the stepper is probably not useful (if it's a decimal number)
  3. the stepper is unstyled. I didn't look into how to style it. But it's a weird experience unstyled. (looks like you can hide them, but it's browser specific)

I don't know if it makes sense to restrict this input so heavily, but I'll leave that up to you. What you might want to do instead is add some logic to prevent undesired characters. Unfortunately, this can be complex since the user would normally put in the value one character at a time, so you'd need to allow bad values, then validate it later. I can't remember if we do any validation as part of the create step, so that might also need to be implemented.

className={cx('c2-event-input')}
value={props.value.processID !== undefined ? props.value.processID.toString() : ''}
onChange={processID =>{
const pidNum = parseFloat(processID);
if (!isNaN(pidNum)) {
props.onChange({ ...props.value, processID: pidNum });
} else {
console.error('Invalid input. Please enter a number.'); // cowboy error handling... whats the ASHIRT way to do this?
}
}}
disabled={props.disabled}
/>
</div>
<div className={cx('command')}>
<Input
label="Command"
className={cx('c2-event-input')}
value={props.value.command || ''}
disabled={props.disabled}
onChange={command => props.onChange({...props.value, command})}
/>
</div>
<div className={cx('result')}>
<C2EventTextArea
label="Result" className={cx('c2-event-input', 'resizeable')}
value={props.value.result || ''}
disabled={props.disabled}
onChange={result => props.onChange({...props.value, result})}
/>
</div>
</div>
)
}