如何利用Docker构建命令控制服务器

 

一、前言

在红蓝对抗中,红方成功突破对手防线是再正常不过的一件事情。许多经验证明(根据我的团队工作经验),如果想不断扩大规模、紧跟经验丰富的安全运营中心(security operations center,SOC)的发展步伐,那就必须紧抓自动化处理流程这个关键因素。结合我过去的操作经验来看,这一点在许多情况下是一个不争的事实。需要注意的是,“先人一步”不一定意味着我们就可以成功赢得市场。实际情况中我们的确会面临各种各样的挑战,但在许多情况下,手头上拥有精心策划的一个产品能起到事半功倍的效果。基于这个原因,我希望我们团队能够更有效地设计及部署C2服务器,我也会在本文中分享一些个人经验。

 

二、Docker介绍

如果有些人对与Docker了解甚少,这里我们来介绍下一些基础知识以及简单的专业术语(如果你对此比较了解可以直接跳过这一部分)。Docker是一个容器化平台,开发者及运营团队可以在Docker的支持下轻松构建及部署软件环境,并且Docker支持几乎所有的系统平台,兼容性非常好。在深入了解相关知识之前,你需要先熟悉以下几个名词:

1、Dockerfile:该文件用来指导如何创建docker镜像,创建(build)过程通常是一个自动化处理过程。

2、Docker镜像(Image):该镜像由docker build文件生成,其地位类似于操作系统镜像(如Ubuntu 16.04镜像)。

3、Docker容器(Container):镜像其实并不是一个单独的文件,而是具有层级结构,多层容器可以组成一个完整的镜像,这也是Docker强大的地方所在。想想一下,如果我们想构建5个Ubuntu镜像,这几个镜像彼此仅在ENV(环境变量)上有点区别。那么首先我们需要从头开始构建一个镜像,但随后只需要简单修改一层后,就能得到一个新的“镜像”。之所以能这么做,原因在于这些镜像只是基础镜像的快照而已。

4、Docker数据卷(Volume):与挂载的驱动器类似,Volume是一个存储容器,可以支持数据的持久化存储。

 

三、Docker及C2

Docker最主要的一个优点是采用了分层容器系统,这样一来,多个Image可以使用不同的volume以及不同的Container。这种方案有如下几点明显的优势:

1、大小:减少部署不同的环境或镜像时所占的空间大小。

2、成本:减少所需的VPS基础设施的数量。

3、资源:通常情况下,我们几乎不会达到可利用资源的极限值,使用Docker后,我们可以在成本为5美元的Digital Ocean虚拟机实例上运行5个Empire服务器。

4、集中控制:我讨厌同时维护多个实例,采用端口映射的Docker网络解决方案后,我们可以在一台服务器上同时维护长期、短期以及备份用途的C2服务器。

5、安全性:可以避免容器之间通信。Docker容器与LXC容器非常类似,这两者具有相似的安全特性(均使用内核命名空间来操作)。

6、数据集成:挂载Volume后,你可以将C2数据存放在一个地方,不同C2数据之间可以相互隔离。如果出现问题,别人也只能拿到其中一部分数据。

7、管理:安装一次性工具的主要问题之一在于这些工具的支持及依赖问题不是那么好解决,毕竟许多都是黑客们热衷的工具,不考虑易用性问题。在Docker方案中,宿主OS可以充当基础镜像(Base Image)角色,然后我们可以在Base Image中解决掉所有依赖问题。

 

四、Empire Docker

现在Empire官方支持使用Docker作为操作平台。目前这一方案仍处于dev分支中,但我们已经可以在Docker Hub上找到这个镜像。对Dockerfile的分析并不在本文讨论范围内,我准备在将来再讨论自动化方面内容。如果你想深入了解Dockerfile的代码,可以访问此处了解相关细节,该文件需要与release.shbuild.sh脚本配合使用。

首先你需要获取Empire最新的版本,具体命令为:docker pull empireproject/empire。如果想要运行Empire,只需要一条简单的命令即可:ocker run -ti empireproject/empire。Empire运行起来后,你可以输入其他命令行参数。

你可能会注意到,Empire会运行reset.sh以创建数据库及秘钥。这是一种安全特性,属于正常操作,Empire团队通过这种方法避免为所有的Docker镜像创建相同的秘钥。

Docker中非常强大的一种用法就是--entrypoint参数,这个参数可以覆盖镜像中内置的ENTRYPOINT(入口点)。你需要进入容器中,像正常操作那样运行Empire。为了进入容器,你可以使用如下命令替换ENTRYPOINT:

alexanders-MacBook-Pro:~ alexanderrymdeko-harvey$ docker run -ti --entrypoint bash empireproject/empire 
root@cc4ca15ed8ab:/opt/Empire# ls
Dockerfile  LICENSE  README.md  VERSION  changelog  data  empire  lib  plugins  setup
root@cc4ca15ed8ab:/opt/Empire#

在生产环境中,这么做无法正常工作,最主要的问题是存储问题。每当我们运行及重启镜像时,除非我们提交变更(commit),否则镜像都会恢复到基础状态。为了实现持久性存储,我们可以使用Docker Volume,以便将改动挂载到镜像中的特定位置。在这个案例中,我们只需要挂载到Empire数据目录,就可以维护我们的数据库以及证书,具体命令为:docker create -v /opt/Empire --name data empireproject/empire。最后,我们可以使用--volumes-from标识,将之前的data卷挂载到我们的安装目录中,命令为docker run -ti --volumes-from data empireproject/empire。这些操作的运行结果如下所示:

alexanders-MacBook-Pro:~ alexanderrymdeko-harvey$ docker create -v /opt/Empire --name data empireproject/empire
5cae53e19681b4d97646c79ff1673218a421cf689046fcdfa2fdbd4602dd24ae
alexanders-MacBook-Pro:~ alexanderrymdeko-harvey$ docker volume ls
DRIVER              VOLUME NAME
local               cbb254a5d09b2c0ee828509a67dab0697bdbe5f901a71aa24a565433d6f4a854
alexanders-MacBook-Pro:~ alexanderrymdeko-harvey$ docker run -ti --volumes-from data empireproject/empire
[*] Loading stagers from: /opt/Empire//lib/stagers/
[*] Loading modules from: /opt/Empire//lib/modules/
[*] Loading listeners from: /opt/Empire//lib/listeners/
<SNIP>

最后,我们需要将我们的Docker容器对宿主网络环境开放,我们可以使用多种方法完成这一任务,但我发现使用publish参数可以有效解决这种应用场景。这个功能可以让我们“将容器的端口开放给主机网络”。我们可以在-p参数后面带上<HOST-IP>:<HOST-PORT-TO-EXPOSE>:<GUEST-PORT-TO-EXPOSE>。这样操作后,我们可以绑定端口,将流量直接转到Docker容器中,即使我们使用了不同的外部端口也没有问题。

alexanders-MacBook-Pro:~ alexanderrymdeko-harvey$ docker run -ti --volumes-from data -p 10.0.0.207:80:80 empireproject/empire

<SNIP>

================================================================
 [Empire]  Post-Exploitation Framework
================================================================
 [Version] 2.3 | [Web] https://github.com/empireProject/Empire
================================================================

   _______ .___  ___. .______    __  .______       _______
  |   ____||   /   | |   _    |  | |   _       |   ____|
  |  |__   |    /  | |  |_)  | |  | |  |_)  |    |  |__
  |   __|  |  |/|  | |   ___/  |  | |      /     |   __|
  |  |____ |  |  |  | |  |      |  | |  |  ----.|  |____
  |_______||__|  |__| | _|      |__| | _| `._____||_______|


       282 modules currently loaded

       0 listeners currently active

       0 agents currently active


(Empire) > listeners
[!] No listeners currently active 
(Empire: listeners) > uselistener http
(Empire: listeners/http) > info

    Name: HTTP[S]
Category: client_server

Authors:
  @harmj0y

Description:
  Starts a http[s] listener (PowerShell or Python) that uses a
  GET/POST approach.

HTTP[S] Options:

  Name              Required    Value                            Description
  ----              --------    -------                          -----------
  SlackToken        False                                        Your SlackBot API token to communicate with your Slack instance.
  ProxyCreds        False       default                          Proxy credentials ([domain]username:password) to use for request (default, none, or other).
  KillDate          False                                        Date for the listener to exit (MM/dd/yyyy).
  Name              True        http                             Name for the listener.
  Launcher          True        powershell -noP -sta -w 1 -enc   Launcher string.
  DefaultDelay      True        5                                Agent delay/reach back interval (in seconds).
  DefaultLostLimit  True        60                               Number of missed checkins before exiting
  WorkingHours      False                                        Hours for the agent to operate (09:00-17:00).
  SlackChannel      False       #general                         The Slack channel or DM that notifications will be sent to.
  DefaultProfile    True        /admin/get.php,/news.php,/login/ Default communication profile for the agent.
                                process.php|Mozilla/5.0 (Windows
                                NT 6.1; WOW64; Trident/7.0;
                                rv:11.0) like Gecko
  Host              True        http://172.17.0.2:80             Hostname/IP for staging.
  CertPath          False                                        Certificate path for https listeners.
  DefaultJitter     True        0.0                              Jitter in agent reachback interval (0.0-1.0).
  Proxy             False       default                          Proxy to use for request (default, none, or other).
  UserAgent         False       default                          User-agent string to use for the staging request (default, none, or other).
  StagingKey        True        G:IfjvH;Z#J|]FSs9XU~},D{[)8yuR2n Staging key for initial agent negotiation.
  BindIP            True        0.0.0.0                          The IP to bind to on the control server.
  Port              True        80                               Port for the listener.
  ServerVersion     True        Microsoft-IIS/7.5                Server header for the control server.
  StagerURI         False                                        URI for the stager. Must use /download/. Example: /download/stager.php


(Empire: listeners/http) > execute
[*] Starting listener 'http'
[+] Listener successfully started!
(Empire: listeners/http) >

了解这些命令后,我们基本上就可以启动并运行目标环境了。Docker中还有一些高级功能,我们会在接下来的CobaltStrike内容中加以介绍。

 

五、CobaltStrike Docker

红方通常会使用CS(CobaltStrike)作为首选的工具/植入程序。因此我决定使用Dockerfile来构建一个基础镜像。最近CS在Docker Hub上的许可/试用方案有了些变化,这也是我为什么不使用官方镜像的原因所在。不过不用担心,在本地构建一个Docker镜像并没有那么复杂。我已经公开了相关的Dockerfile,大家可以访问此处了解具体内容。整个过程中需要注意一些关键步骤,不过最重要的是构建过程中需要将CS许可秘钥传递给Dockerfile。

首选,我们需要clone这个Dockerfile。

git clone https://github.com/killswitch-GUI/CobaltStrike-ToolKit.git

接下来我们开始构建镜像,将cskey替换为我们自己的许可秘钥。这个过程中会下载一些文件,我们需要稍等片刻,放松一下。

docker build --build-arg cskey="xxxx-xxxx-xxxx-xxxx" -t cobaltstrikecs .

像往常一样,在Dockerfile中,我将ENTRYPOINT设置为teamserver,接下来你可以在镜像字符串后传入所需的参数:

docker run -d -p 192.168.2.238:50050:50050 --name "war_games"  cobaltstrikecs 192.168.2.238 password

这里我们使用了两个新的概念:

1、-d:守护模式,这样我们就可以后台启动这个容器。

2、--name:命名这个容器,这样我们可以在容器启动后使用这个名称来引用该容器。

启动容器后,我们可以查看日志,监控CS实例的运行状态,具体命令为:docker logs -f "war_games"

alexanders-MacBook-Pro:Dockerfiles alexanderrymdeko-harvey$ docker logs -f "war_games"
[*] Generating X509 certificate and keystore (for SSL)
[+] Team server is up on 50050
[*] SHA256 hash of SSL cert is: 2013748909fd61ff687711688e5dc4306d0fb1c3afa8ece4f30630c31ba1557c

如果想要访问该实例,我们可以使使用exec命令,如下所示:

alexanders-MacBook-Pro: Dockerfiles alexanderrymdeko-harvey$ docker exec -ti war_games bash
root@bb7d339b3699:/opt/cobaltstrike#

如果要终止该容器,使用如下这条命令即可:

docker kill war_games

CS Volume

对了,别忘了为CS实例创建一个容器,否则我们会丢失掉所有的数据:

docker create -v /opt/cobaltstrike --name cs-data cobaltstrikecs

接下来,我们可以使用这个volume来启动该实例:

docker run -d --volumes-from cs-data -p 192.168.2.238:50050:50050 --name "war_games"  cobaltstrikecs 192.168.2.238 password
(完)