diff --git a/.eslintrc.json b/.eslintrc.json index e8c1ea81..c5efe558 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -33,7 +33,7 @@ { "prefer": "type-imports", "fixStyle": "separate-type-imports", - "disallowTypeAnnotations": true + "disallowTypeAnnotations": false } ], "import/no-anonymous-default-export": ["error"] diff --git a/.moon/tasks.yml b/.moon/tasks.yml index 9a51f959..7daac4ec 100644 --- a/.moon/tasks.yml +++ b/.moon/tasks.yml @@ -44,6 +44,18 @@ fileGroups: # This setting requires a map, where the key is a unique name for the task, # and the value is an object of task parameters. tasks: + xstate-typegen: + command: + - xstate + - typegen + - src/**/*.machine.ts?(x) + outputs: + - 'src/**/*.machine.typegen.{ts,tsx}' + inputs: + - 'src/**/*.machine.{ts,tsx}' + platform: node + options: + runInCI: false test-jest: command: 'jest . --coverage --reporters="jest-junit" --passWithNoTests' inputs: @@ -69,7 +81,7 @@ tasks: options: runInCI: false lint-global: - command: 'eslint --config $workspaceRoot/.eslintrc.json --ignore-path $workspaceRoot/.gitignore src --fix --max-warnings 0' + command: 'eslint --config $workspaceRoot/.eslintrc.json --ignore-path $workspaceRoot/.gitignore src --fix' inputs: - '@globs(sources)' - '@globs(tests)' diff --git a/.moon/workspace.yml b/.moon/workspace.yml index 17fa010c..73c550a4 100644 --- a/.moon/workspace.yml +++ b/.moon/workspace.yml @@ -14,8 +14,12 @@ $schema: 'https://moonrepo.dev/schemas/workspace.json' # path to the project folder as the map value. File paths are relative from the workspace root, # and cannot reference projects located outside the workspace boundary. projects: - - 'applications/*' - - 'packages/*' + globs: + - 'applications/*' + - 'packages/*' + - 'parsers/*' + sources: + landing-page: 'docs/landing-page' # Configures the version control system to utilize within the workspace. A VCS # is required for determining touched (added, modified, etc) files, calculating file hashes, diff --git a/Dockerfile b/Dockerfile index 516198f1..fda3fc90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,13 +6,13 @@ ENV CYPRESS_INSTALL_BINARY=0 RUN npm install -g pkg RUN yarn install --immutable --inline-builds RUN curl -fsSL https://moonrepo.dev/install/moon.sh | bash -RUN moon run server:build client:build cs-parser:build +RUN moon run server:build client:build cobalt-strike-parser:build RUN pkg applications/server/package.json -t node16-mac-x64 -o release/mac/RedEye RUN pkg applications/server/package.json -t node16-linux-x64 -o release/linux/RedEye RUN pkg applications/server/package.json -t node16-windows-x64 -o release/windows/RedEye -RUN pkg packages/cs-parser/package.json -t node16-mac-x64 -o release/mac/parsers/cs-parser -RUN pkg packages/cs-parser/package.json -t node16-linux-x64 -o release/linux/parsers/cs-parser -RUN pkg packages/cs-parser/package.json -t node16-windows-x64 -o release/windows/parsers/cs-parser +RUN pkg packages/cobalt-strike-parser/package.json -t node16-mac-x64 -o release/mac/parsers/cobalt-strike-parser +RUN pkg packages/cobalt-strike-parser/package.json -t node16-linux-x64 -o release/linux/parsers/cobalt-strike-parser +RUN pkg packages/cobalt-strike-parser/package.json -t node16-windows-x64 -o release/windows/parsers/cobalt-strike-parser RUN tar -zcvf release.tar.gz ./release/ RUN mkdir outputs RUN cp release.tar.gz outputs/release.tar.gz diff --git a/README.md b/README.md index d8816fe4..909bc604 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,19 @@ Red Team: [![Red Team](https://img.shields.io/endpoint?url=https://cloud.cypress Blue Team: [![Blue Team](https://img.shields.io/endpoint?url=https://cloud.cypress.io/badge/simple/46ahz3&style=flat&logo=cypress)](https://cloud.cypress.io/projects/46ahz3/runs) -## [User Guide](docs/UserGuide.md) +## [User Guide](https://github.com/cisagov/RedEye/blob/develop/docs/UserGuide.md) -Follow along with the [User Guide](docs/UserGuide.md) to learn about RedEye's feature set. +Follow along with the [User Guide](https://github.com/cisagov/RedEye/blob/develop/docs/UserGuide.md) to learn about RedEye's feature set. ## Quick start 1. **Download** the latest RedEye binaries for your OS[\*](#platform-support) from the [Releases](https://github.com/cisagov/RedEye/releases) page. -2. **Pick a mode** and **Run the server** - - [ **Red Team mode**](#red-team) enables the full feature set: upload C2 logs, explore data, and create presentations. To start the server in Red Team mode, run the following in a terminal. _You must provide a password to run in RedTeam mode._ + 2. **Pick a mode** and **Run the server** + - [ **Red Team mode**](#red-team) enables the full feature set: upload C2 logs, explore data, and create presentations. To start the server in Red Team mode, run the following in a terminal. _You must provide a password to run in RedTeam mode._ ``` - AUTHENTICATION_PASSWORD= ./RedEye --redTeam + ./RedEye --redTeam --password ``` - - [**Blue Team mode**](#blue-team) (default) enables a simplified, read-only UI for reviewing campaigns exported by a Red Team. To start the server in Blue Team mode. Double-click on the 'RedEye' executable or run `./RedEye` from the command line. + - [**Blue Team mode**](#blue-team) (default) enables a simplified, read-only UI for reviewing campaigns exported by a Red Team. To start the server in Blue Team mode. Double-click on the 'RedEye' executable or run `./RedEye` from the command line. 3. **Use the web app** in a browser at http://127.0.0.1:4000. The RedEye binary runs as a server in a terminal window and will automatically open the web app UI your default browser. You must close the terminal window to quit the RedEye server. _**MacOS Issue** - When running RedEye for the first time, you may get a "not verified" error. You must go to "System Preferences" > "Security & Privacy" > "General" and click "Open Anyway." More info on the [Apple support page](https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unidentified-developer-mh40616/)._ @@ -46,13 +46,13 @@ _Note: Both Red and Blue Team modes can be started from the same RedEye applicat The downloaded binary comes in two parts: - The `RedEye` application binary -- The `parsers` folder containing the `cs-parser` Cobalt Strike log parser binary +- The `parsers` folder containing parser binaries (e.g. `cobalt-strike-parser` Cobalt Strike log parser binary) There are three options to run RedEye in Red Team mode: 1. Run the downloaded binary, passing in the `--redTeam` and password options: ``` - AUTHENTICATION_PASSWORD= ./RedEye --redTeam + ./RedEye --redTeam --password ``` 2. Clone, install, and run the project directly (covered in the [Local Build](#local-build) section). 3. Docker Compose @@ -100,23 +100,23 @@ RedEye runs as a server and can be setup to serve the UI on a network.. Type `./Redeye -h` to view the options ``` --d, --developmentMode [boolean]  put the database and server in development mode --r, --redTeam [boolean]          run the server in red team mode --p, --port [number]              the port the server should be exposed at --t, --childProcesses [number]    max # of child processes the parser can use --h, --help                       display help for command +-d, --developmentMode [boolean] put the database and server in development mode +-r, --redTeam [boolean] run the server in red team mode +--port [number] the port the server should be exposed at +-p, --password [string] the password for user authentication +--parsers [string...] A list of parsers to use or a flag to use all parsers in the parsers folder +-t, --childProcesses [number] max # of child processes the parser can use +-h, --help display help for command ``` -you can also configure the sever parameters in an `.env` file that sits next to the `RedEye` binary +you can also configure the server parameters in a `config.json` file that sits next to the `RedEye` binary +```json - - -```env -AUTHENTICATION_PASSWORD=937038570 -AUTHENTICATION_SECRET=supertopsecretdonttellanyone -DATABASE_MODE=DEV_PERSIST -SERVER_BLUE_TEAM=false -SERVER_PRODUCTION=false +{ + "password": "937038570", + "redTeam": true, + "parsers": ["cobalt-strike-parser", "brute-ratel-parser"] +} ``` ## Local Build diff --git a/applications/client/.eslintrc.yaml b/applications/client/.eslintrc.yaml index d6d1a064..84538de3 100644 --- a/applications/client/.eslintrc.yaml +++ b/applications/client/.eslintrc.yaml @@ -18,6 +18,8 @@ extends: - airbnb - airbnb-typescript - prettier +globals: + globalThis: false rules: react-hooks/exhaustive-deps: 0 # Disabled for mobx react/no-unknown-property: 0 # Disabled for css and cy-test diff --git a/applications/client/moon.yml b/applications/client/moon.yml index aa674783..c439d3ab 100644 --- a/applications/client/moon.yml +++ b/applications/client/moon.yml @@ -17,9 +17,15 @@ tasks: command: mk-gql --outDir=./src/store/graphql ../server/schema.graphql options: runInCI: false + lint-graphql: + command: 'eslint src/store/graphql --fix' + options: + runInCI: false generate-graphql: command: 'prettier --write src/store/graphql/*.ts' deps: - ~:graphql + - ~:lint-graphql options: runInCI: false + runDepsInParallel: false diff --git a/applications/client/src/components/Mitre/process-enterprise-attack.js b/applications/client/src/components/Mitre/process-enterprise-attack.js index 6be00852..3e115509 100644 --- a/applications/client/src/components/Mitre/process-enterprise-attack.js +++ b/applications/client/src/components/Mitre/process-enterprise-attack.js @@ -69,11 +69,11 @@ Object.keys(mitreAttackDictionary) : mitreAttackDictionary[id]; }); -console.log(`Parsed ${Object.entries(alphabeticalMitreAttackDictionary).length} MITRE ATT&CK ids`); +globalThis.console.log(`Parsed ${Object.entries(alphabeticalMitreAttackDictionary).length} MITRE ATT&CK ids`); // it helps to manually run prettier on this after its generated const mitreAttackDictionaryPathTs = path.join(__dirname, 'mitreAttackDictionary.ts'); const tsFileContents = `export const mitreAttackDictionary = ${JSON.stringify(alphabeticalMitreAttackDictionary)}`; fs.writeFile(mitreAttackDictionaryPathTs, tsFileContents, (err) => { - if (err) console.error(err); + if (err) globalThis.console.error(err); }); diff --git a/applications/client/src/store/graphql/BeaconMetaModel.base.ts b/applications/client/src/store/graphql/BeaconMetaModel.base.ts index 12c76686..e09240aa 100644 --- a/applications/client/src/store/graphql/BeaconMetaModel.base.ts +++ b/applications/client/src/store/graphql/BeaconMetaModel.base.ts @@ -33,10 +33,14 @@ export class BeaconMetaModelBase extends Model({ ip: prop().withSetter(), /** Process Identifier the beacon is running on */ pid: prop().withSetter(), + /** The IP of the host at the time of the metadata line */ + port: prop().withSetter(), + /** Process Identifier the beacon is running on */ + process: prop().withSetter(), /** The shape of the beacon */ shape: prop().withSetter(), /** The log line from which the BeaconMeta was extracted */ - source: prop>().withSetter(), + source: prop | null>().withSetter(), /** The start time of the beacon */ startTime: prop().withSetter(), /** The communication type used by the beacon */ @@ -65,6 +69,12 @@ export class BeaconMetaModelSelector extends QueryBuilder { get pid() { return this.__attr(`pid`); } + get port() { + return this.__attr(`port`); + } + get process() { + return this.__attr(`process`); + } get shape() { return this.__attr(`shape`); } @@ -85,4 +95,5 @@ export function selectFromBeaconMeta() { return new BeaconMetaModelSelector(); } -export const beaconMetaModelPrimitives = selectFromBeaconMeta().color.endTime.ip.pid.shape.startTime.type.username; +export const beaconMetaModelPrimitives = + selectFromBeaconMeta().color.endTime.ip.pid.port.process.shape.startTime.type.username; diff --git a/applications/client/src/store/graphql/BeaconTypeEnum.ts b/applications/client/src/store/graphql/BeaconTypeEnum.ts index fe6d5c59..f52f4d7c 100644 --- a/applications/client/src/store/graphql/BeaconTypeEnum.ts +++ b/applications/client/src/store/graphql/BeaconTypeEnum.ts @@ -9,10 +9,10 @@ import { types, prop, tProp, Model, Ref } from 'mobx-keystone'; */ export enum BeaconType { - DNS = 'DNS', - HTTP = 'HTTP', - HTTPS = 'HTTPS', - SMB = 'SMB', + dns = 'dns', + http = 'http', + https = 'https', + smb = 'smb', } /** diff --git a/applications/client/src/store/graphql/CampaignModel.base.ts b/applications/client/src/store/graphql/CampaignModel.base.ts index 2b6c3d3c..c4fe0489 100644 --- a/applications/client/src/store/graphql/CampaignModel.base.ts +++ b/applications/client/src/store/graphql/CampaignModel.base.ts @@ -5,9 +5,11 @@ import { types, prop, tProp, Model, Ref, idProp } from 'mobx-keystone'; import { QueryBuilder } from 'mk-gql'; +import type { CampaignParserModel } from './CampaignParserModel'; import type { GlobalOperatorModel } from './GlobalOperatorModel'; import type { ParsingStatus } from './ParsingStatusEnum'; +import { CampaignParserModelSelector, campaignParserModelPrimitives } from './CampaignParserModel'; import { GlobalOperatorModelSelector, globalOperatorModelPrimitives } from './GlobalOperatorModel'; /* The TypeScript type that explicits the refs to other models in order to prevent a circular refs issue */ @@ -33,6 +35,7 @@ export class CampaignModelBase extends Model({ lastOpenedBy: prop | null>().withSetter(), migrationError: prop().withSetter(), name: prop().withSetter(), + parsers: prop(() => []).withSetter(), parsingStatus: prop().withSetter(), serverCount: prop().withSetter(), }) { @@ -91,6 +94,14 @@ export class CampaignModelSelector extends QueryBuilder { ) { return this.__child(`lastOpenedBy`, GlobalOperatorModelSelector, builder); } + parsers( + builder?: + | string + | CampaignParserModelSelector + | ((selector: CampaignParserModelSelector) => CampaignParserModelSelector) + ) { + return this.__child(`parsers`, CampaignParserModelSelector, builder); + } } export function selectFromCampaign() { return new CampaignModelSelector(); diff --git a/applications/client/src/store/graphql/CampaignModel.ts b/applications/client/src/store/graphql/CampaignModel.ts index 0c50f831..a1329e97 100644 --- a/applications/client/src/store/graphql/CampaignModel.ts +++ b/applications/client/src/store/graphql/CampaignModel.ts @@ -9,6 +9,7 @@ export { campaignModelPrimitives, CampaignModelSelector, selectFromCampaign } fr export interface Servers { name: string; + displayName: string; fileData: FormData | undefined; fileCount: number; isParsingFiles: boolean; diff --git a/applications/client/src/store/graphql/CampaignParserModel.base.ts b/applications/client/src/store/graphql/CampaignParserModel.base.ts new file mode 100644 index 00000000..594e0041 --- /dev/null +++ b/applications/client/src/store/graphql/CampaignParserModel.base.ts @@ -0,0 +1,31 @@ +/* This is a mk-gql generated file, don't modify it manually */ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck + +import { types, prop, tProp, Model, Ref, idProp } from 'mobx-keystone'; +import { QueryBuilder } from 'mk-gql'; + +/** + * CampaignParserBase + * auto generated base class for the model CampaignParserModel. + */ +export class CampaignParserModelBase extends Model({ + __typename: tProp('CampaignParser'), + parserName: prop().withSetter(), + path: prop().withSetter(), +}) {} + +export class CampaignParserModelSelector extends QueryBuilder { + get parserName() { + return this.__attr(`parserName`); + } + get path() { + return this.__attr(`path`); + } +} +export function selectFromCampaignParser() { + return new CampaignParserModelSelector(); +} + +export const campaignParserModelPrimitives = selectFromCampaignParser().parserName.path; diff --git a/applications/client/src/store/graphql/CampaignParserModel.ts b/applications/client/src/store/graphql/CampaignParserModel.ts new file mode 100644 index 00000000..790bd90e --- /dev/null +++ b/applications/client/src/store/graphql/CampaignParserModel.ts @@ -0,0 +1,15 @@ +import { ExtendedModel, model } from 'mobx-keystone'; +import { CampaignParserModelBase } from './CampaignParserModel.base'; + +/* A graphql query fragment builders for CampaignParserModel */ +export { + selectFromCampaignParser, + campaignParserModelPrimitives, + CampaignParserModelSelector, +} from './CampaignParserModel.base'; + +/** + * CampaignParserModel + */ +@model('CampaignParser') +export class CampaignParserModel extends ExtendedModel(CampaignParserModelBase, {}) {} diff --git a/applications/client/src/store/graphql/FileDisplayModel.base.ts b/applications/client/src/store/graphql/FileDisplayModel.base.ts new file mode 100644 index 00000000..174ae1a1 --- /dev/null +++ b/applications/client/src/store/graphql/FileDisplayModel.base.ts @@ -0,0 +1,27 @@ +/* This is a mk-gql generated file, don't modify it manually */ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck + +import { types, prop, tProp, Model, Ref, idProp } from 'mobx-keystone'; +import { QueryBuilder } from 'mk-gql'; + +/** + * FileDisplayBase + * auto generated base class for the model FileDisplayModel. + */ +export class FileDisplayModelBase extends Model({ + __typename: tProp('FileDisplay'), + editable: prop().withSetter(), +}) {} + +export class FileDisplayModelSelector extends QueryBuilder { + get editable() { + return this.__attr(`editable`); + } +} +export function selectFromFileDisplay() { + return new FileDisplayModelSelector(); +} + +export const fileDisplayModelPrimitives = selectFromFileDisplay().editable; diff --git a/applications/client/src/store/graphql/FileDisplayModel.ts b/applications/client/src/store/graphql/FileDisplayModel.ts new file mode 100644 index 00000000..478fc74f --- /dev/null +++ b/applications/client/src/store/graphql/FileDisplayModel.ts @@ -0,0 +1,11 @@ +import { ExtendedModel, model } from 'mobx-keystone'; +import { FileDisplayModelBase } from './FileDisplayModel.base'; + +/* A graphql query fragment builders for FileDisplayModel */ +export { selectFromFileDisplay, fileDisplayModelPrimitives, FileDisplayModelSelector } from './FileDisplayModel.base'; + +/** + * FileDisplayModel + */ +@model('FileDisplay') +export class FileDisplayModel extends ExtendedModel(FileDisplayModelBase, {}) {} diff --git a/applications/client/src/store/graphql/FileModel.base.ts b/applications/client/src/store/graphql/FileModel.base.ts index 611aa62a..d3e8c5a9 100644 --- a/applications/client/src/store/graphql/FileModel.base.ts +++ b/applications/client/src/store/graphql/FileModel.base.ts @@ -17,7 +17,6 @@ export class FileModelBase extends Model({ fileFlag: prop().withSetter(), fileName: prop().withSetter(), id: prop().withSetter(), - ip: prop().withSetter(), location: prop().withSetter(), /** Generated automatically when using the upload command, the MD5 message-digest algorithm is a widely used hash function producing a 128-bit hash value. */ md5: prop().withSetter(), @@ -40,9 +39,6 @@ export class FileModelSelector extends QueryBuilder { get id() { return this.__attr(`id`); } - get ip() { - return this.__attr(`ip`); - } get location() { return this.__attr(`location`); } @@ -54,4 +50,4 @@ export function selectFromFile() { return new FileModelSelector(); } -export const fileModelPrimitives = selectFromFile().dateTime.fileFlag.fileName.ip.location.md5; +export const fileModelPrimitives = selectFromFile().dateTime.fileFlag.fileName.location.md5; diff --git a/applications/client/src/store/graphql/FileUploadModel.base.ts b/applications/client/src/store/graphql/FileUploadModel.base.ts new file mode 100644 index 00000000..9fae6eee --- /dev/null +++ b/applications/client/src/store/graphql/FileUploadModel.base.ts @@ -0,0 +1,45 @@ +/* This is a mk-gql generated file, don't modify it manually */ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck + +import { types, prop, tProp, Model, Ref, idProp } from 'mobx-keystone'; +import { QueryBuilder } from 'mk-gql'; +import type { UploadType } from './UploadTypeEnum'; +import type { ValidationMode } from './ValidationModeEnum'; + +/** + * FileUploadBase + * auto generated base class for the model FileUploadModel. + */ +export class FileUploadModelBase extends Model({ + __typename: tProp('FileUpload'), + acceptedExtensions: prop().withSetter(), + description: prop().withSetter(), + example: prop().withSetter(), + type: prop().withSetter(), + validate: prop().withSetter(), +}) {} + +export class FileUploadModelSelector extends QueryBuilder { + get acceptedExtensions() { + return this.__attr(`acceptedExtensions`); + } + get description() { + return this.__attr(`description`); + } + get example() { + return this.__attr(`example`); + } + get type() { + return this.__attr(`type`); + } + get validate() { + return this.__attr(`validate`); + } +} +export function selectFromFileUpload() { + return new FileUploadModelSelector(); +} + +export const fileUploadModelPrimitives = selectFromFileUpload().acceptedExtensions.description.example.type.validate; diff --git a/applications/client/src/store/graphql/FileUploadModel.ts b/applications/client/src/store/graphql/FileUploadModel.ts new file mode 100644 index 00000000..414fcd6c --- /dev/null +++ b/applications/client/src/store/graphql/FileUploadModel.ts @@ -0,0 +1,11 @@ +import { ExtendedModel, model } from 'mobx-keystone'; +import { FileUploadModelBase } from './FileUploadModel.base'; + +/* A graphql query fragment builders for FileUploadModel */ +export { selectFromFileUpload, fileUploadModelPrimitives, FileUploadModelSelector } from './FileUploadModel.base'; + +/** + * FileUploadModel + */ +@model('FileUpload') +export class FileUploadModel extends ExtendedModel(FileUploadModelBase, {}) {} diff --git a/applications/client/src/store/graphql/HostMetaModel.base.ts b/applications/client/src/store/graphql/HostMetaModel.base.ts index 898a9e45..e66f7879 100644 --- a/applications/client/src/store/graphql/HostMetaModel.base.ts +++ b/applications/client/src/store/graphql/HostMetaModel.base.ts @@ -18,6 +18,7 @@ export class HostMetaModelBase extends Model({ id: prop().withSetter(), ip: prop().withSetter(), os: prop().withSetter(), + osVersion: prop().withSetter(), shape: prop().withSetter(), type: prop().withSetter(), }) { @@ -39,6 +40,9 @@ export class HostMetaModelSelector extends QueryBuilder { get os() { return this.__attr(`os`); } + get osVersion() { + return this.__attr(`osVersion`); + } get shape() { return this.__attr(`shape`); } @@ -50,4 +54,4 @@ export function selectFromHostMeta() { return new HostMetaModelSelector(); } -export const hostMetaModelPrimitives = selectFromHostMeta().color.ip.os.shape.type; +export const hostMetaModelPrimitives = selectFromHostMeta().color.ip.os.osVersion.shape.type; diff --git a/applications/client/src/store/graphql/LogEntryModel.base.ts b/applications/client/src/store/graphql/LogEntryModel.base.ts index 32c5c1d0..be979eb6 100644 --- a/applications/client/src/store/graphql/LogEntryModel.base.ts +++ b/applications/client/src/store/graphql/LogEntryModel.base.ts @@ -30,7 +30,7 @@ export class LogEntryModelBase extends Model({ blob: prop().withSetter(), command: prop | null>().withSetter(), dateTime: prop().withSetter(), - filepath: prop().withSetter(), + filepath: prop().withSetter(), id: prop().withSetter(), lineNumber: prop().withSetter(), lineType: prop().withSetter(), diff --git a/applications/client/src/store/graphql/ParserInfoModel.base.ts b/applications/client/src/store/graphql/ParserInfoModel.base.ts new file mode 100644 index 00000000..30b04a21 --- /dev/null +++ b/applications/client/src/store/graphql/ParserInfoModel.base.ts @@ -0,0 +1,48 @@ +/* This is a mk-gql generated file, don't modify it manually */ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck + +import { types, prop, tProp, Model, Ref, idProp } from 'mobx-keystone'; +import { QueryBuilder } from 'mk-gql'; +import type { UploadFormModel } from './UploadFormModel'; + +import { UploadFormModelSelector, uploadFormModelPrimitives } from './UploadFormModel'; + +/** + * ParserInfoBase + * auto generated base class for the model ParserInfoModel. + */ +export class ParserInfoModelBase extends Model({ + __typename: tProp('ParserInfo'), + id: prop().withSetter(), + name: prop().withSetter(), + uploadForm: prop().withSetter(), + version: prop().withSetter(), +}) { + getRefId() { + return String(this.id); + } +} + +export class ParserInfoModelSelector extends QueryBuilder { + get id() { + return this.__attr(`id`); + } + get name() { + return this.__attr(`name`); + } + get version() { + return this.__attr(`version`); + } + uploadForm( + builder?: string | UploadFormModelSelector | ((selector: UploadFormModelSelector) => UploadFormModelSelector) + ) { + return this.__child(`uploadForm`, UploadFormModelSelector, builder); + } +} +export function selectFromParserInfo() { + return new ParserInfoModelSelector(); +} + +export const parserInfoModelPrimitives = selectFromParserInfo().name.version; diff --git a/applications/client/src/store/graphql/ParserInfoModel.ts b/applications/client/src/store/graphql/ParserInfoModel.ts new file mode 100644 index 00000000..fdb7ac07 --- /dev/null +++ b/applications/client/src/store/graphql/ParserInfoModel.ts @@ -0,0 +1,11 @@ +import { ExtendedModel, model } from 'mobx-keystone'; +import { ParserInfoModelBase } from './ParserInfoModel.base'; + +/* A graphql query fragment builders for ParserInfoModel */ +export { selectFromParserInfo, parserInfoModelPrimitives, ParserInfoModelSelector } from './ParserInfoModel.base'; + +/** + * ParserInfoModel + */ +@model('ParserInfo') +export class ParserInfoModel extends ExtendedModel(ParserInfoModelBase, {}) {} diff --git a/applications/client/src/store/graphql/RootStore.base.ts b/applications/client/src/store/graphql/RootStore.base.ts index bfe8dab8..6d4d7ade 100644 --- a/applications/client/src/store/graphql/RootStore.base.ts +++ b/applications/client/src/store/graphql/RootStore.base.ts @@ -25,6 +25,7 @@ import { AnnotationModel, annotationModelPrimitives, AnnotationModelSelector } f import { BeaconModel, beaconModelPrimitives, BeaconModelSelector } from './BeaconModel'; import { BeaconMetaModel, beaconMetaModelPrimitives, BeaconMetaModelSelector } from './BeaconMetaModel'; import { CampaignModel, campaignModelPrimitives, CampaignModelSelector } from './CampaignModel'; +import { CampaignParserModel, campaignParserModelPrimitives, CampaignParserModelSelector } from './CampaignParserModel'; import { CommandModel, commandModelPrimitives, CommandModelSelector } from './CommandModel'; import { CommandGroupModel, commandGroupModelPrimitives, CommandGroupModelSelector } from './CommandGroupModel'; import { @@ -33,6 +34,8 @@ import { CommandTypeCountModelSelector, } from './CommandTypeCountModel'; import { FileModel, fileModelPrimitives, FileModelSelector } from './FileModel'; +import { FileDisplayModel, fileDisplayModelPrimitives, FileDisplayModelSelector } from './FileDisplayModel'; +import { FileUploadModel, fileUploadModelPrimitives, FileUploadModelSelector } from './FileUploadModel'; import { GlobalOperatorModel, globalOperatorModelPrimitives, GlobalOperatorModelSelector } from './GlobalOperatorModel'; import { HostModel, hostModelPrimitives, HostModelSelector } from './HostModel'; import { HostMetaModel, hostMetaModelPrimitives, HostMetaModelSelector } from './HostMetaModel'; @@ -45,6 +48,7 @@ import { NonHidableEntitiesModelSelector, } from './NonHidableEntitiesModel'; import { OperatorModel, operatorModelPrimitives, OperatorModelSelector } from './OperatorModel'; +import { ParserInfoModel, parserInfoModelPrimitives, ParserInfoModelSelector } from './ParserInfoModel'; import { ParsingProgressModel, parsingProgressModelPrimitives, @@ -75,6 +79,7 @@ import { timelineCommandCountTupleModelPrimitives, TimelineCommandCountTupleModelSelector, } from './TimelineCommandCountTupleModel'; +import { UploadFormModel, uploadFormModelPrimitives, UploadFormModelSelector } from './UploadFormModel'; import type { BeaconLineType } from './BeaconLineTypeEnum'; import type { BeaconType } from './BeaconTypeEnum'; @@ -83,6 +88,7 @@ import type { GenerationType } from './GenerationTypeEnum'; import type { LogType } from './LogTypeEnum'; import type { MitreTechniques } from './MitreTechniquesEnum'; import type { ParsingStatus } from './ParsingStatusEnum'; +import type { ServerDelineationTypes } from './ServerDelineationTypesEnum'; import type { ServerType } from './ServerTypeEnum'; import type { Shapes } from './ShapesEnum'; import type { SortDirection } from './SortDirectionEnum'; @@ -90,6 +96,8 @@ import type { SortOption } from './SortOptionEnum'; import type { SortOptionComments } from './SortOptionCommentsEnum'; import type { SortOptionCommentsList } from './SortOptionCommentsListEnum'; import type { SortOptionCommentsTab } from './SortOptionCommentsTabEnum'; +import type { UploadType } from './UploadTypeEnum'; +import type { ValidationMode } from './ValidationModeEnum'; export type AnonymizationInput = { findReplace?: FindReplaceInput[]; @@ -139,6 +147,7 @@ type Refs = { links: ObservableMap; logEntries: ObservableMap; operators: ObservableMap; + parserInfos: ObservableMap; presentationCommandGroups: ObservableMap; presentationItems: ObservableMap; servers: ObservableMap; @@ -170,6 +179,7 @@ export enum RootStoreBaseQueries { queryLogsByBeaconId = 'queryLogsByBeaconId', queryNonHidableEntities = 'queryNonHidableEntities', queryOperators = 'queryOperators', + queryParserInfo = 'queryParserInfo', queryParsingProgress = 'queryParsingProgress', queryPresentationItems = 'queryPresentationItems', querySearchAnnotations = 'querySearchAnnotations', @@ -212,10 +222,13 @@ export class RootStoreBase extends ExtendedModel( ['Beacon', () => BeaconModel], ['BeaconMeta', () => BeaconMetaModel], ['Campaign', () => CampaignModel], + ['CampaignParser', () => CampaignParserModel], ['Command', () => CommandModel], ['CommandGroup', () => CommandGroupModel], ['CommandTypeCount', () => CommandTypeCountModel], ['File', () => FileModel], + ['FileDisplay', () => FileDisplayModel], + ['FileUpload', () => FileUploadModel], ['GlobalOperator', () => GlobalOperatorModel], ['Host', () => HostModel], ['HostMeta', () => HostMetaModel], @@ -224,6 +237,7 @@ export class RootStoreBase extends ExtendedModel( ['LogEntry', () => LogEntryModel], ['NonHidableEntities', () => NonHidableEntitiesModel], ['Operator', () => OperatorModel], + ['ParserInfo', () => ParserInfoModel], ['ParsingProgress', () => ParsingProgressModel], ['PresentationCommandGroup', () => PresentationCommandGroupModel], ['PresentationItem', () => PresentationItemModel], @@ -234,6 +248,7 @@ export class RootStoreBase extends ExtendedModel( ['Timeline', () => TimelineModel], ['TimelineBucket', () => TimelineBucketModel], ['TimelineCommandCountTuple', () => TimelineCommandCountTupleModel], + ['UploadForm', () => UploadFormModel], ], [ 'Annotation', @@ -251,6 +266,7 @@ export class RootStoreBase extends ExtendedModel( 'Link', 'LogEntry', 'Operator', + 'ParserInfo', 'PresentationCommandGroup', 'PresentationItem', 'Server', @@ -275,6 +291,7 @@ export class RootStoreBase extends ExtendedModel( links: prop(() => objectMap()), logEntries: prop(() => objectMap()), operators: prop(() => objectMap()), + parserInfos: prop(() => objectMap()), presentationCommandGroups: prop(() => objectMap()), presentationItems: prop(() => objectMap()), servers: prop(() => objectMap()), @@ -686,6 +703,23 @@ export class RootStoreBase extends ExtendedModel( return this.query<{ operators: OperatorModel[] }>( `query operators($campaignId: String!, $hidden: Boolean) { operators(campaignId: $campaignId, hidden: $hidden) { ${typeof resultSelector === 'function' ? resultSelector(OperatorModelSelector).toString() : resultSelector} + } }`, + variables, + options, + !!clean + ); + } + @modelAction queryParserInfo( + variables?: {}, + resultSelector: + | string + | ((qb: typeof ParserInfoModelSelector) => typeof ParserInfoModelSelector) = parserInfoModelPrimitives.toString(), + options: QueryOptions = {}, + clean?: boolean + ) { + return this.query<{ parserInfo: ParserInfoModel[] }>( + `query parserInfo { parserInfo { + ${typeof resultSelector === 'function' ? resultSelector(ParserInfoModelSelector).toString() : resultSelector} } }`, variables, options, @@ -935,14 +969,14 @@ export class RootStoreBase extends ExtendedModel( } // Create a new Campaign @modelAction mutateCreateCampaign( - variables: { creatorName: string; name: string }, + variables: { creatorName: string; name: string; parser: string }, resultSelector: | string | ((qb: typeof CampaignModelSelector) => typeof CampaignModelSelector) = campaignModelPrimitives.toString(), optimisticUpdate?: () => void ) { return this.mutate<{ createCampaign: CampaignModel }>( - `mutation createCampaign($creatorName: String!, $name: String!) { createCampaign(creatorName: $creatorName, name: $name) { + `mutation createCampaign($creatorName: String!, $name: String!, $parser: String!) { createCampaign(creatorName: $creatorName, name: $name, parser: $parser) { ${typeof resultSelector === 'function' ? resultSelector(CampaignModelSelector).toString() : resultSelector} } }`, variables, @@ -1257,6 +1291,8 @@ export const logEntriesRef = appRef(RootStoreBase, 'LogEntry', 'l export const operatorsRef = appRef(RootStoreBase, 'Operator', 'operators'); +export const parserInfosRef = appRef(RootStoreBase, 'ParserInfo', 'parserInfos'); + export const presentationCommandGroupsRef = appRef( RootStoreBase, 'PresentationCommandGroup', @@ -1291,6 +1327,7 @@ export const rootRefs = { links: linksRef, logEntries: logEntriesRef, operators: operatorsRef, + parserInfos: parserInfosRef, presentationCommandGroups: presentationCommandGroupsRef, presentationItems: presentationItemsRef, servers: serversRef, diff --git a/applications/client/src/store/graphql/ServerDelineationTypesEnum.ts b/applications/client/src/store/graphql/ServerDelineationTypesEnum.ts new file mode 100644 index 00000000..8f9e5f84 --- /dev/null +++ b/applications/client/src/store/graphql/ServerDelineationTypesEnum.ts @@ -0,0 +1,19 @@ +/* This is a mk-gql generated file, don't modify it manually */ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { types, prop, tProp, Model, Ref } from 'mobx-keystone'; + +/** + * Typescript enum + */ + +export enum ServerDelineationTypes { + Database = 'Database', + Folder = 'Folder', +} + +/** + * ServerDelineationTypes + */ +export const ServerDelineationTypesEnumType = types.enum(ServerDelineationTypes); diff --git a/applications/client/src/store/graphql/ServerModel.base.ts b/applications/client/src/store/graphql/ServerModel.base.ts index f24cf07e..27934b0a 100644 --- a/applications/client/src/store/graphql/ServerModel.base.ts +++ b/applications/client/src/store/graphql/ServerModel.base.ts @@ -28,7 +28,7 @@ export class ServerModelBase extends Model({ beacons: prop[]>(() => []).withSetter(), commandsCount: prop().withSetter(), commentCount: prop().withSetter(), - displayName: prop().withSetter(), + displayName: prop().withSetter(), hidden: prop().withSetter(), id: prop().withSetter(), logsCount: prop().withSetter(), diff --git a/applications/client/src/store/graphql/ServerTypeEnum.ts b/applications/client/src/store/graphql/ServerTypeEnum.ts index 7fb65cec..6405cad1 100644 --- a/applications/client/src/store/graphql/ServerTypeEnum.ts +++ b/applications/client/src/store/graphql/ServerTypeEnum.ts @@ -9,10 +9,10 @@ import { types, prop, tProp, Model, Ref } from 'mobx-keystone'; */ export enum ServerType { - DNS = 'DNS', - HTTP = 'HTTP', - HTTPS = 'HTTPS', - SMB = 'SMB', + dns = 'dns', + http = 'http', + https = 'https', + smb = 'smb', } /** diff --git a/applications/client/src/store/graphql/UploadFormModel.base.ts b/applications/client/src/store/graphql/UploadFormModel.base.ts new file mode 100644 index 00000000..1f62cb59 --- /dev/null +++ b/applications/client/src/store/graphql/UploadFormModel.base.ts @@ -0,0 +1,53 @@ +/* This is a mk-gql generated file, don't modify it manually */ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck + +import { types, prop, tProp, Model, Ref, idProp } from 'mobx-keystone'; +import { QueryBuilder } from 'mk-gql'; +import type { FileDisplayModel } from './FileDisplayModel'; +import type { FileUploadModel } from './FileUploadModel'; +import type { ServerDelineationTypes } from './ServerDelineationTypesEnum'; + +import { FileDisplayModelSelector, fileDisplayModelPrimitives } from './FileDisplayModel'; +import { FileUploadModelSelector, fileUploadModelPrimitives } from './FileUploadModel'; + +/** + * UploadFormBase + * auto generated base class for the model UploadFormModel. + */ +export class UploadFormModelBase extends Model({ + __typename: tProp('UploadForm'), + enabledInBlueTeam: prop().withSetter(), + fileDisplay: prop().withSetter(), + fileUpload: prop().withSetter(), + serverDelineation: prop().withSetter(), + tabTitle: prop().withSetter(), +}) {} + +export class UploadFormModelSelector extends QueryBuilder { + get enabledInBlueTeam() { + return this.__attr(`enabledInBlueTeam`); + } + get serverDelineation() { + return this.__attr(`serverDelineation`); + } + get tabTitle() { + return this.__attr(`tabTitle`); + } + fileDisplay( + builder?: string | FileDisplayModelSelector | ((selector: FileDisplayModelSelector) => FileDisplayModelSelector) + ) { + return this.__child(`fileDisplay`, FileDisplayModelSelector, builder); + } + fileUpload( + builder?: string | FileUploadModelSelector | ((selector: FileUploadModelSelector) => FileUploadModelSelector) + ) { + return this.__child(`fileUpload`, FileUploadModelSelector, builder); + } +} +export function selectFromUploadForm() { + return new UploadFormModelSelector(); +} + +export const uploadFormModelPrimitives = selectFromUploadForm().enabledInBlueTeam.serverDelineation.tabTitle; diff --git a/applications/client/src/store/graphql/UploadFormModel.ts b/applications/client/src/store/graphql/UploadFormModel.ts new file mode 100644 index 00000000..2379843f --- /dev/null +++ b/applications/client/src/store/graphql/UploadFormModel.ts @@ -0,0 +1,11 @@ +import { ExtendedModel, model } from 'mobx-keystone'; +import { UploadFormModelBase } from './UploadFormModel.base'; + +/* A graphql query fragment builders for UploadFormModel */ +export { selectFromUploadForm, uploadFormModelPrimitives, UploadFormModelSelector } from './UploadFormModel.base'; + +/** + * UploadFormModel + */ +@model('UploadForm') +export class UploadFormModel extends ExtendedModel(UploadFormModelBase, {}) {} diff --git a/applications/client/src/store/graphql/UploadTypeEnum.ts b/applications/client/src/store/graphql/UploadTypeEnum.ts new file mode 100644 index 00000000..75ee2042 --- /dev/null +++ b/applications/client/src/store/graphql/UploadTypeEnum.ts @@ -0,0 +1,19 @@ +/* This is a mk-gql generated file, don't modify it manually */ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { types, prop, tProp, Model, Ref } from 'mobx-keystone'; + +/** + * Typescript enum + */ + +export enum UploadType { + Directory = 'Directory', + File = 'File', +} + +/** + * UploadType + */ +export const UploadTypeEnumType = types.enum(UploadType); diff --git a/applications/client/src/store/graphql/ValidationModeEnum.ts b/applications/client/src/store/graphql/ValidationModeEnum.ts new file mode 100644 index 00000000..ae1e37ee --- /dev/null +++ b/applications/client/src/store/graphql/ValidationModeEnum.ts @@ -0,0 +1,20 @@ +/* This is a mk-gql generated file, don't modify it manually */ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { types, prop, tProp, Model, Ref } from 'mobx-keystone'; + +/** + * Typescript enum + */ + +export enum ValidationMode { + FileExtensions = 'FileExtensions', + None = 'None', + Parser = 'Parser', +} + +/** + * ValidationMode + */ +export const ValidationModeEnumType = types.enum(ValidationMode); diff --git a/applications/client/src/store/graphql/root.ts b/applications/client/src/store/graphql/root.ts index 10b971dd..fd85f52c 100644 --- a/applications/client/src/store/graphql/root.ts +++ b/applications/client/src/store/graphql/root.ts @@ -9,11 +9,14 @@ export * from './BeaconLineTypeEnum'; export * from './BeaconMetaModel'; export * from './BeaconTypeEnum'; export * from './CampaignModel'; +export * from './CampaignParserModel'; export * from './CommandModel'; export * from './CommandGroupModel'; export * from './CommandTypeCountModel'; export * from './FileModel'; +export * from './FileDisplayModel'; export * from './FileFlagEnum'; +export * from './FileUploadModel'; export * from './GenerationTypeEnum'; export * from './GlobalOperatorModel'; export * from './HostModel'; @@ -25,11 +28,13 @@ export * from './LogTypeEnum'; export * from './MitreTechniquesEnum'; export * from './NonHidableEntitiesModel'; export * from './OperatorModel'; +export * from './ParserInfoModel'; export * from './ParsingProgressModel'; export * from './ParsingStatusEnum'; export * from './PresentationCommandGroupModel'; export * from './PresentationItemModel'; export * from './ServerModel'; +export * from './ServerDelineationTypesEnum'; export * from './ServerMetaModel'; export * from './ServerParsingProgressModel'; export * from './ServerTypeEnum'; @@ -43,5 +48,8 @@ export * from './TagModel'; export * from './TimelineModel'; export * from './TimelineBucketModel'; export * from './TimelineCommandCountTupleModel'; +export * from './UploadFormModel'; +export * from './UploadTypeEnum'; +export * from './ValidationModeEnum'; export * from './RootStore'; export * from './RootStore.base'; diff --git a/applications/client/src/views/Campaign/Explore/Panels/Beacon/BeaconRow.tsx b/applications/client/src/views/Campaign/Explore/Panels/Beacon/BeaconRow.tsx index 26c37613..fc139aad 100644 --- a/applications/client/src/views/Campaign/Explore/Panels/Beacon/BeaconRow.tsx +++ b/applications/client/src/views/Campaign/Explore/Panels/Beacon/BeaconRow.tsx @@ -54,7 +54,7 @@ export const BeaconRow = observer(({ beacon, ...props }) => { }); const skeletonClass = useMemo( - () => (!(beacon.displayName || beacon.server?.displayName) ? Classes.SKELETON : ''), + () => (!(beacon.computedName || beacon.server?.computedName) ? Classes.SKELETON : ''), [beacon.displayName, beacon.server?.displayName] ); @@ -136,7 +136,7 @@ export const BeaconRow = observer(({ beacon, ...props }) => { */} - {beacon?.displayName || `${beacon.server?.displayName}`} + {beacon?.computedName || `${beacon.server?.computedName}`} {beacon.meta?.[0]?.maybeCurrent?.username} diff --git a/applications/client/src/views/Campaigns/Campaigns.tsx b/applications/client/src/views/Campaigns/Campaigns.tsx index 3c8f9330..9bfa9dd4 100644 --- a/applications/client/src/views/Campaigns/Campaigns.tsx +++ b/applications/client/src/views/Campaigns/Campaigns.tsx @@ -4,7 +4,7 @@ import { css } from '@emotion/react'; import { AuthCheck, CarbonIcon, ErrorFallback, ExpandingSearchBox, AppHeader } from '@redeye/client/components'; import { createState } from '@redeye/client/components/mobx-create-state'; import type { CampaignModel } from '@redeye/client/store'; -import { useStore } from '@redeye/client/store'; +import { parserInfoModelPrimitives, useStore } from '@redeye/client/store'; import { CampaignCard, NewCampaignDialog } from '@redeye/client/views'; import { FlexSplitter, Header, Txt } from '@redeye/ui-styles'; import { useQuery } from '@tanstack/react-query'; @@ -18,6 +18,20 @@ type CampaignsProps = ComponentProps<'div'> & {}; const Campaigns = observer(() => { const store = useStore(); const campaignQuery = useQuery(['campaigns'], async () => await store.graphqlStore.queryCampaigns()); + useQuery( + ['parsers'], + async () => + await store.graphqlStore.queryParserInfo( + {}, + parserInfoModelPrimitives + .uploadForm((up) => + up.enabledInBlueTeam.tabTitle + .fileUpload((fileUp) => fileUp.example.type.validate.acceptedExtensions.description) + .fileDisplay((fileDis) => fileDis.editable) + ) + .toString() + ) + ); const state = createState({ openAddCampaign: false, search: '', diff --git a/applications/client/src/views/Campaigns/Upload/CobaltStrikeUploadForm.tsx b/applications/client/src/views/Campaigns/Upload/CobaltStrikeUploadForm.tsx deleted file mode 100644 index 6ad9acfe..00000000 --- a/applications/client/src/views/Campaigns/Upload/CobaltStrikeUploadForm.tsx +++ /dev/null @@ -1,466 +0,0 @@ -import { - Button, - Callout, - Classes, - Collapse, - ControlGroup, - Divider, - FileInput, - FormGroup, - InputGroup, - Intent, - Position, - Radio, - RadioGroup, -} from '@blueprintjs/core'; -import { Popover2 } from '@blueprintjs/popover2'; -import { - ChevronDown16, - ChevronRight16, - Download16, - Folder16, - FolderOff16, - TrashCan16, - Warning20, -} from '@carbon/icons-react'; -import { css } from '@emotion/react'; -import { - CarbonIcon, - DialogBodyEx, - DialogFooterEx, - HoverButton, - ScrollBox, - ScrollChild, -} from '@redeye/client/components'; -import { createState } from '@redeye/client/components/mobx-create-state'; -import type { Servers } from '@redeye/client/store'; -import { useStore } from '@redeye/client/store'; -import type { DirectoryFile, DirectoryInput } from '@redeye/client/types/directory'; -import { DirectorWorkerType } from '@redeye/client/types/directory'; -import { Txt } from '@redeye/ui-styles'; -import { action, observable } from 'mobx'; -import { observer } from 'mobx-react-lite'; -import type { ChangeEvent, ComponentProps, FormEvent } from 'react'; -// eslint-disable-next-line import/extensions -import workerString from './file-worker-js.js?raw'; -import FileWorker from './file-worker?worker'; - -const workerBlob = new Blob([workerString], { type: 'text/javascript' }); -const workerURL = URL.createObjectURL(workerBlob); - -type CobaltStrikeUploadFormProps = ComponentProps<'form'> & { - onClose: (...args: any) => void; -}; - -type MessageEvents = - | { type: DirectorWorkerType.FILES; validFiles: DirectoryFile[]; invalidFiles: DirectoryFile[] } - | { type: DirectorWorkerType.SERVER_FILES; serverFiles: DirectoryFile[]; serverName: string } - | { type: DirectorWorkerType.FINISHED }; - -const defaultServer: Servers = { - name: '', - fileCount: 0, - fileData: undefined, - completed: 0, - totalTasks: 0, - isParsingFiles: false, -}; - -export const CobaltStrikeUploadForm = observer(({ ...props }) => { - const store = useStore(); - const state = createState({ - campaignName: '' as string, - campaignId: undefined as undefined | number, - servers: observable.array(), - nameTaken: false as boolean, - originalFiles: observable.array(), - files: observable.array(), - invalidFiles: observable.array(), - uploadError: undefined as undefined | string, - multiServerUpload: undefined as undefined | boolean, - loading: false, - uploading: false, - showExample: false, - setCampaignName(e) { - this.nameTaken = Array.from(store.graphqlStore.campaigns.values()).some( - (d) => d?.name.toLowerCase() === e.target.value.trim().toLowerCase() - ); - this.campaignName = e.target.value; - }, - createServers(files: DirectoryFile[], serverName: string) { - const server = { ...defaultServer, name: serverName }; - const data = new FormData(); - files.forEach((file: DirectoryFile) => { - const f: File = new File( - [file.blob], - file.webkitRelativePath.replace(/\//g, ':').split(':').slice(1).join(':'), - { - type: file.type, - } - ); - data.append('file', f); - }); - server.fileCount = files.length; - server.fileData = data; - this.servers.push(server); - }, - fileError(error: string) { - this.uploadError = error; - this.files.clear(); - }, - fileSelect() { - this.files.clear(); - this.invalidFiles.clear(); - this.uploadError = undefined; - if (this.originalFiles.length) { - this.loading = true; - const worker = navigator.userAgent.match(/firefox|fxios/i) - ? new Worker(workerURL, { - type: 'classic', - }) - : new FileWorker(); - worker.postMessage({ - acceptedFiles: this.originalFiles.map((file) => ({ - ...file, - webkitRelativePath: file.webkitRelativePath, - name: file.name, - blob: file.slice(), - })), - multiServerUpload: this.multiServerUpload, - }); - worker.onmessage = action((event: MessageEvent) => { - switch (event.data.type) { - case DirectorWorkerType.FILES: - this.files.push(...event.data.validFiles); - this.invalidFiles.push(...event.data.invalidFiles); - break; - case DirectorWorkerType.SERVER_FILES: - this.createServers(event.data.serverFiles, event.data.serverName); - break; - case DirectorWorkerType.FINISHED: - worker.terminate(); - this.loading = false; - break; - default: - break; - } - }); - worker.onmessageerror = action((error) => { - this.fileError(error.type); - worker.terminate(); - this.loading = false; - }); - worker.onerror = action((error) => { - this.fileError(error.message.replace('Uncaught Error:', '')); - worker.terminate(); - this.loading = false; - }); - } else { - // return this.fileError('No Valid Files Found'); // not sure this is an error? - } - }, - *submitData(e: FormEvent) { - e.preventDefault(); - this.uploading = true; - const campaign: Awaited> = - yield store.graphqlStore.mutateCreateCampaign({ - creatorName: store.auth.userName!, - name: this.campaignName, - }); - - for (const server of this.servers) { - server.fileData?.set('serverName', server.name); - try { - const res: Response = yield store.auth.protectedFetch( - `${store.auth.serverUrl}/api/server/upload/${campaign.createCampaign.id}`, - { - mode: 'cors', - cache: 'no-cache', - credentials: 'include', - method: 'POST', - body: server.fileData, - } - ); - if (res.status !== 200) { - window.console.error('Error Uploading Logs'); - // this should provide some UI feedback in the form as to the reason - } else { - server.isParsingFiles = true; - } - } catch (error) { - window.console.warn('Error Uploading Logs', error); - } - } - yield store.graphqlStore.mutateServersParse({ campaignId: campaign.createCampaign.id }); - campaign?.createCampaign?.processServers?.(); - this.uploading = false; - props.onClose(); - }, - onMultiServerUploadChange(e: FormEvent) { - this.multiServerUpload = e.currentTarget.value === UploadMode.Multi; - this.servers.clear(); - this.fileSelect(); - }, - onFileInputChange(e: ChangeEvent) { - this.originalFiles.replace(e.target.files ? Array.from(e.target.files) : []); - this.servers.clear(); - this.fileSelect(); - this.showExample = false; - }, - }); - - // Typing for input element doesn't like the directory props - const inputProps: DirectoryInput = { - webkitdirectory: 'true', - directory: 'true', - mozdirectory: 'true', - type: 'file', - }; - - return ( -
- - - - - - - - - - - - - {state.multiServerUpload == null ? ( - - Select Upload Mode - - ) : ( - <> - - {state.multiServerUpload ? ( - - Select a single Campaign Folder that contains multiple CobaltStrike  - Server Folders.
- Each Server Folder should contain beacon logs in dated ( - YYMMDD) folders. -
- ) : ( - - Select a single CobaltStrike Server Folder containing beacon logs in dated ( - YYMMDD) folders. - - )} - - } - > - -