无垠之码

深度剖析代码之道


cowrie蜜罐

蜜罐(Honeypot)是指一种故意暴露的系统、服务或数据,用来引诱攻击者进行入侵、探测或攻击,然后监控、记录他们行为的信息化系统。其主要目的是迷惑攻击者,监控攻击行为,收集威胁情报。本文介绍的cowrie是一种模拟SSH服务器,诱捕爆破和远程登录攻击行为的中交互蜜罐。

cowrie作为一种交互式蜜罐具有以下优点:

  • 可以输出丰富的日志格式:如文本、JSON格式,方便后续处理
  • 能够直接接入SIEM系统(比如 Splunk、ELK Stack)、安全告警系统,实现实时监控
  • 轻松介入misp开源威胁情报平台
  • 虚拟文件系统,伪装度高,不容易被识破,攻击者虽然可以登录和操作,但其实运行在受控的模拟环境里,不能真正破坏宿主机
  • 记录登录行为,命令执行,收集恶意样本
  • 社区活跃,持续修复Bug、更新新功能,定制化程度高

1. 部署蜜罐


cowrie的安装官网已介绍的是否详细,这里不再赘述,为使得模拟环境更加真实,下面使用qemu虚拟机+官方的docker镜像快速搭建蜜罐环境

sudo apt-get install u-boot-qemu opensbi qemu-system qemu guestfs-tools
wget https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/cloud/generic_alpine-3.21.2-x86_64-bios-cloudinit-r0.qcow2
mv generic_alpine-3.21.2-x86_64-bios-cloudinit-r0.qcow2 alpine.qcow2
qemu-img resize ./alpine.qcow2 +10G
sudo virt-customize -a alpine.qcow2 --root-password password:123456

# 由于虚拟机内存实在太小,只能分配给qemu实例256M内存
qemu-system-x86_64 \
  --nographic \
  -m 256M \
  -smp 1 \
  -net nic -net user,hostfwd=tcp::9090-:22 \
  -drive file=alpine.qcow2,format=qcow2 

# 增加用户
useradd -m peter

# ubuntu云主机镜像需要执行下列操作实际扩容
growpart /dev/sda 1
resize2fs /dev/sda1

下面运行cowrie实例,配置关闭其关闭telnet,简单的将流量转发至后端主机9090端口,此刻攻击者通过连接8822端口,即可访问qemu虚拟机

docker run --name=cowrie \
	--user=cowrie \
	--env COWRIE_TELNET_ENABLES=no \
	--env COWRIE_SSH_LISTEN_ENDPOINTS=tcp:8822:interface=0.0.0.0 \
	--env COWRIE_HONEYPOT_BACKEND=proxy \
	--env COWRIE_PROXY_BACKEND=simple \
	--env COWRIE_PROXY_BACKEND_SSH_HOST=103.79.76.198 \
	--env COWRIE_PROXY_BACKEND_SSH_PORT=9090 \
	--env COWRIE_PROXY_BACKEND_USER=peter \
	--env COWRIE_PROXY_BACKEND_PASS=topsec \
	--volume /cowrie/cowrie-git/etc \
	--volume /cowrie/cowrie-git/var \
	--cap-drop=ALL \
	--workdir=/cowrie/cowrie-git \
	-p 8822:8822 \
	--detach=true \
	cowrie/cowrie:114c918e 

可以看到攻击者117.151.88.32使用root/1234账户登录主机,执行date命令后退出,整个会话持续96秒

image

其实使用cowrie的backend_pool配置,可以自动启动虚拟机池(虚拟机由libvirt来创建和管理),当有攻击者连接时,cowrie将连接转发至给其中一台VM,使用backend_pool的一大优点是其支持更细粒度的控制,比如控制多久回收并重建虚拟机(防止系统污染),创建虚拟机快照等

修改实例中的cowrie.cfg文件,可以将相关信息直接录入至威胁情报平台

[output_misp]
enabled = true
base_url = http://10.10.0.107
api_key = StMhTXPnmErXDGrWMfddlhzdsHkkrhkUSDNyDKUQ
verify_cert = false
publish_event = true
debug = false

2.源码走读


cowrie使用Twisted异步网络编程框架编写,核心入口类是CowrieServiceMaker,作为IServiceMaker接口的实现,CowrieServiceMaker在makeService方法中加载output模块(启动模块加入Twisted日志系统的观察者,这些输出模块继承于Output这个虚类,实现emit方法),创建service驱动程序运行。

@implementer(IServiceMaker, IPlugin)
class CowrieServiceMaker:
	def makeService(self, options: dict) -> service.Service:
		....
		self.output_plugins = []
		for x in CowrieConfig.sections():
			if not x.startswith("output_"):
				continue
			if CowrieConfig.getboolean(x, "enabled", fallback=False) is False:
				continue
			engine: str = x.split("_")[1]
			try:
				output = import_module(f"cowrie.output.{engine}").Output()
				log.addObserver(output.emit)
				self.output_plugins.append(output)
			....
			except Exception:
				log.err()
				log.msg(f"Failed to load output engine: {engine}")
	def pool_ready(self) -> None:
		...
		if self.enableSSH:
            factory = cowrie.ssh.factory.CowrieSSHFactory(backend, self.pool_handler)
			...
            create_endpoint_services(reactor, self.topService, listen_endpoints, factory)
		...

在不转发或者未启用backend_pool配置的情况,调用pool_ready创建CowrieSSHFactory实例,将ssh的实例加入topService,在攻击者每次连接时调用buildProtocol创建一个Twisted的SSHServerTransport派生类对象处理连接请求。

image

connectionMade是HoneyPotSSHTransport的连接处理入口,dataReceived处理流程中判断是否接受到支持的对方版本号,进而进入密钥交换阶段,随后调用getPacket解密ssh二进制消息内容,验证消息摘要,通过消息类型进入ssh的状态机(密钥协商,用户认证请求,通道打开请求等),dispatchMessage是ssh消息路由的核心函数,这些处理函数由Twisted实现。

# 同时定义消息编号的处理函数, 如MSG_NEWKEYS处理函数ssh_newkeys
MSG_DISCONNECT = 1
MSG_IGNORE = 2
MSG_UNIMPLEMENTED = 3
MSG_DEBUG = 4
MSG_SERVICE_REQUEST = 5
MSG_SERVICE_ACCEPT = 6
MSG_EXT_INFO = 7
MSG_KEXINIT = 20
MSG_NEWKEYS = 21
MSG_KEXDH_INIT = 30
MSG_KEXDH_REPLY = 31
MSG_KEX_DH_GEX_REQUEST_OLD = 30
MSG_KEX_DH_GEX_REQUEST = 34
MSG_KEX_DH_GEX_GROUP = 31
MSG_KEX_DH_GEX_INIT = 32
MSG_KEX_DH_GEX_REPLY = 33

#  C> MSG_KEXINIT
#  S> MSG_KEXINIT
#  C> MSG_KEX_DH_GEX_REQUEST  or   MSG_KEXDH_INIT
#  S> MSG_KEX_DH_GEX_GROUP    or   MSG_KEXDH_REPLY
#  C> MSG_KEX_DH_GEX_INIT     or   --
#  S> MSG_KEX_DH_GEX_REPLY    or   --
#  C> MSG_NEWKEYS
#  S> MSG_NEWKEYS

代码片段中的注释部分,说明ssh状态的流转。MSG_SERVICE_REQUEST,MSG_SERVICE_ACCEPT中service包括ssh-userauth,ssh-connection,ssh-session,ssh-filexfer,ssh-portforwarding等,密钥协商完成一般是请求认证服务(userauth),其实cowrie支持的service在CowrieSSHFactory实例化的时候已设置。

class CowrieSSHFactory(factory.SSHFactory):
    ....
    def __init__(self, backend, pool_handler):
        ....
        self.services = {
            b"ssh-userauth": (
                ProxySSHAuthServer
                if self.backend == "proxy"
                else HoneyPotSSHUserAuthServer
            ),
            b"ssh-connection": connection.CowrieSSHConnection,
        }
        super().__init__()

显而易见CowrieSSHConnection类处理ssh-connection服务,其中核心是创建channel,在SSH协议中,所有的用户交互数据(如命令、输出、文件传输)都发生在Channel内部,而session类型的 channel是用来承载交互式shell、exec 命令、scp/sftp等的。

cowrie在认证完成后返回的avatar.ConchUser派生类CowrieUser的lookupChannel方法,创建了shell环境,即加载了预置命令的相关输出文件,后面执行命令时直接输出文件中定义的输出内容。

实际上,后续的大部分流程在结构上都大同小异。至此只是完成源码中happy-branch部分的走读。很多细节仍有待深入理解,后续将逐步补充和展开。

3.参考文献

  1. https://github.com/cowrie/cowrie
  2. https://docs.twisted.org/en/stable/
comments powered by Disqus