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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ dist/
cmd/client/client
cmd/client/dropdown
cmd/dropdown/dropdown
cmd/web/web
cmd/web/web
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
2 changes: 1 addition & 1 deletion frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
<body>
<script src="/assets/main.js"></script>
</body>
</html>
</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
228 changes: 228 additions & 0 deletions frontend/src/components/c2-event/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import * as React from 'react'
import { default as Input, TextArea, 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 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,
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')}>
<TextArea
label="Command" className={cx('c2-event-input', 'resizeable')}
value={props.value.command || ''}
disabled={props.disabled}
readOnly
/>
</div>
<div className={cx('result')}>
<TextArea
label="Result" className={cx('c2-event-input', 'resizeable')}
value={props.value.result || ''}
disabled={props.disabled}
readOnly
/>
</div>
</div>
)
}

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 =>{
props.onChange({ ...props.value, processID: processID });
}}
disabled={props.disabled}
/>
</div>
<div className={cx('command')}>
<TextArea
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')}>
<TextArea
label="Result" className={cx('c2-event-input', 'resizeable')}
value={props.value.result || ''}
disabled={props.disabled}
onChange={result => props.onChange({...props.value, result})}
/>
</div>
</div>
)
}
122 changes: 122 additions & 0 deletions frontend/src/components/c2-event/stylesheet.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
@import '~src/vars'

.code-viewer
Copy link
Collaborator

Choose a reason for hiding this comment

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

You're not using code-viewer, so you can just remove this whole section.

position: relative
width: 100%
height: 100%
min-height: 100px
min-width: 100px

.source
position: absolute
top: 0
left: 0
max-width: 100%
height: 35px
line-height: 30px
padding: 0 10px
box-sizing: border-box
background: #0b1115
border-top-right-radius: 3px
border-top-left-radius: 3px
white-space: nowrap
text-overflow: ellipsis
overflow: hidden

.ace
position: absolute
top: 30px
bottom: 0
left: 0
right: 0

.c2-event-input
text-align: left
overflow: auto; /* Allow content to be scrollable when it overflows */


.c2-event-textarea
height: 150px
line-height: 1.2
height: 70px
width: 100%
box-sizing: border-box
padding: 0 10px
background: $darker-background
box-shadow: 0 0 0 0 rgba(19,124,189,0), 0 0 0 0 rgba(19,124,189,0), 0 0 0 0 rgba(19,124,189,0), inset 0 0 0 1px rgba(16,22,26,.3), inset 0 1px 1px rgba(16,22,26,.4)
color: $foreground
border: none
outline: none
border-radius: 3px
focus-box-shadow()
resize: vertical

.c2-event-grid
padding: 5px 10px
width: 100%
display: grid;
grid-template-columns: auto; //repeat(2, 1fr);
gap: 10px;
grid-template-rows: auto; //repeat(5, 1fr);

.c2framework
grid-row: 1; /* This item starts in row 1 and ends in row 2 */
grid-column: 1;

.operator
grid-row: 2;
grid-column: 1;

.user-context
grid-row: 3;
grid-column: 1;

.beacon
grid-row: 2;
grid-column: 2;

.hostname
grid-row: 3;
grid-column: 2;

.intIP
grid-row: 4;
grid-column: 1;

.extIP
grid-row: 4;
grid-column: 2;

.process
grid-row: 5;
grid-column: 1;

.procID
grid-row: 5;
grid-column: 2;

.command
grid-row: 6;
grid-column: 1 / 3;

.result
grid-row: 7;
grid-column: 1 / 3;

.c1
grid-column: 1;

.c2
grid-column: 2;

.r1
grid-row: 1;

.r2
grid-row: 2;

.r3
grid-row: 3;

.r4
grid-row: 4;
1 change: 1 addition & 0 deletions frontend/src/components/code_block/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,4 @@ export const SourcelessCodeblock = (props: {
</div>
)
}