自制攻击欺骗防御系统
部门每周三例行的技术分享投稿文章,首发在小米安全中心的公众号
自制攻击欺骗防御系统概述
峥嵘栋梁,一旦而摧;水月镜像,无心来去。
本次自制的欺骗防御系统名字叫镜花水月,镜花水月是《死神》中蓝染惣右介的斩魄刀(幻觉系最强斩魄刀)的名字,能力是完全支配对手的五感。
这个名字非常适合攻击欺骗防御类系统:把攻击者的流量从正常的请求中区分、剥离出来,并无缝地转移到伪造了正常业务和服务的沙盒中,让攻击者去沙盒环境中玩耍并会记录其详细的行为。
与传统的蜜罐相比,镜花水月的优点为:
零误报(只对内部或外部的攻击行为进行报警); 保护业务系统(第一时间会将攻击者从正常的服务中转移到沙箱环境中,攻击者却浑然不知); 方便取证及定位攻击者(沙盒中会记录攻击者的来源及详细的攻击行为)。技术架构
镜花水月由4个模块组成:
Agent,部署于服务器中的Agent,用于实时获取用户的访问日志并传递到检测端Server中,如果是恶意攻击,则会将流量重定向到沙盒中。目前支持的服务有: WEB FTP SSH Rsync Mysql Redis Mongodb Server,攻击检测服务器,实时检测Agent传递过来的日志并判断是否为攻击者,并为Agent动态、实时地维护了一份攻击者的来源IP策略; Manager,策略管理服务器,有为Agent和server提供策略、攻击log统计、查看的功能 沙盒由以下3部分构成: 安装了FTP、SSH、Rsync、Mysql、Redis和Mongodb的虚拟机或Docker,可实时将这些服务的访问log发送到远程的Rsyslog中 克隆的WEB站点 日志服务器,将沙盒通过Rsyslog发送过来的数据解析出来并入库,供Manager查询、分析为了方便部署以及高性能的要求,镜花水月的各组件中除了WEB攻击检测模块是用lua开发的外,其余的全是用go语言开发的(WEB攻击检测模块是基于openresty+lua开发的)。
Agent的实现
agent的架构
Agent支持以蜜罐、镜花水月、攻击反弹和防火墙4种模式运行。
$ ./client -h
Usage of ./client:
-c string
command, such as start or stop policy (default "start")
-m string
running mode, honeypot, unreal, back or firewall mode (default "honeypot")
这四种模式的区别及功能分别如下:
启动时会应用不同的策略: 蜜罐会将除了白名单IP和端口外的所有流量直接转向沙盒中; 镜花水月只将被Server判定为恶意攻击者转向沙盒,不影响正常的请求(攻击欺骗防御模式); 反弹模式是将攻击者的流量全部原封不动的返回,类似于金庸小说《天龙八部》中的斗转星移; 防火墙模式是将识别出来的攻击者的IP Block掉。 以镜花水月、攻击反弹和防火墙模式启动时,除了会应用从manager获取到的策略外,还会在Agent中启动4个服务监控的goroutine,这些goroutine会将收集到的Vsftpd、Rsync、mysql和Redis服务的日志实时发到Server端。Agent的整体工作流如下:
判断启动模式,如果启动参数为unreal,则以镜花水月模式启动,默认以蜜罐模式启动; 从配置文件conf/app.ini读取配置参数,如果是蜜罐模式,按配置文件中指定的时间间隔,定期从Manager获取策略并应用,如果是镜花水月模式,则每10秒更新一次策略,并启动Vsftpd、Rsync、mysql和Redis的监控goroutine关于蜜罐的详细实现,可以参考笔者之前的文章自制蜜罐之前端部分,https://xsec.io/2016/7/8/how-to-develop-a-honeypot.html,本文只贴一些关键的部分代码。
项目的代码结构如下图所示:
Agent的日志配置
Agent是通过rsyslog向server实时汇报采集到的非WEB应用程序的数据的(web的攻击检测会在后面说明),在部署agent前需要做以下3步的配置:
配置服务器中运行的应用的日志参数,将能写syslog的写入到syslog中,不能写syslog的写入到指定的文件中 将写入文件中的log读取出来,转换成syslog格式再转存到本地的syslog中 配置rsyslog的规则,将我们需要分析的log转发到后端server中,rsyslog的转发策略如下图所示:ssh和mongodb的日志可以写入到syslog中,直接配置rsyslog的转发策略即可。
redis的操作内容不支持写入syslog,需要先连接redis,然后利用monitor命令监控redis指令的执行,经测试无法监控到config指令,而且启用monitor后,性能会降低一半。 所以redis的指令监控服务不适合部署在对性能要求很高的服务器中。
Vsftpd、Rsync和Mysql服务需要配置应用对日志的支持,以下为关键的配置项:
// VsftpdLog的配置
xferlog_enable=YES
xferlog_file=/var/log/xferlog
xferlog_std_format=YES
dual_log_enable=YES
syslog_enable=YES
log_ftp_protocol=YES
// Rsync的配置
$ cat /etc/rsyncd.conf
log file = /var/log/rsyncd.log
// mysql的配置
[mysqld]
bind-address = 127.0.0.1
general-log-file = /var/log/mysqld.log
general_log = 1
把Vsftpd、Rsync和mysql的日志转存为syslog方法一样,都是不断地Tail文件,并把新增的日志发送到syslog中,代码如下:
// "/var/log/vsftpd.log"
func MonitorVsftpd(logName string) {
t, err := tail.TailFile(logName, tail.Config{Follow: true})
if err == nil {
for line := range t.Lines {
l3, err := syslog.New(syslog.LOG_ERR, "vsftpd-server")
defer l3.Close()
if err != nil {
log.Fatal("error")
}
l3.Info(line.Text)
fmt.Println(line.Text)
}
}
}
以下为将redis的monitor指令的结果转存为syslog的代码:
func MonitorRedis(host, port, password string) {
flag.Parse()
conn, err := net.Dial("tcp", host+":"+port)
if err == nil {
conn.Write([]byte(fmt.Sprintf("auth %v\r\n", password)))
conn.Write([]byte("monitor \r\n"))
defer conn.Close()
ch := make(chan []byte)
eCh := make(chan error)
l3, err := syslog.New(syslog.LOG_ERR, "redis-server")
defer l3.Close()
if err != nil {
log.Fatal("error")
}
go func(ch chan []byte, eCh chan error) {
for {
// try to read the data
data := make([]byte, 512)
_, err := conn.Read(data)
if err != nil {
eCh <- err
return
}
ch <- data
}
}(ch, eCh)
ticker := time.Tick(time.Second)
for {
select {
case data := <-ch:
n := bytes.IndexByte(data, 0)
s := string(data[:n])
d := strings.Replace(s, "\r\n", "", -1)
l3.Warning(d)
case err := <-eCh:
fmt.Println(err)
break
case <-ticker:
}
}
}
}
Agent的编码实现
启动模式的处理部分代码如下:
// start honeypot
func StartHoneypot(mode string) {
for {
p, err := GetPolicy()
log.Println(p, err)
if err == nil {
run(p, mode)
}
if strings.ToLower(mode) == "honeypot" {
time.Sleep(time.Second * time.Duration(setting.Interval))
} else {
time.Sleep(time.Second * 10)
}
}
}
// start agent
func Start(mode string) {
if strings.ToLower(mode) != "honeypot" {
go util.MonitorVsftpd(setting.VsftpdLog)
go util.MonitorRsync(setting.RsyncLog)
go util.MonitorMysql(setting.MysqlLog)
go util.MonitorRedis(setting.RedisHost, setting.RedisPort, setting.RedisPass)
}
StartHoneypot(mode)
}
不同模式会应用不同的iptables的策略,部分代码如下:
// set iptables
func SetIptables(policy Policy, mode string) {
if strings.ToLower(mode) == "honeypot" {
whiteIpPolicy := policy.WhiteIp
// set white policy
for _, whiteIp := range whiteIpPolicy {
fmt.Println("/sbin/iptables", "-t", "filter", "-A", "WHITELIST", "-i", setting.Interface, "-s", whiteIp, "-j", "DROP")
exec.Command("/sbin/iptables", "-t", "filter", "-A", "WHITELIST", "-i", setting.Interface, "-s", whiteIp, "-j", "DROP").Output()
}
fmt.Println("/sbin/iptables", "-t", "nat", "-A", "HONEYPOT", "-i", setting.Interface, "-p", "tcp", "-m", "multiport", "!", "--dports", strings.Join(policy.WhitePort, ","), "-j", "DNAT", "--to-destination", policy.Backend)
ret, err := exec.Command("/sbin/iptables", "-t", "nat", "-A", "HONEYPOT", "-i", setting.Interface, "-p", "tcp", "-m", "multiport", "!", "--dports", strings.Join(policy.WhitePort, ","), "-j", "DNAT", "--to-destination", policy.Backend).Output()
fmt.Println(ret, err)
} else if strings.ToLower(mode) == "unreal" {
blackIps := policy.BlackIp
for _, blackIp := range blackIps {
if strings.TrimSpace(blackIp) != "127.0.0.1" {
fmt.Println("/sbin/iptables", "-t", "nat", "-A", "HONEYPOT", "-i", setting.Interface, "-p", "tcp", "-m", "multiport", "!", "--dports", strings.Join(policy.WhitePort, ","), "-s", blackIp, "-j", "DNAT", "--to-destination", policy.Backend)
ret, err := exec.Command("/sbin/iptables", "-t", "nat", "-A", "HONEYPOT", "-i", setting.Interface, "-p", "tcp", "-m", "multiport", "!", "--dports", strings.Join(policy.WhitePort, ","), "-s", blackIp, "-j", "DNAT", "--to-destination", policy.Backend).Output()
fmt.Println(ret, err)
}
}
} else if strings.ToLower(mode) == "back" {
blackIps := policy.BlackIp
for _, blackIp := range blackIps {
if strings.TrimSpace(blackIp) != "127.0.0.1" {
fmt.Println("/sbin/iptables", "-t", "nat", "-A", "HONEYPOT", "-i", setting.Interface, "-p", "tcp", "-m", "multiport", "!", "--dports", strings.Join(policy.WhitePort, ","), "-s", blackIp, "-j", "DNAT", "--to-destination", blackIp)
ret, err := exec.Command("/sbin/iptables", "-t", "nat", "-A", "HONEYPOT", "-i", setting.Interface, "-p", "tcp", "-m", "multiport", "!", "--dports", strings.Join(policy.WhitePort, ","), "-s", blackIp, "-j", "DNAT", "--to-destination", blackIp).Output()
fmt.Println(ret, err)
}
}
} else if strings.ToLower(mode) == "firewall" {
blackIps := policy.BlackIp
for _, blackIp := range blackIps {
fmt.Println("/sbin/iptables", "-t", "filter", "-A", "WHITELIST", "-i", setting.Interface, "-s", blackIp, "-j", "DROP")
exec.Command("/sbin/iptables", "-t", "filter", "-A", "WHITELIST", "-i", setting.Interface, "-s", blackIp, "-j", "DROP").Output()
}
}
exec.Command("/sbin/iptables", "-t", "nat", "-A", "POSTROUTING", "-o", setting.Interface, "-j", "MASQUERADE").Output()
}
WEB服务的攻击检测
WEB服务器的攻击检测是利用反向代理型的waf实现的,关于waf的自制,可以参考笔者之前的文章《中小企业如何自建免费的云waf》,https://xsec.io/2016/8/23/how-to-develop-a-free-cloud-waf.html
WAF在检测到攻击后有3种处理模式: 1. 跳转到指定的URL 1. 输出自定义的HTML内容 1. 镜花水月模式,攻击者的请求不再反代到正常的后端,而是反代到克隆的WEB业务后端,克隆的WEB站点可以伪造得和真实站点相同,并打开了详细的log,如access log、Debug log和orm log等,连接的是脱敏的测试数据库。
waf检测到攻击后,开启镜花水月模式的处理代码如下:
-- WAF response
function _M.waf_output()
if config.config_waf_model == "redirect" then
ngx.redirect(config.config_waf_redirect_url, 301)
elseif config.config_waf_model == "jinghuashuiyue" then
local bad_guy_ip = _M.get_client_ip()
_M.set_bad_guys(bad_guy_ip, config.config_expire_time)
else
ngx.header.content_type = "text/html"
ngx.status = ngx.HTTP_FORBIDDEN
ngx.say(string.format(config.config_output_html, _M.get_client_ip()))
ngx.exit(ngx.status)
end
end
-- set bad guys ip to ngx.shared dict
function _M.set_bad_guys(bad_guy_ip, expire_time)
local badGuys = ngx.shared.badGuys
local req, _ = badGuys:get(bad_guy_ip)
if req then
badGuys:incr(bad_guy_ip, 1)
else
badGuys:set(bad_guy_ip, 1, expire_time)
end
end
在利用waf-admin添加需要保护的后端站点时,需要增加2种后端的地址:
正常的后端地址 克隆业务的后端地址如下图所示(请不要在意安全后端工程师做的UI):
waf管理后台在生成新站点的配置文件时后,会在每个请求中都会调用waf.start_jingshuishuiyue()函数,waf.start_jingshuishuiyue()会判断用户的类型是好人或坏蛋,如果是坏蛋的话,直接转到克隆的后端中去。
以下为waf.start_jingshuishuiyue()的代码:
function _M.start_jingshuishuiyue()
local host = util.get_server_host()
ngx.var.target = "proxy_"
if host and _M.bad_guy_check() then
ngx.var.target = "unreal_"
end
end
以下为waf管理端在录入新的网站并为其生成配置文件时的模板文件内容:
upstream proxy_{{.site.SiteName}} { {{range .site.BackendAddr}}
server {{.}} max_fails=3 fail_timeout=20s;
{{end}} }
upstream unreal_{{.site.SiteName}} { {{range .site.UnrealAddr}}
server {{.}} max_fails=3 fail_timeout=20s;
{{end}} }
server {
listen {{.site.Port}};
ssl {{.site.Ssl}};
server_name {{.site.SiteName}};
client_max_body_size 100m;
charset utf-8;
access_log /var/log/nginx/{{.site.SiteName}}-access.log;
error_log /var/log/nginx/{{.site.SiteName}}-debug.log {{.site.DebugLevel}};
location ~* ^/ {
access_by_lua 'waf.start_jingshuishuiyue()';
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_pass $scheme://${target}{{.site.SiteName}};
}
error_page 404 /index.html;
error_page 500 502 503 504 /index.html;
}
Server的实现
Server的架构
server的主要功能是在TCP和UDP的514端口上启动Rsyslog服务,接收各个Agent发来的Rsyslog数据并判断Agent的启动类型、各服务是否被攻击。
如果收到的是蜜罐模式传过来的数据,则会根据规则进行报警; 如果收到的是镜花水月模式传递过来的数据,则会检测是否为攻击,攻击了几次,是否需要对该攻击者发动镜花水月,如果需要发动的话,则会实时修改manager中的策略,Agent会在几秒后拿到新策略,将攻击者转移到沙盒中。Server端支持水平扩展,只要能连接到邮件服务器,后端的mongodb与Redis即可,关于蜜罐的实现部分请参考笔者之前的文章,本文只写攻击检测部分。
server端的项目结构如图所示:
如上图所示,如果不是蜜罐的日志,则会判断应用日志的类型,并调用相应的威胁检测模块,检测的方法都是通过正则匹配中攻击的特征,比如暴破密码、rsync列目录、上传、下载文件等。然后将攻击者的IP,攻击次数记录到manger的Redis中。
以下为VSFTP密码暴力破解的检测示例:
package check
import (
"fmt"
"regexp"
"strings"
"time"
"gopkg.in/redis.v3"
"xsec-honeypot/server/setting"
)
func CheckvsFTP(sysLog map[string]interface{}, redisClient *redis.Client) {
content, _ := sysLog["content"].(string)
client, _ := sysLog["client"].(string)
hostname, _ := sysLog["hostname"].(string)
re, _ := regexp.Compile(`.* \[pid .*] \[(.+?)\] FTP response: Client "(.+?)", "530 .*"`)
ret := re.FindStringSubmatch(content)
if len(ret) > 0 {
src := ret[2]
slice_dst := strings.Split(client, ":")
dst := client
if len(slice_dst) > 0 {
dst = slice_dst[0]
}
fmt.Printf("Src:%v, User:%v, Client:%v, NodeName:%v\n", src, ret[1], client, hostname)
k := fmt.Sprintf("app-%v-%v-%v", src, dst, dst)
bRet, _ := redisClient.Exists(k).Result()
if bRet {
redisClient.HIncrBy(k, "times", 1)
} else {
redisClient.HSet(k, "times", "1")
redisClient.Expire(k, time.Duration(setting.AlarmRecoveryTime)*time.Minute)
}
}
}
Manager的实现
Manager的架构
Manager是整套系统的管理后台,支持的功能有:
用户管理 Agent资产管理 蜜罐策略管理 攻击日志、报警日志展示 为Agent和server提供策略拉取服务Manager的代码结构如下:
Manager的代码实现
从manager获取策略时,需要进行认证,防止攻击者无意间获取到蜜罐或镜花水月的所有策略后,进行针对性的避开去攻击其他系统。
获取策略的api接口只支持post方法:
m.Group("/api/", func() {
m.Post("/policy/", routers.PolicyJSON)
})
Agent和Server配置文件中的key必须与manager中的一致,否则会因认证失败无法获取到策略,如下代码所示:
func PolicyJSON(ctx *macaron.Context) {
ctx.Req.ParseForm()
timestamp := ctx.Req.Form.Get("timestamp")
secureKey := ctx.Req.Form.Get("secureKey")
log.Println(timestamp, secureKey)
mySecureKey := util.MakeMd5(fmt.Sprintf("%v%v", timestamp, setting.AppKey))
ret := models.APIData{}
if mySecureKey == secureKey {
policy, _ := models.ListPolicy()
ret = models.APIData{Code: 0, Data: map[string]interface{}{"message": "Get policy successful",
"value": policy}}
} else {
ret = models.APIData{Code: 1, Data: map[string]interface{}{"message": "Api signature verification failed",
"value": make([]string, 0)}}
}
ctx.JSON(200, ret)
}
每次获取策略的接口时,Manager会从redis中查出所有满足策略的攻击者的IP,然后返回给Agent,Agent便可以对这些攻击者的IP发动镜花水月模式了。
func ListPolicy() (policy []Policy, err error) {
err = collPolicy.Find(nil).All(&policy)
blackIp, err1 := ListRealTimePolicy()
fmt.Println(blackIp, err1)
if err == nil && err1 == nil {
if len(policy) > 0 {
policy[0].BlackIp = blackIp
}
}
return policy, err
}
func ListRealTimePolicy() (blackIp []string, err error) {
ret, err := RedisClient.Keys("app-*-*-*").Result()
// fmt.Printf("%v,%v", ret, ret)
for _, item := range ret {
v := strings.Split(item, "-")
fmt.Println(v, len(v))
if len(v) > 0 {
attacker := Attacker{}
attacker.Src = v[1]
attacker.Dst = v[2]
attacker.Client = v[3]
strTimes, _ := RedisClient.HGet(item, "times").Result()
times, _ := strconv.Atoi(strTimes)
if times > AuthFailuresTimes {
blackIp = append(blackIp, attacker.Src)
}
}
}
return blackIp, err
}
以上代码中,ListRealTimePolicy()
表示从redis中筛选出攻击者的IP,并由ListPolicy()
方法一起返回蜜罐的策略。
沙盒的实现
沙盒可以装在虚拟机中或者封装到docker中,沙盒产生的数据的记录方式有以下2种:
利用tcpdump在宿主机中抓出相应协议的包,然后利用dpkt解析pcap文件,然后存入manager的DB中 在沙盒中部署类似Agent中的日志分析脚本,并将结果发送到rsyslog中,然后再由rsyslog转发到我们的server中,server收到后再存入manager中的DB中文章来源:
Author:记录人生旅程中的迷茫与探索
link:http://www.xsec.io/2016/11/2/how-to-develop-a-unreal.html