Skip to content

Commit

Permalink
Added "secure" flag to require TLS x.509 certificate validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Ne0nd0g committed Nov 2, 2023
1 parent fa25301 commit 0a70aa9
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 41 deletions.
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ USERAGENT = Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML,
XUSERAGENT =-X "main.useragent=$(USERAGENT)"
HEADERS =
XHEADERS =-X "main.headers=$(HEADERS)"
SECURE ?= true
XSECURE =-X "main.secure=${SECURE}"
SKEW ?= 3000
XSKEW=-X "main.skew=${SKEW}"
PAD ?= 4096
Expand All @@ -56,8 +58,8 @@ LISTENER ?=
XLISTENER=-X "main.listener=${LISTENER}"

# Compile Flags
LDFLAGS=-ldflags '-s -w ${XADDR} ${XAUTH} ${XTRANSFORMS} ${XLISTENER} ${XBUILD} ${XPROTO} ${XURL} ${XHOST} ${XPSK} ${XSLEEP} ${XPROXY} $(XUSERAGENT) $(XHEADERS) ${XSKEW} ${XPAD} ${XKILLDATE} ${XRETRY} ${XPARROT} -buildid='
WINAGENTLDFLAGS=-ldflags '-s -w ${XAUTH} ${XADDR} ${XTRANSFORMS} ${XLISTENER} ${XBUILD} ${XPROTO} ${XURL} ${XHOST} ${XPSK} ${XSLEEP} ${XPROXY} $(XUSERAGENT) $(XHEADERS) ${XSKEW} ${XPAD} ${XKILLDATE} ${XRETRY} ${XPARROT} -H=windowsgui -buildid='
LDFLAGS=-ldflags '-s -w ${XADDR} ${XAUTH} ${XTRANSFORMS} ${XLISTENER} ${XBUILD} ${XPROTO} ${XURL} ${XHOST} ${XPSK} ${XSECURE} ${XSLEEP} ${XPROXY} $(XUSERAGENT) $(XHEADERS) ${XSKEW} ${XPAD} ${XKILLDATE} ${XRETRY} ${XPARROT} -buildid='
WINAGENTLDFLAGS=-ldflags '-s -w ${XAUTH} ${XADDR} ${XTRANSFORMS} ${XLISTENER} ${XBUILD} ${XPROTO} ${XURL} ${XHOST} ${XPSK} ${XSECURE} ${XSLEEP} ${XPROXY} $(XUSERAGENT) $(XHEADERS) ${XSKEW} ${XPAD} ${XKILLDATE} ${XRETRY} ${XPARROT} -H=windowsgui -buildid='
GCFLAGS=-gcflags=all=-trimpath=$(GOPATH)
ASMFLAGS=-asmflags=all=-trimpath=$(GOPATH)# -asmflags=-trimpath=$(GOPATH)

Expand Down
79 changes: 43 additions & 36 deletions clients/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ import (
// Client is a type of MerlinClient that is used to send and receive Merlin messages from the Merlin server
type Client struct {
Authenticator authenticators.Authenticator
authenticated bool // authenticated tracks if the Agent has successfully authenticated
Client *http.Client // Client to send messages with
Protocol string
authenticated bool // authenticated tracks if the Agent has successfully authenticated
Client *http.Client // Client to send messages with
Protocol string // Protocol contains the transportation protocol the agent is using (i.e., http2 or smb-reverse)
URL []string // A slice of URLs to send messages to (e.g., https://127.0.0.1:443/test.php)
Host string // HTTP Host header value
Proxy string // Proxy string
Expand All @@ -93,13 +93,14 @@ type Client struct {
AgentID uuid.UUID // AgentID the Agent's unique identifier
currentURL int // the current URL the agent is communicating with
transformers []transformer.Transformer // Transformers an ordered list of transforms (encoding/encryption) to apply when constructing a message
insecureTLS bool // insecureTLS is a boolean that determines if the InsecureSkipVerify flag is set to true or false
sync.Mutex
}

// Config is a structure that is used to pass in all necessary information to instantiate a new Client
// Config is a structure used to pass in all necessary information to instantiate a new Client
type Config struct {
AgentID uuid.UUID // AgentID the Agent's UUID
Protocol string // Protocol contains the transportation protocol the agent is using (i.e. http2 or http3)
Protocol string // Protocol contains the transportation protocol the agent is using (i.e., http2 or smb-reverse)
Host string // Host is used with the HTTP Host header for Domain Fronting activities
Headers string // Headers is a new-line separated string of additional HTTP headers to add to client requests
URL []string // URL is the protocol, domain, and page that the agent will communicate with (e.g., https://google.com/test.aspx)
Expand All @@ -112,22 +113,24 @@ type Config struct {
AuthPackage string // AuthPackage is the type of authentication the agent should use when communicating with the server
Opaque []byte // Opaque is the byte representation of the EnvU object used with the OPAQUE protocol (future use)
Transformers string // Transformers is an ordered comma seperated list of transforms (encoding/encryption) to apply when constructing a message
InsecureTLS bool // InsecureTLS is a boolean that determines if the InsecureSkipVerify flag is set to true or false
}

// New instantiates and returns a Client that is constructed from the passed in Config
// New instantiates and returns a Client constructed from the passed in Config
func New(config Config) (*Client, error) {
cli.Message(cli.DEBUG, "Entering into clients.http.New()...")
cli.Message(cli.DEBUG, fmt.Sprintf("Config: %+v", config))
client := Client{
AgentID: config.AgentID,
URL: config.URL,
UserAgent: config.UserAgent,
Host: config.Host,
Protocol: config.Protocol,
Proxy: config.Proxy,
JA3: config.JA3,
Parrot: config.Parrot,
psk: config.PSK,
AgentID: config.AgentID,
URL: config.URL,
UserAgent: config.UserAgent,
Host: config.Host,
Protocol: config.Protocol,
Proxy: config.Proxy,
JA3: config.JA3,
Parrot: config.Parrot,
psk: config.PSK,
insecureTLS: config.InsecureTLS,
}

// Authenticator
Expand Down Expand Up @@ -213,7 +216,7 @@ func New(config Config) (*Client, error) {
}

// Get the HTTP client
client.Client, err = getClient(client.Protocol, client.Proxy, client.JA3, client.Parrot)
client.Client, err = getClient(client.Protocol, client.Proxy, client.JA3, client.Parrot, client.insecureTLS)
if err != nil {
return &client, err
}
Expand All @@ -238,13 +241,13 @@ func New(config Config) (*Client, error) {
}

// getClient returns an HTTP client for the passed in protocol (i.e., h2 or http3)
func getClient(protocol string, proxyURL string, ja3 string, parrot string) (*http.Client, error) {
func getClient(protocol string, proxyURL string, ja3 string, parrot string, insecure bool) (*http.Client, error) {
cli.Message(cli.DEBUG, "Entering into clients.http.getClient()...")
cli.Message(cli.DEBUG, fmt.Sprintf("Protocol: %s, Proxy: %s, JA3 String: %s, Parrot: %s", protocol, proxyURL, ja3, parrot))
// Setup TLS configuration
TLSConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true, // #nosec G402 - see https://github.com/Ne0nd0g/merlin/issues/59 TODO fix this
InsecureSkipVerify: insecure,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
Expand Down Expand Up @@ -300,7 +303,7 @@ func getClient(protocol string, proxyURL string, ja3 string, parrot string) (*ht
TLSConfig.NextProtos = []string{"h3"} // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
transport = &http3.RoundTripper{
QuicConfig: &quic.Config{
// Opted for a long timeout to prevent the client from sending a PING Frame
// Opted for a long timeout to prevent the client from sending a PING Frame.
// If MaxIdleTimeout is too high, agent will never get an error if the server is offline and will perpetually run without exiting because MaxFailedCheckins is never incremented
MaxIdleTimeout: time.Second * 30,
// KeepAlivePeriod will send an HTTP/2 PING frame to keep the connection alive
Expand Down Expand Up @@ -354,7 +357,7 @@ func (client *Client) getJWT() (string, error) {
// Create encrypter
encrypter, encErr := jose.NewEncrypter(jose.A256GCM,
jose.Recipient{
Algorithm: jose.DIRECT, // Doesn't create a per message key
Algorithm: jose.DIRECT, // Doesn't create a per-message key
Key: key[:]},
(&jose.EncrypterOptions{}).WithType("JWT").WithContentType("JWT"))
if encErr != nil {
Expand Down Expand Up @@ -397,8 +400,8 @@ func (client *Client) Listen() (returnMessages []messages.Base, err error) {
return
}

// Send takes in a Merlin message structure, performs any encoding or encryption, and sends it to the server
// The function also decodes and decrypts response messages and return a Merlin message structure.
// Send takes in a Merlin message structure, performs any encoding or encryption, and sends it to the server.
// The function also decodes and decrypts response messages and returns a Merlin message structure.
// This is where the client's logic is for communicating with the server.
func (client *Client) Send(m messages.Base) (returnMessages []messages.Base, err error) {
cli.Message(cli.DEBUG, fmt.Sprintf("clients/http.Send(): Entering into function with message: %+v", m))
Expand Down Expand Up @@ -469,15 +472,18 @@ func (client *Client) Send(m messages.Base) (returnMessages []messages.Base, err
e = "Building new HTTP/3 client because received QUIC CONNECTION_CLOSE frame with NO_ERROR transport error code"
}

// Handshake timeout happens when a new client was not able to reach the server and setup a crypto handshake for the first time (no listener or no access)
// Handshake timeout happens when a new client was not able to reach the server and set up a crypto handshake for the first time (no listener or no access)
if strings.Contains(err.Error(), "NO_ERROR: Handshake did not complete in time") {
n = true
e = "Building new HTTP/3 client because QUIC HandshakeTimeout reached"
}

// No recent network activity happens when a PING timeout occurs. KeepAlive setting can be used to prevent MaxIdleTimeout
// When the client has previously established a crypto handshake but does not hear back from it's PING frame the server within the client's MaxIdleTimeout
// Typically happens when the Merlin Server application is killed/quit without sending a CONNECTION_CLOSE frame from stopping the listener
// No recent network activity happens when a PING timeout occurs.
// KeepAlive setting can be used to prevent MaxIdleTimeout.
// When the client has previously established a crypto handshake, but does not hear back from its PING frame,
// the server within the client's MaxIdleTimeout.
// Typically, it happens when the Merlin Server application is killed/quit without sending a
// CONNECTION_CLOSE frame from stopping the listener.
if strings.Contains(err.Error(), "NO_ERROR: No recent network activity") {
n = true
e = "Building new HTTP/3 client because QUIC MaxIdleTimeout reached"
Expand All @@ -488,7 +494,7 @@ func (client *Client) Send(m messages.Base) (returnMessages []messages.Base, err
if n {
cli.Message(cli.NOTE, e)
var errClient error
client.Client, errClient = getClient(client.Protocol, "", "", "")
client.Client, errClient = getClient(client.Protocol, "", "", "", client.insecureTLS)
if errClient != nil {
cli.Message(cli.WARN, fmt.Sprintf("there was an error getting a new HTTP/3 client: %s", errClient.Error()))
}
Expand Down Expand Up @@ -552,7 +558,7 @@ func (client *Client) Send(m messages.Base) (returnMessages []messages.Base, err
return
}

// Update the Agent's JWT if one was returned by the server in the response message
// Update the Agent's JWT if the server returned one in the response message
if respMessage.Token != "" {
client.JWT = respMessage.Token
}
Expand All @@ -561,7 +567,7 @@ func (client *Client) Send(m messages.Base) (returnMessages []messages.Base, err
return
}

// Set is a generic function that is used to modify a Client's field values
// Set is a generic function used to modify a Client's field values
func (client *Client) Set(key string, value string) (err error) {
cli.Message(cli.DEBUG, fmt.Sprintf("clients/http.Set(): entering into function with key: %s, value: %s", key, value))
defer cli.Message(cli.DEBUG, fmt.Sprintf("clients/http.Set(): exiting function with err: %v", err))
Expand All @@ -582,10 +588,10 @@ func (client *Client) Set(key string, value string) (err error) {
}
}
client.URL = urls
client.Client, err = getClient(client.Protocol, client.Proxy, client.JA3, client.Parrot)
client.Client, err = getClient(client.Protocol, client.Proxy, client.JA3, client.Parrot, client.insecureTLS)
case "ja3":
ja3String := strings.Trim(value, "\"'")
client.Client, err = getClient(client.Protocol, client.Proxy, ja3String, client.Parrot)
client.Client, err = getClient(client.Protocol, client.Proxy, ja3String, client.Parrot, client.insecureTLS)
if ja3String != "" {
cli.Message(cli.NOTE, fmt.Sprintf("Set agent JA3 signature to:%s", ja3String))
} else if ja3String == "" {
Expand All @@ -597,7 +603,7 @@ func (client *Client) Set(key string, value string) (err error) {
client.JWT = value
case "parrot":
parrot := strings.Trim(value, "\"'")
client.Client, err = getClient(client.Protocol, client.Proxy, client.JA3, parrot)
client.Client, err = getClient(client.Protocol, client.Proxy, client.JA3, parrot, client.insecureTLS)
if parrot != "" {
cli.Message(cli.NOTE, fmt.Sprintf("Set agent HTTP transport parrot to:%s", parrot))
} else if parrot == "" {
Expand All @@ -614,7 +620,7 @@ func (client *Client) Set(key string, value string) (err error) {
return
}

// Get is a generic function that is used to retrieve the value of a Client's field
// Get is a generic function used to retrieve the value of a Client's field
func (client *Client) Get(key string) (value string) {
cli.Message(cli.DEBUG, fmt.Sprintf("clients/http.Get(): entering into function with key: %s", key))
defer cli.Message(cli.DEBUG, fmt.Sprintf("clients/http.Get(): leaving function with value: %s", value))
Expand Down Expand Up @@ -682,7 +688,7 @@ func (client *Client) Authenticate(msg messages.Base) (err error) {
return
}

// Add response message to the next loop iteration
// Add a response message to the next loop iteration
if len(msgs) > 0 {
msg = msgs[0]
}
Expand All @@ -701,7 +707,7 @@ func (client *Client) Construct(msg messages.Base) (data []byte, err error) {
cli.Message(cli.DEBUG, fmt.Sprintf("clients/http.Construct(): Transformers: %+v", client.transformers))
for i := len(client.transformers); i > 0; i-- {
if i == len(client.transformers) {
// First call should always take a Base message
// The first call should always take a Base message
data, err = client.transformers[i-1].Construct(msg, client.secret)
cli.Message(cli.DEBUG, fmt.Sprintf("%d call with transform %s - Constructed data(%d) %T: %X\n", i, client.transformers[i-1], len(data), data, data))
} else {
Expand Down Expand Up @@ -751,7 +757,8 @@ func (client *Client) Deconstruct(data []byte) (messages.Base, error) {
}

// Initial contains all the steps the agent and/or the communication profile need to take to set up and initiate
// communication with server. If the agent needs to authenticate before it can send messages, that process will occur here.
// communication with the server.
// If the agent needs to authenticate before it can send messages, that process will occur here.
func (client *Client) Initial() (err error) {
cli.Message(cli.DEBUG, "clients/http.Initial(): entering into function")
return client.Authenticate(messages.Base{})
Expand Down
4 changes: 4 additions & 0 deletions docs/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- GitHub Actions for building and testing the Merlin Agent
- Implemented "services" and "repositories"
- Services are: agent, client, job, message, and p2p
- Configurable TLS x.509 certificate validation
- Default is `false`, TLS certificates are not validated
- Added `-secure` command line argument to require TLS X.509 certificate validation
- Added `SECURE` variable to Make file (e.g., `make windows SECURE=true`)

### Changed

Expand Down
24 changes: 21 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"fmt"
"io"
"os"
"strconv"
"strings"
"time"

Expand All @@ -52,10 +53,10 @@ import (
// auth the authentication method the Agent will use to authenticate to the server
var auth = "opaque"

// addr the interface and port the agent will use for network connections
// addr is the interface and port the agent will use for network connections
var addr = "127.0.0.1:7777"

// headers a list of HTTP headers the agent will use with the HTTP protocol to communicate with the server
// headers is a list of HTTP headers that the agent will use with the HTTP protocol to communicate with the server
var headers = ""

// host a specific HTTP header used with HTTP communications; notably used for domain fronting
Expand All @@ -67,7 +68,7 @@ var ja3 = ""
// killdate the date and time, as a unix epoch timestamp, that the agent will quit running
var killdate = "0"

// listener the UUID of the peer-to-peer listener this agent belongs to. Used with delegate messages
// listener the UUID of the peer-to-peer listener this agent belongs to, used with delegate messages
var listener = ""

// maxretry the number of failed connections to the server before the agent will quit running
Expand All @@ -91,6 +92,11 @@ var proxy = ""
// psk is the Pre-Shared Key, the secret used to encrypt messages communications with the server
var psk = "merlin"

// secure a boolean value as a string that determines the value of the TLS InsecureSkipVerify option for HTTP
// communications.
// Must be a string, so it can be set from the Makefile
var secure = "false"

// sleep the amount of time the agent will sleep before it attempts to check in with the server
var sleep = "30s"

Expand Down Expand Up @@ -121,6 +127,7 @@ func main() {
flag.StringVar(&host, "host", host, "HTTP Host header")
flag.StringVar(&ja3, "ja3", ja3, "JA3 signature string (not the MD5 hash). Overrides -proto & -parrot flags")
flag.StringVar(&parrot, "parrot", ja3, "parrot or mimic a specific browser from github.com/refraction-networking/utls (e.g., HelloChrome_Auto")
flag.StringVar(&secure, "secure", secure, "Require TLS certificate validation for HTTP communications")
flag.StringVar(&sleep, "sleep", sleep, "Time for agent to sleep")
flag.StringVar(&skew, "skew", skew, "Amount of skew, or variance, between agent checkins")
flag.StringVar(&killdate, "killdate", killdate, "The date, as a Unix EPOCH timestamp, that the agent will quit running")
Expand Down Expand Up @@ -175,6 +182,16 @@ func main() {
os.Exit(1)
}

// Parse the secure flag
var verify bool
verify, err = strconv.ParseBool(secure)
if err != nil {
if *verbose {
color.Red(err.Error())
}
os.Exit(1)
}

// Get the client
var client clients.Client
var listenerID uuid.UUID
Expand All @@ -194,6 +211,7 @@ func main() {
AuthPackage: auth,
Opaque: opaque,
Transformers: transforms,
InsecureTLS: !verify,
}

if url != "" {
Expand Down

0 comments on commit 0a70aa9

Please sign in to comment.