diff --git a/.gitattributes b/.gitattributes index e6b32a4..bf09970 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,2 @@ *.go linguist-language=Go -*.json linguist-language=Javascript -vendor/* linguist-vendored -docs/* linguist-documentation -data/bin/powershell/Invoke-Merlin.ps1 linguist-detectable=false \ No newline at end of file +docs/* linguist-documentation \ No newline at end of file diff --git a/.gitignore b/.gitignore index de43f84..fb300ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,5 @@ .DS_STORE data/agents data/temp -data/db data/log -data/bin/darwin -data/bin/dll/merlin.dll -data/bin/linux -data/bin/windows .idea/ \ No newline at end of file diff --git a/docs/MerlinCheatSheet.pdf b/docs/MerlinCheatSheet.pdf deleted file mode 100644 index 65cc6be..0000000 Binary files a/docs/MerlinCheatSheet.pdf and /dev/null differ diff --git a/test/testServer/main.go b/test/testServer/main.go deleted file mode 100644 index 5913f7d..0000000 --- a/test/testServer/main.go +++ /dev/null @@ -1,360 +0,0 @@ -// Merlin is a post-exploitation command and control framework. -// This file is part of Merlin. -// Copyright (C) 2021 Russel Van Tuyl - -// Merlin is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. - -// Merlin is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Merlin. If not, see . - -package testserver - -import ( - // Standard - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/gob" - "fmt" - "io/ioutil" - "log" - "math/big" - "net" - "net/http" - "strings" - "testing" - "time" - - // 3rd Party - "github.com/cretz/gopaque/gopaque" - "github.com/satori/go.uuid" - "gopkg.in/square/go-jose.v2" - "gopkg.in/square/go-jose.v2/jwt" - - // Merlin - "github.com/Ne0nd0g/merlin/pkg/agents" - "github.com/Ne0nd0g/merlin/pkg/core" - "github.com/Ne0nd0g/merlin/pkg/handlers" - "github.com/Ne0nd0g/merlin/pkg/messages" - "github.com/Ne0nd0g/merlin/pkg/opaque" - "github.com/Ne0nd0g/merlin/pkg/server/jobs" -) - -func (ts *TestServer) handler(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet && r.RequestURI == "/isup" { - w.WriteHeader(200) - return - } - - // Uses Merlin HTTP Handler - if r.RequestURI == "/merlin" { - ts.ctx.AgentHTTP(w, r) - } - - // Make sure the message has a JWT - token := r.Header.Get("Authorization") - if token == "" { - fmt.Println("the request did not contain an Authorization header") - w.WriteHeader(404) - return - } - //Read the request message until EOF - requestBytes, errRequestBytes := ioutil.ReadAll(r.Body) - if errRequestBytes != nil { - fmt.Printf("there was an error reading the request message:\r\n%s\r\n", errRequestBytes.Error()) - w.WriteHeader(500) - return - } - - // Decode gob to JWE string - var jweString string - errDecode := gob.NewDecoder(bytes.NewReader(requestBytes)).Decode(&jweString) - if errDecode != nil { - fmt.Printf("there was an error decoding the message from gob to JWE string:\r\n%s\r\n", errDecode.Error()) - w.WriteHeader(500) - return - } - - // Validate JWT and get claims - var agentID uuid.UUID - var errValidate error - - agentID, errValidate = validateJWT(strings.Split(token, " ")[1], []byte("xZF7fvaGD6p2yeLyf9i7O9gBBHk05B0u")) - if errValidate != nil { - // Validate JWT using interface PSK; Used by unauthenticated agents - hashedKey := sha256.Sum256([]byte("test")) - key := hashedKey[:] - agentID, errValidate = validateJWT(strings.Split(token, " ")[1], key) - if errValidate != nil { - fmt.Printf("there was an error validating the JWT:\n%s\n", errValidate) - w.WriteHeader(404) - return - } - } - - key, errKey := agents.GetEncryptionKey(agentID) - if errKey != nil { - fmt.Printf("there was an error getting the Agent's decryption key:\n%s\n", errKey) - // Use the default key when the agent isn't registered - k := sha256.Sum256([]byte("test")) - key = k[:] - } - - // Decrypt JWE - j, errDecryptPSK := decryptJWE(jweString, key) - if errDecryptPSK != nil { - fmt.Printf("there was an error decrypting the JWE on the server:\r\n%s\r\n", errDecryptPSK.Error()) - w.WriteHeader(500) - return - } - - //fmt.Println(fmt.Sprintf("Request: %+v\nBody:%+v", r, j)) //uncomment here if you want to print out exactly what the test server receives - - var returnMessage messages.Base - var err error - - // User Agent based actions - switch r.UserAgent() { - case "invalidMessageBaseType": - returnMessage.Type = 200 - } - - // Message type based action - switch j.Type { - case messages.JOBS: - returnMessage, err = jobs.Handler(j) - case messages.OPAQUE: - if j.Payload.(opaque.Opaque).Type == opaque.AuthComplete { - returnMessage, err = handlers.OPAQUEHandler(j.ID, j.Payload.(opaque.Opaque)) - } else { - returnMessage, err = handlers.OPAQUEUnAuthHandler(j.ID, j.Payload.(opaque.Opaque), gopaque.CryptoDefault.NewKey(nil)) - } - case 201: - w.Header().Set("Content-Type", "application/octet-stream") - errBadPayload := gob.NewEncoder(w).Encode([]byte("Hack the planet!")) - if errBadPayload != nil { - fmt.Println(errBadPayload.Error()) - } - default: - - } - - if err != nil { - log.Fatal(err) - } - returnMessage.ID = agentID - // Encode messages.Base into a gob - returnMessageBytes := new(bytes.Buffer) - errReturnMessageBytes := gob.NewEncoder(returnMessageBytes).Encode(returnMessage) - if errReturnMessageBytes != nil { - fmt.Printf("there was an error encoding the return message into a gob:\r\n%s\r\n", errReturnMessageBytes.Error()) - w.WriteHeader(500) - return - } - - // Get JWE - jwe, errJWE := core.GetJWESymetric(returnMessageBytes.Bytes(), key[:]) - if errJWE != nil { - fmt.Printf("there was an error encrypting the message into a JWE:\r\n%s\r\n", errJWE.Error()) - w.WriteHeader(500) - return - } - - // Encode JWE to GOB and send it to the agent - w.Header().Set("Content-Type", "application/octet-stream") - errEncode := gob.NewEncoder(w).Encode(jwe) - if errEncode != nil { - fmt.Printf("there was an error encoding the JWE into a gob:\r\n%s\r\n", errEncode.Error()) - w.WriteHeader(500) - return - } -} - -//TestServer is a webserver instance that facilitates functional testing of code that requires the ability to send web requests -type TestServer struct { - tes *testing.T - ctx handlers.HTTPContext -} - -//since tls/pki is such a pain this generate them every time -func generateTLSConfig() *tls.Config { - //https://golang.org/src/crypto/tls/generate_cert.go taken from here mostly - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - log.Fatalf("failed to generate serial number: %s", err) - } - tpl := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - CommonName: "127.0.0.1", - Organization: []string{"Joey is the best hacker in Hackers"}, - }, - IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, - DNSNames: []string{"127.0.0.1", "localhost"}, - NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Minute * 20), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - priv, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - panic(err) - } - crtBytes, e := x509.CreateCertificate(rand.Reader, &tpl, &tpl, priv.Public(), priv) - if e != nil { - panic(e) - } - - crt := tls.Certificate{ - Certificate: [][]byte{crtBytes}, - PrivateKey: priv, - } - /* #nosec G402 */ - // G402: TLS InsecureSkipVerify set true. (Confidence: HIGH, Severity: HIGH) Allowed for testing - // G402 (CWE-295): TLS MinVersion too low. (Confidence: HIGH, Severity: HIGH) - // TLS version is not configured to facilitate dynamic JA3 configurations - return &tls.Config{ - Certificates: []tls.Certificate{crt}, - NextProtos: []string{"h2", "hq"}, - } -} - -//Start starts the test HTTP server on the input port -func (TestServer) Start(port string, finishedTest, setup chan struct{}, t *testing.T) { - s := http.NewServeMux() - ts := TestServer{ - tes: t, - } - s.HandleFunc("/", ts.handler) - srv := http.Server{} - - srv.TLSConfig = generateTLSConfig() - srv.Handler = s - srv.Addr = "127.0.0.1:" + port - - ctx := handlers.HTTPContext{ - PSK: "test", - JWTKey: []byte(core.RandStringBytesMaskImprSrc(32)), - OpaqueKey: gopaque.CryptoDefault.NewKey(nil), - } - ts.ctx = ctx - go func() { - ln, e := net.Listen("tcp", srv.Addr) - - defer func() { - err := ln.Close() - if err != nil { - log.Fatal(err) - } - }() - - if e != nil { - panic(e) - } - tlsListener := tls.NewListener(ln, srv.TLSConfig) - e = srv.Serve(tlsListener) - if e != nil { //should be set by the tls config - panic(e) - } - }() - for { - time.Sleep(time.Second * 1) - /* #nosec G402 */ - // G402: TLS InsecureSkipVerify set true. (Confidence: HIGH, Severity: HIGH) Allowed for testing - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - } - resp, err := client.Get("https://localhost:" + port + "/isup") - if err != nil { - continue - } - errC := resp.Body.Close() - if errC != nil { - log.Fatalf("There was an error closing the body:\r\n%s", errC) - } - if resp.StatusCode != http.StatusOK { - continue - } - // Reached this point: server is up and running! - break - } - - close(setup) - <-finishedTest //this is an ultra gross hack :( -} - -// decryptJWE takes provided JWE string and decrypts it using the per-agent key -func decryptJWE(jweString string, key []byte) (messages.Base, error) { - var m messages.Base - - // Parse JWE string back into JSONWebEncryption - jwe, errObject := jose.ParseEncrypted(jweString) - if errObject != nil { - return m, fmt.Errorf("there was an error parseing the JWE string into a JSONWebEncryption object:\r\n%s", errObject) - } - - // Decrypt the JWE - jweMessage, errDecrypt := jwe.Decrypt(key) - if errDecrypt != nil { - return m, fmt.Errorf("there was an error decrypting the JWE:\r\n%s", errDecrypt.Error()) - } - - // Decode the JWE payload into a messages.Base struct - errDecode := gob.NewDecoder(bytes.NewReader(jweMessage)).Decode(&m) - if errDecode != nil { - return m, fmt.Errorf("there was an error decoding JWE payload message sent by an agent:\r\n%s", errDecode.Error()) - } - - return m, nil -} - -// validateJWT validates the provided JSON Web Token -func validateJWT(agentJWT string, key []byte) (uuid.UUID, error) { - var agentID uuid.UUID - - claims := jwt.Claims{} - - // Parse to make sure it is a valid JWT - nestedToken, err := jwt.ParseSignedAndEncrypted(agentJWT) - if err != nil { - return agentID, fmt.Errorf("there was an error parsing the JWT:\r\n%s", err.Error()) - } - - // Decrypt JWT - token, errToken := nestedToken.Decrypt(key) - if errToken != nil { - return agentID, fmt.Errorf("there was an error decrypting the JWT:\r\n%s", errToken.Error()) - } - - // Deserialize the claims and validate the signature - errClaims := token.Claims(key, &claims) - if errClaims != nil { - return agentID, fmt.Errorf("there was an deserializing the JWT claims:\r\n%s", errClaims.Error()) - } - - // Validate claims - errValidate := claims.Validate(jwt.Expected{ - Time: time.Now(), - }) - if errValidate != nil { - return agentID, errValidate - } - agentID = uuid.FromStringOrNil(claims.ID) - return agentID, nil -}