Peach原理简介与实战:以Fuzz Web API为例

0×0 此文目的

Fuzz即模糊测试,是一种使用大量的随机数据测试系统安全的方法,Peach就是一种这样的工具。网上零零星星有些介绍Peach的文章,也有少部分使用Peach测试某种文件的教程(其实就是直接翻译官方文档),并没有针对实际协议的真正测试。初学者,往往无从下手,本文从实战出发,穿插讲解Peach套件的一些语法和原理,让你真正从0开始一段奇妙的模糊测试之旅!

0×1 Peach简介

Peach是一个遵守MIT开源许可证的模糊测试框架,首个版本发布于2004年,使用python编写,第二版于2007年发布,被收购后,最新的第三版使用C#重写了整个框架,而且分为社区版和付费版。付费版本拥有更好的扩展功能,便于管理的Web界面,更加智能的建模机制,上手更容易。但是,鉴于广大同胞囊中羞涩,本次当然重点讲解社区版(免费版)。

Peach原理简介与实战:以Fuzz Web API为例

Peach Fuzz其实是一种黑盒测试方法,通过发送大量的随机数据到被测试系统,再结合监视器,检测系统的运行状态,来发现被测试系统或进程中,可能存在的安全问题。英文好的同学,可自行点击Peach官网传送门

0×2 Peach安装

安装其实很简单,对于本次的测试来说,不需要一些花里胡哨的操作(比如,.NET,debug tools),对于Win10来说,更是如此。官方安装指南——看看就好,不必当真。

所以,只需要一步,在官网上下载peach-3.1.124-win-x86-release或者x64社区版本即可;任性的同学也可以下载Linux/Mac的版本。下载之后,解压。为了方便后续的测试,最好将peach的目录,加入到系统的环境变量。

Peach原理简介与实战:以Fuzz Web API为例

0×3 结合Burpsuite对Web API进行fuzz测试

终于到了实战环节,这也是本文的另一个重点内容。这部分从0开始,一步步带你领略Peach的神奇魅力,更高级的功能,需要我们以后共同探索。

0×31 使用Burpsuite抓取需要fuzz的Web接口数据

设置代理,对目标接口进行抓包,这一步我相信大伙都会,不会的同学请自行移步Burpsuite抓包教程,我在这里就不重复造轮子了。

需要fuzz的API接口

Peach原理简介与实战:以Fuzz Web API为例

抓取数据包

Peach原理简介与实战:以Fuzz Web API为例

我们的目的是要将抓取的数据包,转换成数据模型,在此之前,需要先保存该数据包为.bin文件。

全选:Ctrl + A

复制:Ctrl + C

创建文件login.bin,并将数据包拷贝过来并保存:Ctrl + V

Peach原理简介与实战:以Fuzz Web API为例

将文件保存在E:MyPeachPit这里只是指定一个文件夹,方便后面把所有的配置文件都放在一个目录中。你们可随意创建一个目录。

0×32 创建数据模型

创建一个文件,命名为 1-my_data.xml(命名请随意),同样放在E:MyPeachPit。首先是根标签,也就是描述文件,你也可以在属性中加入文件的整体注释信息。

<?xml version="1.0" encoding="utf-8"?>
<Peach xmlns="http://peachfuzzer.com/2012/Peach" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://peachfuzzer.com/2012/Peach ../peach.xsd">

	<DataModel name="my_data_model">
		
	</DataModel>

</Peach>

在<Peach>标签中,创建数据模型<DataModel>,属性name可以随便起个名字,但是后面会用到。数据模型其实就是我们需要的fuzz对象。按照上一节抓取的数据包,一步步来编写这个数据模型,这是定制化fuzz的核心步骤。

数据包的第一行:POST /formLogin.htm HTTP/1.1。请注意这里的空格,编写数据模型一定要一一对应。

Peach原理简介与实战:以Fuzz Web API为例

第一行对应的数据模型如下

Peach原理简介与实战:以Fuzz Web API为例

每个标签都可以起一个名字,为空也是可以的。value是实际的值,token字段用于分隔,表明这是一段用于分隔其他字符的标签。mutable,顾名思义,就是这段标签能否用来变异,也就是说,你到底想不想改变这段数据。HTTP报文头部后面几行数据以此类推,相信大家都会编写了。

温馨提示1:HTTP头部报文和请求体报文之间,存在一个空行,在数据模型的编写中,也是要注意的。

接下来是比较重要的HTTP请求体的内容,对应的数据模型了。常见的请求体主要有两种:XML和JSON。比如说,如果请求体是这样

{"key1":"admin", "key2":"pw123"}

那么对应的数据模型应该是

Peach原理简介与实战:以Fuzz Web API为例

这里有几点注意下:双引号的转移字符是 &quot; 剩下的内容以此内推。同样的,标签可以起名字,也可以为空,但是不能全为 name=“”但是像上图那样会有问题,会提示键重复。请删除 name=“”。

温馨提示2:普通字符和特殊字符最好分开写,不然很容易出错。比如

Peach原理简介与实战:以Fuzz Web API为例

最好编写成

Peach原理简介与实战:以Fuzz Web API为例

温馨提示3:编写数据模型文件很容易出错,最好写一段就配合0×33节的步骤,对数据模型文件进行校验。

0×33 验证数据模型与抓取的数据包bin文件

数据模型文件编写的过程本身就是一项比较繁琐的工作,编写好的文件,也极有可能出错,PeachValidator.exe 应运而生。在编写好数据模型之后,我们需要使用Peach解压后,自带的该工具,验证编写好的数据模型,与抓取的数据包bin文件内容是否一致。

Peach原理简介与实战:以Fuzz Web API为例

如上图所示,点击完刷新按钮之后,如果没有报错,说明数据模型文件编写正确,否则,需要根据错误提示,进行修改。

Peach原理简介与实战:以Fuzz Web API为例

本节所示的两幅图,即PeachValidator.exe 与 notepad++ 配合工作,使得调试数据模型较为方便。

经历过数据模型的定制后,有没有觉得很麻烦?没办法,Peach后期的自动化依赖于前期的定制,所以数据模型的编写正确与否,至关重要。根据不同的API,数据模型一旦编写好之后,后续的步骤大同小异,可以套用。

0×34 创建配置模型

配置模型,顾名思义,是一些全局的配置信息,例如IP地址、端口之类的。你定义好了数据包,总得定义发送目标把。文件命名为2-my.xml.config(命名依然请随意)。

<?xml version="1.0" encoding="utf-8"?>
<PitDefines>
	<All>
		<IPv4 key="TargetAddress"
			  value="192.168.10.1" />
			  
		<Range key="TargetPort"
			   value="80"
			   min="0"
			   max="65535" />
			   
		<Range key="Timeout"
			   value="5000"
			   min="0"
			   max="999999" />
		<!--
		还可以定义一些配置信息,具体可参考官方手册
		!-->
	</All>

</PitDefines>

</Peach>

0×35 创建状态模型

状态文件,主要包括input和output两个动作。编写状态模型文件如下,并命名为3-my_state.xml

Peach原理简介与实战:以Fuzz Web API为例

input:从Publisher即发包器中接收或者读取输入

output:通过Publisher发送或写output操作

未知数据保持在一个“Blob”元素(二进制大对象或者字节数组)中

0×36 创建综合测试模型

综合测试模型文件,定义了一些关键要素,例如发包器、监控器以及测试策略等。相当于把所有文件综合成一个文件。实际上,针对一些简单的API,也可以把所有文件直接写在一个文件中。

Publishers是Peach的I/O连接,它是实现输出、输入和调用等操作之间的管道。对于文件fuzzer来说,将使用一个称之为文件的Publisher,它允许我们对一个文件进行写操作。编写综合测试模型文件如下,命名为4-my_integrate.xml

Peach原理简介与实战:以Fuzz Web API为例

Test元素被用于配置一个指定的fuzzing测试,这个配置通过一个Publisher和其他选项(比如including/excluding等被变异的元素、Agents、fuzzing策略)组合成一个StateModel。多个test元素是支持的,在Peach命令行使用中,只需简单提供test元素的名字即可。

至此,所有的xml文件编写告一段落,那么怎么测试这些xml文件是否编写正确呢?请看下节。

0×37 验证测试套

输入以下命令,即可验证编写的所有文件是否符合Peach的标准。请注意参数 -1 是数字1,而不是字母l。

peach E:MyPeachPit4-my_integrate.xml -l --debug

第一次运行时,肯定会报错,需要根据错误提示,不断修正我们刚刚编写的测试套xml文件。

Peach原理简介与实战:以Fuzz Web API为例

如果打印出上面的信息,那么恭喜你,终于完成了测试套的编写!也就是完成了Peach的数据建模,基本上,你已经完成了99%的前期准备工作!

0×38 结合Burp进行正式测试与结果分析

为了实时监控数据流的变化状况,我们需要监控Peach发送给测试目标的数据。Burpsuite给我们提供了一个便捷的“监控器”。

第一步绑定代理

Peach原理简介与实战:以Fuzz Web API为例

请注意,上述的 127.0.0.1:8080,与编写的测试套 4-my_integrate.xml 中的信息要一致。所以,需要将下面的红框中的数据,也改为127.0.0.1:8080

Peach原理简介与实战:以Fuzz Web API为例

第二步peach发送的流量重定向到webserver

Peach原理简介与实战:以Fuzz Web API为例

终于到了最后一步,也就是愉快的fuzz啦!

peach E:MyPeachPit4-my_integrate.xml –range 1,150000

–range 指变异的报文数量,官方推荐15W。

Peach原理简介与实战:以Fuzz Web API为例

等fuzz完之后,筛选一下Burpsuite抓到的所有报文,就可以看到哪些畸形报文,对目标产生了影响。

0×4 总结

Peach最繁重的工作,都在协议的建模上,也就是编写测试套件,中途可能会出现很多错误,需要耐心排查。关于一些更高级的用法,读者可自行查阅官方手册(中文翻译版)。本文中用到的所有代码,都是笔者亲自编写,并且进行了验证。祝大家都能用行之有效的方法,挖意想不到的大洞!

*本文原创作者:榕树下的少年,本文属于FreeBuf原创奖励计划,未经许可禁止转载

本文来源于互联网:Peach原理简介与实战:以Fuzz Web API为例

Sparrow-WiFi:一款Linux平台下的图形化WiFi及蓝牙分析工具

Sparrow-WiFi:一款Linux平台下的图形化WiFi及蓝牙分析工具 

工具概述

Sparrow-wifi本质上一款针对下一代2.4GHz和5GHz的WiFi频谱感知工具,它不仅提供了GUI图形化用户界面,而且功能更加全面,可以代替类似inSSIDer和linssid之类的Linux工具。在其最完整的使用场景下,Sparrow-wifi可以将WiFi、软件定义无线电(hackrf)、高级蓝牙工具、传统GPS(gpsd)和漫游GPS(mavlink)整合到一个解决方案之中。

该工具完全使用Python3开发,并且适用于以下场景:

1、基本的WiFi SSID识别;

2、WiFi源捕捉;

3、2.4GHz和5GHz频谱查看;

4、蓝牙识别;

5、蓝牙源捕捉;

6、iBeacon广播;

7、远程操作(通过GUI);

8、可挂载到无人机,支持树莓派;

9、远程代理基于JSON,可整合进其他应用;

10、CSV和JSON数据导出/导入;

11、可根据GPS坐标生成Google地图;

工具运行截图

Sparrow-wifi主面板,执行基础的WiFi扫描:

Sparrow-WiFi:一款Linux平台下的图形化WiFi及蓝牙分析工具 

WiFi和蓝牙跟踪:

Sparrow-WiFi:一款Linux平台下的图形化WiFi及蓝牙分析工具 

工具安装

Sparrow-wifi使用了Python3、qt5和qtchart来构建UI界面,在标准的基于Debian的平台上,已经自带了Python3和qt5,只需要单独配置qtchart即可。在Ubuntu和Kali Linux上执行下列命令:

sudo apt-get install python3-pip gpsd gpsd-clients python3-tk python3-setuptools

sudo pip3 install QScintilla PyQtChart gps3 dronekit manuf python-dateutil numpy matplotlib

当然了,你也可以在Python虚拟环境(virtualenv)中使用该工具:

git clone https://github.com/ghostop14/sparrow-wifi

cd sparrow-wifi

virtualenv --python=python3 $HOME/sparrow

source $HOME/sparrow/bin/activate

pip3 install gps3 python-dateutil requests pyqt5 pyqtchart numpy matplotlib

sudo python3 sparrow-wifi.py

工具运行

该工具需要使用标准的命令行工具“iw”来执行WiFi扫描,因此我们需要使用root权限来运行:

sudo ./sparrow-wifi.py

GPS通信

Sparrow-wifi基于gpsd来提供标准的GPS通信功能,我们可以使用下列命令来进行快速的GPS测试:

gpsd -D 2 -N /dev/ttyUSB0

运行Sparrow-wifi远程代理

由于代理所需的权限跟GUI工具的运行权限相同,因此这里我们同样需要使用到root权限:

sudo ./sparrowwifiagent.py

默认配置下,代理会监听端口8020。我们也可以使用下列命令来指定其他端口:

sudo ./sparrowwifiagent.py --port=<myport>

工具帮助菜单

我们可以使用–help命令来查看Sparrow-wifi的帮助菜单以及参数选项:

usage: sparrowwifiagent.py [-h] [--port PORT] [--allowedips ALLOWEDIPS]

                           [--mavlinkgps MLINKGPS] [--sendannounce]

                           [--userpileds] [--recordinterface RECORDINTERFACE]

                           [--ignorecfg] [--cfgfile CFGFILE]

                           [--delaystart DELAYSTART]

Sparrow-wifi agent

optional arguments:

  -h, --help            show this help message and exit

  --port PORT           Port for HTTP server to listen on

  --allowedips ALLOWEDIPS

                        IP addresses allowed to connect to this agent. Default

                        is any. This can be a comma-separated list for

                        multiple IP addresses

  --mavlinkgps MLINKGPS

                        Use Mavlink (drone) for GPS. Options are: '3dr' for a

                        Solo, 'sitl' for local simulator, or full connection

                        string ('udp/tcp:<ip>:<port>' such as:

                        'udp:10.1.1.10:14550')

  --sendannounce        Send a UDP broadcast packet on the specified port to

                        announce presence

  --userpileds          Use RPi LEDs to signal state. Red=GPS

                        [off=None,blinking=Unsynchronized,solid=synchronized],

                        Green=Agent Running [On=Running, blinking=servicing

                        HTTP request]

  --recordinterface RECORDINTERFACE

                        Automatically start recording locally with the given

                        wireless interface (headless mode) in a recordings

                        directory

  --ignorecfg           Don't load any config files (useful for overriding

                        and/or testing)

  --cfgfile CFGFILE     Use the specified config file rather than the default

                        sparrowwifiagent.cfg file

  --delaystart DELAYSTART

                        Wait <delaystart> seconds before initializing

树莓派

我们可以使用下列命令在树莓派上完成工具的安装以及构建:

sudo apt-get install libsqlite3-dev

cd /tmp

wget https://www.python.org/ftp/python/3.5.5/Python-3.5.5.tgz

tar -zxvf Python-3.5.5.tgz

cd Python-3.5.5

./configure && make -j3 && sudo make install

完成之后,安装其他的依赖模块:

sudo pip3.5 install gps3 dronekit manuf python-dateutil

接下来,直接使用下列命令运行代理即可:

/usr/local/bin/python3.5 ./sparrowwifiagent.py

/usr/local/bin/python3.5 ./sparrowwifiagent.py --mavlinkgps=3dr --recordinterface=wlan0

项目地址

Sparrow-wifi:【GitHub传送门

* 参考来源:ghostop14,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

本文来源于互联网:Sparrow-WiFi:一款Linux平台下的图形化WiFi及蓝牙分析工具

Unit42发布powershell自动反混淆工具

概述

近日,Unit42安全团队在Github上公开了自己研发的powershell自动反混淆工具,Star数几日之间就突破了300,接下来,就来了解下这款神器的用法及原理。

项目地址:https://github.com/pan-unit42/public_tools/tree/master/powershellprofiler

Unit42发布powershell自动反混淆工具

使用方法

工具用法很简洁,python PowerShellProfiler.py -f <file_name>,若要查看反混淆过程的话可以加上-d参数进行调试,下面使用官方给出的恶意脚本initial_obfusctaed_sample.ps1进行演示,该样本使用了IEX替换、字符串编码、运算符冗余、变量重命名等方式进行混淆。

Unit42发布powershell自动反混淆工具运行PowerShellProfiler.py后,PowerShellProfiler打印出了每一步反混淆的步骤,反混淆后的脚本,以及最后的结论:该样本的恶意分数18.5,高威胁,是个Downloader脚本,病毒家族为Veil Stream。

Unit42发布powershell自动反混淆工具

工具原理

演示完了PowerShellProfiler.py的使用方法,来看一下工具的原理,主要为4个核心步骤:反混淆目标脚本 -> 病毒家族检测 -> 可疑字符串扫描 -> 目标脚本恶意分数统计

除了第一个函数,后三个函数的原理都是通过正则表达式特征码匹配,下面重点分析下第一个反混淆的函数unravelContent。

Unit42发布powershell自动反混淆工具进入该函数后,核心的反混淆函数为normalize(),在反混淆之前,工具会先对字符串进行相应的倒序、反编码、解压、解密等操作。这次的脚本initial_obfusctaed_sample.ps1存在frombase64string、decompress等字符串,首先会进入decompressContent这个反编码函数。

Unit42发布powershell自动反混淆工具该函数首先对脚本进行base64解密,然后尝试用各种压缩方式进行解压,直到解压成功为止。

Unit42发布powershell自动反混淆工具

完成以上操作后,工具就调用normalize()函数进行反混淆,原理就是即时翻译powershell常用的混淆运算符、字符串,如:

1.删除一些无用的运算符:”空格”、”^”、”`”。

2.转化Char字节为ASCII码。

3.去掉”+”,连接字符串。

4.replace字符串。

Unit42发布powershell自动反混淆工具反混淆前的脚本如下,存在大量的无用的运算符、char字符,以及一些明显的-replace替换函数。

Unit42发布powershell自动反混淆工具进行了normalize()函数反混淆后,得到的脚本如下,结构已经很清晰了,接下来工具就可以对该脚本进行特征码匹配了,如:downloadstring、Start-Process等可疑字符串的提取。

Unit42发布powershell自动反混淆工具

结尾

powershell作为APT攻击的常用攻击方式,免杀、混淆的方式千变万化,光使用该工具无法保证能对所有ps脚本进行反混淆检测,我们更应该深入理解ps脚本的混淆常用方法及原理,才能实时地与这类恶意攻击样本进行对抗。

*本文作者:深信服千里目安全实验室,转载请注明来自FreeBuf.COM

本文来源于互联网:Unit42发布powershell自动反混淆工具

Applepie:一款整合了WHVP和Bochs的模糊测试工具

Applepie:一款整合了WHVP和Bochs的模糊测试工具

Applepie这款工具是一个专门用于模糊测试、内部安全审查和寻找漏洞的工具,从本质上来说,它是一款使用了Windows(版本:Windows 10 v17763)虚拟机管理程序平台API(WHVP)的管理程序。其中,Applepie还整合了Bochs来提供深度内部检测和设备模拟等功能。

Windows虚拟机管理程序平台(WHVP)是一套API套件,可以给我们提供访问Hyper-V的虚拟机管理程序功能的服务。这个API可以帮助我们在用户空间中轻松创建一个虚拟机,而不需要任何的驱动程序或其他权限。

功能介绍

该工具的主要功能是帮助研究人员在安全分析的过程中进行模糊测试和内部安全检查。通过使用常见的模糊测试技术,我们可以在任何测试目标、内核和用户环境中使用该工具。整个环境允许研究人员在不需要任何目标资源的情况下对目标进行模糊测试。而且该工具代码覆盖率非常高,而且可以在扫描的过程中构建模糊测试用例以备后续使用。

针对系统快照的模糊测试也是该工具的一个特点,当我们拿到目标系统在某个状态下的系统快照之后,我们就可以加载快照并进行模糊测试了。由于虚拟机重置的成本是非常低的,所以我们可以轻松降低安全分析所需的时间。

项目构建

工具要求

1、最新版本的MSVC编译器(Visual Studio 2017)

2、Rust(https://rustup.rs/

3、Python 3(https://www.python.org/

4、Cygwin+autoconf+GNU

5、Hyper-V

构建步骤

请大家遵循下列步骤完成项目代码构建:

Clean install of Windows 10, Build 17763

rustc 1.33.0-nightly (8e2063d02 2019-01-07)

Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27025.1 for x64

Visual Studio Community 2017 version 15.9.4

applepie commit `f84c084feb487e2e7f31f9052a4ab0addd2c4cf9`

Python 3.7.2 x64

git version 2.20.1.windows.1

首先,确保Windows 10已经更新至最新版本,因为我们只在最新版本的Windows 10系统中测试了WHVP等功能。

在“启动或关闭Windows功能中”,点击“Hyper-V”,然后选择“Windows虚拟机监控程序平台”,最后点击确定并重启设备。

Applepie:一款整合了WHVP和Bochs的模糊测试工具

安装VS Community 2017并更新版本,选择“Desktop development with C++”:

Applepie:一款整合了WHVP和Bochs的模糊测试工具

安装“Rust nightly for x86_64-pc-windows-msvc”:

Applepie:一款整合了WHVP和Bochs的模糊测试工具

Applepie:一款整合了WHVP和Bochs的模糊测试工具

接下来,分别安装Git、Cygwin x64、Python和”x64 Native Tools Command Prompt for VS 2017″,

最后,运行下列命令将项目代码克隆至本地:

git clone https://github.com/zolabs/applepie

然后切换至本地项目目录,运行下列命令:

python build.py

工具演示视频

添加的新功能

视频地址:【点我观看

代码覆盖演示

视频地址:【点我观看

项目地址

Applepie:【GitHub传送门

*参考来源:zolabs,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

本文来源于互联网:Applepie:一款整合了WHVP和Bochs的模糊测试工具

Suricata + Lua实现本地情报对接

背景

由于近期网站遭受恶意攻击, 通过对于登录接口的审计与分析, 现已确定了一批可疑账号。既然之前写过一个登录接口的审计脚本, 那么完全可以通过扩展这个脚本来实现对于可疑账号的比对。主要思路: 通过将可疑账存进Redis中, 再利用Lua脚本调用Redis接口进行账号的比对。

先说一下Suricata默认是存在黑名单机制的, 如下:

# IP Reputation
#reputation-categories-file: /etc/suricata/iprep/categories.txt
#default-reputation-path: /etc/suricata/iprep
#reputation-files:
# - reputation.list

在Suricata 5.0版本中更是增加了新的功能Datasets。大概看了一下, 可以通过在规则中使用dataset和datarep关键字将大量数据与sticky buffer进行匹配。确实是个很赞的功能!

alert http any any -> any any (http.user_agent; dataset:set, ua-seen, type string, save ua-seen.lst; sid:1;)
alert dns any any -> any any (dns.query; to_sha256; dataset:set, dns-sha256-seen, type sha256, save dns-sha256-seen.lst; sid:2;)
alert http any any -> any any (http.uri; to_md5; dataset:isset, http-uri-md5-seen, type md5, load http-uri-md5-seen.lst; sid:3;)

但是… 这并不适用我现在的场景。因为在我的场景中, 用户的登录请求存在于POST Body中, 默认的Suricata方法并不能准确定位到我们需要的账号。这个时候我们就只能依赖于Lua脚本来扩展。当然这些需求Zeek也可以满足, 只是…Zeek的脚本真是难写…主要是我技术太low~

准备阶段

运行环境

OS:Ubuntu 18.04

Suricata: Suricata 5.0.0 RELEASE (我是AWS的流量镜像, 必须使用4.1.5或者5.0版本, 因为要解析VXLAN)

LuaRocks

1.由于Ubuntu默认没有安装LuaRocks(LuaRocks is the package manager for Lua modules), 这里需要我们手动安装。

# 通过apt直接安装, 简单省事儿。
$ apt-get install luarocks

2. 通过luarocks安装我们所需要的lua模块, 这里我们需要用到redis-lualuasocket这两个模块。

# Install Modules
$ luarocks install luasocket
$ luarocks install redis-lua

$ ll /usr/local/share/lua/5.1/
total 72
drwxr-xr-x 3 root root  4096 Oct 25 03:35 ./
drwxr-xr-x 3 root root  4096 Sep 17 14:14 ../
-rw-r--r-- 1 root root  8331 Oct 25 03:34 ltn12.lua
-rw-r--r-- 1 root root  2487 Oct 25 03:34 mime.lua
-rw-r--r-- 1 root root 35599 Oct 25 03:35 redis.lua
drwxr-xr-x 2 root root  4096 Oct 25 03:34 socket/
-rw-r--r-- 1 root root  4451 Oct 25 03:34 socket.lua

3. 安装成功后, 可以简单的测试一下。

3.1 利用Docker启动Redis容器

$ docker run -ti -d -p 6379:6379 redis

3.2 测试脚本 hello_redis.lua

local redis = require "redis"

local client = redis.connect("127.0.0.1", 6379)

local response = client:ping()
if response == false then
	return 0
end

client:set("hello", "world")

local var = client:get("hello")
print(var)

3.3 可能会存在环境变量不对导致的报错

$ luajit hello_redis.lua
	luajit: /usr/local/share/lua/5.1/redis.lua:793: module 'socket' not found:
	no field package.preload['socket']
	no file './socket.lua'
	no file '/usr/local/share/luajit-2.0.5/socket.lua'
	no file '/usr/local/share/lua/5.1/socket.lua'
	no file '/usr/local/share/lua/5.1/socket/init.lua'
	no file './socket.so'
	no file '/usr/local/lib/lua/5.1/socket.so'
	no file '/usr/local/lib/lua/5.1/loadall.so'
stack traceback:
	[C]: in function 'require'
	/usr/local/share/lua/5.1/redis.lua:793: in function 'create_connection'
	/usr/local/share/lua/5.1/redis.lua:836: in function 'connect'
	a.lua:3: in main chunk
	[C]: at 0x56508049e440

3.4 执行luarocks path –bin 并将结果输入

$ luarocks path --bin
Warning: The directory '/home/canon/.cache/luarocks' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing /usr/local/bin/luarocks with sudo, you may want sudo's -H flag.
export LUA_PATH='/home/canon/.luarocks/share/lua/5.1/?.lua;/home/canon/.luarocks/share/lua/5.1/?/init.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;./?.lua;/usr/local/share/luajit-2.0.5/?.lua'
export LUA_CPATH='/home/canon/.luarocks/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/?.so;./?.so;/usr/local/lib/lua/5.1/loadall.so'
export PATH='/home/canon/.luarocks/bin:/usr/local/bin:/home/canon/anaconda3/bin:/home/canon/anaconda3/condabin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin'

3.5 执行脚本, 将会看到如下输出

$ luajit hello_redis.lua
world

CJson

这里建议大家使用CJson模块, 我之前为了测试随便从github上找了个json模块来使用。这几天发现在网站的高峰时期 Suricataapp_layer.flow这个字段非常的大, 从而导致了kernel_drops。由于我们的网站是面对海外用户想定位问题又存在时差, 经过几天的熬夜最终定位到是由于json模块太过于消耗性能而导致。可以看下这个截图:

a.Suricata监控图 -启用CJson模块之前

Suricata + Lua实现本地情报对接

b.Suricata监控图 -启用CJson模块之后

Suricata + Lua实现本地情报对接

1.下载 CJson

# wget 下载
$ wget https://www.kyne.com.au/~mark/software/download/lua-cjson-2.1.0.tar.gz

# Git Clone
$ git clone git@github.com:mpx/lua-cjson.git

2. 根据Lua环境修改Makefile(个人配置)

##### Build defaults #####
LUA_VERSION =       5.1
TARGET =            cjson.so
PREFIX =            /usr/local
#CFLAGS =            -g -Wall -pedantic -fno-inline
CFLAGS =            -O3 -Wall -pedantic -DNDEBUG
CJSON_CFLAGS =      -fpic
CJSON_LDFLAGS =     -shared
LUA_INCLUDE_DIR =   $(PREFIX)/include/luajit-2.0
LUA_CMODULE_DIR =   $(PREFIX)/lib/lua/$(LUA_VERSION)
LUA_MODULE_DIR =    $(PREFIX)/share/lua/$(LUA_VERSION)
LUA_BIN_DIR =       $(PREFIX)/bin

3. 安装 CJson

$ make && make install

登录接口代码示例

json = require "cjson.safe"
md5 = require "md5"
redis = require "redis"

-- 登录接口
login_url = "/login" -- 根据实际接口而定
-- 登录错误提示
success_code = 0
-- event_name
event_name = "login_audit"
-- event_type
event_type = "lua"
-- logs
name = "login_audit.json"
-- 协议
proto = "TCP"

-- redis_config
host = "127.0.0.1"
port = 6379

-- common_mapping 通用请求头
http_common_mapping = '{"accept":"accept","accept-charset":"accept_charset","accept-encoding":"accept_encoding","accept-language":"accept_language","user-agent":"user_agent"}'
common_mapping_table = json.decode(http_common_mapping)

-- request_mapping 自定义请求头
http_request_mapping = '{"content-length":"request_content_length","content-type":"request_content_type"}'
request_mapping_table = json.decode(http_request_mapping)

-- response_mapping 自定义响应头
http_response_mapping = '{"content-length":"response_content_length","content-type":"response_content_type"}')


-- custom defind functioin
function md5Encode(args)
    m = md5.new()
    m:update(args)
    return md5.tohex(m:finish())
end

function formatBody(args)
    t = {}
    ios = string.match(args, 'canon')
    if ios ~= nil then
        mail = 'email"%s+(.-)%s'
        t['email'] = string.match(args, mail)
    else
        data = string.split(args, '&')
        for n, v in ipairs(data) do
            d = string.split(v, '=')
            t[d[1]] = d[2]
        end
    end
    return t
end

function string.split(s, p)
    rt = {}
    string.gsub(s, '[^'..p..']+', function(w) table.insert(rt, w) end )
    return rt
end

-- default function
function init (args)
    local needs = {}
    needs["protocol"] = "http"
    return needs
end

function setup (args)
    filename = SCLogPath() .. "/" .. name
    file = assert(io.open(filename, "a"))
    SCLogInfo("app_login_audit filename: " .. filename)
    http = 0
  
    -- Connect Redis Server 连接Redis服务器
    SCLogInfo("Connect Redis Server...")
    client = redis.connect(host, port)
    response = client:ping()
    if response then
        SCLogInfo("Redis Server connection succeeded.")
    end
end

function log(args)
    -- init tables
    http_table = {}

    -- ti tables
    ti = {
        tags = {}
    }

    -- init score 初始分数(为后期规则判断而准备, 符合规则进行加分。)
    score = 50

    -- http_hostname & http_url
    http_hostname = HttpGetRequestHost()
    http_url = HttpGetRequestUriNormalized()
    
    -- http_method
    rl = HttpGetRequestLine()
    if rl then
        http_method = string.match(rl, "%w+")
        if http_method then
            http_table["method"] = http_method
        end
    end
	
    -- 为了保证 Suricata 的性能不受影响, 指定登录接口以及请求才能进入此逻辑。
    if http_url == login_url and http_method == "POST" then
        http_table["hostname"] = http_hostname
        http_table["url"] = http_url
        http_table["url_path"] = http_url
        
        -- http_status & http_protocol
        rsl = HttpGetResponseLine()
        if rsl then
            status_code = string.match(rsl, "%s(%d+)%s")
            http_table["status"] = tonumber(status_code)

            http_protocol = string.match(rsl, "(.-)%s")
            http_table["protocol"] = http_protocol
        end

        -- login_results
        a, o, e = HttpGetResponseBody()
        if a then
            for n, v in ipairs(a) do
                body = json.decode(v)
                results_code = tonumber(body["code"])
                if results_code == success_code then
                    http_table["results"] = "success"
                else
                    http_table["results"] = "failed"
                end
            end
            http_table["results_code"] = results_code
        end
        
        --[[
            1. 获取用户登录email并查询Redis中是否存在该账号
            2. 根据结果进行相应的打分以及tags标注
        --]]
        a, o, e = HttpGetRequestBody()
        if a then
            for n, v in ipairs(a) do
                res = formatStr(v)
                if res["email"] then
                    -- 查询Redis对比黑名单
                    black_ioc = client:get(res["email"])
                    if black_ioc then
                        ti["provider"] = "Canon"
                        ti["producer"] = "NTA"
                        table.insert(ti["tags"], "account in blacklist")
                        score = score + 10
                    end
                end
            end
        end

        -- RequestHeaders 根据自定义的请求头进行获取, 对于业务安全来说有些请求头还是有必要获取的。
        rh = HttpGetRequestHeaders()
        if rh then
            for k, v in pairs(rh) do
                key = string.lower(k)
                common_var = common_mapping_table[key]
                if common_var then
                    http_table[common_var] = v
                end
    
                request_var = request_mapping_table[key]
                if request_var then
                    http_table[request_var] = v
                end
            end
        end

        -- ResponseHeaders 自定义获取响应头
        rsh = HttpGetResponseHeaders()
        if rsh then
            for k, v in pairs(rsh) do
                key = string.lower(k)
                common_var = common_mapping_table[key]
                if common_var then
                    http_table[common_var] = v
                end
        
                response_var = response_mapping_table[key]
                if response_var then
                    http_table[response_var] = v
                end
            end
        end

        -- timestring
        sec, usec = SCPacketTimestamp()
        timestring = os.date("!%Y-%m-%dT%T", sec) .. '.' .. usec .. '+0000'
        
        -- flow_info
        ip_version, src_ip, dst_ip, protocol, src_port, dst_port = SCFlowTuple()

        -- flow_id
        id = SCFlowId()
        flow_id = string.format("%.0f", id)
        flow_id = tonumber(flow_id)

        -- alerts 查询这笔flow是否存在特征匹配后的告警
        has_alerts = SCFlowHasAlerts()

        -- true_ip
        true_client_ip = HttpGetRequestHeader("True-Client-IP")
        if true_client_ip ~= nil then
            src_ip = true_client_ip
        end

        -- session_id
        tetrad = src_ip .. src_port .. dst_ip .. dst_port
        session_id = md5Encode(tetrad)

        -- table
        raw_data = {
            timestamp = timestring,
            flow_id = flow_id,
            session_id = session_id,
            src_ip = src_ip,
            src_port = src_port,
            proto = proto,
            dest_ip = dst_ip,
            dest_port = dst_port,
            event_name = event_name,
            event_type = event_type,
            app_type = app_type,
            http = http_table,
            alerted = has_alerts,
            ti = ti,
            score = score
        }

        -- json encode
        data = json.encode(raw_data)

        file:write(data .. "n")
        file:flush()

        http = http + 1
    end

end

function deinit (args)
    SCLogInfo ("app_login_audit transactions logged: " .. http);
    file:close(file)
end

1. 简单说下以上脚本的功能:

a.登录接口的用户名审计(废话…);

b.通过请求Redis比对当前用户是否在黑名单中, 并进行相应的打分、标签处理;

c.根据自定义的需求获取的http headers, 个人觉得这个对于业务安全上还是有点用的;

d. 新增字段”session_id”, 主要考虑是针对CDN或者Nginx这种方向代理的场景下, 可以直接对 xff 或者 true_client_ip 进行四元组的hash, 得到session_id, 这样溯源的时候会比较方便。因为在这种场景下传统的四层flow_id就不是那么有用了。

e.后续可以追加一些简单的检测方法, 例如: (这些适用于我们, 其他的请头脑风暴)

检查请求头中的字段是否完整;

检查请求头中的某个字段长度是否符合合规;

头脑风暴…

2. 配置Suricata启用Lua脚本

- lua:
    enabled: yes
    scripts-dir: /etc/suricata/lua-output/
    scripts:
      - login_audit.lua

3. 启动Suricata

$ suricata -vvv --pfring -k none -c /etc/suricata/suricata.yaml

注: 这里-vvv 参数建议加上. 如果你的Lua脚本有一些问题, 如果加上了这个参数, 就可以通过这个日志看出。

$ tailf /data/logs/suricata/suricata.log
4/11/2019 -- 02:22:25 - <Warning> - [ERRCODE: SC_ERR_PF_RING_VLAN(304)] - no VLAN header in the raw packet. See #2355.
4/11/2019 -- 02:22:25 - <Warning> - [ERRCODE: SC_ERR_PF_RING_VLAN(304)] - no VLAN header in the raw packet. See #2355.
4/11/2019 -- 02:22:25 - <Warning> - [ERRCODE: SC_ERR_PF_RING_VLAN(304)] - no VLAN header in the raw packet. See #2355.
4/11/2019 -- 02:22:25 - <Warning> - [ERRCODE: SC_ERR_PF_RING_VLAN(304)] - no VLAN header in the raw packet. See #2355.
4/11/2019 -- 02:22:25 - <Warning> - [ERRCODE: SC_ERR_PF_RING_VLAN(304)] - no VLAN header in the raw packet. See #2355.
4/11/2019 -- 02:28:03 - <Info> - failed to run script: /usr/local/share/luajit-2.0.5/md5.lua:347: attempt to get length of local 's' (a nil value)

输出日志样例

{
    "src_port": 62722,
    "score": 60,
    "session_id": "c863aeb2ef8d1b37f3257f8c210bf440",
    "ti": {
        "tags": [
            "account in blacklist"
        ],
        "provider": "Canon",
        "producer": "NTA"
    },
    "alert": {
        "alerted": true,
        "rules": {
            "请求头校验": "dev-id"
        }
    },
    "proto": "TCP",
    "flow_id": "1064295903559076",
    "timestamp": "2019-10-25T08:33:55.585519+0000",
    "event_type": "lua",
    "src_ip": "1.1.1.1",
    "dest_port": 80,
    "http": {
        "response_content_length": "96",
        "response_content_type": "application/json; charset=UTF-8",
        "accept_encoding": "gzip",
        "accept": "application/json",
        "results_code": 400504,
        "server": "nginx",
        "date": "Fri, 25 Oct 2019 08:33:55 GMT",
        "app_version": "6.6.0",
        "request_content_type": "application/x-www-form-urlencoded",
        "user_agent": "okhttp/3.12.0",
        "url": "/login",
        "email": "canon@gmail.com",
        "results": "failed",
        "pragma": "no-cache",-
        "cache_control": "no-cache, max-age=0, no-store",
        "connection": "keep-alive",
        "status": 200,
        "protocol": "HTTP/1.1",
        "hostname": "x.x.x.x",
        "url_path": "/login",
        "method": "POST",
        "device": "RMX1920 Android8.0.0",
        "device_type": "Android",
        "request_content_length": "39"
    },
    "event_name": "login_audit",
    "dest_ip": "2.2.2.2"
}

Suricata + Lua实现本地情报对接

*本文作者:Shell.,转载请注明来自FreeBuf.COM

本文来源于互联网:Suricata + Lua实现本地情报对接