diff --git a/CHANGELOG.md b/CHANGELOG.md index eea688a2..f6485155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.1] - 2025-07-11 + +- Added agent language as filter to avoid displaying incompatible modules +- Added folder display to agent modules + ## [3.0.0] - 2025-03-25 ### Added @@ -411,7 +416,9 @@ Including but not limited to: - Initial Release -[Unreleased]: https://github.com/BC-SECURITY/Starkiller-Sponsors/compare/v3.0.0...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Starkiller-Sponsors/compare/v3.0.1...HEAD + +[3.0.1]: https://github.com/BC-SECURITY/Starkiller-Sponsors/compare/v3.0.0...v3.0.1 [3.0.0]: https://github.com/BC-SECURITY/Starkiller-Sponsors/compare/v2.8.2...v3.0.0 diff --git a/package.json b/package.json index 33b20637..38a775c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "starkiller", - "version": "3.0.0", + "version": "3.0.1", "private": true, "scripts": { "dev": "vite", diff --git a/src/components/agents/AgentExecuteModule.vue b/src/components/agents/AgentExecuteModule.vue index b5e4d56e..333f280d 100644 --- a/src/components/agents/AgentExecuteModule.vue +++ b/src/components/agents/AgentExecuteModule.vue @@ -9,19 +9,71 @@
- Executing on Agents: {{ agents.join(", ") }} + Executing on Agents: + {{ agents.map((agent) => agent.name || agent.session_id).join(", ") }} - + + + No modules are compatible with all selected agents. + + + + + + + + + + + + + + + + Browse Modules + + + + + + + Close + + + + +
+ + Submit + @@ -158,6 +214,9 @@ export default { errorState: false, ignoreAdminCheck: false, ignoreLanguageCheck: false, + treeSelected: [], + treeOpen: [], + showTreeDialog: false, }; }, computed: { @@ -165,30 +224,106 @@ export default { return useModuleStore(); }, modules() { - return this.moduleStore.modules.filter((el) => el.enabled === true); + return this.moduleStore.modules.filter((el) => el.enabled); + }, + compatibleModules() { + if (!this.agents.length) return this.modules; + const getCompatibleLanguages = (agentLang) => { + switch ((agentLang || "").toLowerCase()) { + case "powershell": + case "csharp": + case "go": + return ["powershell", "csharp", "bof"]; + case "ironpython": + return ["powershell", "csharp", "bof", "python"]; + case "python": + return ["python"]; + default: + return []; + } + }; + const agentLangSets = this.agents.map((agent) => + getCompatibleLanguages(agent.language || agent.type), + ); + const sharedLangs = agentLangSets.reduce((acc, curr) => + acc.filter((lang) => curr.includes(lang)), + ); + return this.modules.filter((mod) => + sharedLangs.includes(mod.language?.toLowerCase()), + ); }, - selectOptions() { - return this.modules.map((el) => el.id); + autocompleteOptions() { + return this.compatibleModules.map((mod) => ({ + text: mod.id, + value: mod.id, + })); }, - moduleOptions() { - const options = this.selectedItem.options || {}; + treeOptions() { + const structure = {}; + this.compatibleModules.forEach((mod) => { + const parts = mod.id.split("_"); + const language = parts[0] || "unknown"; - if (options && options.Agent) { - delete options.Agent; - } + let category = parts[1] || "uncategorized"; + if ( + parts.length >= 3 && + ["situational", "lateral", "code"].includes(parts[1]) + ) { + const joined = `${parts[1]}_${parts[2]}`; + if ( + [ + "situational_awareness", + "lateral_movement", + "code_execution", + ].includes(joined) + ) { + category = joined; + } + } + + if (!structure[language]) structure[language] = {}; + if (!structure[language][category]) structure[language][category] = []; + structure[language][category].push({ + id: mod.id, + name: mod.id, + }); + }); + return Object.entries(structure) + .sort(([langA], [langB]) => langA.localeCompare(langB)) // Sort languages + .map(([lang, categories]) => { + const children = Object.entries(categories) + .sort(([catA], [catB]) => catA.localeCompare(catB)) // Sort categories + .flatMap(([cat, modules]) => { + modules.sort((a, b) => a.name.localeCompare(b.name)); // Sort modules + + if (modules.length === 1) { + return modules; + } + return { + id: `${lang}_${cat}`, + name: cat, + children: modules, + }; + }); + + return { + id: lang, + name: lang, + children, + }; + }); + }, + moduleOptions() { + const options = this.selectedItem.options || {}; + if (options.Agent) delete options.Agent; Object.keys(this.moduleOptionDefaults || {}).forEach((key) => { - if (options[key]) { - options[key].value = this.moduleOptionDefaults[key]; - } + if (options[key]) options[key].value = this.moduleOptionDefaults[key]; }); return options; }, moduleInfo() { - if (Object.keys(this.selectedItem).length === 0) { - return {}; - } - + if (Object.keys(this.selectedItem).length === 0) return {}; return { authors: this.selectedItem.authors, description: this.selectedItem.description, @@ -238,6 +373,9 @@ export default { await this.moduleStore.getModules(); }, methods: { + toggleTree() { + this.showTreeDialog = true; + }, async handleSelect(item) { this.errorState = false; if (!item) { @@ -258,9 +396,19 @@ export default { this.reset = true; }, 500); }, + handleTreeSelect(selected) { + if (selected.length > 0) { + const moduleId = selected[0]; + const match = this.compatibleModules.find((m) => m.id === moduleId); + if (match) { + this.selectedModule = moduleId; + this.handleSelect(moduleId); + this.showTreeDialog = false; + } + } + }, rowClass(item) { - if (item.status === "rejected") return "red"; - return ""; + return item.status === "rejected" ? "red" : ""; }, emitModuleChange(newVal) { this.$emit("moduleChange", newVal); @@ -269,26 +417,21 @@ export default { return this.$refs.generalform.$refs.form.validate(); }, async create() { - if (this.agents.length < 1 || this.loading || !this.validate()) { - return; - } - + if (this.agents.length < 1 || this.loading || !this.validate()) return; this.loading = true; - const result = await Promise.allSettled( this.agents.map((agent) => moduleApi.executeModule( this.selectedModule, { ...this.form, - Agent: agent, + Agent: agent.session_id, }, this.ignoreAdminCheck, this.ignoreLanguageCheck, ), ), ); - if (result.some((item) => item.status === "rejected")) { const split = result.reduce( (acc, val) => { @@ -297,7 +440,6 @@ export default { }, { rejected: [], fulfilled: [] }, ); - if (this.agents.length > 1) { this.$snack.warn( `Module failed to execute for ${split.rejected.length} out of ${this.agents.length} agents.`, @@ -317,15 +459,21 @@ export default { this.selectedModule = ""; this.$emit("submitted"); } - this.loading = false; }, }, }; - diff --git a/src/views/AgentEdit.vue b/src/views/AgentEdit.vue index 43402fb1..46694c6d 100644 --- a/src/views/AgentEdit.vue +++ b/src/views/AgentEdit.vue @@ -153,8 +153,8 @@

Execute Module

- + +