基于开源蜜罐的实践与功能扩展

2019-11-01 16:21:44 浏览数 (1)

0x00 前言

具有一定规模的公司都会有自己的机房,当网络规模和硬件系统到达一定程度,就需要跟进各种安全预警防护手段,而蜜罐系统就是一种常见的防护手段之一,蜜罐主要是通过在网络环境当中,用虚拟各种真实服务的守护进程,去模拟各种常见的应用服务,比如http、mysql、FTP服务等。

一般情况下,蜜罐系统是模拟出来的服务,不具备提供真实服务的能力,比如有蜜罐监听的ftp端口21,但21端口其实只是一个空的监听,不能提供FTP服务,并且己方人员,知道蜜罐是一个报警装置,不会去刻意访问,只有不明身份的攻击者才有可能去访问蜜罐进行漏洞尝试探测。

如果有人对蜜罐进行了大量的针对性操作,并且访问操作的Payload属于攻击数据,我们可以蜜罐的报警日志,判断这个可能是一个可以的攻击行为,或是误碰蜜罐行为,虽然服务不是真实的服务,但攻击的的渗透Payload数据都是真实的。

一般可以在网络环境中,通过交换机的端口聚合技术,在一台机器上创建多个网段的IP,将流量引入集中到一台机器上,然后在这台机器上部署各个IP的监听,理想的状态下是可以提供这种机制的,如果不行也可以通过树莓派等手段,创建蜜罐。现在有很多种的蜜罐系统可以提供给安全运维人员使用,进行蜜罐系统部署,在各色的开源蜜罐系统中,我们选了相对比较有代表性的蜜罐系统给大家介绍,一般会根据蜜罐的:难易性、可用性、展示性、交互性等方面考察蜜罐系统的特点,从另一个角度划分,可分成纯命令行和UI画面支持的。

a):纯命令行式的操作方式,比如Opencanary这个蜜罐系统。 b):有Dashboard界面的可视化蜜罐系统,比如OpenCanary,Hfish等,这次主要介绍的是Opecanary。 OpenCanary蜜罐系统

OpenCanary是一种开源蜜罐系统,在黑帽在2015开源的,实现语言采用Python实现,因为是开源的代码,我们可基于这一套代码,进行扩展和改写, 也因为Python的友好性,可以相对快速的实现我们想要的新功能。Opencanary的代码量真的不是很大,代码结构清晰,扩展简单,设计的不复杂。

OpenCanay是一种基于命令行式的蜜罐服务系统,没有给出过多的UI操作界面,基本操作都是通过命令行,然后通过改写配置文件,完成对蜜罐的各种配置。通过配置文件的方式对专家啊来说,不是一问题,但操作友好性上还是一般,但是可以达到正配置目的。

Opencanary的基本实现原理也是通过设置各种监听的端口,模拟各种流行的服务,而底层实现端口监听,依赖于twisted的python库实现。

0x01 安装Python

opencanary是基于Python的运行环境的,提到运行环境,我们往往就面临着一个不同操作系统Python环境不同的问题,为了避免各种平台差异造成的依赖包困扰,我们选择虚拟的Python环境virtualenv来安装Opencananry,这样所需环境都在虚拟的环境下安装。同时又在默认的系统环境下,通过pip安装部署,大多数情况下,是不会出现问题。

部署Opencanary之前请确保您的机器上有大于等Python2.7版本的Python语言环境系统,至少可以用于运行Python语言,之后基于这个创建我们需要的Opencanary运行环境。

0x02 部署依赖

使用virtualenv保证Python环境下依赖包互隔离不影响,还是要强调一下,不用这个也可以正常装,这样可以直接在系统默认的库下改源码。

相关的依赖库,在Python安装之后,都是通过pip来安装。

2.1 安装PIP

代码语言:javascript复制
python get-pip.py

在安装PIP的前,还要需要安装Python的开发安装包:python-deve,如果不安装python开发包,会提示找不到python.h这个文件,pip是没有办法正常安装。

2.2 安装VirtualEnv

安装virtualenv之后,我们可以将之后使用的各种依赖包,都装到虚拟环境中,而不装到python默认的库文件中,一般用root用户安装的virtualenv生成文件都在/root/.virtualenvs/中。而系统默认安装的库文件的位置是在,/usr/lib/python2.7/site-packages/中,这样环境之间的依赖包不互相影响。

细分的这么清楚,是因为后续我们要修改opencanary源码,不同的部署方法,源代码的位置是不一样的。opencanary是在python2.x下运行的,在官网github上可以看到更具体说明,而一些特别的需要关注点的,在这里给出一些重点的说明。

代码语言:javascript复制
sudo pip install virtualenvsudo pip install virtualenvwrapper --upgrade --ignore-installed

有了virtualenv,我们可以在一台机器上,装多个python的环境。但实际我们装个python2.7版本的环境就基本满足目前的需求。

2.3 创建Python虚拟环境

编辑.bash_profile文件内容。

代码语言:javascript复制
source /usr/local/bin/virtualenvwrapper.sh

virtualevnwrapper.sh的所在位置,取决于你安装时指定的安装位置,有可能在/usr/local/bin下,也可能在/usr/bin下,当初指定的安装目录位置,只有运行了这个 shell脚本,后续执行在shell中,才能找到mkvirtualenv执行文件。

我们创建了一个叫做py27的虚拟环境。

代码语言:javascript复制
mkvirtualenv py27 -p /usr/bin/python

我们切换到py27这个Python环境下。

代码语言:javascript复制
workon py27

2.4 安装Opencananry

pip在安装opencanary时,会自动安装他所需求要的各种依赖,一般不出问题的话,一切都会顺利安装完成。

代码语言:javascript复制
sudo pip install opencanary

0x03 管理配置

Opencanary的配置几乎都在配置文件里生成,第一次安装的时候,我们可以通过命令行创建一个配置文件,这个配置默认只打开了FTP的蜜罐监听

代码语言:javascript复制
opencanaryd --copyconfig

加copyconfig选项的意思是,是生成了一个模板配置文件/root/.opencanary.conf,整个opencanary支持多种服务的监听:

FTP HTTP Proxy MSSQL MySQL NTP Portscan RDP Samba SIP SNMP SSH Telnet TFTP VNC

为了更直观一些,我们截取一段ftp监听的代码:

代码语言:javascript复制
from opencanary.modules import CanaryService
from twisted.application import internet
from twisted.protocols.ftp import FTPFactory, FTPRealm, FTP, 
USR_LOGGED_IN_PROCEED, GUEST_LOGGED_IN_PROCEED, IFTPShell, 
AuthorizationError
from twisted.cred.portal import Portal
from zope.interface import implements
from twisted.cred.checkers import ICredentialsChecker
from twisted.python import failure
from twisted.cred import error as cred_error, credentials
FTP_PATH = "/briar/data/ftp"
class DenyAllAccess:
implements(ICredentialsChecker)
credentialInterfaces = (credentials.IAnonymous, credentials.IUsernamePassword)
def requestAvatarId(self, credentials):
return failure.Failure(cred_error.UnauthorizedLogin())
class LoggingFTP(FTP):
#ripped from main FTP class, overridden to extract connection info
def ftp_PASS(self, password):
"""
Second part of login. Get the password the peer wants to
authenticate with.
"""
if self.factory.allowAnonymous and self._user == self.factory.userAnonymous:
# anonymous login
creds = credentials.Anonymous()
reply = GUEST_LOGGED_IN_PROCEED
else:
# user login
creds = credentials.UsernamePassword(self._user, password)
reply = USR_LOGGED_IN_PROCEED
logdata = {'USERNAME': self._user, 'PASSWORD': password}
self.factory.canaryservice.log(logdata, transport=self.transport)
del self._user
def _cbLogin((interface, avatar, logout)):
assert interface is IFTPShell, "The realm is busted, jerk."
self.shell = avatar
self.logout = logout
self.workingDirectory = []
self.state = self.AUTHED
return reply
def _ebLogin(failure):
failure.trap(cred_error.UnauthorizedLogin, cred_error.UnhandledCredentials)
self.state = self.UNAUTH
raise AuthorizationError
d = self.portal.login(creds, None, IFTPShell)
d.addCallbacks(_cbLogin, _ebLogin)
return d
class CanaryFTP(CanaryService):
NAME = 'ftp'
def __init__(self,config=None, logger=None):
CanaryService.__init__(self, config=config, logger=logger)
self.banner = config.getVal('ftp.banner', default='FTP Ready.').encode('utf8')
self.port = config.getVal('ftp.port', default=21)
# find a place to check that logtype is initialised
# find a place to check that factory has service attached
self.logtype = logger.LOG_FTP_LOGIN_ATTEMPT
self.listen_addr = config.getVal('device.listen_addr', default='')
def getService(self):
p = Portal(FTPRealm(FTP_PATH), [DenyAllAccess()])
f = FTPFactory(p)
f.protocol = LoggingFTP
f.welcomeMessage = self.banner
f.canaryservice = self
return internet.TCPServer(self.port, f, interface=self.listen_addr)

ftp监听服代码在80多行,就完成模拟服务,可以看到使用twisted库。

代码语言:javascript复制
from twisted.application import internet
from twisted.protocols.ftp import FTPFactory, FTPRealm, FTP, 
USR_LOGGED_IN_PROCEED, GUEST_LOGGED_IN_PROCEED, IFTPShell, 
AuthorizationError

其它模拟代码都在moules目录下,都可参考。

配置文件中,默认打开了FTP的访问,用一个正常的ftp客户端,无论是命令行还是GUI工具都可以:

代码语言:javascript复制
{
"device.node_id": "opencanary-1",
"git.enabled": false,
"git.port" : 9418,
"ftp.enabled": true,
"ftp.port": 21,
"ftp.banner": "FTP server ready",
"http.banner": "Apache/2.2.22 (Ubuntu)",
"http.enabled": false,
"http.port": 80,
"http.skin": "nasLogin",
"http.skin.list": [
{
"desc": "Plain HTML Login",
"name": "basicLogin"
},
{
"desc": "Synology NAS Login",
"name": "nasLogin"
}
],
"httpproxy.enabled" : false,
"httpproxy.port": 8080,
"httpproxy.skin": "squid",
"httproxy.skin.list": [
{
"desc": "Squid",
"name": "squid"
},
{
"desc": "Microsoft ISA Server Web Proxy",
"name": "ms-isa"
}
],
"logger": {
"class": "PyLogger",
"kwargs": {
"formatters": {
"plain": {
"format": "%(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.FileHandler",
"filename": "/var/tmp/opencanary.log"
}
}
}
},
"portscan.enabled": false,
"portscan.logfile":"/var/log/kern.log",
"portscan.synrate": 5,
"portscan.nmaposrate": 5,
"portscan.lorate": 3,
"smb.auditfile": "/var/log/samba-audit.log",
"smb.enabled": false,
"mysql.enabled": false,
"mysql.port": 3306,
"mysql.banner": "5.5.43-0ubuntu0.14.04.1",
"ssh.enabled": false,
"ssh.port": 22,
"ssh.version": "SSH-2.0-OpenSSH_5.1p1 Debian-4",
"redis.enabled": false,
"redis.port": 6379,
"rdp.enabled": false,
"rdp.port": 3389,
"sip.enabled": false,
"sip.port": 5060,
"snmp.enabled": false,
"snmp.port": 161,
"ntp.enabled": false,
"ntp.port": "123",
"tftp.enabled": false,
"tftp.port": 69,
"tcpbanner.maxnum":10,
"tcpbanner.enabled": false,
"tcpbanner_1.enabled": false,
"tcpbanner_1.port": 8001,
"tcpbanner_1.datareceivedbanner": "",
"tcpbanner_1.initbanner": "",
"tcpbanner_1.alertstring.enabled": false,
"tcpbanner_1.alertstring": "",
"tcpbanner_1.keep_alive.enabled": false,
"tcpbanner_1.keep_alive_secret": "",
"tcpbanner_1.keep_alive_probes": 11,
"tcpbanner_1.keep_alive_interval":300,
"tcpbanner_1.keep_alive_idle": 300,
"telnet.enabled": false,
"telnet.port": "23",
"telnet.banner": "",
"telnet.honeycreds": [
{
"username": "admin",
"password": "$pbkdf2-sha512$19000$bG1NaY3xvjdGyBlj7N37Xw$dGrmBqqWa1okTCpN3QEmeo9j5DuV2u1EuVFD8Di0GxNiM64To5O/Y66f7UASvnQr8.LCzqTm6awC8Kj/aGKvwA"
},
{
"username": "admin",
"password": "admin1"
}
],
"mssql.enabled": false,
"mssql.version": "2012",
"mssql.port":1433,
"vnc.enabled": false,
"vnc.port":5000
}

当配置文件用--copyconfig生成后,我们执行Opencanary服务启动程序。对于我们想创建的监听服务,我们把对应服务在配置中的.enabled选项设置成true即可,用--restart重启opencanary服务之后,就可以打开服务端口监听

启动服务

代码语言:javascript复制
opencanaryd --start

当所有的模拟监听服务都打开的时候,会出现类似下面的进程信息。

代码语言:javascript复制
netstat -plunt
代码语言:javascript复制
tcp 0 0 0.0.0.0:2222 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:21 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:23 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:1433 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:3389 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:8001 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:9418 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 12683/python
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 12683/python
udp 0 0 0.0.0.0:57197 0.0.0.0:* 8994/python
udp 0 0 0.0.0.0:5060 0.0.0.0:* 12683/python
udp 0 0 0.0.0.0:69 0.0.0.0:* 12683/python
udp 0 0 0.0.0.0:123 0.0.0.0:* 12683/python
udp 0 0 0.0.0.0:161 0.0.0.0:* 12683/python

重启服务

代码语言:javascript复制
opencanaryd --restart

关闭服务

代码语言:javascript复制
opencanaryd --stop

其它的命令参数使用可以用--help查一下。

代码语言:javascript复制
opencanaryd --help

0x04 报警日志

Opencanary模拟了各种服务的监听,当模拟监听被触发的时候就会报警,产生日志,下面给出发报警的方式和产生的日志格式。一般情况下,我们会在机房和内网的网络环境下部署蜜罐。如果蜜罐系统放到外网,会不时的被各种组织的探测手段发现,进行标识统计。而在内网外部的探测行为是进不了内网的,除非节点被攻破,成为攻击的入口。

正是当有人非法进入内网后,才会出现勿碰蜜罐的事件发生概率提高,而内部防御不只是一种防御手段,各家公司都提供了流量监听产品,HIDS这种工具,Opencanary就像您居家过日子,晚上门口多放了几个啤酒瓶子,如真的门锁被破开,入侵者有能会碰到屋中放置的啤酒瓶子。

蜜罐系统,可以模拟各种程序协议,我们以FTP为例,展示一下蜜罐的触碰与报警日志生成的流程,Opencanary中各种服务的触碰方法,我们会这个例子的后面列出来。

关于FTP,我们测试蜜罐服务器是192.168.0.6,我们启动了opencanary,然后用ftp客户端进行访问:

代码语言:javascript复制
ftp 192.168.0.6

一般会出现,类似下面的操作过程:

代码语言:javascript复制
[root@localhost opencanary]# ftp 192.168.0.6
Connected to 192.168.0.6 (192.168.0.6).
220 FTP server ready
Name (192.168.0.6:root): test
331 Password required for test.
Password:
530 Sorry, Authentication failed.
Login failed.
ftp>

这个过程交互结束之后,就会生成一条json数据,json日志数据如下:

代码语言:javascript复制
{"src_port": 35990, "logdata": {"USERNAME": "test", "PASSWORD": "123456"}, "logtype": 2000, "dst_host": "192.168.0.6", "dst_port": 21, "src_host": "192.168.0.5"}

这是一次针对192.168.0.6的21号端口ftp访问,OpenCanary有一个ftp模拟的python监听实现脚本,之前也说过是基于twisted的实现,当蜜罐系统监听到入侵者访问时,就会把相应的paylog存起来,然后记录到日志文件中,在opencanary.conf中设置的日志文件位置是/usr/tmp/opencanary.log中。

官方的opencanary的日志都是生成到本地,后面我们会给出一,如何将日志转存到其它地方。

下面我们列举出,几乎所有opencanary蜜罐支持协议的触发方法:

4.1 HTTP

触发方法

代码语言:javascript复制
curl 0.0.0.0:80

日志数据

代码语言:javascript复制
{"dst_host": "172.18.200.58", "dst_port": 80, "local_time": "2019-01-07 13:47:45.817940", "logdata": {"HOSTNAME": "172.18.200.58", "PASSWORD": "admin888", "PATH": "/index.html", "SKIN": "nasLogin", "USERAGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:61.0) Gecko/20100101 Firefox/61.0", "USERNAME": "admin"}, "logtype": 3001, "node_id": "opencanary-1", "src_host": "172.18.205.14", "src_port": 54488}

4.2 FTP

触发方法

代码语言:javascript复制
ftp 172.12.200.58

日志数据

代码语言:javascript复制
{"dst_host": "172.18.200.58", "dst_port": 80, "local_time": "2019-01-07 13:47:45.817940", "logdata": {"HOSTNAME": "172.18.200.58", "PASSWORD": "admin888", "PATH": "/index.html", "SKIN": "nasLogin", "USERAGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:61.0) Gecko/20100101 Firefox/61.0", "USERNAME": "admin"}, "logtype": 3001, "node_id": "opencanary-1", "src_host": "172.18.205.14", "src_port": 54488}

4.3 SSH

触发方法

代码语言:javascript复制
ssh root@172.18.200.58

日志

代码语言:javascript复制
{"dst_host": "172.18.200.58", "dst_port": 2222, "local_time": "2019-01-07 13:54:27.811101", "logdata": {"SESSION": "3"}, "logtype": 4000, "node_id": "opencanary-1", "src_host": "172.18.205.14", "src_port": 54639}

4.4 Telnet

触发方法

代码语言:javascript复制
telnet 172.18.200.58

日志数据

代码语言:javascript复制
{"dst_host": "172.18.200.58", "dst_port": 23, "honeycred": false, "local_time": "2019-01-07 13:56:45.341785", "logdata": {"PASSWORD": "admin888", "USERNAME": "admin123"}, "logtype": 6001, "node_id": "opencanary-1", "src_host": "172.18.205.14", "src_port": 54676}

4.5 Mysql

触发方法

代码语言:javascript复制
mysql -h172.18.200.58 -uroot -p

日志数据

代码语言:javascript复制
{"dst_host": "172.18.200.58", "dst_port": 3306, "local_time": "2019-01-07 13:58:25.922257", "logdata": {"PASSWORD": "18076c09615de80ddb2903191b783714918b4c4f", "USERNAME": "root"}, "logtype": 8001, "node_id": "opencanary-1", "src_host": "172.18.220.253", "src_port": 46662}

4.6 Git

触发方法

代码语言:javascript复制
git clone git://192.168.1.7:9418/tmp.git

日志数据

代码语言:javascript复制
{"dst_host": "192.168.1.7", "dst_port": 9418, "local_time": "2019-01-05 15:38:46.368627", "logdata": {"HOST": "192.168.1.7:9418", "REPO": "tmp.git"}, "logtype": 16001, "node_id": "opencanary-1", "src_host": "192.168.1.3", "src_port": 57606}

4.7 NTP

触发方法

代码语言:javascript复制
git clone git://192.168.1.7:9418/tmp.git

日志数据

代码语言:javascript复制
{"dst_host": "0.0.0.0", "dst_port": 123, "local_time": "2019-01-05 15:58:52.075987", "logdata": {"NTP CMD": "monlist"}, "logtype": 11001, "node_id": "opencanary-1", "src_host": "192.168.1.6", "src_port": 57886}

4.8 Redis

触发方法

代码语言:javascript复制
(env) [root@honeypot Honeypot]# redis-cli -h 192.168.1.7192.168.1.7:6379> keys *(error) NOAUTH Authentication required.192.168.1.7:6379> config get requirepass(error) ERR unknown command 'config'192.168.1.7:6379> auth admin(error) ERR invalid password192.168.1.7:6379>

日志数据

代码语言:javascript复制
{"dst_host": "192.168.1.7", "dst_port": 6379, "local_time": "2019-01-05 16:05:11.637269", "logdata": {"ARGS": "", "CMD": "COMMAND"}, "logtype": 17001, "node_id": "opencanary-1", "src_host": "192.168.1.6", "src_port": 34471}

4.9 TCP

触发方法

代码语言:javascript复制
telnet 192.168.1.6 8001

日志

代码语言:javascript复制
{"dst_host": "192.168.1.6", "dst_port": 8001, "local_time": "2019-01-05 17:18:51.601478", "logdata": {"BANNER_ID": "1", "DATA": "", "FUNCTION": "CONNECTION_MADE"}, "logtype": 18002, "node_id": "opencanary-1", "src_host": "192.168.1.3", "src_port": 59176}

4.10 VNC

触发方法

用VNC客户端访问

日志数据

代码语言:javascript复制
{"dst_host": "192.168.1.7", "dst_port": 5000, "local_time": "2019-01-06 08:21:28.951940", "logdata": {"VNC Client Response": "58c00be9ee5b7f3b666771dd2bda9309", "VNC Password": "<Password was not in the common list>", "VNC Server Challenge": "953e2dff7e4d3a3114527c282817ce1d"}, "logtype": 12001, "node_id": "opencanary-1", "src_host": "192.168.1.6", "src_port": 54634}

4.11 RDP

触发方法

日志数据

代码语言:javascript复制
{"dst_host": "192.168.1.7", "dst_port": 3389, "local_time": "2019-01-06 08:59:13.890934", "logdata": {"DOMAIN": "", "HOSTNAME": "HelloHost", "PASSWORD": "helloword", "USERNAME": "administrator1"}, "logtype": 14001, "node_id": "opencanary-1", "src_host": "192.168.1.6", "src_port": 59955}

4.12 SIP

触发方法

代码语言:javascript复制
hydra -l adminsip -p password 192.168.1.7 sip

日志格式

代码语言:javascript复制
{"dst_host": "0.0.0.0", "dst_port": 5060, "local_time": "2019-01-06 09:55:12.578148", "logdata": {"HEADERS": {"call-id": ["1337@192.168.1.7"], "content-length": ["0"], "cseq": ["1 REGISTER"], "from": ["<sip:adminsip@192.168.1.7>"], "to": ["<sip:adminsip@192.168.1.7>"], "via": ["SIP/2.0/UDP 10.0.2.15:46759;received=192.168.1.7"]}}, "logtype": 15001, "node_id": "opencanary-1", "src_host": "192.168.1.7", "src_port": 46759}

4.13 SNMP

触发方法

代码语言:javascript复制
hydra -p password 192.168.1.7 snmp

日志数据

代码语言:javascript复制
{"dst_host": "0.0.0.0", "dst_port": 161, "local_time": "2019-01-06 11:17:27.266214", "logdata": {"COMMUNITY_STRING": "password", "REQUESTS": ["1.3.6.1.2.1.1.1"]}, "logtype": 13001, "node_id": "opencanary-1", "src_host": "192.168.1.7", "src_port": 47112}

4.14 NMAP

触发方法

代码语言:javascript复制
sudo nmap -v -Pn -O 192.168.1.7

日志数据

代码语言:javascript复制
{"dst_host": "192.168.1.7", "dst_port": "21", "local_time": "2019-01-06 16:35:24.356080", "logdata": {"FIN": "", "ID": "37499", "IN": "eth1", "LEN": "60", "MAC": "08:00:27:da:4c:e2:6c:96:cf:dd:ee:bd:08:00", "OUT": "", "PREC": "0x00", "PROTO": "TCP", "PSH": "", "RES": "0x00", "SYN": "", "TOS": "0x00", "TTL": "56", "URG": "", "URGP": "0", "WINDOW": "256"}, "logtype": 5002, "node_id": "opencanary-1", "src_host": "192.168.1.6", "src_port": "40098"}

4.15 SYN探测

触发方法

代码语言:javascript复制
sudo nmap -sS 192.168.1.7

日志数据

代码语言:javascript复制
{"dst_host": "192.168.1.7", "dst_port": "21", "local_time": "2019-01-06 16:35:24.190176", "logdata": {"ID": "51918", "IN": "eth1", "LEN": "56", "MAC": "08:00:27:da:4c:e2:6c:96:cf:dd:ee:bd:08:00", "OUT": "", "PREC": "0x00", "PROTO": "TCP", "RES": "0x00", "SYN": "", "TOS": "0x00", "TTL": "58", "URGP": "0", "WINDOW": "512"}, "logtype": 5001, "node_id": "opencanary-1", "src_host": "192.168.1.6", "src_port": "40088"}

4.16 FIN探测

触发方法

代码语言:javascript复制
sudo nmap -sF 192.168.1.7

日志数据

代码语言:javascript复制
{"dst_host": "192.168.1.7", "dst_port": "23", "local_time": "2019-01-06 16:46:18.336954", "logdata": {"FIN": "", "ID": "29768", "IN": "eth1", "LEN": "40", "MAC": "08:00:27:da:4c:e2:6c:96:cf:dd:ee:bd:08:00", "OUT": "", "PREC": "0x00", "PROTO": "TCP", "RES": "0x00", "TOS": "0x00", "TTL": "59", "URGP": "0", "WINDOW": "1024"}, "logtype": 5005, "node_id": "opencanary-1", "src_host": "192.168.1.6", "src_port": "35116"}

4.17 XmasTree探测

触发方法

代码语言:javascript复制
sudo nmap -sX 192.168.1.7

日志数据

代码语言:javascript复制
{"dst_host": "192.168.1.7", "dst_port": "139", "local_time": "2019-01-06 16:48:46.225539", "logdata": {"FIN": "", "ID": "19984", "IN": "eth1", "LEN": "40", "MAC": "08:00:27:da:4c:e2:6c:96:cf:dd:ee:bd:08:00", "OUT": "", "PREC": "0x00", "PROTO": "TCP", "PSH": "", "RES": "0x00", "TOS": "0x00", "TTL": "56", "URG": "", "URGP": "0", "WINDOW": "1024"}, "logtype": 5004, "node_id": "opencanary-1", "src_host": "192.168.1.6", "src_port": "50913"}

4.18 Null探测

触发方法

代码语言:javascript复制
sudo nmap -sN 192.168.1.7

日志数据

代码语言:javascript复制
{"dst_host": "192.168.1.7", "dst_port": "5060", "local_time": "2019-01-06 16:51:07.789903", "logdata": {"ID": "26441", "IN": "eth1", "LEN": "40", "MAC": "08:00:27:da:4c:e2:6c:96:cf:dd:ee:bd:08:00", "OUT": "", "PREC": "0x00", "PROTO": "TCP", "RES": "0x00", "TOS": "0x00", "TTL": "50", "URGP": "0", "WINDOW": "1024"}, "logtype": 5003, "node_id": "opencanary-1", "src_host": "192.168.1.6", "src_port": "58015"}

4.19 MSSQL

触发方法

MSSQL客户端工具

日志数据

代码语言:javascript复制
{"dst_host": "172.18.200.58", "dst_port": 1433, "local_time": "2019-01-07 09:04:58.690137", "logdata": {"AppName": "SQLPro for MSSQL (hankinsoft.com)", "CltIntName": "DB-Library", "Database": "test", "HostName": "Piroguehost", "Language": "us_english", "Password": "sa123456", "ServerName": "172.18.200.58:1433", "UserName": "sa"}, "logtype": 9001, "node_id": "opencanary-1", "src_host": "172.18.205.14", "src_port": 64344}

4.20 HTTPProxy

触发方法

浏览器配置

日志数据

代码语言:javascript复制
{"dst_host": "172.18.200.58", "dst_port": 8080, "local_time": "2019-01-07 13:26:47.761297", "logdata": {"PASSWORD": "passsquid", "USERNAME": "squidadmin"}, "logtype": 7001, "node_id": "opencanary-1", "src_host": "172.18.205.14", "src_port": 53798}

0x05 Opencanary的功能扩展

Opencanay是用Python开源的,我们可以根据我们具体的需求,扩展opencanary的功能,这个需求产生的原因,是我们需要对报警日志进行汇聚。

蜜罐日志是日常威胁日志收集的一部分,蜜罐报警的日志分析的优先级相对是很高的,因为一般情况下,蜜罐的误碰不应该太多(白名单),所以第一时间出现问题,需要蜜罐把数据推给日志分析系统、或是日志接收服务,数据的取得有多种方式,之前一种方式是把日志存到本地的日志文本文件中,实时也写到mysql数据中,然后再由其它程序进行轮训,这种处理方式有一个问题是,轮询是有时间间隔的,定时任务的轮训机制,真实的报警时间和攻击实际发生时间可能不在一个时间点,这样在做关联分析时,如果没有唯一标识机器属性时,比如:mac,没法判断内网IP对应的是同一个人的行为,所以,我们要第一时间接收到蜜罐的报警。

OpenCanary的代码是开源的, 我们只需要改源代码,就可以实现我们的需求,OpenCanary有一个logger日志写入类,我们只要改这个模块的代码即可。

我们一般有几种日志数据存储形式:

1.RESTAPI:我们在蜜罐写日志的地方,调用一个REST API的接口服务,将报警数据转发给其它的REST接口,由接口写数据,分析,报警。

2.RPC:在蜜罐写日志的地方加一个RPC调用接口,像调用一般函数一样,调用一个远程RPC写日志方法。然后在PPC服务端加入各种判定策略逻辑,就像我们之间介绍的semaphore那个项目。

3.UDP Syslog:在蜜罐写日志的地方,直接通过socketc通过syslog协议数据,把json数据发给一个syslog监听,有不同的开源方案都能解决这个问题, 我们一般采用的都是Graylog的数据收集方法,将syslog监听的数据接收后,对其中的json格式数据时进行解析,然后存入到多复本的ES集群中,这个过程中Graylog做了很多ES数据的管理工作,让ES操作管理变得容易,而Splunk如果数据量不大是可以免费使用的, 对于一般数据规模,几个ES集群这种情况,Graylog就可以解决,功能看实际需求,根据自己情况进行扩展,开源可以省下很多开发人月。

修改源代码位置:

logger是一人单例模式的类,logger.py在 opencanary的库文件中,如果不出意外的话,位置应该是:

代码语言:javascript复制
/usr/lib/python2.7/site-packages/opencanary

如果您是用virtualenv安装,那库的位置就在您的虚拟化python的位置的库文件中。

logger.py源代码大家可以在库文件中找到, 我们不列出所有源代码,为了方便展示,我们不在文件中重写一个handler类,而是直接在写本地日志的地方,将报警日志外发到Syslog监听,或是直接放给Graylog服务器,作为数据接收端接受数据,后期对数据进行黑白名单处理,数据聚合处理。

这样进行扩展的好处就是,不破坏原有的执行时序,达到数据转存的目的。

代码语言:javascript复制
def log(self, logdata, retry=True):
import syslog_client
graylog = syslog_client.Syslog("198.168.0.8")
graylog.send(json.dumps(logdata), syslog_client.Level.INFO)
logdata = self.sanitizeLog(logdata)
self.logger.warn(json.dumps(logdata, sort_keys=True))

下面这句话是写日志,

代码语言:javascript复制
self.logger.warn(json.dumps(logdata, sort_keys=True))

我们另外加入几行转发syslog的日志处理:

代码语言:javascript复制
import syslog_client
graylog = syslog_client.Syslog("198.168.0.8")
graylog.send(json.dumps(logdata), syslog_client.Level.INFO)

这里要特别注意的地方是,在发送日志数据之前,要把数据包装成json数据格式,这样当数据发送给Graylog之后, 就可能直接对数据按json格式的数据切割。

也可以通过自定义的syslog监听服务,对数据进行各种加工处理。

我们看一下opencanary.conf中是如何关联到logger这个类的。

代码语言:javascript复制
    "logger": {
        "class": "PyLogger",
        "kwargs": {
            "formatters": {
                "plain": {
                    "format": "%(message)s"
                }
            },
            "handlers": {
                "console": {
                    "class": "logging.StreamHandler",
                    "stream": "ext://sys.stdout"
                },
                "file": {
                    "class": "logging.FileHandler",
                    "filename": "/var/tmp/opencanary.log"
                }
            }
        }
    },

从这个配置里我们可以看到是,直接关联到了logging.FileaHandler这个类,就是直接写了文件。

实际上我们可以根据Opencanry提供的Exampler.py的模式写一个模块来扩展蜜罐的功能,一样可以在opencanary.conf中引用,这种模板是基于创建监听的。

下面是模块编写的模板。

代码语言:javascript复制
from opencanary.modules import CanaryService
from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.application import internet
class Example0Protocol(Protocol):
"""
Example (Fictional) Protocol
$ nc localhost 8007
Welcome!
password: wrong0
password: wrong1
password: wrong2
Bad passwords
$
"""
def __init__(self):
self.prompts = 0
def connectionMade(self):
self.transport.write("Welcome!rnpassword: ")
self.prompts  = 1
def dataReceived(self, data):
"""
Careful, data recieved here is unbuffered. See example1
for how this can be better handled.
"""
password = data.strip("rn")
logdata = {"PASSWORD" : password}
self.factory.log(logdata, transport=self.transport)
if self.prompts < 3:
self.transport.write("rnpassword: ")
self.prompts  = 1
else:
self.transport.write("rnBad passwordsrn")
self.transport.loseConnection()
class CanaryExample0(Factory, CanaryService):
NAME = 'example0'
protocol = Example0Protocol
def __init__(self, config=None, logger=None):
CanaryService.__init__(self, config, logger)
self.port = 8007
self.logtype = logger.LOG_BASE_EXAMPLE
CanaryServiceFactory = CanaryExample0

还有一种方式,我们在logger文件中直接加一个handler处理,把FileHandler变成syslog的SyslogHandler。

代码语言:javascript复制
class DemoHandler(logging.Handler):
    def __init__(self, demo_userid, demo_authkey, allowed_ports):
        logging.Handler.__init__(self)
        self.demo_userid = str(demo_userid)
        self.demo_authkey = str(demo_authkey)
        try:
            # Extract the list of allowed ports
            self.allowed_ports = map(int, str(allowed_ports).split(','))
        except:
            # By default, report only port 22
            self.allowed_ports = [ 22 ]

    def emit(self, record):
        ...

再修改opencanary.conf配置文件。

代码语言:javascript复制
"logger": {
    "class" : "PyLogger",
    "kwargs" : {
        "formatters": {
            "plain": {
                "format": "%(message)s"
            }
        },
        "handlers": {
            "dshield": {
                "class": "opencanary.logger.DemoHandler",
                "demo_userid": "test",
                "demo_authkey": "$%$##$#%$%",
                "allowed_ports": "22,23"
            }
        }
    }
}

syslog协议是一种可以基于UDP协议进行数据传输的协议,在早期数据野蛮生长的阶段,我们创建很多基于syslog的服务数据收集,因为一般的安全设备都支持syslog数据的发送。

后期ELK是一种最常见的数据管理,包括Graylog和Clickhosue这些数据管理手段,都可以很好的集中管理数据,但是这是一种粗放的日志数据管理,还没有进入数据深加工的阶段。

0x06 蜜罐与威胁情报

对于内网蜜罐来说,很多时候的攻击IP可能是内网的IP, 并不都是外网IP,所有在与威胁情报库进行比对的时候,查不到相关的数据。但是内网IP会与其它的设备发生关联,在其它的一些网络防护设备上,我们可能看到相关的内网IP发生的其它报警行为。

如果我们将所有防御设备上的交通流量,直接进入关联,那将是一个海量数据的比较,这种情况下, 为了快速的索引数据,我们可能要用ClickHouse这种级别的数据据库产品,在初期阶段,我们相对大量的收集了日志数据,交通数据和报警数据都有,但问题是数据操作起来不灵活。

随着业务数据不断的增长,这种方式不一定适合我们新数据分析场景,我们用一个IP与海量的日志数据,进行比对,是一个很耗费性能资源和时间的事情,所以我们考虑,在原始数据日志之上,建立新一层日数据汇聚层,将有用的数据进行深入的加工和汇聚,我们的安全策略不再直接基于海量的原始日志数据,而是建立在更精准的数据汇聚层的,简单说这些数据是进过整理、整形、去重、提纯的数据,数据之间存在更强的关联,并且是更精巧的数据集。

我们原始的日志数据,基于REST API,这样服务之间的通信友好,不需要特定客户端协议的依赖,只要支持 REST就可以。中间层的数据是对原始数据的精括聚合,我们使用RPC的方式进行数据的查询,我们可以将业务模块的数据,变成像普通函数一样的RPC调用。

对于中间的数据服务来说,无论是RPC还是REST,其实都可以实现我们数据划分的目的,技术手段多种多样,但本质上的问题,某些场景下,用空间换时间, 或是时间换空间。

对于蜜罐报警,我们可以作为一个事件发现的起始点,也是很重要的一点时间点,攻击进行时,因为内网蜜罐的报警,一般情况下是不应该出现高频率的报警的, 所以,实时推送报警信息,在并发生性能上没有太大的压力,直接在可以威胁发生的时间点就报警,异步进行关联分析。

如果是采用报警写数据库,再用有时间间隔的定时计划任务去轮训就会有时间延迟,这也是我们想直接改造opencanary的源代码的一个原因,当然这个问题产生本身和opencanary没关系。

0x07 蜜罐的集中管理

一般在交换机上支持端口复用的环境下,都可以在一台机器上,配置多个IP,opencanary提供了一个报警蜜罐的实现解决方案,但是更多的时候是提供了一个agent,场景多用于内网蜜罐的部署。修改配置文件中的server.ip,对应多个指定的IP。

企业都有很多的机房,所以蜜罐的结点管理就是一个课题,国人在Opencanay的基础上开发社区版的后面管理界面:opencanary web。

这个图上的agent就是opencanary, 报警的方式还是传统的邮件,这里就存一个问题,这个邮件告警是集中后的后发出,从图上没有看出是在agent上直接发出的,除非在写入数据库时就报警,这样是第一时间进行的报警,另外大家可以将邮件告警,增加到用公众号报警和syslog告警。

系统实现基于nginx和tornadoweb,数据库系统使用的是mysql,前端口使用的是vue, 具体的安装方法大家可以直接看github。

Opencanary支持Hpfeeds,还记得那个客户端吗:

代码语言:javascript复制
hpfeeds-client --host localhost -p 10000 -i honeymap -s cfdd6a68be69464666ae60b66dae69f6 -c geoloc.events publish "{countrycode:'NA', latitude:37.7749, longitude:-122.4194, city:'San Francisco'}"

这个Opencanary也是支持和hpfeeds进行交互的,但是Opencanary用于内网部署,很多IP都是内网IP,不太容易看出地图炮效果。有的IP可以看到在国内的位置地理信息,和CMDB系统关联信息。

有没有一种方法不用WEB管理端也能聚合报警日志呢,其实可以的,用本文的扩展方法,直接将报警数据,第一时间转存到Graylog上,用Graylog管理ES集群,直接在Graylog加查询,聚合,还有报警功能,如果你们公司,有spluk,用splunk也可以做到,总是有很多的方案都可以达到您的目的,但是基于数据关联的策略,还是需要本地中间层数据聚合。

最直接的蜜罐探活监听就是fping,最简单,间隔判断这些机器的网络是否通,zabbix也可以监控到服务,但是需要对zabbix加白名单。

如果使用Graylog或是splunk这种系统,地图信息管理IP和地图炮功能都是有的,但是一篇肯定是写不完了,不介绍可自行实验。

0x08 总结:

Opencanary是一款适合内网使用的蜜罐系统,部署简单,配置容易,扩展方便,但在模拟服务的数据量上可能还不是最全的,我们旨在通过一个蜜罐的例子,来串起整个系统,从感知Agent到威胁报警日志数据汇聚的过程。Opencanary有很大的扩展潜能,因为Opencanary本身的易的扩展性,简单性,只要自己动手丰衣足食,进行大型扩展也要看增加的特性量。Opencanary用于内网蜜罐还是可以满足常用需求的。

传统的管理后台,是一种结点监控和数据日志汇总的角色,再加上报警处理等,然而威胁发现的确认逻辑,是要将所以尽可能取得的关联信息进行策略的过滤匹配,而传统系统如果只是报警存储,威胁的可视范围就局限于自己的威胁发现区域,特别是这种陷阱式的蜜罐。变成威胁信息孤岛,不如将蜜罐作为检查威胁事件的触发者,事件起始点,一旦蜜罐发生报警,将蜜罐与提前准备好的关联威胁数据视图,按内部安全逻辑进行交互,得出更精确的判断,效果要好于白名单的严格和黑名单的粗放控制。

*本文原创作者:糖果L5Q,本文属FreeBuf原创奖励计划,未经许可禁止转载

0 人点赞