starting auth adjustments to remove cookies

This commit is contained in:
its-a-feature
2026-03-30 08:07:30 -05:00
parent 6eb53da6b5
commit 22ef644aea
23 changed files with 198 additions and 135 deletions
@@ -3,6 +3,7 @@ import {MythicStyledTooltip} from "./MythicStyledTooltip";
import WifiIcon from '@mui/icons-material/Wifi';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import { faLink } from '@fortawesome/free-solid-svg-icons';
import {ImageWithAuth} from "../utilities/ImageWithAuth";
export const MythicAgentSVGIcon = ({payload_type, style, is_p2p}) => {
const theme = useTheme();
@@ -11,8 +12,8 @@ export const MythicAgentSVGIcon = ({payload_type, style, is_p2p}) => {
}
return (
<MythicStyledTooltip title={payload_type} >
<img src={"/static/" + payload_type + "_" + theme.palette.mode + ".svg"}
style={{...style}} />
<ImageWithAuth src={"/static/" + payload_type + "_" + theme.palette.mode + ".svg"}
style={{...style}} />
{is_p2p === false &&
<WifiIcon style={{width: "20px", height: "20px", marginLeft: "-20px", marginBottom: "5px",
borderRadius: "10px", border: "1px solid",
@@ -35,7 +36,8 @@ export const MythicAgentSVGIconNoTooltip = ({payload_type, is_p2p, className, st
const extraStyles = style ? style : {};
return (
<div style={{display: "inline-block", left: "50%"}} className={className}>
<img src={"/static/" + payload_type + "_" + theme.palette.mode + ".svg"} style={{height: "20px", margin: 'auto', ...extraStyles}}/>
<ImageWithAuth src={"/static/" + payload_type + "_" + theme.palette.mode + ".svg"}
style={{height: "20px", margin: 'auto', ...extraStyles}} />
{is_p2p === false &&
<WifiIcon style={{width: "10px", height: "10px", marginLeft: "-5px", marginBottom: "0px",
borderRadius: "10px", border: "1px solid",
@@ -3,9 +3,9 @@ import {snackActions} from "../utilities/Snackbar";
import {MythicStyledTooltip} from "./MythicStyledTooltip";
import {MythicDialog} from "./MythicDialog";
import {Link} from '@mui/material';
import {useMutation, useLazyQuery, gql} from '@apollo/client';
import {gql} from '@apollo/client';
import {b64DecodeUnicode} from "../pages/Callbacks/ResponseDisplay";
import {PreviewFileMediaDialog} from "../pages/Search/PreviewFileMedia";
import {PreviewFileMediaDialog} from "./PreviewFileMedia";
import {faPhotoVideo} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {useMythicLazyQuery} from "../utilities/useMythicLazyQuery";
@@ -6,8 +6,8 @@ import DialogTitle from '@mui/material/DialogTitle';
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";
import {ResponseDisplayMedia} from "../pages/Callbacks/ResponseDisplayMedia";
import {MythicDraggableDialogTitle} from "./MythicDraggableDialogTitle";
export function PreviewFileMediaDialog({agent_file_id, filename, onClose}) {
const onClick = (e) => {
@@ -38,6 +38,7 @@ import {getIconName} from "./ResponseDisplayTable";
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import RestartAltIcon from '@mui/icons-material/RestartAlt';
import { MythicAgentSVGIconNoTooltip} from "../../MythicComponents/MythicAgentSVGIcon";
import {ImageWithAuth} from "../../utilities/ImageWithAuth";
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
@@ -399,7 +400,7 @@ function AgentNode({data}) {
))
}
<img alt={data.img} style={{margin: "auto"}} src={data.img} className={"circleImageNode"} />
<ImageWithAuth alt={data.img} style={{margin: "auto"}} src={data.img} className={"circleImageNode"} />
<Handle type={"target"} position={targetPosition} isConnectable={false}/>
<Typography style={{textAlign: "center", margin: 0, padding: 0}} >{data.label}</Typography>
</div>
@@ -31,7 +31,7 @@ import SettingsIcon from '@mui/icons-material/Settings';
import { toLocalTime } from '../../utilities/Time';
import {b64DecodeUnicode} from "./ResponseDisplay";
import {faPhotoVideo} from '@fortawesome/free-solid-svg-icons';
import {PreviewFileMediaDialog} from "../Search/PreviewFileMedia";
import {PreviewFileMediaDialog} from "../../MythicComponents/PreviewFileMedia";
import RefreshIcon from '@mui/icons-material/Refresh';
import {Dropdown, DropdownMenuItem, DropdownNestedMenuItem} from "../../MythicComponents/MythicNestedMenus";
import {RenderSingleTask} from "../SingleTaskView/SingleTaskView";
@@ -10,7 +10,7 @@ import DialogTitle from '@mui/material/DialogTitle';
import {Button, Link, Typography} from '@mui/material';
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
import {MythicDialog} from "../../MythicComponents/MythicDialog";
import {PreviewFileMediaDialog} from "../Search/PreviewFileMedia";
import {PreviewFileMediaDialog} from "../../MythicComponents/PreviewFileMedia";
import {faPhotoVideo} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
@@ -63,6 +63,8 @@ import {Backdrop, CircularProgress} from '@mui/material';
// eslint-disable-next-line import/no-webpack-loader-syntax
import sqlWasm from "!!file-loader?name=sql-wasm-[contenthash].wasm!sql.js/dist/sql-wasm.wasm";
import {copyStringToClipboard} from "../../utilities/Clipboard";
import {FileDownloadButtonWithAuth} from "../../utilities/FileDownloadWithAuth";
import {ImageWithAuth} from "../../utilities/ImageWithAuth";
export const modeOptions = ["csharp", "golang", "html", "json", "markdown", "ruby", "python", "java",
"javascript", "yaml", "toml", "swift", "sql", "rust", "powershell", "pgsql", "perl", "php", "objectivec",
@@ -149,10 +151,10 @@ export const ResponseDisplayMedia = ({media, expand, task}) =>{
<Tab className={value === 2 ? "selectedCallback": ""} label={"Hex"}></Tab>
<Tab className={value === 3 ? "selectedCallback": ""} label={"Database"}></Tab>
<MythicStyledTooltip title={"Download the file"} tooltipStyle={{display: "inline-flex"}}>
<Button style={{}} size={"small"} href={"/direct/download/" + media.agent_file_id}
<FileDownloadButtonWithAuth style={{}} size={"small"} href={"/direct/download/" + media.agent_file_id}
download color={"success"}>
<DownloadIcon />
</Button>
</FileDownloadButtonWithAuth>
</MythicStyledTooltip>
</Tabs>
</AppBar>
@@ -343,9 +345,9 @@ export const DisplayMedia = ({agent_file_id, filename, expand, task, fileMetaDat
if(fileData.display_type === "image"){
return (
<>
<img width={"100%"} onClick={clickedScreenshot}
<ImageWithAuth width={"100%"} onClick={clickedScreenshot}
src={"/direct/view/" + agent_file_id} style={{cursor: "zoom-in", width: "100%"}}>
</img>
</ImageWithAuth>
{openScreenshot &&
<MythicDialog fullWidth={true} maxWidth="xl" open={openScreenshot}
onClose={() => {
@@ -455,7 +457,11 @@ const DisplayText = ({agent_file_id, expand, filename, preview, fileMetaData}) =
previewFileString({variables: {file_id: agent_file_id}})
}else{
// get entire file
fetch('/direct/view/' + agent_file_id).then((response) => {
fetch('/direct/view/' + agent_file_id, {
headers: {
Authorization: `Bearer ${localStorage.getItem('access_token')}`,
}
}).then((response) => {
if(response.status !== 200){
snackActions.warning("Failed to fetch contents from Mythic");
return;
@@ -762,7 +768,11 @@ const DisplayDatabase = ({agent_file_id, expand, fileMetaData}) => {
React.useEffect( () => {
async function initialize(){
const newSQL = await initSQLJS({locateFile: () => sqlWasm});
fetch('/direct/view/' + agent_file_id).then((response) => {
fetch('/direct/view/' + agent_file_id, {
headers: {
Authorization: `Bearer ${localStorage.getItem('access_token')}`
}
}).then((response) => {
if(response.status !== 200){
setLoading(false);
snackActions.warning("Failed to fetch contents from Mythic");
@@ -6,6 +6,7 @@ import { MobileStepper } from '@mui/material';
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft';
import {useTheme} from '@mui/material/styles';
import {ImageWithAuth} from "../../utilities/ImageWithAuth";
export function ResponseDisplayScreenshotModal({onClose, images, startIndex}) {
const [zoom, setZoom] = React.useState(false);
@@ -25,7 +26,7 @@ export function ResponseDisplayScreenshotModal({onClose, images, startIndex}) {
< >
<DialogContent dividers={true} style={{padding: 0}} >
<div style={{height: "calc(80vh)", display: zoom ? "" : "flex", alignItems: zoom ? "" : "flex-start" , background: theme.palette.mode === "dark" ? "rgb(44, 52, 60)" : "grey"}}>
<img onClick={toggleZoom} src={"/api/v1.4/files/screencaptures/" + images[activeStep]}
<ImageWithAuth onClick={toggleZoom} src={"/api/v1.4/files/screencaptures/" + images[activeStep]}
style={{width: zoom ? "" : "100%", cursor: zoom ? "zoom-out" : "zoom-in", overflow: "auto"}} />
</div>
</DialogContent>
@@ -15,7 +15,7 @@ import {b64DecodeUnicode} from "../Callbacks/ResponseDisplay";
import { updateFileDeleted} from "../Search/FileMetaTable";
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
import {MythicDialog} from "../../MythicComponents/MythicDialog";
import {PreviewFileMediaDialog} from "../Search/PreviewFileMedia";
import {PreviewFileMediaDialog} from "../../MythicComponents/PreviewFileMedia";
import MythicStyledTableCell from "../../MythicComponents/MythicTableCell";
import {faPhotoVideo} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
@@ -43,7 +43,7 @@ import FormatListBulletedAddIcon from '@mui/icons-material/FormatListBulletedAdd
import AddchartIcon from '@mui/icons-material/Addchart';
import PlaylistRemoveIcon from '@mui/icons-material/PlaylistRemove';
import {MythicSelectFromListDialog} from "../../MythicComponents/MythicSelectFromListDialog";
import {PreviewFileMediaDialog} from "../Search/PreviewFileMedia";
import {PreviewFileMediaDialog} from "../../MythicComponents/PreviewFileMedia";
import {faPhotoVideo} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {Link} from '@mui/material';
@@ -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 {ImageWithAuth} from "../../utilities/ImageWithAuth";
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
const LeadDashboardQuery = gql`
@@ -1534,9 +1535,10 @@ const Top10RecentScreenshotsDashboardElement = ({me, data, editing, removeElemen
</Button>
</MythicTableCell>
<MythicTableCell>
<img onClick={(e) => onPreviewMedia(e, activeStep)}
src={"/api/v1.4/files/screencaptures/" + files[activeStep] + "?" + now}
style={{height: "200px", cursor: "pointer"}}/>
<ImageWithAuth src={"/api/v1.4/files/screencaptures/" + files[activeStep] + "?" + now}
style={{height: "200px", cursor: "pointer"}}
onClick={(e) => onPreviewMedia(e, activeStep)}
/>
</MythicTableCell>
<MythicTableCell style={{borderBottom: 0}} >
<Button
@@ -30,9 +30,11 @@ import Checkbox from '@mui/material/Checkbox';
import {HostFileDialog} from "../Payloads/HostFileDialog";
import PublicIcon from '@mui/icons-material/Public';
import {getStringSize} from '../Callbacks/ResponseDisplayTable';
import {PreviewFileMediaDialog} from "./PreviewFileMedia";
import {PreviewFileMediaDialog} from "../../MythicComponents/PreviewFileMedia";
import {faPhotoVideo} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {ImageWithAuth} from "../../utilities/ImageWithAuth";
import {FileDownloadLinkWithAuth} from "../../utilities/FileDownloadWithAuth";
export const downloadBulkQuery = gql`
mutation downloadBulkMutation($files: [String!]!){
@@ -347,7 +349,9 @@ function FileMetaDownloadTableRow(props){
return (
<React.Fragment>
<TableRow hover onClick={expandRow}>
<MythicConfirmDialog onClose={() => {setOpenDelete(false);}} onSubmit={onAcceptDelete} open={openDelete}/>
{openDelete &&
<MythicConfirmDialog onClose={() => {setOpenDelete(false);}} onSubmit={onAcceptDelete} open={openDelete}/>
}
<MythicStyledTableCell>
{props.deleted ? null : (
<MythicStyledTooltip title="Toggle to download multiple files at once">
@@ -766,7 +770,7 @@ function FileMetaUploadTableRow(props){
)}
</MythicStyledTableCell>
<MythicStyledTableCell>
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" href={"/direct/download/" + props.agent_file_id}>{props.filename_text}</Link>
<FileDownloadLinkWithAuth style={{wordBreak: "break-all"}} color="textPrimary" underline="always" href={"/direct/download/" + props.agent_file_id}>{props.filename_text}</FileDownloadLinkWithAuth>
{props.complete ? null : (
<Typography color="secondary" style={{wordBreak: "break-all"}} >{props.chunks_received} / {props.total_chunks} Chunks Received</Typography>
)
@@ -1052,7 +1056,8 @@ function FileMetaScreenshotTableRow(props){
)}
</MythicStyledTableCell>
<MythicStyledTableCell >
<img onClick={() => setOpenScreenshot(true)} src={"/api/v1.4/files/screencaptures/" + props.agent_file_id + "?" + now} style={{width: "270px", cursor: "pointer"}} />
<ImageWithAuth src={"/api/v1.4/files/screencaptures/" + props.agent_file_id}
style={{width: "270px", cursor: "pointer"}} />
{openScreenshot &&
<MythicDialog fullWidth={true} maxWidth="xl" open={openScreenshot}
onClose={()=>{setOpenScreenshot(false);}}
@@ -0,0 +1,52 @@
import {Button, Link} from '@mui/material';
import {snackActions} from "./Snackbar";
export const handleAuthLink = async (event, href) => {
event.preventDefault(); // Prevent default anchor link navigation
try {
const response = await fetch(href, {
method: 'GET',
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
},
});
if (response.ok) {
const blob = await response.blob();
const fileURL = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = fileURL;
const filename = response.headers.get("content-disposition");
console.log(filename, filename.split('filename=')[1]);
link.download = filename.split('filename=')[1].replaceAll('"', "");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(fileURL);
} else {
console.error('Authorization failed');
snackActions.error("Failed to download file");
console.error(response);
}
} catch (error) {
console.error('Error:', error);
}
};
export function FileDownloadLinkWithAuth({href, children, ...props}){
return (
<Link href={href} onClick={(e) => handleAuthLink(e, href)} {...props}>
{children}
</Link>
);
}
export function FileDownloadButtonWithAuth({href, children, ...props}){
return (
<Button href={href} onClick={(e) => handleAuthLink(e, href)} {...props}>
{children}
</Button>
);
}
@@ -0,0 +1,31 @@
import React from 'react';
export const ImageWithAuth = ({ src, ...props }) => {
const [imageSrc, setImageSrc] = React.useState('');
const headers = {
Authorization: `Bearer ${localStorage.getItem('access_token')}`,
MythicSource: "web"
}
React.useEffect(() => {
const fetchImage = async () => {
try {
const response = await fetch(src, { headers });
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);
setImageSrc(objectUrl);
} catch (error) {
console.error('Error fetching image:', error);
// Handle error (e.g., set a fallback image)
}
};
fetchImage();
// Cleanup: revoke the object URL when the component unmounts or src changes
return () => {
if (imageSrc) {
URL.revokeObjectURL(imageSrc);
}
};
}, [src]);
return <img src={imageSrc} {...props} />;
};
-40
View File
@@ -20,7 +20,6 @@ var (
ErrBearerInvalidValue = errors.New("Authorization bearer value is invalid")
ErrMissingAuthorizationBearerToken = errors.New("Missing Authorization Bearer token")
ErrMissingAuthorizationHeader = errors.New("Missing Authorization header")
ErrMissingCookieValue = errors.New("Missing cookie value")
ErrMissingJWTToken = errors.New("Missing JWT header")
)
@@ -245,42 +244,3 @@ func ExtractAPIToken(c *gin.Context) (string, error) {
//logging.LogTrace("got apitoken header", "apitoken", token)
return token, nil
}
func CookieTokenValid(c *gin.Context) error {
tokenString, err := ExtractCookieToken(c)
if err != nil {
return err
}
claims := mythicjwt.CustomClaims{}
_, err = jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
logging.LogError(ErrUnexpectedSigningMethod, "signing method", token.Header["alg"])
return nil, fmt.Errorf("%w: %v", ErrUnexpectedSigningMethod, token.Header["alg"])
}
return utils.MythicConfig.JWTSecret, nil
})
if err != nil {
logging.LogError(err, "Failed to parse JWT with claims", "JWT", tokenString)
return err
}
c.Set("user_id", claims.UserID)
operator, err := database.GetUserFromID(claims.UserID)
if err == nil {
c.Set("username", operator.Username)
}
c.Request.Header.Add("Authorization", fmt.Sprintf("Bearer: %s", tokenString))
c.Request.Header.Add("MythicSource", "cookie")
return nil
}
func ExtractCookieToken(c *gin.Context) (string, error) {
token, err := c.Cookie("mythic")
if err != nil {
return "", ErrMissingCookieValue
}
if len(token) > 0 {
return token, nil
}
logging.LogDebug("Failed to find cookie value")
return "", ErrMissingCookieValue
}
+2 -14
View File
@@ -2,10 +2,11 @@ package authentication
import (
"fmt"
"github.com/its-a-feature/Mythic/rabbitmq"
"net"
"net/http"
"github.com/its-a-feature/Mythic/rabbitmq"
"github.com/gin-gonic/gin"
"github.com/its-a-feature/Mythic/database"
"github.com/its-a-feature/Mythic/logging"
@@ -25,19 +26,6 @@ func JwtAuthMiddleware() gin.HandlerFunc {
}
}
func CookieAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
err := CookieTokenValid(c)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"message": "Unauthorized"})
c.Abort()
return
}
c.Request.Header.Add("MythicSource", "cookie")
c.Next()
}
}
func IPBlockMiddleware() gin.HandlerFunc {
// verify that xforward-for address is in range from utils.Config.AllowedIPBlocks
return func(c *gin.Context) {
@@ -4,14 +4,15 @@ import (
"crypto/rand"
"errors"
"fmt"
"github.com/golang-jwt/jwt"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
"github.com/its-a-feature/Mythic/utils"
"math/big"
"strings"
"sync"
"time"
"github.com/golang-jwt/jwt"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
"github.com/its-a-feature/Mythic/utils"
)
type CustomClaims struct {
@@ -37,10 +38,10 @@ var (
ErrUnexpectedSigningMethod = errors.New("Unexpected signing method")
)
func generateRandomPassword(pw_length int) (string, error) {
func generateRandomPassword(passwordLength int) (string, error) {
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
var b strings.Builder
for i := 0; i < pw_length; i++ {
for i := 0; i < passwordLength; i++ {
nBig, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
if err != nil {
logging.LogError(err, "[-] Failed to generate random number for password generation\n")
@@ -0,0 +1,32 @@
package rabbitmq
import (
"errors"
"sync"
"github.com/its-a-feature/Mythic/utils"
)
var containerAuthMap = make(map[string]string)
var containerAuthMapLock sync.RWMutex
func NewContainerAuth(containerName string) string {
containerAuthMapLock.Lock()
defer containerAuthMapLock.Unlock()
if _, ok := containerAuthMap[containerName]; ok {
return containerAuthMap[containerName]
}
containerAuthMap[containerName] = utils.GenerateRandomPassword(36)
return containerAuthMap[containerName]
}
func GetContainerFromAuth(password string) (string, error) {
containerAuthMapLock.RLock()
defer containerAuthMapLock.RUnlock()
for key, value := range containerAuthMap {
if value == password {
return key, nil
}
}
return "", errors.New("container not found")
}
@@ -306,13 +306,22 @@ func listenForAsyncAgentMessagePostResponseContent() {
UpdateCachedResponseIntercept()
for {
msg := <-asyncAgentMessagePostResponseChannel
// force chunking user_output can be useful, but might also affect command's browserscripts
//chunkSize := 1024 * 1024
//chunks := int(len(msg.Response)/chunkSize) + 1 // 1MB chunks
//for j := 0; j < chunks; j++ {
// currentChunkStart := j * chunkSize
// currentChunkEnd := currentChunkStart + chunkSize
// if currentChunkEnd > len(msg.Response) {
// currentChunkEnd = len(msg.Response)
// }
// chunkBuf := msg.Response[currentChunkStart:currentChunkEnd]
responseInterceptMapLock.RLock()
if eventGroupID, ok := responseInterceptMapOperationIDToEventGroupID[msg.Task.OperationID]; ok {
output := ""
responseID := handleAgentMessagePostResponseUserOutput(msg.Task, agentMessagePostResponse{
TaskID: msg.Task.AgentTaskID,
UserOutput: &output,
SequenceNumber: msg.SequenceNum,
TaskID: msg.Task.AgentTaskID,
UserOutput: &output,
}, false)
if responseID > 0 {
// we have an interception possibility, so send that off for processing
@@ -327,12 +336,12 @@ func listenForAsyncAgentMessagePostResponseContent() {
}
} else {
handleAgentMessagePostResponseUserOutput(msg.Task, agentMessagePostResponse{
TaskID: msg.Task.AgentTaskID,
UserOutput: &msg.Response,
SequenceNumber: msg.SequenceNum,
TaskID: msg.Task.AgentTaskID,
UserOutput: &msg.Response,
}, true)
}
responseInterceptMapLock.RUnlock()
//}
}
}
func handleAgentMessagePostResponse(incoming *map[string]interface{}, uUIDInfo *cachedUUIDInfo) (map[string]interface{}, error) {
@@ -473,9 +482,8 @@ func handleAgentMessagePostResponse(incoming *map[string]interface{}, uUIDInfo *
// do it in the background - the agent doesn't need the result of this directly
//handleAgentMessagePostResponseUserOutput(currentTask, agentResponse, true)
asyncAgentMessagePostResponseChannel <- agentAgentMessagePostResponseChannelMessage{
Task: currentTask,
Response: *agentMessage.Responses[i].UserOutput,
SequenceNum: agentMessage.Responses[i].SequenceNumber,
Task: currentTask,
Response: *agentMessage.Responses[i].UserOutput,
}
}
if agentMessage.Responses[i].Stdout != nil {
@@ -24,7 +24,7 @@ func FileDirectDownloadWebhook(c *gin.Context) {
filemeta := databaseStructs.Filemeta{}
payload := databaseStructs.Payload{}
operatorUsername := "unknown"
userID, err := GetUserIDFromGinAllowCookies(c)
userID, err := GetUserIDFromGin(c)
if err == nil {
user, err := database.GetUserFromID(userID)
if err == nil {
@@ -24,7 +24,7 @@ func FileDirectViewWebhook(c *gin.Context) {
filemeta := databaseStructs.Filemeta{}
payload := databaseStructs.Payload{}
operatorUsername := "unknown"
userID, err := GetUserIDFromGinAllowCookies(c)
userID, err := GetUserIDFromGin(c)
if err == nil {
user, err := database.GetUserFromID(userID)
if err == nil {
@@ -1,11 +1,11 @@
package webcontroller
import (
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"net/http"
"strings"
"time"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/gin-gonic/gin"
"github.com/its-a-feature/Mythic/authentication"
"github.com/its-a-feature/Mythic/database"
@@ -49,9 +49,6 @@ func Login(c *gin.Context) {
"view_utc_time": currentOperation.CurrentOperator.ViewUtcTime,
"current_utc_time": time.Now().UTC(),
}
// setting cookie max age to 2 days
c.SetCookie("mythic", accessToken, 60*60*24*2, "/", strings.Split(c.Request.Host, ":")[0], false, true)
c.SetSameSite(http.SameSiteStrictMode)
c.JSON(http.StatusOK, gin.H{"access_token": accessToken, "refresh_token": refreshToken, "user": user})
return
@@ -132,8 +129,6 @@ func RefreshJWT(c *gin.Context) {
// setting cookie max age to 2 days
c.Set("user_id", currentOperation.CurrentOperator.ID)
c.Set("username", currentOperation.CurrentOperator.Username)
c.SetCookie("mythic", accessToken, 60*60*24*2, "/", strings.Split(c.Request.Host, ":")[0], false, true)
c.SetSameSite(http.SameSiteStrictMode)
c.JSON(http.StatusOK, gin.H{"access_token": accessToken, "refresh_token": refreshToken, "user": user})
return
@@ -22,17 +22,6 @@ func GetUserIDFromGin(c *gin.Context) (int, error) {
}
return customClaims.UserID, nil
}
func GetUserIDFromGinAllowCookies(c *gin.Context) (int, error) {
customClaims, err := authentication.GetClaims(c)
if err != nil {
err = authentication.CookieTokenValid(c)
if err != nil {
return 0, err
}
return GetUserIDFromGin(c)
}
return customClaims.UserID, nil
}
const (
tagTypePreview = "FilePreviewed"
+6 -20
View File
@@ -147,10 +147,10 @@ func setRoutes(r *gin.Engine) {
r.POST("/agent_message", webcontroller.AgentMessageWebhook)
r.GET("/agent_message", webcontroller.AgentMessageGetWebhook)
// healthcheck endpoint
r.GET("/health", webcontroller.HealthCheckSimple)
r.GET("/healthDetailed", webcontroller.HealthCheckDetailed)
r.Use(authentication.IPBlockMiddleware())
{
r.GET("/health", webcontroller.HealthCheckSimple)
r.GET("/healthDetailed", webcontroller.HealthCheckDetailed)
// login page
r.POST("/auth", webcontroller.Login)
r.POST("/invite", webcontroller.UseInviteLink)
@@ -169,24 +169,7 @@ func setRoutes(r *gin.Engine) {
// unauthenticated file upload based on file UUID
// this is for payload containers to upload files that are too big for rabbitmq
r.POST("/direct/upload/:file_uuid", webcontroller.FileDirectUploadWebhook)
// create a protected group that allows Cookie values to access things instead of only a JWT header field
// this mainly allows the user through the UI to view agent icons and download files
allowAuthenticatedCookies := r.Group("/")
{
// allow access to getting images for agent icons, still blocked by IP range
allowAuthenticatedCookies.Use(authentication.CookieAuthMiddleware())
{
allowAuthenticatedCookies.Static("/static", "./static")
allowAuthenticatedCookies.GET("direct/view/:file_uuid", webcontroller.FileDirectViewWebhook)
allOperationMembersWithCookies := allowAuthenticatedCookies.Group("/api/v1.4/")
allOperationMembersWithCookies.Use(authentication.RBACMiddlewareAll())
{
allOperationMembersWithCookies.GET("files/download/:file_uuid", webcontroller.DownloadFileAuthWebhook)
allOperationMembersWithCookies.GET("files/screencaptures/:file_uuid", webcontroller.DownloadFileAuthWebhook)
}
}
}
// create a protected group that requires valid auth with a JWT to access
protected := r.Group("/")
protected.Use(authentication.JwtAuthMiddleware())
@@ -213,6 +196,8 @@ func setRoutes(r *gin.Engine) {
protected.POST("/api/v1.4/get_global_settings_webhook", webcontroller.GetGlobalSettingWebhook)
// a refresh post will contain the access_token and refresh_token
protected.POST("/refresh", webcontroller.RefreshJWT)
protected.Static("/static", "./static")
protected.GET("direct/view/:file_uuid", webcontroller.FileDirectViewWebhook)
// following require you to have an operation set
allOperationMembers := protected.Group("/api/v1.4/")
allOperationMembers.Use(authentication.RBACMiddlewareAll())
@@ -231,6 +216,7 @@ func setRoutes(r *gin.Engine) {
// file
allOperationMembers.POST("download_bulk_webhook", webcontroller.DownloadBulkFilesWebhook)
allOperationMembers.POST("preview_file_webhook", webcontroller.PreviewFileWebhook)
allOperationMembers.GET("files/screencaptures/:file_uuid", webcontroller.DownloadFileAuthWebhook)
}
noSpectators := protected.Group("/api/v1.4/")
noSpectators.Use(authentication.RBACMiddlewareNoSpectators())