Compare commits

...

6 Commits

Author SHA1 Message Date
github-actions b91bd3eae2 Bump Dockerfile tag to match release 'v0.0.2.5' 2024-09-13 20:30:34 +00:00
its-a-feature 200bb3f339 adding support to forward user-agent and full url 2024-09-13 15:24:06 -05:00
github-actions 7dbe513591 Bump Dockerfile tag to match release 'v0.0.2.4' 2024-09-11 22:06:48 +00:00
its-a-feature 68f4c32f00 fixing rewrite rules with multiple bound ports 2024-09-11 17:00:09 -05:00
github-actions 95ff364a78 Bump Dockerfile tag to match release 'v0.0.2.3' 2024-09-10 20:07:09 +00:00
its-a-feature 4d11b7b43d updating to return custom error code/files
added error_file_path and error_status_code options
2024-09-10 15:00:31 -05:00
10 changed files with 107 additions and 47 deletions
+2 -2
View File
@@ -20,10 +20,10 @@ run:
cd http/c2_code && make run
./${BINARY_NAME}
build_macos:
local:
CGO_ENABLED=0 go build -o ${BINARY_NAME} .
run_custom: build_macos
run_custom: local
DEBUG_LEVEL=${DEBUG_LEVEL} \
RABBITMQ_HOST=${RABBITMQ_HOST} \
RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} \
+6 -6
View File
@@ -6,7 +6,7 @@ toolchain go1.22.2
//replace github.com/MythicMeta/MythicContainer => ../../../../MythicMeta/MythicContainer
require github.com/MythicMeta/MythicContainer v1.4.1
require github.com/MythicMeta/MythicContainer v1.4.5
require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
@@ -28,11 +28,11 @@ require (
github.com/spf13/viper v1.19.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.66.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
+12
View File
@@ -2,6 +2,8 @@ github.com/MythicMeta/MythicContainer v1.4.0 h1:vy8LsoSu5JBIEMWZn9ZnwQUqFvR6/35k
github.com/MythicMeta/MythicContainer v1.4.0/go.mod h1:KMFetMj5A+s3IKt+oSgIFyQRXashXZzCABRnaETzPeY=
github.com/MythicMeta/MythicContainer v1.4.1 h1:/2jixKxwrPjN+qT14ZZNogXMHp9bfLfxVuEsEfqsCaE=
github.com/MythicMeta/MythicContainer v1.4.1/go.mod h1:f9nIBe8teDUW+NP/jYw9Otor1RZHXpC/4cxMv88+KQE=
github.com/MythicMeta/MythicContainer v1.4.5 h1:bnXH3eA3NXpENk5ljUi9j91M2nfupztzfPxLArsVwng=
github.com/MythicMeta/MythicContainer v1.4.5/go.mod h1:f9nIBe8teDUW+NP/jYw9Otor1RZHXpC/4cxMv88+KQE=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -82,10 +84,14 @@ golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJI
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -93,14 +99,20 @@ golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240709173604-40e1e62336c5 h1:SbSDUWW1PAO24TNpLdeheoYPd7kllICcLU52x6eD4kQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240709173604-40e1e62336c5/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
+1 -1
View File
@@ -19,7 +19,7 @@ run:
build_macos:
CGO_ENABLED=0 go build -o ${BINARY_NAME} .
run_custom:
run_custom: build_local
DEBUG_LEVEL=${DEBUG_LEVEL} \
MYTHIC_SERVER_HOST=${MYTHIC_SERVER_HOST} \
MYTHIC_SERVER_PORT=${MYTHIC_SERVER_PORT} \
+19 -18
View File
@@ -1,20 +1,21 @@
{
"instances": [
{
"port": 80,
"key_path": "privkey.pem",
"cert_path": "fullchain.pem",
"debug": false,
"use_ssl": false,
"ServerHeaders": {
"Cache-Control": "max-age=0, no-cache",
"Connection": "keep-alive",
"Content-Type": "application/javascript; charset=utf-8",
"Pragma": "no-cache",
"Server": "NetDNA-cache/2.2"
},
"payloads": {
}
}
]
"instances": [
{
"port": 80,
"key_path": "privkey.pem",
"cert_path": "fullchain.pem",
"debug": true,
"use_ssl": false,
"ServerHeaders": {
"Cache-Control": "max-age=0, no-cache",
"Connection": "keep-alive",
"Content-Type": "application/javascript; charset=utf-8",
"Pragma": "no-cache",
"Server": "NetDNA-cache/2.2"
},
"error_file_path": "",
"error_status_code": 404,
"payloads": {}
}
]
}
@@ -20,6 +20,8 @@ type instanceConfig struct {
Headers map[string]string `json:"ServerHeaders"`
PayloadHostPaths map[string]string `json:"payloads"`
BindIP string `json:"bind_ip"`
ErrorFilePath string `json:"error_file_path"`
ErrorStatusCode int `json:"error_status_code"`
}
var (
@@ -9,6 +9,7 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"net/http"
@@ -25,16 +26,17 @@ func Initialize(configInstance instanceConfig) *gin.Engine {
//if mythicConfig.MythicConfig.DebugLevel == "warning" {
// gin.SetMode(gin.ReleaseMode)
//} else {
gin.DisableConsoleColor()
gin.SetMode(gin.DebugMode)
//}
r := gin.New()
gin.DisableConsoleColor()
// Global middleware
r.Use(InitializeGinLogger(configInstance))
// Recovery middleware recovers from any panics and writes a 500 if there was one.
r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
if err, ok := recovered.(string); ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("error: %s", err)})
logging.LogError(nil, err)
}
c.AbortWithStatus(http.StatusInternalServerError)
}))
@@ -129,13 +131,43 @@ func InitializeGinLogger(configInstance instanceConfig) gin.HandlerFunc {
func setRoutes(r *gin.Engine, configInstance instanceConfig) {
// define generic get/post routes
director := func(req *http.Request) {
req.Header.Add("mythic", "http")
req.Header.Add("X-forwarded-user-agent", req.Header.Get("User-Agent"))
req.Header.Add("x-forwarded-url", req.URL.RequestURI())
req.URL.Scheme = "http"
req.URL.Host = fmt.Sprintf("%s:%d", mythicConfig.MythicConfig.MythicServerHost, mythicConfig.MythicConfig.MythicServerPort)
req.Host = fmt.Sprintf("%s:%d", mythicConfig.MythicConfig.MythicServerHost, mythicConfig.MythicConfig.MythicServerPort)
req.URL.Path = "/agent_message"
req.Header.Add("mythic", "http")
}
proxy := &httputil.ReverseProxy{Director: director,
modifyResponse := func(resp *http.Response) error {
//logging.LogInfo("hitting modify response", "responseCode", resp.StatusCode)
if resp.StatusCode != http.StatusOK {
if configInstance.ErrorFilePath != "" {
statusCode := 200
if configInstance.ErrorStatusCode > 0 {
statusCode = configInstance.ErrorStatusCode
}
file, err := os.Open(configInstance.ErrorFilePath)
if err != nil {
logging.LogError(err, "failed to get error_file_path")
return err
}
fileStat, err := file.Stat()
if err != nil {
logging.LogError(err, "failed to stat error_file_path")
return err
}
resp.Body = io.NopCloser(file)
resp.Header["Content-Length"] = []string{fmt.Sprint(fileStat.Size())}
resp.StatusCode = statusCode
return nil
}
}
return nil
}
proxy := &httputil.ReverseProxy{
Director: director,
ModifyResponse: modifyResponse,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
@@ -154,13 +186,17 @@ func setRoutes(r *gin.Engine, configInstance instanceConfig) {
for path, value := range configInstance.PayloadHostPaths {
localVal := value
directorForFiles := func(req *http.Request) {
req.Header.Add("mythic", "http")
req.Header.Add("X-forwarded-user-agent", req.Header.Get("User-Agent"))
req.Header.Add("x-forwarded-url", req.URL.RequestURI())
req.URL.Scheme = "http"
req.URL.Host = fmt.Sprintf("%s:%d", mythicConfig.MythicConfig.MythicServerHost, mythicConfig.MythicConfig.MythicServerPort)
req.Host = fmt.Sprintf("%s:%d", mythicConfig.MythicConfig.MythicServerHost, mythicConfig.MythicConfig.MythicServerPort)
req.URL.Path = fmt.Sprintf("/direct/download/%s", localVal)
req.Header.Add("mythic", "http")
}
proxyForFiles := httputil.ReverseProxy{Director: directorForFiles,
proxyForFiles := httputil.ReverseProxy{
Director: directorForFiles,
ModifyResponse: modifyResponse,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
+13 -7
View File
@@ -207,19 +207,25 @@ RewriteEngine On
## Logic: If a requested URI AND the User-Agent matches, proxy the connection to the Teamserver
## Consider adding other HTTP checks to fine tune the check. (HTTP Cookie, HTTP Referer, HTTP Query String, etc)
## Refer to http://httpd.apache.org/docs/current/mod/mod_rewrite.html
## Only allow GET and POST methods to pass to the C2 server
RewriteCond %%{REQUEST_METHOD} ^(GET|POST) [NC]
## Profile URIs
RewriteCond %%{REQUEST_URI} ^(%s)$
## Profile UserAgent
RewriteCond %%{HTTP_USER_AGENT} "%s"
%s
## Redirect all other traffic here
RewriteRule ^.*$ redirect/? [L,R=302]
## .htaccess END
########################################
`
htaccess := fmt.Sprintf(htaccessTemplate, urisString, uaString, strings.Join(c2RewriteOutput, "\n"))
htaccessConditionTemplate := `
## Only allow GET and POST methods to pass to the C2 server
RewriteCond %%{REQUEST_METHOD} ^(GET|POST) [NC]
## Profile URIs
RewriteCond %%{REQUEST_URI} ^(%s)$
## Profile UserAgent
RewriteCond %%{HTTP_USER_AGENT} "%s"`
htaccessConditions := fmt.Sprintf(htaccessConditionTemplate, urisString, uaString)
allHtaccessConditions := ""
for _, c2Entry := range c2RewriteOutput {
allHtaccessConditions += htaccessConditions + "\n" + c2Entry + "\n"
}
htaccess := fmt.Sprintf(htaccessTemplate, allHtaccessConditions)
output += "#\tReplace 'redirect' with the http(s) address of where non-matching traffic should go, ex: https://redirect.com\n"
output += "\n" + htaccess
response.Message = output
+1 -1
View File
@@ -5,6 +5,6 @@
"exclude_documentation_c2": false,
"exclude_agent_icons": false,
"remote_images": {
"http": "ghcr.io/mythicc2profiles/http:v0.0.2.2"
"http": "ghcr.io/mythicc2profiles/http:v0.0.2.5"
}
}
+9 -6
View File
@@ -25,7 +25,7 @@ Legend:
- Dotted line is a message within that connection
## Configuration Options
The profile reads a `config.json` file for a set of instances of `Sanic` webservers to stand up (`80` by default) and redirects the content.
The profile reads a `config.json` file for a set of instances of `gin` webservers to stand up (`80` by default) and redirects the content.
```JSON
{
@@ -42,15 +42,20 @@ The profile reads a `config.json` file for a set of instances of `Sanic` webserv
"key_path": "privkey.pem",
"cert_path": "fullchain.pem",
"debug": true,
"use_ssl": false
"use_ssl": false,
"error_file_path": "",
"error_status_code": 404,
"payloads": {}
}
]
}
```
You can specify the headers that the profile will set on Server responses. If there's an error, the server will return a `404` message based on the `fake.html` file contents in `C2_Profiles/HTTP/c2_code`.
You can specify the headers that the profile will set on Server responses.
If there's an error, the server will return a `error_status_code` message based on the `error_file_path` file contents in `C2_Profiles/HTTP/c2_code`.
If you want to use SSL within this container specifically, then you can put your key and cert in the `C2_Profiles/HTTP/c2_code` folder and update the `key_path` and `cert_path` variables to have the `names` of those files. Alternatively, if you specify `use_ssl` as true and you don't have any certs already placed on disk, then the profile will automatically generate some self-signed certs for you to use.
If you want to use SSL within this container specifically, then you can put your key and cert in the `C2_Profiles/HTTP/c2_code` folder and update the `key_path` and `cert_path` variables to have the `names` of those files.
Alternatively, if you specify `use_ssl` as true and you don't have any certs already placed on disk, then the profile will automatically generate some self-signed certs for you to use.
You should get a notification when the server starts with information about the configuration:
```
@@ -115,5 +120,3 @@ If you need to manually specify a proxy endpoint, this is where you specify the
This profile doesn't do any randomization of network components outside of allowing operators to specify internals/jitter. Every GET request for tasking will be the same. This is important to take into consideration for profiling/beaconing analytics.
## Development
All of the code for the server is Python3 using `Sanic` and located in `C2_Profiles/HTTP/c2_code/server`. It loops through the `instances` in the `config.json` file and stands up those individual web servers.