9 21

跨平台的安全检测工具包

概述

xsec-checker是一款服务器安全检测的辅助工具,由GO语言开发,天生支持跨平台,同一份代码可以交叉编译出linux与windows平台下的可执行文件,且无任何库的依赖。

关于跨平台说点题外话,笔者工作前7,8年一直在游戏行业,从安全、运维及开发都有涉猎,记得移动互联网兴起时,许多手机游戏都是先只支持iphone平台,后来才慢慢支持Android平台的,原因是同一款游戏的客户端,需要2个团队,一个team用objective-c开发iphone版的,另一个team用java再重写一版android平台的,到了后期的维护成本及复杂度可想而知。

当时国内最流行的手游开发框架是cocos2d-iphone,是objective-c实现的,后来王哲推出了c++版本的cocos2d-x,只写一套c++版本的代码就可以交叉编译出多种平台的客户端,极大地提高了手机游戏开发的效率。

但业内马上又出现了新的难题,因C++语言难以驾驭,靠谱的C++非常不好招而且人员成本很高,后来cocos2d-x又推出了lua与js的绑定版本,这样的话,在一个研发Team中,只需极少的C++大神就可以搭建好底层框架,具体的业务与逻辑代码,能快速上手lua与js的新手就可以做了,甚至连策划都可以上手,直接写游戏逻辑代码验证自己的设计了,减少沟通与在验证玩法时反反复复让研发修改代码的成本。

目前安全界流行使用python,笔者建议在有高性能要求、跨平台部署、无外部依赖、部署方便、源码加密等要求的场景下使用go语言,go同python一样,也是种全栈的语言。

目前实现的功能如下所示:

项目地址:https://github.com/netxfly/sec_check

8 31

应急响应浅谈

应急响应浅谈

应急响应及应急响应中心

应急响应是安全从业者最常见的工作之一(系统被黑后紧急救火,PDR模型-防护、检测、响应中的三大模块之一)。很多人可能认为应急响应就是发现服务器被黑之后,登录上去查后门的那段过程。 其实应急响应的完整定义为:组织为了应对突发/重大信息安全事件的发生所做的准备,以及在事件发生后所采取的措施

通俗地讲,应急响应不应该只包括救火,还应包括救火前的一系列准备。如果在工作中忽略了准备部分,可能会出现以下几种情况:

  1. 不具备基本的入侵检测能力,平时检测不到入侵事件,更谈不上应急响应了。有可能被入侵成功很久了却浑然不知,攻击者可能早就在达成目标后悄然离去了;
  2. 能检测到入侵事件,但没有专门的应急响应小组,资产管理系统也不完善,安全工程师花了很长时间才找到对应的负责人,因进入响应时间太晚,攻击者可能在达到目标并擦除痕迹后全身而退了,或者进一步把其他关联的系统一并拿下了;
  3. 平时无应急响应技能及入侵检测工具包的积累,接到事件的工程师登录到服务器上绞尽脑汁敲了几行命令后,最后得出『经排查安全的结论』给部门Leader与业务部门了,但真实情况是真被入侵并植入后门了。

现在各大厂商都成立了相应的安全应急响应中心(SRC),用来接收外部白帽子的提交的漏洞与威胁情报,虽然叫应急响应中心,但是这里提交过来的漏洞与情报不需要每次都启动应急响应,需要根据漏洞的类型、危害级别判断。

安全应急响应中心是对自己安全团队所做的安全保障工作的补充,如果SRC发现的漏洞与入侵事件比例很高,安全团队就该好好反思下安全工作为啥只治标不治本、频繁地被动救火了。

指导原则及方法论

应急响应是既紧急又重要的工作,对工程师的技术与意识都有一定的要求,比如很多安全工程师接到业务系统被黑的情报后,可能会联系业务负责人要到服务器账户,然后登录到服务器中检查被渗透的痕迹与后门。这段时间非常宝贵,反映太慢可能会使一些本来可以快速平息的安全小事件发酵成造成重大的损失安全事故。

  • 对于应急响应,首先要了解应急响应的指导原则与方法论,只关注技术的话,可能会本末倒置。

    因信息安全事件的种类和严重程度各有不同,应急响应的处理方式也各不相同,比如DDOS、业务系统被入侵、钓鱼邮件的应急响应方式与过程肯定是不同的,被业内广为接受的应急响应模型与方法论有PDCERF模型与ITIL中的事件管理问题管理模块。

  • 其次要求应急人员有较高的入侵检测能力,否则在排查被入侵的系统时,上去查了半天啥也发现不了,最后给出的结论是安全的。

    笔者在第一份工作时,部门老大要求在进行代码审计与应急响应等依赖人员技术和经验的工作时,必须采用双人Check机制,最后汇总对比结果,防止遗漏。 入侵检测需要检测的项目很多,最好能整理出相应的自动化检测工具自动给出报告,这样不但可以提高工作效率,还可以弱化对应急响应人员技术水平的依赖。

PDCERF模型

8 28

用Docker制作一个高交互ssh蜜罐

概述

实现一个高交互的SSH蜜罐有方式有多种:

  • 可以使用现成的开源系统,如kippocowrie等,
  • 可以利用一些SSH库做2次开发,如https://github.com/gliderlabs/ssh
  • 也可以用Docker定制

市面上已经有这么多开源的SSH蜜罐了,我为什么还要再造个轮子呢,理由如下:

  • 部署这些系统要安装一堆的依赖,运维部署成本较高
  • 想要的功能没法添加,不想要的功能也没法删减
  • 如果要支持多种高交互的服务,必须用一堆开源的系统拼凑出一堆服务来,每种系统的后端DB不同,数据结构也各不相同,无法统一处理
  • 自研的系统部署简单,服务可自由扩展,数据格式可自由定制和统一,方便运维与运营

技术架构

笔者在之前的文章《自制攻击欺骗防御系统》中介绍过完整的架构,整个系统由以下几个模块组成:

  • Agent端,部署于服务器中的Agent,用于实时获取用户的访问日志并传递到检测端Server中,如果是恶意攻击,则会将流量重定向到沙盒中。目前支持的服务有:

    • WEB
    • FTP
    • SSH
    • Rsync
    • Mysql
    • Redis
    • Mongodb
  • Server端,攻击检测服务器,实时检测Agent传递过来的日志并判断是否为攻击者,并为Agent动态、实时地维护了一份攻击者的来源IP策略

  • Mamager端,策略管理服务器,有为Agent和server提供策略、攻击log统计、查看的功能

  • 高交互蜜罐系统及守护进程,高交互蜜罐系统由docker封装的一些服务组成,守护进程负责把仿真系统中产生的LOG的数据格式化后再传给Server端进行攻击检测与入库

本文只讲SSH高交互蜜罐的实现。

5 17

「技术随笔」iptables报too many ports specified的解决

背景

笔者写的蜜罐的agent底层依赖iptables,在设置高交互蜜罐的端口转发规则时会用到iptables的multiport --dports指令,但是超过--dports超过15个的话,会报too many ports specified错误:

iptables -A INPUT -p tcp -m multiport --dports 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
iptables -A INPUT -p tcp -m multiport --dports 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
iptables v1.4.21: too many ports specified
Try `iptables -h' or 'iptables --help' for more information.

关于蜜罐与攻击欺骗防御系统的设计与实现,可以参考笔者之前写过的文章。

  1. 设计灵敏的蜜罐传感器
  2. 自制攻击欺骗防御系统
  3. 自制蜜罐之前端部分

解决方案

投石问路

蜜罐的规则在运营过程是肯定会超过15个,不可能为了规避iptables的这个特性就缩小规则列表。 IPTABLES报这个错误的根本原因是iptables的源码中include/linux/netfilter/xt_multiport.h的宏XT_MULTI_PORTS指定了参数个数为15个,如下所示:

#ifndef _XT_MULTIPORT_H
#define _XT_MULTIPORT_H

#include <linux/types.h>c

enum xt_multiport_flags {
    XT_MULTIPORT_SOURCE,
    XT_MULTIPORT_DESTINATION,
    XT_MULTIPORT_EITHER
};

#define XT_MULTI_PORTS    15

很傻很天真的我最初认为把这个将XT_MULTI_PORTS的值改大重新编译iptables就可以了,事实证明我还是太年青了。等编译完后一执行又报错了,提示让看dmesg,发现如下错误:

[ 1379.325905] x_tables: ip_tables: multiport.1 match: invalid size 48 (kernel) != (user) 456
[ 1650.126296] x_tables: ip_tables: multiport.1 match: invalid size 48 (kernel) != (user) 304

以上2条LOG分别是将XT_MULTI_PORTS改为150和100产生的。为什么捏?

通过观察include/linux/netfilter/xt_multiport.h的代码片断,确定为正好是以下struct中XT_MULTI_PORTS分别为150和100的size。

struct xt_multiport_v1 {
    __u8 flags;                /* Type of comparison */
    __u8 count;                /* Number of ports */
    __u16 ports[XT_MULTI_PORTS];    /* Ports */
    __u8 pflags[XT_MULTI_PORTS];    /* Port flags */
    __u8 invert;            /* Invert flag */
};

这个时候我才恍然大悟,本来iptables就是netfilter的用户接口,最终的操作结果是传到内核级模块netfilter中的,还需要修内核中netfilter模块相对应的代码部分,经确定在以下文件中include/uapi/linux/netfilter/xt_multiport.h,修改完还要重新编译内核。这个方案比较麻烦,先PASS了,还是在agent中实现吧。

柳暗花明

如果一条策略中的端口超过了15个,那我们将策略分成多条即可。先写一个端口数量分割的工具函数:

func SplitWhitePorts(ports []string) (map[int][]string) {
    result := make(map[int][]string)
    total := len(ports)
    batch := 0
    if total%15 == 0 {
        batch = total / 15
        for i := 0; i < batch; i++ {
            result[i] = ports[i*15 : (i+1)*15]
        }
    } else {
        batch = total / 15
        for i := 0; i < batch; i++ {
            result[i] = ports[i*15 : (i+1)*15]
        }
        result[batch] = ports[batch*15 : total]
    }

    return result
}

测试结果满足预期:

[ `go run csrf.go` | done: 561.973248ms ]
    map[0:[1 2 3 4 5 6]]
    map[0:[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]]
    map[0:[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15] 1:[16 17 18 19 20]]

然后在刷新策略部分应用之:

if strings.ToLower(mode) == "honeypot" {
        whiteIpPolicy := vars.HoneypotPolicy.WhiteIp
        // set white policy
        for _, whiteIp := range whiteIpPolicy {
            logger.Log.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()
        }

        for _, ports := range util.SplitWhitePorts(vars.HoneypotPolicy.WhitePort) {
            logger.Log.Println("/sbin/iptables", "-t", "nat", "-A", "HONEYPOT", "-i", setting.Interface,
                "-p", "tcp", "-m", "multiport", "!", "--dports", strings.Join(ports, ","),
                "-j", "DNAT", "--to-destination", vars.HoneypotPolicy.Backend)

            ret, err := exec.Command("/sbin/iptables", "-t", "nat", "-A", "HONEYPOT", "-i", setting.Interface,
                "-p", "tcp", "-m", "multiport", "!", "--dports", strings.Join(ports, ","), "-j",
                "DNAT", "--to-destination", vars.HoneypotPolicy.Backend).Output()
            logger.Log.Println(ret, err)
        }
    } 
Next