<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-01-05T00:04:34+00:00</updated><id>/feed.xml</id><title type="html">The Grey Corner</title><subtitle>Stephen Bradshaw&apos;s blog about pentesting and computer security  </subtitle><author><name>Stephen Bradshaw</name></author><entry><title type="html">Direct assessment of Istio external authorisers using Python</title><link href="/2025/12/31/test-istio-external-authorizer-using-python.html" rel="alternate" type="text/html" title="Direct assessment of Istio external authorisers using Python" /><published>2025-12-31T06:04:00+00:00</published><updated>2025-12-31T06:04:00+00:00</updated><id>/2025/12/31/test-istio-external-authorizer-using-python</id><content type="html" xml:base="/2025/12/31/test-istio-external-authorizer-using-python.html"><![CDATA[<p align="center">
  <img width="800" height="800" src="/assets/img/external_authz.svg" alt="External Autorizer" />
</p>

<h1 id="introduction">Introduction</h1>

<p>In Kubernetes environments using Istio you have the option of delegating access control decisions to a custom application, rather than relying solely on Istio’s built-in Role-Based Access Control (RBAC). This concept is discussed in more detail <a href="https://istio.io/latest/blog/2021/better-external-authz/">here</a>.</p>

<p>This approach works as follows: When an incoming HTTP request to the cluster enters the Istio mesh to be routed, Istio pauses the request and sends a “check” request using gRPC or HTTP to your custom authorisation service. Based on the response from that service (Allow or Deny), Istio either forwards the original request to the destination (with optional request modification) or returns a 403 Forbidden message to the requester.</p>

<p>When performing security assessments of these custom authorisers, there are a few factors that add friction to the testing process.</p>

<p>First, these authoriser services are usually packaged up in a container and can be isolated from direct communication when running in a Kubernetes cluster. For example the authoriser might be running as a sidecar in the target services pod with communication only allowed from the envoy sidecar within the same pod. In this case, you have to test the authoriser app indirectly - by first getting the request you want to assess to the gateway that uses the authoriser. This is not necessarily a problem, as it should be the only way that requests reach the authoriser in actual use. This does mean however that you are limited in the request parameters you can assess (e.g. hostname and ports) by the routing/communication configuration of the Kubernetes cluster. If these change in future to some configuration you did not anticipate, vulnerable configurations in the authoriser that were previously not “reachable” may become exploitable.</p>

<p>Configuration of the authoriser application is also usually done through environment variables or configuration/data files mounted in the container filesystem. As a result, testing variations in the authorisers configuration introduces overhead. This is because you need to call the Kubernetes API and relaunch the authoriser container for configuration changes to take effect. The level of testing overhead this generates will depend on how much configuration churn is needed and how long it takes to perform each configuration change. This is affected by factors such as your ability to directly call the Kubernetes API or whether you need to apply changes indirectly via gitops or similar, the cluster capacity and more.</p>

<p>There are also other things that would just be easier to do if you could run the authoriser locally, like gaining access to the authoriser apps debug/logging output and easily deploying mock supporting services such as JWKS servers that the authoriser can reach.</p>

<p>Due to these reasons, I wanted to figure out how I could run one of these authoriser applications locally, and directly communicate with it. This would allow me to run the server directly on my machine without any Kubernetes/containerisation overhead or interference, directly view its logs, quickly change its configuration and run supporting mock services on my local machine. One problem however - gRPC. For authorisers that only support this interface, and not HTTP, communication with the service is performed using complex protobuf message structures that require encoding/decoding.</p>

<p>In this blog post, I discuss how I was able to get Python to communicate directly with Istio external authorisers, and provide code for talking to two simple Istio extauth servers you can download.</p>

<h1 id="enabling-communication-with-the-grpc-endpoint-using-python">Enabling communication with the gRPC endpoint using Python</h1>

<p>As mentioned in the introduction, the Istio authoriser services can communicate using either gRPC or HTTP. The HTTP communication method involves directly sending the request to filter to the endpoint, but many authoriser services (including the particular one I wanted to test) do not support that method. The gRPC communication method involves encapsulating the HTTP request into a specific <a href="https://protobuf.dev/">protobuf</a> message and sending this to the authoriser using gRPC (essentially HTTP 2.0 with extra headers and protobuf messages). This is a protocol I have previously discussed on my blog in relation to the containerd socket <a href="/2025/02/19/containerd-socket-exploitation-part-2.html#understanding-and-exploring-the-proto-service-definition-files-and-parsing-protobuf-messages">here</a>.</p>

<p>Getting the particular messages required for Istio authoriser communication working in Python code is quite fiddly, and involves grabbing <code class="language-plaintext highlighter-rouge">.proto</code> definition files for multiple message types from six different git repositories and compiling them to Python equivalents locally. This will create a directory tree of Python modules that implement the multiple different message types, that you can then add to your Python path in order to create the related protobuf message types in Python code.</p>

<p>To make it a bit easier to set this up I wrote a <a href="https://github.com/stephenbradshaw/pentesting_stuff/blob/master/protobuf/external_authoriser_dependency_adder.sh">script</a> to automate the dependency setup process on *nix systems. The following discusses how to use this script.</p>

<p>First install the general use protobuf and gRPC Python modules.  On systems with a managed Python install (e.g. Ubuntu) you may need to do this in a venv as the inbuilt <code class="language-plaintext highlighter-rouge">validate</code> module will break loading of the protobuf module with the same name. In that case you should run something like the following first to setup your virtual environment.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python -m venv .venv
source .venv/bin/activate
</code></pre></div></div>

<p>Now install the protobuf and gRPC Python modules.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip install protobuf grpcio-tools
</code></pre></div></div>

<p>Now install the protobuf command line compiler <code class="language-plaintext highlighter-rouge">protoc</code> and make sure its in your path. The binaries for various different platforms and architectures can be obtained from <a href="https://github.com/protocolbuffers/protobuf/releases">here</a> - grab, extract and put the <code class="language-plaintext highlighter-rouge">protoc</code> binary in your path.</p>

<p>Now you are ready to run my script to setup the dependant Python protobuf modules. The script will download the necessary Git repositories to <code class="language-plaintext highlighter-rouge">/tmp/code</code>, copy the required protobuf file/directory structures from those repositories to the <code class="language-plaintext highlighter-rouge">protoc_build</code> in the present working directory, and compile the many <code class="language-plaintext highlighter-rouge">.proto</code> files to Python format.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://raw.githubusercontent.com/stephenbradshaw/pentesting_stuff/refs/heads/master/protobuf/external_authoriser_dependency_adder.sh
chmod +x external_authoriser_dependency_adder.sh
cd protoc_build
</code></pre></div></div>

<p>The script will show a number of errors during the compilation phase, which is <em>usually</em> fine as a number of uneeded protobuf files are included in the directories copied from git and these are missing some other dependencies we have not downloaded. As long as you end up with a few hundred files matching the pattern <code class="language-plaintext highlighter-rouge">*_pb2.py</code> in the <code class="language-plaintext highlighter-rouge">protoc_build</code> directory structure you are likely ok. Check like so to see how many matching files you have, and if you have a similar number to the below you should have whats required:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find . -iname "*_pb2.py" | wc -l
698
</code></pre></div></div>

<p>From this point, any Python script you write to communicate with the gRPC authoriser endpoint will need to be run from within this <code class="language-plaintext highlighter-rouge">protoc_build</code> folder. You will also need to include environment modification code in your scripts similar to the following to ensure that the Python loader will look in the current directory when trying to find the protobuf message definitions. The example scripts included later in this post include this snippet.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import sys
import os

sys.path.insert(0, os.getcwd())
</code></pre></div></div>

<h1 id="example-1-the-istio-sample-extauthz-server">Example 1: The Istio sample extauthz server</h1>

<p>For our first example to demonstrate communicating with the extauthz gRPC endpoint, we will use the Istio sample extauthz server, the code for which is available <a href="https://github.com/istio/istio/blob/master/samples/extauthz/cmd/extauthz/main.go">here</a>.</p>

<p>Make sure you have <a href="https://go.dev/doc/install">go installed</a> locally so you can compile these example servers.</p>

<p>Clone Istio locally:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/istio/istio.git

</code></pre></div></div>

<p>Change to the program folder and compile:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd istio/samples/extauthz/cmd/extauthz
go build
</code></pre></div></div>

<p>Run the server:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./extauthz
2025/12/31 14:43:28 Starting gRPC server at [::]:9000
2025/12/31 14:43:28 Starting HTTP server at [::]:8000
</code></pre></div></div>

<p>Note from the above that this service is running both a gRPC and a HTTP endpoint. Communication with the HTTP endpoint on port 8000 is very straightforward - we can do it using curl. Below we can see both the request and the (successful) response from the authoriser service, which does include in its response some additional headers to insert into the request when it is passed by the Istio gateway to its actual destination in the cluster.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -v -H'x-ext-authz: allow' http://127.0.0.1:8000/
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
&gt; GET / HTTP/1.1
&gt; Host: 127.0.0.1:8000
&gt; User-Agent: curl/8.5.0
&gt; Accept: */*
&gt; x-ext-authz: allow
&gt;
&lt; HTTP/1.1 200 OK
&lt; X-Ext-Authz-Additional-Header-Override:
&lt; X-Ext-Authz-Check-Received: GET 127.0.0.1:8000/, headers: map[Accept:[*/*] User-Agent:[curl/8.5.0] X-Ext-Authz:[allow]], body: []
&lt; X-Ext-Authz-Check-Result: allowed
&lt; Date: Wed, 31 Dec 2025 04:55:31 GMT
&lt; Content-Length: 0
&lt;
* Connection #0 to host 127.0.0.1 left intact
</code></pre></div></div>

<p>This will generate the following log entry from the running authoriser service.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2025/12/31 15:55:31 [HTTP][allowed]: GET 127.0.0.1:8000/, headers: map[Accept:[*/*] User-Agent:[curl/8.5.0] X-Ext-Authz:[allow]], body: []
</code></pre></div></div>

<p>Not very interesting.</p>

<p>Now lets look at the gRPC service on port 9000. The following script (run from the <code class="language-plaintext highlighter-rouge">protoc_build</code> folder with dependencies we setup earlier) will send a request to this endpoint.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/usr/bin/env python
import sys 
import os 
import grpc

sys.path.insert(0, os.getcwd())

import external_auth_pb2
import external_auth_pb2_grpc

from envoy.service.auth.v3.attribute_context_pb2 import AttributeContext

# Reference extauth requestor for: https://github.com/istio/istio/blob/master/samples/extauthz/cmd/extauthz/main.go

def send_request():
    with grpc.insecure_channel("localhost:9000") as channel:
        stub = external_auth_pb2_grpc.AuthorizationStub(channel)
        checkrequest = external_auth_pb2.CheckRequest()
        attributecontext = AttributeContext()
        attributecontext.request.http.method = 'GET'
        attributecontext.request.http.headers["Host"] = "test.example.com"
        attributecontext.request.http.host = "test.example.com"
        attributecontext.request.http.path = "/"
        #attributecontext.request.http.protocol = "HTTP/1.1"
        attributecontext.request.http.scheme = "http"
        attributecontext.request.http.headers["x-ext-authz"] = "allow"
        attributecontext.destination.address.socket_address.port_value = 80
        #attributecontext.request.http.body = b'data'
        checkrequest.attributes.CopyFrom(attributecontext)
        response = stub.Check(checkrequest)
    print(f"Client received: {str(response)}")


if __name__ == "__main__":
    send_request()
</code></pre></div></div>

<p>This script is simulating the effect of the authorisation server being asked to make an access control decision on a HTTP 1.1 request to <code class="language-plaintext highlighter-rouge">http://test.example.com/</code> with a <code class="language-plaintext highlighter-rouge">x-ext-authz</code> header value of <code class="language-plaintext highlighter-rouge">allow</code> received by the associated Istio gateway/proxy. This is similar to what would be generated by curl if we ran the following command (although the <code class="language-plaintext highlighter-rouge">User-Agent</code> and <code class="language-plaintext highlighter-rouge">Accept</code> request headers normally added by curl will be absent). Because we are encapsulating the request details in gRPC however, we can arbitrarily set the host and port settings for the request without worrying about routing details that we would need to consider for the HTTP endpoint for the request to be received.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl  -H'x-ext-authz: allow' http://test.example.com/
</code></pre></div></div>

<p>Running this prints out the following interpreted protobuf response from the authoriser server. The additional headers that the authoriser server is instructing the Itio gateway to add to the request when it is passed on are a bit clearer in this example.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Client received:
status {
}
ok_response {
  headers {
    header {
      key: "x-ext-authz-check-result"
      value: "allowed"
    }
  }
  headers {
    header {
      key: "x-ext-authz-check-received"
      value: "destination:{address:{socket_address:{port_value:80}}}  request:{http:{method:\"GET\"  headers:{key:\"Host\"  value:\"test.example.com\"}  headers:{key:\"x-ext-authz\"  value:\"allow\"}  path:\"/\"  host:\"test.example.com\"  scheme:\"http\"}}"
    }
  }
  headers {
    header {
      key: "x-ext-authz-additional-header-override"
      value: "grpc-additional-header-override-value"
    }
  }
}
</code></pre></div></div>

<p>The following log entry is output from the server console output.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2025/12/31 16:21:33 [gRPCv3][allowed]: test.example.com/, attributes: destination:{address:{socket_address:{port_value:80}}}  request:{http:{method:"GET"  headers:{key:"Host"  value:"test.example.com"}  headers:{key:"x-ext-authz"  value:"allow"}  path:"/"  host:"test.example.com"  scheme:"http"}}
</code></pre></div></div>

<p>As you might be able to tell, this reference authorization server is essentially configured to “allow” HTTP requests with the <code class="language-plaintext highlighter-rouge">x-ext-authz</code> header set to a value of <code class="language-plaintext highlighter-rouge">allow</code>, and when such a request is received by the associated Istio gateway it will authorise it for routing and add the listed header values in the response.</p>

<p>In order to see what a deny response looks like, lets modify the following line in our script:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>attributecontext.request.http.headers["x-ext-authz"] = "allow"
</code></pre></div></div>

<p>To this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>attributecontext.request.http.headers["x-ext-authz"] = "not allow"
</code></pre></div></div>

<p>Run the script again, and now we get the following “deny” response.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Client received:
status {
  code: 7
}
denied_response {
  status {
    code: Forbidden
  }
  headers {
    header {
      key: "x-ext-authz-check-result"
      value: "denied"
    }
  }
  headers {
    header {
      key: "x-ext-authz-check-received"
      value: "destination:{address:{socket_address:{port_value:80}}}  request:{http:{method:\"GET\"  headers:{key:\"Host\"  value:\"test.example.com\"}  headers:{key:\"x-ext-authz\"  value:\"not allow\"}  path:\"/\"  host:\"test.example.com\"  scheme:\"http\"}}"
    }
  }
  headers {
    header {
      key: "x-ext-authz-additional-header-override"
      value: "grpc-additional-header-override-value"
    }
  }
  body: "denied by ext_authz for not found header `x-ext-authz: allow` in the request"
}
</code></pre></div></div>

<p>Being a simple reference server this doesn’t have any complex authentication logic for us to test, but it does provide a good example of the request process and the accept/deny logic at play for these servers and the difference between the available HTTP and gRPC communication endpoints.</p>

<p>Lets look at one more example.</p>

<h1 id="example-2-istio-external-authorization-server">Example 2: “Istio external authorization server”</h1>

<p>Another simple authorization server I found whilst developing this testing methodology is available <a href="https://github.com/salrashid123/istio_external_authorization_server">here</a>, with the server code <a href="https://github.com/salrashid123/istio_external_authorization_server/blob/master/authz_server/grpc_server.go">here</a>. Lets test the same approach with it.</p>

<p>Clone the code</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/istio/istio.git
</code></pre></div></div>

<p>This server hard codes the filename of a required private key file to an absolute path in a folder structure that does not exist on my test system <a href="https://github.com/salrashid123/istio_external_authorization_server/blob/master/authz_server/grpc_server.go#L66">here</a>. I like to modify this to a relative pathname as follows.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
const (
        address string = ":50051"
        keyfile string = "key.pem"
)

</code></pre></div></div>

<p>With the modification made, build the server.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd istio_external_authorization_server/authz_server
go build
</code></pre></div></div>

<p>Note if you are doing this on a newer Mac, you might see the error <code class="language-plaintext highlighter-rouge">dyld[52734]: missing LC_UUID load command</code>  when trying to run this server built as above. You can resolve this by compiling with an additional linker flag as follows: <code class="language-plaintext highlighter-rouge">go build -ldflags="-linkmode=external"</code>.</p>

<p>Now we need to prepare the environment a little, by generating a private key in the right format and setting an environment variable the server uses for access control decisions.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl genrsa -traditional -out key.pem 2048
export AUTHZ_ALLOWED_USERS=bob,trevor
</code></pre></div></div>

<p>Now we can run the server:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./main
2025/12/31 16:46:43 Starting gRPC Server at :50051
</code></pre></div></div>

<p>This server only has the gRPC endpoint, listening now at port <code class="language-plaintext highlighter-rouge">50051</code>.</p>

<p>Our test script for communicating with this server looks like the following, modified from the previous example for the new port and the new authorisation configuration.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/usr/bin/env python
import sys 
import os 
import grpc

sys.path.insert(0, os.getcwd())

import external_auth_pb2
import external_auth_pb2_grpc

from envoy.service.auth.v3.attribute_context_pb2 import AttributeContext

# Reference extauth requestor for: https://github.com/salrashid123/istio_external_authorization_server/blob/master/authz_server/grpc_server.go

def send_request():
    with grpc.insecure_channel("localhost:50051") as channel:
        stub = external_auth_pb2_grpc.AuthorizationStub(channel)
        checkrequest = external_auth_pb2.CheckRequest()
        attributecontext = AttributeContext()
        attributecontext.request.http.method = 'GET'
        attributecontext.request.http.headers["Host"] = "test.example.com"
        attributecontext.request.http.host = "test.example.com"
        attributecontext.request.http.path = "/"
        #attributecontext.request.http.protocol = "HTTP/1.1"
        attributecontext.request.http.scheme = "http"
        attributecontext.request.http.headers["authorization"] = "Bearer bob"
        #attributecontext.request.http.body = b'data'
        attributecontext.destination.address.socket_address.port_value = 80
        checkrequest.attributes.CopyFrom(attributecontext)
        response = stub.Check(checkrequest)
    print(f"Client received:\n{str(response)}")


if __name__ == "__main__":
    send_request()

</code></pre></div></div>

<p>This script is now simulating the effect of a request similar to that created by the following curl command being received by the Istio gateway associated with this authorization server.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl  -H'authorization: Bearer bob' http://test.example.com/
</code></pre></div></div>

<p>The following is the script output - an “allow” response including a new <code class="language-plaintext highlighter-rouge">Authorization</code> HTTP header value with a JWT token to add to the incoming request.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Client received:
status {
}
ok_response {
  headers {
    header {
      key: "Authorization"
      value: "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJib2IiLCJhdWQiOlsiaHR0cDovL3N2YzIuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbDo4MDgwLyJdLCJleHAiOjE3NjcxNjAxMzYsImlhdCI6MTc2NzE2MDA3Nn0.OuORclJm0RRuPC6e3lLb4hpl-7H7exZnwlVpxmsJgMz9UKkiWuMd701dN-av12eTSAkFDqyCox4d4bYwPE5IRsCQUPK7-R8GC4ws0n2zYm7SlED14o_uJEnu8A93987vBWy-dT0-TB6JFr18aoXIChdiKZwv9nZ-WS3sC637TR0y5qi66WNoh1883hBnqslmMWSotzoRdhBobsgBmlMRrMDdzIW_5F-a91Url97CQVDkW7HH2SKyL_8h8u5UlkBet1hvUSWY6IMLk-BbuVMMXXVuw2lV_dsB21FCUJ0Tw8OkXBEA2xAQAmA94PefuQO0UxEBsva-MoHcro6zsELB1Q"
    }
  }
}
</code></pre></div></div>

<p>The following log entries appear in the server output.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2025/12/31 16:47:56 &gt;&gt;&gt; Authorization called check()
2025/12/31 16:47:56 Authorization Header Bearer bob
2025/12/31 16:47:56 Using Claim {bob [http://svc2.default.svc.cluster.local:8080/] {  [] 2025-12-31 16:48:56.921699108 +1100 AEDT m=+133.013451078 &lt;nil&gt; 2025-12-31 16:47:56.921699066 +1100 AEDT m=+73.013451036 }}
2025/12/31 16:47:56 Issuing outbound Header eyJhbGciOiJSUzI1NiIsImtpZCI6IiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJib2IiLCJhdWQiOlsiaHR0cDovL3N2YzIuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbDo4MDgwLyJdLCJleHAiOjE3NjcxNjAxMzYsImlhdCI6MTc2NzE2MDA3Nn0.OuORclJm0RRuPC6e3lLb4hpl-7H7exZnwlVpxmsJgMz9UKkiWuMd701dN-av12eTSAkFDqyCox4d4bYwPE5IRsCQUPK7-R8GC4ws0n2zYm7SlED14o_uJEnu8A93987vBWy-dT0-TB6JFr18aoXIChdiKZwv9nZ-WS3sC637TR0y5qi66WNoh1883hBnqslmMWSotzoRdhBobsgBmlMRrMDdzIW_5F-a91Url97CQVDkW7HH2SKyL_8h8u5UlkBet1hvUSWY6IMLk-BbuVMMXXVuw2lV_dsB21FCUJ0Tw8OkXBEA2xAQAmA94PefuQO0UxEBsva-MoHcro6zsELB1Q
</code></pre></div></div>

<p>This <code class="language-plaintext highlighter-rouge">accept</code> result is because the <code class="language-plaintext highlighter-rouge">authorization</code> header value we sent in the request for user <code class="language-plaintext highlighter-rouge">bob</code> was included in the environment variable we set using the command <code class="language-plaintext highlighter-rouge">export AUTHZ_ALLOWED_USERS=bob,trevor</code>.</p>

<p>Now lets change the following value in the script to a new value to see a deny response.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>attributecontext.request.http.headers["authorization"] = "Bearer bob"
</code></pre></div></div>

<p>Try the following modification:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>attributecontext.request.http.headers["authorization"] = "Bearer john"
</code></pre></div></div>

<p>The new response from the script should now be as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Client received:
status {
  code: 7
}
denied_response {
  status {
    code: Unauthorized
  }
  body: "Permission Denied"
}
</code></pre></div></div>

<h1 id="conclusion">Conclusion</h1>

<p>While these are some very simple examples of Istio authoriser servers, hopefully this post provided enough information to give anyone reading a good starting point for doing their own assessments on more complicated Istio authoriser servers. Example code supporting this post is <a href="https://github.com/stephenbradshaw/pentesting_stuff/tree/master/protobuf">here</a></p>]]></content><author><name>Stephen Bradshaw</name></author><category term="istio" /><category term="kubernetes" /><category term="external-authoriser" /><category term="pentest" /><category term="python" /><category term="protobuf" /><category term="grpc" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Abusing cloud services for command and control - AWS, Azure, GCP summary</title><link href="/2025/10/02/abusing-cloud-services-for-c2.html" rel="alternate" type="text/html" title="Abusing cloud services for command and control - AWS, Azure, GCP summary" /><published>2025-10-02T03:04:00+00:00</published><updated>2025-10-02T03:04:00+00:00</updated><id>/2025/10/02/abusing-cloud-services-for-c2</id><content type="html" xml:base="/2025/10/02/abusing-cloud-services-for-c2.html"><![CDATA[<p align="center">
  <img width="500" height="500" src="/assets/img/cloud_puppet_master.png" alt="Cloud Puppet Master" />
</p>

<h1 id="introduction">Introduction</h1>

<p>Last weekend, I presented at <a href="https://www.bsidesau.com.au/">BSides Canberra</a> on the topic of abusing native cloud services for Command and Control (C2). The talk discussed the concept of the high reputation redirector, why these are used in Command and Control designs, and listed a number of ways to create these redirectors using native cloud services from AWS, Azure and GCP.</p>

<p>The slides from the presesntation are shared <a href="https://github.com/stephenbradshaw/pentesting_stuff/blob/master/presentations/Abusing_Cloud_Services_for_C2_slides.pdf">here</a>, and at some point in the near future the recording of the talk will be released on the <a href="https://www.youtube.com/@bsidescanberra9688">BSides Canberra YouTube channel</a>. I will update the post with a direct link to the talk once its available.</p>

<p>Given that I have been working on this subject since late 2023 and the various content I have written is scattered across multiple posts, I thought I would do a single summary post to organise everything and make specific information easier to find.</p>

<p>This post will explain the concept of using a high reputation redirector for C2, will list the various applicable cloud services that can be used, and will provide direct links to detailed posts where applicable. Some of the summary information below will be reproduced here from the original posts, so be aware you might see some repeated information from individual entries in the series.</p>

<h1 id="why-do-we-use-high-reputation-redirectors-in-command-and-control-designs">Why do we use high reputation redirectors in Command and Control designs?</h1>

<p>To my knowledge, the idea for C2 fronting was first discussed back in 2017 in a post from the Cobalt Strike blog which can be found <a href="https://www.cobaltstrike.com/blog/high-reputation-redirectors-and-domain-fronting">here</a>. This post talks about two different concepts, domain fronting and high reputation redirectors. As discussed in the following section, domain fronting no longer works well in many modern CDNs, but the high reputation redirector concept still has value.</p>

<p>So, what do we mean by high reputation redirectors and why would we want to use them?</p>

<p>Consider this basic architecture diagram of a C2 system which uses HTTP/S for implant communications.</p>

<p align="center">
  <img src="/assets/img/c2_architecture_basic.png" alt="Basic C2 Architecture" />
</p>

<p>In this case we are using a single C2 host accessible on the Internet. We as an attacker compromise victim systems (shown on the left) and install our implant software. This software connects back to our C2 server via HTTP/S to request tasking. This tasking could involve running other executables, uploading files or data from the victim system and more. Operators of the C2 system (shown on the right), connect to the C2 via a seperate admin communication channel to provide tasking through the C2 for the victim systems.</p>

<p>In this simple type of design the implant endpoint of the C2 is usually given a DNS name that victim hosts will connect to. In this case the DNS name is <code class="language-plaintext highlighter-rouge">www.evil.com</code>. Given that implant traffic will need to bypass any network security controls used by the victim system, choosing the right endpoint address can impact the liklihood of getting a successful connection.</p>

<p>In the case of the high reputation redirectors concept, we are trying to take advantage of the good reputation of the redirector services domain name to avoid our implant traffic being blocked due to poor domain reputation. Instead of some newly registered dodgy looking domain like <code class="language-plaintext highlighter-rouge">www.evil.com</code>, we instead connect to the domain used by the forwarding service. This forwarding services are widely used for legitimate traffic, and are often trusted by security solutions.</p>

<p>Heres an example of a redesigned C2 system using a high reputation cloud service for fronting.</p>

<p align="center">
  <img src="/assets/img/c2_architecture_basic_fronted.png" alt="Basic C2 Architecture with Cloud Service Fronting" />
</p>

<p>Here, the basic design has been modified to host the C2 service in a private VPC. The C2 server is no longer directly exposed to the Internet, and the implant traffic is directed to the C2 service by the use of a <a href="https://aws.amazon.com/cloudfront/">AWS Cloudfront</a> distribution, which has not had a custom domain associated with it. So, instead of the implants having to connect to the suspicious looking address of <code class="language-plaintext highlighter-rouge">www.evil.com</code>, the implants instead connect to the much more trustworthy address <code class="language-plaintext highlighter-rouge">abcdefghijklm.cloudfront.net</code> that the CloudFront service auto generates for us when we create a new distribution. We even get a SSL certificate for HTTPS traffic included!</p>

<h1 id="what-about-domain-fronting-an-aside">What about domain fronting? An aside….</h1>

<p>Back in 2023 when I first started looking into the high reputation redirector concept, I also investigated domain fronting given it was always referenced when anyone talked about using CloudFront with C2.  What follows in this section is reproduced from what I <a href="/2023/08/30/aws-service-C2-forwarding.html#a-note-on-domain-fronting-and-cloudfront">wrote at the time</a> on the subject, specific to AWS services. Keep in mind I have not done any testing to update this information for GCP or Azure services or to account for changes since 2023. Consequently, while the details below about how domain fronting works are still accurate, specifics about whether or how it works with particular cloud services may have changed.</p>

<p>Domain fronting is a technique that attempts to hide the true destination of a HTTP request or redirect traffic to possibly restricted locations by abusing the HTTP routing capabilities of CDNs or certain other complex network environments. For version 1.1 of the protocol, HTTP involves a TCP connection being made to a destination server on a given IP address (normally associated with a domain name) and port, with additional TLS/SSL encryption support for the connection in HTTPS. Over this connection a structured plain text message is sent that requests a given resource and references a server in the <code class="language-plaintext highlighter-rouge">Host</code> header. Under normal circumstances the domain name associated with the TCP connection and the <code class="language-plaintext highlighter-rouge">Host</code> header in the HTTP message match.  In domain fronting, the destination for the TCP connection domain name is set to a site that you want to appear to be visiting, and the <code class="language-plaintext highlighter-rouge">Host</code> header in the HTTP request is set to the location you actually want to visit. Both locations must be served by the same CDN.</p>

<p>The following curl command demonstrates in the simplest possible way how the approach is performed in suited environments. In the example, <code class="language-plaintext highlighter-rouge">http://fakesite.cloudfront.net/</code> is what you want to <strong>appear</strong> to be visiting, and <code class="language-plaintext highlighter-rouge">http://actualsite.cloudfront.net</code> is where you actually want to go:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -H 'Host: actualsite.cloudfront.net'  http://fakesite.cloudfront.net/
</code></pre></div></div>

<p>In this example, any DNS requests resolved on the client site are resolving the “fake” address, and packet captures will show the TCP traffic going to that fake systems IP address. If HTTPS is supported, and you use a <code class="language-plaintext highlighter-rouge">https://</code> URL, the actual destination you are visiting located in the HTTP <code class="language-plaintext highlighter-rouge">Host</code> header will also be hidden in the encrypted tunnel.</p>

<p>While this <strong>is</strong> a great way of hiding C2 traffic, due to a widespread practice of domain fronting being used to evade censorship restrictions, various CDNs did crack down on the approach a few years ago. Some changes were rolled back in some cases, but as at the time of my testing in 2023 this simple approach to domain fronting <strong>does not work</strong> in CloudFront for HTTPS. If the DNS hostname that you connect to does not match any of the certificates you have associated with your CloudFront distribution, you will get the following error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The distribution does not match the certificate for which the HTTPS connection was established with.
</code></pre></div></div>

<p>This applies only to HTTPS - HTTP still works using the approach shown in the example above. However, given the fact that HTTP has the <code class="language-plaintext highlighter-rouge">Host</code> header value exposed in the clear in network traffic this leaves something to be desired when the purpose is hiding where you’re going. Depending on the capability of inspection devices, it might be good enough for certain purposes however.</p>

<p>It is possible to make HTTPS domain fronting work on CloudFront via use of Server Name Indication <a href="https://www.cloudflare.com/en-gb/learning/ssl/what-is-sni/">(SNI)</a> to specify a Server Name value during the TLS negotiation that matches a certificate in your Cloudfront distribution. In other words, you TCP connect via HTTPS to a fake site on the CDN and set the SNI servername for the TLS negotation <strong>AND</strong> the HTTP <code class="language-plaintext highlighter-rouge">Host</code> to your actual intended host.</p>

<p>Heres how this connection looks using openssl.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl s_client  -quiet -connect fakesite.cloudfront.net:443 -servername actualsite.cloudfront.net &lt; request.txt
depth=2 C = US, O = Amazon, CN = Amazon Root CA 1
verify return:1
depth=1 C = US, O = Amazon, CN = Amazon RSA 2048 M01
verify return:1
depth=0 CN = *.cloudfront.net
verify return:1
</code></pre></div></div>

<p>Where file <code class="language-plaintext highlighter-rouge">request.txt</code> contains something like the following:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET / HTTP/1.1
Host: actualsite.cloudfront.net


</code></pre></div></div>

<p>Unfortunately, I’m not aware of any C2 implant that supports specifying the TLS servername in a manner similar to what is shown above, so C2 HTTPS domain fronting using CloudFront is not a viable approach until this time. However, this does not mean that CloudFront is completely unusable for C2. As already mentioned, you can do domain fronting via HTTP. Its also possible to access the distribution via HTTPS using the <code class="language-plaintext highlighter-rouge">&lt;name&gt;.cloudfront.net</code> name that is created randomly for you when you setup your distribution. This domain does have a good trust profile in some URL categorisation databases.</p>

<h1 id="protectionfiltering-for-the-c2-servers-http-endpoint">Protection/filtering for the C2 servers HTTP endpoint</h1>

<p>When I setup C2, I usually also add in a seperate HTTP proxy service before the C2s HTTP listener. This provides some additional protection to the C2 server from malicious access or attempts to fingerprint the service. The ideal design would have this proxy sitting on its own restricted dedicated bastion host. However, for the POC C2 architectures I have discussed in the majority of the blog posts in this series, I run this proxy on the same host running the C2 server to minimise cost and complexity. The proxy I use is an Apache instance listening on port 80, which will in turn forward <em>certain</em> incoming HTTP traffic on to the C2 HTTP endpoint, which is listening on localhost port 8888.</p>

<p>A script for Ubuntu Linux that will set this up for you using the <a href="https://github.com/BishopFox/sliver">Sliver</a> C2 server can be found <a href="https://github.com/stephenbradshaw/pentesting_stuff/blob/master/setup_scripts/c2_setup.sh">here</a>. This is the same script that is used in the <a href="/2025/06/04/azure_c&amp;c_poc_infra_deployment.html">deployment templates that I created for the Azure entries in this series</a>.</p>

<p>The filtering in this design is pretty simple, configured in <code class="language-plaintext highlighter-rouge">/var/www/html/.htaccess</code> and works in the following manner:</p>
<ul>
  <li>Any requests for any file that exists in the Apache web root (<code class="language-plaintext highlighter-rouge">/var/www/html</code>) will be served directly by Apache - there is a default index page, a robots.txt and a randomly named php script that exists for troubleshooting</li>
  <li>Any requests for any URL that does not exist in the Apache web root will be forwarded to the C2 endpoint <em>if</em> it comes from a non-bot/automated/command-line user agent, otherwise the request will get a generic 404 response</li>
</ul>

<p>This is not perfect, and can be refined to filter more stringently based on the HTTP communication profile of a particular C2, but it does help prevent against casual identification of the C2 or <em>certain</em> direct attacks against its implant endpoint, as the robust and tested Apache server receives the web traffic first and does some response header modifications. The use of Apache also gives us an easy and well tested way of setting up a HTTPS listener for the C2 as well if we want this.</p>

<h1 id="what-characteristics-make-a-service-suitable-to-be-used-as-a-high-reputation-redirector">What characteristics make a service suitable to be used as a high reputation redirector?</h1>

<p>So what qualities do we look for in a cloud service that makes it a good candidate for use as a high reputation redirector for C2?</p>

<p>Lets define the goal as achieving functional HTTP/S communication for unmodified C2 software. While there are C2 solutions that support various custom communication channels such as <a href="https://www.praetorian.com/blog/using-slack-as-c2-channel-mitre-attack-web-service-t1102/">Slack</a>, <a href="https://securityintelligence.com/x-force/self-checkout-discord-c2/">Discord</a>, <a href="https://github.com/MythicC2Profiles/github">GitHub</a> and more, I was interested in HTTP/S because its commonly used and very well supported by C2 software solutions.</p>

<p>More specifically, the cloud service must:</p>
<ul>
  <li>Provide the ability to proxy, redirect or forward HTTP/1.1 traffic to a configurable backend. This backend could be a service in a private VPC hosted in the cloud providers network, or a service accessible more generally over the Internet.</li>
  <li>Provide a trusted DNS name from the cloud provider. We dont want to have to provide our own custom domain name that we need to age and build trust for.</li>
  <li>Ideally, provide a trusted certificate for the cloud provider DNS domain, that will enable HTTPS for you without having to set it up yourself and have the domain appear in certificate transparency logs as a result of creating that certificate yourself using a service such as LetsEncrypt.</li>
  <li>Support at least the GET and POST HTTP methods.</li>
  <li>Support binary content in the HTTP message body for requests and responses.</li>
  <li>Support the ability to send requests and responses uncached.</li>
  <li>Retain any cookies set by the C2 HTTP server (through the Set-Cookie header).</li>
  <li>Maintain the URL path and query parameters for all forwarded requests as-is. Some consistent content included at the start of the URL path is fine (e.g. <code class="language-plaintext highlighter-rouge">/api/</code>), as most C2 servers can deal with this, as long as everything after this section of the path is sent with the same value as sent by the client.</li>
</ul>

<h1 id="previous-work-on-using-cloud-services-as-high-reputation-redirectors">Previous work on using cloud services as high reputation redirectors</h1>

<p>This section will list a few posts from others.</p>

<h2 id="aws">AWS</h2>

<p>I found the following approaches discussed online for AWS services:</p>
<ul>
  <li><a href="https://blog.xpnsec.com/aws-lambda-redirector/">This post</a> by Adam Chester which talks about using the <a href="https://serverless.com/">Serverless framework</a> to create an <a href="https://aws.amazon.com/lambda/">AWS Lambda</a> that will receive traffic from the <a href="https://aws.amazon.com/api-gateway/">AWS API Gateway</a> and then forward it to another arbitrary destination. In Adams example, he forwards the traffic using ngrok to a local web server, but this approach could be used point to an EC2 instance in the same AWS account or other server on the Internet.</li>
  <li><a href="https://scottctaylor12.github.io/lambda-function-urls.html">This post</a> from Scott Taylor which again uses a Lambda to perform traffic redirection, but this time the entry point is via <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html">Lambda function URLs</a> instead of the API Gateway. There is associated code/instructions to deploy this Lambda to AWS as well as to create an EC2 instance to forward the traffic to.</li>
  <li>Many posts on using a <a href="https://aws.amazon.com/cloudfront/">CloudFront distribution</a> and domain fronting to receive traffic from CloudFront sites and send them to a C2 server somewhere else. Some examples are <a href="https://www.cobaltstrike.com/blog/high-reputation-redirectors-and-domain-fronting">this</a>, <a href="https://digi.ninja/blog/cloudfront_example.php">this</a>, <a href="https://www.mdsec.co.uk/2017/02/domain-fronting-via-cloudfront-alternate-domains/">this</a> and <a href="https://www.blackhillsinfosec.com/using-cloudfront-to-relay-cobalt-strike-traffic/">this</a>.</li>
</ul>

<h2 id="azure">Azure</h2>

<p>I found the following approaches discussed online for Azure services:</p>
<ul>
  <li>A number of posts discuss using <a href="https://azure.microsoft.com/en-us/products/functions">Azure Function Apps</a>, including <a href="https://web.archive.org/web/20201107235933/https://vincentyiu.com/red-team/attack-infrastructure/azure-apps-for-command-and-control">this</a> and <a href="https://0xdarkvortex.dev/c2-infra-on-azure/">this</a></li>
  <li><a href="https://0xdarkvortex.dev/c2-infra-on-azure/">This previously mentioned post</a> also discusses using the (no longer operational) Azure Edge CDN (replaced by <a href="https://azure.microsoft.com/en-us/products/cdn">Azure Front Door</a>) and the <a href="https://azure.microsoft.com/en-au/products/api-management">Azure API Management Service</a></li>
  <li>Finally we have <a href="https://rosesecurity.gitbook.io/red-teaming-ttps/guides/azure-static-web-application-c2-redirectors">this post</a> which talks about using an <a href="https://azure.microsoft.com/en-us/products/app-service/static">Azure Static Web App</a>. The discussed approach however, involves a configuration which performs a HTTP 301 client side redirect to the backend C2 server. This unfortunately allows leakage of the C2 origin, something we dont want. The discussed Static Web App service also allows the ability to proxy directly to a backend API service, an idea that is not discussed in the post. This would ordinarily be a good way to provide a high reputation redirector, <em>however</em> this API proxying approach does remove cookies set using the <code class="language-plaintext highlighter-rouge">Set-Cookie</code> header from the backend API service, meaning it will not work with certain C2 servers. This is why Azure Static Web Apps are not in my list of services for creating high reputation redirectors for C2.</li>
</ul>

<h2 id="gcp">GCP</h2>

<p>I wasn’t able to find very many examples of other people abusing GCP for fronting C2 in this way. Perhaps its bad to my poor searching, but the only example I found was <a href="https://www.securityartwork.es/2017/01/31/simple-domain-fronting-poc-with-gae-c2-server/">this post from 2017 talking about using Google App Engine for C2 fronting</a>. Even this post does not provide a working example configuration that works for forwarding to generic C2 servers, and instead used a custom C2 backend - but the App Engine service itself WILL work .</p>

<h1 id="abusable-cloud-services-by-category">Abusable cloud services by category</h1>

<p>Now we will start to look at the cloud services (AWS, Azure and GCP) that I’ve personally verified for use as high reputation redirectors for C2 in the last 6 months. This section will group the services by service category, and the following section will group them by provider.</p>

<h2 id="vm">VM</h2>

<p>When you create a virtual machine instance in Azure or AWS, and give that instance a public IP address, you can get a cloud provider domain name associated with that public IP. You can create your own proxy service on that instance that can forward incoming traffic reaching the address to a backend of your choice. This requires that you install and configure the proxying software yourself on the instance, using Apache, Nginx or similar. If you want HTTPS, you need to set this up yourself.  One no cost way you can do this is by getting a certificate from LetsEncrypt for the cloud provider domain name and configure this with your proxying software. This will result in your domain name ending up in the certificate transparency logs and receiving some amount of attention in the form of scanning of the domain, so be aware of this consequence any time you need to setup HTTPS yourself.</p>

<p><strong>Note</strong>: I only have a blog post discussing a POC implementation of the Azure VM approach, but the discussed approach is largely transferrable to AWS as well. In addition, the POC (simplified for cost and ease of use) involves running the C2 server on the same VM, with an Apache server running proxying traffic to the C2 HTTP interface listening on <code class="language-plaintext highlighter-rouge">localhost:8888</code>. This configuration is discussed in some more detail in the section <a href="#protectionfiltering-for-the-c2-servers-http-endpoint">above</a>. I dont recommend running the C2 on the same host that is receiving public traffic for any production use, and would suggest instead running just the proxy on the public VM, and having the C2 on its own seperated host. The Apache config in the design forwards communication to <code class="language-plaintext highlighter-rouge">http://backend:8888</code> (with the address of <code class="language-plaintext highlighter-rouge">backend</code> defined in the <code class="language-plaintext highlighter-rouge">/etc/hosts</code> file), and this can be modified to a new destination of your choice.</p>

<p><strong>AWS EC2</strong>:</p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">ec2-&lt;IP-ADDRESS&gt;.&lt;REGION&gt;.compute.amazonaws.com</code></li>
  <li><strong>HTTPS?</strong>: BYO (configure using LetsEncrypt or similar)</li>
</ul>

<p><strong><a href="/2025/07/10/azure-service-c2-forwarding-part3.html">Azure VM</a></strong>:</p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.&lt;REGION&gt;.cloudapp.azure.com</code></li>
  <li><strong>HTTPS?</strong>: BYO (configure using LetsEncrypt or similar)</li>
</ul>

<h2 id="content-delivery-networks-cdn">Content Delivery Networks (CDN)</h2>

<p>Content Delivery Networks make your content more globally available using points of presence around the world. These services are designed to make generic websites more available, so configuring these services for C2 redirection just involves setting the backend C2 service in the CDN configuration. Be aware that these CDN services are also biased towards caching content, which will cause a problem for C2 HTTP communication, so you need to make sure you disable caching to work around this.</p>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#cloudfront-distribution-forwarding">AWS CloudFront</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.cloudfront.net</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/06/25/azure-service-c2-forwarding-part2.html">Azure Front Door</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;-&lt;RANDOM&gt;.*.azurefd.net</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<h2 id="api-services">API Services</h2>

<p>API Services make it easy to run your HTTP based API and make it available on the Internet. The approach required to configure these services to forward arbitrary HTTP traffic differs depending on the service. For AWS, you have options to either send incoming traffic to backend serverless code which will forward the messages, or you can configure direct proxying. For Azure and GCP, you need to provide specifically structured API definitions that will forward traffic from arbitrary routes.</p>

<p><strong>Note</strong>: As mentioned, the AWS API Gateway can use two different backend, both approaches of which are discussed in the <a href="/2023/08/30/aws-service-C2-forwarding.html">same post here</a>. The first approach is <a href="/2023/08/30/aws-service-C2-forwarding.html#api-gateway-to-lambda">forwarding to a Lambda</a>, and the second approach is <a href="/2023/08/30/aws-service-C2-forwarding.html#api-gateway-direct-proxying">direct proxying to a backend HTTP service</a>. Lambdas can also have web traffic directed into them using two different approaches, and are discussed below in the Serverless category.</p>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#api-gateway-to-lambda">AWS API Gateway forwarding to Lambda</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.execute-api.&lt;REGION&gt;.amazonaws.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#api-gateway-direct-proxying">AWS API Gateway direct proxying</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.execute-api.&lt;REGION&gt;.amazonaws.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/07/30/azure-service-c2-forwarding-part4.html">Azure API Management Service</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.azure-api.net</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/06/25/azure-service-c2-forwarding-part2.html">GCP API Gateway</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;-&lt;RANDOM&gt;.&lt;REGIONID&gt;.gateway.dev</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<h2 id="managed-website-services">Managed Website services</h2>

<p>These services make it easier to launch a website on the Internet. In the case of AWS Amplify this website can be a direct proxy configuration to a backend site, for Elastic Beanstalk you can run a custom web application that can forward traffic to a chosen backend.</p>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#aws-amplify-application">AWS Amplify</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.&lt;RANDOM&gt;.amplifyapp.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/08/27/aws-service-c2-forwarding-part3.html">AWS Elastic Beanstalk</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.&lt;RANDOM&gt;.&lt;REGION&gt;.elasticbeanstalk.com</code></li>
  <li><strong>HTTPS?</strong>: BYO (configure using LetsEncrypt or similar)</li>
</ul>

<h2 id="container-execution-services">Container execution services</h2>

<p>These services make it easier to run a container and make it accessible on the Internet for web traffic. To use this for C2 redirection, we can run a custom container similar to <a href="https://github.com/stephenbradshaw/HTTPForwardContainer">this</a> that will forward traffic to a configured backend. The GCP Cloud Run service mentioned in this section also operates as a Serverless service, so will be listed in both sections - if you provide it a container it runs the container, if you provide it code it will convert that to a container for you and run it.</p>

<p><strong>Note</strong>: The GCP Cloud Run <a href="/2025/04/23/gcp-service-C2-forwarding-part-3.html">post</a> I have written only discusses implementation using the Serverless code approach.</p>

<p><strong><a href="/2025/08/20/aws-service-c2-forwarding-part2.html">AWS AppRunner</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.&lt;REGION&gt;.awsapprunner.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/08/27/azure-service-c2-forwarding-part5.html">Azure Container Applications</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.&lt;ENVIRONMENT-ID&gt;.&lt;REGION&gt;.azurecontainerapps.io</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/04/23/gcp-service-C2-forwarding-part-3.html">GCP Cloud Run</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;-&lt;PROJECT&gt;.&lt;REGION&gt;.run.app</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<h2 id="serverless-code-execution-services">Serverless code execution services</h2>

<p>Serverless code execution services allow you to provide a bit of code that the cloud provider will wrap in a lightweight VM or container that will run without you having to maintain the host. This will then be made available to receive web traffic at a cloud provider domain name.  The code used in the POCs I discuss below are variants on a Python app that rewrites HTTP requests. <a href="https://flask.palletsprojects.com/">Flask</a> is used for receiving web requests where this is not handled by the parent framework, and either <a href="https://requests.readthedocs.io/">requests</a> or the Python standard <a href="https://docs.python.org/3/library/http.html">HTTP</a> and <a href="https://docs.python.org/3/library/ssl.html">SSL</a> libraries are used to replay the requests to the backend C2 service.</p>

<p><strong>Note</strong>: The Lambda function code referred to in the post below is an older version that requires the Python <a href="https://requests.readthedocs.io/">requests</a>  module which used to be included in the Lambda Python runtime environment in older versions, but now needs to be added alongside your code in a layer. My updated version of the Lambda code available <a href="https://github.com/stephenbradshaw/pentesting_stuff/blob/master/redirector_lambda/lambda_function_updated.py">here</a> uses the Python standard http and ssl libraries, and can be used with the latest available Python Lambda runtime version without having to add any additional modules.</p>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#api-gateway-to-lambda">AWS API Gateway forwarding to Lambda</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.execute-api.&lt;REGION&gt;.amazonaws.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#function-url-to-lambda">AWS Lambda from Function URL</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.lambda-url.&lt;REGION&gt;.on.aws</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/05/07/azure-service-C2-forwarding.html">Azure Function App</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.azurewebsites.net</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/03/26/gcp-service-C2-forwarding.html">GCP Cloud Run</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;-&lt;PROJECT&gt;.&lt;REGION&gt;.run.app</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/04/23/gcp-service-C2-forwarding-part-3.html">GCP Cloud Run Functions</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;REGION&gt;-&lt;PROJECT&gt;.cloudfunctions.net/&lt;YOUR-LABEL&gt;/</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/03/26/gcp-service-C2-forwarding.html">GCP App Engine</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;PROJECT&gt;.&lt;REGION&gt;.*.appspot.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<h1 id="abusable-cloud-services-by-provider">Abusable cloud services by provider</h1>

<p>This section lists the same services from the section above, but categorised by cloud provider.</p>

<h2 id="aws-services">AWS Services</h2>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#cloudfront-distribution-forwarding">AWS CloudFront</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.cloudfront.net</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#api-gateway-to-lambda">AWS API Gateway forwarding to Lambda</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.execute-api.&lt;REGION&gt;.amazonaws.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#api-gateway-direct-proxying">AWS API Gateway direct proxying</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.execute-api.&lt;REGION&gt;.amazonaws.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#function-url-to-lambda">AWS Lambda from Function URL</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.lambda-url.&lt;REGION&gt;.on.aws</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2023/08/30/aws-service-C2-forwarding.html#aws-amplify-application">AWS Amplify</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.&lt;RANDOM&gt;.amplifyapp.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/08/27/aws-service-c2-forwarding-part3.html">AWS Elastic Beanstalk</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.&lt;RANDOM&gt;.&lt;REGION&gt;.elasticbeanstalk.com</code></li>
  <li><strong>HTTPS?</strong>: BYO</li>
</ul>

<p><strong><a href="/2025/08/20/aws-service-c2-forwarding-part2.html">AWS AppRunner</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;RANDOM&gt;.&lt;REGION&gt;.awsapprunner.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong>AWS EC2</strong> (follow the approach discussed in the post on <a href="/2025/07/10/azure-service-c2-forwarding-part3.html">abusing Azure VMs</a> )</p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">ec2-&lt;IP-ADDRESS&gt;.&lt;REGION&gt;.compute.amazonaws.com</code></li>
  <li><strong>HTTPS?</strong>: BYO (configure using LetsEncrypt or similar)</li>
</ul>

<h2 id="azure-services">Azure services</h2>

<p>All of the following Azure services can be easily deployed in POC form using deployment templates as discussed in the post <a href="/2025/06/04/azure_c&amp;c_poc_infra_deployment.html">here</a>. These templates are available on GitHub <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment">here</a>.</p>

<p><strong><a href="/2025/05/07/azure-service-C2-forwarding.html">Azure Function App</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.azurewebsites.net</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/06/25/azure-service-c2-forwarding-part2.html">Azure Front Door</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;-&lt;RANDOM&gt;.*.azurefd.net</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/07/10/azure-service-c2-forwarding-part3.html">Azure VM</a></strong>:</p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.&lt;REGION&gt;.cloudapp.azure.com</code></li>
  <li><strong>HTTPS?</strong>: BYO (configure using LetsEncrypt or similar)</li>
</ul>

<p><strong><a href="/2025/07/30/azure-service-c2-forwarding-part4.html">Azure API Management Service</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.azure-api.net</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/08/27/azure-service-c2-forwarding-part5.html">Azure Container Applications</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;.&lt;ENVIRONMENT-ID&gt;.&lt;REGION&gt;.azurecontainerapps.io</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<h2 id="gcp-services">GCP services</h2>

<p><strong><a href="/2025/03/26/gcp-service-C2-forwarding.html">GCP Cloud Run</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;-&lt;PROJECT&gt;.&lt;REGION&gt;.run.app</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/04/23/gcp-service-C2-forwarding-part-3.html">GCP Cloud Run Functions</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;REGION&gt;-&lt;PROJECT&gt;.cloudfunctions.net/&lt;YOUR-LABEL&gt;/</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/03/26/gcp-service-C2-forwarding.html">GCP App Engine</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;PROJECT&gt;.&lt;REGION&gt;.*.appspot.com</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>

<p><strong><a href="/2025/06/25/azure-service-c2-forwarding-part2.html">GCP API Gateway</a></strong></p>
<ul>
  <li><strong>Domain name</strong>: <code class="language-plaintext highlighter-rouge">&lt;YOUR-LABEL&gt;-&lt;RANDOM&gt;.&lt;REGIONID&gt;.gateway.dev</code></li>
  <li><strong>HTTPS?</strong>: Yes</li>
</ul>]]></content><author><name>Stephen Bradshaw</name></author><category term="Serverless" /><category term="C2" /><category term="domain fronting" /><category term="forwarding" /><category term="sliver" /><category term="Azure service fronting" /><category term="AWS" /><category term="Azure" /><category term="GCP" /><category term="App Runner" /><category term="API Gateway" /><category term="Azure API Management Service" /><category term="Lambda" /><category term="CloudFront" /><category term="Amplify" /><category term="AppRunner" /><category term="Elastic Beanstalk" /><category term="Function Apps" /><category term="Function URL" /><category term="Cloud Run Functions" /><category term="Azure Service" /><category term="Azure Front Door" /><category term="Azure Container Apps" /><category term="App Engine" /><category term="redirector" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">AWS Service Command and Control HTTP traffic forwarding part 3</title><link href="/2025/08/27/aws-service-c2-forwarding-part3.html" rel="alternate" type="text/html" title="AWS Service Command and Control HTTP traffic forwarding part 3" /><published>2025-08-27T06:55:00+00:00</published><updated>2025-08-27T06:55:00+00:00</updated><id>/2025/08/27/aws-service-c2-forwarding-part3</id><content type="html" xml:base="/2025/08/27/aws-service-c2-forwarding-part3.html"><![CDATA[<p align="center">
  <img width="500" height="500" src="/assets/img/climbing_the_elastic_beanstalk.png" alt="Climbing the Elastic Beanstalk" />
</p>

<p>Heres the third entry in my series on abusing AWS services to provide C2 high reputation redirectors.</p>

<p>The first part in this series was <a href="/2023/08/30/aws-service-C2-forwarding.html">all the way back in 2023</a>, and in it I discussed abusing the following services to provide a trusted endpoint for C2 communications:</p>
<ul>
  <li><a href="https://aws.amazon.com/api-gateway/">API Gateway</a> direct proxying,</li>
  <li><a href="https://aws.amazon.com/lambda/">Lambda</a> receiving traffic through either <a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html">Function Urls</a> or the <a href="https://aws.amazon.com/api-gateway/">API Gateway</a>,</li>
  <li><a href="https://aws.amazon.com/amplify/">Amplify</a> apps and,</li>
  <li><a href="https://aws.amazon.com/cloudfront/">CloudFront</a> distributions.</li>
</ul>

<p>The <a href="/2025/08/20/aws-service-c2-forwarding-part2.html">second post</a> was last week and covered <a href="https://aws.amazon.com/apprunner/">AWS App Runner</a>.</p>

<p>Check out those posts for more detail.</p>

<p>This time around, after diversions into the worlds of <a href="/tags.html#azure">Azure</a> and <a href="/tags.html#gcp">GCP</a>, I’ve looped back for another look at AWS, to catch any services I missed the first time around.</p>

<p>In this post I will be looking at <a href="https://aws.amazon.com/elasticbeanstalk/">AWS Elastic Beanstalk</a>, which is a service for easy migration, deployment and scaling of full stack applications on AWS. In this post we will see how it can instead be used to provide a trusted endpoint to receive C2 traffic and forward it to a C2 server running on an EC2 instance in a private VPC. We will do this through describing how you can setup a simple POC environment in your own AWS account.</p>

<p>Want more detail on why this would be useful? See <a href="/2025/03/26/gcp-service-C2-forwarding.html#what-is-the-purpose-of-c2-fronting">this section in my first blog post on GCP service abuse.</a></p>

<h1 id="overview">Overview</h1>

<p>The POC environment we will be looking at will use the following AWS resources:</p>
<ol>
  <li>A VPC that our C2 instance and Elastic Beanstalk Application will use for internal communications. At minimum this will need one public subnet, that our EC2 instance and Elastic Beanstalk Application will be connected to.</li>
  <li>At least one EC2 instance running a C2 service to receive the traffic.</li>
  <li>A security group associated with the EC2 instance to control access to it.</li>
  <li>A security group for our Elastic Beanstalk environment</li>
  <li>An Elastic Beanstalk Environment to run our app that connects to our VPC</li>
  <li>An Elastic Beanstalk Application running in the Environment that takes incoming traffic and forwards it to the C2 EC2 instance</li>
</ol>

<p>For the purpose of this post I will discuss parts 1-3 in the section named <code class="language-plaintext highlighter-rouge">The C2 Server and supporting networking</code>, and 4-6 in the section named <code class="language-plaintext highlighter-rouge">Elastic Beanstalk setup</code>. Given a lot of this is pretty straightforward to deploy I will just provide general clickops creation instructions using the AWS web console.</p>

<h1 id="the-c2-server-and-supporting-networking">The C2 server and supporting networking</h1>

<p>The C2 service and supporting networking will use a VPC, an EC2 instance attached to that VPC and a security group associated with the EC2 instance to control network access to the instance.  The steps in this section are exactly the same as the same section from <a href="/2025/08/20/aws-service-c2-forwarding-part2.html">post 2</a> in this AWS series, repeated here for ease of use.</p>

<h2 id="vpc">VPC</h2>

<p>An appropriate VPC can be deployed by visiting the VPC section of the AWS console, hitting the <code class="language-plaintext highlighter-rouge">Create VPC</code> button, and using the Wizard to create <code class="language-plaintext highlighter-rouge">VPC and more</code>.</p>

<p>In the wizard you can get away with the following minimal settings to create something functional for this POC:</p>
<ul>
  <li>1 availability zone</li>
  <li>1 public subnet</li>
  <li>0 private subnets</li>
  <li>0 NAT gateways</li>
  <li>0 VPC endpoints</li>
</ul>

<p>This will look something like the following:</p>

<p align="center">
  <img src="/assets/img/basic_aws_vpc.png" alt="Minimum VPC" />
</p>

<p>After creation, ensure all of the following resources that you create are associated with this new VPC and in the same AWS region.</p>

<h2 id="ec2-and-security-group">EC2 and Security group</h2>

<p>We can create the EC2 instance and security group in one step. Visit the EC2 section of the console and hit <code class="language-plaintext highlighter-rouge">Launch Instance</code> to start the new instance wizard.</p>

<p>The settings below are some simple guidelines and minimums to consider when configuring the instance in the wizard.</p>

<p><strong>Machine image</strong></p>
<ul>
  <li><strong>AMI</strong>: Ubuntu Server 24.04 (HMV), SSD Volume type</li>
  <li><strong>Architecture</strong>: 64 bit architecture</li>
</ul>

<p><strong>Instance type</strong></p>
<ul>
  <li><strong>Instance type</strong>: Minimum of <code class="language-plaintext highlighter-rouge">t2.nano</code> or <code class="language-plaintext highlighter-rouge">t3.nano</code></li>
</ul>

<p><strong>Key Pair</strong></p>
<ul>
  <li><strong>Key</strong>: Generate a new key or reuse an existing key</li>
</ul>

<p><strong>Network settings</strong></p>
<ul>
  <li><strong>Network</strong>: Select your new VPC</li>
  <li><strong>Subnet</strong>: Select the public subnet in your new VPC</li>
  <li><strong>Auto assign public-ip</strong>: <code class="language-plaintext highlighter-rouge">Enable</code></li>
  <li><strong>Security Group</strong>: Create a new security group and give it a sensible name and description (you cant change the name later, so pick sensibly!)</li>
  <li><strong>SSH Access</strong>: Allow SSH from your IP address in the security group config for direct access, or you can assign an <code class="language-plaintext highlighter-rouge">IAM instance profile</code> in the <code class="language-plaintext highlighter-rouge">Advanced details</code> section allowing SSM access if you want to access the instance via <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-enable-ssh-connections.html">SSM intermediated SSH</a></li>
</ul>

<p><strong>Configure storage</strong></p>
<ul>
  <li><strong>Volume type</strong>: Choose <code class="language-plaintext highlighter-rouge">30GB</code> of storage and the <code class="language-plaintext highlighter-rouge">gp3</code> volume type</li>
</ul>

<p><strong>Advanced Settings</strong></p>

<p>Go down to the <code class="language-plaintext highlighter-rouge">User data - optional</code> and  include <a href="https://github.com/stephenbradshaw/pentesting_stuff/blob/master/setup_scripts/c2_setup.sh">this</a> script. This will configure the instance as a <a href="https://github.com/BishopFox/sliver">Sliver</a> C2 server with an Apache forwarder listening on port 80 that receives implant traffic.</p>

<p>Once the instance is up and running and the user data script has completed running, the environment will look something like the following:</p>

<p align="center">
  <img src="/assets/img/aws_poc_simple.png" alt="Simple AWS POC environment" />
</p>

<p>We have a C2 server running, but at the moment we have no way to route implant traffic into the environment. This will be setup in the next section.</p>

<h1 id="elastic-beanstalk-setup">Elastic Beanstalk setup</h1>

<h2 id="create-the-elastic-beanstalk-security-group">Create the Elastic Beanstalk security group</h2>

<p>We need to create a security group for the Elastic Beanstalk Application we are about to create. This can be done from the AWS console <a href="https://console.aws.amazon.com/ec2/home#CreateSecurityGroup">here</a>. Just make sure its created in the VPC we made earlier. We dont need to set any specific rules in the group, we will use it as a source to allow the Elastic Beanstalk Application we create to talk to the EC2 instance we created in the previous section.</p>

<h2 id="create-the-elastic-beanstalk-environment">Create the Elastic Beanstalk environment</h2>

<p>The Elastic Beanstalk environment creation wizard can be started by hitting <code class="language-plaintext highlighter-rouge">Create application</code> after visiting the <a href="https://console.aws.amazon.com/elasticbeanstalk/home">Elastic Beanstalk section of the AWS console</a>. The wizard has six steps, and the below will describe the important values to change for each step. Any option not specifically mentioned can be left at the default values.</p>

<p><strong>Step 1: Configure environment</strong></p>
<ul>
  <li><strong>Environment tier</strong>:  Web server environment</li>
  <li><strong>Application information</strong>
    <ul>
      <li><strong>Application name</strong>:  A name for your application - I used <code class="language-plaintext highlighter-rouge">teststalk</code></li>
    </ul>
  </li>
  <li><strong>Environment information</strong>
    <ul>
      <li><strong>Environment name</strong>: This is used to set the first component of your apps domain name. I used <code class="language-plaintext highlighter-rouge">teststalk-env</code></li>
      <li><strong>Domain</strong>: This is used to set the second component of the apps domain name. You can leave this blank for an autogenerated value (like I did) or pick something that has to be globally unique in AWS for your chosen region.</li>
    </ul>
  </li>
  <li><strong>Platform</strong>
    <ul>
      <li><strong>Platform</strong>: Python</li>
      <li><strong>Platform branch</strong>: Latest Python version on 64 bit Amazon Linux</li>
      <li><strong>Platform version</strong>: Pick the recommended version</li>
    </ul>
  </li>
  <li><strong>Application code</strong>: Start with the Sample application which we will replace later once the environment is running</li>
  <li><strong>Presets</strong>: Any option will work, select based on your performance and cost requirements</li>
</ul>

<p>Heres a screenshot</p>

<p align="center">
  <img src="/assets/img/ebs_step1.png" alt="Elastic Beanstalk config step 1" />
</p>

<p><strong>Step 2: Configure service access</strong></p>
<ul>
  <li><strong>Service role</strong>: Use the wizard to create a suitable role</li>
  <li><strong>EC2 instance profile</strong>: Optional, if you use SSM to access instances make sure you set a profile with SSM permissions here</li>
  <li><strong>EC2 key pair</strong>: Optional, but picking the same key used for your C2 EC2 instance is a good choice</li>
</ul>

<p><strong>Step 3: Set up networking, database, and tags</strong></p>
<ul>
  <li><strong>Instance settings</strong>
    <ul>
      <li><strong>VPC</strong>: Pick the VPC you created earlier</li>
      <li><strong>Public IP address</strong>: Enable</li>
      <li><strong>Instance subnets</strong>: Pick the public subnet in your new VPC</li>
    </ul>
  </li>
</ul>

<p><strong>Step 4: Configure instance traffic and scaling</strong></p>
<ul>
  <li><strong>EC2 security groups</strong>: Pick the group you created for Elastic Beanstalk here</li>
</ul>

<p><strong>Step 5: Configure updates, monitoring, and logging</strong></p>
<ul>
  <li><strong>Managed platform updates</strong>: Disable</li>
  <li><strong>Platform software</strong>
    <ul>
      <li><strong>Container options</strong>: Proxy server -&gt; Apache</li>
      <li><strong>Environment properties</strong>: Set a <code class="language-plaintext highlighter-rouge">DESTINATION</code> environment variable and set the value to the internal IP address of your C2 EC2 instance</li>
    </ul>
  </li>
</ul>

<p><strong>Step 6: Review</strong></p>
<ul>
  <li>Check all the options above have been set correctly and hit <code class="language-plaintext highlighter-rouge">Deploy</code></li>
</ul>

<p>Monitor the deployment in the AWS console and once the <code class="language-plaintext highlighter-rouge">Health</code> of the deployment in the Elastic Beanstalk Environment overview screen is Green you should be able to get the Domain value for you environment and visit it to ensure you can correctly visit the Sample applications web page (the value is hyperlinked in the console so just clicking will open it in a new tab).</p>

<p>My Domain value was <code class="language-plaintext highlighter-rouge">teststalk-env.eba-5p3yniaa.ap-southeast-2.elasticbeanstalk.com</code>.</p>

<p>If you can see the sample app website you can now move ahead to deploying the forwarding web app that will instead route traffic to your C2.</p>

<h2 id="deploy-the-application-code">Deploy the application code</h2>

<p>The code we will be replacing the sample app with is available <a href="https://github.com/stephenbradshaw/AWSEBSC2Forwarder">here</a>. Clone it locally and create a deployment zip file (<code class="language-plaintext highlighter-rouge">/tmp/dep.zip</code>) like so after switching to the source as your present working directory:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zip -r /tmp/dep.zip application.py requirements.txt
</code></pre></div></div>

<p>Use the <code class="language-plaintext highlighter-rouge">Upload and deploy</code> button in the Elastic Beanstalk Environment overview section of the console, and in the following window, upload the zip file you created, and set a new version label for the app.</p>

<p align="center">
  <img src="/assets/img/ebs_deploy.png" alt="Elastic Beanstalk deploy code" />
</p>

<p>Once you have hit <code class="language-plaintext highlighter-rouge">Deploy</code> and the Running Version section of the Beanstalks Environment page lists your new version, you should be able to access the Apache redirector on your EC2 C2 server by visiting your assigned domain using a http (NOT HTTPS!) request and see a response like the following.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl http://teststalk-env.eba-5p3yniaa.ap-southeast-2.elasticbeanstalk.com/ 
&lt;html&gt;
&lt;/html&gt;
</code></pre></div></div>

<p>The setup we now have is represented by the following network diagram.</p>

<p align="center">
  <img src="/assets/img/aws_poc_elastic_beanstalk.png" alt="Elastic Beanstalk C2 diagram" />
</p>

<h1 id="accessing-the-beanstalk-via-https">Accessing the Beanstalk via HTTPS?</h1>

<p>You may have noticed that all instructions of how to access the Elastic Beanstalk app above specifically referenced HTTP and not HTTPS. Unlike some other fronting approaches I have discussed before, Elastic Beanstalk does NOT automatically provide HTTPS accessibility or a trusted certificate for you to use. It is however possible to get a free certificate through LetsEncrypt and use it for your app. This will involve adding some additional files to the deployment zip file you upload. Keep in mind this will get your endpoint some additional attention (and some free security scans!) as your Beanstalk app domain name will appear in the certificate transparency logs.</p>

<p>To enable HTTPS you can follow a process like <a href="https://medium.com/edataconsulting/how-to-get-a-ssl-certificate-running-in-aws-elastic-beanstalk-using-certbot-6daa9baa3997">the one described in this blog post</a>. Just be sure to replace <code class="language-plaintext highlighter-rouge">nginx</code> in the examples with <code class="language-plaintext highlighter-rouge">apache</code> as we have used in this POC environment.</p>]]></content><author><name>Stephen Bradshaw</name></author><category term="AWS" /><category term="Elastic Beanstalk" /><category term="C2" /><category term="domain fronting" /><category term="forwarding" /><category term="redirector" /><category term="Sliver" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Azure Service Command and Control HTTP traffic forwarding part 5</title><link href="/2025/08/27/azure-service-c2-forwarding-part5.html" rel="alternate" type="text/html" title="Azure Service Command and Control HTTP traffic forwarding part 5" /><published>2025-08-27T03:20:00+00:00</published><updated>2025-08-27T03:20:00+00:00</updated><id>/2025/08/27/azure-service-c2-forwarding-part5</id><content type="html" xml:base="/2025/08/27/azure-service-c2-forwarding-part5.html"><![CDATA[<p align="center">
  <img width="500" height="500" src="/assets/img/container_apps_puppeteer.png" alt="Azure Container Apps" />
</p>

<p>Back in July this year I released the <a href="/2025/07/30/azure-service-c2-forwarding-part4.html">fourth</a> and (I thought) final entry in my series on exploiting Azure native services to provide high reputation C2 redirectors. Well, things move fast in cloud land, and I’ve discovered yet another Azure service that can be abused for this purpose - <a href="https://azure.microsoft.com/en-us/products/container-apps">Azure Container Apps</a>. So now here is the final FINAL entry in the Azure specific series. (I also have entries talking about <a href="/tags.html#GCP">GCP</a> and <a href="/tags.html#AWS">AWS</a> if you’re interested).</p>

<p>Azure Container Apps provide an easy way to run a containerised applications in the cloud, with internal VPC connectivity and Internet accessibility (with an included TLS certificate and HTTPS support) at the <code class="language-plaintext highlighter-rouge">.azurecontainerapps.io</code> domain.</p>

<p>Similar to other <a href="/tags.html#azure-service-fronting">entries in the series</a>, this blog post will demonstrate how you can setup your own POC C2 environment with some base resources (dedicated resource group, VPC, Linux VM instance running <a href="https://github.com/BishopFox/sliver">Sliver</a>, etc) fronted by an Azure Container App that routes traffic from infected hosts into the C2 application.</p>

<p>I’ve again updated the <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment">Azure C2 POC base template set</a> I’ve been using in the other entries in the series to make it easy for you.</p>

<p>Lets look at how we can use these templates to setup a working Container App C2 POC environment. We will need to do the following:</p>
<ul>
  <li>Create the base resources (resource group, networking, C2 vm, etc) for the C2 we will forward to</li>
  <li>Create the Container App application that will forward traffic to the C2</li>
</ul>

<h1 id="create-the-base-resources">Create the base resources</h1>

<p>Base resources for this POC environment include a Linux VM with an Apache forwarder and the Sliver C2 software installed and running, plus the necessary network plumbing. Ive discussed details about these resources previously in a post <a href="/2025/05/07/azure-service-C2-forwarding.html#manual-deployment---azure-poc-environment">here</a> and how the deployment templates work in a post <a href="/2025/06/04/azure_c&amp;c_poc_infra_deployment.html">here</a>, but I will briefly cover how to set these up here so you can easily start from scratch.</p>

<p>Create a ssh key to use when accessing the VM, if you dont already have one.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_azure
</code></pre></div></div>

<p>Make sure the <a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt">Azure CLI is installed and working</a>.</p>

<p>Create the <code class="language-plaintext highlighter-rouge">C2VMRG</code> resource group.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ az group create --name C2VMRG --location westus2
</code></pre></div></div>

<p>Clone the POC deployment templates locally</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone https://github.com/stephenbradshaw/AzureC2PocDeployment.git
</code></pre></div></div>

<p>Modify the <code class="language-plaintext highlighter-rouge">base</code> deployment templates for your environment (which uses your Internet facing IP and public key data for access):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd AzureC2PocDeployment/base
$ export PUBLIC_IP=$(curl ipinfo.io/ip)
$ sed -i "s/&lt;YOUR_IP_ADDRESS_HERE&gt;/$PUBLIC_IP/g" parameters.json
$ export PUBLIC_KEY=$(cat ~/.ssh/id_ed25519_azure.pub | sed 's/\//\\\//g')
$ sed -i "s/&lt;YOUR_PUBLIC_KEY_HERE&gt;/$PUBLIC_KEY/g" parameters.json
$ export DNS_LABEL=myvm1234567890
$ sed -i "s/&lt;YOUR_DNS_LABEL_HERE&gt;/$DNS_LABEL/g" parameters.json
</code></pre></div></div>

<p>Then deploy:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ az deployment group create --resource-group C2VMRG --template-file template.json --parameters @parameters.json
</code></pre></div></div>

<p>Once the deployment is done, you can also get the public IP address of your VM as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ az vm list-ip-addresses -g C2VMRG --query "[].virtualMachine[].network.publicIpAddresses[].ipAddress | [0]"
</code></pre></div></div>

<p>Add an ssh hosts file entry for your VM as follows, replacing <code class="language-plaintext highlighter-rouge">&lt;PUBLIC_IP_ADDRESS&gt;</code> with the output of the above.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host azurevm
    hostname &lt;PUBLIC_IP_ADDRESS&gt;
    identityfile ~/.ssh/id_ed25519_azure
    user ubuntu
</code></pre></div></div>

<p>You should now be able to ssh into that instance once it has fully deployed - which will take a few minutes. Access will be limited to the Internet facing IP address of the machine you performed the deployment from using a Network Security Group deployed with the resources defined in the templates.</p>

<p>To access the installed C2 you can ssh in and run the Sliver client like so:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/opt/sliver/sliver-client
</code></pre></div></div>

<p>The VM is setup using a userdata script that installs Apache and Sliver and configures it, etc,  on initial VM launch, and it should be basically “ready to go” once the script completes.  The script creates a <code class="language-plaintext highlighter-rouge">setupdone</code> file in the user home directory to let you know when its finished, because it is possible to ssh in before this setup is done. On some occaisions however, for reasons I’m not entirely clear on, parts of the install process will fail - most likely the creation of the Sliver listener job on local port 8888. You can see what steps are performed to help troublshoot any issues by looking at the <code class="language-plaintext highlighter-rouge">install.sh</code> in the <code class="language-plaintext highlighter-rouge">base</code> template directory (this is essentially a plaintext copy of the base64 userData field in <code class="language-plaintext highlighter-rouge">parameters.json</code>). Its also possible to pull this script from the VMs metadata service like so:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -s -H "Metadata:true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | jq -r .compute.userData | base64 -d
</code></pre></div></div>

<h1 id="create-the-container-app-application">Create the Container App application</h1>

<p>Now we get to the new content - the container app. We will be deploying a copy of my http forwarding container, the latest version of which will will be pulled from the <a href="https://hub.docker.com/r/stephenbradshaw/httpforwardcontainer">docker hub here</a> by the automation templates. If you want to see the source for the container, its available <a href="https://github.com/stephenbradshaw/HTTPForwardContainer">here</a>.</p>

<p>The template will setup a Container App in a new Container App environment that is connected to our internal VPC, allowing traffic to be routed between our new forwarding app and the internal C2 VM over this private network.</p>

<p>To deploy, change to the <code class="language-plaintext highlighter-rouge">containerapp</code> folder in your local copy of the <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment">Azure C2 POC base template set</a>.</p>

<p>Now we need to choose an APP_NAME for our container app. This will form part of the address by which our app will be accessible on the Internet, so pick something sensible. I’ll use <code class="language-plaintext highlighter-rouge">testabc123xyz</code> for this example.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export APP_NAME=testabc123xyz
sed -i "s/&lt;APP_NAME&gt;/$APP_NAME/g" parameters.json
</code></pre></div></div>

<p>Now do the deployment of the service.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az deployment group create --resource-group C2VMRG --template-file template.json --parameters @parameters.json
</code></pre></div></div>

<p>Once deployment is done you can get the domain name at which your container app is available at using a command like the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az containerapp list -g C2VMRG --query '[0].properties.configuration.ingress.fqdn'
</code></pre></div></div>

<p>The domain you will get back will be in the form <code class="language-plaintext highlighter-rouge">&lt;APP_NAME&gt;.&lt;RANDOM_WORD&gt;-&lt;HEX_VALUE&gt;.&lt;REGION&gt;.azurecontainerapps.io</code>.</p>

<p>Mine was <code class="language-plaintext highlighter-rouge">testabc123xyz.kindrock-8322fc0c.westus2.azurecontainerapps.io</code>. In this case the <code class="language-plaintext highlighter-rouge">kindrock-8322fc0c.westus2.azurecontainerapps.io</code> component of the name is associated with the Container Apps environment created to host our app, and it gets it’s own wildcard certificate generated for use with HTTPS. This means that the HTTPS certificate presented for connections will be new (with an “Issued” date matching the time of creation of the Container App Environment) and this component of the name will appear in certificate transparency logs, so keep that fact in mind.</p>

<p>We can test this to make sure it is working by fetching the index content from the C2 Apache forwarding server by doing something like the below (making sure you see the contents of the <code class="language-plaintext highlighter-rouge">/var/www/html/index.html</code> in the curl response - its empty HTML by default):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export CADOMAIN=$(az containerapp list -g C2VMRG --query '[0].properties.configuration.ingress.fqdn' | jq -r)
$ curl "https://$CADOMAIN/"
&lt;html&gt;
&lt;/html&gt;
</code></pre></div></div>

<p>The architecture we have deployed looks something like the following, with two externally accessible endpoints for implant traffic to reach our C2:</p>

<p align="center">
  <img src="/assets/img/c2_architecture_basic_azure_ca.png" alt="C2 with single Azure VM and Azure Container App" />
</p>

<p>When you are done with the POC environment, you can easily delete the resource group and all associated resources as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az group delete --name C2VMRG
</code></pre></div></div>

<h1 id="conclusion">Conclusion</h1>

<p>Well, Ive said this before and been wrong but who knows - I think this is (really truly) the final Azure post I will do on this subject. However if you’re aware of any Azure services Ive missed that could be abused in this way please let me know.</p>]]></content><author><name>Stephen Bradshaw</name></author><category term="Azure" /><category term="C2" /><category term="domain fronting" /><category term="forwarding" /><category term="sliver" /><category term="Azure service fronting" /><category term="Azure Container Apps" /><category term="redirector" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">AWS Service Command and Control HTTP traffic forwarding part 2</title><link href="/2025/08/20/aws-service-c2-forwarding-part2.html" rel="alternate" type="text/html" title="AWS Service Command and Control HTTP traffic forwarding part 2" /><published>2025-08-20T03:04:00+00:00</published><updated>2025-08-20T03:04:00+00:00</updated><id>/2025/08/20/aws-service-c2-forwarding-part2</id><content type="html" xml:base="/2025/08/20/aws-service-c2-forwarding-part2.html"><![CDATA[<p align="center">
  <img width="500" height="500" src="/assets/img/hacker-cloud-runner.png" alt="AWS" />
</p>

<p>Heres a second entry in my series on abusing AWS services to provide high reputation redirectors for Command and Control. The first AWS part in this series was <a href="/2023/08/30/aws-service-C2-forwarding.html">all the way back in 2023</a>, and in that entry I discussed abusing the following services to provide a trusted endpoint for C2 implant communications:</p>
<ul>
  <li><a href="https://aws.amazon.com/api-gateway/">API Gateway</a> direct proxying,</li>
  <li><a href="https://aws.amazon.com/lambda/">Lambda</a> receiving traffic through either <a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html">Function Urls</a> or the <a href="https://aws.amazon.com/api-gateway/">API Gateway</a>,</li>
  <li><a href="https://aws.amazon.com/amplify/">Amplify</a> apps and,</li>
  <li><a href="https://aws.amazon.com/cloudfront/">CloudFront</a> distributions.</li>
</ul>

<p>Check out that post if any of those topics interest you.</p>

<p>This time around, after diversions into the abuse of <a href="/tags.html#azure">Azure</a> and <a href="/tags.html#gcp">GCP</a>, I’ve looped back for a second look at AWS, to catch any services I missed the first time around.</p>

<p>In this post I will be looking at <a href="https://aws.amazon.com/apprunner/">AWS App Runner</a>, which is an AWS service for easily deploying web applications, to see how it can be used to provide a trusted endpoint to receive incoming implant traffic and forward it to a C2 service on EC2 instance in a private AWS VPC.</p>

<h1 id="overview">Overview</h1>

<p>The POC environment we will be looking at will use the following AWS resources:</p>
<ol>
  <li>A VPC that our C2 instance and App Runner app will use for internal communications. At minimum this will need one public subnet, that our EC2 instance and App Runner app will be connected to.</li>
  <li>At least one EC2 instance running a C2 service to receive the traffic.</li>
  <li>A security group associated with the EC2 instance to control access to it.</li>
  <li>A security group for our App Runner app</li>
  <li>A copy of the <a href="https://github.com/stephenbradshaw/HTTPForwardContainer">HTTPForwardContainer</a> container in the Elastic Container Registry in the AWS account</li>
  <li>An App Runner app executing the <code class="language-plaintext highlighter-rouge">HTTPForwardContainer</code> app</li>
</ol>

<p>For the purpose of this post I will discuss parts 1-3 in the section named <code class="language-plaintext highlighter-rouge">The C2 Server and supporting networking</code>, and 4-6 in the section named <code class="language-plaintext highlighter-rouge">App Runner instance</code>. Given a lot of this is pretty straightforward to deploy I will just provide general clickops creation instructions using the AWS web console.</p>

<h1 id="the-c2-server-and-supporting-networking">The C2 server and supporting networking</h1>

<p>The C2 service and supporting networking will use a VPC, an EC2 instance attached to that VPC and a security group associated with the EC2 instance to control network access to the instance.</p>

<h2 id="vpc">VPC</h2>

<p>An appropriate VPC can be deployed by visiting the VPC section of the AWS console, hitting the <code class="language-plaintext highlighter-rouge">Create VPC</code> button, and using the Wizard to create <code class="language-plaintext highlighter-rouge">VPC and more</code>.</p>

<p>In the wizard you can get away with the following minimal settings to create something functional for this POC:</p>
<ul>
  <li>1 availability zone</li>
  <li>1 public subnet</li>
  <li>0 private subnets</li>
  <li>0 NAT gateways</li>
  <li>0 VPC endpoints</li>
</ul>

<p>This will look something like the following:</p>

<p align="center">
  <img src="/assets/img/basic_aws_vpc.png" alt="Minimum VPC" />
</p>

<p>After creation, ensure all of the following resources that you create are associated with this new VPC and in the same AWS region.</p>

<h2 id="ec2-and-security-group">EC2 and Security group</h2>

<p>We can create the EC2 instance and security group in one step. Visit the EC2 section of the console and hit <code class="language-plaintext highlighter-rouge">Launch Instance</code> to start the new instance wizard.</p>

<p>The settings below are some simple guidelines and minimums to consider when configuring the instance in the wizard.</p>

<p><strong>Machine image</strong></p>
<ul>
  <li><strong>AMI</strong>: Ubuntu Server 24.04 (HMV), SSD Volume type</li>
  <li><strong>Architecture</strong>: 64 bit architecture</li>
</ul>

<p><strong>Instance type</strong></p>
<ul>
  <li><strong>Instance type</strong>: Minimum of <code class="language-plaintext highlighter-rouge">t2.nano</code> or <code class="language-plaintext highlighter-rouge">t3.nano</code></li>
</ul>

<p><strong>Key Pair</strong></p>
<ul>
  <li><strong>Key</strong>: Generate a new key or reuse an existing key</li>
</ul>

<p><strong>Network settings</strong></p>
<ul>
  <li><strong>Network</strong>: Select your new VPC</li>
  <li><strong>Subnet</strong>: Select the public subnet in your new VPC</li>
  <li><strong>Auto assign public-ip</strong>: <code class="language-plaintext highlighter-rouge">Enable</code></li>
  <li><strong>Security Group</strong>: Create a new security group and give it a sensible name and description (you cant change the name later, so pick sensibly!)</li>
  <li><strong>SSH Access</strong>: Allow SSH from your IP address in the security group config for direct access, or you can assign an <code class="language-plaintext highlighter-rouge">IAM instance profile</code> in the <code class="language-plaintext highlighter-rouge">Advanced details</code> section allowing SSM access if you want to access the instance via <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-enable-ssh-connections.html">SSM intermediated SSH</a></li>
</ul>

<p><strong>Configure storage</strong></p>
<ul>
  <li><strong>Volume type</strong>: Choose <code class="language-plaintext highlighter-rouge">30GB</code> of storage and the <code class="language-plaintext highlighter-rouge">gp3</code> volume type</li>
</ul>

<p><strong>Advanced Settings</strong></p>

<p>Go down to the <code class="language-plaintext highlighter-rouge">User data - optional</code> and  include <a href="https://github.com/stephenbradshaw/pentesting_stuff/blob/master/setup_scripts/c2_setup.sh">this</a> script. This will configure the instance as a <a href="https://github.com/BishopFox/sliver">Sliver</a> C2 server with an Apache forwarder listening on port 80 that receives implant traffic.</p>

<p>Once the instance is up and running and the user data script has completed running, the environment will look something like the following:</p>

<p align="center">
  <img src="/assets/img/aws_poc_simple.png" alt="Simple AWS POC environment" />
</p>

<p>We have a C2 server running, but at the moment we have no way to route implant traffic into the environment. This will be setup in the next section.</p>

<h1 id="app-runner-instance">App Runner instance</h1>

<p>The App Runner application we will be running is a simple Python Flask app that has been containerised to run in Docker. The source code is available <a href="https://github.com/stephenbradshaw/HTTPForwardContainer">here</a> and the container is also available on the Docker hub <a href="https://hub.docker.com/r/stephenbradshaw/httpforwardcontainer">here</a>.</p>

<h2 id="upload-the-container-image-to-ecr">Upload the container image to ECR</h2>

<p>The first thing we will have to do is upload the container to the Elastic Container Registry of the AWS account you are using.</p>

<p>The first thing you need to to is <a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-create.html">create a private repository</a>.</p>

<p>Go to your <a href="https://console.aws.amazon.com/ecr/repositories">list of Private Repositories</a> and select <code class="language-plaintext highlighter-rouge">Create repository</code>. Create a repository with the name <code class="language-plaintext highlighter-rouge">httpforwardcontainer</code>. You can accept the defaults.</p>

<p>Then we need to <a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html">push the image to the private repository</a>. For this you need <a href="https://docs.docker.com/engine/install/">docker installed locally</a> and a copy of the <code class="language-plaintext highlighter-rouge">httpforwardcontainer</code> image downloaded to your local docker instance.</p>

<p>To get the container image stored locally you can either:</p>
<ol>
  <li>Clone the <a href="https://github.com/stephenbradshaw/HTTPForwardContainer">source</a> and build <code class="language-plaintext highlighter-rouge">sudo docker build -t stephenbradshaw/httpforwardcontainer:latest .</code> from the source directory, OR</li>
  <li>Pull the latest build from the docker hub using <code class="language-plaintext highlighter-rouge">sudo docker pull stephenbradshaw/httpforwardcontainer:latest</code>.</li>
</ol>

<p>Then follow the instructions for pushing a local container to the private ECR registry <a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html">here</a></p>

<p>The commands to upload the image will likely look something like the following (replacing <code class="language-plaintext highlighter-rouge">&lt;ACCOUNTID&gt;</code> with you AWS account ID and <code class="language-plaintext highlighter-rouge">&lt;REGION&gt;</code> with the region in which you are deploying all your resources):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws ecr get-login-password --region &lt;REGION&gt; | sudo docker login --username AWS --password-stdin &lt;ACCOUNTID&gt;.dkr.ecr.&lt;REGION&gt;.amazonaws.com
sudo docker tag stephenbradshaw/httpforwardcontainer:latest &lt;ACCOUNTID&gt;.dkr.ecr.&lt;REGION&gt;.amazonaws.com/httpforwardcontainer:latest
sudo docker push &lt;ACCOUNTID&gt;.dkr.ecr.&lt;REGION&gt;.amazonaws.com/httpforwardcontainer:latest
</code></pre></div></div>

<h2 id="create-the-app-runner-security-group">Create the App Runner security group</h2>

<p>We need to create a security group for the App Runner app we are about to create. This can be done from the AWS console <a href="https://console.aws.amazon.com/ec2/home#CreateSecurityGroup">here</a>. Just make sure its created in the VPC we made earlier. We dont need to set any specific rules in the group, we will use it as a source to allow the App Runner app we create to talk to the EC2 instance we created in the previous section.</p>

<h2 id="create-the-app-runner-application">Create the App Runner application</h2>

<p>We can create the App Runner application in the AWS console <a href="https://console.aws.amazon.com/apprunner/home">here</a>.  Then hit the <code class="language-plaintext highlighter-rouge">Create service</code> button to start the wizard.</p>

<p>The first section of the wizard <strong>Source and deployment</strong>, allows you to select the container image we uploaded in the previous section to use for our app.</p>

<p>In the <strong>Source</strong> section, select <code class="language-plaintext highlighter-rouge">Container registry</code> as the <code class="language-plaintext highlighter-rouge">Repository type</code>, and <code class="language-plaintext highlighter-rouge">Amazon ECR</code> as the <code class="language-plaintext highlighter-rouge">Provider</code>.</p>

<p>The use the Browse button next to the <code class="language-plaintext highlighter-rouge">Container image URI</code> to select the image we uploaded. The wizard screen will now look something like the following.</p>

<p>The other options can be left at the default. Hit <code class="language-plaintext highlighter-rouge">Next</code> to move to the next section of the wizard.</p>

<p align="center">
  <img src="/assets/img/app_runner_create1.png" alt="App Runner Create 1" />
</p>

<p>The next section of the wizard <strong>Configure service</strong>, allows us to set the app configuration.</p>

<p>There are a few things we need to change here, first in the <strong>Service settings</strong> section of the window. Here, we need to set a <code class="language-plaintext highlighter-rouge">Service name</code> (the value is only for internal use, choose whatever your want, I’ve selected <code class="language-plaintext highlighter-rouge">AppRunnerTest</code>), we need to set a <code class="language-plaintext highlighter-rouge">DESTINATION</code> environment variable in which we set the internal (private) IP address of the EC2 instance we created earlier.</p>

<p>We also need to change the <code class="language-plaintext highlighter-rouge">Port</code> variable to <code class="language-plaintext highlighter-rouge">80</code>. This will end up looking something like the following:</p>

<p align="center">
  <img src="/assets/img/app_runner_create2.png" alt="App Runner Create 2" />
</p>

<p>The other section of this part of the wizard that we need to change is the <strong>Networking</strong> section. We need to modify the <strong>Outgoing network traffic</strong> to be <code class="language-plaintext highlighter-rouge">Custom VPC</code>. This is shown in the screenshot below. Next to the <strong>VPC connector</strong> drop down box hit the <code class="language-plaintext highlighter-rouge">Add new</code> button to create a VPC connector we can use to connect this app to the VPC we created earlier.</p>

<p align="center">
  <img src="/assets/img/app_runner_create3.png" alt="App Runner Create 3" />
</p>

<p>The VPC connector creation box looks like the following. Select the VPC, VPC subnet and app runner security group we created earlier and hit <code class="language-plaintext highlighter-rouge">Add</code>. Select this new VPC connector in the <strong>VPC connector</strong> drop down.</p>

<p align="center">
  <img width="400" height="400" src="/assets/img/app_runner_vpc_connect.png" alt="VPC Connect" />
</p>

<p>Skip through the rest of the wizard to complete the App Runner configuration. Once the app has been created the <code class="language-plaintext highlighter-rouge">Default domain</code> of the new app can be found in the App Runner section of the AWS <a href="https://console.aws.amazon.com/apprunner/home">console</a> - this is the endpoint by which our implant traffic can enter the C2 system, and will be of the form <code class="language-plaintext highlighter-rouge">https://&lt;RANDOMVALUE&gt;.&lt;REGION&gt;.awsapprunner.com</code>.</p>

<h2 id="allow-traffic-to-flow-between-the-app-runner-app-and-the-ec2-instance">Allow traffic to flow between the App Runner app and the EC2 instance</h2>

<p>Now that the App Runner app is running, there is one final step we need to take to get things working - we need to go to the Security group associated with the EC2 instance (this was created at the same time as the instance itself using the Launch wizard), and we need to allow traffic on port 80 (for the HTTP service) from the security group that we created for the App Runner app.</p>

<p>Once this is done, we should be able to verify the setup is working by making a HTTP request to the endpoint address obtained from the previous step - it should retrieve the contents of the <code class="language-plaintext highlighter-rouge">/var/www/html/index.html</code> page created by the User Data setup script we provided when we setup the EC2 instance. Heres how that looks for a domain of <code class="language-plaintext highlighter-rouge">https://abcdefghijklm.us-east-1.awsapprunner.com/</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl https://abcdefghijklm.us-east-1.awsapprunner.com/ 
&lt;html&gt;
&lt;/html&gt;
</code></pre></div></div>

<p>The setup we now have is represented by the following network diagram.</p>

<p align="center">
  <img src="/assets/img/aws_poc_app_runner.png" alt="App Runner C2 diagram" />
</p>]]></content><author><name>Stephen Bradshaw</name></author><category term="AWS" /><category term="App Runner" /><category term="C2" /><category term="domain fronting" /><category term="forwarding" /><category term="redirector" /><category term="Sliver" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Azure Service Command and Control HTTP traffic forwarding part 4</title><link href="/2025/07/30/azure-service-c2-forwarding-part4.html" rel="alternate" type="text/html" title="Azure Service Command and Control HTTP traffic forwarding part 4" /><published>2025-07-30T04:30:00+00:00</published><updated>2025-07-30T04:30:00+00:00</updated><id>/2025/07/30/azure-service-c2-forwarding-part4</id><content type="html" xml:base="/2025/07/30/azure-service-c2-forwarding-part4.html"><![CDATA[<p align="center">
  <img src="/assets/img/architecture-apim-api-scenario.png" alt="Azure APIM" />
</p>

<p>Heres the final (for now at least) entry in my series on abusing Azure services to provide high reputation redirectors for Command and Control. This time around, we are looking at <a href="https://azure.microsoft.com/en-us/products/api-management">Azure’s API Management (APIM) Service</a>. This service actually gives us a number of different configurations we can use to stick a trusted HTTP frontend in front of other Azure components, however in the example below I will be chosing a simple approach that leverages some of the other work I have previously done in this space.</p>

<p>In this POC I will outline an approach that has an APIM API instance forwarding traffic to an Azure Function App that in turn sits in front of an Azure VM running our C2. The Function App approach was discussed in <a href="/2025/05/07/azure-service-C2-forwarding.html">part 1</a> of this Azure series, and the approach of adding another Azure service to the front of the Function App chain was also used in <a href="/2025/06/25/azure-service-c2-forwarding-part2.html">part 2</a> of the series where I talked about Azure Front Door.</p>

<p>In essence this essentially adds another Internet accessible front end to our C2 setup - allowing both to be used to get traffic into the environment.</p>

<p>I’ve again updated the <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment">Azure C2 POC base template set</a> I’ve been using in my other <a href="/tags.html#azure-service-fronting">Azure entries in this series</a> to make it easy to setup your own iteration of this POC.</p>

<p>Lets look at how we can use these templates to setup a working APIM C2 POC environment. We will need to do the following:</p>
<ul>
  <li>Setup the base resources (resource group, networking, C2 vm, etc) for the C2 we will forward to</li>
  <li>Setup the Azure Function App that forwards traffic to our C2</li>
  <li>Setup the APIM service instance and an API that will forward traffic to the C2 via the Function App</li>
</ul>

<h1 id="create-the-base-resources">Create the base resources</h1>

<p>Base resources for this POC environment include a Linux VM with an Apache forwarder and the Sliver C2 software installed and running, plus the necessary network plumbing. Ive discussed details about these resources previously in a post <a href="/2025/05/07/azure-service-C2-forwarding.html#manual-deployment---azure-poc-environment">here</a> and how the deployment templates work in a post <a href="/2025/06/04/azure_c&amp;c_poc_infra_deployment.html">here</a>, but I will briefly cover how to set these up here so you can easily start from scratch.</p>

<p>Create a ssh key to use when accessing the VM, if you dont already have one.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_azure
</code></pre></div></div>

<p>Make sure the <a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt">Azure CLI is installed and working</a>.</p>

<p>Create the <code class="language-plaintext highlighter-rouge">C2VMRG</code> resource group.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ az group create --name C2VMRG --location westus2
</code></pre></div></div>

<p>Clone the POC deployment templates locally</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone https://github.com/stephenbradshaw/AzureC2PocDeployment.git
</code></pre></div></div>

<p>Modify the <code class="language-plaintext highlighter-rouge">base</code> deployment templates for your environment (which uses your Internet facing IP and public key data for access) and deploy the resources.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd AzureC2PocDeployment/base
$ export PUBLIC_IP=$(curl ipinfo.io/ip)
$ sed -i "s/&lt;YOUR_IP_ADDRESS_HERE&gt;/$PUBLIC_IP/g" parameters.json
$ export PUBLIC_KEY=$(cat ~/.ssh/id_ed25519_azure.pub | sed 's/\//\\\//g')
$ sed -i "s/&lt;YOUR_PUBLIC_KEY_HERE&gt;/$PUBLIC_KEY/g" parameters.json
$ export DNS_LABEL=myvm1234567890
$ sed -i "s/&lt;YOUR_DNS_LABEL_HERE&gt;/$DNS_LABEL/g" parameters.json
$ az deployment group create --resource-group C2VMRG --template-file template.json --parameters @parameters.json
</code></pre></div></div>

<p>Once the deployment is done, you can also get the public IP address of your VM as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ az vm list-ip-addresses -g C2VMRG --query "[].virtualMachine[].network.publicIpAddresses[].ipAddress | [0]"
</code></pre></div></div>

<p>Add an ssh hosts file entry for your VM as follows, replacing <code class="language-plaintext highlighter-rouge">&lt;PUBLIC_IP_ADDRESS&gt;</code> with the output of the above.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host azurevm
    hostname &lt;PUBLIC_IP_ADDRESS&gt;
    identityfile ~/.ssh/id_ed25519_azure
    user ubuntu
</code></pre></div></div>

<p>You should now be able to ssh into that instance once it has fully deployed - which will take a few minutes. Access will be limited to the Internet facing IP address of the machine you performed the deployment from using a Network Security Group deployed with the resources defined in the templates.</p>

<p>To access the installed C2 you can ssh in and run the Sliver client like so:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/opt/sliver/sliver-client
</code></pre></div></div>

<p>The VM is setup using a userdata script that installs Apache and Sliver and configures it, etc,  on initial VM launch, and it should be basically “ready to go” once the script completes.  The script creates a <code class="language-plaintext highlighter-rouge">setupdone</code> file in the user home directory to let you know when its finished, because it is possible to ssh in before this setup is done. On some occaisions however, for reasons I’m not entirely clear on, parts of the install process will fail - most likely the creation of the Sliver listener job on local port 8888. You can see what steps are performed to help troublshoot any issues by looking at the <code class="language-plaintext highlighter-rouge">install.sh</code> in the <code class="language-plaintext highlighter-rouge">base</code> template directory (this is essentially a plaintext copy of the base64 userData field in <code class="language-plaintext highlighter-rouge">parameters.json</code>). Its also possible to pull this script from the VMs metadata service like so:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -s -H "Metadata:true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | jq -r .compute.userData | base64 -d
</code></pre></div></div>

<h1 id="create-the-function-app">Create the Function App</h1>

<p>To deploy the Function App we need the <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment">Azure C2 POC base template set</a> used in the last section to create the Function App resource and the Function App code available <a href="https://github.com/stephenbradshaw/AzureFunctionC2Forwarder">here</a></p>

<p>We will start with deploying the Function App azure resources. Change to the <code class="language-plaintext highlighter-rouge">functionapp</code> folder in the Azure template set</p>

<p>From here, we need to choose a name for our function app instance, by which the app will be reachable from the Internet. This name must be globally unique in Azure - in this example I will use <code class="language-plaintext highlighter-rouge">mytestfunctionxyz123</code> (you need to choose something else). The Function App will be accessible from a subdomain under the <code class="language-plaintext highlighter-rouge">azurewebsites.net</code> domain - one way you can find an unused name is to test out domain resolution for your chosen name such as <code class="language-plaintext highlighter-rouge">&lt;yourlabel&gt;.azurewebsites.net</code> and see if it returns a valid answer.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export FUNCTION_APP_NAME=mytestfunctionxyz123
sed -i "s/&lt;FUNCTION_APP_NAME_HERE&gt;/$FUNCTION_APP_NAME/g" parameters.json
</code></pre></div></div>

<p>Now deploy. If things fail at this point, one likely reason is that the app name you chose is not globally unique - change the value to something else.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az deployment group create --resource-group C2VMRG --template-file template.json --parameters @parameters.json
</code></pre></div></div>

<p>Now change to the directory where you cloned the Function App <a href="https://github.com/stephenbradshaw/AzureFunctionC2Forwarder">code</a> and package it into a zip file (<code class="language-plaintext highlighter-rouge">/tmp/dep.zip</code> in the example below) for deployment.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zip -r /tmp/dep.zip ./function_app.py ./host.json ./requirements.txt
</code></pre></div></div>

<p>Now you can deploy the code to your Function App</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az functionapp deployment source config-zip -g C2VMRG -n $FUNCTION_APP_NAME --src /tmp/dep.zip
</code></pre></div></div>

<p>Once deployed your C2 should be reachable by the Function Apps domain. We can then get the name and test connectivity.</p>

<p>Get the Function App hostname and store it in a variable.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az functionapp list --query '[0].hostNames[0]'
export FAHOST=$(az functionapp list -g C2VMRG --query '[0].hostNames[0]' | jq -r)
</code></pre></div></div>

<p>Perform a curl request to fetch the index page from the C2 hosts Apache forwarder.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl https://$FAHOST/
&lt;html&gt;
&lt;/html&gt;
</code></pre></div></div>

<h1 id="setup-the-apim-service-instance">Setup the APIM service instance</h1>

<p>Now we get to the new content - the API Service instance. We need to create an APIM service which is the framework and infrastructure to host your APIs, and then create an API instance within that service that will actually forward the traffic.</p>

<p>It is actually possible to have multiple forwarding APIs within the single API service, each available at sub paths under the main service URL, however we will just deploy a single API accessible at the root path <code class="language-plaintext highlighter-rouge">/</code>.</p>

<p>To deploy, change to the <code class="language-plaintext highlighter-rouge">apim</code> folder in the <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment">Azure C2 POC base template set</a>.</p>

<p>Now we need to choose a API service name, where our API service will be accessible. The name needs to be globally unique like your previously created Function App - find an unused value by checking for no correct DNS resolution of <code class="language-plaintext highlighter-rouge">&lt;your-label&gt;.azure-api.net</code>. Im using <code class="language-plaintext highlighter-rouge">myapi987755321</code> in the example below. You also need to specify an email address - this address will be notified when the deployment is complete (it does take a while).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export API_NAME=myapi987755321
sed -i "s/&lt;API_NAME&gt;/$API_NAME/g" parameters.json

export NOTIFY_EMAIl='myemail@whatever.com'
sed -i "s/&lt;NOTIFY_EMAIL&gt;/$NOTIFY_EMAIl/g" parameters.json
</code></pre></div></div>

<p>Now do the deployment of the service.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az deployment group create --resource-group C2VMRG --template-file template.json --parameters @parameters.json
</code></pre></div></div>

<p>This will take a while to complete, and the <code class="language-plaintext highlighter-rouge">az</code> process will usually not return/exit til the deployment is done.  The email you configured in the previous step will get an email once the service is ready. Its also possible to monitor the deployment (waiting for the “Succeeded” status) using the following command:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az apim list -g C2VMRG --query '[0].provisioningState'
</code></pre></div></div>

<p>Once the API service is setup we next need to setup an API to actually forward traffic. First we need to setup an environment variable with the location of our Function App if this is not still set from the previous step.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export FAHOST=$(az functionapp list -g C2VMRG --query '[0].hostNames[0]' | jq -r)
</code></pre></div></div>

<p>Now we setup the API.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az apim api import -g C2VMRG -n $API_NAME --specification-format OpenApiJson --api-id myapi --path '/' --display-name 'MyAPI' --subscription-required false  --service-url https://$FAHOST/  --api-type http --protocols http https --description 'My API' --specification-url https://raw.githubusercontent.com/stephenbradshaw/AzureC2PocDeployment/refs/heads/main/apim/azure_api_management_service_def.json
</code></pre></div></div>

<p>At this point our API should be setup and forwarding to the C2, and we can get the URL at which our new API is available like so:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az apim list --query '[0].gatewayUrl'
</code></pre></div></div>

<p>We can test this to make sure it is working by fetching the index content from the C2 Apache forwarding server by doing something like the below:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export APIGW_URL=$(az apim list --query '[0].gatewayUrl' | jq -r)
$ curl $APIGW_URL
&lt;html&gt;
&lt;/html&gt;
</code></pre></div></div>

<p>The architecture we have deployed looks something like the following, with two externally accessible endpoints for implant traffic to reach our C2:</p>

<p align="center">
  <img src="/assets/img/c2_architecture_basic_azure_api_new.png" alt="C2 with single Azure VM, Function App and Azure API Gateway" />
</p>

<p>When you are done with the POC environment, you can easily delete the resource group and all associated resources as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az group delete --name C2VMRG
</code></pre></div></div>

<h1 id="conclusion">Conclusion</h1>

<p>This is most lkely the final post I will do on this subject for Azure, as I think Ive fully explored the available options at this point, but if you’re aware of any Azure services Ive missed, please let me know.</p>]]></content><author><name>Stephen Bradshaw</name></author><category term="Azure" /><category term="C2" /><category term="domain fronting" /><category term="forwarding" /><category term="sliver" /><category term="Azure service fronting" /><category term="Azure API Management Service" /><category term="redirector" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Azure Service Command and Control HTTP traffic forwarding part 3</title><link href="/2025/07/10/azure-service-c2-forwarding-part3.html" rel="alternate" type="text/html" title="Azure Service Command and Control HTTP traffic forwarding part 3" /><published>2025-07-10T04:30:00+00:00</published><updated>2025-07-10T04:30:00+00:00</updated><id>/2025/07/10/azure-service-c2-forwarding-part3</id><content type="html" xml:base="/2025/07/10/azure-service-c2-forwarding-part3.html"><![CDATA[<p align="center">
  <img src="/assets/img/azure-vms.jpg" alt="Azure VMs" />
</p>

<p>Another quick entry in my ongoing series where I find ways to use cloud native services to receive command and control traffic - Azure edition.</p>

<p>A simple trick we can use in Azure environments to get a trusted domain for a C2 endpoint is automatically generated VM domain names. A VM you create in Azure can have a DNS record in the format <code class="language-plaintext highlighter-rouge">&lt;domainNameLabel&gt;.&lt;region&gt;.cloudapp.azure.com</code> automatically created for you and linked to the public IP address for your VM, where <code class="language-plaintext highlighter-rouge">&lt;domainNameLabel&gt;</code> is a (globally unique) value chosen by you and <code class="language-plaintext highlighter-rouge">&lt;region&gt;</code> is the Azure region your VM is running in.</p>

<p>Unlike some of the other fronting approaches I’ve discussed in previous posts, this method only gets you a DNS name and not a certificate, so you dont get HTTPS by default with this approach. You can easily create your own SSL certificate for your unique domain name in the <code class="language-plaintext highlighter-rouge">&lt;region&gt;.cloudapp.azure.com</code> domain using LetsEncrypt or similar services, however this will result in a brand new (unaged) certificate that will appear in certificate transparency logs, which does have some minor downsides. To enable HTTPS in this scenario you also need to be able to create a C2 SSL listener/forwarder that uses the LetsEncrypt certificates for the domain for it to be considered “trusted”. I’ll discuss an approach for doing this below.</p>

<p>I’ve updated the <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment">Azure C2 POC base template set</a> I’ve been using in my other <a href="/tags.html#azure-service-fronting">Azure entries in this series</a> to add this DNS entry automatically to the C2 VM based on a DNS label you provide before deployment.  Lets have a look at how the whole setup would work.</p>

<h1 id="create-the-poc-environment">Create the POC environment</h1>

<p>Create a ssh key to use when accessing the vm, if you dont already have one.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_azure
</code></pre></div></div>

<p>Make sure the <a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt">Azure CLI is installed and working</a>.</p>

<p>Create the <code class="language-plaintext highlighter-rouge">C2VMRG</code> resource group.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ az group create --name C2VMRG --location westus2
</code></pre></div></div>

<p>Clone the POC deployment templates locally</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone https://github.com/stephenbradshaw/AzureC2PocDeployment.git
</code></pre></div></div>

<p>Modify the <code class="language-plaintext highlighter-rouge">base</code> deployment templates for your environment (which uses your Internet facing IP and public key data for access) and deploy the resources.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd AzureC2PocDeployment/base
$ export PUBLIC_IP=$(curl ipinfo.io/ip)
$ sed -i "s/&lt;YOUR_IP_ADDRESS_HERE&gt;/$PUBLIC_IP/g" parameters.json
$ export PUBLIC_KEY=$(cat ~/.ssh/id_ed25519_azure.pub | sed 's/\//\\\//g')
$ sed -i "s/&lt;YOUR_PUBLIC_KEY_HERE&gt;/$PUBLIC_KEY/g" parameters.json
$ export DNS_LABEL=myvm1234567890
$ sed -i "s/&lt;YOUR_DNS_LABEL_HERE&gt;/$DNS_LABEL/g" parameters.json
$ az deployment group create --resource-group C2VMRG --template-file template.json --parameters @parameters.json
</code></pre></div></div>

<p>Once the deployment is done, you can also get the public IP address of your VM as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ az vm list-ip-addresses -g C2VMRG --query "[].virtualMachine[].network.publicIpAddresses[].ipAddress | [0]"
</code></pre></div></div>

<p>Add an ssh hosts file entry for your VM as follows, replacing <code class="language-plaintext highlighter-rouge">&lt;PUBLIC_IP_ADDRESS&gt;</code> with the output of the above.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host azurevm
    hostname &lt;PUBLIC_IP_ADDRESS&gt;
    identityfile ~/.ssh/id_ed25519_azure
    user ubuntu
</code></pre></div></div>

<p>You should now be able to ssh into that instance once it has fully deployed - which will take a few minutes. Access will be limited to the Internet facing IP address of the machine you performed the deployment from using a Network Security Group deployed with the resources defined in the templates.</p>

<p>The DNS name created for your VM can be obtained using the following command. The label provided before deployment (<code class="language-plaintext highlighter-rouge">myvm1234567890</code> in my case) has been combined in the template with a randomly generated value based on the Azure subscription ID to help ensure that the label will be globally unique in Azure and wont cause a deployment error due to the name aleady being in use.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az network public-ip list -g C2VMRG --query "[].dnsSettings.fqdn | [0]"
</code></pre></div></div>

<h1 id="allow-http-traffic-to-instance">Allow HTTP traffic to instance</h1>

<p>By default, the deployment templates block all inbound traffic to the VMs public IP address apart from port 22 (SSH) from the Internet facing IP deployment host. If we want to also allow implant traffic to reach the VM in this scenario, we also need to allow HTTP (and optionally HTTPS) traffic from the Internet.</p>

<p>For HTTP traffic, we can add the appropriate Network Security Group rule using the AZ cli from our deployment machine like so.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az network nsg rule create \
  --resource-group C2VMRG \
  --nsg-name C2VM-nsg \
  --name AllowPublicHttpInbound \
  --priority 1020 \
  --direction inbound \
  --source-address-prefixes '*' \
  --source-port-ranges '*' \
  --destination-address-prefixes '*' \
  --destination-port-ranges 80 \
  --protocol TCP\
  --access Allow
</code></pre></div></div>

<p>This will give us a working C2 environment, where implants can reach our C2 via HTTP using a name sitting within the <code class="language-plaintext highlighter-rouge">cloudapp.azure.com</code> domain. In my example case, the address is <code class="language-plaintext highlighter-rouge">http://myvm1234567890krzonahte76fm.westus2.cloudapp.azure.com/</code>.</p>

<h1 id="protectionfiltering-for-the-c2-servers-http-endpoint">Protection/filtering for the C2 servers HTTP endpoint</h1>

<p>At this point its worthwhile discussing a simple protection that the POC environment has implemented to prevent the C2s HTTP endpoint from being made available directly from the Internet. What Ive done with the C2 host setup in this deployment template setup is install and run Apache in proxy mode on port 80, which will in turn forward <em>certain</em> incoming HTTP traffic on to the Sliver C2 HTTP endpoint, which is listening internally on port 8888.</p>

<p>The filtering is pretty simple, configured in <code class="language-plaintext highlighter-rouge">/var/www/html/.htaccess</code> and works in the following manner:</p>
<ul>
  <li>Any requests for any file that exists in the Apache web root (<code class="language-plaintext highlighter-rouge">/var/www/html</code>) will be served directly by Apache - there is a default index page, a robots.txt and a randomly named php script that exists for troubleshooting</li>
  <li>Any requests for any URL that does not exist in the Apache web root will be forwarded to the C2 endpoint <em>if</em> it comes from a non-bot/automated/command-line user agent, otherwise the request will get a generic 404 response</li>
</ul>

<p>This is not perfect, and can be refined to filter more stringently based on the HTTP communication profile of a particular C2, but it does help prevent against casual identification of the C2 or <em>certain</em> direct attacks against its implant endpoint, as the robust and tested Apache server receives the web traffic first and does some response header modifications. The use of Apache also gives us an easy and well tested way of setting up a HTTPS listener that forwards traffc in the exact same manner as the HTTP listener (see next section for details).</p>

<p>A setup where the Apache forwarder and C2 are on different VM hosts seperated using cloud provider traffic filtering would be an additional improvement, although for a simple POC/testing environment this single VM approach is a more cost effective compromise.</p>

<h1 id="enabling-https">Enabling HTTPS</h1>

<p>The previous steps enable HTTP access to the implant endpoint only. If we want to enable HTTPS as well, we can do this by generating our own LetsEncrypt certificate and configuring the Apache forwarder to use the certificates for HTTPS. Be aware that issuance of these certificates will result in the creation of a few indicators increasing the visibility and apparent “newness” of the endpoint (not ideal for C2). This includes placing the endpoint address into the <a href="https://crt.sh/">certificate transparency logs</a> and having an associated SSL certificate that has todays issuance date. You may even get some casual probing of your endpoint from scanners that consume the certificate transparency logs to identify potentially malicious servers.  I left my server online for a day after requesting a certificate to see how many web requests I would get specifically to the DNS domain name (as opposed to the IP address, indicating the trigger for the request is likely the transparency log), and got a small number of requests from six different IP addresses during the next 13 hours, mainly looking like vulnerability scans. Be aware that this increase in visibility is a “cost” of enabling HTTPS in this manner, in a way that didnt apply for some of the other fronting approaches I have discussed in previous posts.</p>

<p>To set up HTTPS, start by SSHing into the Azure VM and install certbot and request a certificate for your VMs hostname. This will require that the previous steps allowing HTTP traffic to the instance have already been completed as the certificate request process involves hosting content using the Apache server on port 80.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install certbot
sudo certbot certonly --webroot -w /var/www/html -d myvm1234567890krzonahte76fm.westus2.cloudapp.azure.com --staple-ocsp -m youremail@example.com --agree-tos
</code></pre></div></div>

<p>If successful this will provide some output, including the locations of the certificate chain and private key which will look similar to the following.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Certificate is saved at: /etc/letsencrypt/live/myvm1234567890krzonahte76fm.westus2.cloudapp.azure.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/myvm1234567890krzonahte76fm.westus2.cloudapp.azure.com/privkey.pem
</code></pre></div></div>

<p>We need to then update the Apache SSL config to refer to these files. I have included a usable SSL template on the instance in file <code class="language-plaintext highlighter-rouge">/etc/apache2/sites-available/ssl-forward-http.conf</code>, so edit this file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo vi /etc/apache2/sites-available/ssl-forward-http.conf
</code></pre></div></div>

<p>And modify the following lines to point to the LetsEncrypt files, similar to the examples below:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    SSLCertificateFile      /etc/letsencrypt/live/myvm1234567890krzonahte76fm.westus2.cloudapp.azure.com/fullchain.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/myvm1234567890krzonahte76fm.westus2.cloudapp.azure.com/privkey.pem
</code></pre></div></div>

<p>Once those modifications are done, enable the SSL module and the new SSL site like so.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo a2enmod ssl
sudo a2ensite ssl-forward-http
sudo systemctl reload apache2
</code></pre></div></div>

<p>The SSL config should now be setup and working, and all that remains is to allow traffic to port 443 on the instance.</p>

<p>On your deployment machine use the following az CLI command to allow HTTPS traffic to the instance.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az network nsg rule create \
  --resource-group C2VMRG \
  --nsg-name C2VM-nsg \
  --name AllowPublicHttpsInbound \
  --priority 1030 \
  --direction inbound \
  --source-address-prefixes '*' \
  --source-port-ranges '*' \
  --destination-address-prefixes '*' \
  --destination-port-ranges 443 \
  --protocol TCP\
  --access Allow
</code></pre></div></div>

<p>The network diagram of our POC C2 setup now looks as follows, with two listening endpoints provided by the Function App and the Front Door that can be used by implants to talk to the C2.</p>

<p align="center">
  <img src="/assets/img/c2_architecture_basic_azure.png" alt="C2 with single Azure VM and Azure provided VM DNS" />
</p>

<p>When you are done with the POC environment, you can easily delete the resource group and all associated resources as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az group delete --name C2VMRG
</code></pre></div></div>

<h1 id="conclusion">Conclusion</h1>

<p>I intend to keep investigating Azure to see if there are any more ways to use their cloud services to forward for C2, and if I find more I’ll do a follow up post to discuss them. Are you aware of any? Let me know!</p>]]></content><author><name>Stephen Bradshaw</name></author><category term="Azure" /><category term="C2" /><category term="domain fronting" /><category term="forwarding" /><category term="sliver" /><category term="Azure service fronting" /><category term="redirector" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Azure Service Command and Control HTTP traffic forwarding part 2</title><link href="/2025/06/25/azure-service-c2-forwarding-part2.html" rel="alternate" type="text/html" title="Azure Service Command and Control HTTP traffic forwarding part 2" /><published>2025-06-25T05:55:00+00:00</published><updated>2025-06-25T05:55:00+00:00</updated><id>/2025/06/25/azure-service-c2-forwarding-part2</id><content type="html" xml:base="/2025/06/25/azure-service-c2-forwarding-part2.html"><![CDATA[<p align="center">
  <img src="/assets/img/azure_front_door.webp" alt="Azure Front Door" />
</p>

<p>Here is another entry in my ongoing series of posts where I find ways to abuse cloud native services for fronting C2 implant traffic.</p>

<p>This time I will be discussing the second Azure specific approach using <a href="https://learn.microsoft.com/en-us/azure/frontdoor/front-door-overview">Azure Front Door</a>.</p>

<p>For the first Azure specific post, you can go <a href="/2025/05/07/azure-service-C2-forwarding.html">here</a>, and for all of the other posts, also covering AWS and GCP, you can go <a href="/tags.html#domain-fronting">here</a>.</p>

<h1 id="overview">Overview</h1>

<p>Azure Front Door is essentially a Content Delivery Network for other services, so it does need some sort of backend system to forward traffic to. It is possible to have this forward traffic to a public IP address of an Azure VM instance, but given I had already setup a public endpoint for receiving C2 implant traffic in the form of an <a href="/2025/05/07/azure-service-C2-forwarding.html">Azure Function App</a>, I decided to continue to use this for this POC to avoid having to publicly expose the HTTP port on this instance to the Internet and worrying about how to <a href="https://learn.microsoft.com/en-us/azure/frontdoor/origin-security?tabs=app-service-functions&amp;pivots=front-door-standard-premium">secure the origin service</a>.</p>

<p>This means that in my POC, I am using the Front Door service to provide an additional/alternate way for implant traffic to reach my C2. It is possible to <a href="https://learn.microsoft.com/en-us/azure/frontdoor/origin-security?tabs=app-service-functions&amp;pivots=front-door-standard-premium">secure this</a> so that traffic can only come from the frontdoor but I selected to just allow traffic to both endpoints.</p>

<h1 id="setup">Setup</h1>

<p>The supporting infrastructure in the POC environment including an Azure VPC, Linux VM running the <a href="https://github.com/BishopFox/sliver">Sliver C2</a> software and Azure Function App can be automatically deployed as discused in this <a href="/2025/06/04/azure_c&amp;c_poc_infra_deployment.html">blog post</a>.</p>

<p>Then the Azure Front Door component can be deployed as per the instructions <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment?tab=readme-ov-file#creating-an-azure-front-door-instance-for-c2-fronting">here</a></p>

<p>Heres an example of how to do the necessary deployment.</p>

<h2 id="create-the-c2-vm-and-vpc-resources">Create the C2 VM and VPC resources</h2>

<p>Create a ssh key to use when accessing the vm, if you dont already have one.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_azure
</code></pre></div></div>

<p>Make sure the <a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt">Azure CLI is installed and working</a>.</p>

<p>Create the <code class="language-plaintext highlighter-rouge">C2VMRG</code> resource group.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ az group create --name C2VMRG --location westus2
</code></pre></div></div>

<p>Clone the POC deployment templates locally</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone https://github.com/stephenbradshaw/AzureC2PocDeployment.git
</code></pre></div></div>

<p>Modify the <code class="language-plaintext highlighter-rouge">base</code> deployment templates for your environment (which uses your Internet facing IP and public key data for access) and deploy the resources.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd AzureC2PocDeployment/base
$ export PUBLIC_IP=$(curl ipinfo.io/ip)
$ sed -i "s/&lt;YOUR_IP_ADDRESS_HERE&gt;/$PUBLIC_IP/g" parameters.json
$ export PUBLIC_KEY=$(cat ~/.ssh/id_ed25519_azure.pub | sed 's/\//\\\//g')
$ sed -i "s/&lt;YOUR_PUBLIC_KEY_HERE&gt;/$PUBLIC_KEY/g" parameters.json
$ export DNS_LABEL=dnslabel
$ sed -i "s/&lt;YOUR_DNS_LABEL_HERE&gt;/$DNS_LABEL/g" parameters.json
$ az deployment group create --resource-group C2VMRG --template-file template.json --parameters @parameters.json
</code></pre></div></div>

<p>Once this step is done you should be able to view the resources in the Azure Portal in the <a href="https://portal.azure.com/#browse/resourcegroups">Resource Group section</a> in the <code class="language-plaintext highlighter-rouge">C2VMRG</code> group.</p>

<p>You can also get the public IP address of your VM as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ az vm list-ip-addresses -g C2VMRG --query "[].virtualMachine[].network.publicIpAddresses[].ipAddress | [0]"
</code></pre></div></div>

<p>Add an ssh hosts file entry for your VM as follows, replacing <code class="language-plaintext highlighter-rouge">&lt;PUBLIC_IP_ADDRESS&gt;</code> with the output of the above.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host azurevm
    hostname &lt;PUBLIC_IP_ADDRESS&gt;
    identityfile ~/.ssh/id_ed25519_azure
    user ubuntu
</code></pre></div></div>

<p>You should now be able to ssh into that instance once it has fully deployed - which will take a few minutes. Access will be limited to the Internet facing IP address of the machine you performed the deployment from using a Network Security Group deployed with the resources defined in the templates.</p>

<h2 id="create-the-function-app-for-implant-traffic-forwarding">Create the Function App for implant traffic forwarding</h2>

<p>Now we can create the Function App resources. We need to pick a globally unique name the app, I will use <code class="language-plaintext highlighter-rouge">mytestfunctionxyz123</code>, you will need to use something else.</p>

<p>This is done as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd ../functionapp
$ export FUNCTION_APP_NAME=mytestfunctionxyz123
$ sed -i "s/&lt;FUNCTION_APP_NAME_HERE&gt;/$FUNCTION_APP_NAME/g" parameters.json
$ az deployment group create --resource-group C2VMRG --template-file template.json --parameters @parameters.json
</code></pre></div></div>

<p>With the Azure resources created for the App we then need to deploy the code that executes in the Function App. This is done by cloning the example code repo ande deploying it using the az cli similar to the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd ../../
$ git clone https://github.com/stephenbradshaw/AzureFunctionC2Forwarder
$ cd AzureFunctionC2Forwarder
$ zip -r /tmp/dep.zip ./function_app.py ./host.json ./requirements.txt
$ az functionapp deployment source config-zip -g C2VMRG -n $FUNCTION_APP_NAME --src /tmp/dep.zip
</code></pre></div></div>

<p>Once this is successfully deployed, we should be able to make the following curl request and see the default HTML context that sits on the Apache forwarder configured on the Linux C2 VM - this will respond to any requests to the endpoint with any files locally present in the Apache web root and send any other requests to the Sliver C2 listener.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl https://$FUNCTION_APP_NAME.azurewebsites.net/
&lt;html&gt;
&lt;/html&gt;
</code></pre></div></div>

<h2 id="create-the-azure-front-door-as-a-secondaryalternate-forwarding-endpoint">Create the Azure Front Door as a secondary/alternate forwarding endpoint</h2>

<p>Now we can create the Azure Front Door resources by changing back to our deployment template repo in the <code class="language-plaintext highlighter-rouge">azurefd</code> subdirectory and replacing some variables for our Function App name (<code class="language-plaintext highlighter-rouge">mytestfunctionxyz123</code> in my case) and our Front Door name (I am using <code class="language-plaintext highlighter-rouge">testfd</code> below, this value will form part of the URL with which you can access the  ). This looks similar to the following.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd ../AzureC2PocDeployment/azurefd
$ export FUNCTION_APP_NAME=mytestfunctionxyz123
$ sed -i "s/&lt;FUNCTION_APP_NAME_HERE&gt;/$FUNCTION_APP_NAME/g" parameters.json
$ export ENDPOINT_NAME=testfd
$ sed -i "s/&lt;FRONT_DOOR_ENDPOINT_NAME_HERE&gt;/$ENDPOINT_NAME/g" parameters.json
$ az deployment group create --resource-group C2VMRG --template-file template.json --parameters @parameters.json
</code></pre></div></div>

<p>Once done, the Azure resource explorer will show the resources in the <code class="language-plaintext highlighter-rouge">C2VMRG</code> resource group looking similar to the following.</p>

<p align="center">
  <img src="/assets/img/C2VMRG_azurefd.png" alt="Azure infrastructure after " />
</p>

<p>Although the output from the <code class="language-plaintext highlighter-rouge">az deployment</code> command above will include the hostname that is auto created for our Front Door instance, it is buried in a lot of other text, so its also useful to know that it can be queried as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az afd endpoint list -g C2VMRG --profile-name MyFrontDoor --query '[].hostName | [0]'
</code></pre></div></div>

<p>In my case, the address was <code class="language-plaintext highlighter-rouge">testfd-a5g3ahdybnd6a9cz.z01.azurefd.net</code>, and making the following curl request to the endpoint as shown below should retrieve the default contents from the Apache forwarding service as shown below.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl https://testfd-a5g3ahdybnd6a9cz.z01.azurefd.net/
&lt;html&gt;
&lt;/html&gt;
</code></pre></div></div>

<p>The network diagram of our POC C2 setup now looks as follows, with two listening endpoints provided by the Function App and the Front Door that can be used by implants to talk to the C2.</p>

<p align="center">
  <img src="/assets/img/c2_architecture_basic_azure_fd.png" alt="C2 with Azure Function App and Front Door Fronting" />
</p>

<p>When you are done with the POC environment, you can easily delete the resource group and all associated resources as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az group delete --name C2VMRG
</code></pre></div></div>

<h1 id="conclusion">Conclusion</h1>

<p>I intend to keep investigating Azure to see if there are any more ways to use their cloud services to forward for C2, and if I find more I’ll do a follow up post to discuss them. Are you aware of any? Let me know!</p>]]></content><author><name>Stephen Bradshaw</name></author><category term="Azure" /><category term="Azure functions" /><category term="Serverless" /><category term="C2" /><category term="domain fronting" /><category term="forwarding" /><category term="container" /><category term="sliver" /><category term="Azure Front Door" /><category term="Azure service fronting" /><category term="redirector" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Azure Command and Control POC Infrastructure Deployment</title><link href="/2025/06/04/azure_c&c_poc_infra_deployment.html" rel="alternate" type="text/html" title="Azure Command and Control POC Infrastructure Deployment" /><published>2025-06-04T07:06:00+00:00</published><updated>2025-06-04T07:06:00+00:00</updated><id>/2025/06/04/azure_c&amp;c_poc_infra_deployment</id><content type="html" xml:base="/2025/06/04/azure_c&amp;c_poc_infra_deployment.html"><![CDATA[<p align="center">
  <img src="/assets/img/azure-resource-manager.png" alt="Azure Resource Manager" />
</p>

<p>As Ive been playing with Azure on and off over the last few weeks trying to find native services abusable for C2 fronting, I’ve developed a need to quickly setup and tear down POC C2 environments so I can avoid being billed for cloud resources during idle periods.</p>

<p>To this end, Ive created some simple Azure Resource Manager templates that allow me to quickly setup the resources mentioned in my earlier post on Azure Function Apps <a href="/2025/05/07/azure-service-C2-forwarding.html">here</a>. I figured it was worthwhile to share these in case anyone else find them useful.</p>

<p>The templates are available in this <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment">repository on GitHub</a>.</p>

<p>There are currently two template sets in the repository, seperated by folder with names indicated below:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">base</code> - Creates the virtual network, C2 VM, related resources and a network security group with rules that allow internal network HTTP traffic and SSH access from a single IP address over the Internet</li>
  <li><code class="language-plaintext highlighter-rouge">functionapp</code> - Creates the Function App resource and necessary configuration to allow it to route traffic to the C2 VM created by the <code class="language-plaintext highlighter-rouge">base</code> template</li>
</ul>

<p>Both template sets create resources in a resource group named <code class="language-plaintext highlighter-rouge">C2VMRG</code>, which must be created before deployment, and which can be deleted to remove all contained resources.</p>

<h1 id="the-base-template">The base template</h1>

<p>The base template can be used to create a supporting C2 server that runs in Azure and allows operator connections from a single IP address specified at deploy time over the Internet. No implant connectivity is allowed at this stage, this must be provided by additional configuration, such as by deploying a Function App using the <code class="language-plaintext highlighter-rouge">functionapp</code> template.</p>

<p>The Azure resources deployed look like the following.</p>

<p align="center">
  <img src="/assets/img/C2VMRG.png" alt="Azure Resource Manager" />
</p>

<p>The network diagram is at this point very simple - only an operator can connect to the C2 server. This is performed over the Internet using SSH to the instances associated public IP address (the <code class="language-plaintext highlighter-rouge">C2VM-ip</code> resource). It is allowed from a single IP address belonging to the operator which must be defined at deploy time.</p>

<p align="center">
  <img src="/assets/img/c2_architecture_basic_azure_base.png" alt="Partial Network Diagram" />
</p>

<p>The C2VM is configured with an Apache forwarder listening on TCP port 80 that performs some simple filtering and forwards relevant traffic to port 8888 where a Sliver HTTP listener job is ready to receive incoming implant connections. The network security group however only allows TCP 80 traffic from the private network however, so we need to perform further steps for a working test C2 environment.</p>

<h1 id="the-functionapp-template">The functionapp template</h1>

<p>The functionapp template deploys additional Azure resources to the <code class="language-plaintext highlighter-rouge">C2VMRG</code> resource group, providing a way for implant traffic to enter the environment from the Internet. It depends on the resources created by the <code class="language-plaintext highlighter-rouge">base</code> template having been deployed first.</p>

<p>The Azure resources look like the following after deployment (assuming a chosen function app name of <code class="language-plaintext highlighter-rouge">mytestfunctionxyz123</code> - these names must be globally unique within Azure, pick a different name if you try this yourself).</p>

<p align="center">
  <img src="/assets/img/C2VMRG_more.png" alt="Azure Resource Manager" />
</p>

<p>Now the network diagram looks as follows - compromised hosts that we want to control with our C2 now have a way to communicate to the C2 by the Function Apps Internet facing endpoint of <code class="language-plaintext highlighter-rouge">https://mytestfunctionxyz123.azurewebsites.net/</code> - this app will receive traffic and forward it to port 80 on the C2 VM, where it will be forwarded to the Sliver service after some simple filtering.</p>

<p>Now the network diagram of our environment looks like the following:</p>

<p align="center">
  <img src="/assets/img/c2_architecture_basic_azure_fr1.png" alt="Full Network Diagram" />
</p>

<h1 id="conclusion">Conclusion</h1>

<p>If you want to give these templates a try to create your own testing C2 environment in Azure, more detailed deploy documentation is included in <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment">the repository on GitHub</a>. Keep in mind that this configuration is only suitable for testing and development and would need to be modified and made more robust for any sort of real operational use, but even in that case would serve as a decent starting point.</p>

<p>When you are done with testing, the resources created by the templates can all easiler be deleted by deleting the entire <code class="language-plaintext highlighter-rouge">C2VMRG</code> resource group in the <a href="https://portal.azure.com/#browse/resourcegroups">Azure Portal</a> or by running the following command:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az group delete --name C2VMRG
</code></pre></div></div>]]></content><author><name>Stephen Bradshaw</name></author><category term="Azure" /><category term="Azure functions" /><category term="Serverless" /><category term="C2" /><category term="domain fronting" /><category term="forwarding" /><category term="container" /><category term="sliver" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Azure Service Command and Control HTTP traffic forwarding</title><link href="/2025/05/07/azure-service-C2-forwarding.html" rel="alternate" type="text/html" title="Azure Service Command and Control HTTP traffic forwarding" /><published>2025-05-07T08:06:00+00:00</published><updated>2025-05-07T08:06:00+00:00</updated><id>/2025/05/07/azure-service-C2-forwarding</id><content type="html" xml:base="/2025/05/07/azure-service-C2-forwarding.html"><![CDATA[<p align="center">
  <img width="500" height="500" src="/assets/img/azure_cloud.png" alt="Azure Hacker" />
</p>

<p>This is a continuation of an ongoing series of blog posts I have made on the topic of using cloud provider native services to provide high reputation redirectors for command and control HTTP/S implant traffic.</p>

<p>I have previously looked at this topic for services in <a href="/tags.html#gcp">GCP</a> and <a href="/2023/08/30/aws-service-C2-forwarding.html">AWS</a>, and now I’m looking at Azure. This post will look at how we can use <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview">Azure Functions</a> to forward implant traffic.</p>

<p>If you want some more details on why you might want to do this, check out the linked portion of this previous post 
<a href="/2025/03/26/gcp-service-C2-forwarding.html#what-is-the-purpose-of-c2-fronting">here</a>.</p>

<h1 id="update---automated-deployment-of-azure-resources">Update - Automated deployment of Azure resources</h1>

<p><strong><em>Update 4 June 2025</em></strong>: In the original version of this post I discussed how to manually setup all of the needed resources for this proof of concept domain fronting test, including basic C2 infrastructure to forward traffic to and the Function App Azure resource and code. The original manual steps are documented below. In doing further tests in Azure however, I have since created Azure Resource Manager templates that can be used to help automate these setup steps.</p>

<p>This inclues a “base” template set to create the “Azure POC environment” of a VPC and VM running Sliver as discussed in the <a href="#manual-deployment---azure-poc-environment">Manual deployment - Azure POC environment</a> section below.</p>

<p>It also includes a “functionapp” template set that creates the Azure resources for the Function App and configures them as described in sections <a href="#manual-creation-of-the-azure-function-app">Manual creation of the Azure Function App</a> and <a href="#manual-configuration-of-the-function-app-and-network-security-group">Manual configuration of the Function App and network security group</a>.</p>

<p>The templates are available <a href="https://github.com/stephenbradshaw/AzureC2PocDeployment">here</a> including deployment documentation, and are discussed in more detail in a post <a href="/2025/06/04/azure_c&amp;c_poc_infra_deployment.html">here</a>.</p>

<h1 id="manual-deployment---azure-poc-environment">Manual deployment - Azure POC environment</h1>

<p>Similar to what I did for the <a href="/tags.html#gcp">GCP</a> POCs discussed in previous posts, I needed some simple C2 infrastructure to forward traffic to.</p>

<p>To this end, I created the following using the Azure Portal, using <code class="language-plaintext highlighter-rouge">West Central US</code> (<code class="language-plaintext highlighter-rouge">West US 2</code>) for regional resources:</p>
<ul>
  <li>A resource group <code class="language-plaintext highlighter-rouge">C2VMRG</code> used to collect all the related infrastructure used for the POC</li>
  <li>A Virtual Network, with network range <code class="language-plaintext highlighter-rouge">10.0.0.0/16</code>, with a <code class="language-plaintext highlighter-rouge">default</code> subnet network with range <code class="language-plaintext highlighter-rouge">10.0.0.0/24</code> where the C2 VM instance will be attached</li>
  <li>A Linux virtual machine with the <a href="https://github.com/BishopFox/sliver">Sliver</a> C2 installed and a public IP address that I could use to access the instance by SSH. The internal IP of this instance was <code class="language-plaintext highlighter-rouge">10.0.0.4</code>.</li>
  <li>A network security group associated with the instance, configured with custom rules, restricting SSH traffic to my home IP address only. SSH is the means by which I accessed the instance to configure the Operating System and operate the C2 service.</li>
</ul>

<p>With this basic infrastructure available to forward to, the Function App can be created.</p>

<h1 id="manual-creation-of-the-azure-function-app">Manual creation of the Azure Function App</h1>

<p>The Function App and associated resources needed for the POC can be created using the Azure Portal <a href="https://portal.azure.com/#browse/Microsoft.Web%2Fsites/kind/functionapp">here</a></p>

<p>Start the new app wizard using the <code class="language-plaintext highlighter-rouge">Create</code> button to start the “Create Function App” wizard.</p>

<p>Select <code class="language-plaintext highlighter-rouge">Flex Consumption</code> as the hosting method and then the following options in the associated steps of the wizard.</p>

<p><strong>Basics</strong></p>

<ul>
  <li>Function App Name: Select a function app name. The value you choose must be globally unique and will form part of the URL used to access the app. I chose <code class="language-plaintext highlighter-rouge">mytestfunctionxyz123</code> as my name.</li>
  <li>Region: West Central US</li>
  <li>Runtime stack: Python</li>
  <li>Version: 3.12</li>
  <li>Instance size: 2048 MB</li>
</ul>

<p><strong>Storage</strong></p>
<ul>
  <li>Storage account: Create new</li>
</ul>

<p><strong>Azure OpenAI</strong></p>
<ul>
  <li>Does not need to be enabled</li>
</ul>

<p><strong>Networking</strong></p>
<ul>
  <li>Enable public access: On</li>
  <li>Enable virtual network integration: On</li>
  <li>Virtual Network: Select the existing network</li>
  <li>Inbound access: Enable private endpoints: Off</li>
  <li>Outbound access: Enable VNet integration: On</li>
  <li>Outbound subnet: You wont be able to use the <code class="language-plaintext highlighter-rouge">default</code> subnet so select <code class="language-plaintext highlighter-rouge">Create new</code> and create a subnet within the existing VPC associated with your instance - I called my new subnet <code class="language-plaintext highlighter-rouge">funcsubnet</code> and assigned range <code class="language-plaintext highlighter-rouge">10.0.1.0/24</code>.</li>
</ul>

<p><strong>Monitoring</strong></p>
<ul>
  <li>Enable Application Insights: No</li>
</ul>

<p><strong>Deployment</strong></p>
<ul>
  <li>Continuous deployment: Disable</li>
  <li>Authentication Settings - Basic authentication: Disable</li>
</ul>

<p><strong>Authentication</strong>
Accept the defaults for authentication for storage access</p>

<p><strong>Tags</strong>
Accept the defaults or set tags as required</p>

<p>Hit <code class="language-plaintext highlighter-rouge">Create</code> to start the wizards deployment process - it will create the Function App and a few other required services for you.</p>

<h1 id="manual-configuration-of-the-function-app-and-network-security-group">Manual configuration of the Function App and network security group</h1>

<p>Once the Function App deployment process is done there are a few config steps we need to complete in the Azure Portal.</p>

<p>In the settings of the newly created Function App, go to the “Environment variables” section and set the <code class="language-plaintext highlighter-rouge">DESTINATION</code> variable with the internal IP address of your C2 VM instance. This was <code class="language-plaintext highlighter-rouge">10.0.0.4</code> in my case.</p>

<p>It is also necessary to modify the network security group of the C2 VM instance to allow traffic from the network address range of the newly created subnet (<code class="language-plaintext highlighter-rouge">10.0.1.0/24</code> in my case) to port 80 on the VMinstance.</p>

<h1 id="code-deployment-to-the-function-app-resource">Code deployment to the Function App resource</h1>

<p>Deployment will require installing and setting up the <a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt">Azure CLI</a>. We can deploy code to an existing Function App with the CLI using the zip file deployment approach, which uses a command in the format shown below. The command references the resource group and name of the function and the local zip file containing the source code to deploy in the App.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az functionapp deployment source config-zip -g &lt;RESOURCE_GROUP&gt; -n &lt;FUNCTION_NAME&gt; --src &lt;CODE_ZIP_FILE&gt;
</code></pre></div></div>

<p>Get the function code from <a href="https://github.com/stephenbradshaw/AzureFunctionC2Forwarder">here</a>, clone it locally and change to the root of the source as your present working directory.</p>

<p>You can the make a zip file <code class="language-plaintext highlighter-rouge">/tmp/dep.zip</code> with the needed files from the repo like so.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zip -r /tmp/dep.zip ./function_app.py ./host.json ./requirements.txt
</code></pre></div></div>

<p>In my example case, I did the deployment using the following command, given my resource group of <code class="language-plaintext highlighter-rouge">C2VMRG</code>, function name <code class="language-plaintext highlighter-rouge">mytestfunctionxyz123</code> and zip file of <code class="language-plaintext highlighter-rouge">/tmp/dep.zip</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az functionapp deployment source config-zip -g C2VMRG -n mytestfunctionxyz123 --src /tmp/dep.zip
</code></pre></div></div>

<p>After deployment, the app was running at <code class="language-plaintext highlighter-rouge">https://mytestfunctionxyz123.azurewebsites.net/</code>.</p>

<p>The architecture looks like the following.</p>

<p align="center">
  <img src="/assets/img/c2_architecture_basic_azure_fr.png" alt="C2 with Azure Function App Fronting" />
</p>

<h1 id="conclusion">Conclusion</h1>

<p>I intend to keep investigating Azure to see if there are any more ways to use their cloud services to forward for C2, and if I find more I’ll do a follow up post to discuss them. Are you aware of any? Let me know!</p>]]></content><author><name>Stephen Bradshaw</name></author><category term="Azure" /><category term="Azure functions" /><category term="Serverless" /><category term="C2" /><category term="domain fronting" /><category term="forwarding" /><category term="container" /><category term="sliver" /><category term="Azure service fronting" /><category term="redirector" /><summary type="html"><![CDATA[]]></summary></entry></feed>