mirror of
https://github.com/its-a-feature/Mythic.git
synced 2026-11-07 17:22:06 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96b24b48a7 | ||
|
|
bdc722f6ce | ||
|
|
f9e71d0859 | ||
|
|
742d878928 | ||
|
|
01ea8bc896 | ||
|
|
94418bcd52 | ||
|
|
3ae9c5fe18 | ||
|
|
5e85a20d14 | ||
|
|
5f46db6ede | ||
|
|
e37e057a89 | ||
|
|
33a46d7db7 | ||
|
|
540aec0f00 | ||
|
|
29be09eef4 | ||
|
|
a5a1a972d7 | ||
|
|
82648e8241 | ||
|
|
e4da2c887f | ||
|
|
39f44f7dec | ||
|
|
0f51584493 | ||
|
|
dc50ecc0dc | ||
|
|
59211a2fbf | ||
|
|
f8d355a29c | ||
|
|
1da8fdcd2a | ||
|
|
2dcc3b5699 | ||
|
|
8deec59270 | ||
|
|
071abeaf74 | ||
|
|
6eb53da6b5 | ||
|
|
d605a2f82c | ||
|
|
31db999945 | ||
|
|
9ae193be76 | ||
|
|
fc731af819 | ||
|
|
d21dbf293b | ||
|
|
b6d98b1794 | ||
|
|
448a57b82b | ||
|
|
1d54d5ea0c | ||
|
|
4d793d5998 | ||
|
|
60455c26cb | ||
|
|
183f0605c2 | ||
|
|
37d3cc8b1b | ||
|
|
9532b96f7a | ||
|
|
e1806f0fd5 | ||
|
|
fa235b6378 | ||
|
|
b87219b1e9 | ||
|
|
b5459b56c8 | ||
|
|
ca6d7d2ce0 | ||
|
|
6148c5f0fb | ||
|
|
6e14b2a472 | ||
|
|
307df57df8 | ||
|
|
4a4d95f1ca | ||
|
|
ffc5041349 | ||
|
|
ecda245963 | ||
|
|
6ea67c69b9 | ||
|
|
64d32eb116 | ||
|
|
9f7a1ce770 | ||
|
|
4a3818988c | ||
|
|
1ec6318b62 | ||
|
|
60902a6e66 | ||
|
|
ddda1a266c | ||
|
|
8dc9f351d9 | ||
|
|
d7b4d51186 | ||
|
|
d2334b79de | ||
|
|
96df4db36e | ||
|
|
a72dd1b086 | ||
|
|
89bee21894 | ||
|
|
26a4410f0a | ||
|
|
36fb4c58bf | ||
|
|
cfdf0c537d | ||
|
|
f23e123a56 | ||
|
|
5b4c210e6f | ||
|
|
3dee8797da | ||
|
|
33f48baab4 | ||
|
|
3198f304c3 | ||
|
|
09ac733d45 | ||
|
|
3e611494cc | ||
|
|
8be3c084e5 | ||
|
|
3796640835 | ||
|
|
6e23b29f22 | ||
|
|
22e5c489b2 | ||
|
|
8cdf34c3e7 | ||
|
|
b285558fce | ||
|
|
e536f59347 | ||
|
|
4c472f7e3a | ||
|
|
cc77f2b1a3 | ||
|
|
1c18703e96 | ||
|
|
f017661b3d | ||
|
|
98dd30d984 | ||
|
|
eb3cf1053b | ||
|
|
58e2c6de5f | ||
|
|
7dc66c43aa | ||
|
|
9c6afe2d8c | ||
|
|
6c5b1d2a24 | ||
|
|
fc8239eb85 | ||
|
|
180c6e1914 | ||
|
|
59c97d791f | ||
|
|
d9e1d5e6bd | ||
|
|
0f5746b4a3 | ||
|
|
71c03251fb | ||
|
|
f742ab3d0f | ||
|
|
d3ae12a220 | ||
|
|
d9a41572ac | ||
|
|
e454531999 | ||
|
|
45cf6a9cd8 | ||
|
|
5011314ba5 | ||
|
|
af9d3c40e6 | ||
|
|
03e27abdb6 | ||
|
|
e7fde3fcde | ||
|
|
51c0eeb5df | ||
|
|
0f259d1f7d | ||
|
|
d86f0d6227 | ||
|
|
3bb93efa72 | ||
|
|
652234d7e1 | ||
|
|
44d8fd168e | ||
|
|
2e418bbb4d | ||
|
|
87249b2891 | ||
|
|
dbb15fdbb3 | ||
|
|
04ebb6aba7 | ||
|
|
8a2e42c48c | ||
|
|
db682d5020 | ||
|
|
6649279e3e | ||
|
|
6d1778bb3b | ||
|
|
0484154eae | ||
|
|
99f2b6cb2f | ||
|
|
303741ee28 | ||
|
|
37a3f66d68 | ||
|
|
8e43275c27 | ||
|
|
6dfe40b274 | ||
|
|
49e1c62502 | ||
|
|
a739837c95 | ||
|
|
063f70a55d | ||
|
|
fb6c3d8368 | ||
|
|
10f0eb9e7a | ||
|
|
afdd0ec9cc | ||
|
|
2391244baa | ||
|
|
9e0fbcfa3a | ||
|
|
54e6cc5d32 | ||
|
|
c56d92ee52 | ||
|
|
706e8d3ce7 | ||
|
|
29e57f1384 | ||
|
|
bf7de9ad22 | ||
|
|
e3cbdd35e7 | ||
|
|
2ca4ad507e | ||
|
|
7f225e51f1 | ||
|
|
bf22f85661 |
No files matched your search
@@ -167,3 +167,6 @@ _ReSharper*/
|
||||
jupyter-docker/jupyter/MythicExamples/.ipynb_checkpoints/
|
||||
|
||||
nginx-docker/config/blockips.conf
|
||||
/MythicReactUI/build/
|
||||
/MythicReactUI/node_modules/
|
||||
/.gocache/
|
||||
+120
@@ -4,6 +4,126 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [3.4.36] - 2026-06-21
|
||||
|
||||
### Changed
|
||||
|
||||
- Addressed 3 issues from GitHub about spectators and overly scoped hasura filters
|
||||
|
||||
## [3.4.35] - 2026-04-16
|
||||
|
||||
### Changed
|
||||
|
||||
- Merged PR #546 to address missing return in GenerateAPITokenWebhook that would allow non-admin users to generate API Tokens for Bot accounts
|
||||
- Added server name to emitted log messages
|
||||
- Merged PR #545 to address missing operation_id in task's log messages for logging containers
|
||||
- Updated C2 profile code in the UI to properly handle `ui_position` from C2 Profile parameters in payload build
|
||||
|
||||
## [3.4.34] - 2026-04-13
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated way that SendAllOperationsMessage functions to reduce duplicate messages to event log
|
||||
- Reduced the number of lookups per error message to increment count by caching error lookup data
|
||||
- Updated event log messages to ensure proper UTF8 encoding (and removal of bad characters)
|
||||
- Using channels to ensure no duplicate messages are created due to go concurrency
|
||||
|
||||
## [3.4.33] - 2026-04-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed an issue where payload/command syncing would error out and leave dangling database connections
|
||||
- Updated the postgres docker image to use customized database info for health checks
|
||||
|
||||
## [3.4.32] - 2026-04-06
|
||||
|
||||
### Changed
|
||||
|
||||
- Merged PR to add GET route for /auth_acs/:containerName/:IDPName that was POST only
|
||||
|
||||
## [3.4.31] - 2026-03-23
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed code in `upload` to overwrite uploaded filename with destination filename in filemeta tracking
|
||||
- Updated `download` code to only write files to disk on new chunks for speed improvements
|
||||
- Updated the `rm` tracking to not blanket wildcard paths, but first check if it's a directory and make sure there's a trailing slash before wildcarding
|
||||
|
||||
## [3.4.30] - 2026-03-20
|
||||
|
||||
### Changed
|
||||
|
||||
- Added a `received_chunk_ids` map to the database for `filemeta` to track not just chunks, but which chunks are received
|
||||
- this helps prevent duplicate chunks getting counted multiple times and causing faulty downloads
|
||||
- Added some default values for `DisplayPath` that were causing null constraint errors on the database
|
||||
- Fixed a remote path null value with uploading eventing files
|
||||
|
||||
## [3.4.29] - 2026-03-18
|
||||
|
||||
### Changed
|
||||
|
||||
- Adjusted the MythicRPCFileSearch function to not return tracked copies of existing files
|
||||
|
||||
## [3.4.28] - 2026-03-17
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed a bug where file transfers would fail if no remote path supplied
|
||||
|
||||
## [3.4.27] - 2026-03-16
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed a bug where mythic would try to write to a null message after processing a bad message
|
||||
|
||||
## [3.4.26] - 2026-03-12
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed a bug in "Callback Imports" that wouldn't recognize exising build parameter types
|
||||
|
||||
## [3.4.25] - 2026-03-04
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated rpfwd to do burst proxy reads similar to socks
|
||||
|
||||
## [3.4.24] - 2026-02-18
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated PushC2 to funnel traffic into one channel instead of two
|
||||
- Updated socks proxy reads to do a basic burst for read sizes to reduce number of concurrent waiting messages
|
||||
- this increases throughput for socks traffic with a bit more bursty memory usage
|
||||
- Updated key reflection in agent messages to omit cwd and impersonation_context fields
|
||||
- Updated empty BFS paths to be cached as well so non-existent paths aren't recomputed a bunch
|
||||
|
||||
## [3.4.23] - 2026-02-08
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated the payload type syncing to resync for all payload types, not just new wrappers
|
||||
- this fixes a bug where payload types syncing for the first time after their wrapper synced wouldn't match them up
|
||||
|
||||
## [3.4.22] - 2026-02-02
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed an issue where building payloads while c2 profiles were syncing would double issue tasks to c2 profile and hang
|
||||
- Fixed an issue here cached p2p paths weren't getting flushed properly
|
||||
|
||||
## [3.4.21] - 2026-01-14
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed an issue where downloaded files weren't having their sizes reflected after completion in the file browser
|
||||
|
||||
## [3.4.20] - 2026-01-08
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated IterateAndAct to treat a crypto value of "" the same as "none"
|
||||
|
||||
## [3.4.19] - 2025-12-31
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -4,6 +4,150 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.3.113] - 2026-07-01
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed a bug in the process browser that would set a filter to undefined instead of ""
|
||||
|
||||
## [0.3.112] - 2026-05-17
|
||||
|
||||
- Updated the single task view to show interactive tasking surrounding results
|
||||
|
||||
## [0.3.111] - 2026-05-07
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed an issue with useLazyQuery spamming requests
|
||||
|
||||
## [0.3.110] - 2026-04-16
|
||||
|
||||
### Changed
|
||||
|
||||
- Added more generic MythicPageBody and MythicServerHeader components to wrap most pages/headers
|
||||
- Added a new border color you can select for borders of a few components
|
||||
- Updated a few places with hard-coded px values to more scalable rem values
|
||||
|
||||
## [0.3.109] - 2026-04-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated the UI to sort c2 profile parameters during payload build by ui_position if specified
|
||||
|
||||
## [0.3.107] - 2026-03-23
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed typo in file browser for remove_folder
|
||||
|
||||
## [0.3.106] - 2026-03-21
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed an issue with clicking file uploads in modals
|
||||
|
||||
## [0.3.105] - 2026-03-20
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated some of the file upload pieces in the UI to support drag-and-drop
|
||||
- Added an `autoScroll` button to interactive tasking so you can force auto scroll or not
|
||||
- Fixed some of the drag and drop modal components
|
||||
|
||||
## [0.3.104] - 2026-03-19
|
||||
|
||||
### Changed
|
||||
|
||||
- Added ability to drag and move some modals and copy data from behind them
|
||||
|
||||
## [0.3.103] - 2026-03-17
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed issue with file uploads for payload/wrapper builds when starting from existing payload
|
||||
|
||||
## [0.3.102] - 2026-03-12
|
||||
|
||||
### Changed
|
||||
|
||||
- Slight tweak to `tabs` browser script capability to cap the max size of the tabs
|
||||
- Fixed a scrolling issue on the single task view page
|
||||
|
||||
## [0.3.101] - 2026-02-19
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed an issue with database view in split view tasking that would cause overflow
|
||||
|
||||
## [0.3.100] - 2026-02-18
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated the eventing page to only stream in updates that match the current filter
|
||||
- Updated payload parameter array type to start empty
|
||||
|
||||
## [0.3.99] - 2026-02-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated process_browser and file_browser tables to allow column sorting, hiding, and saved to user preferences
|
||||
- Updated process_browser to have a new column for integrity_level
|
||||
|
||||
## [0.3.98] - 2026-02-08
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated the default shortcuts to remove Reporting and add CreatePayload
|
||||
- Updated Task Search on Parameters to include agent parameters, not just original and display
|
||||
- Updated callback table bulk actions to include a note about callback last checkin times not updates in those windows
|
||||
|
||||
## [0.3.97] - 2026-02-02
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated browser script tables to rever sort after toggling ASC/DESC to match other virtualized tables in Mythic
|
||||
|
||||
## [0.3.96] - 2026-01-30
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated the file browser to support `file_browser:remove` and `file_browser:remove_folder`
|
||||
- this allows separate commands for deleting files vs deleting folders (or the same command if you want)
|
||||
|
||||
## [0.3.95] - 2026-01-29
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated tasking data for browser scripts to also provide callback's pid
|
||||
- Updated browser scripting to provide new type: `tabs`
|
||||
- returning `{"tabs": [ {"title": "tab title here", "content": {"table": {table dict} } } ] }`
|
||||
- this creates one tab with title `tab title here` that renders a table
|
||||
|
||||
## [0.3.94] - 2026-01-27
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed a UI caching bug in split tasking view that would hide tasks on clicking previously issued tasks
|
||||
|
||||
## [0.3.93] - 2026-01-14
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated UI to allow output background color and output text color to be set separate from the "theme"
|
||||
- Updated UI to reflect file size updates from file browser while streaming
|
||||
|
||||
## [0.3.92] - 2026-01-09
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed a bug in dynamic query parameters in the modal that would sometimes error on choices
|
||||
|
||||
## [0.3.91] - 2026-01-08
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed a bug where c2 parameter deviations would try to iterate over null
|
||||
|
||||
## [0.3.90] - 2026-01-01
|
||||
|
||||
### Changed
|
||||
|
||||
Generated
+226
-276
@@ -45,7 +45,7 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-moment": "^1.1.3",
|
||||
"react-router-dom": "^7.5.2",
|
||||
"react-router-dom": "^7.13.1",
|
||||
"react-scrollbar-size": "^5.0.0",
|
||||
"react-split": "^2.0.14",
|
||||
"react-toastify": "^11.0.5",
|
||||
@@ -58,7 +58,7 @@
|
||||
"sql.js": "^1.13.0",
|
||||
"styled-components": "^6.1.17",
|
||||
"uuid": "^9.0.1",
|
||||
"webpack": "^5.99.6",
|
||||
"webpack": "^5.105.0",
|
||||
"word-wrap": "^1.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -2811,9 +2811,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4703,9 +4703,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/express": {
|
||||
@@ -5548,9 +5548,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@@ -5583,6 +5583,18 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-import-phases": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
|
||||
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"acorn": "^8.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-jsx": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||
@@ -5641,9 +5653,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
||||
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5675,9 +5687,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@@ -6473,6 +6485,15 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.9.19",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
|
||||
"integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/batch": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||
@@ -6775,9 +6796,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
|
||||
"integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
|
||||
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -6794,10 +6815,11 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001688",
|
||||
"electron-to-chromium": "^1.5.73",
|
||||
"node-releases": "^2.0.19",
|
||||
"update-browserslist-db": "^1.1.1"
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
"electron-to-chromium": "^1.5.263",
|
||||
"node-releases": "^2.0.27",
|
||||
"update-browserslist-db": "^1.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@@ -6993,9 +7015,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001748",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz",
|
||||
"integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==",
|
||||
"version": "1.0.30001769",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz",
|
||||
"integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -7485,12 +7507,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
@@ -8869,9 +8895,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.142",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.142.tgz",
|
||||
"integrity": "sha512-Ah2HgkTu/9RhTDNThBtzu2Wirdy4DC9b0sMT1pUhbkZQ5U/iwmE+PHZX1MpjD5IkJCc2wSghgGG/B04szAx07w==",
|
||||
"version": "1.5.286",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
|
||||
"integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/elkjs": {
|
||||
@@ -8944,13 +8970,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
|
||||
"version": "5.19.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
|
||||
"integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
"tapable": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
@@ -9107,9 +9133,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
|
||||
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
@@ -9741,9 +9767,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -9984,40 +10010,40 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"version": "4.22.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
||||
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"body-parser": "~1.20.3",
|
||||
"content-disposition": "~0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.7.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"cookie": "~0.7.1",
|
||||
"cookie-signature": "~1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.3.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"finalhandler": "~1.3.1",
|
||||
"fresh": "~0.5.2",
|
||||
"http-errors": "~2.0.0",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"on-finished": "~2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.12",
|
||||
"path-to-regexp": "~0.1.12",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.13.0",
|
||||
"qs": "~6.14.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.19.0",
|
||||
"serve-static": "1.16.2",
|
||||
"send": "~0.19.0",
|
||||
"serve-static": "~1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"statuses": "~2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
@@ -10057,6 +10083,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/express/node_modules/qs": {
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
|
||||
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -10230,9 +10272,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"version": "5.1.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
|
||||
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -10355,16 +10397,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
|
||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -13338,9 +13380,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -13492,21 +13534,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsonpath": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz",
|
||||
"integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.3.0.tgz",
|
||||
"integrity": "sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esprima": "1.2.2",
|
||||
"static-eval": "2.0.2",
|
||||
"underscore": "1.12.1"
|
||||
"esprima": "1.2.5",
|
||||
"static-eval": "2.1.1",
|
||||
"underscore": "1.13.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonpath/node_modules/esprima": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz",
|
||||
"integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz",
|
||||
"integrity": "sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
@@ -13663,12 +13705,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
|
||||
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
|
||||
"integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.11.5"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/loader-utils": {
|
||||
@@ -13700,9 +13746,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -14042,9 +14088,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -14200,9 +14246,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz",
|
||||
"integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
|
||||
"dev": true,
|
||||
"license": "(BSD-3-Clause OR GPL-2.0)",
|
||||
"engines": {
|
||||
@@ -14217,9 +14263,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
||||
"version": "2.0.27",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
@@ -14824,9 +14870,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -15508,16 +15554,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-load-config/node_modules/yaml": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
|
||||
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-loader": {
|
||||
@@ -16961,14 +17010,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.2.tgz",
|
||||
"integrity": "sha512-9Rw8r199klMnlGZ8VAsV/I8WrIF6IyJ90JQUdboupx1cdkgYqwnrYjH+I/nY/7cA1X5zia4mDJqH36npP7sxGQ==",
|
||||
"version": "7.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
|
||||
"integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
"turbo-stream": "2.4.0"
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -16984,12 +17032,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.2.tgz",
|
||||
"integrity": "sha512-yk1XW8Fj7gK7flpYBXF3yzd2NbX6P7Kxjvs2b5nu1M04rb5pg/Zc4fGdBNTeT4eDYL2bvzWNyKaIMJX/RKHTTg==",
|
||||
"version": "7.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz",
|
||||
"integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.5.2"
|
||||
"react-router": "7.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -17727,9 +17775,9 @@
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.79.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
|
||||
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
||||
"version": "2.80.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz",
|
||||
"integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -17972,9 +18020,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
|
||||
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
@@ -17991,9 +18039,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@@ -18221,9 +18269,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
@@ -18631,111 +18679,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/static-eval": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz",
|
||||
"integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz",
|
||||
"integrity": "sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escodegen": "^1.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/static-eval/node_modules/escodegen": {
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
|
||||
"integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"esprima": "^4.0.1",
|
||||
"estraverse": "^4.2.0",
|
||||
"esutils": "^2.0.2",
|
||||
"optionator": "^0.8.1"
|
||||
},
|
||||
"bin": {
|
||||
"escodegen": "bin/escodegen.js",
|
||||
"esgenerate": "bin/esgenerate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/static-eval/node_modules/estraverse": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/static-eval/node_modules/levn": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
|
||||
"integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/static-eval/node_modules/optionator": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deep-is": "~0.1.3",
|
||||
"fast-levenshtein": "~2.0.6",
|
||||
"levn": "~0.3.0",
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2",
|
||||
"word-wrap": "~1.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/static-eval/node_modules/prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
"integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/static-eval/node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/static-eval/node_modules/type-check": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
"integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prelude-ls": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
"escodegen": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
@@ -19186,9 +19136,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sucrase/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -19227,13 +19177,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sucrase/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
|
||||
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
"brace-expansion": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
@@ -19528,12 +19478,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
|
||||
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/temp-dir": {
|
||||
@@ -19614,9 +19568,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin": {
|
||||
"version": "5.3.14",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
|
||||
"integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
|
||||
"version": "5.3.16",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz",
|
||||
"integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
@@ -19902,12 +19856,6 @@
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/turbo-stream": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
||||
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@@ -20081,9 +20029,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
|
||||
"integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==",
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
|
||||
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -20189,9 +20137,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -20389,9 +20337,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
|
||||
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
|
||||
"integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
@@ -20422,34 +20370,36 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.99.6",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.6.tgz",
|
||||
"integrity": "sha512-TJOLrJ6oeccsGWPl7ujCYuc0pIq2cNsuD6GZDma8i5o5Npvcco/z+NKvZSFsP0/x6SShVb0+X2JK/JHUjKY9dQ==",
|
||||
"version": "5.105.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz",
|
||||
"integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/estree": "^1.0.8",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@webassemblyjs/ast": "^1.14.1",
|
||||
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||
"acorn": "^8.14.0",
|
||||
"browserslist": "^4.24.0",
|
||||
"acorn": "^8.15.0",
|
||||
"acorn-import-phases": "^1.0.3",
|
||||
"browserslist": "^4.28.1",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.17.1",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
"enhanced-resolve": "^5.19.0",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"eslint-scope": "5.1.1",
|
||||
"events": "^3.2.0",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"json-parse-even-better-errors": "^2.3.1",
|
||||
"loader-runner": "^4.2.0",
|
||||
"loader-runner": "^4.3.1",
|
||||
"mime-types": "^2.1.27",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^4.3.0",
|
||||
"tapable": "^2.1.1",
|
||||
"terser-webpack-plugin": "^5.3.11",
|
||||
"watchpack": "^2.4.1",
|
||||
"webpack-sources": "^3.2.3"
|
||||
"schema-utils": "^4.3.3",
|
||||
"tapable": "^2.3.0",
|
||||
"terser-webpack-plugin": "^5.3.16",
|
||||
"watchpack": "^2.5.1",
|
||||
"webpack-sources": "^3.3.3"
|
||||
},
|
||||
"bin": {
|
||||
"webpack": "bin/webpack.js"
|
||||
@@ -20593,9 +20543,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-sources": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
|
||||
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
@@ -20903,9 +20853,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/workbox-build/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -21275,9 +21225,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
|
||||
"integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-moment": "^1.1.3",
|
||||
"react-router-dom": "^7.5.2",
|
||||
"react-router-dom": "^7.13.1",
|
||||
"react-scrollbar-size": "^5.0.0",
|
||||
"react-split": "^2.0.14",
|
||||
"react-toastify": "^11.0.5",
|
||||
@@ -63,7 +63,7 @@
|
||||
"sql.js": "^1.13.0",
|
||||
"styled-components": "^6.1.17",
|
||||
"uuid": "^9.0.1",
|
||||
"webpack": "^5.99.6",
|
||||
"webpack": "^5.105.0",
|
||||
"word-wrap": "^1.2.5"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -19,6 +19,11 @@ export const taskTimestampDisplayFieldOptions = [
|
||||
}
|
||||
]
|
||||
export const taskingContextFieldsOptions = ["impersonation_context", "cwd", "user", "host", "ip", "pid", "process_short_name", "extra_info", "architecture"].sort();
|
||||
export const defaultShortcuts = [
|
||||
"ActiveCallbacks", "Payloads", "PayloadTypesAndC2",
|
||||
"Operations", "SearchFiles", "SearchProxies",
|
||||
"CreatePayload", "Eventing",
|
||||
].sort();
|
||||
export const operatorSettingDefaults = {
|
||||
fontSize: 12,
|
||||
navBarOpen: false,
|
||||
@@ -38,7 +43,8 @@ export const operatorSettingDefaults = {
|
||||
hideBrowserTasking: false,
|
||||
hideTaskingContext: false,
|
||||
taskingContextFields: ["impersonation_context", "cwd"],
|
||||
["experiment-responseStreamLimit"]: 50,
|
||||
["experiment-responseStreamLimit"]: 200,
|
||||
sideShortcuts: defaultShortcuts,
|
||||
palette: {
|
||||
primary: {
|
||||
dark: "#75859b",
|
||||
@@ -140,13 +146,21 @@ export const operatorSettingDefaults = {
|
||||
dark: '#bebebe',
|
||||
light: '#a6a5a5'
|
||||
},
|
||||
outputBackgroundColor: {
|
||||
dark: '#282828',
|
||||
light: '#f6f6f6'
|
||||
},
|
||||
outputTextColor: {
|
||||
dark: '#f6f6f6',
|
||||
light: '#282828',
|
||||
},
|
||||
borderColor: {
|
||||
dark: "#595858",
|
||||
light: "#c5c5c5"
|
||||
}
|
||||
},
|
||||
}
|
||||
export const defaultShortcuts = [
|
||||
"ActiveCallbacks", "Payloads", "PayloadTypesAndC2",
|
||||
"Operations", "SearchFiles", "SearchProxies",
|
||||
"Reporting", "Eventing",
|
||||
].sort();
|
||||
|
||||
export const mePreferences = makeVar(operatorSettingDefaults);
|
||||
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ import {snackActions} from "./utilities/Snackbar";
|
||||
import {TopAppBarVertical} from "./TopAppBarVertical";
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import * as Icons from '@fortawesome/free-solid-svg-icons';
|
||||
import {Jupyter} from "./pages/Jupyter/Jupyter";
|
||||
import {Hasura} from "./pages/Hasura/Hasura";
|
||||
|
||||
// add all fas icons
|
||||
const iconList = Object
|
||||
@@ -160,6 +162,12 @@ export function App(props) {
|
||||
preferences?.palette?.taskContextExtraColor?.light || operatorSettingDefaults.palette.taskContextExtraColor.light,
|
||||
emptyFolderColor: themeMode === 'dark' ? preferences?.palette?.emptyFolderColor?.dark || operatorSettingDefaults.palette.emptyFolderColor.dark :
|
||||
preferences?.palette?.emptyFolderColor?.light || operatorSettingDefaults.palette.emptyFolderColor.light,
|
||||
outputBackgroundColor: themeMode === 'dark' ? preferences?.palette?.outputBackgroundColor?.dark || operatorSettingDefaults.palette.outputBackgroundColor.dark :
|
||||
preferences?.palette?.outputBackgroundColor?.light || operatorSettingDefaults.palette.outputBackgroundColor.light,
|
||||
outputTextColor: themeMode === 'dark' ? preferences?.palette?.outputTextColor?.dark || operatorSettingDefaults.palette.outputTextColor.dark :
|
||||
preferences?.palette?.outputTextColor?.light || operatorSettingDefaults.palette.outputTextColor.light,
|
||||
borderColor: themeMode === 'dark' ? preferences?.palette?.borderColor?.dark || operatorSettingDefaults.palette.borderColor.dark :
|
||||
preferences?.palette?.borderColor?.light || operatorSettingDefaults.palette.borderColor.light,
|
||||
})
|
||||
}catch(error){
|
||||
console.log(error);
|
||||
@@ -241,6 +249,12 @@ export function App(props) {
|
||||
operatorSettingDefaults.palette.navBarBottomColor.light,
|
||||
emptyFolderColor: themeMode === 'dark' ? operatorSettingDefaults.palette.emptyFolderColor.dark :
|
||||
operatorSettingDefaults.palette.emptyFolderColor.light,
|
||||
outputBackgroundColor: themeMode === 'dark' ? operatorSettingDefaults.palette.outputBackgroundColor.dark :
|
||||
operatorSettingDefaults.palette.outputBackgroundColor.light,
|
||||
outputTextColor: themeMode === 'dark' ? operatorSettingDefaults.palette.outputTextColor.dark :
|
||||
operatorSettingDefaults.palette.outputTextColor.light,
|
||||
borderColor: themeMode === 'dark' ? operatorSettingDefaults.palette.borderColor.dark :
|
||||
operatorSettingDefaults.palette.borderColor.light,
|
||||
typography: {
|
||||
fontSize: 12,//operatorSettingDefaults?.fontSize,
|
||||
fontFamily: operatorSettingDefaults?.fontFamily
|
||||
@@ -427,6 +441,8 @@ export function App(props) {
|
||||
element={<LoggedInRoute me={me}><Tags me={me}/></LoggedInRoute>}/>
|
||||
<Route exact path='/new/eventing'
|
||||
element={<LoggedInRoute me={me}><Eventing me={me}/></LoggedInRoute>}/>
|
||||
<Route exact path='/new/jupyter' element={<LoggedInRoute me={me}><Jupyter/></LoggedInRoute>}/>
|
||||
<Route exact path='/new/hasura' element={<LoggedInRoute me={me}><Hasura/></LoggedInRoute>}/>
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,10 +21,26 @@ import {useTheme} from '@mui/material/styles';
|
||||
import WrapTextIcon from '@mui/icons-material/WrapText';
|
||||
import {IconButton} from '@mui/material';
|
||||
import {MythicStyledTooltip} from "./MythicStyledTooltip";
|
||||
import Draggable from 'react-draggable';
|
||||
import {MythicDraggableDialogTitle} from "./MythicDraggableDialogTitle";
|
||||
|
||||
export function MythicDialog(props) {
|
||||
const descriptionElementRef = React.useRef(null);
|
||||
React.useEffect(() => {
|
||||
const [draggedState, setDraggedState] = React.useState({
|
||||
style: {},
|
||||
paperStyle: {
|
||||
margin: "0",
|
||||
height: "fit-content",
|
||||
width: "stretch"
|
||||
},
|
||||
containerStyle: {
|
||||
|
||||
},
|
||||
hideBackdrop: false,
|
||||
modified: false
|
||||
});
|
||||
const nodeRef = React.useRef(null);
|
||||
const descriptionElementRef = React.useRef(null);
|
||||
React.useEffect(() => {
|
||||
if (props.open) {
|
||||
const { current: descriptionElement } = descriptionElementRef;
|
||||
if (descriptionElement !== null) {
|
||||
@@ -33,30 +49,91 @@ export function MythicDialog(props) {
|
||||
}
|
||||
}, [props.open]);
|
||||
const dialogOnClick = (e) => {
|
||||
e.stopPropagation();
|
||||
//e.preventDefault();
|
||||
//e.stopPropagation();
|
||||
if(e.target.classList.length > 0 && e.target.classList.contains("MuiDialog-container")){
|
||||
props.onClose();
|
||||
props.onClose();
|
||||
}
|
||||
}
|
||||
const dialogOnContextMenu = (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
}
|
||||
const handleOnClose = (event, reason) => {
|
||||
if(reason === "backdropClick" && draggedState.hideBackdrop){
|
||||
return;
|
||||
}
|
||||
props.onClose();
|
||||
}
|
||||
const onStart = (e) => {
|
||||
if(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
if(!draggedState.modified){
|
||||
setDraggedState({
|
||||
style: {
|
||||
height: e.target.offsetParent.offsetHeight + "px",
|
||||
width: e.target.offsetParent.offsetWidth + "px",
|
||||
margin: "auto",
|
||||
overflowY: "auto",
|
||||
},
|
||||
paperStyle: {
|
||||
//height: e.target.offsetParent.offsetHeight + "px",
|
||||
width: e.target.offsetParent.offsetWidth + "px",
|
||||
margin: 0,
|
||||
overflowY: "auto",
|
||||
},
|
||||
containerStyle: {
|
||||
height: "fit-content",
|
||||
overflowY: "auto"
|
||||
},
|
||||
hideBackdrop: true,
|
||||
modified: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
const onStop = (e) => {
|
||||
if(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
scroll="paper"
|
||||
maxWidth={props.maxWidth}
|
||||
fullWidth={props.fullWidth}
|
||||
style={props.style}
|
||||
aria-labelledby="scroll-dialog-title"
|
||||
aria-describedby="scroll-dialog-description"
|
||||
onMouseDown={dialogOnClick}
|
||||
onContextMenu={dialogOnContextMenu}
|
||||
<Draggable
|
||||
nodeRef={nodeRef}
|
||||
handle="#mythic-draggable-title"
|
||||
cancel={'[class*="MuiDialogContent-root"]'}
|
||||
onStart={onStart}
|
||||
onStop={onStop}
|
||||
>
|
||||
{props.innerDialog}
|
||||
</Dialog>
|
||||
<Dialog
|
||||
ref={nodeRef}
|
||||
open={props.open}
|
||||
onClick={dialogOnClick}
|
||||
onClose={handleOnClose}
|
||||
scroll="paper"
|
||||
maxWidth={props.maxWidth}
|
||||
fullWidth={true}
|
||||
style={{...props.style, ...draggedState.style}}
|
||||
disableEnforceFocus={true}
|
||||
disablePortal={false}
|
||||
hideBackdrop={draggedState.hideBackdrop}
|
||||
aria-labelledby="scroll-dialog-title"
|
||||
aria-describedby="scroll-dialog-description"
|
||||
sx={{
|
||||
[".MuiPaper-root"]: {
|
||||
...draggedState.paperStyle
|
||||
},
|
||||
[".MuiDialog-container"]: {
|
||||
...draggedState.containerStyle
|
||||
}
|
||||
}}
|
||||
onMouseDown={dialogOnClick}
|
||||
onContextMenu={dialogOnContextMenu}
|
||||
>
|
||||
{props.innerDialog}
|
||||
</Dialog>
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,17 +162,16 @@ export function MythicModifyStringDialog(props) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{props.title !== "" &&
|
||||
<DialogTitle id="form-dialog-title">{props.title}
|
||||
<MythicDraggableDialogTitle>{props.title}
|
||||
<MythicStyledTooltip title={wrap ? "Toggle off word wrap" : "Toggl on word wrap"}
|
||||
tooltipStyle={{float: "right"}}>
|
||||
<IconButton onClick={() => {setWrap(!wrap)}}>
|
||||
<WrapTextIcon color={wrap ? "success" : "secondary"} />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
|
||||
</DialogTitle>
|
||||
</MythicDraggableDialogTitle>
|
||||
}
|
||||
<DialogContent dividers={true} style={{height: "100%", margin: 0, padding: 0}}>
|
||||
<DialogContent dividers={true} style={{ margin: 0, padding: 0}}>
|
||||
<AceEditor
|
||||
mode="json"
|
||||
theme={theme.palette.mode === 'dark' ? 'monokai' : 'github'}
|
||||
@@ -198,7 +274,7 @@ export function MythicViewJSONAsTableDialog(props) {
|
||||
}, [props.value, props.leftColumn, props.rightColumn]);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title" style={{wordBreak: "break-all", maxWidth: "100%"}}>{props.title}</DialogTitle>
|
||||
<MythicDraggableDialogTitle style={{wordBreak: "break-all", maxWidth: "100%"}}>{props.title}</MythicDraggableDialogTitle>
|
||||
|
||||
<TableContainer className="mythicElement" style={{paddingLeft: "10px"}}>
|
||||
<Table size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "scroll"}}>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
|
||||
|
||||
export const MythicDraggableDialogTitle = ({children}) => {
|
||||
return (
|
||||
<DialogTitle id="mythic-draggable-title" style={{ cursor: 'move', width: "100%" }}>
|
||||
{children}
|
||||
</DialogTitle>
|
||||
)
|
||||
}
|
||||
@@ -71,6 +71,13 @@ export const MythicFileContext = ({agent_file_id, display_link, filename, extraS
|
||||
if(fileData.agent_file_id === '' || fileData.filename === undefined){
|
||||
return agent_file_id
|
||||
}
|
||||
const onClose = (e) => {
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
setOpenPreviewMediaDialog(false)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<MythicStyledTooltip title={"Preview Media"} tooltipStyle={extraStyles ? extraStyles : {}}>
|
||||
@@ -83,11 +90,11 @@ export const MythicFileContext = ({agent_file_id, display_link, filename, extraS
|
||||
</Link>
|
||||
{openPreviewMediaDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="xl" open={openPreviewMediaDialog}
|
||||
onClose={(e)=>{setOpenPreviewMediaDialog(false);}}
|
||||
onClose={onClose}
|
||||
innerDialog={<PreviewFileMediaDialog
|
||||
agent_file_id={fileData.agent_file_id}
|
||||
filename={fileData.filename}
|
||||
onClose={(e)=>{setOpenPreviewMediaDialog(false);}} />}
|
||||
onClose={onClose} />}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
export const MythicPageBody = ({children}) => {
|
||||
return (
|
||||
<div style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
margin: "0.5rem",
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {Typography} from "@mui/material";
|
||||
|
||||
export const MythicPageHeader = ({children, title, style={}, headerVariant="h5"}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Paper elevation={2}
|
||||
variant={"elevation"}
|
||||
style={{
|
||||
backgroundColor: theme.pageHeader.main,
|
||||
color: theme.pageHeaderText.main,
|
||||
border: `1px solid ${theme.borderColor}`,
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: "0.5rem",
|
||||
padding: "0.5rem",
|
||||
borderRadius: "5px",
|
||||
...style
|
||||
}}
|
||||
>
|
||||
<Typography variant={headerVariant} style={{textAlign: "left", display: "inline-block", marginLeft: "1rem", width: "100%"}}>
|
||||
{title}
|
||||
</Typography>
|
||||
<div style={{display: "flex", marginRight: "1rem", alignItems: "center"}}>
|
||||
{children}
|
||||
</div>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export function MythicTabPanel(props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function a11yProps(index) {
|
||||
export function a11yProps(index) {
|
||||
return {
|
||||
id: `scrollable-auto-tab-${index}`,
|
||||
'aria-controls': `scrollable-auto-tabpanel-${index}`,
|
||||
|
||||
@@ -29,6 +29,7 @@ import Typography from '@mui/material/Typography';
|
||||
import MythicStyledTableCell from "./MythicTableCell";
|
||||
import {meState} from "../../cache";
|
||||
import { useReactiveVar } from '@apollo/client';
|
||||
import {MythicDraggableDialogTitle} from "./MythicDraggableDialogTitle";
|
||||
|
||||
const createNewTagMutationTemplate = ({target_object}) => {
|
||||
// target_object should be something like "task_id"
|
||||
@@ -495,20 +496,17 @@ const onAcceptDelete = () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Edit Tags
|
||||
</DialogTitle>
|
||||
<DialogContent dividers={true}>
|
||||
{openNewDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="xl" open={openNewDialog}
|
||||
<MythicDraggableDialogTitle >Edit Tags </MythicDraggableDialogTitle>
|
||||
<DialogContent dividers={true} style={{width: "100%"}}>
|
||||
{openNewDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openNewDialog}
|
||||
onClose={()=>{setOpenNewDialog(false);}}
|
||||
innerDialog={<NewTagDialog me={props.me}
|
||||
target_object={props.target_object}
|
||||
target_object_id={props.target_object_id}
|
||||
onClose={()=>{setOpenNewDialog(false);}}
|
||||
onSubmit={handleNewTagCreate} />}
|
||||
/>) : null}
|
||||
<TableContainer className="mythicElement">
|
||||
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
/>}
|
||||
<Table size="small" style={{ width: "100%", overflow: "scroll"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell style={{width: "30%"}}>Select Existing Tag to Edit or Add New</MythicStyledTableCell>
|
||||
@@ -578,7 +576,6 @@ return (
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onClose} variant="contained" color="primary">
|
||||
@@ -647,10 +644,9 @@ export function NewTagDialog(props) {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Add New Tag</DialogTitle>
|
||||
<DialogContent dividers={true}>
|
||||
<TableContainer className="mythicElement">
|
||||
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<MythicDraggableDialogTitle >Add New Tag</MythicDraggableDialogTitle>
|
||||
<DialogContent dividers={true} style={{width: "100%"}}>
|
||||
<Table size="small" style={{ overflow: "scroll", width: "100%"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell style={{width: "20%"}}>
|
||||
@@ -717,7 +713,6 @@ export function NewTagDialog(props) {
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onClose} variant="contained" color="primary">
|
||||
|
||||
@@ -78,6 +78,8 @@ import {
|
||||
} from "@hello-pangea/dnd";
|
||||
import {reorder} from "./MythicComponents/MythicDraggableList";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
|
||||
const PREFIX = 'TopAppBarVertical';
|
||||
|
||||
@@ -436,7 +438,7 @@ const Eventing = () => {
|
||||
}
|
||||
const JupyterNotebook = () => {
|
||||
return (
|
||||
<StyledListItem className={classes.listSubHeader} target="_blank" component={Link} to='/jupyter' key={"jupyter"} >
|
||||
<StyledListItem className={classes.listSubHeader} target="_blank" component={Link} to='/jupyter' key={"jupyter"} >
|
||||
<StyledListItemIcon>
|
||||
<MythicStyledTooltip title={"Jupyter Notebooks"} tooltipStyle={{display: "inline-flex"}}>
|
||||
<img src={JupyterLogo} height={"25px"} width={"25px"} />
|
||||
@@ -448,7 +450,7 @@ const JupyterNotebook = () => {
|
||||
}
|
||||
const GraphQL = () => {
|
||||
return (
|
||||
<StyledListItem className={classes.listSubHeader} target="_blank" component={Link} to='/console' key={"console"} >
|
||||
<StyledListItem className={classes.listSubHeader} target="_blank" component={Link} to='/console' key={"console"} >
|
||||
<StyledListItemIcon>
|
||||
<MythicStyledTooltip title={"GraphQL Console"} tooltipStyle={{display: "inline-flex"}}>
|
||||
<img src={GraphQLLogo} height={"25px"} width={"25px"} className="mythicElement"/>
|
||||
@@ -898,16 +900,29 @@ function TopBarRightShortcutsVertical({me, menuOpen, serverName}){
|
||||
{
|
||||
name: (
|
||||
<>
|
||||
<Typography paragraph={true} variant="caption" style={{marginBottom: "0"}}>Signed in as:</Typography>
|
||||
<Typography paragraph={true} variant="body1" style={{marginBottom: "0", fontWeight: 600}}> {me?.user?.username || "" } </Typography>
|
||||
</>
|
||||
<Typography variant={"caption"} style={{marginBottom: "0"}}>Signed in as:</Typography>
|
||||
<Typography variant={"body1"} style={{marginBottom: "0", fontWeight: 600}}>{me?.user?.username}</Typography>
|
||||
</>),
|
||||
disabled: true,
|
||||
click: handleSettingsClose
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div style={{display: "flex", alignItems: "center"}}>
|
||||
<TuneIcon style={{marginRight: "0.5rem"}}/>
|
||||
<Typography variant="body1" style={{marginBottom: "0", }}> Settings </Typography>
|
||||
</div>
|
||||
),
|
||||
to: "/new/settings",
|
||||
component: Link,
|
||||
click: handleSettingsClose
|
||||
},
|
||||
{
|
||||
name: "Logout",
|
||||
name: (
|
||||
<div style={{display: "flex", alignItems: "center"}}>
|
||||
<LogoutIcon style={{marginRight: "0.5rem"}} /> Logout
|
||||
</div>
|
||||
),
|
||||
click: handleLogout,
|
||||
to: "",
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {BrowserScriptsTable} from './BrowserScriptsTable';
|
||||
import {snackActions} from '../../utilities/Snackbar';
|
||||
import { Backdrop } from '@mui/material';
|
||||
import {CircularProgress} from '@mui/material';
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
|
||||
|
||||
const SUB_BrowserScripts = gql`
|
||||
@@ -135,7 +136,7 @@ export function BrowserScripts({me}){
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MythicPageBody>
|
||||
<Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute"}} invisible={false}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Backdrop>
|
||||
@@ -145,6 +146,6 @@ export function BrowserScripts({me}){
|
||||
onSubmitEdit={onSubmitEdit} onRevert={onRevert} onSubmitNew={onSubmitCreateNewBrowserScript}
|
||||
/>
|
||||
|
||||
</React.Fragment>
|
||||
</MythicPageBody>
|
||||
);
|
||||
}
|
||||
@@ -13,30 +13,27 @@ import AddCircleIcon from '@mui/icons-material/AddCircle';
|
||||
import Button from '@mui/material/Button';
|
||||
import { MythicDialog } from '../../MythicComponents/MythicDialog';
|
||||
import {EditScriptDialog} from './EditScriptDialog';
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
|
||||
|
||||
export function BrowserScriptsTable(props){
|
||||
const theme = useTheme();
|
||||
const [openNewScriptDialog, setOpenNewScriptDialog] = React.useState(false);
|
||||
return (
|
||||
<div style={{display: "flex", flexDirection: "column", height: "100%"}}>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main}} variant={"elevation"}>
|
||||
<Typography variant="h5" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
Browser Scripts
|
||||
</Typography>
|
||||
<Button size="small" onClick={() => setOpenNewScriptDialog(true)} style={{float: "right", marginRight: "30px", color: "white"}}
|
||||
<>
|
||||
<MythicPageHeader title={"Browser Scripts"}>
|
||||
<Button size="small" onClick={() => setOpenNewScriptDialog(true)} style={{whiteSpace: "nowrap", color: "white"}}
|
||||
startIcon={<AddCircleIcon color={"success"} style={{backgroundColor: "white", borderRadius: "10px"}}/>} >
|
||||
New Script
|
||||
</Button>
|
||||
{openNewScriptDialog ? (
|
||||
{openNewScriptDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="xl" open={openNewScriptDialog}
|
||||
onClose={()=>{setOpenNewScriptDialog(false);}}
|
||||
innerDialog={
|
||||
<EditScriptDialog me={props.me} onClose={()=>{setOpenNewScriptDialog(false);}} title="Create New Browser Script" new={true} onSubmitEdit={props.onSubmitNew} />
|
||||
} />
|
||||
) : null
|
||||
} />
|
||||
}
|
||||
</Paper>
|
||||
</MythicPageHeader>
|
||||
<TableContainer className="mythicElement" style={{height: "100%", flexGrow: 1}}>
|
||||
<Table stickyHeader={true} size="small" style={{"tableLayout": "fixed", "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableHead>
|
||||
@@ -61,8 +58,8 @@ export function BrowserScriptsTable(props){
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
</TableContainer>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import CardHeader from '@mui/material/CardHeader';
|
||||
import {gql, useQuery} from '@apollo/client';
|
||||
import { CardContent } from '@mui/material';
|
||||
import {classes, StyledButton, StyledDivider} from '../../MythicComponents/MythicTransferList';
|
||||
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
|
||||
|
||||
const getCommandsQuery = gql`
|
||||
query GetCallbackDetails($callback_id: Int!) {
|
||||
@@ -184,8 +185,8 @@ export function AddRemoveCallbackCommandsDialog(props) {
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Add or Remove Commands for Callback {props.display_id} </DialogTitle>
|
||||
<DialogContent dividers={true} style={{height: "100%", display: "flex", flexDirection: "column", position: "relative", maxHeight: "100%"}}>
|
||||
<DialogTitle>Add or Remove Commands for Callback {props.display_id} </DialogTitle>
|
||||
<DialogContent dividers={true} style={{height: "100%", display: "flex", flexDirection: "column", position: "relative", overflowY: "auto"}}>
|
||||
This will add or remove commands associated with this callback from Mythic's perspective.
|
||||
This does NOT add or remove commands within the payload itself that's beaconing out to Mythic.
|
||||
<div style={{display: "flex", flexDirection: "row", overflowY: "auto", flexGrow: 1, minHeight: 0}}>
|
||||
|
||||
@@ -106,6 +106,7 @@ export const taskingDataFragment = gql`
|
||||
domain
|
||||
ip
|
||||
mythictree_groups
|
||||
pid
|
||||
}
|
||||
callback_id
|
||||
commentOperator{
|
||||
@@ -175,6 +176,7 @@ export const taskingDataFragment = gql`
|
||||
domain
|
||||
ip
|
||||
mythictree_groups
|
||||
pid
|
||||
}
|
||||
completed
|
||||
subtask_group_name
|
||||
|
||||
@@ -316,13 +316,13 @@ function CallbacksTablePreMemo(props){
|
||||
const ele = document.getElementById("download_config");
|
||||
if(ele !== null){
|
||||
ele.href = URL.createObjectURL(dataBlob);
|
||||
ele.download = data.exportCallbackConfig.agent_callback_id + ".json";
|
||||
ele.download = "exported_callback.json";
|
||||
ele.click();
|
||||
}else{
|
||||
const element = document.createElement("a");
|
||||
element.id = "download_config";
|
||||
element.href = URL.createObjectURL(dataBlob);
|
||||
element.download = data.exportCallbackConfig.agent_callback_id + ".json";
|
||||
element.download = "exported_callback.json";
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
}
|
||||
@@ -711,16 +711,16 @@ function CallbacksTablePreMemo(props){
|
||||
return;
|
||||
}
|
||||
if (!column.key) {
|
||||
setSortData({"sortKey": null, "sortType":null, "sortDirection": "ASC"});
|
||||
setSortData({sortKey: null, sortType:null, sortDirection: "ASC"});
|
||||
}
|
||||
if (sortData.sortKey === column.key) {
|
||||
if (sortData.sortDirection === 'ASC') {
|
||||
setSortData({...sortData, "sortDirection": "DESC"});
|
||||
setSortData({...sortData, sortDirection: "DESC"});
|
||||
} else {
|
||||
setSortData({"sortKey": null, "sortType":null, "sortDirection": "ASC"});
|
||||
setSortData({sortKey: null, sortType:null, sortDirection: "ASC"});
|
||||
}
|
||||
} else {
|
||||
setSortData({"sortKey": column.key, "sortType":column.type, "sortDirection": "ASC"});
|
||||
setSortData({sortKey: column.key, sortType:column.type, sortDirection: "ASC"});
|
||||
}
|
||||
};
|
||||
const onRowDoubleClick = React.useCallback( (event, rowID, rowDataStatic) => {
|
||||
|
||||
+13
-3
@@ -17,7 +17,11 @@ import MythicTextField from "../../MythicComponents/MythicTextField";
|
||||
export function CallbacksTableEditTriggerOnCheckinDialog(props) {
|
||||
const [comment, setComment] = React.useState(0);
|
||||
const onChange = (name, value, error) => {
|
||||
setComment(parseInt(value));
|
||||
if(isNaN(parseInt(value))){
|
||||
setComment(0);
|
||||
} else {
|
||||
setComment(parseInt(value));
|
||||
}
|
||||
}
|
||||
useEffect( () => {
|
||||
setComment(props.trigger_on_checkin_after_time);
|
||||
@@ -29,8 +33,14 @@ export function CallbacksTableEditTriggerOnCheckinDialog(props) {
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">{"Adjust this callback's trigger threshold"}</DialogTitle>
|
||||
<DialogContent dividers={true} style={{height: "100%"}}>
|
||||
<Typography>
|
||||
{"This adjusts how long, in minutes, this callback must not checkin before finally checking in to trigger an eventing workflow (trigger is callback_checkin). A zero value means never trigger."}
|
||||
<Typography >
|
||||
This adjusts how long, in minutes, this callback must <b>not</b> checkin before finally checking in to trigger an <b>eventing workflow</b> (trigger is callback_checkin).
|
||||
</Typography>
|
||||
<Typography >
|
||||
A zero value means never trigger.
|
||||
</Typography>
|
||||
<Typography >
|
||||
If no eventing workflow for <b>callback_checkin</b> that matches the right payload_types and supported_os restrictions, then nothing will happen.
|
||||
</Typography>
|
||||
<MythicTextField autoFocus={true} onChange={onChange} type={"number"} value={comment} onEnter={onSubmit} name={"trigger threshold in minutes"} showLabel={false} />
|
||||
</DialogContent>
|
||||
|
||||
@@ -47,6 +47,7 @@ const fileDataFragment = gql`
|
||||
color
|
||||
id
|
||||
}
|
||||
id
|
||||
}
|
||||
host
|
||||
id
|
||||
@@ -320,16 +321,19 @@ export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me, setNe
|
||||
} else {
|
||||
// we need to merge data in because we already have some info
|
||||
let existingData = treeRootDataRef.current[currentGroups[j]][data.data.mythictree_stream[i]["host"]][data.data.mythictree_stream[i]["full_path_text"]];
|
||||
if(existingData.task_id < data.data.mythictree_stream[i].task_id){
|
||||
if(existingData.task_id <= data.data.mythictree_stream[i].task_id){
|
||||
existingData.deleted = data.data.mythictree_stream[i].deleted;
|
||||
}
|
||||
if( (existingData.success === null || !existingData.success) && data.data.mythictree_stream[i].success !== null){
|
||||
existingData.success = data.data.mythictree_stream[i].success;
|
||||
existingData.task_id = data.data.mythictree_stream[i].task_id;
|
||||
}
|
||||
existingData.comment += data.data.mythictree_stream[i].comment;
|
||||
existingData.comment = data.data.mythictree_stream[i].comment;
|
||||
existingData.tags = [...existingData.tags, ...data.data.mythictree_stream[i].tags];
|
||||
existingData.has_children = data.data.mythictree_stream[i].has_children || existingData.has_children;
|
||||
existingData.metadata.size = data.data.mythictree_stream[i].metadata.size;
|
||||
existingData.metadata.access_time = data.data.mythictree_stream[i].metadata.access_time;
|
||||
existingData.metadata.modify_time = data.data.mythictree_stream[i].metadata.modify_time;
|
||||
if(data.data.mythictree_stream[i].metadata.permissions !== undefined && data.data.mythictree_stream[i].metadata.permissions !== null){
|
||||
if(Array.isArray(existingData.metadata.permissions) && Array.isArray(data.data.mythictree_stream[i].metadata.permissions)){
|
||||
existingData.metadata.permissions = [...existingData.metadata.permissions, ...data.data.mythictree_stream[i].metadata.permissions];
|
||||
@@ -458,7 +462,7 @@ export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me, setNe
|
||||
} else {
|
||||
// we need to merge data in because we already have some info
|
||||
let existingData = treeRootDataRef.current[currentGroups[j]][mythictree[i]["host"]][mythictree[i]["full_path_text"]];
|
||||
if(existingData.task_id < mythictree[i].task_id){
|
||||
if(existingData.task_id <= mythictree[i].task_id){
|
||||
existingData.deleted = mythictree[i].deleted;
|
||||
}
|
||||
if( (existingData.success === null || !existingData.success) && mythictree[i].success !== null){
|
||||
@@ -468,6 +472,9 @@ export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me, setNe
|
||||
existingData.comment += mythictree[i].comment;
|
||||
existingData.tags = [...existingData.tags, ...mythictree[i].tags];
|
||||
existingData.has_children = mythictree[i].has_children || existingData.has_children;
|
||||
existingData.metadata.size = mythictree[i].metadata.size;
|
||||
existingData.metadata.access_time = mythictree[i].metadata.access_time;
|
||||
existingData.metadata.modify_time = mythictree[i].metadata.modify_time;
|
||||
//console.log("updating permissions", "existing", existingData.metadata.permissions, "new", mythictree[i].metadata)
|
||||
if(mythictree[i].metadata.permissions !== undefined && mythictree[i].metadata.permissions !== null){
|
||||
if(Array.isArray(existingData.metadata.permissions) && Array.isArray(mythictree[i].metadata.permissions)){
|
||||
|
||||
@@ -26,8 +26,6 @@ import FileCopyOutlinedIcon from '@mui/icons-material/FileCopyOutlined';
|
||||
import { copyStringToClipboard } from '../../utilities/Clipboard';
|
||||
import MythicResizableGrid from '../../MythicComponents/MythicResizableGrid';
|
||||
import { MythicStyledTooltip } from '../../MythicComponents/MythicStyledTooltip';
|
||||
import {TableFilterDialog} from './TableFilterDialog';
|
||||
import {MythicTransferListDialog} from '../../MythicComponents/MythicTransferList';
|
||||
import {TagsDisplay, ViewEditTags} from '../../MythicComponents/MythicTag';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import { toLocalTime } from '../../utilities/Time';
|
||||
@@ -37,7 +35,12 @@ import {PreviewFileMediaDialog} from "../Search/PreviewFileMedia";
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import {Dropdown, DropdownMenuItem, DropdownNestedMenuItem} from "../../MythicComponents/MythicNestedMenus";
|
||||
import {RenderSingleTask} from "../SingleTaskView/SingleTaskView";
|
||||
import {GetComputedFontSize} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {
|
||||
GetComputedFontSize,
|
||||
GetMythicSetting,
|
||||
useSetMythicSetting
|
||||
} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {CallbacksTableColumnsReorderDialog} from "./CallbacksTableColumnsReorderDialog";
|
||||
|
||||
const getFileDownloadHistory = gql`
|
||||
query getFileDownloadHistory($full_path_text: String!, $host: String!, $group: [String!]) {
|
||||
@@ -76,28 +79,33 @@ const updateFileComment = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const defaultVisibleColumns = ["Info", "Name", "Size", "Last Modify"];
|
||||
const columnDefaults = [
|
||||
{ name: 'Info', width: 65, disableDoubleClick: true, disableSort: true, disableFilterMenu: true, key: "info", visible: true },
|
||||
{ name: 'Name', type: 'string', key: 'name_text', fillWidth: true, visible: true },
|
||||
{ name: "Size", type: "size", key: "size", inMetadata: true, width: 100, visible: true},
|
||||
{ name: "Last Modify", type: "date", key: "modify_time", inMetadata: true, width: 250, visible: true},
|
||||
{ name: 'Tags', type: 'tags', disableSort: true, disableFilterMenu: true, width: 220, key: "tags", visible: false },
|
||||
{ name: 'Comment', type: 'string', key: 'comment', width: 200, visible: false },
|
||||
];
|
||||
export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
const [updateSetting] = useSetMythicSetting();
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [allData, setAllData] = React.useState([]);
|
||||
const [openContextMenu, setOpenContextMenu] = React.useState(false);
|
||||
const [filterOptions, setFilterOptions] = React.useState({});
|
||||
const [selectedColumn, setSelectedColumn] = React.useState({});
|
||||
const [sortData, setSortData] = React.useState({"sortKey": null, "sortDirection": null, "sortType": null})
|
||||
const [columnVisibility, setColumnVisibility] = React.useState({
|
||||
"visible": ["Info", "Name", "Size", "Last Modify"],
|
||||
"visible": defaultVisibleColumns,
|
||||
"hidden": ["Tags", "Comment"]
|
||||
});
|
||||
const [openAdjustColumnsDialog, setOpenAdjustColumnsDialog] = React.useState(false);
|
||||
const [openReorderDialog, setOpenReorderDialog] = React.useState(false);
|
||||
const [columnOrder, setColumnOrder] = React.useState(columnDefaults);
|
||||
const [selectedRows, setSelectedRows] = React.useState([]);
|
||||
const columns = React.useMemo(
|
||||
() =>
|
||||
[
|
||||
{ name: 'Info', width: 65, disableDoubleClick: true, disableSort: true, disableFilterMenu: true },
|
||||
{ name: 'Name', type: 'string', key: 'name_text', fillWidth: true },
|
||||
{ name: "Size", type: "size", key: "size", inMetadata: true, width: 100},
|
||||
{ name: "Last Modify", type: "date", key: "modify_time", inMetadata: true, width: 250},
|
||||
{ name: 'Tags', type: 'tags', disableSort: true, disableFilterMenu: true, width: 220 },
|
||||
{ name: 'Comment', type: 'string', key: 'comment', width: 200 },
|
||||
].reduce( (prev, cur) => {
|
||||
columnOrder.reduce( (prev, cur) => {
|
||||
if(columnVisibility.visible.includes(cur.name)){
|
||||
if(filterOptions[cur.key] && String(filterOptions[cur.key]).length > 0){
|
||||
return [...prev, {...cur, filtered: true}];
|
||||
@@ -108,7 +116,7 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
return [...prev];
|
||||
}
|
||||
}, [])
|
||||
, [filterOptions, columnVisibility]
|
||||
, [filterOptions, columnVisibility, columnOrder]
|
||||
);
|
||||
const sortedData = React.useMemo(() => {
|
||||
if (sortData.sortKey === null || sortData.sortType === null) {
|
||||
@@ -189,7 +197,14 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
return tempData;
|
||||
}, [allData, sortData]);
|
||||
const onSubmitFilterOptions = (value) => {
|
||||
setFilterOptions({...filterOptions, [selectedColumn.key]: value });
|
||||
|
||||
try{
|
||||
let newValue = value ? value : "";
|
||||
setFilterOptions({...filterOptions, [selectedColumn.key]: newValue });
|
||||
updateSetting({setting_name: `file_browser_filter_options`, value: {...filterOptions,[selectedColumn.key]: newValue }});
|
||||
}catch(error){
|
||||
console.log("failed to save filter options", error);
|
||||
}
|
||||
}
|
||||
const filterRow = (row) => {
|
||||
if(props.treeRootData[props.selectedFolderData.group] === undefined ||
|
||||
@@ -324,18 +339,84 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Show/Hide Columns",type: "item", icon: null,
|
||||
name: "Reorder Columns and Adjust Visibility",type: "item", icon: null,
|
||||
click: ({event, columnIndex}) => {
|
||||
if(event){
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
setOpenAdjustColumnsDialog(true);
|
||||
setOpenReorderDialog(true);
|
||||
}
|
||||
}
|
||||
];
|
||||
const onSubmitAdjustColumns = ({left, right}) => {
|
||||
setColumnVisibility({visible: right, hidden: left});
|
||||
React.useEffect( () => {
|
||||
try {
|
||||
const storageItem = GetMythicSetting({setting_name: `file_browser_table_columns`, default_value: defaultVisibleColumns});
|
||||
if(storageItem !== null){
|
||||
let allColumns = [...columnVisibility["visible"].map(c => c), ...columnVisibility["hidden"].map(c => c)];
|
||||
let newHidden = [];
|
||||
allColumns.forEach((v,i,a) => {
|
||||
if(!storageItem.includes(v)){
|
||||
newHidden.push(v);
|
||||
}
|
||||
});
|
||||
if(storageItem.length !== 0){
|
||||
setColumnVisibility({visible: storageItem, hidden: newHidden});
|
||||
}
|
||||
}
|
||||
}catch(error){
|
||||
console.log("Failed to load custom browser_table_columns", error);
|
||||
}
|
||||
try {
|
||||
const storageItemOptions = GetMythicSetting({setting_name: `file_browser_filter_options`, default_value: {}});
|
||||
if(storageItemOptions !== null){
|
||||
setFilterOptions(storageItemOptions);
|
||||
}
|
||||
}catch(error){
|
||||
console.log("Failed to load custom browser_table_filter_options", error);
|
||||
}
|
||||
try {
|
||||
const storageColumnOrder = GetMythicSetting({setting_name: `file_browser_column_order`, default_value: columns.map(c => c.name)});
|
||||
if(storageColumnOrder !== null){
|
||||
let newOrder = [];
|
||||
for(let i = 0; i < storageColumnOrder.length; i++){
|
||||
for(let j = 0; j < columnOrder.length; j++){
|
||||
if(columnOrder[j].name === storageColumnOrder[i]){
|
||||
newOrder.push(columnOrder[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
setColumnOrder(newOrder);
|
||||
}
|
||||
}catch(error){
|
||||
console.log("Failed to load file_browser_table_filter_options", error);
|
||||
}
|
||||
setLoading(false);
|
||||
}, []);
|
||||
const onSubmitColumnReorder = (newOrder) => {
|
||||
let newVisible = [];
|
||||
let newHidden = [];
|
||||
for(let i = 0; i < newOrder.length; i++){
|
||||
if(newOrder[i].visible){
|
||||
newVisible.push(newOrder[i].name);
|
||||
} else {
|
||||
newHidden.push(newOrder[i].name);
|
||||
}
|
||||
}
|
||||
if(newVisible.length === 0){
|
||||
snackActions.error("Can't update to show no fields");
|
||||
return;
|
||||
}
|
||||
console.log("newOrder", newOrder)
|
||||
updateSetting({setting_name: `file_browser_column_order`, value: newOrder.map(c => c.name)});
|
||||
setColumnOrder(newOrder);
|
||||
setColumnVisibility({visible: newVisible, hidden: newHidden});
|
||||
updateSetting({setting_name: `file_browser_table_columns`, value: newVisible});
|
||||
setOpenReorderDialog(false);
|
||||
}
|
||||
const onResetColumnReorder = () => {
|
||||
onSubmitColumnReorder(columnDefaults);
|
||||
}
|
||||
const sortColumn = columns.findIndex((column) => column.key === sortData.sortKey);
|
||||
const onRowClick = ({event, rowDataStatic}) => {
|
||||
@@ -415,10 +496,16 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
if(listCommand !== undefined){
|
||||
listDisplay = `List (${listCommand.command.cmd})`;
|
||||
}
|
||||
let removeCommand = await props.getLoadedCommandForUIFeature(callback_id, props.baseUIFeature + ":remove");
|
||||
let removeCommand = await props.getLoadedCommandForUIFeature(callback_id, element.can_have_children ? props.baseUIFeature + ":remove_folder" : props.baseUIFeature + ":remove");
|
||||
let removeDisplay = "Remove (Unsupported)";
|
||||
if( element.can_have_children) {
|
||||
removeDisplay = "Remove Folder (Unsupported)"
|
||||
}
|
||||
if(removeCommand !== undefined){
|
||||
removeDisplay = `Remove (${removeCommand.command.cmd})`;
|
||||
if(element.can_have_children){
|
||||
removeDisplay = `Remove Folder (${removeCommand.command.cmd})`;
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
@@ -462,7 +549,7 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
full_path: element.full_path_text,
|
||||
host: element.host,
|
||||
filename: element.name_text,
|
||||
uifeature: props.baseUIFeature + ':remove',
|
||||
uifeature: element.can_have_children ? props.baseUIFeature + ':remove_folder' : props.baseUIFeature + ':remove',
|
||||
getConfirmation: true,
|
||||
callback_id, callback_display_id
|
||||
});
|
||||
@@ -550,6 +637,21 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
}
|
||||
return options;
|
||||
}
|
||||
if(loading){
|
||||
return (
|
||||
<div style={{width: '100%', height: '100%', position: "relative",}}>
|
||||
<div style={{overflowY: "hidden", flexGrow: 1}}>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
left: "35%",
|
||||
top: "40%"
|
||||
}}>
|
||||
{"Loading Saved Browser Customizations..."}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div style={{width: '100%', height: '100%', overflow: "hidden", position: "relative"}}>
|
||||
{displayFormat === "normal" &&
|
||||
@@ -644,15 +746,20 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
}
|
||||
/>
|
||||
}
|
||||
{openAdjustColumnsDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openAdjustColumnsDialog}
|
||||
onClose={()=>{setOpenAdjustColumnsDialog(false);}}
|
||||
innerDialog={
|
||||
<MythicTransferListDialog onClose={()=>{setOpenAdjustColumnsDialog(false);}}
|
||||
onSubmit={onSubmitAdjustColumns} right={columnVisibility.visible} rightTitle="Show these columns"
|
||||
leftTitle={"Hidden Columns"} left={columnVisibility.hidden} dialogTitle={"Edit which columns are shown"}/>}
|
||||
{openReorderDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={openReorderDialog}
|
||||
onClose={()=>{setOpenReorderDialog(false);}}
|
||||
innerDialog={
|
||||
<CallbacksTableColumnsReorderDialog
|
||||
onClose={()=>{setOpenReorderDialog(false);}}
|
||||
visible={columnVisibility.visible}
|
||||
hidden={columnVisibility.hidden}
|
||||
onReset={onResetColumnReorder}
|
||||
onSubmit={onSubmitColumnReorder}
|
||||
initialItems={columnOrder}
|
||||
/>}
|
||||
/>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import {hideCallbacksMutation} from './CallbackMutations';
|
||||
import {snackActions} from "../../utilities/Snackbar";
|
||||
import {CallbacksTableLastCheckinCell, CallbacksTablePayloadTypeCell, CallbacksTableIPCell} from "./CallbacksTableRow";
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
|
||||
|
||||
const callbacksAndFeaturesQuery = gql`
|
||||
@@ -175,6 +176,9 @@ export function CallbacksTabsHideMultipleDialog({onClose}) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Hide Multiple Callbacks at Once</DialogTitle>
|
||||
<DialogContentText style={{textAlign: "center"}}>
|
||||
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
|
||||
</DialogContentText>
|
||||
<CustomSelectTable initialData={initialData}
|
||||
selectedData={selectedData}
|
||||
sortModel={{ field: 'last_checkin', sort: 'asc' }}
|
||||
@@ -217,6 +221,9 @@ export function CallbacksTabsSelectMultipleDialog({onClose, onSubmit}) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Select Multiple Callbacks</DialogTitle>
|
||||
<DialogContentText style={{textAlign: "center"}}>
|
||||
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
|
||||
</DialogContentText>
|
||||
<CustomSelectTable initialData={initialData}
|
||||
selectedData={selectedData}
|
||||
sortModel={{ field: 'display_id', sort: 'desc' }}
|
||||
|
||||
@@ -5,6 +5,7 @@ import DialogTitle from '@mui/material/DialogTitle';
|
||||
import {useQuery, gql } from '@apollo/client';
|
||||
import {CallbacksTableLastCheckinCell, CallbacksTablePayloadTypeCell, CallbacksTableIPCell} from "./CallbacksTableRow";
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
|
||||
|
||||
const callbacksAndFeaturesQuery = gql`
|
||||
@@ -140,6 +141,9 @@ export function CallbacksTabsOpenMultipleDialog({onClose, tabType, onOpenTabs})
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Select Callback to open {tabType} tab</DialogTitle>
|
||||
<DialogContentText style={{textAlign: "center"}}>
|
||||
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
|
||||
</DialogContentText>
|
||||
<CustomSelectTable initialData={initialData}
|
||||
selectedData={selectedData}
|
||||
onRowClick={onRowClick}
|
||||
|
||||
@@ -6,8 +6,6 @@ import {useTheme} from '@mui/material/styles';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import {CallbacksTabsProcessBrowserTable} from './CallbacksTabsProcessBrowserTable';
|
||||
import {MythicModifyStringDialog} from '../../MythicComponents/MythicDialog';
|
||||
import {TaskFromUIButton} from './TaskFromUIButton';
|
||||
@@ -271,11 +269,14 @@ export const CallbacksTabsProcessBrowserPanel = ({index, value, tabInfo, me, set
|
||||
treeRootDataRef.current[currentGroups[j]][data.data.mythictree_stream[i]["host"]][data.data.mythictree_stream[i]["full_path_text"]].callbacks = [data.data.mythictree_stream[i].callback]
|
||||
} else {
|
||||
// we need to merge data in because we already have some info
|
||||
if(data.data.mythictree_stream[i].deleted){
|
||||
delete treeRootDataRef.current[currentGroups[j]][data.data.mythictree_stream[i]["host"]][data.data.mythictree_stream[i]["full_path_text"]];
|
||||
continue;
|
||||
}
|
||||
let existingData = treeRootDataRef.current[currentGroups[j]][data.data.mythictree_stream[i]["host"]][data.data.mythictree_stream[i]["full_path_text"]];
|
||||
if(existingData.task_id <= data.data.mythictree_stream[i].task_id){
|
||||
if(data.data.mythictree_stream[i].deleted){
|
||||
delete treeRootDataRef.current[currentGroups[j]][data.data.mythictree_stream[i]["host"]][data.data.mythictree_stream[i]["full_path_text"]];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
existingData.has_children = data.data.mythictree_stream[i].has_children || existingData.has_children;
|
||||
existingData.callbacks.push(data.data.mythictree_stream[i].callback)
|
||||
existingData.comment += data.data.mythictree_stream[i].comment;
|
||||
existingData.tags = [...existingData.tags, ...data.data.mythictree_stream[i].tags];
|
||||
|
||||
@@ -9,8 +9,6 @@ import ListIcon from '@mui/icons-material/List';
|
||||
import { snackActions } from '../../utilities/Snackbar';
|
||||
import 'react-virtualized/styles.css';
|
||||
import MythicResizableGrid from '../../MythicComponents/MythicResizableGrid';
|
||||
import {TableFilterDialog} from './TableFilterDialog';
|
||||
import {MythicTransferListDialog} from '../../MythicComponents/MythicTransferList';
|
||||
import {TagsDisplay, ViewEditTags} from '../../MythicComponents/MythicTag';
|
||||
import TerminalIcon from '@mui/icons-material/Terminal';
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
@@ -22,7 +20,12 @@ import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import {Dropdown, DropdownMenuItem, DropdownNestedMenuItem} from "../../MythicComponents/MythicNestedMenus";
|
||||
import {faSkullCrossbones, faSyringe, faKey,} from '@fortawesome/free-solid-svg-icons';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {GetComputedFontSize} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {
|
||||
GetComputedFontSize,
|
||||
GetMythicSetting,
|
||||
useSetMythicSetting
|
||||
} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {CallbacksTableColumnsReorderDialog} from "./CallbacksTableColumnsReorderDialog";
|
||||
|
||||
const getPermissionsDataQuery = gql`
|
||||
query getPermissionsQuery($mythictree_id: Int!) {
|
||||
@@ -40,24 +43,40 @@ const updateFileComment = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const columnDefaults = [
|
||||
{ name: 'Info', width: 50, disableAutosize: true, disableSort: true, disableFilterMenu: true, key: "info", visible: true },
|
||||
{ name: 'PID', type: 'number', key: 'process_id', inMetadata: true, width: 100, visible: true},
|
||||
{ name: 'PPID', type: 'number', key: 'parent_process_id', inMetadata: true, width: 100, visible: true},
|
||||
{ name: 'Name', type: 'string', disableSort: false, key: 'name_text', fillWidth: true, visible: true },
|
||||
{ name: "Arch", type: 'string', key: 'architecture', inMetadata: true, width: 70, visible: true},
|
||||
{ name: "Integrity", type: "number", key: "integrity_level", inMetadata: true, width: 100, visible: false },
|
||||
{ name: 'Session', type: 'number', key: 'session_id', inMetadata: true, width: 100, visible: true},
|
||||
{ name: "User", type: 'string', key: 'user', inMetadata: true, fillWidth: true, visible: true},
|
||||
{ name: 'Tags', type: 'tags', disableSort: true, disableFilterMenu: true, width: 220, key: "tags", visible: false },
|
||||
{ name: 'Comment', type: 'string', key: 'comment', disableSort: false, width: 200, visible: false },
|
||||
{ name: "CMD", type: "string", key: 'command_line', inMetadata: true, fillWidth: true, visible: true},
|
||||
];
|
||||
const defaultVisibleColumns = ["Info","PID", "PPID", "Name", "Arch", "Session", "User", "CMD"];
|
||||
export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, me, onRowDoubleClick,
|
||||
onTaskRowAction, host, group, showDeletedFiles, tabInfo,
|
||||
expandOrCollapseAll, getLoadedCommandForUIFeature}) => {
|
||||
//const [allData, setAllData] = React.useState([]);
|
||||
//console.log("treeAdjMatrix updated in table", treeAdjMatrix)
|
||||
const [updateSetting] = useSetMythicSetting();
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [sortData, setSortData] = React.useState({"sortKey": null, "sortDirection": null, "sortType": null});
|
||||
const [openNodes, setOpenNodes] = React.useState({});
|
||||
const [openContextMenu, setOpenContextMenu] = React.useState(false);
|
||||
const [filterOptions, setFilterOptions] = React.useState({});
|
||||
const selectedColumn = React.useRef({});
|
||||
const [columnVisibility, setColumnVisibility] = React.useState({
|
||||
"visible": ["Info","PID", "PPID", "Name", "Arch", "Session", "User", "CMD"],
|
||||
"visible": defaultVisibleColumns,
|
||||
"hidden": [ "Comment", "Tags" ]
|
||||
})
|
||||
const [singleTreeData, setSingleTreeData] = React.useState({});
|
||||
const [viewSingleTreeData, setViewSingleTreeData] = React.useState(false);
|
||||
const [openAdjustColumnsDialog, setOpenAdjustColumnsDialog] = React.useState(false);
|
||||
const [openReorderDialog, setOpenReorderDialog] = React.useState(false);
|
||||
const [columnOrder, setColumnOrder] = React.useState(columnDefaults);
|
||||
const [updatedTreeAdjMatrix, setUpdatedTreeAdjMatrix] = React.useState(treeAdjMatrix);
|
||||
const openAllNodes = (state) => {
|
||||
let onodes = {};
|
||||
@@ -192,21 +211,10 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
// }
|
||||
|
||||
};
|
||||
const columnDefaults = [
|
||||
{ name: 'Info', width: 50, disableAutosize: true, disableSort: true, disableFilterMenu: true },
|
||||
{ name: 'PID', type: 'number', key: 'process_id', inMetadata: true, width: 100},
|
||||
{ name: 'PPID', type: 'number', key: 'parent_process_id', inMetadata: true, width: 100},
|
||||
{ name: 'Name', type: 'string', disableSort: false, key: 'name_text', fillWidth: true },
|
||||
{ name: "Arch", type: 'string', key: 'architecture', inMetadata: true, width: 70},
|
||||
{ name: 'Session', type: 'number', key: 'session_id', inMetadata: true, width: 100},
|
||||
{ name: "User", type: 'string', key: 'user', inMetadata: true, fillWidth: true},
|
||||
{ name: 'Tags', type: 'tags', disableSort: true, disableFilterMenu: true, width: 220 },
|
||||
{ name: 'Comment', type: 'string', key: 'comment', disableSort: false, width: 200 },
|
||||
{ name: "CMD", type: "string", key: 'command_line', inMetadata: true, fillWidth: true},
|
||||
];
|
||||
|
||||
const columns = React.useMemo(
|
||||
() =>
|
||||
columnDefaults.reduce( (prev, cur) => {
|
||||
() =>
|
||||
columnOrder.reduce( (prev, cur) => {
|
||||
if(columnVisibility.visible.includes(cur.name)){
|
||||
if(filterOptions[cur.key] && String(filterOptions[cur.key]).length > 0){
|
||||
return [...prev, {...cur, filtered: true}];
|
||||
@@ -217,7 +225,7 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
return [...prev];
|
||||
}
|
||||
}, [])
|
||||
, [filterOptions, columnVisibility]
|
||||
, [filterOptions, columnVisibility, columnOrder]
|
||||
);
|
||||
const flattenNode = useCallback(
|
||||
(node, host, group, depth = 0) => {
|
||||
@@ -385,7 +393,13 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
return tempData;
|
||||
}, [allData, sortData]);
|
||||
const onSubmitFilterOptions = (value) => {
|
||||
setFilterOptions({...filterOptions, [selectedColumn.current.key]: value });
|
||||
let newValue = value ? value : "";
|
||||
try{
|
||||
setFilterOptions({...filterOptions, [selectedColumn.current.key]: newValue });
|
||||
updateSetting({setting_name: `process_browser_filter_options`, value: {...filterOptions,[selectedColumn.current.key]: newValue }});
|
||||
}catch(error){
|
||||
console.log("failed to save filter options", error, filterOptions, selectedColumn.current.key, newValue);
|
||||
}
|
||||
if(viewSingleTreeData){
|
||||
return
|
||||
}
|
||||
@@ -482,7 +496,7 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
}
|
||||
|
||||
setSingleTreeData(singleTreeAdjMatrix);
|
||||
onSubmitFilterOptions({});
|
||||
onSubmitFilterOptions(undefined);
|
||||
}
|
||||
const toggleViewSingleTreeData = () => {
|
||||
setViewSingleTreeData(!viewSingleTreeData);
|
||||
@@ -550,6 +564,13 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
group={group}
|
||||
rowData={row}
|
||||
cellData={treeRootData[group][host][row.full_path_text]?.parent_path_text || ""} />;
|
||||
case "Integrity":
|
||||
return <FileBrowserTableRowStringCell
|
||||
treeRootData={treeRootData[group]}
|
||||
host={host}
|
||||
group={group}
|
||||
rowData={row}
|
||||
cellData={treeRootData[group][host][row.full_path_text ]?.metadata?.integrity_level || ''} />;
|
||||
case "Tags":
|
||||
return <FileBrowserTagsCell
|
||||
rowData={row}
|
||||
@@ -574,7 +595,7 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
cellData={treeRootData[group][host][row.full_path_text ]?.metadata?.command_line || ""}
|
||||
/>;
|
||||
default:
|
||||
console.log("hit default case in switch on c.name)")
|
||||
console.log("hit default case in switch on c.name)", c.name)
|
||||
}
|
||||
})];
|
||||
}
|
||||
@@ -620,7 +641,7 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Show/Hide Columns", type: "item", icon: null,
|
||||
name: "Reorder Columns and Adjust Visibility", type: "item", icon: null,
|
||||
click: ({event, columnIndex}) => {
|
||||
if(event){
|
||||
event.stopPropagation();
|
||||
@@ -630,12 +651,77 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
snackActions.warning("Can't filter that column");
|
||||
return;
|
||||
}
|
||||
setOpenAdjustColumnsDialog(true);
|
||||
setOpenReorderDialog(true);
|
||||
}
|
||||
}
|
||||
];
|
||||
const onSubmitAdjustColumns = ({left, right}) => {
|
||||
setColumnVisibility({visible: right, hidden: left});
|
||||
React.useEffect( () => {
|
||||
try {
|
||||
const storageItem = GetMythicSetting({setting_name: `process_browser_table_columns`, default_value: defaultVisibleColumns});
|
||||
if(storageItem !== null){
|
||||
let allColumns = [...columnVisibility["visible"].map(c => c), ...columnVisibility["hidden"].map(c => c)];
|
||||
let newHidden = [];
|
||||
allColumns.forEach((v,i,a) => {
|
||||
if(!storageItem.includes(v)){
|
||||
newHidden.push(v);
|
||||
}
|
||||
});
|
||||
if(storageItem.length !== 0){
|
||||
setColumnVisibility({visible: storageItem, hidden: newHidden});
|
||||
}
|
||||
}
|
||||
}catch(error){
|
||||
console.log("Failed to load custom browser_table_columns", error);
|
||||
}
|
||||
try {
|
||||
const storageItemOptions = GetMythicSetting({setting_name: `process_browser_filter_options`, default_value: {}});
|
||||
if(storageItemOptions !== null){
|
||||
setFilterOptions(storageItemOptions);
|
||||
}
|
||||
}catch(error){
|
||||
console.log("Failed to load custom browser_table_filter_options", error);
|
||||
}
|
||||
try {
|
||||
const storageColumnOrder = GetMythicSetting({setting_name: `process_browser_column_order`, default_value: columns.map(c => c.name)});
|
||||
if(storageColumnOrder !== null){
|
||||
let newOrder = [];
|
||||
for(let i = 0; i < storageColumnOrder.length; i++){
|
||||
for(let j = 0; j < columnOrder.length; j++){
|
||||
if(columnOrder[j].name === storageColumnOrder[i]){
|
||||
newOrder.push(columnOrder[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
setColumnOrder(newOrder);
|
||||
}
|
||||
}catch(error){
|
||||
console.log("Failed to load process_browser_table_filter_options", error);
|
||||
}
|
||||
setLoading(false);
|
||||
}, []);
|
||||
const onSubmitColumnReorder = (newOrder) => {
|
||||
let newVisible = [];
|
||||
let newHidden = [];
|
||||
for(let i = 0; i < newOrder.length; i++){
|
||||
if(newOrder[i].visible){
|
||||
newVisible.push(newOrder[i].name);
|
||||
} else {
|
||||
newHidden.push(newOrder[i].name);
|
||||
}
|
||||
}
|
||||
if(newVisible.length === 0){
|
||||
snackActions.error("Can't update to show no fields");
|
||||
return;
|
||||
}
|
||||
updateSetting({setting_name: `process_browser_column_order`, value: newOrder.map(c => c.name)});
|
||||
setColumnOrder(newOrder);
|
||||
setColumnVisibility({visible: newVisible, hidden: newHidden});
|
||||
updateSetting({setting_name: `process_browser_table_columns`, value: newVisible});
|
||||
setOpenReorderDialog(false);
|
||||
}
|
||||
const onResetColumnReorder = () => {
|
||||
onSubmitColumnReorder(columnDefaults);
|
||||
}
|
||||
const sortColumn = columns.findIndex((column) => column.key === sortData.sortKey);
|
||||
React.useEffect( () => {
|
||||
@@ -648,6 +734,21 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
openAllNodes(false);
|
||||
}
|
||||
}, [expandOrCollapseAll, updatedTreeAdjMatrix]);
|
||||
if(loading){
|
||||
return (
|
||||
<div style={{width: '100%', height: '100%', position: "relative",}}>
|
||||
<div style={{overflowY: "hidden", flexGrow: 1}}>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
left: "35%",
|
||||
top: "40%"
|
||||
}}>
|
||||
{"Loading Saved Browser Customizations..."}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100%', overflow: "hidden", position: "relative" }}>
|
||||
<MythicResizableGrid
|
||||
@@ -667,7 +768,7 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
<MythicModifyStringDialog
|
||||
title='Filter Column'
|
||||
onSubmit={onSubmitFilterOptions}
|
||||
value={filterOptions[selectedColumn.current]}
|
||||
value={String(filterOptions[selectedColumn.current.key])}
|
||||
onClose={() => {
|
||||
setOpenContextMenu(false);
|
||||
}}
|
||||
@@ -675,15 +776,20 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
}
|
||||
/>
|
||||
}
|
||||
{openAdjustColumnsDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openAdjustColumnsDialog}
|
||||
onClose={()=>{setOpenAdjustColumnsDialog(false);}}
|
||||
innerDialog={
|
||||
<MythicTransferListDialog onClose={()=>{setOpenAdjustColumnsDialog(false);}}
|
||||
onSubmit={onSubmitAdjustColumns} right={columnVisibility.visible} rightTitle="Show these columns"
|
||||
leftTitle={"Hidden Columns"} left={columnVisibility.hidden} dialogTitle={"Edit which columns are shown"}/>}
|
||||
{openReorderDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={openReorderDialog}
|
||||
onClose={()=>{setOpenReorderDialog(false);}}
|
||||
innerDialog={
|
||||
<CallbacksTableColumnsReorderDialog
|
||||
onClose={()=>{setOpenReorderDialog(false);}}
|
||||
visible={columnVisibility.visible}
|
||||
hidden={columnVisibility.hidden}
|
||||
onReset={onResetColumnReorder}
|
||||
onSubmit={onSubmitColumnReorder}
|
||||
initialItems={columnOrder}
|
||||
/>}
|
||||
/>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {CallbacksTableIPCell, CallbacksTableLastCheckinCell} from "./CallbacksTa
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import { validate as uuidValidate } from 'uuid';
|
||||
import {snackActions} from "../../utilities/Snackbar";
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
|
||||
|
||||
const callbacksAndFeaturesQuery = gql`
|
||||
@@ -291,6 +292,9 @@ export function CallbacksTabsTaskMultipleDialog({onClose, callback}) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Task Multiple {callback.payload.payloadtype.name} Callbacks at Once</DialogTitle>
|
||||
<DialogContentText style={{textAlign: "center"}}>
|
||||
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
|
||||
</DialogContentText>
|
||||
<CustomSelectTable initialData={initialData}
|
||||
selectedData={selectedData} />
|
||||
<Grid size={12}>
|
||||
|
||||
@@ -827,7 +827,7 @@ export function CallbacksTabsTaskingInputPreMemo(props){
|
||||
buffer += value;
|
||||
return;
|
||||
}
|
||||
else if(value === " "){
|
||||
else if([" ", "\t", "\r", "\n"].includes(value)){
|
||||
if(backslash){
|
||||
backslash = false;
|
||||
//buffer += " ";
|
||||
|
||||
@@ -377,7 +377,7 @@ export const CallbacksTabsTaskingSplitPanel = ({tabInfo, index, value, onCloseTa
|
||||
}
|
||||
}
|
||||
const changeSelectedTask = (task) => {
|
||||
let updated = {...taskingData};
|
||||
let updated = {...taskingDataRef.current};
|
||||
for(let i = 0; i < updated.task.length; i++){
|
||||
if(updated.task[i].id === task.id){
|
||||
updated.task[i].selected = true;
|
||||
|
||||
@@ -39,8 +39,8 @@ export function DownloadHistoryDialog(props){
|
||||
<TableRow>
|
||||
<TableCell style={{}}></TableCell>
|
||||
<TableCell style={{width: "15rem"}}>Time</TableCell>
|
||||
<TableCell style={{width: "5rem"}}>Task</TableCell>
|
||||
<TableCell style={{width: "5rem"}}>Callback</TableCell>
|
||||
<TableCell style={{width: "7rem"}}>Task</TableCell>
|
||||
<TableCell style={{width: "7rem"}}>Callback</TableCell>
|
||||
<TableCell>Comment</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
@@ -16,6 +16,8 @@ import {ResponseDisplayInteractive} from "./ResponseDisplayInteractive";
|
||||
import {ResponseDisplayMedia} from "./ResponseDisplayMedia";
|
||||
import {GetMythicSetting} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {ResponseDisplayGraph} from "./ResponseDisplayGraph";
|
||||
import {operatorSettingDefaults} from "../../../cache";
|
||||
import {ResponseDisplayTabs} from "./ResponseDisplayTabs";
|
||||
|
||||
const subResponsesStream = gql`
|
||||
subscription subResponsesStream($task_id: Int!){
|
||||
@@ -95,13 +97,14 @@ export const ResponseDisplay = (props) =>{
|
||||
const interactive = props?.task?.command?.supported_ui_features.includes("task_response:interactive") || false;
|
||||
return (
|
||||
interactive ? (
|
||||
<ResponseDisplayInteractive {...props} />
|
||||
<ResponseDisplayInteractive {...props} key={props?.task?.id} />
|
||||
) : (
|
||||
<NonInteractiveResponseDisplay {...props} />
|
||||
<NonInteractiveResponseDisplay {...props} key={props?.task?.id} />
|
||||
)
|
||||
)
|
||||
}
|
||||
const NonInteractiveResponseDisplay = (props) => {
|
||||
const theme = useTheme();
|
||||
const [output, setOutput] = React.useState("");
|
||||
const [rawResponses, setRawResponses] = React.useState([]);
|
||||
const seenResponseIDs = React.useRef([]);
|
||||
@@ -109,7 +112,7 @@ const NonInteractiveResponseDisplay = (props) => {
|
||||
const [totalCount, setTotalCount] = React.useState(0);
|
||||
const [openBackdrop, setOpenBackdrop] = React.useState(true);
|
||||
const togglingAllOutputToPaginated = React.useRef(false);
|
||||
const initialResponseStreamLimit = GetMythicSetting({setting_name: "experiment-responseStreamLimit", default_value: 50});
|
||||
const initialResponseStreamLimit = GetMythicSetting({setting_name: "experiment-responseStreamLimit", default_value: operatorSettingDefaults["experiment-responseStreamLimit"]});
|
||||
const [fetchMoreResponses] = useLazyQuery(getResponsesLazyQuery, {
|
||||
fetchPolicy: "network-only",
|
||||
onCompleted: (data) => {
|
||||
@@ -289,13 +292,16 @@ const NonInteractiveResponseDisplay = (props) => {
|
||||
}, [search.current]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Backdrop open={openBackdrop} onClick={()=>{setOpenBackdrop(false);}} style={{zIndex: 2, position: "absolute"}}>
|
||||
<CircularProgress color="inherit" disableShrink />
|
||||
</Backdrop>
|
||||
|
||||
{!openBackdrop &&
|
||||
<div style={{display: "flex", flexDirection: "column", height: "100%", width: "100%"}}>
|
||||
<div style={{display: "flex", flexDirection: "column", height: "100%", width: "100%",
|
||||
backgroundColor: theme.outputBackgroundColor + (theme.palette.mode === 'dark' ? "D0" : "D0"),
|
||||
color: theme.outputTextColor,
|
||||
}}>
|
||||
{props.searchOutput &&
|
||||
<SearchBar onSubmitSearch={onSubmitSearch} />
|
||||
}
|
||||
@@ -312,7 +318,7 @@ const NonInteractiveResponseDisplay = (props) => {
|
||||
</div>
|
||||
|
||||
}
|
||||
</React.Fragment>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export const ResponseDisplayConsole = (props) => {
|
||||
@@ -405,7 +411,6 @@ export const PaginationBar = ({selectAllOutput, totalCount, onSubmitPageChange,
|
||||
if(pageCount < 2 || pageCount === Infinity || isNaN(pageCount)){
|
||||
return (<div id={'scrolltotaskbottom' + task.id}></div>)
|
||||
}
|
||||
console.log(pageCount);
|
||||
return (
|
||||
<div id={'scrolltotaskbottom' + task.id} style={{background: "transparent", display: "flex", justifyContent: "center", alignItems: "center", paddingBottom: "10px",}} >
|
||||
<Pagination count={pageCount} page={currentPage} variant="contained" color="primary" showFirstButton showLastButton
|
||||
@@ -541,47 +546,64 @@ const ResponseDisplayComponent = ({rawResponses, viewBrowserScript, output, comm
|
||||
return null
|
||||
}
|
||||
return (
|
||||
localViewBrowserScript && Object.keys(browserScriptData).length > 0 ? (
|
||||
<React.Fragment>
|
||||
{browserScriptData?.screenshot?.map( (scr, index) => (
|
||||
<ResponseDisplayScreenshot key={"screenshot" + index + 'fortask' + task.id} task={task} {...scr}
|
||||
displayType={displayType} expand={expand} />
|
||||
))
|
||||
}
|
||||
{browserScriptData?.plaintext !== undefined &&
|
||||
<ResponseDisplayPlaintext plaintext={browserScriptData["plaintext"]} task={task}
|
||||
expand={expand} displayType={displayType} />
|
||||
}
|
||||
{browserScriptData?.table?.map( (table, index) => (
|
||||
|
||||
<ResponseDisplayTable callback_id={task.callback_id} task={task} expand={expand}
|
||||
table={table} key={"tablefortask" + task.id + "table" + index}
|
||||
displayType={displayType}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{browserScriptData?.download?.map( (dl, index) => (
|
||||
<ResponseDisplayDownload download={dl} task={task} displayType={displayType}
|
||||
key={"download" + index + "fortask" + task.id} />
|
||||
))
|
||||
}
|
||||
{browserScriptData?.search?.map( (s, index) => (
|
||||
<ResponseDisplaySearch search={s} task={task} displayType={displayType}
|
||||
key={"searchlink" + index + "fortask" + task.id} />
|
||||
))
|
||||
}
|
||||
{browserScriptData?.media?.map( (s, index) => (
|
||||
<ResponseDisplayMedia key={"searchmedia" + index + "fortask" + task.id}
|
||||
displayType={displayType}
|
||||
task={task} media={s} expand={expand} />
|
||||
))}
|
||||
{browserScriptData?.graph !== undefined &&
|
||||
<ResponseDisplayGraph graph={browserScriptData.graph} task={task}
|
||||
expand={expand} displayType={displayType} />
|
||||
}
|
||||
</React.Fragment>
|
||||
localViewBrowserScript ? (
|
||||
<ResponseDisplayBrowserScriptComponent expand={expand} displayType={displayType}
|
||||
task={task} browserScriptData={browserScriptData}
|
||||
output={output}/>
|
||||
) : (
|
||||
<ResponseDisplayPlaintext plaintext={output} task={task} expand={expand} displayType={displayType}/>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export function ResponseDisplayBrowserScriptComponent({output, browserScriptData, task, expand, displayType}) {
|
||||
return (
|
||||
<>
|
||||
{Object.keys(browserScriptData).length > 0 ? (
|
||||
<>
|
||||
{browserScriptData?.screenshot?.map( (scr, index) => (
|
||||
<ResponseDisplayScreenshot key={"screenshot" + index + 'fortask' + task.id} task={task} {...scr}
|
||||
displayType={displayType} expand={expand} />
|
||||
))
|
||||
}
|
||||
{browserScriptData?.plaintext !== undefined &&
|
||||
<ResponseDisplayPlaintext plaintext={browserScriptData["plaintext"]} task={task}
|
||||
expand={expand} displayType={displayType} />
|
||||
}
|
||||
{browserScriptData?.table?.map( (table, index) => (
|
||||
<ResponseDisplayTable callback_id={task.callback_id} task={task} expand={expand}
|
||||
table={table} key={"tablefortask" + task.id + "table" + index}
|
||||
displayType={displayType}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{browserScriptData?.download?.map( (dl, index) => (
|
||||
<ResponseDisplayDownload download={dl} task={task} displayType={displayType}
|
||||
key={"download" + index + "fortask" + task.id} />
|
||||
))
|
||||
}
|
||||
{browserScriptData?.search?.map( (s, index) => (
|
||||
<ResponseDisplaySearch search={s} task={task} displayType={displayType}
|
||||
key={"searchlink" + index + "fortask" + task.id} />
|
||||
))
|
||||
}
|
||||
{browserScriptData?.media?.map( (s, index) => (
|
||||
<ResponseDisplayMedia key={"searchmedia" + index + "fortask" + task.id}
|
||||
displayType={displayType}
|
||||
task={task} media={s} expand={expand} />
|
||||
))}
|
||||
{browserScriptData?.graph !== undefined &&
|
||||
<ResponseDisplayGraph graph={browserScriptData.graph} task={task}
|
||||
expand={expand} displayType={displayType} />
|
||||
}
|
||||
{browserScriptData.tabs && browserScriptData.tabs.length > 0 &&
|
||||
<ResponseDisplayTabs task={task} expand={expand} displayType={displayType} tabs={browserScriptData.tabs} output={output} />
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<ResponseDisplayPlaintext plaintext={output} task={task} expand={expand} displayType={displayType}/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import WrapTextIcon from '@mui/icons-material/WrapText';
|
||||
import { useReactiveVar } from '@apollo/client';
|
||||
import { meState } from '../../../cache';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import HeightIcon from '@mui/icons-material/Height';
|
||||
|
||||
const getInteractiveTaskingQuery = gql`
|
||||
${taskingDataFragment}
|
||||
@@ -168,7 +169,6 @@ export const GetOutputFormatAll = ({data, myTask, taskID, useASNIColor, message
|
||||
|
||||
}
|
||||
|
||||
|
||||
const InteractiveMessageTypes = [
|
||||
{"name": "None", "value": -1, "text": "None"},
|
||||
{"name": "Tab", "value": 13, "text": "^I"},
|
||||
@@ -202,8 +202,9 @@ const EnterOptions = [
|
||||
];
|
||||
export const ResponseDisplayInteractive = (props) =>{
|
||||
const me = useReactiveVar(meState);
|
||||
const theme = useTheme();
|
||||
const [backdropOpen, setBackdropOpen] = React.useState(false);
|
||||
const [scrollToBottom, setScrollToBottom] = React.useState(false);
|
||||
const [scrollToBottom, setScrollToBottom] = React.useState(true);
|
||||
const pageSize = React.useRef(100);
|
||||
const highestFetched = React.useRef(0);
|
||||
const [taskData, setTaskData] = React.useState([]);
|
||||
@@ -218,6 +219,7 @@ export const ResponseDisplayInteractive = (props) =>{
|
||||
const [useASNIColor, setUseANSIColor] = React.useState(true);
|
||||
const [showTaskStatus, setShowTaskStatus] = React.useState(true);
|
||||
const [wrapText, setWrapText] = React.useState(true);
|
||||
const [autoScroll, setAutoScroll] = React.useState(true);
|
||||
const {loading: loadingTasks} = useSubscription(getInteractiveTaskingQuery, {
|
||||
variables: {parent_task_id: props.task.id},
|
||||
onError: data => {
|
||||
@@ -279,8 +281,7 @@ export const ResponseDisplayInteractive = (props) =>{
|
||||
if(backdropOpen){
|
||||
setBackdropOpen(false);
|
||||
}
|
||||
|
||||
}, [highestFetched.current, rawResponses, props.task.id, backdropOpen, taskIDRef.current]);
|
||||
}, [highestFetched.current, rawResponses, props.task.id, backdropOpen, taskIDRef.current, autoScroll]);
|
||||
useSubscription(subResponsesQuery, {
|
||||
variables: {task_id: props.task.id},
|
||||
fetchPolicy: "no-cache",
|
||||
@@ -367,11 +368,14 @@ export const ResponseDisplayInteractive = (props) =>{
|
||||
const toggleWrapText = () => {
|
||||
setWrapText(!wrapText);
|
||||
}
|
||||
useEffect( () => {
|
||||
if(scrollToBottom){
|
||||
messagesEndRef.current.scrollIntoView();
|
||||
const toggleAutoScroll = () => {
|
||||
setAutoScroll(!autoScroll);
|
||||
}
|
||||
useEffect(() => {
|
||||
if(autoScroll){
|
||||
messagesEndRef?.current?.scrollIntoView({ behavior: "auto", block: "nearest" });
|
||||
}
|
||||
}, [scrollToBottom]);
|
||||
}, [props.responseRef?.current?.scrollHeight, autoScroll]);
|
||||
React.useEffect( () => {
|
||||
if(loadingTasks){
|
||||
setTaskData([]);
|
||||
@@ -392,7 +396,9 @@ export const ResponseDisplayInteractive = (props) =>{
|
||||
height: props.expand ? "100%" : undefined,
|
||||
maxHeight: props.expand ? "100%" : "500px",
|
||||
flexDirection: "column",
|
||||
width: "100%"
|
||||
width: "100%",
|
||||
backgroundColor: theme.outputBackgroundColor + (theme.palette.mode === 'dark' ? "B0" : "E0"),
|
||||
color: theme.outputTextColor,
|
||||
}}>
|
||||
<Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute",}} invisible={false}>
|
||||
<div style={{
|
||||
@@ -414,16 +420,14 @@ export const ResponseDisplayInteractive = (props) =>{
|
||||
<div style={{overflowY: "auto", width: "100%", marginBottom: "5px", height: props.expand ? "100%": undefined,
|
||||
flexGrow: 1, paddingLeft: "10px", minHeight: "50px"}} ref={props.responseRef}
|
||||
id={`ptytask${props.task.id}`}>
|
||||
|
||||
<GetOutputFormatAll data={alloutput}
|
||||
myTask={props.task.operator.username === (me?.user?.username || "")}
|
||||
taskID={props.task.id}
|
||||
useASNIColor={useASNIColor}
|
||||
messagesEndRef={messagesEndRef}
|
||||
showTaskStatus={showTaskStatus}
|
||||
search={props.searchOutput ? search : undefined}
|
||||
wrapText={wrapText}/>
|
||||
|
||||
<GetOutputFormatAll data={alloutput}
|
||||
myTask={props.task.operator.username === (me?.user?.username || "")}
|
||||
taskID={props.task.id}
|
||||
useASNIColor={useASNIColor}
|
||||
messagesEndRef={messagesEndRef}
|
||||
showTaskStatus={showTaskStatus}
|
||||
search={props.searchOutput ? search : undefined}
|
||||
wrapText={wrapText}/>
|
||||
<div ref={messagesEndRef}/>
|
||||
</div>
|
||||
{!props.task?.is_interactive_task &&
|
||||
@@ -432,6 +436,7 @@ export const ResponseDisplayInteractive = (props) =>{
|
||||
useASNIColor={useASNIColor} toggleANSIColor={toggleANSIColor}
|
||||
showTaskStatus={showTaskStatus} toggleShowTaskStatus={toggleShowTaskStatus}
|
||||
wrapText={wrapText} toggleWrapText={toggleWrapText}
|
||||
autoScroll={autoScroll} toggleAutoScroll={toggleAutoScroll}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@@ -445,7 +450,8 @@ export const ResponseDisplayInteractive = (props) =>{
|
||||
}
|
||||
const InteractiveTaskingBar = ({
|
||||
task, taskData, useASNIColor, toggleANSIColor,
|
||||
showTaskStatus, toggleShowTaskStatus, wrapText, toggleWrapText
|
||||
showTaskStatus, toggleShowTaskStatus, wrapText, toggleWrapText,
|
||||
autoScroll, toggleAutoScroll
|
||||
}) => {
|
||||
const [inputText, setInputText] = React.useState("");
|
||||
const theme = useTheme();
|
||||
@@ -462,8 +468,8 @@ const InteractiveTaskingBar = ({
|
||||
});
|
||||
const [taskOptionsIndex, setTaskOptionsIndex] = React.useState(-1);
|
||||
const [taskOptions, setTaskOptions] = React.useState([]);
|
||||
const [selectedEnterOption, setSelectedEnterOption] = React.useState(EnterOptions[1]);
|
||||
const [selectedControlOption, setSelectedControlOption] = React.useState(InteractiveMessageTypes[0]);
|
||||
const [selectedEnterOption, setSelectedEnterOption] = React.useState(1);
|
||||
const [selectedControlOption, setSelectedControlOption] = React.useState(0);
|
||||
React.useEffect( () => {
|
||||
const newTaskOptions = taskData.filter( t => t.display_params.length > 1 && (t.interactive_task_type === 0 || t.interactive_task_type === 8));
|
||||
newTaskOptions.sort( (a,b) => a.id > b.id ? -1 : 1);
|
||||
@@ -513,23 +519,23 @@ const InteractiveTaskingBar = ({
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if(event.shiftKey){
|
||||
setInputText(inputText + selectedEnterOption.value);
|
||||
setInputText(inputText + EnterOptions[selectedEnterOption].value);
|
||||
return;
|
||||
}
|
||||
if(event.metaKey || event.ctrlKey){
|
||||
setInputText(inputText + selectedEnterOption.value);
|
||||
setInputText(inputText + EnterOptions[selectedEnterOption].value);
|
||||
return;
|
||||
}
|
||||
if(selectedControlOption.value > 0){
|
||||
let ctrlSequence = selectedControlOption.text;
|
||||
let enterOption = selectedEnterOption.value;
|
||||
if(InteractiveMessageTypes[selectedControlOption].value > 0){
|
||||
let ctrlSequence = InteractiveMessageTypes[selectedControlOption].text;
|
||||
let enterOption = EnterOptions[selectedEnterOption].value;
|
||||
let originalParams = inputText + ctrlSequence + enterOption;
|
||||
// if we're looking at a tab, never send enter along with it
|
||||
if (selectedControlOption.value === 8){
|
||||
if (InteractiveMessageTypes[selectedControlOption].value === 8){
|
||||
originalParams = inputText + ctrlSequence;
|
||||
enterOption = "";
|
||||
// if we're looking at escape, never send enter along with it
|
||||
} else if(selectedControlOption.value === 4){
|
||||
} else if(InteractiveMessageTypes[selectedControlOption].value === 4){
|
||||
originalParams = ctrlSequence + inputText;
|
||||
enterOption = "";
|
||||
}
|
||||
@@ -542,16 +548,16 @@ const InteractiveTaskingBar = ({
|
||||
parameter_group_name: "default",
|
||||
parent_task_id: task.id,
|
||||
is_interactive_task: true,
|
||||
interactive_task_type: selectedControlOption.value,
|
||||
interactive_task_type: InteractiveMessageTypes[selectedControlOption].value,
|
||||
}})
|
||||
}else {
|
||||
// no control option selected, just send data along as input
|
||||
createTask({variables: {
|
||||
callback_id: task.callback.display_id,
|
||||
command: task.command.cmd,
|
||||
params: inputText + selectedEnterOption.value,
|
||||
params: inputText + EnterOptions[selectedEnterOption].value,
|
||||
tasking_location: "command_line",
|
||||
original_params: inputText + selectedEnterOption.value,
|
||||
original_params: inputText + EnterOptions[selectedEnterOption].value,
|
||||
parameter_group_name: "default",
|
||||
parent_task_id: task.id,
|
||||
is_interactive_task: true,
|
||||
@@ -559,7 +565,7 @@ const InteractiveTaskingBar = ({
|
||||
}})
|
||||
}
|
||||
setInputText("");
|
||||
setSelectedControlOption(InteractiveMessageTypes[0]);
|
||||
setSelectedControlOption(0);
|
||||
setTaskOptionsIndex(-1);
|
||||
}
|
||||
const onChangeSelect = (event) => {
|
||||
@@ -576,20 +582,29 @@ const InteractiveTaskingBar = ({
|
||||
<div style={{
|
||||
display: "flex",
|
||||
alignItems: "flex-end",
|
||||
backgroundColor: theme.palette.graphGroupRGBA,
|
||||
//backgroundColor: theme.outputBackgroundColor + (theme.palette.mode === 'dark' ? "F0" : "50"),
|
||||
color: theme.outputTextColor,
|
||||
paddingTop: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "4px",
|
||||
width: "100%"}}>
|
||||
<FormControl style={{width: "10rem", marginTop: "2px"}} >
|
||||
<InputLabel id="control-label" style={{}}>Controls</InputLabel>
|
||||
<FormControl style={{width: "10rem", marginTop: "2px", color: theme.outputTextColor}} >
|
||||
<InputLabel id="control-label" style={{color: theme.outputTextColor}}>Controls</InputLabel>
|
||||
<Select
|
||||
labelId="control-label"
|
||||
id="control-select"
|
||||
value={selectedControlOption}
|
||||
onChange={onChangeSelect}
|
||||
input={<Input style={{margin: 0}} />}
|
||||
sx={{
|
||||
// Target the icon element directly within the Select
|
||||
'.MuiSelect-icon': {
|
||||
color: theme.outputTextColor, // Set your desired color
|
||||
},
|
||||
}}
|
||||
input={<Input style={{margin: 0, color: theme.outputTextColor}} />}
|
||||
>
|
||||
{InteractiveMessageTypes.map( (opt) => (
|
||||
<MenuItem value={opt} key={opt.name}>{opt.name}</MenuItem>
|
||||
{InteractiveMessageTypes.map( (opt,index) => (
|
||||
<MenuItem value={index} key={opt.name}>{opt.name}</MenuItem>
|
||||
) )}
|
||||
</Select>
|
||||
</FormControl>
|
||||
@@ -597,29 +612,34 @@ const InteractiveTaskingBar = ({
|
||||
<MythicTextField autoFocus={true} maxRows={5} multiline={true} onChange={onInputChange} onEnter={submitTask}
|
||||
value={inputText} variant={"standard"} placeholder={">_ type here..."} inline={true}
|
||||
debounceDelay={50}
|
||||
marginBottom={"0px"} InputProps={{style: { width: "100%"}}}/>
|
||||
<FormControl style={{width: "7rem"}} >
|
||||
marginBottom={"0px"} InputProps={{style: { width: "100%", color: theme.outputTextColor}}}/>
|
||||
<FormControl style={{width: "7rem", color: theme.outputTextColor}} >
|
||||
<Select
|
||||
labelId="linefeed-label"
|
||||
id="linefeed-control"
|
||||
value={selectedEnterOption}
|
||||
autoWidth
|
||||
sx={{
|
||||
// Target the icon element directly within the Select
|
||||
'.MuiSelect-icon': {
|
||||
color: theme.outputTextColor, // Set your desired color
|
||||
},
|
||||
}}
|
||||
onChange={onChangeSelectEnterOption}
|
||||
input={<Input />}
|
||||
input={<Input style={{color: theme.outputTextColor}} />}
|
||||
IconComponent={KeyboardReturnIcon}
|
||||
>
|
||||
{EnterOptions.map( (opt) => (
|
||||
<MenuItem value={opt} key={opt.name}>{opt.name}</MenuItem>
|
||||
{EnterOptions.map( (opt,index) => (
|
||||
<MenuItem value={index} key={opt.name}>{opt.name}</MenuItem>
|
||||
) )}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<MythicStyledTooltip title={useASNIColor ? "Disable ANSI Color" : "Enable ANSI Color"} >
|
||||
<IconButton onClick={toggleANSIColor} style={{paddingLeft: 0, paddingRight: 0}} disableRipple={true} disableFocusRipple={true}>
|
||||
<IconButton onClick={toggleANSIColor} style={{paddingLeft: 0, paddingRight: 0,}} disableRipple={true} disableFocusRipple={true}>
|
||||
<PaletteIcon color={useASNIColor ? "success" : "secondary"}
|
||||
style={{cursor: "pointer"}}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={showTaskStatus ? "Hide Task Status" : "Show Task Status"} >
|
||||
<IconButton onClick={toggleShowTaskStatus} style={{paddingLeft: 0, paddingRight: 0}} disableRipple={true} disableFocusRipple={true}>
|
||||
@@ -627,7 +647,6 @@ const InteractiveTaskingBar = ({
|
||||
style={{cursor: "pointer",}}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={wrapText ? "Unwrap Text" : "Wrap Text"} >
|
||||
<IconButton onClick={toggleWrapText} style={{paddingLeft: 0, paddingRight: 0}} disableRipple={true} disableFocusRipple={true}>
|
||||
@@ -635,7 +654,13 @@ const InteractiveTaskingBar = ({
|
||||
style={{cursor: "pointer"}}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={autoScroll ? "Stop Auto Scroll" : "Auto Scroll"} >
|
||||
<IconButton onClick={toggleAutoScroll} style={{paddingLeft: 0, paddingRight: 0}} disableRipple={true} disableFocusRipple={true}>
|
||||
<HeightIcon color={autoScroll ? "success" : "secondary"}
|
||||
style={{cursor: "pointer"}}
|
||||
/>
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -436,7 +436,10 @@ const DisplayText = ({agent_file_id, expand, filename, preview, fileMetaData}) =
|
||||
setLimitedPreviewWarning(true);
|
||||
}
|
||||
}else{
|
||||
snackActions.error(data.previewFile.error)
|
||||
console.log(data.previewFile.error);
|
||||
if(data.previewFile.error !== "EOF"){
|
||||
snackActions.error(data.previewFile.error);
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (data) => {
|
||||
@@ -828,52 +831,53 @@ const DisplayDatabase = ({agent_file_id, expand, fileMetaData}) => {
|
||||
}}/>
|
||||
<Button color={"success"} variant={"contained"} onClick={onSubmitQuery} >Query</Button>
|
||||
</div>
|
||||
<TabContext style={{width: "100%", height: "100%", borderBottom: "1px solid grey"}} value={selectedTab}>
|
||||
<TabList onChange={onChangeTab} indicatorColor='secondary'
|
||||
textColor='primary'
|
||||
sx={{
|
||||
'& .Mui-selected': {
|
||||
color: "unset !important"
|
||||
}
|
||||
}}>
|
||||
{results.map((result, index) => (
|
||||
<Tab key={result.query + index} style={{padding: 0, paddingLeft: "10px", paddingRight: "10px"}} label={
|
||||
<div style={{display: "inline-block"}}>
|
||||
{result.name}
|
||||
<IconButton style={{padding: 0, marginLeft: "5px", marginRight: "5px", marginBottom: "5px"}}
|
||||
color={"error"}
|
||||
onClick={(e) => removeQuery(e, index)}>
|
||||
x
|
||||
</IconButton>
|
||||
</div>} value={index} className={selectedTab === index ? "selectedCallback" : ""} />
|
||||
))}
|
||||
</TabList>
|
||||
|
||||
<TabContext style={{width: "100%", height: "100%", borderBottom: "1px solid grey", overflow: "auto"}} value={selectedTab}>
|
||||
<TabList onChange={onChangeTab} indicatorColor='secondary'
|
||||
textColor='primary'
|
||||
sx={{
|
||||
'& .Mui-selected': {
|
||||
color: "unset !important"
|
||||
}
|
||||
}}>
|
||||
{results.map((result, index) => (
|
||||
<>
|
||||
{result?.results?.values?.length >= MAX_ROWS &&
|
||||
<Paper key={"overflow" + index} style={{display: selectedTab === index ? "flex" : "none",
|
||||
justifyContent: "center", hidden: selectedTab !== index}}>
|
||||
<WarningOutlinedIcon color={"warning"}></WarningOutlinedIcon>
|
||||
{"Output is truncated to first " + MAX_ROWS + " rows"}
|
||||
<WarningOutlinedIcon color={"warning"}></WarningOutlinedIcon>
|
||||
</Paper>
|
||||
}
|
||||
<div key={"output" + index} style={{display: selectedTab === index ? "flex" : "none", alignItems: "center"}}>
|
||||
{"Save Output As: "}
|
||||
<Button variant={"outlined"} style={{marginLeft: "10px"}} onClick={() => onSaveOutputCSV(result)} >CSV</Button>
|
||||
<Button variant={"outlined"} style={{marginLeft: "10px"}} onClick={() => onSaveOutputPrettyPrint(result)} >Pretty Print</Button>
|
||||
</div>
|
||||
<TabPanel value={index} key={"tabpanel" + index}
|
||||
style={{padding: 0, height: "100%", width: "100%", overflow: "auto", position: "relative",
|
||||
display: selectedTab === index ? "flex" : "none",
|
||||
flexDirection: "column"}} >
|
||||
|
||||
<DisplayDatabaseResult result={result?.results} />
|
||||
</TabPanel>
|
||||
</>
|
||||
<Tab key={result.query + index} style={{padding: 0, paddingLeft: "10px", paddingRight: "10px"}} label={
|
||||
<div style={{display: "inline-block"}}>
|
||||
{result.name}
|
||||
<IconButton style={{padding: 0, marginLeft: "5px", marginRight: "5px", marginBottom: "5px"}}
|
||||
color={"error"}
|
||||
onClick={(e) => removeQuery(e, index)}>
|
||||
x
|
||||
</IconButton>
|
||||
</div>} value={index} className={selectedTab === index ? "selectedCallback" : ""} />
|
||||
))}
|
||||
</TabContext>
|
||||
</TabList>
|
||||
|
||||
{results.map((result, index) => (
|
||||
<div key={index} style={{display: selectedTab === index ? "flex" : "none",
|
||||
width: "100%", height: "100%", overflow: "auto",
|
||||
flexDirection: "column"}}>
|
||||
{result?.results?.values?.length >= MAX_ROWS &&
|
||||
<Paper key={"overflow" + index} style={{display: selectedTab === index ? "flex" : "none",
|
||||
justifyContent: "center", hidden: selectedTab !== index}}>
|
||||
<WarningOutlinedIcon color={"warning"}></WarningOutlinedIcon>
|
||||
{"Output is truncated to first " + MAX_ROWS + " rows"}
|
||||
<WarningOutlinedIcon color={"warning"}></WarningOutlinedIcon>
|
||||
</Paper>
|
||||
}
|
||||
<div key={"output" + index} style={{display: selectedTab === index ? "flex" : "none", alignItems: "center", width: "100%"}}>
|
||||
{"Save Output As: "}
|
||||
<Button variant={"outlined"} style={{marginLeft: "10px"}} onClick={() => onSaveOutputCSV(result)} >CSV</Button>
|
||||
<Button variant={"outlined"} style={{marginLeft: "10px"}} onClick={() => onSaveOutputPrettyPrint(result)} >Pretty Print</Button>
|
||||
</div>
|
||||
<TabPanel value={index} key={"tabpanel" + index}
|
||||
style={{padding: 0, height: "100%", width: "100%", overflow: "auto", position: "relative",
|
||||
display: selectedTab === index ? "flex" : "none",
|
||||
flexDirection: "column"}} >
|
||||
<DisplayDatabaseResult result={result?.results} />
|
||||
</TabPanel>
|
||||
</div>
|
||||
))}
|
||||
</TabContext>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -881,29 +885,25 @@ const DisplayDatabaseResult = ({result}) => {
|
||||
const [columns, setColumns] = React.useState(result.columns);
|
||||
const [rows, setRows] = React.useState(result.values?.slice(0, MAX_ROWS));
|
||||
return (
|
||||
<>
|
||||
|
||||
<Table style={{overflow: "auto"}}>
|
||||
<TableHead >
|
||||
<TableRow>
|
||||
{columns.map((column, index) => (
|
||||
<TableCell key={"header" + index}>{column}</TableCell>
|
||||
<Table stickyHeader style={{width: "100%", height: "100%", position: "absolute"}}>
|
||||
<TableHead >
|
||||
<TableRow>
|
||||
{columns.map((column, index) => (
|
||||
<TableCell key={"header" + index}>{column}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rows.map((row, index1) => (
|
||||
<TableRow key={"row" + index1} hover>
|
||||
{columns.map((_, index2) => (
|
||||
<TableCell key={"row" + index1+ "cell" + index2}>
|
||||
{row[index2]}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rows.map((row, index1) => (
|
||||
<TableRow key={"row" + index1} hover>
|
||||
{columns.map((_, index2) => (
|
||||
<TableCell key={"row" + index1+ "cell" + index2}>
|
||||
{row[index2]}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import WrapTextIcon from '@mui/icons-material/WrapText';
|
||||
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { IconButton } from '@mui/material';
|
||||
import { IconButton, Select } from '@mui/material';
|
||||
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
|
||||
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
|
||||
import { useReactiveVar } from '@apollo/client';
|
||||
@@ -16,6 +16,7 @@ import { meState } from '../../../cache';
|
||||
import CodeIcon from '@mui/icons-material/Code';
|
||||
import {GetOutputFormatAll} from "./ResponseDisplayInteractive";
|
||||
import PaletteIcon from '@mui/icons-material/Palette';
|
||||
import Input from '@mui/material/Input';
|
||||
|
||||
const MaxRenderSize = 2000000;
|
||||
export const ResponseDisplayPlaintext = (props) =>{
|
||||
@@ -96,14 +97,25 @@ export const ResponseDisplayPlaintext = (props) =>{
|
||||
return (
|
||||
<div style={{display: "flex", height: "100%", flexDirection: "column"}}>
|
||||
{showOptions &&
|
||||
<div style={{display: "inline-flex", flexDirection: "row", alignItems: "center"}}>
|
||||
<FormControl sx={{ display: "inline-block", marginLeft: "10px" }} size="small">
|
||||
<div style={{display: "inline-flex", flexDirection: "row", alignItems: "center",
|
||||
color: theme.outputTextColor,
|
||||
backgroundColor: theme.outputBackgroundColor + (theme.palette.mode === 'dark' ? "D0" : "20"),}}>
|
||||
<FormControl sx={{ display: "inline-block", marginLeft: "10px", color: theme.outputTextColor, }} size="small">
|
||||
<TextField
|
||||
label={"Syntax"}
|
||||
select
|
||||
margin={"dense"}
|
||||
size={"small"}
|
||||
style={{display: "inline-block", width: "100%"}}
|
||||
sx={{
|
||||
// Target the icon element directly within the Select
|
||||
'.MuiSelect-icon': {
|
||||
color: theme.outputTextColor, // Set your desired color
|
||||
},
|
||||
'.MuiInputBase-root': {
|
||||
color: theme.outputTextColor,
|
||||
}
|
||||
}}
|
||||
style={{display: "inline-block", width: "100%",}}
|
||||
value={mode}
|
||||
onChange={onChangeMode}
|
||||
>
|
||||
@@ -139,7 +151,6 @@ export const ResponseDisplayPlaintext = (props) =>{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
zIndex: 1,
|
||||
backgroundColor: theme.palette.secondary.main
|
||||
}}>
|
||||
{showOptions &&
|
||||
<UnfoldLessIcon onClick={onChangeShowOptions}
|
||||
|
||||
@@ -570,9 +570,9 @@ export const ResponseDisplayTable = ({table, callback_id, expand, task}) =>{
|
||||
}
|
||||
if (sortData.sortKey === column.plaintext) {
|
||||
if (sortData.sortDirection === 'ASC') {
|
||||
setSortData({sortKey: column.plaintext, sortType: column.type, sortDirection: "DESC"});
|
||||
setSortData({...sortData, sortDirection: "DESC"});
|
||||
} else {
|
||||
setSortData({sortKey: column.plaintext, sortType: column.type, sortDirection: "ASC"});
|
||||
setSortData({sortKey: null, sortType: null, sortDirection: "ASC"});
|
||||
}
|
||||
} else {
|
||||
setSortData({sortKey: column.plaintext, sortType: column.type, sortDirection: "ASC"});
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Tabs from '@mui/material/Tabs';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import {a11yProps} from "../../MythicComponents/MythicTabPanel";
|
||||
import {ResponseDisplayBrowserScriptComponent} from "./ResponseDisplay";
|
||||
|
||||
function ResponseDisplayTabsLabel(props) {
|
||||
const { label, index, ...other } =
|
||||
props;
|
||||
return (
|
||||
<Tab
|
||||
label={
|
||||
<span>
|
||||
{label}
|
||||
</span>
|
||||
}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
border: "2px solid grey",
|
||||
borderRadius: "4px",
|
||||
//backgroundColor: 'info.main', // Color when selected
|
||||
},
|
||||
}}
|
||||
wrapped={true}
|
||||
{...a11yProps(index)}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
}
|
||||
function ResponseDisplayTabsPanel(props) {
|
||||
const { children, value, index, ...other } =
|
||||
props;
|
||||
const style =
|
||||
props.style === undefined
|
||||
? {
|
||||
display: value === index ? 'flex' : 'none',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
overflowY: "auto",
|
||||
}
|
||||
: props.style;
|
||||
return (
|
||||
<div
|
||||
role='tabpanel'
|
||||
hidden={value !== index}
|
||||
id={`scrollable-auto-tabpanel-${index}`}
|
||||
aria-labelledby={`scrollable-auto-tab-${index}`}
|
||||
style={style}
|
||||
{...other}>
|
||||
{<React.Fragment>{children}</React.Fragment>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export function ResponseDisplayTabs({ tabs, task, expand, displayType, output }) {
|
||||
const mountedRef = React.useRef(true);
|
||||
const [value, setValue] = React.useState(0);
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
React.useEffect( () => {
|
||||
return() => {
|
||||
mountedRef.current = false;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div style={{width: "100%", maxWidth: "100%", display: 'flex', flexDirection: 'column', flexGrow: 1, height: expand ? "100%" : "400px", }}>
|
||||
<AppBar color='default' position={"static"} style={{maxHeight: "25%", overflowY: 'auto', maxWidth: "100%", flexWrap: "no-wrap"}} >
|
||||
<Tabs
|
||||
value={value}
|
||||
variant={"standard"}
|
||||
onChange={handleChange}
|
||||
indicatorColor='primary'
|
||||
textColor='primary'
|
||||
style={{ }}
|
||||
TabIndicatorProps={{style: {
|
||||
display: "none",
|
||||
}}}
|
||||
aria-label='scrollable tabs'>
|
||||
{tabs.map((tab, index) => (
|
||||
<ResponseDisplayTabsLabel
|
||||
key={'tablabel' + task.id + index}
|
||||
index={index}
|
||||
label={tab.title}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
{tabs.map((tab, index) => (
|
||||
<ResponseDisplayTabsPanel
|
||||
key={'tabpanel' + task.id + index}
|
||||
value={value}
|
||||
index={index}>
|
||||
<ResponseDisplayBrowserScriptComponent
|
||||
task={task} expand={expand} displayType={displayType} output={output}
|
||||
browserScriptData={tab.content}
|
||||
/>
|
||||
</ResponseDisplayTabsPanel>
|
||||
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -288,41 +288,36 @@ const SideDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput,
|
||||
}
|
||||
return (
|
||||
<div style={{height: "100%"}}>
|
||||
{openTaskTagDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
|
||||
{openTaskTagDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
|
||||
onClose={()=>{setOpenTaskTagDialog(false);}}
|
||||
innerDialog={<ViewEditTagsDialog me={me} target_object={"task_id"} target_object_id={task.id} onClose={()=>{setOpenTaskTagDialog(false);}} />}
|
||||
/>) : null}
|
||||
{openCommentDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="lg" open={openCommentDialog}
|
||||
/>
|
||||
}
|
||||
{openCommentDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openCommentDialog}
|
||||
onClose={()=>{setOpenCommentDialog(false);}}
|
||||
innerDialog={<TaskCommentDialog task_id={task.id} onClose={()=>{setOpenCommentDialog(false);}} />}
|
||||
/>) : null
|
||||
/>
|
||||
}
|
||||
{openParametersDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
|
||||
{openParametersDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
|
||||
onClose={()=>{setOpenParametersDialog(false);}}
|
||||
innerDialog={<TaskViewParametersDialog task_id={task.id} onClose={()=>{setOpenParametersDialog(false);}} />}
|
||||
/>) : null
|
||||
/>
|
||||
}
|
||||
{openTokenDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
|
||||
{openTokenDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
|
||||
onClose={()=>{setOpenTokenDialog(false);}}
|
||||
innerDialog={<TaskTokenDialog token_id={task.token === undefined ? 0 : task.token.id} onClose={()=>{setOpenTokenDialog(false);}} />}
|
||||
/>) : null
|
||||
/>
|
||||
}
|
||||
{openOpsecDialog.open ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="md" open={openOpsecDialog.open}
|
||||
{openOpsecDialog.open && <MythicDialog fullWidth={true} maxWidth="lg" open={openOpsecDialog.open}
|
||||
onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}}
|
||||
innerDialog={<TaskOpsecDialog task_id={task.id} view={openOpsecDialog.view} onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}} />}
|
||||
/>) : null
|
||||
/>
|
||||
}
|
||||
|
||||
{openStdoutStderrDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
|
||||
{openStdoutStderrDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
|
||||
onClose={()=>{setOpenStdoutStderrDialog(false);}}
|
||||
innerDialog={<TaskViewStdoutStderrDialog task_id={task.id} onClose={()=>{setOpenStdoutStderrDialog(false);}} />}
|
||||
/>) : null
|
||||
/>
|
||||
}
|
||||
{openEventingDialog &&
|
||||
<MythicDialog
|
||||
@@ -562,41 +557,36 @@ const SpeedDialDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Backdrop open={openSpeedDial} onClick={()=>{setOpenSpeedDial(false);}} style={{zIndex: 2, position: "absolute"}}/>
|
||||
{openTaskTagDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
|
||||
{openTaskTagDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
|
||||
onClose={()=>{setOpenTaskTagDialog(false);}}
|
||||
innerDialog={<ViewEditTagsDialog me={me} target_object={"task_id"} target_object_id={task.id} onClose={()=>{setOpenTaskTagDialog(false);}} />}
|
||||
/>) : null}
|
||||
{openCommentDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="lg" open={openCommentDialog}
|
||||
/>
|
||||
}
|
||||
{openCommentDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openCommentDialog}
|
||||
onClose={()=>{setOpenCommentDialog(false);}}
|
||||
innerDialog={<TaskCommentDialog task_id={task.id} onClose={()=>{setOpenCommentDialog(false);}} />}
|
||||
/>) : null
|
||||
/>
|
||||
}
|
||||
{openParametersDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
|
||||
{openParametersDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
|
||||
onClose={()=>{setOpenParametersDialog(false);}}
|
||||
innerDialog={<TaskViewParametersDialog task_id={task.id} onClose={()=>{setOpenParametersDialog(false);}} />}
|
||||
/>) : null
|
||||
/>
|
||||
}
|
||||
{openTokenDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
|
||||
{openTokenDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
|
||||
onClose={()=>{setOpenTokenDialog(false);}}
|
||||
innerDialog={<TaskTokenDialog token_id={task.token === undefined ? 0 : task.token.id} onClose={()=>{setOpenTokenDialog(false);}} />}
|
||||
/>) : null
|
||||
/>
|
||||
}
|
||||
{openOpsecDialog.open ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="md" open={openOpsecDialog.open}
|
||||
{openOpsecDialog.open && <MythicDialog fullWidth={true} maxWidth="lg" open={openOpsecDialog.open}
|
||||
onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}}
|
||||
innerDialog={<TaskOpsecDialog task_id={task.id} view={openOpsecDialog.view} onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}} />}
|
||||
/>) : null
|
||||
/>
|
||||
}
|
||||
|
||||
{openStdoutStderrDialog ?
|
||||
(<MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
|
||||
{openStdoutStderrDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
|
||||
onClose={()=>{setOpenStdoutStderrDialog(false);}}
|
||||
innerDialog={<TaskViewStdoutStderrDialog task_id={task.id} onClose={()=>{setOpenStdoutStderrDialog(false);}} />}
|
||||
/>) : null
|
||||
/>
|
||||
}
|
||||
{openEventingDialog &&
|
||||
<MythicDialog
|
||||
|
||||
@@ -21,6 +21,7 @@ import {Backdrop, CircularProgress} from '@mui/material';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import {b64DecodeUnicode} from './ResponseDisplay';
|
||||
import {snackActions} from "../../utilities/Snackbar";
|
||||
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
|
||||
|
||||
//if we need to get all the loaded commands for the callback and filter, use this
|
||||
const GetLoadedCommandsQuery = gql`
|
||||
@@ -977,7 +978,7 @@ export function TaskParametersDialog(props) {
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">{commandInfo.cmd}'s Parameters</DialogTitle>
|
||||
<MythicDraggableDialogTitle>{commandInfo.cmd}'s Parameters</MythicDraggableDialogTitle>
|
||||
<DialogContent dividers={true}>
|
||||
<Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute"}}>
|
||||
<CircularProgress color="inherit" />
|
||||
|
||||
@@ -27,6 +27,7 @@ import MythicStyledTableCell from '../../MythicComponents/MythicTableCell';
|
||||
import {MythicFileContext} from "../../MythicComponents/MythicFileContext";
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import {updateCredentialDeleted} from "../Search/CredentialTable";
|
||||
import CloudUploadTwoToneIcon from '@mui/icons-material/CloudUploadTwoTone';
|
||||
|
||||
export const getDynamicQueryParamsString = `
|
||||
mutation getDynamicParamsMutation($callback: Int!, $command: String!, $payload_type: String!, $parameter_name: String!, $other_parameters: jsonb){
|
||||
@@ -127,7 +128,9 @@ export function TaskParametersDialogRow(props){
|
||||
if(data.dynamic_query_function.status === "success"){
|
||||
try{
|
||||
let choicesInUse = [];
|
||||
if (data.dynamic_query_function.complex_choices !== null && data.dynamic_query_function.complex_choices.length > 0) {
|
||||
if (data.dynamic_query_function.complex_choices !== null &&
|
||||
data.dynamic_query_function.complex_choices !== undefined &&
|
||||
data.dynamic_query_function.complex_choices.length > 0) {
|
||||
usingDynamicParamComplexChoices.current = true;
|
||||
setChoiceOptions([...data.dynamic_query_function.complex_choices]);
|
||||
choicesInUse = [...data.dynamic_query_function.complex_choices];
|
||||
@@ -208,6 +211,7 @@ export function TaskParametersDialogRow(props){
|
||||
}
|
||||
}
|
||||
}catch(error){
|
||||
console.log(error);
|
||||
setBackdropOpen(false);
|
||||
snackActions.warning("Failed to parse dynamic parameter results");
|
||||
usingDynamicParamComplexChoices.current = false;
|
||||
@@ -556,13 +560,13 @@ export function TaskParametersDialogRow(props){
|
||||
setValue(event.target.checked);
|
||||
props.onChange(props.name, event.target.checked);
|
||||
}
|
||||
const onFileChange = (evt) => {
|
||||
setFileValue({name: evt.target.files[0].name});
|
||||
props.onChange(props.name, evt.target.files[0]);
|
||||
const onFileChange = (newFile) => {
|
||||
setFileValue({name: newFile.name});
|
||||
props.onChange(props.name, newFile);
|
||||
}
|
||||
const onFileMultChange = (evt) => {
|
||||
setFileMultValue([...evt.target.files]);
|
||||
props.onChange(props.name, [...evt.target.files]);
|
||||
const onFileMultChange = (newFiles) => {
|
||||
setFileMultValue([...newFiles]);
|
||||
props.onChange(props.name, [...newFiles]);
|
||||
}
|
||||
const onChangeAgentConnectHost = (event) => {
|
||||
setAgentConnectHost(event.target.value);
|
||||
@@ -920,28 +924,14 @@ export function TaskParametersDialogRow(props){
|
||||
case "File":
|
||||
return (
|
||||
<>
|
||||
<Button variant="contained" component="label">
|
||||
{ fileValue.name === "" ? "Select File" : fileValue.name }
|
||||
<input onChange={onFileChange} type="file" hidden />
|
||||
</Button>
|
||||
<DragAndDropFileUpload value={fileValue} multiple={false} onChange={onFileChange} />
|
||||
</>
|
||||
|
||||
)
|
||||
case "FileMultiple":
|
||||
return (
|
||||
<>
|
||||
<Button variant="contained" component="label">
|
||||
Select Files
|
||||
<input onChange={onFileMultChange} type="file" hidden multiple />
|
||||
</Button>
|
||||
{ fileMultValue.length > 0 &&
|
||||
fileMultValue.map((f, i) => (
|
||||
<div key={i}>
|
||||
{typeof f === "string" && <MythicFileContext agent_file_id={f} />}
|
||||
{typeof f !== "string" && (f.name)}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<DragAndDropFileUpload values={fileMultValue} multiple={true} onChange={onFileMultChange} />
|
||||
</>
|
||||
)
|
||||
case "LinkInfo":
|
||||
@@ -1216,3 +1206,115 @@ export function TaskParametersDialogRow(props){
|
||||
)
|
||||
}
|
||||
|
||||
export const DragAndDropFileUpload = ({value, values, multiple, onChange}) => {
|
||||
const theme = useTheme();
|
||||
const inputRef = React.useRef(null);
|
||||
const [files, setFiles] = React.useState(values ? values : []);
|
||||
const [file, setFile] = React.useState(value ? value : {name: ""});
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
|
||||
React.useEffect( () => {
|
||||
if(value){
|
||||
setFile(value);
|
||||
}
|
||||
if(values){
|
||||
setFiles(values);
|
||||
}
|
||||
}, [value, values]);
|
||||
const handleDragEnter = () => setIsDragging(true);
|
||||
const handleDragLeave = (event) => {
|
||||
if (event.currentTarget.contains(event.relatedTarget)) {
|
||||
return; // Don't treat as a real leave event
|
||||
}
|
||||
setIsDragging(false);
|
||||
}
|
||||
const handleDrop = (e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
const droppedFiles = Array.from(e.dataTransfer.files);
|
||||
if(multiple){
|
||||
setFiles(droppedFiles);
|
||||
onChange(droppedFiles)
|
||||
} else {
|
||||
setFile(droppedFiles[0]);
|
||||
onChange(droppedFiles[0]);
|
||||
}
|
||||
};
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
const onFileChange = (evt) => {
|
||||
setFile({name: evt.target.files[0].name});
|
||||
onChange(evt.target.files[0]);
|
||||
}
|
||||
const onFileMultChange = (evt) => {
|
||||
setFiles([...evt.target.files]);
|
||||
onChange(evt.target.files)
|
||||
}
|
||||
const onClick = (e) => {
|
||||
inputRef.current.click();
|
||||
}
|
||||
return (
|
||||
<div
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
border: isDragging ? `4px dashed ${theme.palette.success.main}` : `4px dashed ${theme.palette.primary.main}`,
|
||||
padding: "20px",
|
||||
textAlign: "center",
|
||||
borderRadius: "10px",
|
||||
cursor: "pointer"
|
||||
//backgroundColor: isDragging ? "#f0f8ff" : "#fff",
|
||||
}}
|
||||
>
|
||||
<input ref={inputRef} onChange={multiple ? onFileMultChange : onFileChange} type="file" hidden multiple={multiple} />
|
||||
<div style={{display: "flex", flexDirection: "column", alignItems: "center"}}>
|
||||
{!multiple && file.name !== "" &&
|
||||
<>
|
||||
<CloudUploadTwoToneIcon fontSize={"large"} color={"success"} />
|
||||
<Typography>
|
||||
Selected:
|
||||
</Typography>
|
||||
<Typography>
|
||||
{!file.legacy && file.name}
|
||||
{file.legacy &&
|
||||
<MythicFileContext agent_file_id={file.name}
|
||||
extraStyles={{ position: "relative", marginLeft: "5px", marginRight: "5px"}} />
|
||||
}
|
||||
</Typography>
|
||||
</>
|
||||
|
||||
}
|
||||
{multiple && files.length > 0 &&
|
||||
<>
|
||||
<CloudUploadTwoToneIcon fontSize={"large"} color={"success"} />
|
||||
<Typography>
|
||||
Selected:
|
||||
</Typography>
|
||||
{files.map( (f, i) => (
|
||||
<div key={i}>
|
||||
{typeof f === "string" && <MythicFileContext agent_file_id={f} />}
|
||||
{typeof f !== "string" && (f.name)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
|
||||
}
|
||||
{file.name === "" && files.length === 0 &&
|
||||
<>
|
||||
<CloudUploadTwoToneIcon fontSize={"large"} color={"success"} />
|
||||
<Typography>
|
||||
Drag and drop files here
|
||||
</Typography>
|
||||
<Typography>
|
||||
Click to open dialog
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
|
||||
|
||||
export const allTokenDataFragment = gql`
|
||||
fragment allTokenData on token {
|
||||
@@ -89,7 +89,7 @@ export function TaskTokenDialog(props) {
|
||||
});
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Token Information</DialogTitle>
|
||||
<MythicDraggableDialogTitle >Token Information</MythicDraggableDialogTitle>
|
||||
<TableContainer className="mythicElement">
|
||||
<Table size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "scroll"}}>
|
||||
<TableHead>
|
||||
|
||||
@@ -68,13 +68,14 @@ export function TaskViewParametersDialog(props) {
|
||||
return <div>Error!</div>;
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MythicModifyStringDialog title={`View Task Parameters And Timestamps`}
|
||||
onClose={props.onClose}
|
||||
maxRows={40}
|
||||
wrap={true}
|
||||
value={comment} />
|
||||
</React.Fragment>
|
||||
<>
|
||||
<MythicModifyStringDialog title={`View Task Parameters And Timestamps`}
|
||||
onClose={props.onClose}
|
||||
maxRows={40}
|
||||
wrap={true}
|
||||
value={comment} />
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { Backdrop } from '@mui/material';
|
||||
import {CircularProgress} from '@mui/material';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import {DragAndDropFileUpload} from "../Callbacks/TaskParametersDialogRow";
|
||||
|
||||
export const getDynamicQueryBuildParameterParams = gql`
|
||||
mutation getDynamicBuildParamsMutation($payload_type: String!, $parameter_name: String!, $selected_os: String!){
|
||||
@@ -59,7 +60,7 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
const [dictSelectOptions, setDictSelectOptions] = React.useState([]);
|
||||
const [dictSelectOptionsChoice, setDictSelectOptionsChoice] = React.useState("");
|
||||
const [dateValue, setDateValue] = React.useState(dayjs(new Date()));
|
||||
const [arrayValue, setArrayValue] = React.useState([""]);
|
||||
const [arrayValue, setArrayValue] = React.useState([]);
|
||||
const [typedArrayValue, setTypedArrayValue] = React.useState([]);
|
||||
const [fileValue, setFileValue] = React.useState({name: ""});
|
||||
const [fileMultValue, setFileMultValue] = React.useState([]);
|
||||
@@ -182,13 +183,13 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
const submitDictChange = (list) => {
|
||||
onChange(name, list, false);
|
||||
};
|
||||
const onFileChange = (evt) => {
|
||||
setFileValue({name: evt.target.files[0].name});
|
||||
onChange(name, evt.target.files[0]);
|
||||
const onFileChange = (newFile) => {
|
||||
setFileValue({name: newFile.name});
|
||||
onChange(name, newFile);
|
||||
}
|
||||
const onFileMultChange = (evt) => {
|
||||
setFileMultValue([...evt.target.files]);
|
||||
onChange(name, [...evt.target.files]);
|
||||
const onFileMultChange = (newFiles) => {
|
||||
setFileMultValue([...newFiles]);
|
||||
onChange(name, [...newFiles]);
|
||||
}
|
||||
useEffect( () => {
|
||||
if( parameter_type === "ChooseOne" || parameter_type === "ChooseOneCustom" ){
|
||||
@@ -474,32 +475,13 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
case "FileMultiple":
|
||||
return (
|
||||
<>
|
||||
<Button variant="contained" component="label">
|
||||
Select Files
|
||||
<input onChange={onFileMultChange} type="file" hidden multiple />
|
||||
</Button>
|
||||
{ fileMultValue.length > 0 &&
|
||||
fileMultValue?.map((f, i) => (
|
||||
<div key={i} style={{display: "inline-block"}}>
|
||||
{typeof f === "string" && <MythicFileContext agent_file_id={f}
|
||||
extraStyles={{bottom: "-10px", position: "relative", marginLeft: "5px", marginRight: "5px"}} />}
|
||||
{typeof f !== "string" && (f.name)}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<DragAndDropFileUpload values={fileMultValue} multiple={true} onChange={onFileMultChange} />
|
||||
</>
|
||||
)
|
||||
case "File":
|
||||
return (
|
||||
<>
|
||||
<Button variant="contained" component="label">
|
||||
{ fileValue.legacy ? "Select New File" : fileValue.name === "" ? "Select File" : fileValue.name }
|
||||
<input onChange={onFileChange} type="file" hidden />
|
||||
</Button>
|
||||
{fileValue.legacy &&
|
||||
<MythicFileContext agent_file_id={fileValue.name}
|
||||
extraStyles={{ position: "relative", marginLeft: "5px", marginRight: "5px"}} />
|
||||
}
|
||||
<DragAndDropFileUpload value={fileValue} multiple={false} onChange={onFileChange} />
|
||||
</>
|
||||
|
||||
)
|
||||
|
||||
@@ -39,6 +39,7 @@ query getPayloadTypesC2ProfilesQuery($payloadType: String!, $operation_id: Int!)
|
||||
required
|
||||
verifier_regex
|
||||
choices
|
||||
ui_position
|
||||
}
|
||||
c2profileparametersinstances(where: {instance_name: {_is_null: false}, operation_id: {_eq: $operation_id}}, distinct_on: instance_name, order_by: {instance_name: asc}){
|
||||
instance_name
|
||||
@@ -61,6 +62,7 @@ query getProfileInstanceQuery($name: String!, $operation_id: Int!, $c2_profile_i
|
||||
required
|
||||
verifier_regex
|
||||
choices
|
||||
ui_position
|
||||
c2profile {
|
||||
name
|
||||
}
|
||||
@@ -86,6 +88,7 @@ query getDefaultC2ProfileParameters($c2profile_id: Int!) {
|
||||
required
|
||||
verifier_regex
|
||||
choices
|
||||
ui_position
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,12 +123,12 @@ export const getModifiedC2Params = (c2, c2profileparameters, buildOptions, use_s
|
||||
configuredParam.trackedValue = c2Config.default_value;
|
||||
configuredParam.initialValue = c2Config.default_value;
|
||||
}
|
||||
if(c2Config.dictionary_choices !== undefined){
|
||||
if(c2Config.dictionary_choices !== undefined && c2Config.dictionary_choices !== null){
|
||||
configuredParam.choices = c2Config.dictionary_choices.map(c => {return {...c, value: c.default_value}});
|
||||
configuredParam.value = configuredParam.choices;
|
||||
configuredParam.trackedValue = configuredParam.choices;
|
||||
configuredParam.initialValue = configuredParam.choices;
|
||||
}else if(c2Config.choices !== undefined){
|
||||
}else if(c2Config.choices !== undefined && c2Config.choices !== null){
|
||||
configuredParam.choices = c2Config.choices;
|
||||
if(!configuredParam.choices.includes(configuredParam.default_value)){
|
||||
if(configuredParam.choices.length > 0){
|
||||
@@ -150,7 +153,13 @@ export function Step4C2Profiles(props){
|
||||
onCompleted: data => {
|
||||
const profiles = data.c2profile.map( (c2) => {
|
||||
const parameters = getModifiedC2Params(c2, c2.c2profileparameters, props.buildOptions, false);
|
||||
parameters.sort((a,b) => -b.description.localeCompare(a.description));
|
||||
let hasUiPositions = parameters.filter(p => p.ui_position > 0).length > 0;
|
||||
if(hasUiPositions){
|
||||
parameters.sort((a,b) => a.ui_position > b.ui_position ? 1 : a.ui_position === b.ui_position ? 0 : -1);
|
||||
} else {
|
||||
parameters.sort((a,b) => -b.description.localeCompare(a.description));
|
||||
}
|
||||
|
||||
return {...c2, c2profileparameters: parameters, "selected_instance": "None"};
|
||||
});
|
||||
profiles.sort((a, b) => -b.name.localeCompare(a.name))
|
||||
@@ -285,7 +294,13 @@ export function Step4C2Profiles(props){
|
||||
return inst;
|
||||
});
|
||||
updates = getModifiedC2Params(c2, updates, props.buildOptions, true);
|
||||
updates.sort( (a, b) => a.description < b.description ? -1 : 1);
|
||||
//updates.sort( (a, b) => a.description < b.description ? -1 : 1);
|
||||
let hasUiPositions = updates.filter(p => p.ui_position > 0).length > 0;
|
||||
if(hasUiPositions){
|
||||
updates.sort((a,b) => a.ui_position > b.ui_position ? 1 : a.ui_position === b.ui_position ? 0 : -1);
|
||||
} else {
|
||||
updates.sort((a,b) => -b.description.localeCompare(a.description));
|
||||
}
|
||||
const updatedc2 = includedC2Profiles.map( (curc2, indx) => {
|
||||
if(index === indx){
|
||||
return {...curc2, c2profileparameters: updates, selected_instance: evt.target.value};
|
||||
@@ -310,7 +325,13 @@ export function Step4C2Profiles(props){
|
||||
choices: getDefaultChoices(param)};
|
||||
});
|
||||
updates = getModifiedC2Params(c2, updates, props.buildOptions, false);
|
||||
updates.sort( (a, b) => a.description < b.description ? -1 : 1);
|
||||
let hasUiPositions = updates.filter(p => p.ui_position > 0).length > 0;
|
||||
if(hasUiPositions){
|
||||
updates.sort((a,b) => a.ui_position > b.ui_position ? 1 : a.ui_position === b.ui_position ? 0 : -1);
|
||||
} else {
|
||||
updates.sort((a,b) => -b.description.localeCompare(a.description));
|
||||
}
|
||||
//updates.sort( (a, b) => a.description < b.description ? -1 : 1);
|
||||
const updatedc2 = includedC2Profiles.map( (curc2, indx) => {
|
||||
if(index === indx){
|
||||
return {...curc2, c2profileparameters: updates, selected_instance: 'None'};
|
||||
|
||||
@@ -81,6 +81,8 @@ export function Step5Build(props){
|
||||
} else if (param.parameter_type === "File") {
|
||||
if (typeof param.value === "string") {
|
||||
buildParameters.push({name: param.name, value: param.value});
|
||||
}else if(param.value.legacy){
|
||||
buildParameters.push({name: param.name, value: param.value.name});
|
||||
} else {
|
||||
const newUUID = await UploadTaskFile(param.value, "Uploaded as build parameter for " + filename);
|
||||
if (newUUID) {
|
||||
@@ -97,6 +99,8 @@ export function Step5Build(props){
|
||||
for(let j = 0; j < param.value.length; j++){
|
||||
if (typeof param.value[j] === "string") {
|
||||
fileMultipleValues.push(param.value[j]);
|
||||
}else if(param.value[j].legacy){
|
||||
fileMultipleValues.push(param.value[j].name);
|
||||
} else {
|
||||
const newUUID = await UploadTaskFile(param.value[j], "Uploaded as build parameter for " + filename);
|
||||
if (newUUID) {
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
import React, { } from 'react';
|
||||
import {useQuery, gql} from '@apollo/client';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Select from '@mui/material/Select';
|
||||
import { CreatePayloadNavigationButtons} from './CreatePayloadNavigationButtons';
|
||||
import {CreatePayloadBuildParametersTable} from './CreatePayloadBuildParametersTable';
|
||||
import {snackActions} from '../../utilities/Snackbar';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import {getDefaultValueForType, getDefaultChoices} from '../CreatePayload/Step2SelectPayloadType';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
|
||||
const GET_Payload_Types = gql`
|
||||
query getPayloadTypesBuildParametersQuery($os: jsonb!) {
|
||||
payloadtype(where: {supported_os: {_contains: $os}, deleted: {_eq: false}, wrapper: {_eq: true}}, order_by: {name: asc}) {
|
||||
name
|
||||
id
|
||||
file_extension
|
||||
supports_dynamic_loading
|
||||
buildparameters(where: {deleted: {_eq: false} }, order_by: {description: asc}) {
|
||||
default_value
|
||||
description
|
||||
format_string
|
||||
id
|
||||
name
|
||||
parameter_type
|
||||
randomize
|
||||
required
|
||||
verifier_regex
|
||||
choices
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function Step2SelectPayloadType(props){
|
||||
const [selectedPayloadType, setSelectedPayloadType] = React.useState('');
|
||||
const [selectedPayloadTypeID, setSelectedPayloadTypeID] = React.useState(0);
|
||||
const [fileExtension, setFileExtension] = React.useState('');
|
||||
const [payloadTypeParameters, setSelectedPayloadTypeParameters] = React.useState([]);
|
||||
const { loading, error, data } = useQuery(GET_Payload_Types,
|
||||
{variables:{os: props.buildOptions},
|
||||
fetchPolicy: "no-cache",
|
||||
onCompleted: data => {
|
||||
if(data.payloadtype.length > 0){
|
||||
if(props.prevData !== undefined && props.prevData.os === props.buildOptions){
|
||||
|
||||
setSelectedPayloadType(props.prevData.payload_type);
|
||||
setSelectedPayloadTypeID(props.prevData.payload_type_id);
|
||||
setFileExtension(props.prevData.file_extension);
|
||||
const payloadtypedata = data.payloadtype.reduce( (prev, payload) => {
|
||||
if(payload.name === props.prevData.payload_type){
|
||||
const params = payload.buildparameters.map( (param) => {
|
||||
for(let p = 0; p < props.prevData.parameters.length; p++){
|
||||
if(props.prevData.parameters[p]["name"] === param.name){
|
||||
return {...param, error: false,
|
||||
value: props.prevData.parameters[p]["value"],
|
||||
trackedValue: props.prevData.parameters[p]["value"],
|
||||
initialValue: getDefaultValueForType(param),
|
||||
choices: getDefaultChoices(param)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return [...prev, ...params];
|
||||
}
|
||||
return [...prev];
|
||||
}, []);
|
||||
payloadtypedata.sort((a,b) => -b.description.localeCompare(a.description));
|
||||
setSelectedPayloadTypeParameters(payloadtypedata);
|
||||
if(payloadtypedata.length === 0){
|
||||
snackActions.warning("No available payload types exist for the selected OS");
|
||||
}
|
||||
}else{
|
||||
setSelectedPayloadType(data.payloadtype[0].name);
|
||||
setSelectedPayloadTypeID(data.payloadtype[0].id);
|
||||
setFileExtension(data.payloadtype[0].file_extension);
|
||||
const payloadtypedata = data.payloadtype.reduce( (prev, payload) => {
|
||||
if(payload.name === data.payloadtype[0].name){
|
||||
const params = payload.buildparameters.map( (param) => {
|
||||
const initialValue = getDefaultValueForType(param);
|
||||
return {...param, error: false, value: initialValue,
|
||||
trackedValue: initialValue,
|
||||
initialValue: initialValue,
|
||||
choices: getDefaultChoices(param)}
|
||||
});
|
||||
return [...prev, ...params];
|
||||
}
|
||||
return [...prev];
|
||||
}, []);
|
||||
payloadtypedata.sort((a,b) => -b.description.localeCompare(a.description));
|
||||
setSelectedPayloadTypeParameters(payloadtypedata);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const finished = () => {
|
||||
if(selectedPayloadType === ""){
|
||||
snackActions.warning("No payload type selected");
|
||||
return;
|
||||
}
|
||||
props.finished({"payload_type": selectedPayloadType,
|
||||
"parameters": payloadTypeParameters,
|
||||
"payload_type_id": selectedPayloadTypeID,
|
||||
"file_extension": fileExtension,
|
||||
"os": props.buildOptions});
|
||||
}
|
||||
const canceled = () => {
|
||||
props.canceled();
|
||||
}
|
||||
const changePayloadType = (evt) => {
|
||||
setSelectedPayloadType(evt.target.value);
|
||||
const payloadtypedata = data.payloadtype.reduce( (prev, payload) => {
|
||||
if(payload.name === evt.target.value){
|
||||
setFileExtension(payload.file_extension);
|
||||
setSelectedPayloadTypeID(payload.id);
|
||||
const params = payload.buildparameters.map( (param) => {
|
||||
const initialValue = getDefaultValueForType(param);
|
||||
return {...param, error: false, value: initialValue,
|
||||
trackedValue: initialValue,
|
||||
initialValue: initialValue,
|
||||
choices: getDefaultChoices(param)}
|
||||
});
|
||||
return [...prev, ...params];
|
||||
}
|
||||
return [...prev];
|
||||
}, []);
|
||||
payloadtypedata.sort((a,b) => -b.description.localeCompare(a.description));
|
||||
setSelectedPayloadTypeParameters(payloadtypedata);
|
||||
}
|
||||
const onChange = (name, value, error) => {
|
||||
const newParams = payloadTypeParameters.map( (param) => {
|
||||
if(param.name === name){
|
||||
return {...param, value, error}
|
||||
}
|
||||
return {...param};
|
||||
});
|
||||
setSelectedPayloadTypeParameters(newParams);
|
||||
}
|
||||
if (loading) {
|
||||
return <div><CircularProgress /></div>;
|
||||
}
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return <div>Error! {error.message}</div>;
|
||||
}
|
||||
return (
|
||||
<div style={{height: "100%", display: "flex", flexDirection: "column"}}>
|
||||
<Typography variant="h3" align="left" id="selectospage" component="div"
|
||||
style={{"marginLeft": "10px"}}>
|
||||
Select Target Payload Type
|
||||
</Typography>
|
||||
<Select
|
||||
value={selectedPayloadType}
|
||||
onChange={changePayloadType}
|
||||
>
|
||||
{
|
||||
data.payloadtype.map((opt) => (
|
||||
<MenuItem key={"step2" + opt.name} value={opt.name}>{opt.name}</MenuItem>
|
||||
))
|
||||
}
|
||||
</Select><br/>
|
||||
<div style={{display: "flex", flexGrow: 1, overflowY: "auto"}}>
|
||||
<CreatePayloadBuildParametersTable onChange={onChange} buildParameters={payloadTypeParameters}/>
|
||||
</div>
|
||||
<CreatePayloadNavigationButtons first={props.first} last={props.last} canceled={canceled}
|
||||
finished={finished}/>
|
||||
<br/><br/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -103,6 +103,8 @@ export function Step5Build(props){
|
||||
} else if (param.parameter_type === "File") {
|
||||
if (typeof param.value === "string") {
|
||||
buildParameters.push({name: param.name, value: param.value});
|
||||
} else if(param.value.legacy){
|
||||
buildParameters.push({name: param.name, value: param.value.name});
|
||||
} else {
|
||||
const newUUID = await UploadTaskFile(param.value, "Uploaded as build parameter for " + filename);
|
||||
if (newUUID) {
|
||||
@@ -119,6 +121,8 @@ export function Step5Build(props){
|
||||
for(let j = 0; j < param.value.length; j++){
|
||||
if (typeof param.value[j] === "string") {
|
||||
fileMultipleValues.push(param.value[j]);
|
||||
}else if(param.value[j].legacy){
|
||||
fileMultipleValues.push(param.value[j].name);
|
||||
} else {
|
||||
const newUUID = await UploadTaskFile(param.value[j], "Uploaded as build parameter for " + filename);
|
||||
if (newUUID) {
|
||||
|
||||
@@ -93,6 +93,9 @@ mutation UpdateLevelOperationEventLog($id: Int!) {
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const levelOptions = [
|
||||
"All Levels", "warning (unresolved)", "warning (resolved)", "info", "debug", "api", "auth", "agent"
|
||||
];
|
||||
export function EventFeed({}){
|
||||
const [pageData, setPageData] = React.useState({
|
||||
"totalCount": 0,
|
||||
@@ -112,7 +115,30 @@ export function EventFeed({}){
|
||||
updatingPrev[indx] = cur;
|
||||
return [...updatingPrev];
|
||||
}
|
||||
return [...prev, cur];
|
||||
// only add this if this msg fits into the right current view
|
||||
switch(level){
|
||||
case "All Levels":
|
||||
return [...prev, cur];
|
||||
case "warning (unresolved)":
|
||||
if( (cur.warning || cur.level === 'warning') && cur.resolved === false){
|
||||
return [...prev, cur]
|
||||
} else {
|
||||
return [...prev];
|
||||
}
|
||||
case "warning (resolved)":
|
||||
if( (cur.warning || cur.level === 'warning') && cur.resolved === true){
|
||||
return [...prev, cur];
|
||||
} else {
|
||||
return [...prev];
|
||||
}
|
||||
default:
|
||||
if(cur.level === level && !cur.warning){
|
||||
return [...prev, cur];
|
||||
} else {
|
||||
return [...prev];
|
||||
}
|
||||
}
|
||||
//return [...prev, cur];
|
||||
}, [...operationeventlog]);
|
||||
newEvents.sort((a,b) => (a.id > b.id) ? -1 : ((b.id > a.id) ? 1 : 0));
|
||||
setOperationEventLog(newEvents);
|
||||
|
||||
@@ -14,6 +14,9 @@ import MenuItem from '@mui/material/MenuItem';
|
||||
import Select from '@mui/material/Select';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import {alertCount} from "../../../cache";
|
||||
import {levelOptions} from "./EventFeed";
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
|
||||
const EventList = ({onUpdateLevel, onUpdateResolution, operationeventlog}) => {
|
||||
return (
|
||||
@@ -31,9 +34,7 @@ export function EventFeedTable(props){
|
||||
const theme = useTheme();
|
||||
const [search, setSearch] = React.useState("");
|
||||
const [level, setLevel] = React.useState("info");
|
||||
const levelOptions = [
|
||||
"All Levels", "warning (unresolved)", "warning (resolved)", "info", "debug", "api", "auth", "agent"
|
||||
];
|
||||
|
||||
|
||||
const handleSearchValueChange = (name, value, error) => {
|
||||
setSearch(value);
|
||||
@@ -52,42 +53,41 @@ export function EventFeedTable(props){
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
<div style={{display: "flex", flexDirection: "column", height: "100%", maxWidth: "100%", overflowX: "hidden"}}>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.body, marginBottom: "5px", marginRight: "5px"}}>
|
||||
<Grid container spacing={1} style={{ marginTop: "0px"}}>
|
||||
<Grid style={{paddingTop: 0}} size={10}>
|
||||
<MythicTextField placeholder="Search..." value={search} marginBottom={"0px"} marginTop={"0px"}
|
||||
onChange={handleSearchValueChange} onEnter={submitSearch} InputProps={{
|
||||
endAdornment:
|
||||
<React.Fragment>
|
||||
<Tooltip title="Search">
|
||||
<IconButton onClick={submitSearch} size="large"><SearchIcon style={{color: theme.palette.info.main}}/></IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Resolve Viewable Errors">
|
||||
<IconButton onClick={props.resolveViewableErrors} size="large"><AutoFixHighIcon style={{color: theme.palette.success.main}}/></IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Resolve All Errors">
|
||||
<IconButton onClick={props.resolveAllErrors} size="large"><HealingIcon style={{color: theme.palette.success.main}}/></IconButton>
|
||||
</Tooltip>
|
||||
</React.Fragment>,
|
||||
style: {padding: 0}
|
||||
}}/>
|
||||
</Grid>
|
||||
<Grid style={{paddingTop: 0, paddingLeft: 0}} size={2}>
|
||||
<Select
|
||||
style={{width: "100%"}}
|
||||
value={level}
|
||||
onChange={handleLevelValueChange}
|
||||
>
|
||||
{
|
||||
levelOptions.map((opt, i) => (
|
||||
<MenuItem key={"levelFilter" + opt} value={opt}>{opt}</MenuItem>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
</Grid>
|
||||
<MythicPageBody>
|
||||
<Grid container spacing={1} style={{ marginTop: "0px"}}>
|
||||
<Grid style={{paddingTop: 0}} size={10}>
|
||||
<MythicTextField placeholder="Search..." value={search} marginBottom={"0px"} marginTop={"0px"}
|
||||
onChange={handleSearchValueChange} onEnter={submitSearch} InputProps={{
|
||||
endAdornment:
|
||||
<React.Fragment>
|
||||
<Tooltip title="Search">
|
||||
<IconButton onClick={submitSearch} size="large"><SearchIcon style={{color: theme.palette.info.main}}/></IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Resolve Viewable Errors">
|
||||
<IconButton onClick={props.resolveViewableErrors} size="large"><AutoFixHighIcon style={{color: theme.palette.success.main}}/></IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Resolve All Errors">
|
||||
<IconButton onClick={props.resolveAllErrors} size="large"><HealingIcon style={{color: theme.palette.success.main}}/></IconButton>
|
||||
</Tooltip>
|
||||
</React.Fragment>,
|
||||
style: {padding: 0}
|
||||
}}/>
|
||||
</Grid>
|
||||
</Paper>
|
||||
<Grid style={{paddingTop: 0, paddingLeft: 0}} size={2}>
|
||||
<Select
|
||||
style={{width: "100%"}}
|
||||
value={level}
|
||||
onChange={handleLevelValueChange}
|
||||
>
|
||||
{
|
||||
levelOptions.map((opt, i) => (
|
||||
<MenuItem key={"levelFilter" + opt} value={opt}>{opt}</MenuItem>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<div style={{display: "flex", flexDirection: "column", overflowY: "auto", flexGrow: 1, overflowX: "Hidden"}}>
|
||||
<EventList
|
||||
onUpdateResolution={props.onUpdateResolution}
|
||||
@@ -99,6 +99,6 @@ export function EventFeedTable(props){
|
||||
siblingCount={1} onChange={props.onChangePage} showFirstButton={true} showLastButton={true} style={{padding: "20px"}}/>
|
||||
<Typography style={{paddingLeft: "10px"}}>Total Results: {props.pageData.totalCount}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</MythicPageBody>
|
||||
);
|
||||
}
|
||||
@@ -66,7 +66,7 @@ mutation eventingManualTrigger($eventgroup_id: Int!){
|
||||
}
|
||||
}
|
||||
`)
|
||||
export function EventGroupTable({selectedEventGroup, me, showInstances, showGraph}) {
|
||||
export function EventGroupTable({selectedEventGroup, me, showInstances, showGraph, height}) {
|
||||
const [openEventStepRender, setOpenEventStepRender] = React.useState(false);
|
||||
const [openEnvView, setOpenEnvView] = React.useState(false);
|
||||
const [openTriggerDataView, setOpenTriggerDataView] = React.useState(false);
|
||||
@@ -141,7 +141,7 @@ export function EventGroupTable({selectedEventGroup, me, showInstances, showGrap
|
||||
triggerManually({variables: {eventgroup_id: selectedEventGroup.id}});
|
||||
}
|
||||
return (
|
||||
<div style={{marginLeft: "5px", display: "flex", overflowY: "auto", flexDirection: "column", height: "100%"}}>
|
||||
<div style={{marginLeft: "5px", display: "flex", overflowY: "auto", flexDirection: "column", height: height || "100%"}}>
|
||||
|
||||
{selectedEventGroup.id === 0 &&
|
||||
<Typography variant={"h4"}><strong>All Eventing Runs</strong></Typography>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {TextField} from '@mui/material';
|
||||
import {snackActions} from "../../utilities/Snackbar";
|
||||
import {MeContext} from "../../App";
|
||||
import {EventGroupTable} from "./EventGroupTable";
|
||||
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
|
||||
|
||||
const triggerManualMutation = gql(`
|
||||
mutation triggerManualMutation($eventgroup_id: Int!, $env_data: jsonb){
|
||||
@@ -203,7 +204,7 @@ export function EventTriggerContextSelectDialog({onClose, triggerContext}) {
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Trigger a workflow</DialogTitle>
|
||||
<MythicDraggableDialogTitle >Trigger a workflow</MythicDraggableDialogTitle>
|
||||
<DialogContent dividers={true} style={{maxHeight: "calc(70vh)"}}>
|
||||
<DialogContentText>
|
||||
Trigger a workflow with selected context and optional additional data.
|
||||
@@ -220,7 +221,7 @@ export function EventTriggerContextSelectDialog({onClose, triggerContext}) {
|
||||
}
|
||||
</Select>
|
||||
{selectedEventGroup.id !== 0 &&
|
||||
<EventGroupTable me={me} selectedEventGroup={selectedEventGroup} showInstances={false} showGraph={false}/>
|
||||
<EventGroupTable me={me} selectedEventGroup={selectedEventGroup} showInstances={false} showGraph={false} height={"unset"}/>
|
||||
}
|
||||
<Table>
|
||||
<TableHead>
|
||||
|
||||
@@ -59,8 +59,8 @@ export function ExpandedCallbackSideDetailsTable(props){
|
||||
<Table size="small" style={{tableLayout: "fixed"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "12rem"}}></TableCell>
|
||||
<TableCell></TableCell>
|
||||
<TableCell style={{width: "12rem"}}>Callback Info</TableCell>
|
||||
<TableCell>Value</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody style={{whiteSpace: "pre-wrap", wordBreak: "break-all"}}>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
export const Hasura = () => {
|
||||
return (
|
||||
<iframe height={"100%"} width={"100%"} style={{border:"none"}}
|
||||
src={"/console"} title={"Hasura Console"} />
|
||||
)
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useQuery, gql} from '@apollo/client';
|
||||
import LinearProgress from '@mui/material/LinearProgress';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import { MythicStyledTooltip } from '../../MythicComponents/MythicStyledTooltip';
|
||||
|
||||
const GetC2ProfilesAndPayloadTypes = gql`
|
||||
query GetC2AndPayloadType {
|
||||
c2profile(where: {deleted: {_eq: false}}) {
|
||||
name
|
||||
id
|
||||
container_running
|
||||
}
|
||||
payloadtype(where: {deleted: {_eq: false}, agent_type: {_eq: "agent"}}) {
|
||||
name
|
||||
wrapper
|
||||
id
|
||||
container_running
|
||||
payloadtypec2profiles {
|
||||
c2profile {
|
||||
name
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
wrappers: payloadtype(where: {deleted: {_eq: false}, agent_type: {_eq: "agent"}, wrapper: {_eq: true}}) {
|
||||
name
|
||||
wrapper
|
||||
id
|
||||
container_running
|
||||
wrap_these_payload_types {
|
||||
wrapped {
|
||||
wrapper
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function AgentC2Overview(props){
|
||||
const theme = useTheme();
|
||||
const [c2Profiles, setC2Profiles] = React.useState([]);
|
||||
const [payloadTypeRows, setPayloadTypeRows] = React.useState([]);
|
||||
const [payloadTypeRowsNoWrappers, setPayloadTypeRowsNoWrappers] = React.useState([]);
|
||||
const [wrappers, setWrappers] = React.useState([]);
|
||||
const { loading } = useQuery(GetC2ProfilesAndPayloadTypes, {fetchPolicy: "network-only",
|
||||
onCompleted: (data) => {
|
||||
const c2Headers = data.c2profile.map( (c2) => c2.name);
|
||||
const payloadRows = data.payloadtype.map( (payload) => {
|
||||
const payloadc2 = payload.payloadtypec2profiles.map( (c2) => {
|
||||
return c2.c2profile.name;
|
||||
})
|
||||
return {name: payload.name, payloadtypec2profiles: payloadc2, wrapper: payload.wrapper };
|
||||
});
|
||||
|
||||
c2Headers.sort();
|
||||
payloadRows.sort( (a,b) => a.name < b.name ? -1 : 1);
|
||||
const payloadTypeNoWrappers = payloadRows.filter( p => !p.wrapper);
|
||||
const wrapperRows = data.wrappers.map( (payload) => {
|
||||
const wrapped = payload.wrap_these_payload_types.map( (w) => {
|
||||
return w.wrapped.name;
|
||||
});
|
||||
return {name: payload.name, wrapped}
|
||||
});
|
||||
wrapperRows.sort( (a,b) => a.name < b.name ? -1 : 1);
|
||||
setWrappers(wrapperRows);
|
||||
setC2Profiles(c2Headers);
|
||||
setPayloadTypeRows(payloadRows);
|
||||
setPayloadTypeRowsNoWrappers(payloadTypeNoWrappers);
|
||||
},
|
||||
onError: (data) => {
|
||||
|
||||
}
|
||||
});
|
||||
if (loading) {
|
||||
return <LinearProgress />;
|
||||
}
|
||||
return (
|
||||
<div style={{ marginTop: "10px", marginRight: "5px"}}>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main, marginBottom: "5px", marginTop: "10px", marginRight: "5px"}} variant={"elevation"}>
|
||||
<Typography variant="h3" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
Agent / C2 Overview
|
||||
</Typography>
|
||||
</Paper>
|
||||
<TableContainer component={Paper} className="mythicElement">
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow hover>
|
||||
<TableCell></TableCell>
|
||||
{c2Profiles.map( (c2) => (
|
||||
<TableCell key={c2}>{c2}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{payloadTypeRowsNoWrappers.map( (payload) => (
|
||||
<TableRow key={payload.name} hover>
|
||||
<TableCell>{payload.name}</TableCell>
|
||||
{c2Profiles.map( (c2) => (
|
||||
<TableCell key={'payload' + c2}>
|
||||
{payload.payloadtypec2profiles.includes(c2) ?
|
||||
<MythicStyledTooltip title={payload.name + " supports " + c2}>
|
||||
<CheckCircleIcon color="success"/>
|
||||
</MythicStyledTooltip>
|
||||
: ""}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{wrappers.length > 0 &&
|
||||
<TableContainer component={Paper} className="mythicElement" style={{marginTop: "10px"}}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow hover>
|
||||
<TableCell></TableCell>
|
||||
{payloadTypeRows.map( (pt) => (
|
||||
<TableCell key={'wrapped' + pt.name}>{pt.name}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{wrappers.map( (payload) => (
|
||||
<TableRow key={'wrapper' + payload.name} hover>
|
||||
<TableCell>{payload.name}</TableCell>
|
||||
{payloadTypeRows.map( (wr) => (
|
||||
<TableCell key={'payload' + wr.name}>
|
||||
{payload.wrapped.includes(wr.name) ?
|
||||
<MythicStyledTooltip title={payload.name + " wraps " + wr.name}>
|
||||
<CheckCircleIcon color="success"/>
|
||||
</MythicStyledTooltip>
|
||||
: ""}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -57,6 +57,7 @@ import {copyStringToClipboard} from "../../utilities/Clipboard";
|
||||
import MythicStyledTableCell from "../../MythicComponents/MythicTableCell";
|
||||
import {faCopy} from '@fortawesome/free-solid-svg-icons';
|
||||
import MythicTextField from "../../MythicComponents/MythicTextField";
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
|
||||
const LeadDashboardQuery = gql`
|
||||
${taskingDataFragment}
|
||||
@@ -1521,13 +1522,7 @@ const Top10RecentScreenshotsDashboardElement = ({me, data, editing, removeElemen
|
||||
<>
|
||||
<TableDataCard title={"Recent Screenshots"}
|
||||
tableHead={
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicTableCell></MythicTableCell>
|
||||
<MythicTableCell></MythicTableCell>
|
||||
<MythicTableCell></MythicTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
null
|
||||
}
|
||||
tableBody={
|
||||
<TableBody>
|
||||
@@ -1594,11 +1589,7 @@ const Top10RecentTasksDashboardElement = ({me, data, editing, removeElement}) =>
|
||||
<>
|
||||
<TableDataCard title={"Recent Tasking"}
|
||||
tableHead={
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicTableCell></MythicTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
null
|
||||
}
|
||||
tableBody={
|
||||
<TableBody>
|
||||
@@ -1714,20 +1705,20 @@ const OperatorDashboard = ({me, setLoading, loading}) => {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div style={{display: "flex", marginLeft: "5px", marginBottom: "5px", marginTop: "5px"}}>
|
||||
<div style={{display: "flex", marginLeft: "0.5rem", marginBottom: "0.5rem",}}>
|
||||
<ActiveCallbacksDashboardElement me={me} data={analysisData}/>
|
||||
<Top10RecentPayloadsDashboardElement me={me} data={analysisData}/>
|
||||
<Top10RecentWorkflowsDashboardElement me={me} data={analysisData}/>
|
||||
</div>
|
||||
<div style={{display: "flex", marginLeft: "5px", marginBottom: "5px"}}>
|
||||
<div style={{display: "flex", marginLeft: "0.5rem", marginBottom: "0.5rem"}}>
|
||||
<ProxyUsageDashboardElement me={me} data={analysisData}/>
|
||||
<Top10RecentFileDownloadsDashboardElement me={me} data={analysisData}/>
|
||||
<Top10RecentCredentialsDashboardElement me={me} data={analysisData}/>
|
||||
</div>
|
||||
<div style={{display: "flex", marginLeft: "5px", marginBottom: "5px"}}>
|
||||
<div style={{display: "flex", marginLeft: "0.5rem", marginBottom: "0.5rem"}}>
|
||||
<Top10RecentTasksDashboardElement me={me} data={analysisData}/>
|
||||
</div>
|
||||
<div style={{display: "flex", marginLeft: "5px", marginBottom: "5px"}}>
|
||||
<div style={{display: "flex", marginLeft: "0.5rem", marginBottom: "0.5rem"}}>
|
||||
<Top10RecentScreenshotsDashboardElement me={me} data={analysisData}/>
|
||||
<MyOperationsDashboardElement me={me} data={analysisData} reloadDashboard={reloadDashboard}/>
|
||||
</div>
|
||||
@@ -1754,16 +1745,16 @@ const LeadDashboard = ({me, setLoading, loading}) => {
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<div style={{display: "flex", marginLeft: "5px", marginBottom: "5px", marginTop: "5px"}}>
|
||||
<div style={{display: "flex", marginLeft: "0.5rem", marginBottom: "0.5rem",}}>
|
||||
<ActiveCallbacksDashboardElement me={me} data={analysisData} />
|
||||
<Top10CommandStatsDashboardElement me={me} data={analysisData} />
|
||||
<Top10UserContextsDashboardElement me={me} data={analysisData} />
|
||||
<Top10HostContextsDashboardElement me={me} data={analysisData} />
|
||||
</div>
|
||||
<div style={{display: "flex", marginLeft: "5px", marginBottom: "5px"}}>
|
||||
<div style={{display: "flex", marginLeft: "0.5rem", marginBottom: "0.5rem"}}>
|
||||
<ActivityPerDayDashboardElement me={me} data={analysisData} />
|
||||
</div>
|
||||
<div style={{display: "flex", marginLeft: "5px"}}>
|
||||
<div style={{display: "flex", marginLeft: "0.5rem"}}>
|
||||
<TaskStatusDashboardElement me={me} data={analysisData} />
|
||||
<OperatorActivityDashboardElement me={me} data={analysisData} />
|
||||
<Top10ArtifactsDashboardElement me={me} data={analysisData} />
|
||||
@@ -1932,7 +1923,7 @@ const CustomDashboard = ({me, setLoading, loading, editing}) => {
|
||||
<div style={{height: "100%", overflowY: "auto"}}>
|
||||
{dashboards.map((d, i) => (
|
||||
<div key={"dashboardRow" + i}
|
||||
style={{display: "flex", marginLeft: "5px", marginTop: "5px", borderRadius: "4px", }}>
|
||||
style={{display: "flex", marginLeft: "0.5rem", marginBottom: "0.5rem", borderRadius: "5px", }}>
|
||||
{editing &&
|
||||
<div style={{display: "flex", flexDirection: "column", width: "40px", justifyContent: "center"}}>
|
||||
<MythicStyledTooltip title={"Add New Chart To This Row"}>
|
||||
@@ -1976,7 +1967,6 @@ const CustomDashboard = ({me, setLoading, loading, editing}) => {
|
||||
}
|
||||
|
||||
export function CallbacksCard({me}) {
|
||||
const theme = useTheme();
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [editing, setEditing] = React.useState(false);
|
||||
const initialDashboardView = GetMythicSetting({
|
||||
@@ -1992,24 +1982,20 @@ export function CallbacksCard({me}) {
|
||||
setLoading(true);
|
||||
}
|
||||
return (
|
||||
<div style={{width: "100%", height: "100%", display: "flex", flexDirection: "column", position: "relative"}}>
|
||||
<Paper elevation={5} variant={"elevation"} style={{
|
||||
backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main,
|
||||
display: "flex", alignItems: "center", justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5" style={{textAlign: "left", display: "inline-block", marginLeft: "10px"}}>
|
||||
<>
|
||||
<MythicPageHeader title={
|
||||
<>
|
||||
Welcome <b>{me.user.username}</b>
|
||||
{me.user.current_operation_id === 0 ? null : (
|
||||
<>
|
||||
{" to"} <b>{me.user.current_operation}</b>'s Dashboard
|
||||
{" to"} <b>{me.user.current_operation}</b>'s Dashboard
|
||||
</>
|
||||
)}
|
||||
|
||||
</Typography>
|
||||
<span>
|
||||
</>
|
||||
}>
|
||||
<div style={{display: "flex", alignItems: "center"}}>
|
||||
<TextField
|
||||
style={{color: "white", marginTop: "5px", width: "10rem"}}
|
||||
style={{color: "white", width: "10rem", margin: "0.5rem"}}
|
||||
InputProps={{style: {color: "white"}}}
|
||||
size={"small"}
|
||||
select={true}
|
||||
@@ -2022,8 +2008,8 @@ export function CallbacksCard({me}) {
|
||||
<MenuItem key={"searchopt" + opt} value={opt}>{opt}</MenuItem>
|
||||
))
|
||||
}
|
||||
</TextField>
|
||||
<MythicStyledTooltip title={"Analyze Operation Data Again"}>
|
||||
</TextField>
|
||||
<MythicStyledTooltip title={"Analyze Operation Data Again"} tooltipStyle={{display: "inline-block"}}>
|
||||
<IconButton style={{color: "white"}} onClick={() => setLoading(true)}>
|
||||
<ReplayIcon />
|
||||
</IconButton>
|
||||
@@ -2035,8 +2021,8 @@ export function CallbacksCard({me}) {
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
</span>
|
||||
</Paper>
|
||||
</div>
|
||||
</MythicPageHeader>
|
||||
<div style={{width: "100%", height: "100%", display: "flex", flexDirection: "column", position: "relative", overflowY: "auto"}}>
|
||||
{loading &&
|
||||
<div style={{
|
||||
@@ -2070,7 +2056,7 @@ export function CallbacksCard({me}) {
|
||||
me={me} loading={loading} setLoading={setLoading} editing={editing}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import Paper from "@mui/material/Paper";
|
||||
|
||||
const normalColors = [
|
||||
'#09bdff',
|
||||
@@ -45,16 +46,17 @@ export const PieChartCard = ({
|
||||
const toggleLegend = () => {
|
||||
setShowLegend(!showLegend);
|
||||
}
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div style={{
|
||||
marginRight: "5px",
|
||||
<Paper variant={"elevation"} elevation={1} style={{
|
||||
marginRight: "0.5rem",
|
||||
width: width,
|
||||
height: "100%",
|
||||
border: "1px solid gray",
|
||||
borderRadius: "4px",
|
||||
border: `1px solid ${theme.borderColor}`,
|
||||
borderRadius: "5px",
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
<h3 style={{marginTop: 0, marginLeft: "5px", marginBottom: 0, paddingBottom: 0,}}>
|
||||
<h3 style={{marginTop: 0, marginLeft: "0.5rem", marginBottom: 0, paddingBottom: 0,}}>
|
||||
{editing &&
|
||||
<span>
|
||||
<MythicStyledTooltip title={"Remove element"}>
|
||||
@@ -120,7 +122,7 @@ export const PieChartCard = ({
|
||||
}}>
|
||||
{innerElement}
|
||||
</PieChart>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
function GaugePointer() {
|
||||
@@ -151,8 +153,7 @@ export const GaugeCard = ({data, width = "100%", additionalStyles, innerElement,
|
||||
right: 10,
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
}, colors = normalColors, onClick, title = "", editing, removeElement, customizeElement
|
||||
}) => {
|
||||
}, colors = normalColors, onClick, title = "", editing, removeElement, customizeElement }) => {
|
||||
const theme = useTheme();
|
||||
const getFillColor = () => {
|
||||
if(data['total'] === 0){return theme.palette.text.disabled}
|
||||
@@ -166,15 +167,15 @@ export const GaugeCard = ({data, width = "100%", additionalStyles, innerElement,
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div style={{
|
||||
marginRight: "5px",
|
||||
<Paper variant={"elevation"} elevation={1} style={{
|
||||
marginRight: "0.5rem",
|
||||
width: width,
|
||||
height: "100%",
|
||||
border: "1px solid gray",
|
||||
border: `1px solid ${theme.borderColor}`,
|
||||
borderRadius: "5px",
|
||||
overflow: "hidden",
|
||||
borderRadius: "4px",
|
||||
}}>
|
||||
<h3 style={{marginTop: 0, marginLeft: "5px", marginBottom: 0, paddingBottom: 0,}}>
|
||||
<h3 style={{marginTop: 0, marginLeft: "0.5rem", marginBottom: 0, paddingBottom: 0,}}>
|
||||
{editing &&
|
||||
<span>
|
||||
<MythicStyledTooltip title={"Remove element"}>
|
||||
@@ -208,21 +209,22 @@ export const GaugeCard = ({data, width = "100%", additionalStyles, innerElement,
|
||||
})}
|
||||
>
|
||||
</Gauge>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
export const CallbackDataCard = ({mainTitle, secondTitle, mainElement, secondaryElement, width="100%",
|
||||
editing, removeElement}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div style={{
|
||||
marginRight: "5px",
|
||||
<Paper variant={"elevation"} elevation={1} style={{
|
||||
marginRight: "0.5rem",
|
||||
width: width,
|
||||
height: "100%",
|
||||
border: "1px solid gray",
|
||||
border: `1px solid ${theme.borderColor}`,
|
||||
borderRadius: "5px",
|
||||
overflow: "hidden",
|
||||
borderRadius: "4px",
|
||||
}} >
|
||||
<h5 style={{marginTop: 0, marginLeft: "5px", marginBottom: 0, paddingBottom: 0}}>
|
||||
<h2 style={{marginTop: 0, marginLeft: "0.5rem", marginBottom: 0, paddingBottom: 0}}>
|
||||
{editing &&
|
||||
<span >
|
||||
<MythicStyledTooltip title={"Remove element"}>
|
||||
@@ -233,7 +235,7 @@ export const CallbackDataCard = ({mainTitle, secondTitle, mainElement, secondary
|
||||
</span>
|
||||
}
|
||||
{mainTitle}
|
||||
</h5>
|
||||
</h2>
|
||||
<div style={{height: 180, cursor: "pointer"}}>
|
||||
<MythicStyledTooltip title={"Go to Active Callbacks"}>
|
||||
{mainElement}
|
||||
@@ -243,43 +245,46 @@ export const CallbackDataCard = ({mainTitle, secondTitle, mainElement, secondary
|
||||
{secondaryElement}
|
||||
</MythicStyledTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
export const TableDataCard = ({title, width = "100%", tableHead, tableBody, editing, removeElement, customizeElement}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div style={{
|
||||
marginRight: "5px",
|
||||
<Paper variant={"elevation"} elevation={1} style={{
|
||||
marginRight: "0.5rem",
|
||||
width: width,
|
||||
height: "100%",
|
||||
border: "1px solid gray",
|
||||
borderRadius: "4px",
|
||||
border: `1px solid ${theme.borderColor}`,
|
||||
borderRadius: "5px",
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
<h3 style={{marginTop: 0, marginLeft: "5px", marginBottom: 0, paddingBottom: 0}}>
|
||||
<h3 style={{marginTop: 0, marginLeft: "0.5rem", marginBottom: 0, paddingBottom: 0}}>
|
||||
{editing &&
|
||||
<span >
|
||||
<MythicStyledTooltip title={"Remove element"}>
|
||||
<IconButton onClick={removeElement} >
|
||||
<DeleteIcon color={"error"}/>
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</span>
|
||||
<MythicStyledTooltip title={"Remove element"}>
|
||||
<IconButton onClick={removeElement} >
|
||||
<DeleteIcon color={"error"}/>
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</span>
|
||||
}
|
||||
{title}
|
||||
<span style={{float: "right", marginRight: "5px"}}>
|
||||
<span style={{float: "right", marginRight: "0.5rem"}}>
|
||||
{customizeElement}
|
||||
</span>
|
||||
</h3>
|
||||
<div style={{height: 200, overflowY: "auto"}}>
|
||||
<Table style={{ "maxWidth": "100%", "overflow": "auto"}} stickyHeader size="small">
|
||||
<div style={{height: 200, overflowY: "auto", width: "100%"}}>
|
||||
<Table style={{ maxWidth: "100%", overflow: "auto"}} stickyHeader size="small">
|
||||
{tableHead}
|
||||
{tableBody}
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
export const LineTimeChartCard = ({data, additionalStyles}) => {
|
||||
const theme = useTheme();
|
||||
const [value, setValue] = React.useState([0, 0]);
|
||||
const [range, setRange] = React.useState([0, 0]);
|
||||
React.useEffect( () => {
|
||||
@@ -309,12 +314,12 @@ export const LineTimeChartCard = ({data, additionalStyles}) => {
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div style={{
|
||||
marginBottom: "5px",
|
||||
marginTop: "10px",
|
||||
<Paper variant={"elevation"} elevation={1} style={{
|
||||
marginRight: "0.5rem",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
border: "1px solid gray",
|
||||
border: `1px solid ${theme.borderColor}`,
|
||||
borderRadius: "5px",
|
||||
overflow: "hidden",
|
||||
}} >
|
||||
<Typography variant={"h3"} style={{margin: 0, padding: 0, position: "relative", left: "30%"}}>
|
||||
@@ -369,11 +374,12 @@ export const LineTimeChartCard = ({data, additionalStyles}) => {
|
||||
max={range[1]}
|
||||
sx={{ mt: 2, width: "80%", left: "10%" }}
|
||||
/>
|
||||
</div>
|
||||
</Paper>
|
||||
|
||||
)
|
||||
}
|
||||
export const LineTimeMultiChartCard = ({data, additionalStyles, colors=normalColors, view_utc_time, editing, removeElement, customizeElement}) => {
|
||||
const theme = useTheme();
|
||||
const [value, setValue] = React.useState([0, 0]);
|
||||
const [range, setRange] = React.useState([0, 0]);
|
||||
React.useEffect( () => {
|
||||
@@ -423,13 +429,13 @@ export const LineTimeMultiChartCard = ({data, additionalStyles, colors=normalCol
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div style={{
|
||||
<Paper variant={"elevation"} elevation={1} style={{
|
||||
width: "100%",
|
||||
marginRight: "0.5rem",
|
||||
height: "100%",
|
||||
marginRight: "5px",
|
||||
border: "1px solid gray",
|
||||
border: `1px solid ${theme.borderColor}`,
|
||||
borderRadius: "5px",
|
||||
overflow: "hidden",
|
||||
borderRadius: "4px",
|
||||
}} >
|
||||
<Typography variant={"h3"} style={{margin: 0, padding: 0, position: "relative", left: "30%"}}>
|
||||
{editing &&
|
||||
@@ -500,7 +506,7 @@ export const LineTimeMultiChartCard = ({data, additionalStyles, colors=normalCol
|
||||
max={range[1]}
|
||||
sx={{ mt: 2, width: "80%", left: "10%" }}
|
||||
/>
|
||||
</div>
|
||||
</Paper>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -509,12 +515,14 @@ export const StackedBarChartCard = ({data, labels, title, width="100%", hidden,
|
||||
top: 40,
|
||||
bottom: 10,
|
||||
}}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div style={{
|
||||
marginRight: "5px",
|
||||
<Paper variant={"elevation"} elevation={1} style={{
|
||||
marginRight: "0.5rem",
|
||||
width: width,
|
||||
height: "100%",
|
||||
border: "1px solid gray",
|
||||
border: `1px solid ${theme.borderColor}`,
|
||||
borderRadius: "5px",
|
||||
overflow: "hidden",
|
||||
}} >
|
||||
<h3 style={{marginTop: 0, marginLeft: "5px", marginBottom: 0, paddingBottom: 0, position: "absolute"}}>
|
||||
@@ -544,6 +552,6 @@ export const StackedBarChartCard = ({data, labels, title, width="100%", hidden,
|
||||
}
|
||||
}
|
||||
}} />
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import React from 'react';
|
||||
import {CallbacksCard} from "./CallbacksCard";
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
|
||||
|
||||
export function Home({me}) {
|
||||
return (
|
||||
<div style={{width: "100%", height: "100%", display: "flex"}}>
|
||||
<MythicPageBody>
|
||||
<CallbacksCard me={me} />
|
||||
</div>
|
||||
</MythicPageBody>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
export const Jupyter = () => {
|
||||
return (
|
||||
<iframe height={"100%"} width={"100%"} style={{border:"none"}}
|
||||
src={"/jupyter"} title={"Jupyter Notebooks"} />
|
||||
)
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export function LoginForm(props){
|
||||
const [authOptions, setAuthOptions] = React.useState([]);
|
||||
const selectedAuthOptionRef = React.useRef({});
|
||||
let queryParams = new URLSearchParams(window.location.search);
|
||||
const redirectPath = queryParams.has("redirect") ? queryParams.get("redirect") : "/new";
|
||||
const redirectPath = React.useRef(queryParams.has("redirect") ? queryParams.get("redirect") : "/new");
|
||||
useEffect( () => {
|
||||
if(!isJWTValid()){
|
||||
console.log("calling FailedRefresh on the login page to remove old data");
|
||||
@@ -197,7 +197,7 @@ export function LoginForm(props){
|
||||
return (
|
||||
<div style={{justifyContent: "center", display: "flex"}}>
|
||||
{
|
||||
me.loggedIn && (<Navigate replace to={redirectPath}/>)
|
||||
me.loggedIn && (<Navigate replace to={redirectPath.current}/>)
|
||||
}
|
||||
|
||||
<div style={{backgroundColor: "transparent"}}>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Backdrop } from '@mui/material';
|
||||
import {CircularProgress} from '@mui/material';
|
||||
import { MythicSelectFromListDialog } from '../../MythicComponents/MythicSelectFromListDialog';
|
||||
import { MythicDialog } from '../../MythicComponents/MythicDialog';
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
|
||||
const Get_MITREATTACK = gql`
|
||||
query GetMitreAttack {
|
||||
@@ -367,7 +368,7 @@ export function MitreAttack({me}){
|
||||
getTasksFilteredByTag({variables: {tasks: taskIds}})
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MythicPageBody>
|
||||
<Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute"}} invisible={false}>
|
||||
<CircularProgress color="inherit" disableShrink />
|
||||
</Backdrop>
|
||||
@@ -388,7 +389,7 @@ export function MitreAttack({me}){
|
||||
action={"select"} identifier={"cmd"} display={"cmd"}/>}
|
||||
/>
|
||||
}
|
||||
</React.Fragment>
|
||||
</MythicPageBody>
|
||||
|
||||
);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import {CircularProgress} from '@mui/material';
|
||||
import { MythicDisplayTextDialog} from '../../MythicComponents/MythicDisplayTextDialog';
|
||||
import { MythicDialog } from '../../MythicComponents/MythicDialog';
|
||||
import { SelectPayloadTypeDialog } from './SelectPayloadTypeDialog';
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
|
||||
|
||||
export function MitreGrid({entries, onGetCommands, onGetTasks, onGetCommandsFiltered, onGetTasksFiltered, onFilterByTags, showCountGrouping}){
|
||||
@@ -23,12 +24,8 @@ export function MitreGrid({entries, onGetCommands, onGetTasks, onGetCommandsFilt
|
||||
const [backdropOpen, setBackdropOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", flexDirection: "column", height: "100%"}}>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main,
|
||||
marginBottom: "5px", marginRight: "5px"}} variant={"elevation"}>
|
||||
<Typography variant="h5" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
{"MITRE ATT&CK Mappings"}
|
||||
</Typography>
|
||||
<>
|
||||
<MythicPageHeader title={"MITRE ATT&CK Mappings"}>
|
||||
<PoperDropdown onGetCommands={onGetCommands}
|
||||
onGetTasks={onGetTasks}
|
||||
onGetCommandsFiltered={onGetCommandsFiltered}
|
||||
@@ -38,7 +35,7 @@ export function MitreGrid({entries, onGetCommands, onGetTasks, onGetCommandsFilt
|
||||
showCountGrouping={showCountGrouping}
|
||||
entries={entries}
|
||||
/>
|
||||
</Paper>
|
||||
</MythicPageHeader>
|
||||
|
||||
<div style={{display: "flex", flexGrow: 1, overflow: "auto"}}>
|
||||
{backdropOpen && <Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute"}} invisible={false}>
|
||||
@@ -47,7 +44,7 @@ export function MitreGrid({entries, onGetCommands, onGetTasks, onGetCommandsFilt
|
||||
}
|
||||
<MitreGridDisplay entries={entries} showCountGrouping={showCountGrouping} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import {MythicConfirmDialog} from '../../MythicComponents/MythicConfirmDialog';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
|
||||
const newBlockListEntry = gql`
|
||||
mutation newBlockListsEntries($entries: [disabledcommandsprofile_insert_input!]!) {
|
||||
@@ -174,14 +175,11 @@ export function CommandBlockListTable(props){
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main}} variant={"elevation"}>
|
||||
<Typography variant="h5" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
Command Block Lists
|
||||
</Typography>
|
||||
<>
|
||||
<MythicPageHeader title={"Command Block Lists"}>
|
||||
<Button size="small"
|
||||
onClick={()=>{setOpenNewDialog(true);}}
|
||||
style={{marginRight: "10px", float: "right", color: "white"}}
|
||||
style={{color: "white", whiteSpace: "nowrap"}}
|
||||
startIcon={<AddCircleIcon color="success" style={{backgroundColor: "white", borderRadius: "10px"}}/>}
|
||||
>New Block List</Button>
|
||||
{openNew &&
|
||||
@@ -191,7 +189,7 @@ export function CommandBlockListTable(props){
|
||||
/>}
|
||||
/>
|
||||
}
|
||||
</Paper>
|
||||
</MythicPageHeader>
|
||||
<TableContainer className="mythicElement">
|
||||
<Table size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "scroll"}}>
|
||||
<TableHead>
|
||||
@@ -211,7 +209,7 @@ export function CommandBlockListTable(props){
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { MythicStyledTooltip } from '../../MythicComponents/MythicStyledTooltip'
|
||||
import { IconButton } from '@mui/material';
|
||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
||||
import {meState} from "../../../cache";
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
|
||||
const newOperatorMutation = gql`
|
||||
mutation NewOperator($username: String!, $password: String!) {
|
||||
@@ -143,105 +144,100 @@ export function OperationTable(props){
|
||||
newOperation({variables: {name: operation_name}})
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main}} variant={"elevation"}>
|
||||
<Typography variant="h5" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
Operations
|
||||
</Typography>
|
||||
{showDeleted ? (
|
||||
<MythicStyledTooltip title={"Hide Deleted Operations"} tooltipStyle={{float: "right"}}>
|
||||
<IconButton size="small" style={{float: "right", marginRight: "10px"}}
|
||||
variant="contained" onClick={() => setShowDeleted(!showDeleted)}>
|
||||
<VisibilityIcon />
|
||||
<>
|
||||
<MythicPageHeader title={"Operations"}>
|
||||
<Button size="small" onClick={() => {setOpenNewOperationDialog(true);}}
|
||||
style={{ color: "white", whiteSpace: "nowrap"}}
|
||||
startIcon={<AddCircleIcon color="success" style={{backgroundColor: "white", borderRadius: "10px"}}/>} >
|
||||
New Operation
|
||||
</Button>
|
||||
<MythicStyledTooltip title={"Create new operator"} tooltipStyle={{}}>
|
||||
<IconButton size="small"
|
||||
onClick={()=>{setOpenNewOperatorDialog(true);}}
|
||||
style={{}}
|
||||
variant="contained">
|
||||
<PersonAddIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
|
||||
) : (
|
||||
<MythicStyledTooltip title={"Show Deleted Operations"} tooltipStyle={{float: "right"}}>
|
||||
<IconButton size="small" style={{float: "right", marginRight: "10px"}}
|
||||
variant="contained" onClick={() => setShowDeleted(!showDeleted)} >
|
||||
<VisibilityOffIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)}
|
||||
<MythicStyledTooltip title={"Create new operator"} tooltipStyle={{float: "right"}}>
|
||||
<IconButton size="small"
|
||||
onClick={()=>{setOpenNewOperatorDialog(true);}}
|
||||
style={{marginRight: "10px", float: "right"}}
|
||||
variant="contained">
|
||||
<PersonAddIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<Button size="small" onClick={() => {setOpenNewOperationDialog(true);}}
|
||||
style={{marginRight: "20px", float: "right", color: "white"}}
|
||||
startIcon={<AddCircleIcon color="success" style={{backgroundColor: "white", borderRadius: "10px"}}/>} >
|
||||
New Operation
|
||||
</Button>
|
||||
{showDeleted ? (
|
||||
<MythicStyledTooltip title={"Hide Deleted Operations"} tooltipStyle={{}}>
|
||||
<IconButton size="small" style={{}}
|
||||
variant="contained" onClick={() => setShowDeleted(!showDeleted)}>
|
||||
<VisibilityIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) : (
|
||||
<MythicStyledTooltip title={"Show Deleted Operations"} tooltipStyle={{}}>
|
||||
<IconButton size="small" style={{}}
|
||||
variant="contained" onClick={() => setShowDeleted(!showDeleted)} >
|
||||
<VisibilityOffIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)}
|
||||
{openNewOperator &&
|
||||
<MythicDialog open={openNewOperator}
|
||||
onClose={()=>{setOpenNewOperatorDialog(false);}}
|
||||
innerDialog={<SettingsOperatorDialog title="New Operator" onAccept={onSubmitNewOperator} handleClose={()=>{setOpenNewOperatorDialog(false);}} {...props}/>}
|
||||
/>
|
||||
}
|
||||
{openNewOperation &&
|
||||
<MythicDialog
|
||||
fullWidth={true}
|
||||
open={openNewOperation}
|
||||
onClose={() => {setOpenNewOperationDialog(false);}}
|
||||
innerDialog={
|
||||
<MythicModifyStringDialog title={"New Operation's Name"}
|
||||
onClose={() => {setOpenNewOperationDialog(false);}}
|
||||
value={""}
|
||||
onSubmit={onSubmitNewOperation}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
||||
{openNewOperator &&
|
||||
<MythicDialog open={openNewOperator}
|
||||
onClose={()=>{setOpenNewOperatorDialog(false);}}
|
||||
innerDialog={<SettingsOperatorDialog title="New Operator" onAccept={onSubmitNewOperator} handleClose={()=>{setOpenNewOperatorDialog(false);}} {...props}/>}
|
||||
/>
|
||||
}
|
||||
{openNewOperation &&
|
||||
<MythicDialog
|
||||
fullWidth={true}
|
||||
open={openNewOperation}
|
||||
onClose={() => {setOpenNewOperationDialog(false);}}
|
||||
innerDialog={
|
||||
<MythicModifyStringDialog title={"New Operation's Name"}
|
||||
onClose={() => {setOpenNewOperationDialog(false);}}
|
||||
value={""}
|
||||
onSubmit={onSubmitNewOperation}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
||||
</Paper>
|
||||
<TableContainer className="mythicElement">
|
||||
{props.operations.length === 0 &&
|
||||
<div style={{display: "flex", flexDirection: "column", flexGrow: 1}}>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
left: "35%",
|
||||
top: "50%"
|
||||
}}>
|
||||
{"No Operations available!"}<br/>
|
||||
{"Ask a Mythic admin or operation lead to add you to an operation."}
|
||||
</MythicPageHeader>
|
||||
<TableContainer className="mythicElement">
|
||||
{props.operations.length === 0 &&
|
||||
<div style={{display: "flex", flexDirection: "column", flexGrow: 1}}>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
left: "35%",
|
||||
top: "50%"
|
||||
}}>
|
||||
{"No Operations available!"}<br/>
|
||||
{"Ask a Mythic admin or operation lead to add you to an operation."}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<Table size="small" style={{"tableLayout": "fixed", "overflow": "scroll"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "2rem"}}></TableCell>
|
||||
<TableCell style={{width: "8rem"}}>Configure</TableCell>
|
||||
<TableCell style={{width: "8rem"}}>Operators</TableCell>
|
||||
<TableCell>Operation Name</TableCell>
|
||||
<TableCell>Operation Admin</TableCell>
|
||||
<TableCell style={{width: "8rem"}}>Analysis</TableCell>
|
||||
<TableCell style={{width: "11rem"}}>Operation Status</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.operations.map((op) => (
|
||||
showDeleted || !op.deleted ? (
|
||||
<OperationTableRow
|
||||
me={props.me}
|
||||
key={"operation" + op.id}
|
||||
onUpdateOperation={onUpdateOperation}
|
||||
onUpdateCurrentOperation={props.onUpdateCurrentOperation}
|
||||
updateDeleted={props.updateDeleted}
|
||||
{...op} operator={props.operator}
|
||||
/>
|
||||
) : null
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</React.Fragment>
|
||||
}
|
||||
<Table size="small" style={{"tableLayout": "fixed", "overflow": "scroll"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "2rem"}}></TableCell>
|
||||
<TableCell style={{width: "8rem"}}>Configure</TableCell>
|
||||
<TableCell style={{width: "8rem"}}>Operators</TableCell>
|
||||
<TableCell>Operation Name</TableCell>
|
||||
<TableCell>Operation Admin</TableCell>
|
||||
<TableCell style={{width: "8rem"}}>Analysis</TableCell>
|
||||
<TableCell style={{width: "11rem"}}>Operation Status</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.operations.map((op) => (
|
||||
showDeleted || !op.deleted ? (
|
||||
<OperationTableRow
|
||||
me={props.me}
|
||||
key={"operation" + op.id}
|
||||
onUpdateOperation={onUpdateOperation}
|
||||
onUpdateCurrentOperation={props.onUpdateCurrentOperation}
|
||||
updateDeleted={props.updateDeleted}
|
||||
{...op} operator={props.operator}
|
||||
/>
|
||||
) : null
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {useQuery, gql, useLazyQuery} from '@apollo/client';
|
||||
import {CommandBlockListTable} from './CommandBlockListTable';
|
||||
import { snackActions } from '../../utilities/Snackbar';
|
||||
import {useMythicLazyQuery} from "../../utilities/useMythicLazyQuery";
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
|
||||
const GET_Operations = gql`
|
||||
query GetOperations {
|
||||
@@ -114,13 +115,13 @@ export function Operations(props){
|
||||
getBlockLists().then(({data}) => getBlockListsSuccess(data)).catch(({data}) => getBlockListsError(data));
|
||||
}, []);
|
||||
return (
|
||||
<div style={{ height: "100%", display: "flex", flexDirection: "column"}}>
|
||||
<MythicPageBody>
|
||||
<OperationTable operations={operations}
|
||||
onUpdateOperation={onUpdateOperation}
|
||||
onNewOperation={onNewOperation} me={props.me}
|
||||
onUpdateCurrentOperation={onUpdateCurrentOperation}
|
||||
updateDeleted={updateDeleted}/>
|
||||
<CommandBlockListTable blockLists={blockLists} me={props.me} />
|
||||
</div>
|
||||
</MythicPageBody>
|
||||
);
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import { IconButton } from '@mui/material';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import {CustomBrowserRow} from "./CustomBrowserCard";
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
|
||||
const SUB_Payload_Types = gql`
|
||||
subscription getPayloadTypesSubscription {
|
||||
@@ -215,11 +217,8 @@ export function PayloadTypesC2Profiles({me}){
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div style={{ height: "100%", display: "flex", flexDirection: "column", width: "100%"}}>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main}} variant={"elevation"}>
|
||||
<Typography variant="h5" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
Installed Services
|
||||
</Typography>
|
||||
<MythicPageBody>
|
||||
<MythicPageHeader title={"Installed Services"}>
|
||||
{showDeleted ? (
|
||||
<MythicStyledTooltip title={"Hide Deleted Services"} tooltipStyle={{float: "right"}}>
|
||||
<IconButton size="small" style={{float: "right"}} variant="contained" onClick={() => setShowDeleted(!showDeleted)}><VisibilityIcon /></IconButton>
|
||||
@@ -230,7 +229,7 @@ export function PayloadTypesC2Profiles({me}){
|
||||
<IconButton size="small" style={{float: "right"}} variant="contained" onClick={() => setShowDeleted(!showDeleted)} ><VisibilityOffIcon /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)}
|
||||
</Paper>
|
||||
</MythicPageHeader>
|
||||
<AppBar position="static" color="default" className={"no-box-shadow"}>
|
||||
<Tabs
|
||||
value={value}
|
||||
@@ -285,7 +284,7 @@ export function PayloadTypesC2Profiles({me}){
|
||||
{
|
||||
getTabComponent()
|
||||
}
|
||||
</div>
|
||||
</MythicPageBody>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import {gql, useMutation} from '@apollo/client';
|
||||
import { snackActions } from '../../utilities/Snackbar';
|
||||
import {DragAndDropFileUpload} from "../Callbacks/TaskParametersDialogRow";
|
||||
|
||||
const create_payload = gql`
|
||||
mutation createPayloadMutation($payload: String!) {
|
||||
@@ -31,22 +32,19 @@ export function ImportPayloadConfigDialog(props) {
|
||||
createPayloadMutation({variables: {payload: fileValue.contents}}).catch( (e) => {console.log(e)} );
|
||||
props.onClose();
|
||||
}
|
||||
const onFileChange = (evt) => {
|
||||
const onFileChange = (newFile) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const contents = e.target.result;
|
||||
setFileValue({name: evt.target.files[0].name, contents: contents});
|
||||
setFileValue({name: newFile.name, contents: contents});
|
||||
}
|
||||
reader.readAsBinaryString(evt.target.files[0]);
|
||||
reader.readAsBinaryString(newFile);
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Import Payload Config to Generate New Payload</DialogTitle>
|
||||
<DialogContent dividers={true}>
|
||||
<Button variant="contained" component="label">
|
||||
{ fileValue.name === "" ? "Select File" : fileValue.name }
|
||||
<input onChange={onFileChange} type="file" hidden />
|
||||
</Button>
|
||||
<DragAndDropFileUpload value={fileValue} multiple={false} onChange={onFileChange} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={props.onClose} color="primary">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { PayloadsTable } from './PayloadsTable';
|
||||
import {useMutation, gql, useSubscription, useQuery, useLazyQuery} from '@apollo/client';
|
||||
import { snackActions } from '../../utilities/Snackbar';
|
||||
import {getSkewedNow} from "../../utilities/Time";
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
|
||||
export const payloadFragment = gql`
|
||||
fragment payloadData on payload {
|
||||
@@ -325,7 +326,7 @@ export function Payloads(props){
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
return (
|
||||
<div style={{display: "flex", height: "100%", flexDirection: "column", position: "relative"}}>
|
||||
<MythicPageBody>
|
||||
|
||||
<PayloadsTable pageData={pageData} me={props.me}
|
||||
openBackdrop={openBackdrop}
|
||||
@@ -338,7 +339,7 @@ export function Payloads(props){
|
||||
onCallbacksAllowedChanged={onCallbacksAllowedChanged}
|
||||
onChangeShowAutogenerated={onChangeShowAutogenerated}
|
||||
/>
|
||||
</div>
|
||||
</MythicPageBody>
|
||||
|
||||
);
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||
import {useNavigate} from 'react-router-dom';
|
||||
import Pagination from '@mui/material/Pagination';
|
||||
import { Backdrop, CircularProgress } from '@mui/material';
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
|
||||
|
||||
export function PayloadsTable({payload, onDeletePayload, onUpdateCallbackAlert, onRestorePayload, me,
|
||||
@@ -74,70 +75,68 @@ export function PayloadsTable({payload, onDeletePayload, onUpdateCallbackAlert,
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
return (
|
||||
<div style={{display: "flex", flexDirection: "column", height: "100%"}}>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main}} variant={"elevation"}>
|
||||
<Typography variant="h5" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
Payloads
|
||||
</Typography>
|
||||
<>
|
||||
<MythicPageHeader title={"Payloads"}>
|
||||
|
||||
<ButtonGroup variant="text" ref={dropdownAnchorRef} aria-label="split button" style={{float: "right", marginRight: "10px", color: "white"}}>
|
||||
<Button size="small" style={{color: "white"}} aria-controls={dropdownOpen ? 'split-button-menu' : undefined}
|
||||
aria-expanded={dropdownOpen ? 'true' : undefined}
|
||||
aria-haspopup="menu"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}>
|
||||
Actions <ArrowDropDownIcon />
|
||||
aria-expanded={dropdownOpen ? 'true' : undefined}
|
||||
aria-haspopup="menu"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}>
|
||||
Actions <ArrowDropDownIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Popper open={dropdownOpen} anchorEl={dropdownAnchorRef.current} role={undefined} transition disablePortal style={{zIndex: 10}}>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom',
|
||||
}}
|
||||
>
|
||||
<Paper className={"dropdownMenuColored"}>
|
||||
<ClickAwayListener onClickAway={() => setDropdownOpen(false)}>
|
||||
<MenuList id="split-button-menu">
|
||||
{dropDownOptions.map((option, index) => (
|
||||
<MenuItem
|
||||
key={option.name}
|
||||
onClick={(event) => handleMenuItemClick(event, index)}
|
||||
>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom',
|
||||
}}
|
||||
>
|
||||
<Paper className={"dropdownMenuColored"}>
|
||||
<ClickAwayListener onClickAway={() => setDropdownOpen(false)}>
|
||||
<MenuList id="split-button-menu">
|
||||
{dropDownOptions.map((option, index) => (
|
||||
<MenuItem
|
||||
key={option.name}
|
||||
onClick={(event) => handleMenuItemClick(event, index)}
|
||||
>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
{openPayloadImport &&
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={openPayloadImport}
|
||||
onClose={()=>{setOpenPayloadImport(false);}}
|
||||
innerDialog={<ImportPayloadConfigDialog onClose={()=>{setOpenPayloadImport(false);}} />}
|
||||
onClose={()=>{setOpenPayloadImport(false);}}
|
||||
innerDialog={<ImportPayloadConfigDialog onClose={()=>{setOpenPayloadImport(false);}} />}
|
||||
/>
|
||||
}
|
||||
</Paper>
|
||||
</MythicPageHeader>
|
||||
<div style={{flexGrow: 1, overflowY: "auto", height: "100%", position: "relative"}}>
|
||||
{openBackdrop &&
|
||||
<Backdrop open={openBackdrop} style={{zIndex: 2000, position: "absolute"}}>
|
||||
<CircularProgress color="inherit" disableShrink />
|
||||
</Backdrop>
|
||||
}
|
||||
<Table stickyHeader size="small" style={{ "maxWidth": "100%", "overflow": "auto", tableLayout: "fixed"}}>
|
||||
<TableHead >
|
||||
<TableRow>
|
||||
<TableCell style={{width: "6rem"}}></TableCell>
|
||||
<TableCell style={{width: "3rem"}}></TableCell>
|
||||
<TableCell>File</TableCell>
|
||||
<TableCell>Progress</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell >C2 Status</TableCell>
|
||||
<TableCell>Tags</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<Table stickyHeader size="small" style={{ "maxWidth": "100%", "overflow": "auto", tableLayout: "fixed"}}>
|
||||
<TableHead >
|
||||
<TableRow>
|
||||
<TableCell style={{width: "6rem"}}></TableCell>
|
||||
<TableCell style={{width: "3rem"}}></TableCell>
|
||||
<TableCell>File</TableCell>
|
||||
<TableCell>Progress</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell >C2 Status</TableCell>
|
||||
<TableCell>Tags</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{payload.map( (op) => (
|
||||
<PayloadsTableRow
|
||||
me={me}
|
||||
@@ -150,15 +149,16 @@ export function PayloadsTable({payload, onDeletePayload, onUpdateCallbackAlert,
|
||||
{...op}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div style={{background: "transparent", display: "flex", justifyContent: "center", alignItems: "center", paddingTop: "5px", paddingBottom: "10px"}}>
|
||||
<Pagination count={Math.ceil(pageData.totalCount / pageData.fetchLimit)} variant="outlined" color="primary" boundaryCount={1}
|
||||
siblingCount={1} onChange={onChangePage} showFirstButton={true} showLastButton={true} style={{padding: "20px"}}/>
|
||||
siblingCount={1} onChange={onChangePage} showFirstButton={true} showLastButton={true} style={{padding: "20px"}}/>
|
||||
<Typography style={{paddingLeft: "10px"}}>Total Results: {pageData.totalCount}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,12 +5,7 @@ import IconButton from '@mui/material/IconButton';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import { MythicDialog } from '../../MythicComponents/MythicDialog';
|
||||
import {DetailedPayloadComparisonTable, DetailedPayloadTable} from './DetailedPayloadTable';
|
||||
import Grow from '@mui/material/Grow';
|
||||
import Popper from '@mui/material/Popper';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import MenuList from '@mui/material/MenuList';
|
||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {MythicConfirmDialog} from '../../MythicComponents/MythicConfirmDialog';
|
||||
import {PayloadDescriptionDialog} from './PayloadDescriptionDialog';
|
||||
import {PayloadFilenameDialog} from './PayloadFilenameDialog';
|
||||
|
||||
@@ -14,7 +14,7 @@ import TableRow from '@mui/material/TableRow';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
|
||||
export function PayloadsTableRowBuildProgress(props){
|
||||
const [buildProgressData, setBuildProgressData] = React.useState({
|
||||
@@ -109,23 +109,26 @@ export function PayloadsTableRowBuildProgress(props){
|
||||
}
|
||||
}
|
||||
return (
|
||||
<span style={props.build_phase === "success" ? {
|
||||
filter: "grayscale(1)",
|
||||
opacity: 0.5} : {}}>
|
||||
{buildProgressData.total_steps > 0 &&
|
||||
<>
|
||||
<span style={props.build_phase === "success" ? {
|
||||
filter: "grayscale(1)",
|
||||
opacity: 0.5} : {}}>
|
||||
{buildProgressData.total_steps > 0 &&
|
||||
props.payload_build_steps.map( step => (
|
||||
<MythicStyledTooltip title={step.step_name} key={"buildstep" + step.step_number}>
|
||||
{getButton(step)}
|
||||
</MythicStyledTooltip>
|
||||
))
|
||||
}
|
||||
{openStatusDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="lg" open={openStatusDialog}
|
||||
onClose={()=>{setOpenStatusDialog(false);}}
|
||||
innerDialog={<PayloadBuildStepStatusDialog step={displayData} onClose={()=>{setOpenStatusDialog(false);}} />}
|
||||
/>
|
||||
}
|
||||
</span>
|
||||
</span>
|
||||
{openStatusDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="lg" open={openStatusDialog}
|
||||
onClose={()=>{setOpenStatusDialog(false);}}
|
||||
innerDialog={<PayloadBuildStepStatusDialog step={displayData} onClose={()=>{setOpenStatusDialog(false);}} />}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,44 +170,43 @@ export function PayloadBuildStepStatusDialog(props) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Step {props.step.step_number + 1} - {props.step.step_name}</DialogTitle>
|
||||
<TableContainer className="mythicElement">
|
||||
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "30%"}}>Parameter</TableCell>
|
||||
<TableCell >Value</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody style={{whiteSpace: "pre"}}>
|
||||
<TableRow hover>
|
||||
<TableCell>Step Start Time</TableCell>
|
||||
<TableCell>{props.step.start_time}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Step End Time</TableCell>
|
||||
<TableCell>{props.step.end_time}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Duration</TableCell>
|
||||
<TableCell>{duration}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell> {getStatusMessage()}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Step Output</TableCell>
|
||||
<TableCell>{props.step.step_stdout}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Step Error</TableCell>
|
||||
<TableCell>{props.step.step_stderr}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<DialogContent>
|
||||
<Table size="small" style={{ maxWidth: "100%", overflow: "scroll"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "30%"}}>Parameter</TableCell>
|
||||
<TableCell >Value</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody style={{whiteSpace: "pre"}}>
|
||||
<TableRow hover>
|
||||
<TableCell>Step Start Time</TableCell>
|
||||
<TableCell>{props.step.start_time}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Step End Time</TableCell>
|
||||
<TableCell>{props.step.end_time}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Duration</TableCell>
|
||||
<TableCell>{duration}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell> {getStatusMessage()}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Step Output</TableCell>
|
||||
<TableCell>{props.step.step_stdout}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Step Error</TableCell>
|
||||
<TableCell>{props.step.step_stderr}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={props.onClose} color="primary">
|
||||
Close
|
||||
@@ -308,16 +310,11 @@ export function PayloadsTableRowBuildProcessPerStep(props){
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
{buildProgressData.total_steps > 0 ? (
|
||||
<React.Fragment>
|
||||
{props.payload_build_steps.map( step => (
|
||||
step.step_number === props.step_number ? (
|
||||
<React.Fragment key={"buildstep" + step.step_number}>{getButton(step)}</React.Fragment>
|
||||
) : null
|
||||
|
||||
))}
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
{buildProgressData.total_steps > 0 &&
|
||||
props.payload_build_steps.map( step => (
|
||||
step.step_number === props.step_number &&
|
||||
<React.Fragment key={"buildstep" + step.step_number}>{getButton(step)}</React.Fragment>
|
||||
))}
|
||||
{openStatusDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="lg" open={openStatusDialog}
|
||||
onClose={()=>{setOpenStatusDialog(false);}}
|
||||
|
||||
@@ -3,18 +3,16 @@ import Paper from '@mui/material/Paper';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import {ReportingTable} from './ReportingTable';
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
|
||||
|
||||
export function Reporting() {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div style={{height: "100%", display: "flex", flexDirection: "column"}}>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main}} variant={"elevation"}>
|
||||
<Typography variant="h5" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
Mythic Report Generation
|
||||
</Typography>
|
||||
</Paper>
|
||||
<MythicPageBody>
|
||||
<MythicPageHeader title={"Mythic Report Generation"}>
|
||||
</MythicPageHeader>
|
||||
<ReportingTable />
|
||||
</div>
|
||||
</MythicPageBody>
|
||||
);
|
||||
}
|
||||
@@ -7,14 +7,21 @@ import 'ace-builds/src-noconflict/mode-json';
|
||||
import 'ace-builds/src-noconflict/theme-monokai';
|
||||
import 'ace-builds/src-noconflict/theme-xcode';
|
||||
import {ResponseDisplayMedia} from "../Callbacks/ResponseDisplayMedia";
|
||||
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
|
||||
|
||||
export function PreviewFileMediaDialog({agent_file_id, filename, onClose}) {
|
||||
return (
|
||||
const onClick = (e) => {
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
<MythicDraggableDialogTitle>
|
||||
Previewing <b>{filename}</b>
|
||||
</DialogTitle>
|
||||
<DialogContent style={{height: "calc(95vh)", margin: 0, padding: 0}}>
|
||||
</MythicDraggableDialogTitle>
|
||||
<DialogContent onClick={onClick} style={{height: "calc(80vh)", margin: 0, padding: 0}}>
|
||||
<ResponseDisplayMedia media={{agent_file_id, filename}} expand={true} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
|
||||
@@ -36,12 +36,12 @@ query responseQuery($search: String!, $offset: Int!, $fetchLimit: Int!, $status:
|
||||
const parameterSearch = gql`
|
||||
${taskingDataFragment}
|
||||
query parametersQuery($search: String!, $offset: Int!, $fetchLimit: Int!, $status: String!, $host: String!, $filterOperator: String!) {
|
||||
task_aggregate(distinct_on: id, order_by: {id: desc}, where: {status: {_ilike: $status},operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}]}) {
|
||||
task_aggregate(distinct_on: id, order_by: {id: desc}, where: {status: {_ilike: $status},operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {params: {_ilike: $search}}]}) {
|
||||
aggregate {
|
||||
count(columns: id)
|
||||
}
|
||||
}
|
||||
task(limit: $fetchLimit, distinct_on: id, offset: $offset, order_by: {id: desc}, where: {status: {_ilike: $status},operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}]}) {
|
||||
task(limit: $fetchLimit, distinct_on: id, offset: $offset, order_by: {id: desc}, where: {status: {_ilike: $status},operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {params: {_ilike: $search}}]}) {
|
||||
...taskData
|
||||
}
|
||||
}
|
||||
@@ -75,12 +75,12 @@ query commandQuery($search: String!, $offset: Int!, $fetchLimit: Int!, $status:
|
||||
const commandAndParameterSearch = gql`
|
||||
${taskingDataFragment}
|
||||
query parametersQuery($search: String!, $offset: Int!, $fetchLimit: Int!, $status: String!, $host: String!, $filterOperator: String!) {
|
||||
task_aggregate(distinct_on: id, order_by: {id: desc}, where: {status: {_ilike: $status}, operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {command: {cmd: {_ilike: $search}}}, {command_name: {_ilike: $search}}]}) {
|
||||
task_aggregate(distinct_on: id, order_by: {id: desc}, where: {status: {_ilike: $status}, operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {params: {_ilike: $search}}, {command: {cmd: {_ilike: $search}}}, {command_name: {_ilike: $search}}]}) {
|
||||
aggregate {
|
||||
count(columns: id)
|
||||
}
|
||||
}
|
||||
task(limit: $fetchLimit, distinct_on: id, offset: $offset, order_by: {id: desc}, where: {status: {_ilike: $status}, operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {command: {cmd: {_ilike: $search}}}, {command_name: {_ilike: $search}}]}) {
|
||||
task(limit: $fetchLimit, distinct_on: id, offset: $offset, order_by: {id: desc}, where: {status: {_ilike: $status}, operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {params: {_ilike: $search}}, {command: {cmd: {_ilike: $search}}}, {command_name: {_ilike: $search}}]}) {
|
||||
...taskData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { SettingsOperatorTable } from './SettingsOperatorTable';
|
||||
import {useMutation, useQuery, gql} from '@apollo/client';
|
||||
import { meState } from '../../../cache';
|
||||
import { snackActions } from '../../utilities/Snackbar';
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
|
||||
const GET_Operator = gql`
|
||||
query GetOperators {
|
||||
@@ -275,7 +276,7 @@ export function Settings({me}){
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", height: "100%", flexDirection: "column"}}>
|
||||
<MythicPageBody>
|
||||
<SettingsOperatorTable
|
||||
me={me}
|
||||
onViewUTCChanged={onViewUTCChanged}
|
||||
@@ -287,6 +288,6 @@ export function Settings({me}){
|
||||
onUsernameChanged={onUsernameChanged}
|
||||
onPasswordChanged={onPasswordChanged}
|
||||
operators={operators} />
|
||||
</div>
|
||||
</MythicPageBody>
|
||||
);
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import {SettingsOperatorAPITokenSearchDialog} from "./SettingsOperatorAPITokenSe
|
||||
import ForwardToInboxTwoToneIcon from '@mui/icons-material/ForwardToInboxTwoTone';
|
||||
import {InviteLinksDialog} from "./InviteLinksDialog";
|
||||
import {useLazyQuery} from '@apollo/client';
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
|
||||
|
||||
|
||||
@@ -72,23 +74,43 @@ export function SettingsOperatorTable(props){
|
||||
getGlobalSettings().then(({data}) => setInviteLinksEnabled(data.getGlobalSettings.settings["server_config"]["allow_invite_links"]));
|
||||
}, []);
|
||||
return (
|
||||
<div style={{display: "flex", flexDirection: "column", height: "100%"}}>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main}} variant={"elevation"}>
|
||||
<Typography variant="h5" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
Settings
|
||||
</Typography>
|
||||
<MythicStyledTooltip title={"Adjust Global Settings"} tooltipStyle={{float: "right", marginRight: "10px", marginLeft: "5px"}}>
|
||||
<>
|
||||
<MythicPageHeader title={"Settings"}>
|
||||
<MythicStyledTooltip title={"Manage Invite Links"} tooltipStyle={{}}>
|
||||
<IconButton size={"small"} disabled={!inviteLinksEnabled || !userIsAdmin}
|
||||
onClick={()=>{setOpenInviteLinksDialog(true);}} variant={"contained"} >
|
||||
<ForwardToInboxTwoToneIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Create new user account"} tooltipStyle={{}}>
|
||||
<IconButton size="small" onClick={()=>{setOpenNewDialog(true);}} variant="contained">
|
||||
<PersonAddIcon/>
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Create new Bot account"} tooltipStyle={{}}>
|
||||
<IconButton size={"small"}
|
||||
onClick={()=>{setOpenNewBotDialog(true);}} variant="contained">
|
||||
<SmartToyTwoToneIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Search for API Tokens"} tooltipStyle={{}} >
|
||||
<IconButton size={"small"} variant="contained"
|
||||
onClick={() => setOpenAPITokenSearch(true)}>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Adjust Global Settings"} tooltipStyle={{}}>
|
||||
<IconButton size="small" variant="contained"
|
||||
onClick={() => setOpenGlobalSettingsDialog(!openGlobalSettingsDialog)} >
|
||||
<TuneIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
{showDeleted ? (
|
||||
<MythicStyledTooltip title={"Hide Deleted Operators"} tooltipStyle={{float: "right"}}>
|
||||
<MythicStyledTooltip title={"Hide Deleted Operators"} tooltipStyle={{}}>
|
||||
<IconButton size="small" variant="contained" onClick={() => setShowDeleted(!showDeleted)}><VisibilityIcon /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) : (
|
||||
<MythicStyledTooltip title={"Show Deleted Operators"} tooltipStyle={{float: "right"}}>
|
||||
<MythicStyledTooltip title={"Show Deleted Operators"} tooltipStyle={{}}>
|
||||
<IconButton size="small" variant="contained" onClick={() => setShowDeleted(!showDeleted)} ><VisibilityOffIcon /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)}
|
||||
@@ -99,12 +121,6 @@ export function SettingsOperatorTable(props){
|
||||
onClose={closeGlobalSettingsDialog} />}
|
||||
/>
|
||||
}
|
||||
<MythicStyledTooltip title={"Search for API Tokens"} tooltipStyle={{float: "right", marginRight: "5px",}} >
|
||||
<IconButton size={"small"} variant="contained"
|
||||
onClick={() => setOpenAPITokenSearch(true)}>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
{openAPITokenSearch &&
|
||||
<MythicDialog open={openAPITokenSearch} maxWidth={"xl"} fullWidth={true}
|
||||
onClose={()=>{setOpenAPITokenSearch(false);}}
|
||||
@@ -112,23 +128,6 @@ export function SettingsOperatorTable(props){
|
||||
onClose={()=>{setOpenAPITokenSearch(false);}} />}
|
||||
/>
|
||||
}
|
||||
<MythicStyledTooltip title={"Create new Bot account"} tooltipStyle={{float: "right", marginLeft: "5px"}}>
|
||||
<IconButton size={"small"}
|
||||
onClick={()=>{setOpenNewBotDialog(true);}} variant="contained">
|
||||
<SmartToyTwoToneIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Create new user account"} tooltipStyle={{float: "right", marginLeft: "5px"}}>
|
||||
<IconButton size="small" onClick={()=>{setOpenNewDialog(true);}} variant="contained">
|
||||
<PersonAddIcon/>
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Manage Invite Links"} tooltipStyle={{float: "right", marginLeft: "5px"}}>
|
||||
<IconButton size={"small"} disabled={!inviteLinksEnabled || !userIsAdmin}
|
||||
onClick={()=>{setOpenInviteLinksDialog(true);}} variant={"contained"} >
|
||||
<ForwardToInboxTwoToneIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
{openInviteLinksDialog &&
|
||||
<MythicDialog open={openInviteLinksDialog} maxWidth={"xl"} fullWidth={true}
|
||||
onClose={()=>{setOpenInviteLinksDialog(false);}}
|
||||
@@ -151,7 +150,7 @@ export function SettingsOperatorTable(props){
|
||||
innerDialog={<SettingsBotDialog title="New Bot Account" onAccept={onSubmitNewBot} handleClose={()=>{setOpenNewBotDialog(false);}} {...props}/>}
|
||||
/>
|
||||
}
|
||||
</Paper>
|
||||
</MythicPageHeader>
|
||||
<div style={{display: "flex", flexGrow: 1, overflowY: "auto", alignItems: "flex-start"}}>
|
||||
<Table stickyHeader size="small" style={{"tableLayout": "fixed",}}>
|
||||
<TableHead >
|
||||
@@ -189,7 +188,7 @@ export function SettingsOperatorTable(props){
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -234,6 +234,18 @@ export function SettingsOperatorUIConfigDialog(props) {
|
||||
dark: isValidColor(initialPalette?.emptyFolderColor?.dark) ? initialPalette?.emptyFolderColor?.dark : operatorSettingDefaults.palette.emptyFolderColor.dark,
|
||||
light: isValidColor(initialPalette?.emptyFolderColor?.light) ? initialPalette?.emptyFolderColor?.light : operatorSettingDefaults.palette.emptyFolderColor.light,
|
||||
},
|
||||
outputBackgroundColor: {
|
||||
dark: isValidColor(initialPalette?.outputBackgroundColor?.dark) ? initialPalette?.outputBackgroundColor?.dark : operatorSettingDefaults.palette.outputBackgroundColor.dark,
|
||||
light: isValidColor(initialPalette?.outputBackgroundColor?.light) ? initialPalette?.outputBackgroundColor?.light : operatorSettingDefaults.palette.outputBackgroundColor.light,
|
||||
},
|
||||
outputTextColor: {
|
||||
dark: isValidColor(initialPalette?.outputTextColor?.dark) ? initialPalette?.outputTextColor?.dark : operatorSettingDefaults.palette.outputTextColor.dark,
|
||||
light: isValidColor(initialPalette?.outputTextColor?.light) ? initialPalette?.outputTextColor?.light : operatorSettingDefaults.palette.outputTextColor.light,
|
||||
},
|
||||
borderColor: {
|
||||
dark: isValidColor(initialPalette?.borderColor?.dark) ? initialPalette?.borderColor?.dark : operatorSettingDefaults.palette.borderColor.dark,
|
||||
light: isValidColor(initialPalette?.borderColor?.light) ? initialPalette?.borderColor?.light : operatorSettingDefaults.palette.borderColor.light,
|
||||
},
|
||||
});
|
||||
const paletteOptionsSolidColor = [
|
||||
{name: "primary", display: "Primary"},
|
||||
@@ -256,7 +268,8 @@ export function SettingsOperatorUIConfigDialog(props) {
|
||||
{name: "taskContextColor", display: "Tasking Context Generic Background Color"},
|
||||
{name: "taskContextImpersonationColor", display: "Tasking Context User Background Color"},
|
||||
{name: "taskContextExtraColor", display: "Tasking Context Extra Info Background Color"},
|
||||
{name: "emptyFolderColor", display: "Color of the empty folder icon and text in file-based browsers"}
|
||||
{name: "emptyFolderColor", display: "Color of the empty folder icon and text in file-based browsers"},
|
||||
{name: "borderColor", display: "Color of borders around elements"}
|
||||
]
|
||||
const [resumeNotifications, setResumeNotifications] = React.useState(false);
|
||||
const [_, updateSettings, clearSettings] = useSetMythicSetting();
|
||||
@@ -844,6 +857,48 @@ export function SettingsOperatorUIConfigDialog(props) {
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>Task Output Text Color</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div style={{display: "flex", width: "100%", paddingRight: "15px"}}>
|
||||
<div style={{display: "inline-block", width: "100%"}}>
|
||||
<HexColorPicker style={{width: "100%"}} color={palette?.outputTextColor?.dark} onChange={(v) => onChangePaletteColor("outputTextColor", "dark", v)}/>
|
||||
<HexColorInput color={palette?.outputTextColor?.dark} onChange={(v) => onChangePaletteColor("outputTextColor", "dark", v)}/>
|
||||
<Box sx={{width: "100%", height: 25, backgroundColor: palette?.outputBackgroundColor?.dark, display: "flex", alignItems: "center"}}>
|
||||
<Typography style={{color: palette.outputTextColor.dark, display: "inline-block"}}>Dark Mode Output Text Color</Typography>
|
||||
</Box>
|
||||
</div>
|
||||
<div style={{display: "inline-block", width: "100%"}}>
|
||||
<HexColorPicker style={{width: "100%"}} color={palette?.outputTextColor?.light} onChange={(v) => onChangePaletteColor("outputTextColor", "light", v)}/>
|
||||
<HexColorInput color={palette?.outputTextColor?.light} onChange={(v) => onChangePaletteColor("outputTextColor", "light", v)}/>
|
||||
<Box sx={{width: "100%", height: 25, backgroundColor: palette?.outputBackgroundColor?.light, display: "flex", alignItems: "center"}}>
|
||||
<Typography style={{color: palette.outputTextColor.light, display: "inline-block"}}>Light Mode Output Text Color</Typography>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>Task Output Background Color</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div style={{display: "flex", width: "100%", paddingRight: "15px"}}>
|
||||
<div style={{display: "inline-block", width: "100%"}}>
|
||||
<HexColorPicker style={{width: "100%"}} color={palette?.outputBackgroundColor?.dark} onChange={(v) => onChangePaletteColor("outputBackgroundColor", "dark", v)}/>
|
||||
<HexColorInput color={palette?.outputBackgroundColor?.dark} onChange={(v) => onChangePaletteColor("outputBackgroundColor", "dark", v)}/>
|
||||
<Box sx={{width: "100%", height: 25, backgroundColor: palette?.outputBackgroundColor?.dark, display: "flex", alignItems: "center"}}>
|
||||
<Typography style={{color: palette.outputTextColor.dark, display: "inline-block"}}>Dark Mode Output Text Color</Typography>
|
||||
</Box>
|
||||
</div>
|
||||
<div style={{display: "inline-block", width: "100%"}}>
|
||||
<HexColorPicker style={{width: "100%"}} color={palette?.outputBackgroundColor?.light} onChange={(v) => onChangePaletteColor("outputBackgroundColor", "light", v)}/>
|
||||
<HexColorInput color={palette?.outputBackgroundColor?.light} onChange={(v) => onChangePaletteColor("outputBackgroundColor", "light", v)}/>
|
||||
<Box sx={{width: "100%", height: 25, backgroundColor: palette?.outputBackgroundColor?.light, display: "flex", alignItems: "center"}}>
|
||||
<Typography style={{color: palette.outputTextColor.light, display: "inline-block"}}>Light Mode Output Text Color</Typography>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
@@ -66,11 +66,10 @@ query taskMetadataQuery($task_range: [Int!]) {
|
||||
display_id
|
||||
}
|
||||
}`;
|
||||
const lazyQueryOptions = {fetchPolicy: "no-cache"};
|
||||
export function TaskMetadataTable(props){
|
||||
const [tasks, setTasks] = React.useState([]);
|
||||
|
||||
const getMetadata = useMythicLazyQuery(MetadataQuery, {fetchPolicy: "no-cache"
|
||||
});
|
||||
const getMetadata = useMythicLazyQuery(MetadataQuery, lazyQueryOptions);
|
||||
useEffect( () => {
|
||||
getMetadata({variables: {task_range: props.taskIDs } }).then(({data}) => {
|
||||
setTasks(data.task);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {useTheme} from '@mui/material/styles';
|
||||
import {taskingDataFragment} from '../Callbacks/CallbackMutations'
|
||||
import {meState} from "../../../cache";
|
||||
import { useReactiveVar } from '@apollo/client';
|
||||
import {TaskDisplayInteractiveSearch} from "../Search/SearchTabInteractiveTasks";
|
||||
|
||||
const tasksQuery = gql`
|
||||
${taskingDataFragment}
|
||||
@@ -243,7 +244,7 @@ export function SingleTaskView(props){
|
||||
}
|
||||
}, [getTasks, taskId]);
|
||||
return (
|
||||
<div style={{height: "100%", display: "flex", flexDirection: "column", width:"100%",}}>
|
||||
<div style={{height: "100%", display: "flex", flexDirection: "column", width:"100%", overflowY: 'auto',}}>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main, marginBottom: "5px"}} variant={"elevation"}>
|
||||
<Typography variant="h4" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
Task View
|
||||
@@ -252,13 +253,17 @@ export function SingleTaskView(props){
|
||||
onClick={getShareableLink}>Get Shareable link</Button>
|
||||
<Button variant="contained" size="small" style={{display: "inline-block", float: "right", marginTop:"5px", marginRight:"10px", backgroundColor: theme.palette.warning.main}}
|
||||
onClick={removeTasksButton}>Remove Tasks From View</Button>
|
||||
|
||||
</Paper>
|
||||
{tasks.map( (task) => (
|
||||
task.type === "task" ? (
|
||||
<div style={{ display: "flex", flexDirection: "column"}}>
|
||||
{tasks.map( (task) => (
|
||||
task.type === "task" ? (
|
||||
<div key={"taskdisplay:" + task.display_id} style={{marginRight: "5px"}}>
|
||||
<div style={{width: removing ? "95%" : "100%", display: "inline-block"}}>
|
||||
<TaskDisplay me={me} task={task} command_id={task.command === null ? 0 : task.command.id} />
|
||||
{task.is_interactive_task ? (
|
||||
<TaskDisplayInteractiveSearch key={"taskinteractdisplay" + task.id} me={me} task={task} responsesSurrounding={5} />
|
||||
) : (
|
||||
<TaskDisplay key={"taskinteractdisplay" + task.id} me={me} task={task} command_id={task.command == null ? 0 : task.command.id} />
|
||||
)}
|
||||
</div>
|
||||
{removing ? (
|
||||
<Switch
|
||||
@@ -266,28 +271,31 @@ export function SingleTaskView(props){
|
||||
onChange={toggleTaskToRemove}
|
||||
name={"task" + task.display_id}
|
||||
inputProps={{ 'aria-label': 'checkbox', 'color': theme.palette.error.main }}
|
||||
/>
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<Paper key={"taskdisplayforcallback:" + task.id} elevation={5} style={{ marginBottom: "5px", marginTop: "10px"}} variant={"elevation"}>
|
||||
<Typography variant="h4" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
{task.domain === "" ? null : (task.domain + "\\")}{task.user}{task.integrity_level > 2 ? ("*") : null}@{task.host} (
|
||||
<Link style={{wordBreak: "break-all"}} color={"textPrimary"} underline="always" target="_blank" href={"/new/callbacks/" + task.display_id}>{task.display_id}</Link>
|
||||
)
|
||||
</Typography>
|
||||
<Button variant="contained" size="small" style={{display: "inline-block", float: "right", marginTop:"5px", marginRight:"10px", backgroundColor: theme.palette.info.main}}
|
||||
onClick={() => {setTaskSearchInfo(task.display_id)}}>Include More Tasks</Button>
|
||||
</Paper>
|
||||
))
|
||||
|
||||
|
||||
) : (
|
||||
<Paper key={"taskdisplayforcallback:" + task.id} elevation={5} style={{ marginBottom: "5px", marginTop: "10px"}} variant={"elevation"}>
|
||||
<Typography variant="h4" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
{task.domain === "" ? null : (task.domain + "\\")}{task.user}{task.integrity_level > 2 ? ("*") : null}@{task.host} (
|
||||
<Link style={{wordBreak: "break-all"}} color={"textPrimary"} underline="always" target="_blank" href={"/new/callbacks/" + task.display_id}>{task.display_id}</Link>
|
||||
)
|
||||
</Typography>
|
||||
<Button variant="contained" size="small" style={{display: "inline-block", float: "right", marginTop:"5px", marginRight:"10px", backgroundColor: theme.palette.info.main}}
|
||||
onClick={() => {setTaskSearchInfo(task.display_id)}}>Include More Tasks</Button>
|
||||
</Paper>
|
||||
))
|
||||
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{openIncludeMoreTasksDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openIncludeMoreTasksDialog}
|
||||
onClose={()=>{setOpenIncludeMoreTasksDialog(false);}}
|
||||
innerDialog={<IncludeMoreTasksDialog submitFetchTasks={submitIncludeMoreTasks} taskOptions={taskOptions} onClose={()=>{setOpenIncludeMoreTasksDialog(false);}} />}
|
||||
/>
|
||||
}
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openIncludeMoreTasksDialog}
|
||||
onClose={()=>{setOpenIncludeMoreTasksDialog(false);}}
|
||||
innerDialog={<IncludeMoreTasksDialog submitFetchTasks={submitIncludeMoreTasks} taskOptions={taskOptions} onClose={()=>{setOpenIncludeMoreTasksDialog(false);}} />}
|
||||
/>
|
||||
<TaskMetadataTable taskIDs={taskIDs} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { TagtypesTable } from './TagtypesTable';
|
||||
import {useMutation, gql, useQuery} from '@apollo/client';
|
||||
import { snackActions } from '../../utilities/Snackbar';
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
const tagtypeFragment = gql`
|
||||
fragment tagtypeData on tagtype {
|
||||
id
|
||||
@@ -89,8 +90,8 @@ export function Tags(props){
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
return (
|
||||
<div style={{display: "flex", height: "100%", flexDirection: "column"}}>
|
||||
<MythicPageBody>
|
||||
<TagtypesTable onDeleteTagtype={onDeleteTagtype} tagtypes={tagtypes} onNewTag={onNewTag} onUpdateTagtype={onUpdateTagtype} me={props.me}/>
|
||||
</div>
|
||||
</MythicPageBody>
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import FileDownloadIcon from '@mui/icons-material/FileDownload';
|
||||
import AddCircleIcon from '@mui/icons-material/AddCircle';
|
||||
import {downloadFileFromMemory} from '../../utilities/Clipboard';
|
||||
import {useMythicLazyQuery} from "../../utilities/useMythicLazyQuery";
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
|
||||
const importTagtypesMutation = gql`
|
||||
mutation importMultipleTagtypes($tagtypes: String!) {
|
||||
@@ -83,11 +84,8 @@ export function TagtypesTable({tagtypes, onDeleteTagtype, onNewTag, onUpdateTagt
|
||||
.then(({data}) => exportTagTypesSuccess(data)).catch(({data}) => exportTagTypesError(data));
|
||||
}
|
||||
return (
|
||||
<div style={{display: "flex", flexDirection: "column", width: "100%", height: "100%"}}>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main}} variant={"elevation"}>
|
||||
<Typography variant="h5" style={{textAlign: "left", display: "inline-block", marginLeft: "20px"}}>
|
||||
Types of Tags
|
||||
</Typography>
|
||||
<>
|
||||
<MythicPageHeader title={"Types of Tags"}>
|
||||
<Button component="label" size={"small"}
|
||||
style={{float:"right", marginRight: "5px", color: "white"}} >
|
||||
<FileUploadIcon /> {"Import"}
|
||||
@@ -105,7 +103,7 @@ export function TagtypesTable({tagtypes, onDeleteTagtype, onNewTag, onUpdateTagt
|
||||
innerDialog={<NewTagtypesDialog onClose={()=>{setOpenNewDialog(false);}} onSubmit={onNewTag} />}
|
||||
/>
|
||||
}
|
||||
</Paper>
|
||||
</MythicPageHeader>
|
||||
<div style={{display: "flex", flexGrow: 1, overflow: "auto"}}>
|
||||
<TableContainer className="mythicElement">
|
||||
<Table stickyHeader={true} size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
@@ -132,7 +130,7 @@ export function TagtypesTable({tagtypes, onDeleteTagtype, onNewTag, onUpdateTagt
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const useDarkMode = () => {
|
||||
const localTheme = window.localStorage.getItem('theme');
|
||||
if(localTheme){setTheme(localTheme)}
|
||||
else{
|
||||
const checkIsDarkSchemePreferred = () => window?.matchMedia?.('(prefers-color-scheme:dark)')?.matches || false;
|
||||
const checkIsDarkSchemePreferred = () => window?.matchMedia?.('(prefers-color-scheme:dark)')?.matches || true;
|
||||
if(checkIsDarkSchemePreferred()){
|
||||
setMode('dark');
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {jwtDecode} from 'jwt-decode';
|
||||
import {meState} from './cache';
|
||||
import {getSkewedNow} from "./components/utilities/Time";
|
||||
|
||||
export const mythicUIVersion = "0.3.90";
|
||||
export const mythicUIVersion = "0.3.114";
|
||||
|
||||
let fetchingNewToken = false;
|
||||
|
||||
|
||||
@@ -34,17 +34,17 @@ html, body, #root {
|
||||
.MuiTableCell-head {
|
||||
background-color: ${(props) => props.theme.tableHeader} !important;
|
||||
font-weight: bold;
|
||||
border-top: 2px solid grey;
|
||||
border-bottom: 2px solid grey;
|
||||
border-top: 2px solid ${(props) => props.theme.borderColor};
|
||||
border-bottom: 2px solid ${(props) => props.theme.borderColor};
|
||||
}
|
||||
tr:nth-child(even) {
|
||||
background-color: ${(props) => props.theme.palette.mode === 'dark' ? props.theme.tableHover + "0D" : props.theme.tableHover + "80"};
|
||||
background-color: ${(props) => props.theme.palette.mode === 'dark' ? props.theme.tableHover + "80" : props.theme.tableHover + "80"};
|
||||
}
|
||||
.alternateRow {
|
||||
background-color: ${(props) => props.theme.palette.mode === 'dark' ? props.theme.tableHover + "0D" : props.theme.tableHover + "80"};
|
||||
}
|
||||
.MythicResizableGridRowHighlight {
|
||||
background-color: ${(props) => props.theme.palette.mode === 'dark' ? props.theme.tableHover + "0D" : props.theme.tableHover + "80"};
|
||||
background-color: ${(props) => props.theme.palette.mode === 'dark' ? props.theme.tableHover + "60" : props.theme.tableHover + "10"};
|
||||
}
|
||||
.MuiTableRow-hover {
|
||||
&:hover,
|
||||
@@ -146,13 +146,13 @@ tspan {
|
||||
opacity: 1;
|
||||
}
|
||||
.groupNode {
|
||||
border: 1px solid grey;
|
||||
border: 1px solid ${(props) => props.theme.borderColor};
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background: ${(props) => props.theme.palette.graphGroupRGBA} !important;
|
||||
}
|
||||
.groupEventNode {
|
||||
border: 1px solid grey;
|
||||
border: 1px solid ${(props) => props.theme.borderColor};
|
||||
padding: 2px;
|
||||
border-radius: 5px;
|
||||
background: ${(props) => props.theme.palette.graphGroupRGBA} !important;
|
||||
@@ -167,7 +167,7 @@ tspan {
|
||||
}
|
||||
.context-menu {
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
border: 1px solid grey;
|
||||
border: 1px solid ${(props) => props.theme.borderColor};
|
||||
border-radius: 5px;
|
||||
box-shadow: 10px 19px 20px rgba(0, 0, 0, 10%);
|
||||
position: absolute;
|
||||
@@ -212,11 +212,11 @@ tspan {
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
user-select: none;
|
||||
border-top: 2px solid grey;
|
||||
border-bottom: 2px solid grey;
|
||||
border-top: 2px solid ${(props) => props.theme.borderColor};
|
||||
border-bottom: 2px solid ${(props) => props.theme.borderColor};
|
||||
background-color: ${(props) => props.theme.tableHeader} !important;
|
||||
&:first-child-of-type {
|
||||
border-left: 2px solid grey;
|
||||
border-left: 2px solid ${(props) => props.theme.borderColor};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.tableHover};
|
||||
@@ -232,7 +232,7 @@ tspan {
|
||||
padding: 0 0.5em;
|
||||
box-sizing: border-box;
|
||||
font-family: monospace;
|
||||
border-bottom: 1px solid grey;
|
||||
border-bottom: 1px solid ${(props) => props.theme.borderColor};
|
||||
cursor: default !important;
|
||||
}
|
||||
.MythicResizableGrid-cellInner {
|
||||
@@ -276,7 +276,7 @@ tspan {
|
||||
width: 100%;
|
||||
}
|
||||
.MuiPaper-root {
|
||||
border: 1px solid grey;
|
||||
border: 1px solid ${(props) => props.theme.borderColor};
|
||||
border-radius: 5px;
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
background-image: unset;
|
||||
@@ -290,13 +290,13 @@ tspan {
|
||||
|
||||
.MuiList-root {
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
border: 1px solid grey;
|
||||
border: 1px solid ${(props) => props.theme.borderColor};
|
||||
border-radius: 3px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.dropdownMenuColored {
|
||||
background-color: ${(props) => props.theme.palette.background.paper} !important;
|
||||
border: 1px solid grey;
|
||||
border: 1px solid ${(props) => props.theme.borderColor};
|
||||
border-radius: 5px;
|
||||
//color: white;
|
||||
}
|
||||
@@ -331,25 +331,25 @@ tspan {
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.MuiInputLabel-root {
|
||||
color: grey !important;
|
||||
color: ${(props) => props.theme.borderColor} !important;
|
||||
}
|
||||
.MuiOutlinedInput-notchedOutline {
|
||||
border-color: grey !important;
|
||||
border-color: ${(props) => props.theme.borderColor} !important;
|
||||
}
|
||||
.MuiInput-underline {
|
||||
border-color: grey !important;
|
||||
border-color: ${(props) => props.theme.borderColor} !important;
|
||||
}
|
||||
.MuiSelect-outlined {
|
||||
border-color: grey !important;
|
||||
border-color: ${(props) => props.theme.borderColor} !important;
|
||||
}
|
||||
.Mui-focused {
|
||||
border-color: grey !important;
|
||||
border-color: ${(props) => props.theme.borderColor} !important;
|
||||
}
|
||||
.MuiInputBase-input {
|
||||
border-color: grey !important;
|
||||
border-color: ${(props) => props.theme.borderColor} !important;
|
||||
}
|
||||
.MuiInput-root::after {
|
||||
border-color: grey !important;
|
||||
border-color: ${(props) => props.theme.borderColor} !important;
|
||||
}
|
||||
.MuiTableCell-root {
|
||||
padding: 0 16px 0 16px;
|
||||
@@ -372,34 +372,36 @@ tspan {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.ace_editor{
|
||||
background-color: ${(props) => props.theme.palette.background.default + "80"};
|
||||
background-color: ${(props) => props.theme.outputBackgroundColor + "20"};
|
||||
}
|
||||
.ace_gutter {
|
||||
//background: transparent !important;
|
||||
background-color: ${(props) => props.theme.outputBackgroundColor + "20"} !important;
|
||||
color: ${(props) => props.theme.outputTextColor} !important;
|
||||
}
|
||||
.ace_editor .ace_text-layer {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
color: ${(props) => props.theme.outputTextColor};
|
||||
}
|
||||
.rounded-tab {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
padding: 0 10px 0 10px;
|
||||
border-top: 1px solid grey;
|
||||
border-left: 2px solid grey;
|
||||
border-right: 1px solid grey;
|
||||
border-bottom: 1px solid grey;
|
||||
border-top: 1px solid ${(props) => props.theme.borderColor};
|
||||
border-left: 2px solid ${(props) => props.theme.borderColor};
|
||||
border-right: 1px solid ${(props) => props.theme.borderColor};
|
||||
border-bottom: 1px solid ${(props) => props.theme.borderColor};
|
||||
position: relative;
|
||||
top: 2px;
|
||||
word-break: break-all;
|
||||
}
|
||||
.empty-table-header {
|
||||
border: 1px solid grey !important;
|
||||
border: 1px solid ${(props) => props.theme.borderColor} !important;
|
||||
height: 0px !important;
|
||||
padding: 0px !important;
|
||||
margin: 0px !important;
|
||||
}
|
||||
.code-box {
|
||||
border: 1px solid grey !important;
|
||||
border: 1px solid ${(props) => props.theme.borderColor} !important;
|
||||
padding: 0px 5px 0px 5px;
|
||||
overflow: auto;
|
||||
white-space: pre;
|
||||
|
||||
@@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.3.26] - 2026-03-16
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated the docker SDK version
|
||||
|
||||
## [0.3.25] - 2026-01-14
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `postgres_debug` to not add in prometheus, grafana, and postgres-exporter
|
||||
- this will get added back later with better integrations and configurations
|
||||
- updated `postgres_debug` to use postgres_debug.conf instead of postgres.conf
|
||||
- updated to allow `additional_services` keyword in `config.json` installs
|
||||
- This is a `map[string]string` that identifies additional services to install
|
||||
- for example: `"additional_services": {"http": "https://github.com/MythicC2Profiles/http" }`
|
||||
- if you want to specify a specific branch, do: `"additional_services": {"http": "https://github.com/MythicC2Profiles/http branchName" }`
|
||||
- you can also specify a local path instead of a GitHub URL
|
||||
|
||||
## [0.3.24] - 2026-01-02
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove the automatic image pruning and moved it to just the `remove_images` command
|
||||
|
||||
## [0.3.23] - 2025-12-30
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#FROM itsafeaturemythic/mythic_go_base:latest
|
||||
FROM golang:1.21-alpine
|
||||
FROM golang:1.25-alpine
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
ARG GOPROXY=proxy.golang.org
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
BINARY_NAME=mythic-cli
|
||||
LOCAL_PATH=$(shell pwd)
|
||||
BUILDER_IMAGE=ghcr.io/its-a-feature/mythic_cli:v3.4.0.31
|
||||
BUILDER_IMAGE=ghcr.io/its-a-feature/mythic_cli:v3.4.0.61
|
||||
.PHONY: default
|
||||
default: build_linux ;
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@ package config
|
||||
|
||||
// MythicDockerLatest is the most recent tagged version pushed to GitHub packages
|
||||
|
||||
const MythicDockerLatest = "v3.4.0.31"
|
||||
const MythicDockerLatest = "v3.4.0.61"
|
||||
|
||||
@@ -24,9 +24,6 @@ var MythicPossibleServices = []string{
|
||||
"mythic_graphql",
|
||||
"mythic_documentation",
|
||||
"mythic_jupyter",
|
||||
"mythic_grafana",
|
||||
"mythic_prometheus",
|
||||
"mythic_postgres_exporter",
|
||||
}
|
||||
var mythicEnv = viper.New()
|
||||
var mythicEnvInfo = make(map[string]string)
|
||||
@@ -72,18 +69,21 @@ func GetIntendedMythicServiceNames() ([]string, error) {
|
||||
if mythicEnv.GetString("JUPYTER_HOST") == "127.0.0.1" || mythicEnv.GetString("JUPYTER_HOST") == "mythic_jupyter" {
|
||||
containerList = append(containerList, service)
|
||||
}
|
||||
case "mythic_grafana":
|
||||
if mythicEnv.GetBool("postgres_debug") {
|
||||
containerList = append(containerList, service)
|
||||
}
|
||||
case "mythic_prometheus":
|
||||
if mythicEnv.GetBool("postgres_debug") {
|
||||
containerList = append(containerList, service)
|
||||
}
|
||||
case "mythic_postgres_exporter":
|
||||
if mythicEnv.GetBool("postgres_debug") {
|
||||
containerList = append(containerList, service)
|
||||
}
|
||||
/*
|
||||
case "mythic_grafana":
|
||||
if mythicEnv.GetBool("postgres_debug") {
|
||||
containerList = append(containerList, service)
|
||||
}
|
||||
case "mythic_prometheus":
|
||||
if mythicEnv.GetBool("postgres_debug") {
|
||||
containerList = append(containerList, service)
|
||||
}
|
||||
case "mythic_postgres_exporter":
|
||||
if mythicEnv.GetBool("postgres_debug") {
|
||||
containerList = append(containerList, service)
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
return containerList, nil
|
||||
@@ -110,6 +110,9 @@ func setMythicConfigDefaultValues() {
|
||||
mythicEnv.SetDefault("global_restart_policy", "always")
|
||||
mythicEnvInfo["global_restart_policy"] = `This sets the restart policy for the containers within Mythic. Valid options should only be 'always', 'unless-stopped', and 'on-failure'. The default of 'always' will ensure that Mythic comes back up even when the server reboots. The 'unless-stopped' value means that Mythic should come back online after reboot unless you specifically ran './mythic-cli stop' first.`
|
||||
|
||||
mythicEnv.SetDefault("mythic_docker_image_mirror", "")
|
||||
mythicEnvInfo["mythic_docker_image_mirror"] = `This sets an internal Docker registry to pull images from instead of their original upstream registries. When empty (the default), every image is pulled from its original location (for example, "ghcr.io/its-a-feature/mythic_server:<global_docker_latest>" for core services, or whatever URL is declared in each installed agent's/C2 profile's config.json "remote_images" entry). When set to a non-empty value, the leading registry host of every image is replaced with this value while the rest of the path is preserved. For example, with mythic_docker_image_mirror="myregistry.local/mirror", the core image "ghcr.io/its-a-feature/mythic_server:0.0.5" becomes "myregistry.local/mirror/its-a-feature/mythic_server:0.0.5" and an agent image "ghcr.io/mythicagents/apollo:latest" becomes "myregistry.local/mirror/mythicagents/apollo:latest". The same rewrite is applied every time the docker-compose file is generated, so this can be changed and applied with start/build without re-installing services. This setting has no effect on services where *_use_build_context is true. Images without a registry host (for example, "nginx:latest") are treated as having no host and the full path is preserved under the mirror.`
|
||||
|
||||
// nginx configuration ---------------------------------------------
|
||||
mythicEnv.SetDefault("nginx_port", 7443)
|
||||
mythicEnvInfo["nginx_port"] = `This sets the port used for the Nginx reverse proxy - this port is used by the React UI and Mythic's Scripting`
|
||||
@@ -268,6 +271,9 @@ If it's anything else, mythic-cli will not spin up this container as it assumes
|
||||
mythicEnv.SetDefault("postgres_password", utils.GenerateRandomPassword(30))
|
||||
mythicEnvInfo["postgres_password"] = `This is the randomly generated password that mythic_server and mythic_graphql use to connect to the mythic_postgres container`
|
||||
|
||||
mythicEnv.SetDefault("postgres_sslmode", "disable")
|
||||
mythicEnvInfo["postgres_sslmode"] = `This specifies the SSL mode used when connecting to the PostgreSQL database (e.g. disable, require, verify-ca, verify-full)`
|
||||
|
||||
mythicEnv.SetDefault("postgres_cpus", defaultNumberOfCPUs)
|
||||
mythicEnvInfo["postgres_cpus"] = `Set this to limit the maximum number of CPUs this service is able to consume`
|
||||
|
||||
@@ -678,7 +684,7 @@ func GetConfigHelp(entries []string) map[string]string {
|
||||
} else if strings.HasSuffix(setting, "use_build_context") {
|
||||
output[setting] = `This setting determines if you use the pre-built image hosted on GitHub/DockerHub or if you use your local InstalledService/[agent]/Dockerfile to build a new local image for your container. If you're wanting to make changes to an agent or c2 profile (adding commands, updating code, etc), then you need to set this to 'true' and update your Dockerfile to copy in your modified code. If your container code is Golang, then you'll also need to make sure your modified Dockerfile rebuilds that Go code based on your changes (probably with a 'go build' or 'make build' command depending on the agent). If your container code is Python, then simply copying in the changes should be sufficient. Also make sure you change [agent]_use_volume to 'false' so that your changes don't get overwritten by an old volume. Alternatively, you could remove the old volume with './mythic-cli volume rm [agent]_volume' and then build your new container with './mythic-cli build [agent]'.`
|
||||
} else if strings.HasSuffix(setting, "remote_image") {
|
||||
output[setting] = `This setting configures the remote image to use if you have *_use_build_context set to false. This value should get automatically updated by the agent/c2 profile's repo as new releases are created. This value will also get updated each time you install an agent. So if you want to pull an agent's latest image, just re-install the agent (or manually update this value and restart the local container).`
|
||||
output[setting] = `This setting configures the remote image to use if you have *_use_build_context set to false. This value should get automatically updated by the agent/c2 profile's repo as new releases are created. This value will also get updated each time you install an agent. So if you want to pull an agent's latest image, just re-install the agent (or manually update this value and restart the local container). When mythic_docker_image_mirror is set, this value is kept as the upstream image URL but the image actually pulled is rewritten to come from the mirror; clear the mirror to pull from the URL shown here as-is.`
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ package config
|
||||
|
||||
var (
|
||||
// Version Mythic CLI version
|
||||
Version = "v0.3.23"
|
||||
Version = "v0.3.26"
|
||||
)
|
||||
@@ -1,11 +1,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/config"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/internal"
|
||||
"github.com/spf13/cobra"
|
||||
"log"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/config"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/internal"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/manager"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// installCmd represents the config command
|
||||
@@ -40,10 +44,23 @@ func installFolder(cmd *cobra.Command, args []string) {
|
||||
if !keepVolume {
|
||||
localKeepVolume = !config.GetMythicEnv().GetBool("REBUILD_ON_START")
|
||||
}
|
||||
if err := internal.InstallFolder(args[0], force, localKeepVolume, ""); err != nil {
|
||||
err, additionalServices := internal.InstallFolder(args[0], force, localKeepVolume, "")
|
||||
if err != nil {
|
||||
log.Printf("[-] Failed to install service: %v\n", err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
log.Printf("[+] Successfully installed service!\n")
|
||||
}
|
||||
log.Printf("[+] Successfully installed service!\n")
|
||||
if len(additionalServices) > 0 {
|
||||
log.Printf("[*] Attempting to install additional services.\n")
|
||||
}
|
||||
for serviceName, servicePath := range additionalServices {
|
||||
existing3rdParty, _ := manager.GetManager().GetAllInstalled3rdPartyServiceNames()
|
||||
if !slices.Contains(existing3rdParty, serviceName) && (force || config.AskConfirm("[*] Would you like to install "+serviceName+"? ")) {
|
||||
if strings.HasPrefix(servicePath, "http") || strings.HasPrefix(servicePath, "git@") {
|
||||
installGitHub(cmd, strings.Split(servicePath, " "))
|
||||
} else {
|
||||
installFolder(cmd, []string{servicePath})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/config"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/internal"
|
||||
"github.com/spf13/cobra"
|
||||
"log"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/config"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/internal"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/manager"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// installCmd represents the config command
|
||||
@@ -51,10 +55,23 @@ func installGitHub(cmd *cobra.Command, args []string) {
|
||||
if !keepVolume {
|
||||
localKeepVolume = !config.GetMythicEnv().GetBool("REBUILD_ON_START")
|
||||
}
|
||||
if err := internal.InstallService(args[0], branch, force, localKeepVolume); err != nil {
|
||||
err, additionalServices := internal.InstallService(args[0], branch, force, localKeepVolume)
|
||||
if err != nil {
|
||||
log.Printf("[-] Failed to install service: %v\n", err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
log.Printf("[+] Successfully installed service!\n")
|
||||
}
|
||||
log.Printf("[+] Successfully installed service!\n")
|
||||
if len(additionalServices) > 0 {
|
||||
log.Printf("[*] Attempting to install additional services.\n")
|
||||
}
|
||||
for serviceName, servicePath := range additionalServices {
|
||||
existing3rdParty, _ := manager.GetManager().GetAllInstalled3rdPartyServiceNames()
|
||||
if !slices.Contains(existing3rdParty, serviceName) && (force || config.AskConfirm("[*] Would you like to install "+serviceName+"? ")) {
|
||||
if strings.HasPrefix(servicePath, "http") || strings.HasPrefix(servicePath, "git@") {
|
||||
installGitHub(cmd, strings.Split(servicePath, " "))
|
||||
} else {
|
||||
installFolder(cmd, []string{servicePath})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,20 @@ package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/config"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/manager"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/utils"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/config"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/manager"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/utils"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func InstallFolder(installPath string, overWrite bool, keepVolume bool, installURL string) error {
|
||||
func InstallFolder(installPath string, overWrite bool, keepVolume bool, installURL string) (error, map[string]string) {
|
||||
installLocation := installPath
|
||||
if installURL != "" {
|
||||
installLocation = installURL
|
||||
@@ -31,13 +32,16 @@ func InstallFolder(installPath string, overWrite bool, keepVolume bool, installU
|
||||
if err := installConfig.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||
log.Printf("[-] Error while reading in installConfig file: %s", err)
|
||||
return err
|
||||
return err, nil
|
||||
} else {
|
||||
log.Printf("[-] Error while parsing installConfig file: %s", err)
|
||||
return err
|
||||
return err, nil
|
||||
}
|
||||
}
|
||||
latestVersion := installConfig.GetStringMapString("remote_images")
|
||||
if mirror := config.GetMythicEnv().GetString("mythic_docker_image_mirror"); mirror != "" {
|
||||
log.Printf("[*] mythic_docker_image_mirror is set to %q; this service's image(s) will be pulled from there instead of their upstream registry\n", mirror)
|
||||
}
|
||||
for key, val := range latestVersion {
|
||||
config.SetNewConfigStrings(fmt.Sprintf("%s_remote_image", key), val)
|
||||
if !config.GetMythicEnv().InConfig(fmt.Sprintf("%s_use_volume", key)) {
|
||||
@@ -47,12 +51,13 @@ func InstallFolder(installPath string, overWrite bool, keepVolume bool, installU
|
||||
config.SetNewConfigStrings(fmt.Sprintf("%s_use_build_context", key), "false")
|
||||
}
|
||||
}
|
||||
additionalServices := installConfig.GetStringMapString("additional_services")
|
||||
if !installConfig.GetBool("exclude_payload_type") {
|
||||
// handle the payload type copying here
|
||||
files, err := ioutil.ReadDir(filepath.Join(installPath, "Payload_Type"))
|
||||
if err != nil {
|
||||
log.Printf("[-] Failed to list contents of new Payload_Type folder: %v\n", err)
|
||||
return err
|
||||
return err, additionalServices
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
@@ -62,9 +67,10 @@ func InstallFolder(installPath string, overWrite bool, keepVolume bool, installU
|
||||
if overWrite || config.AskConfirm("[*] "+f.Name()+" already exists. Replace current version? ") {
|
||||
log.Printf("[*] Stopping current container\n")
|
||||
if manager.GetManager().IsServiceRunning(strings.ToLower(f.Name())) {
|
||||
if err := ServiceStop([]string{f.Name()}, keepVolume); err != nil {
|
||||
err = ServiceStop([]string{f.Name()}, keepVolume)
|
||||
if err != nil {
|
||||
log.Printf("[-] Failed to stop current container: %v\n", err)
|
||||
return err
|
||||
return err, additionalServices
|
||||
}
|
||||
}
|
||||
log.Printf("[*] Removing current version\n")
|
||||
@@ -73,9 +79,8 @@ func InstallFolder(installPath string, overWrite bool, keepVolume bool, installU
|
||||
log.Printf("[-] Failed to remove current version: %v\n", err)
|
||||
log.Printf("[-] Continuing to the next payload\n")
|
||||
continue
|
||||
} else {
|
||||
log.Printf("[+] Successfully removed the current version\n")
|
||||
}
|
||||
log.Printf("[+] Successfully removed the current version\n")
|
||||
} else {
|
||||
log.Printf("[!] Skipping Payload Type, %s\n", f.Name())
|
||||
continue
|
||||
@@ -122,7 +127,7 @@ func InstallFolder(installPath string, overWrite bool, keepVolume bool, installU
|
||||
files, err := ioutil.ReadDir(filepath.Join(installPath, "C2_Profiles"))
|
||||
if err != nil {
|
||||
log.Printf("[-] Failed to list contents of C2_Profiles folder from clone\n")
|
||||
return err
|
||||
return err, additionalServices
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
@@ -131,9 +136,9 @@ func InstallFolder(installPath string, overWrite bool, keepVolume bool, installU
|
||||
if overWrite || config.AskConfirm("[*] "+f.Name()+" already exists. Replace current version? ") {
|
||||
log.Printf("[*] Stopping current container\n")
|
||||
if manager.GetManager().IsServiceRunning(strings.ToLower(f.Name())) {
|
||||
if err := ServiceStop([]string{f.Name()}, keepVolume); err != nil {
|
||||
if err = ServiceStop([]string{f.Name()}, keepVolume); err != nil {
|
||||
log.Printf("[-] Failed to stop container: %v\n", err)
|
||||
return err
|
||||
return err, additionalServices
|
||||
}
|
||||
}
|
||||
log.Printf("[*] Removing current version\n")
|
||||
@@ -332,7 +337,7 @@ func InstallFolder(installPath string, overWrite bool, keepVolume bool, installU
|
||||
manager.GetManager().BuildServices([]string{"mythic_documentation"}, true)
|
||||
//ServiceStart([]string{"mythic_documentation"}, true)
|
||||
}
|
||||
return nil
|
||||
return nil, additionalServices
|
||||
}
|
||||
func GitClone(url string, branch string) (string, error) {
|
||||
workingPath := utils.GetCwdFromExe()
|
||||
@@ -359,7 +364,7 @@ func RemoveGitClone() error {
|
||||
workingPath := utils.GetCwdFromExe()
|
||||
return os.RemoveAll(filepath.Join(workingPath, "tmp"))
|
||||
}
|
||||
func InstallService(url string, branch string, overWrite bool, keepVolume bool) error {
|
||||
func InstallService(url string, branch string, overWrite bool, keepVolume bool) (error, map[string]string) {
|
||||
// make our temp directory to clone into
|
||||
workingPath := utils.GetCwdFromExe()
|
||||
log.Printf("[*] Creating temporary directory\n")
|
||||
@@ -367,14 +372,14 @@ func InstallService(url string, branch string, overWrite bool, keepVolume bool)
|
||||
err := os.RemoveAll(filepath.Join(workingPath, "tmp"))
|
||||
if err != nil {
|
||||
log.Printf("[-] tmp directory couldn't be deleted for a fresh install: %v\n", err)
|
||||
return err
|
||||
return err, nil
|
||||
}
|
||||
}
|
||||
err := os.Mkdir(filepath.Join(workingPath, "tmp"), 0755)
|
||||
defer os.RemoveAll(filepath.Join(workingPath, "tmp"))
|
||||
if err != nil {
|
||||
log.Printf("[-] Failed to make temp directory for cloning: %v\n", err)
|
||||
return err
|
||||
return err, nil
|
||||
}
|
||||
installURL := url
|
||||
if branch == "" {
|
||||
@@ -386,7 +391,7 @@ func InstallService(url string, branch string, overWrite bool, keepVolume bool)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("[-] Failed to clone down repository: %v\n", err)
|
||||
return err
|
||||
return err, nil
|
||||
}
|
||||
if branch == "" {
|
||||
fileContents, err := os.ReadFile(filepath.Join(workingPath, "tmp", ".git", "HEAD"))
|
||||
@@ -405,21 +410,21 @@ func InstallService(url string, branch string, overWrite bool, keepVolume bool)
|
||||
}
|
||||
installURL = fmt.Sprintf("%s;%s", url, branch)
|
||||
|
||||
if err = InstallFolder(filepath.Join(workingPath, "tmp"), overWrite, keepVolume, installURL); err != nil {
|
||||
err, additionalServices := InstallFolder(filepath.Join(workingPath, "tmp"), overWrite, keepVolume, installURL)
|
||||
if err != nil {
|
||||
log.Printf("[-] Failed to install: %v\n", err)
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
return err, additionalServices
|
||||
}
|
||||
return nil, additionalServices
|
||||
}
|
||||
func installServiceByName(service string) error {
|
||||
func installServiceByName(service string) (error, map[string]string) {
|
||||
// just have a service name, check MythicAgents, MythicC2Profiles, or error
|
||||
agentURL := fmt.Sprintf("https://github.com/MythicAgents/%s", service)
|
||||
c2URL := fmt.Sprintf("https://github.com/MythicC2Profiles/%s", service)
|
||||
if err := runGitLsRemote([]string{"lfs-remote", agentURL, "HEAD"}); err != nil {
|
||||
if err = runGitLsRemote([]string{"lfs-remote", c2URL, "HEAD"}); err != nil {
|
||||
log.Printf("[-] Failed to find an agent or c2 profile by that name")
|
||||
return err
|
||||
return err, nil
|
||||
} else {
|
||||
// this exists as a c2 profile repo, so we can pull that down
|
||||
return InstallService(c2URL, "", true, false)
|
||||
@@ -502,7 +507,6 @@ func UninstallService(services []string) {
|
||||
}
|
||||
if manager.GetManager().IsServiceRunning("mythic_documentation") {
|
||||
log.Printf("[*] Restarting mythic_documentation container to pull in changes\n")
|
||||
ServiceStop([]string{"mythic_documentation"}, true)
|
||||
ServiceStart([]string{"mythic_documentation"}, true)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -83,13 +83,18 @@ func ServiceStart(containers []string, keepVolume bool) error {
|
||||
log.Printf("[-] Failed to start services: %v", err)
|
||||
return err
|
||||
}
|
||||
err = manager.GetManager().RemoveImages()
|
||||
if err != nil {
|
||||
log.Printf("[-] Failed to remove images\n%v\n", err)
|
||||
return err
|
||||
}
|
||||
updateNginxBlockLists()
|
||||
generateCerts()
|
||||
// nginx-specific housekeeping only applies when mythic_nginx runs locally
|
||||
if isServiceInternal("mythic_nginx") {
|
||||
updateNginxBlockLists()
|
||||
if err := generateCerts(); err != nil {
|
||||
log.Printf("[-] Failed to generate certs: %v\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
TestMythicRabbitmqConnection()
|
||||
TestMythicConnection()
|
||||
Status(false)
|
||||
|
||||
@@ -44,7 +44,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
pStruct["image"] = service
|
||||
} else {
|
||||
pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest"))
|
||||
pStruct["image"] = applyImageMirror(fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")), mythicEnv.GetString("mythic_docker_image_mirror"))
|
||||
}
|
||||
|
||||
pStruct["cpus"] = mythicEnv.GetInt("POSTGRES_CPUS")
|
||||
@@ -67,16 +67,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
} else {
|
||||
delete(pStruct, "ports")
|
||||
pStruct["network_mode"] = "host"
|
||||
pStruct["extra_hosts"] = []string{
|
||||
"mythic_server:127.0.0.1",
|
||||
"mythic_rabbitmq:127.0.0.1",
|
||||
"mythic_nginx:127.0.0.1",
|
||||
"mythic_react:127.0.0.1",
|
||||
"mythic_documentation:127.0.0.1",
|
||||
"mythic_graphql:127.0.0.1",
|
||||
"mythic_jupyter:127.0.0.1",
|
||||
"mythic_postgres:127.0.0.1",
|
||||
}
|
||||
pStruct["extra_hosts"] = getExtraHosts()
|
||||
if mythicEnv.GetBool("postgres_bind_localhost_only") {
|
||||
pStruct["command"] = "postgres -c \"max_connections=100\" -p ${POSTGRES_PORT} -c config_file=/etc/postgresql.conf -c \"listen_addresses=localhost\""
|
||||
} else {
|
||||
@@ -95,10 +86,18 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
pStruct["environment"] = environment
|
||||
}
|
||||
if !mythicEnv.GetBool("postgres_use_volume") {
|
||||
pStruct["volumes"] = []string{
|
||||
"./postgres-docker/database:/var/lib/postgresql/data",
|
||||
"./postgres-docker/postgres.conf:/etc/postgresql.conf",
|
||||
if mythicEnv.GetBool("postgres_debug") {
|
||||
pStruct["volumes"] = []string{
|
||||
"./postgres-docker/database:/var/lib/postgresql/data",
|
||||
"./postgres-docker/postgres_debug.conf:/etc/postgresql.conf",
|
||||
}
|
||||
} else {
|
||||
pStruct["volumes"] = []string{
|
||||
"./postgres-docker/database:/var/lib/postgresql/data",
|
||||
"./postgres-docker/postgres.conf:/etc/postgresql.conf",
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
pStruct["volumes"] = []string{
|
||||
"mythic_postgres_volume:/var/lib/postgresql/data",
|
||||
@@ -118,7 +117,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
pStruct["image"] = service
|
||||
} else {
|
||||
pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest"))
|
||||
pStruct["image"] = applyImageMirror(fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")), mythicEnv.GetString("mythic_docker_image_mirror"))
|
||||
}
|
||||
if mythicEnv.GetString("mythic_docker_networking") == "bridge" {
|
||||
if mythicEnv.GetBool("documentation_bind_localhost_only") {
|
||||
@@ -138,16 +137,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
} else {
|
||||
delete(pStruct, "ports")
|
||||
pStruct["network_mode"] = "host"
|
||||
pStruct["extra_hosts"] = []string{
|
||||
"mythic_server:127.0.0.1",
|
||||
"mythic_rabbitmq:127.0.0.1",
|
||||
"mythic_nginx:127.0.0.1",
|
||||
"mythic_react:127.0.0.1",
|
||||
"mythic_documentation:127.0.0.1",
|
||||
"mythic_graphql:127.0.0.1",
|
||||
"mythic_jupyter:127.0.0.1",
|
||||
"mythic_postgres:127.0.0.1",
|
||||
}
|
||||
pStruct["extra_hosts"] = getExtraHosts()
|
||||
if mythicEnv.GetBool("documentation_bind_localhost_only") {
|
||||
pStruct["environment"] = []string{
|
||||
"DOCUMENTATION_PORT=${DOCUMENTATION_PORT}",
|
||||
@@ -177,10 +167,21 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
}
|
||||
case "mythic_graphql":
|
||||
pStruct["depends_on"] = map[string]map[string]string{
|
||||
"mythic_postgres": {
|
||||
dependsOn := map[string]map[string]string{}
|
||||
if isServiceInternal("mythic_server") {
|
||||
dependsOn["mythic_server"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
}
|
||||
}
|
||||
if isServiceInternal("mythic_postgres") {
|
||||
dependsOn["mythic_postgres"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
}
|
||||
}
|
||||
if len(dependsOn) > 0 {
|
||||
pStruct["depends_on"] = dependsOn
|
||||
} else {
|
||||
delete(pStruct, "depends_on")
|
||||
}
|
||||
if mythicEnv.GetBool("hasura_use_build_context") {
|
||||
pStruct["build"] = map[string]interface{}{
|
||||
@@ -189,7 +190,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
pStruct["image"] = service
|
||||
} else {
|
||||
pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest"))
|
||||
pStruct["image"] = applyImageMirror(fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")), mythicEnv.GetString("mythic_docker_image_mirror"))
|
||||
}
|
||||
|
||||
pStruct["cpus"] = mythicEnv.GetInt("HASURA_CPUS")
|
||||
@@ -197,8 +198,8 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
pStruct["mem_limit"] = mythicEnv.GetString("hasura_mem_limit")
|
||||
}
|
||||
environment := []string{
|
||||
"HASURA_GRAPHQL_DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}",
|
||||
"HASURA_GRAPHQL_METADATA_DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}",
|
||||
"HASURA_GRAPHQL_DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}?sslmode=${POSTGRES_SSLMODE:-disable}",
|
||||
"HASURA_GRAPHQL_METADATA_DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}?sslmode=${POSTGRES_SSLMODE:-disable}",
|
||||
"HASURA_GRAPHQL_ENABLE_CONSOLE=true",
|
||||
"HASURA_GRAPHQL_DEV_MODE=false",
|
||||
"HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET}",
|
||||
@@ -228,16 +229,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
} else {
|
||||
delete(pStruct, "ports")
|
||||
pStruct["network_mode"] = "host"
|
||||
pStruct["extra_hosts"] = []string{
|
||||
"mythic_server:127.0.0.1",
|
||||
"mythic_rabbitmq:127.0.0.1",
|
||||
"mythic_nginx:127.0.0.1",
|
||||
"mythic_react:127.0.0.1",
|
||||
"mythic_documentation:127.0.0.1",
|
||||
"mythic_graphql:127.0.0.1",
|
||||
"mythic_jupyter:127.0.0.1",
|
||||
"mythic_postgres:127.0.0.1",
|
||||
}
|
||||
pStruct["extra_hosts"] = getExtraHosts()
|
||||
if mythicEnv.GetBool("hasura_bind_localhost_only") {
|
||||
environment = append(environment, "HASURA_GRAPHQL_SERVER_HOST=127.0.0.1")
|
||||
} else {
|
||||
@@ -253,22 +245,36 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
delete(pStruct, "volumes")
|
||||
}
|
||||
case "mythic_nginx":
|
||||
pStruct["depends_on"] = map[string]map[string]string{
|
||||
"mythic_server": {
|
||||
dependsOn := map[string]map[string]string{}
|
||||
if isServiceInternal("mythic_server") {
|
||||
dependsOn["mythic_server"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_react": {
|
||||
}
|
||||
}
|
||||
if isServiceInternal("mythic_react") {
|
||||
dependsOn["mythic_react"] = map[string]string{
|
||||
"condition": "service_started",
|
||||
},
|
||||
"mythic_jupyter": {
|
||||
}
|
||||
}
|
||||
if isServiceInternal("mythic_jupyter") {
|
||||
dependsOn["mythic_jupyter"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_documentation": {
|
||||
}
|
||||
}
|
||||
if isServiceInternal("mythic_documentation") {
|
||||
dependsOn["mythic_documentation"] = map[string]string{
|
||||
"condition": "service_started",
|
||||
},
|
||||
"mythic_graphql": {
|
||||
}
|
||||
}
|
||||
if isServiceInternal("mythic_graphql") {
|
||||
dependsOn["mythic_graphql"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
}
|
||||
}
|
||||
if len(dependsOn) > 0 {
|
||||
pStruct["depends_on"] = dependsOn
|
||||
} else {
|
||||
delete(pStruct, "depends_on")
|
||||
}
|
||||
if mythicEnv.GetBool("nginx_use_build_context") {
|
||||
pStruct["build"] = map[string]interface{}{
|
||||
@@ -277,7 +283,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
pStruct["image"] = service
|
||||
} else {
|
||||
pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest"))
|
||||
pStruct["image"] = applyImageMirror(fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")), mythicEnv.GetString("mythic_docker_image_mirror"))
|
||||
}
|
||||
|
||||
nginxUseSSL := "ssl"
|
||||
@@ -337,16 +343,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
} else {
|
||||
delete(pStruct, "ports")
|
||||
pStruct["network_mode"] = "host"
|
||||
pStruct["extra_hosts"] = []string{
|
||||
"mythic_server:127.0.0.1",
|
||||
"mythic_rabbitmq:127.0.0.1",
|
||||
"mythic_nginx:127.0.0.1",
|
||||
"mythic_react:127.0.0.1",
|
||||
"mythic_documentation:127.0.0.1",
|
||||
"mythic_graphql:127.0.0.1",
|
||||
"mythic_jupyter:127.0.0.1",
|
||||
"mythic_postgres:127.0.0.1",
|
||||
}
|
||||
pStruct["extra_hosts"] = getExtraHosts()
|
||||
if mythicEnv.GetBool("nginx_bind_localhost_only") {
|
||||
finalNginxEnv = append(finalNginxEnv, "NGINX_BIND_IPV4=127.0.0.1", "NGINX_BIND_IPV6=[::1]")
|
||||
} else {
|
||||
@@ -382,7 +379,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
pStruct["image"] = service
|
||||
} else {
|
||||
pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest"))
|
||||
pStruct["image"] = applyImageMirror(fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")), mythicEnv.GetString("mythic_docker_image_mirror"))
|
||||
}
|
||||
pStruct["cpus"] = mythicEnv.GetInt("RABBITMQ_CPUS")
|
||||
if mythicEnv.GetString("rabbitmq_mem_limit") != "" {
|
||||
@@ -423,16 +420,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
} else {
|
||||
delete(pStruct, "ports")
|
||||
pStruct["network_mode"] = "host"
|
||||
pStruct["extra_hosts"] = []string{
|
||||
"mythic_server:127.0.0.1",
|
||||
"mythic_rabbitmq:127.0.0.1",
|
||||
"mythic_nginx:127.0.0.1",
|
||||
"mythic_react:127.0.0.1",
|
||||
"mythic_documentation:127.0.0.1",
|
||||
"mythic_graphql:127.0.0.1",
|
||||
"mythic_jupyter:127.0.0.1",
|
||||
"mythic_postgres:127.0.0.1",
|
||||
}
|
||||
pStruct["extra_hosts"] = getExtraHosts()
|
||||
if mythicEnv.GetBool("rabbitmq_bind_localhost_only") {
|
||||
environment = append(environment, "RABBITMQ_NODE_IP_ADDRESS=127.0.0.1", "RABBITMQ_NODE_PORT=${RABBITMQ_PORT}")
|
||||
} else {
|
||||
@@ -458,16 +446,26 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
}
|
||||
case "mythic_react":
|
||||
pStruct["depends_on"] = map[string]map[string]string{
|
||||
"mythic_server": {
|
||||
dependsOn := map[string]map[string]string{}
|
||||
if isServiceInternal("mythic_server") {
|
||||
dependsOn["mythic_server"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_graphql": {
|
||||
}
|
||||
}
|
||||
if isServiceInternal("mythic_graphql") {
|
||||
dependsOn["mythic_graphql"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_postgres": {
|
||||
}
|
||||
}
|
||||
if isServiceInternal("mythic_postgres") {
|
||||
dependsOn["mythic_postgres"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
}
|
||||
}
|
||||
if len(dependsOn) > 0 {
|
||||
pStruct["depends_on"] = dependsOn
|
||||
} else {
|
||||
delete(pStruct, "depends_on")
|
||||
}
|
||||
if mythicEnv.GetBool("mythic_react_debug") {
|
||||
pStruct["build"] = map[string]interface{}{
|
||||
@@ -491,7 +489,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
pStruct["image"] = service
|
||||
} else {
|
||||
pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest"))
|
||||
pStruct["image"] = applyImageMirror(fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")), mythicEnv.GetString("mythic_docker_image_mirror"))
|
||||
}
|
||||
|
||||
if !mythicEnv.GetBool("mythic_react_use_volume") {
|
||||
@@ -539,16 +537,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
} else {
|
||||
delete(pStruct, "ports")
|
||||
pStruct["network_mode"] = "host"
|
||||
pStruct["extra_hosts"] = []string{
|
||||
"mythic_server:127.0.0.1",
|
||||
"mythic_rabbitmq:127.0.0.1",
|
||||
"mythic_nginx:127.0.0.1",
|
||||
"mythic_react:127.0.0.1",
|
||||
"mythic_documentation:127.0.0.1",
|
||||
"mythic_graphql:127.0.0.1",
|
||||
"mythic_jupyter:127.0.0.1",
|
||||
"mythic_postgres:127.0.0.1",
|
||||
}
|
||||
pStruct["extra_hosts"] = getExtraHosts()
|
||||
if mythicEnv.GetBool("mythic_react_bind_localhost_only") {
|
||||
pStruct["environment"] = []string{
|
||||
"MYTHIC_REACT_PORT=${MYTHIC_REACT_PORT}",
|
||||
@@ -563,16 +552,26 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
|
||||
case "mythic_jupyter":
|
||||
pStruct["depends_on"] = map[string]map[string]string{
|
||||
"mythic_server": {
|
||||
dependsOn := map[string]map[string]string{}
|
||||
if isServiceInternal("mythic_server") {
|
||||
dependsOn["mythic_server"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_postgres": {
|
||||
}
|
||||
}
|
||||
if isServiceInternal("mythic_graphql") {
|
||||
dependsOn["mythic_graphql"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_graphql": {
|
||||
}
|
||||
}
|
||||
if isServiceInternal("mythic_postgres") {
|
||||
dependsOn["mythic_postgres"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
}
|
||||
}
|
||||
if len(dependsOn) > 0 {
|
||||
pStruct["depends_on"] = dependsOn
|
||||
} else {
|
||||
delete(pStruct, "depends_on")
|
||||
}
|
||||
if mythicEnv.GetBool("jupyter_use_build_context") {
|
||||
pStruct["build"] = map[string]interface{}{
|
||||
@@ -581,7 +580,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
pStruct["image"] = service
|
||||
} else {
|
||||
pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest"))
|
||||
pStruct["image"] = applyImageMirror(fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")), mythicEnv.GetString("mythic_docker_image_mirror"))
|
||||
}
|
||||
|
||||
pStruct["cpus"] = mythicEnv.GetInt("JUPYTER_CPUS")
|
||||
@@ -604,20 +603,12 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
"JUPYTER_TOKEN=${JUPYTER_TOKEN}",
|
||||
"CHOWN_EXTRA=/projects",
|
||||
"CHOWN_EXTRA_OPTS=-R",
|
||||
"JUPYTER_PORT=${JUPYTER_PORT}",
|
||||
}
|
||||
} else {
|
||||
delete(pStruct, "ports")
|
||||
pStruct["network_mode"] = "host"
|
||||
pStruct["extra_hosts"] = []string{
|
||||
"mythic_server:127.0.0.1",
|
||||
"mythic_rabbitmq:127.0.0.1",
|
||||
"mythic_nginx:127.0.0.1",
|
||||
"mythic_react:127.0.0.1",
|
||||
"mythic_documentation:127.0.0.1",
|
||||
"mythic_graphql:127.0.0.1",
|
||||
"mythic_jupyter:127.0.0.1",
|
||||
"mythic_postgres:127.0.0.1",
|
||||
}
|
||||
pStruct["extra_hosts"] = getExtraHosts()
|
||||
environment := []string{
|
||||
"JUPYTER_TOKEN=${JUPYTER_TOKEN}",
|
||||
"CHOWN_EXTRA=/projects",
|
||||
@@ -653,16 +644,21 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
}
|
||||
case "mythic_server":
|
||||
pStruct["depends_on"] = map[string]map[string]string{
|
||||
"mythic_postgres": {
|
||||
dependsOn := map[string]map[string]string{}
|
||||
if isServiceInternal("mythic_rabbitmq") {
|
||||
dependsOn["mythic_rabbitmq"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_graphql": {
|
||||
}
|
||||
}
|
||||
if isServiceInternal("mythic_postgres") {
|
||||
dependsOn["mythic_postgres"] = map[string]string{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_rabbitmq": {
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
}
|
||||
}
|
||||
if len(dependsOn) > 0 {
|
||||
pStruct["depends_on"] = dependsOn
|
||||
} else {
|
||||
delete(pStruct, "depends_on")
|
||||
}
|
||||
if mythicEnv.GetBool("mythic_server_use_build_context") {
|
||||
pStruct["build"] = map[string]interface{}{
|
||||
@@ -671,7 +667,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
pStruct["image"] = service
|
||||
} else {
|
||||
pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest"))
|
||||
pStruct["image"] = applyImageMirror(fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")), mythicEnv.GetString("mythic_docker_image_mirror"))
|
||||
}
|
||||
|
||||
pStruct["cpus"] = mythicEnv.GetInt("MYTHIC_SERVER_CPUS")
|
||||
@@ -701,6 +697,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
"POSTGRES_HOST=${POSTGRES_HOST}",
|
||||
"POSTGRES_PORT=${POSTGRES_PORT}",
|
||||
"POSTGRES_PASSWORD=${POSTGRES_PASSWORD}",
|
||||
"POSTGRES_SSLMODE=${POSTGRES_SSLMODE}",
|
||||
"RABBITMQ_HOST=${RABBITMQ_HOST}",
|
||||
"RABBITMQ_PORT=${RABBITMQ_PORT}",
|
||||
"RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD}",
|
||||
@@ -731,16 +728,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
} else {
|
||||
delete(pStruct, "ports")
|
||||
pStruct["network_mode"] = "host"
|
||||
pStruct["extra_hosts"] = []string{
|
||||
"mythic_server:127.0.0.1",
|
||||
"mythic_rabbitmq:127.0.0.1",
|
||||
"mythic_nginx:127.0.0.1",
|
||||
"mythic_react:127.0.0.1",
|
||||
"mythic_documentation:127.0.0.1",
|
||||
"mythic_graphql:127.0.0.1",
|
||||
"mythic_jupyter:127.0.0.1",
|
||||
"mythic_postgres:127.0.0.1",
|
||||
}
|
||||
pStruct["extra_hosts"] = getExtraHosts()
|
||||
}
|
||||
|
||||
if _, ok := pStruct["environment"]; ok {
|
||||
@@ -765,10 +753,12 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
}
|
||||
}
|
||||
case "mythic_sync":
|
||||
pStruct["depends_on"] = map[string]map[string]string{
|
||||
"mythic_nginx": {
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
if isServiceInternal("mythic_nginx") {
|
||||
pStruct["depends_on"] = map[string]map[string]string{
|
||||
"mythic_nginx": {"condition": "service_healthy"},
|
||||
}
|
||||
} else {
|
||||
delete(pStruct, "depends_on")
|
||||
}
|
||||
absPath, err := filepath.Abs(filepath.Join(manager.GetManager().GetPathTo3rdPartyServicesOnDisk(), service))
|
||||
if err != nil {
|
||||
@@ -813,16 +803,7 @@ func AddMythicService(service string, removeVolume bool) {
|
||||
delete(pStruct, "extra_hosts")
|
||||
} else {
|
||||
pStruct["network_mode"] = "host"
|
||||
pStruct["extra_hosts"] = []string{
|
||||
"mythic_server:127.0.0.1",
|
||||
"mythic_rabbitmq:127.0.0.1",
|
||||
"mythic_nginx:127.0.0.1",
|
||||
"mythic_react:127.0.0.1",
|
||||
"mythic_documentation:127.0.0.1",
|
||||
"mythic_graphql:127.0.0.1",
|
||||
"mythic_jupyter:127.0.0.1",
|
||||
"mythic_postgres:127.0.0.1",
|
||||
}
|
||||
pStruct["extra_hosts"] = getExtraHosts()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -854,29 +835,25 @@ func Add3rdPartyService(service string, additionalConfigs map[string]interface{}
|
||||
"args": config.GetBuildArguments(),
|
||||
}
|
||||
existingConfig["network_mode"] = "host"
|
||||
existingConfig["extra_hosts"] = []string{
|
||||
"mythic_server:127.0.0.1",
|
||||
"mythic_rabbitmq:127.0.0.1",
|
||||
"mythic_nginx:127.0.0.1",
|
||||
"mythic_react:127.0.0.1",
|
||||
"mythic_documentation:127.0.0.1",
|
||||
"mythic_graphql:127.0.0.1",
|
||||
"mythic_jupyter:127.0.0.1",
|
||||
"mythic_postgres:127.0.0.1",
|
||||
existingConfig["extra_hosts"] = getExtraHosts()
|
||||
|
||||
dependsOn := map[string]map[string]string{}
|
||||
if isServiceInternal("mythic_server") {
|
||||
dependsOn["mythic_server"] = map[string]string{"condition": "service_healthy"}
|
||||
}
|
||||
existingConfig["depends_on"] = map[string]map[string]string{
|
||||
"mythic_server": {
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_rabbitmq": {
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_nginx": {
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
"mythic_graphql": {
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
if isServiceInternal("mythic_rabbitmq") {
|
||||
dependsOn["mythic_rabbitmq"] = map[string]string{"condition": "service_healthy"}
|
||||
}
|
||||
if isServiceInternal("mythic_graphql") {
|
||||
dependsOn["mythic_graphql"] = map[string]string{"condition": "service_healthy"}
|
||||
}
|
||||
if isServiceInternal("mythic_nginx") {
|
||||
dependsOn["mythic_nginx"] = map[string]string{"condition": "service_healthy"}
|
||||
}
|
||||
if len(dependsOn) > 0 {
|
||||
existingConfig["depends_on"] = dependsOn
|
||||
} else {
|
||||
delete(existingConfig, "depends_on")
|
||||
}
|
||||
agentConfigs := config.GetConfigStrings([]string{fmt.Sprintf("%s_.*", service)})
|
||||
agentUseBuildContextKey := fmt.Sprintf("%s_use_build_context", service)
|
||||
@@ -885,7 +862,7 @@ func Add3rdPartyService(service string, additionalConfigs map[string]interface{}
|
||||
if useBuildContext, ok := agentConfigs[agentUseBuildContextKey]; ok {
|
||||
if useBuildContext == "false" {
|
||||
delete(existingConfig, "build")
|
||||
existingConfig["image"] = agentConfigs[agentRemoteImageKey]
|
||||
existingConfig["image"] = applyImageMirror(agentConfigs[agentRemoteImageKey], config.GetMythicEnv().GetString("mythic_docker_image_mirror"))
|
||||
}
|
||||
}
|
||||
if useVolume, ok := agentConfigs[agentUseVolumeKey]; ok {
|
||||
@@ -975,3 +952,36 @@ func Initialize() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// applyImageMirror rewrites imageURL to be served from mirror by stripping the
|
||||
// upstream registry host (the first '/'-separated segment, when it contains '.'
|
||||
// or ':' or equals 'localhost' per Docker's reference parsing) and prepending
|
||||
// mirror. An empty mirror returns imageURL unchanged.
|
||||
func applyImageMirror(imageURL, mirror string) string {
|
||||
mirror = strings.TrimSpace(mirror)
|
||||
mirror = strings.TrimRight(mirror, "/")
|
||||
if mirror == "" {
|
||||
return imageURL
|
||||
}
|
||||
if idx := strings.Index(imageURL, "/"); idx > 0 {
|
||||
first := imageURL[:idx]
|
||||
if strings.ContainsAny(first, ".:") || first == "localhost" {
|
||||
return mirror + "/" + imageURL[idx+1:]
|
||||
}
|
||||
}
|
||||
return mirror + "/" + imageURL
|
||||
}
|
||||
|
||||
func isServiceInternal(service string) bool {
|
||||
return manager.GetManager().IsServiceInternal(service)
|
||||
}
|
||||
|
||||
func getExtraHosts() []string {
|
||||
extraHosts := []string{}
|
||||
for _, service := range config.MythicPossibleServices {
|
||||
if isServiceInternal(service) {
|
||||
extraHosts = append(extraHosts, fmt.Sprintf("%s:127.0.0.1", service))
|
||||
}
|
||||
}
|
||||
return extraHosts
|
||||
}
|
||||
@@ -156,6 +156,10 @@ func Status(verbose bool) {
|
||||
log.Printf("\n[*] MythicServer is currently listening on localhost. If you have a remote Service, they will be unable to connect (i.e. one running on another server)")
|
||||
log.Printf("\n Use 'sudo ./mythic-cli config set mythic_server_bind_localhost_only false' and restart mythic ('sudo ./mythic-cli restart') to change this\n")
|
||||
}
|
||||
if mirror := mythicEnv.GetString("mythic_docker_image_mirror"); mirror != "" {
|
||||
log.Printf("\n[*] Image mirror is active: pulling all images from \"%s\" instead of their upstream registries.", mirror)
|
||||
log.Printf("\n Use 'sudo ./mythic-cli config set mythic_docker_image_mirror \"\"' and restart mythic ('sudo ./mythic-cli restart') to disable.\n")
|
||||
}
|
||||
log.Printf("[*] If you are using a remote PayloadType or C2Profile, they will need certain environment variables to properly connect to Mythic.\n")
|
||||
log.Printf(" Use 'sudo ./mythic-cli config service' for configs for these services.\n")
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@ func updateNginxBlockLists() {
|
||||
outputString += "deny all;"
|
||||
if !config.GetMythicEnv().GetBool("nginx_use_volume") {
|
||||
ipFilePath := filepath.Join(utils.GetCwdFromExe(), "nginx-docker", "config", "blockips.conf")
|
||||
if err := os.MkdirAll(filepath.Dir(ipFilePath), 0755); err != nil {
|
||||
log.Fatalf("[-] Failed to create nginx-docker/config directory for block list file: %v\n", err)
|
||||
}
|
||||
if err := os.WriteFile(ipFilePath, []byte(outputString), 0600); err != nil {
|
||||
log.Fatalf("[-] Failed to write out block list file: %v\n", err)
|
||||
}
|
||||
|
||||
@@ -92,6 +92,18 @@ func (d *DockerComposeManager) IsServiceRunning(service string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsServiceInternal checks if a service is configured to run locally in docker vs externally
|
||||
func (d *DockerComposeManager) IsServiceInternal(service string) bool {
|
||||
if !utils.StringInSlice(service, config.MythicPossibleServices) {
|
||||
return true
|
||||
}
|
||||
intendedServices, err := config.GetIntendedMythicServiceNames()
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return utils.StringInSlice(service, intendedServices)
|
||||
}
|
||||
|
||||
// DoesImageExist use Docker API to check existing images for the specified name
|
||||
func (d *DockerComposeManager) DoesImageExist(service string) bool {
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
@@ -225,10 +237,11 @@ func (d *DockerComposeManager) LoadImages(outputPath string) error {
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("[-] Failed to read tar file: %v\n", err))
|
||||
}
|
||||
_, err = cli.ImageLoad(context.Background(), ioReadCloser, false)
|
||||
imageLoadResp, err := cli.ImageLoad(context.Background(), ioReadCloser, nil)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("[-] Failed to load image into Docker: %v\n", err))
|
||||
}
|
||||
imageLoadResp.Body.Close()
|
||||
log.Printf("[+] loaded docker images!\n")
|
||||
return nil
|
||||
|
||||
@@ -470,12 +483,15 @@ func (d *DockerComposeManager) BuildServices(services []string, keepVolume bool)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.runDockerCompose(append([]string{"up", "--build", "-d"}, services...))
|
||||
err = d.runDockerCompose(append([]string{"build"}, services...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.runDockerCompose(append([]string{"up", "-d"}, services...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// GetInstalled3rdPartyServicesOnDisk lists out the name of all 3rd party software installed on disk
|
||||
@@ -620,9 +636,9 @@ func (d *DockerComposeManager) TestPorts(services []string) {
|
||||
var removeServices []string
|
||||
mythicEnv := config.GetMythicEnv()
|
||||
for key, val := range portChecks {
|
||||
// only check ports for services we're about to start
|
||||
// only check ports for services we're about to start that are running locally
|
||||
if utils.StringInSlice(val[1], services) {
|
||||
if mythicEnv.GetString(key) == val[1] || mythicEnv.GetString(key) == "127.0.0.1" {
|
||||
if d.IsServiceInternal(val[1]) {
|
||||
addServices = append(addServices, val[1])
|
||||
p, err := net.Listen("tcp", ":"+strconv.Itoa(mythicEnv.GetInt(val[0])))
|
||||
if err != nil {
|
||||
@@ -955,7 +971,7 @@ func (d *DockerComposeManager) BackupDatabase(backupPath string, useVolume bool)
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
execID, err := cli.ContainerExecCreate(ctx, "mythic_postgres", types.ExecConfig{
|
||||
execID, err := cli.ContainerExecCreate(ctx, "mythic_postgres", container.ExecOptions{
|
||||
AttachStderr: true,
|
||||
AttachStdout: true,
|
||||
AttachStdin: true,
|
||||
@@ -966,7 +982,7 @@ func (d *DockerComposeManager) BackupDatabase(backupPath string, useVolume bool)
|
||||
} else {
|
||||
log.Printf("[*] Created docker exec session")
|
||||
}
|
||||
session, err := cli.ContainerExecAttach(ctx, execID.ID, types.ExecStartCheck{})
|
||||
session, err := cli.ContainerExecAttach(ctx, execID.ID, container.ExecStartOptions{})
|
||||
if err != nil {
|
||||
log.Fatalf("[!] Failed to attach to exec session: %v", err)
|
||||
} else {
|
||||
@@ -1031,7 +1047,7 @@ func (d *DockerComposeManager) RestoreDatabase(backupPath string, useVolume bool
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
execID, err := cli.ContainerExecCreate(ctx, "mythic_postgres", types.ExecConfig{
|
||||
execID, err := cli.ContainerExecCreate(ctx, "mythic_postgres", container.ExecOptions{
|
||||
AttachStderr: true,
|
||||
AttachStdout: true,
|
||||
AttachStdin: true,
|
||||
@@ -1042,7 +1058,7 @@ func (d *DockerComposeManager) RestoreDatabase(backupPath string, useVolume bool
|
||||
} else {
|
||||
log.Printf("[*] Created docker exec session")
|
||||
}
|
||||
session, err := cli.ContainerExecAttach(ctx, execID.ID, types.ExecStartCheck{})
|
||||
session, err := cli.ContainerExecAttach(ctx, execID.ID, container.ExecStartOptions{})
|
||||
if err != nil {
|
||||
log.Fatalf("[!] Failed to attach to exec session: %v", err)
|
||||
} else {
|
||||
@@ -1409,7 +1425,7 @@ func (d *DockerComposeManager) runDockerCompose(args []string) error {
|
||||
return err
|
||||
}
|
||||
if processState.ExitCode() != 0 {
|
||||
return err
|
||||
return fmt.Errorf("docker-compose %v exited with code %d", args, processState.ExitCode())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -12,6 +12,8 @@ type CLIManager interface {
|
||||
GetManagerName() string
|
||||
// IsServiceRunning checks if a service by the specified name is currently running or not
|
||||
IsServiceRunning(service string) bool
|
||||
// IsServiceInternal checks if a service is configured to run locally in docker vs externally
|
||||
IsServiceInternal(service string) bool
|
||||
// CheckRequiredManagerVersion checks if the version of the management software installed is a valid version or not
|
||||
CheckRequiredManagerVersion() bool
|
||||
// GenerateRequiredConfig creates any necessary base configuration files needed by the manager, like a docker-compose.yml file
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/config"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/internal"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -31,6 +33,6 @@ func start(cmd *cobra.Command, args []string) {
|
||||
keepVolume = !config.GetMythicEnv().GetBool("REBUILD_ON_START")
|
||||
}
|
||||
if err := internal.ServiceStart(args, localKeepVolume); err != nil {
|
||||
|
||||
log.Fatalf("[-] Failed to start Mythic: %v\n", err)
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,20 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/config"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/internal"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/manager"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/mod/semver"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/config"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/internal"
|
||||
"github.com/MythicMeta/Mythic_CLI/cmd/manager"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// configCmd represents the config command
|
||||
@@ -63,6 +65,9 @@ func init() {
|
||||
}
|
||||
|
||||
func updateCheck(cmd *cobra.Command, args []string) {
|
||||
if mirror := config.GetMythicEnv().GetString("mythic_docker_image_mirror"); mirror != "" {
|
||||
log.Printf("[*] mythic_docker_image_mirror is set to %q; image URLs printed below are the upstream sources used for version checking, not the mirrored locations actually pulled by docker compose\n", mirror)
|
||||
}
|
||||
var tr = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
@@ -291,6 +296,7 @@ func checkConfigJsonVersion(body []byte, agentName string) (updateVersion string
|
||||
func checkAgentVersions(agents []string, allAgents bool) error {
|
||||
agentUpdateMessages := ""
|
||||
localAgents := agents
|
||||
updatedAgents := []string{}
|
||||
if len(localAgents) == 0 || allAgents {
|
||||
dockerComposeContainers, err := manager.GetManager().GetAllInstalled3rdPartyServiceNames()
|
||||
if err != nil {
|
||||
@@ -340,17 +346,32 @@ func checkAgentVersions(agents []string, allAgents bool) error {
|
||||
if !keepVolume {
|
||||
localKeepVolume = !config.GetMythicEnv().GetBool("REBUILD_ON_START")
|
||||
}
|
||||
err = internal.InstallService(targetInfoPieces[0], targetInfoPieces[1], true, localKeepVolume)
|
||||
err, additionalServices := internal.InstallService(targetInfoPieces[0], targetInfoPieces[1], true, localKeepVolume)
|
||||
if err != nil {
|
||||
log.Printf("[!] Failed to install updated %s: %v\n", agent, err)
|
||||
agentUpdateMessages += fmt.Sprintf("[!] Failed to install updated %s: %v\n", agent, err)
|
||||
} else {
|
||||
log.Printf("[+] Successfully installed updated %s\n", agent)
|
||||
updatedAgents = append(updatedAgents, agent)
|
||||
agentUpdateMessages += fmt.Sprintf("[+] Successfully installed updated %s\n", agent)
|
||||
}
|
||||
if len(additionalServices) > 0 {
|
||||
log.Printf("[*] Attempting to install additional services.\n")
|
||||
}
|
||||
for serviceName, servicePath := range additionalServices {
|
||||
if (installAgentUpdates || config.AskConfirm("[*] Would you like to install "+serviceName+"? ")) && !slices.Contains(updatedAgents, serviceName) {
|
||||
updatedAgents = append(updatedAgents, serviceName)
|
||||
if strings.HasPrefix(servicePath, "http") || strings.HasPrefix(servicePath, "git@") {
|
||||
installGitHub(nil, strings.Split(servicePath, " "))
|
||||
} else {
|
||||
installFolder(nil, []string{servicePath})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Printf("[*] %s is up to date! Version: %s\n", agent, config.GetMythicEnv().GetString(fmt.Sprintf("%s_remote_image", agent)))
|
||||
updatedAgents = append(updatedAgents, agent)
|
||||
agentUpdateMessages += fmt.Sprintf("[*] %s is up to date! Version: %s\n", agent, config.GetMythicEnv().GetString(fmt.Sprintf("%s_remote_image", agent)))
|
||||
}
|
||||
} else {
|
||||
@@ -375,14 +396,28 @@ func checkAgentVersions(agents []string, allAgents bool) error {
|
||||
if !keepVolume {
|
||||
localKeepVolume = !config.GetMythicEnv().GetBool("REBUILD_ON_START")
|
||||
}
|
||||
err = internal.InstallFolder(installLocation, true, localKeepVolume, "")
|
||||
err, additionalServices := internal.InstallFolder(installLocation, true, localKeepVolume, "")
|
||||
if err != nil {
|
||||
log.Printf("[!] Failed to parse config.json for %s: %v\n", agent, err)
|
||||
agentUpdateMessages += fmt.Sprintf("[!] Failed to parse config.json for %s: %v\n", agent, err)
|
||||
continue
|
||||
} else {
|
||||
updatedAgents = append(updatedAgents, agent)
|
||||
log.Printf("[+] Successfully installed updated %s\n", agent)
|
||||
agentUpdateMessages += fmt.Sprintf("[+] Successfully installed updated %s\n", agent)
|
||||
if len(additionalServices) > 0 {
|
||||
log.Printf("[*] Attempting to install additional services.\n")
|
||||
}
|
||||
for serviceName, servicePath := range additionalServices {
|
||||
if (installAgentUpdates || config.AskConfirm("[*] Would you like to install "+serviceName+"? ")) && !slices.Contains(updatedAgents, serviceName) {
|
||||
updatedAgents = append(updatedAgents, serviceName)
|
||||
if strings.HasPrefix(servicePath, "http") || strings.HasPrefix(servicePath, "git@") {
|
||||
installGitHub(nil, strings.Split(servicePath, " "))
|
||||
} else {
|
||||
installFolder(nil, []string{servicePath})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user