mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
more api validation and logging updates
This commit is contained in:
@@ -4,9 +4,10 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
|
||||
"github.com/its-a-feature/Mythic/rabbitmq"
|
||||
"time"
|
||||
|
||||
"github.com/its-a-feature/Mythic/database"
|
||||
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
|
||||
@@ -45,7 +46,7 @@ func ValidateLogin(username string, password string, scriptingVersion string, fr
|
||||
}
|
||||
if user.ID == 1 &&
|
||||
user.FailedLoginCount >= 10 &&
|
||||
(time.Now().UTC().Sub(user.LastFailedLoginTimestamp.Time) > time.Duration(60*time.Second)) {
|
||||
(time.Now().UTC().Sub(user.LastFailedLoginTimestamp.Time) < 60*time.Second) {
|
||||
err = errors.New("Throttling login attempts of default account")
|
||||
logging.LogError(err, "Throttling login attempts of default account", "username", user.Username)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Throttling login attempts of account, %s, from %s", user.Username, fromIP),
|
||||
@@ -109,7 +110,7 @@ func ValidateCustomAuthProviderLogin(email string, authError string, validLogin
|
||||
}
|
||||
if user.ID == 1 &&
|
||||
user.FailedLoginCount >= 10 &&
|
||||
(time.Now().UTC().Sub(user.LastFailedLoginTimestamp.Time) > 60*time.Second) {
|
||||
(time.Now().UTC().Sub(user.LastFailedLoginTimestamp.Time) < 60*time.Second) {
|
||||
err = errors.New("Throttling login attempts of default account")
|
||||
logging.LogError(err, "Throttling login attempts of default account", "username", user.Username)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Throttling login attempts of account, %s, from %s", user.Username, fromIP),
|
||||
|
||||
@@ -54,7 +54,7 @@ func getAPITokenFromDB(c *gin.Context, tokenString string) (databaseStructs.Apit
|
||||
databaseApiToken := databaseStructs.Apitokens{}
|
||||
hashedTokenValue := mythicjwt.HashAPITokenValue(tokenString)
|
||||
if err := database.DB.Get(&databaseApiToken, SQLGetIDForActiveToken, hashedTokenValue); err != nil {
|
||||
logging.LogError(err, "Failed to get apitoken from database", "apitoken", tokenString)
|
||||
logging.LogError(err, "Failed to get apitoken from database")
|
||||
return databaseApiToken, err
|
||||
}
|
||||
c.Set(ContextKeyAPIToken, databaseApiToken)
|
||||
@@ -67,6 +67,7 @@ func claimsFromAPIToken(databaseApiToken databaseStructs.Apitokens) *mythicjwt.C
|
||||
AuthMethod: databaseApiToken.TokenType,
|
||||
APITokensID: databaseApiToken.ID,
|
||||
OperationID: int(databaseApiToken.Operator.CurrentOperationID.Int64),
|
||||
Scopes: []string{mythicjwt.SCOPE_FILE_DIRECT_UPLOAD, mythicjwt.SCOPE_FILE_DIRECT_DOWNLOAD},
|
||||
}
|
||||
if databaseApiToken.EventStepInstanceID.Valid {
|
||||
claims.EventStepInstanceID = int(databaseApiToken.EventStepInstanceID.Int64)
|
||||
|
||||
@@ -118,11 +118,6 @@ func DirectFileScopeMiddleware(requiredScope string) gin.HandlerFunc {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// Backward compatibility: existing normal JWT/APIToken flows without scopes continue to work.
|
||||
if len(claims.Scopes) == 0 {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if !scopeIncludes(claims.Scopes, requiredScope) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"message": "Missing Proper Scope"})
|
||||
c.Abort()
|
||||
|
||||
@@ -63,13 +63,13 @@ func RefreshJWT(access_token string, refresh_token string) (string, string, int,
|
||||
RefreshTokenCacheLock.RUnlock()
|
||||
if !ok {
|
||||
err := ErrFailedToFindRefreshToken
|
||||
logging.LogError(err, "access_token", access_token)
|
||||
logging.LogError(err, "Failed to refresh an access token and find it within RefreshTokenCache")
|
||||
return "", "", 0, err
|
||||
}
|
||||
|
||||
if storedRefresh != refresh_token {
|
||||
err := ErrRefreshTokenMissmatch
|
||||
logging.LogError(err, "refresh_token", refresh_token, "storedRefreshToken", storedRefresh)
|
||||
logging.LogError(err, "Supplied refresh token doesn't match what Mythic has")
|
||||
return "", "", 0, err
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func GenerateJWT(user databaseStructs.Operator, authMethod string, eventStepInst
|
||||
eventStepInstanceID,
|
||||
APITokensID,
|
||||
int(user.CurrentOperationID.Int64),
|
||||
nil,
|
||||
[]string{SCOPE_FILE_DIRECT_UPLOAD, SCOPE_FILE_DIRECT_DOWNLOAD},
|
||||
"",
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
@@ -2,9 +2,10 @@ package webcontroller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/its-a-feature/Mythic/rabbitmq"
|
||||
"net/http"
|
||||
|
||||
"github.com/its-a-feature/Mythic/rabbitmq"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/its-a-feature/Mythic/database"
|
||||
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
|
||||
@@ -23,20 +24,22 @@ func FileDirectDownloadWebhook(c *gin.Context) {
|
||||
// get the associated database information
|
||||
filemeta := databaseStructs.Filemeta{}
|
||||
payload := databaseStructs.Payload{}
|
||||
operatorUsername := "unknown"
|
||||
userID, err := GetUserIDFromGin(c)
|
||||
if err == nil {
|
||||
user, err := database.GetUserFromID(userID)
|
||||
if err == nil {
|
||||
operatorUsername = user.Username
|
||||
}
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
user, err := database.GetUserFromID(userID)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
err = database.DB.Get(&filemeta, `SELECT
|
||||
"path", filename, id, operation_id
|
||||
FROM filemeta
|
||||
WHERE
|
||||
filemeta.agent_file_id=$1 and deleted=false
|
||||
`, agentFileID)
|
||||
filemeta.agent_file_id=$1 and deleted=false and filemeta.operation_id=$2
|
||||
`, agentFileID, user.CurrentOperationID.Int64)
|
||||
if err != nil {
|
||||
err = database.DB.Get(&payload, `SELECT
|
||||
filemeta.path "filemeta.path",
|
||||
@@ -45,7 +48,7 @@ func FileDirectDownloadWebhook(c *gin.Context) {
|
||||
filemeta.operation_id "filemeta.operation_id"
|
||||
FROM payload
|
||||
JOIN filemeta ON payload.file_id = filemeta.id
|
||||
WHERE payload.uuid=$1`, agentFileID)
|
||||
WHERE payload.uuid=$1 and payload.operation_id=$2`, agentFileID, user.CurrentOperationID.Int64)
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to get file data from database")
|
||||
message := fmt.Sprintf("Attempt to download unknown file: %s", agentFileID)
|
||||
@@ -53,11 +56,11 @@ func FileDirectDownloadWebhook(c *gin.Context) {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
go tagFileAs(payload.Filemeta.ID, operatorUsername, payload.Filemeta.OperationID, tagTypeDownload, nil, c, false)
|
||||
go tagFileAs(payload.Filemeta.ID, user.Username, payload.Filemeta.OperationID, tagTypeDownload, nil, c, false)
|
||||
c.FileAttachment(payload.Filemeta.Path, string(payload.Filemeta.Filename))
|
||||
return
|
||||
}
|
||||
go tagFileAs(filemeta.ID, operatorUsername, filemeta.OperationID, tagTypeDownload, nil, c, false)
|
||||
go tagFileAs(filemeta.ID, user.Username, filemeta.OperationID, tagTypeDownload, nil, c, false)
|
||||
c.FileAttachment(filemeta.Path, string(filemeta.Filename))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -30,11 +30,21 @@ func FileDirectUploadWebhook(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
filemeta := databaseStructs.Filemeta{}
|
||||
userID, err := GetUserIDFromGin(c)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
user, err := database.GetUserFromID(userID)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
err = database.DB.Get(&filemeta, `SELECT
|
||||
"path", id
|
||||
FROM
|
||||
filemeta
|
||||
WHERE agent_file_id=$1`, agentFileID)
|
||||
WHERE agent_file_id=$1 AND operation_id=$2`, agentFileID, user.CurrentOperationID.Int64)
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to find file in FileDirectUploadWebhook", "agentFileID", agentFileID)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
|
||||
@@ -2,9 +2,10 @@ package webcontroller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/its-a-feature/Mythic/rabbitmq"
|
||||
"net/http"
|
||||
|
||||
"github.com/its-a-feature/Mythic/rabbitmq"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/its-a-feature/Mythic/database"
|
||||
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
|
||||
@@ -23,20 +24,22 @@ func FileDirectViewWebhook(c *gin.Context) {
|
||||
// get the associated database information
|
||||
filemeta := databaseStructs.Filemeta{}
|
||||
payload := databaseStructs.Payload{}
|
||||
operatorUsername := "unknown"
|
||||
userID, err := GetUserIDFromGin(c)
|
||||
if err == nil {
|
||||
user, err := database.GetUserFromID(userID)
|
||||
if err == nil {
|
||||
operatorUsername = user.Username
|
||||
}
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
user, err := database.GetUserFromID(userID)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
err = database.DB.Get(&filemeta, `SELECT
|
||||
"path", filename, id, operation_id
|
||||
FROM filemeta
|
||||
WHERE
|
||||
filemeta.agent_file_id=$1
|
||||
`, agentFileID)
|
||||
filemeta.agent_file_id=$1 AND filemeta.operation_id=$2
|
||||
`, agentFileID, user.CurrentOperationID.Int64)
|
||||
if err != nil {
|
||||
err = database.DB.Get(&payload, `SELECT
|
||||
filemeta.path "filemeta.path",
|
||||
@@ -45,7 +48,7 @@ func FileDirectViewWebhook(c *gin.Context) {
|
||||
filemeta.operation_id "filemeta.operation_id"
|
||||
FROM payload
|
||||
JOIN filemeta ON payload.file_id = filemeta.id
|
||||
WHERE payload.uuid=$1`, agentFileID)
|
||||
WHERE payload.uuid=$1 AND payload.operation_id=$2`, agentFileID, user.CurrentOperationID.Int64)
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to get file data from database")
|
||||
message := fmt.Sprintf("Attempt to download unknown file: %s", agentFileID)
|
||||
@@ -53,11 +56,11 @@ func FileDirectViewWebhook(c *gin.Context) {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
go tagFileAs(payload.Filemeta.ID, operatorUsername, payload.Filemeta.OperationID, tagTypePreview, nil, c, false)
|
||||
go tagFileAs(payload.Filemeta.ID, user.Username, payload.Filemeta.OperationID, tagTypePreview, nil, c, false)
|
||||
c.File(payload.Filemeta.Path)
|
||||
return
|
||||
}
|
||||
go tagFileAs(filemeta.ID, operatorUsername, filemeta.OperationID, tagTypePreview, nil, c, false)
|
||||
go tagFileAs(filemeta.ID, user.Username, filemeta.OperationID, tagTypePreview, nil, c, false)
|
||||
c.File(filemeta.Path)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -190,22 +190,22 @@ func setRoutes(r *gin.Engine) {
|
||||
// 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",
|
||||
authentication.DirectFileScopeMiddleware(mythicjwt.SCOPE_FILE_DIRECT_DOWNLOAD),
|
||||
webcontroller.FileDirectViewWebhook)
|
||||
// authenticated direct file routes.
|
||||
// Supports normal JWT/APIToken auth and scoped short-lived direct-file tokens.
|
||||
r.GET("/direct/download/:file_uuid",
|
||||
authentication.DirectFileScopeMiddleware(mythicjwt.SCOPE_FILE_DIRECT_DOWNLOAD),
|
||||
webcontroller.FileDirectDownloadWebhook)
|
||||
r.POST("/direct/upload/:file_uuid",
|
||||
authentication.DirectFileScopeMiddleware(mythicjwt.SCOPE_FILE_DIRECT_UPLOAD),
|
||||
webcontroller.FileDirectUploadWebhook)
|
||||
// following require you to have an operation set
|
||||
allOperationMembers := protected.Group("/")
|
||||
allOperationMembers.Use(authentication.RBACMiddlewareAll())
|
||||
{
|
||||
// generic all installed services
|
||||
protected.GET("/direct/view/:file_uuid",
|
||||
authentication.DirectFileScopeMiddleware(mythicjwt.SCOPE_FILE_DIRECT_DOWNLOAD),
|
||||
webcontroller.FileDirectViewWebhook)
|
||||
// authenticated direct file routes.
|
||||
// Supports normal JWT/APIToken auth and scoped short-lived direct-file tokens.
|
||||
r.GET("/direct/download/:file_uuid",
|
||||
authentication.DirectFileScopeMiddleware(mythicjwt.SCOPE_FILE_DIRECT_DOWNLOAD),
|
||||
webcontroller.FileDirectDownloadWebhook)
|
||||
r.POST("/direct/upload/:file_uuid",
|
||||
authentication.DirectFileScopeMiddleware(mythicjwt.SCOPE_FILE_DIRECT_UPLOAD),
|
||||
webcontroller.FileDirectUploadWebhook)
|
||||
allOperationMembers.POST("eventing_import_automatic_webhook", webcontroller.EventingImportAutomaticWebhook)
|
||||
// payloadtype / c2profile
|
||||
allOperationMembers.POST("c2profile_status_webhook", webcontroller.C2ProfileStatusWebhook)
|
||||
|
||||
Reference in New Issue
Block a user