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 16 commits into
base: main
Choose a base branch
from
Open

Conversation

nbaertsch
Copy link

Adding an evidence type to capture C2 events in ASHIRT.

We have ingestors (python script + systemd service file) for both Cobalt Strike and Brute Ratel, though both have some bugs we are working through squashing. If we like the direction of adding this feature into ASHIRT I'd be partial to the ingestor repo being owned by ashirt-ops so that they can live with the rest of the project.

I confirm that this contribution is made under the terms of the license found in the root directory of this repository's source tree and that I have the authority necessary to make this contribution on behalf of its copyright owner.

@jrozner
Copy link
Member

jrozner commented Apr 18, 2024

Saw this come in. Should have some time tomorrow to look through the PR. We're happy to adopt the ingestor repo over to the org. I think that makes sense as well. Do you have some samples of logs from brute ratel or cobalt strike so that we can test things end to end? Also, are the ingestors public currently?

userContext: string,// The user context that the implant/beacon/agent is running under
integrity: string, // "Low", "Medium", "High", "System"
processName: string,// process image file shortname
processID: number, // is 'number' acceptable here? Its a float :/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, number could be fine here. That corresponds to a double sized float.

But, are you sure this is a number at all? I don't know the format, but usually IDs are strings, even if they're only comprised of numbers. A number implies you can, say, add processIDs. Does it make sense to add these IDs?

Copy link
Author

Choose a reason for hiding this comment

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

That being the case, string may be more appropriate.

Copy link
Author

Choose a reason for hiding this comment

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

processID here is the PID of the beacon process - for context.

@nbaertsch
Copy link
Author

Saw this come in. Should have some time tomorrow to look through the PR. We're happy to adopt the ingestor repo over to the org. I think that makes sense as well. Do you have some samples of logs from brute ratel or cobalt strike so that we can test things end to end? Also, are the ingestors public currently?

Just made the ingestors public here. There are some (very succinct) sample logs there for BR and CS (no mythic atm), as well as a python script to simulate log writing to test the ingestors.

Comment on lines 157 to 163
if (props.evidence.contentType === 'codeblock') {
React.useEffect(() => {
getEvidenceAsCodeblock({
operationSlug: props.operationSlug,
evidenceUuid: props.evidence.uuid,
}).then(codeblockField.onChange)
}, [props.evidence.contentType, codeblockField.onChange, props.operationSlug, props.evidence.uuid])
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's been a bit since I last did regular work in react, but I believe that we still want all of the hooks to always execute -- this shouldn't be in a conditional.

Copy link
Author

Choose a reason for hiding this comment

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

I had this quite messed up in the original commit. I've fixed the useEffect issue and cleaned up the return logic.

Comment on lines 1 to 2
// 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.

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.

Copy link
Member

@jrozner jrozner left a comment

Choose a reason for hiding this comment

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

Overall things seem to work. I wasn't able to actually test with real data using the ingestors because the ashirt_worker import is failing. Is that a separate file that you have created that isn't included within the repo?

<script src="/assets/main.js"></script>
</body>
</html>
<head>
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.

@nbaertsch
Copy link
Author

I've added the missing ashirt_worker.py to the ingestor repo - apologies for that. I will take look at the requested changes and try and get this cleaned up and ready to merge over this coming weekend.

@nbaertsch
Copy link
Author

I've made the changes we've discussed here. Sorry for the delay on this one, thanks for your patience. If there's anything else I can do to clean this up do lmk, this was my first time writing React 😁

@nbaertsch nbaertsch requested review from jrozner and JoelAtDeluxe May 29, 2024 17:42
@jrozner
Copy link
Member

jrozner commented May 29, 2024

Thanks for getting the changes in. I should have some time this afternoon to take a look

Copy link
Member

@jrozner jrozner left a comment

Choose a reason for hiding this comment

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

Overall things look pretty good. @JoelAtDeluxe any chance you have time to look at the react components? I don't have much experience with it and can't provide meaningful feedback for idiomatic aspects.

@@ -4,7 +4,7 @@ import WithLabel from 'src/components/with_label'
import classnames from 'classnames/bind'
const cx = classnames.bind(require('./stylesheet'))

type SharedProps = {
export type SharedProps = {
Copy link
Member

Choose a reason for hiding this comment

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

Does this need to be exported?

React.useEffect(() => {
if (props.evidence.contentType !== 'codeblock') {
if (props.evidence.contentType !== 'codeblock' && props.evidence.contentType !== 'c2-event') {
Copy link
Member

Choose a reason for hiding this comment

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

Would you mind running this file through a formatter? It looks like there is an extra indent here and some formatting changes below

@JoelAtDeluxe
Copy link
Collaborator

I'll make some time this weekend to go through it.

Copy link
Collaborator

@JoelAtDeluxe JoelAtDeluxe left a comment

Choose a reason for hiding this comment

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

Still somewhat going through this, but there are enough things here that I'd like to see changed. I can try to find time to help if you need it.

Some big thoughts:

  1. There are a few styling issues to resolve: the grid is too big (when viewing the data, the right side goes off screen), the box shadows aren't rendering correctly (only the top renders), when editing, the grid is off center.
  2. The editor and viewer are nearly identical. Maybe it makes sense to combine them into one (unexported), and create a simple component for reader and writer?
  3. As Joe noted, there are some files with formatting issues. I agree that this should probably be run through a formatter. I usually use VSCode to do this for me at the file level. There are also some other issues around extraneous imports. You could also run eslint to help identify a few of these issues (in frontend, run npm run lint), but it looks like there errors in other files outside of this PR too. 😞

It looks like basically all of the logic is correct at this point though, and I didn't see any issues specifically around React, so it seems like this PR is really just in a cleanup phase.

Comment on lines +4 to +7
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'
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

Comment on lines +35 to +52
<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>
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

<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.

@@ -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.

frontend/src/components/c2-event/index.tsx Outdated Show resolved Hide resolved
frontend/src/components/c2-event/index.tsx Show resolved Hide resolved
Comment on lines +26 to +34
<div className={cx('operator')}>
<Input
label="Operator"
className={cx('c2-event-input')}
value={props.value.c2Operator || ''}
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 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('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.

@nbaertsch
Copy link
Author

Thanks for the review Joel. I'm going to get to work on the code cleanup work you noted. When it comes to styling I'd appreciate some help (especially grid sizing issue, i can't get that thing centered for the life of me), but let me give this one more pass and I'll ping for another review and any styling help I may need.

@JoelAtDeluxe
Copy link
Collaborator

I'll try to find some time. fit seems like the primary issue why it doesn't fit right is because of the padding on the c2-event-grid. That somewhat breaks the lightbox view, but I think that there should be a way to fix specifically that (it's been awhile though, so I don't know what it is off the top of my head).
I'd say look at the har viewer as an example, but that's broken due to lazy loading components. There may still be some insight there though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants