模糊测试——我们常常称其为Fuzzing,被定义为一种通过提供非预期的输入并监视异常结果来发现软件故障的方法。 Fuzzing的乐趣在于探索一些出人意料或隐藏很深的安全性缺陷,这些安全性缺陷不乏如缓冲区溢出、服务器异常崩溃等,一旦确认这些缺陷事实存在,它们往往都是致命性的。当然这并不限制通过为Fuzzing模糊器精心构造一系列的规则和数据库来发现诸如SQL注入、XSS攻击、目录遍历/弱访问控制、弱认证、远程命令执行等缺陷,规则设计的越加智能、针对已知漏洞的数据字典内容越加丰富,Fuzzing测试的效率和结果也会越理想。当然,也同样可以使用暴力构造畸形数据的方式来生成Fuzzing数据。
Fuzzing测试需要借助于上文提到的Fuzzing模糊器,最常见的Fuzzing模糊器主要有两类:
(1)基于变异的模糊器,这种模糊器对已有数据样本应用变异技术以创建测试用例;
(2)基于生成的模糊器,这种模糊器通过对目标协议或文件格式建模的方法从头开始产生测试用例。
SPIKE Fuzzer Creation Kit作为最著名的Fuzzing模糊器框架(spike fuzzing framework),是immunitysec公司的Dave Aitel写的一个黑盒安全性测试工具集SPIKE Suite Tools的一个重要组成部分,为我们提供了一个包含长字符串、带有格式串的字符串、大整数、负数、各种畸形字符等的Fuzzing数据库,一套基于Block-Based Protocol Analysis方法的测试框架以及丰富的API,我们不仅可以利用SPIKE提供的各类实用脚本直接实现Fuzzing测试,更可以利用框架,对API进行轻量级封装,从而构建属于我们自己的模糊器。
我们可以从http://www.immunitysec.com/resources-freesoftware.shtml网站获取并了解它。
SPIKE对于寻找安全性缺陷或脆弱性具备以下特点,了解这些对于利用该框架构建模糊器非常重要:
(1)利用它可以便捷快速地重构一个复杂的二进制协议;
(2)利用它可以很容易地使用Fuzzing数据来混乱协议结构和内容;
(3)SPIKE发展出了一套关于Fuzz的基础理论,该理论总结为构造一些特殊的数字或者字符串作为程序的输入,检查程序是否能够处理这些异常数据;
(4)利用已知的安全缺陷测试新的程序。
SPIKE的数据域类似于FIFO队列或称其为“BufferClass”的结构,格式支持多种的数据类型:Word、halfword、string、Big endian、 little endian 等;
主要包括三种基础的Fuzzing数据构造的方法:
(1)通过调用s_push(buffer,size)向SPIKE数据域下方填充数据,支持基于perl语言的脚本s_push("perl -e 'print "A" x5000'");的实现方式;
(2)通过调用字符串数据构造函数s_string("hi");s_string_variable("hi");向SPIKE数据域下方填充普通字符串或带有Fuzzing变量字符串数据;
(3)通过调用二进制数据构造函数s_binary("\\x41 4141 0x41 41 00"); 向SPIKE数据域下方填充二进制数据,此方法支持直接通过剪切和粘贴16进制数据的方式,并能自动清除中间夹杂的空格。
另外,SPIKE具备一种独特的数据结构,该结构支持对长度与块的支持,从而可以自动填充一些协议中要求的长度域,主要的实现方式是通过“监听”一段特殊的块的结束标识来完成的:
s_size_string("post",5);
s_block_start("post");
s_string_variable("user=bob");
s_block_end("post");
值得注意的是上述的填充方式支持在代码中的嵌套或交织,这种实现极具便利性。
SPIKE还能够轻松的生成网络编码或常见的信号编集数据,如利用这样的函数:s_xdr_string();暴力生成一些重复地数据,如利用这样的函数:s_string_repeat("A",5000);
三、如何使用SPIKE框架
(1)初始化和销毁一个SPIKE
将其处理为一个全局作用域变量:
set_current_spike(*struct spike);
spike_clear();
内存分配:
spike_new();
spike_free();
(2)SPIKE的网络功能
基础的TCP连接器:
spike_tcp_connect(host,port);
spike_send();
spike_close_tcp();
基础的UDP连接器:
spike_udp_connect(host,port);
spike_send();
(3)2 while() loops模糊器模板
利用s_incrementfuzzvariable()和s_incrementfuzzstring()组成被称之为2 while() loops的方式实现持续迭代的Fuzzing模糊器,下面是一个该模糊器标准的实现模板:
struct spike *our_spike;
our_spike = new_spike();
setspike(our_spike);//设置本上下文环境spike为our_spike
s_init_fuzzing();//初始化fuzzing字符串
s_resetfuzzvariable();//重置fuzzing变量
while(!s_didlastvariable()) {//循环直到最后一个fuzzing变量
s_resetfuzzstring();//重置fuzzing字符串
while(!s_didlastfuzzstring()){//循环直到最后一个fuzzing字符串
spike_clear();
s_setfirstvariable();//从第一个变量开始
/*构造Fuzzing数据*/
s_string_variables('&',"/uid=bob&passwd=bob");//以&字符为分割符,将两处的bob设置为fuzzing变量
//或用以下方式实现同样效果
s_string("/uid=");
s_string_variable("bob");
s_string("&passwd=");
s_string_variable("bob");
/*获取Fuzzing数据*/
s_get_databuf();
/*利用Fuzzing数据进行测试
*do_something();
*/
s_incrementfuzzstring();//fuzzing字符串+1
}
s_incrementfuzzvariable();//fuzzing变量+1
}
spike_free(our_spike);
通过2 while() loops模糊器模板从头构建一个针对HTTP协议的模糊器,可以直接参考SPIKE框架中提供的如post_fuzz.c、post_spike.c等源码,但这样的模糊器严重缺乏灵活性,并需要模糊器使用人员深入到代码内部进行对Fuzzing数据的设置和调整,轻量级封装的有易之处在于使得我们所构建的模糊器尽量的灵活或使用简洁,为模糊器使用人员带来便利。以下的例子是一类典型的封装思路,要达到的主要目标是构建Fuzzing数据的过程在2 while() loops结构的外部,构造方法简单易用:
/*
* xf_flexed_fuzz_http_request.c
*
* Created on: 2014年9月12日
* Author: [email protected]
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "spike.h"
#include "hdebug.h"
#include "tcpstuff.h"
#include "udpstuff.h"
#define MAX_FUZZ_NUM 100//最大的fuzzing参数数量,实际为MAX_FUZZ_NUM / 2
#define MAX_BUFFER_SIZE 102400//HTTP收发最大字符数
#define MAX_BUFFER_WRITE_SIZE 2500//一次读取的最大字符数
#define CRLF "\r\n"
#define PARAM_LEFT_EDGE '{'//参数左边距
#define PARAM_RIGHT_EDGE '}'//参数右边距
typedef struct _FORMAT_LINE_{
char *point[2];//用于flexed_fuzz的格式化行,通过s_string(point[0])和s_string_variable(point[1])实现对spike的拼接
} FORMAT_LINE;
static FORMAT_LINE line_array[MAX_FUZZ_NUM];
static int
splitline(char *in, char **pointer);
static int
formatline(char *in, char **pointer);
extern int
read_http_response_status_num(char *in);
char in[] =
"GET /test.htm?uid={xreztento}&passwd={passwd} HTTP/1.1\r\n"
"Host: localhost\r\n"
"User-Agent: Mozilla/5.0 (X11; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0\r\n"
"Accept: {text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8}\r\n"
"Accept-Language: zh-cn,zh;q=0.5\r\n"
"Accept-Encoding: gzip, deflate\r\n"
"Proxy-Connection: keep-alive\r\n"
"Cookie: BAIDUID=2CA5FD322D09701E3EC025BFF9AB62DB:FG=1; BD_UPN=1333; BD_HOME=0; BD_CK_SAM=1; H_PS_PSSID=7544_1443_7802_8057_6505_7634_6018_7893_8123_7606_7799_8035_7790_7941_8115\r\n"
"Cache-Control: max-age=0\r\n";
int
xf_flexed_fuzz_http_request(char *name, char *target, int port, char *request){
int i;
char *pointer[MAX_FUZZ_NUM];//保存格式化后的字符
int row = formatline(in, pointer);//格式化HTTP请求,并返回格式化结构的行数
char buffer[MAX_BUFFER_SIZE];//用于保存HTTP请求和响应数据
struct spike *our_spike;
unsigned long retval;//判断服务器是否响应结束
int notfin;//判断是否服务器超时未返回响应数据
int first;//用于控制一次HTTP请求过程
our_spike = new_spike();
setspike(our_spike);//设置本上下文环境spike为our_spike
s_init_fuzzing();//初始化fuzzing字符串
signal(SIGPIPE,SIG_IGN);
if (our_spike==NULL) {
fprintf(stderr,"Malloc failed trying to allocate a spike.\r\n");
exit(-1);
}
s_resetfuzzvariable();//重置fuzzing变量
while(!s_didlastvariable()) {//循环直到最后一个fuzzing变量
s_resetfuzzstring();//重置fuzzing字符串
while(!s_didlastfuzzstring()){//循环直到最后一个fuzzing字符串
spike_clear();
s_setfirstvariable();
//fuzzing字符串附值
for(i = 0;i < row; i++){
s_string(line_array[i].point[0]);
s_string_variable((unsigned char *)line_array[i].point[1]);
}
s_string(line_array[row].point[0]);
s_string(CRLF);
sleep(1);
printf("Sending to %s on port %d\r\n", target, port);
//发送HTTP请求到服务器
if(spike_send_tcp(target, port) < 0){
printf("Couldn't connect to host or send data!\n");
exit(-1);
}
//buffer保存HTTP请求数据
memset(buffer, 0x00, sizeof(buffer));
memcpy(buffer, s_get_databuf(), s_get_size());
printf("Request:\n%.2500s\nEndRequest\n",buffer);
printf("reading\r\n");
notfin = 1;
retval = 1;
first = 1;
memset(buffer, 0x00, sizeof(buffer));
while(retval && notfin){
char tmp[MAX_BUFFER_WRITE_SIZE];
memset(tmp, 0x00, sizeof(tmp));
notfin = s_fd_wait();//等待读取数据
if (!notfin) {
printf("Server didn't answer in time limit\n");
break;
}
//读取HTTP响应数据
retval = read(our_spike->fd, tmp, MAX_BUFFER_WRITE_SIZE);
if (first && (retval == -1 || retval == 0) ) {
fprintf(stderr,"Server closed connection!\n");
}
first = 0;
if (retval) {
//printf("%s\n", tmp);
strcat(buffer, tmp);
}
}
//printf("%s\n", buffer);
#if 1
int status = read_http_response_status_num(buffer);
printf("Response status:%d\n",status);
//根据HTTP Response Status做处理
//do_something();
#endif
printf("End response\n");
s_incrementfuzzstring();//fuzzing字符串+1
spike_close_tcp();
if(row == 0){
break;
}
}
if(row == 0){
break;
}
s_incrementfuzzvariable();//fuzzing变量+1
}
spike_free(our_spike);
return 0;
}
/**
* 将字符串按FORMAT_LINE结构体数组格式进行格式化
*/
static int
formatline(char *in, char **pointer){
int i;
int j;
int line_num = splitline(in, pointer);//分割后每个子字符串的指针数量
int row_num = ( line_num + 1) / 2;//FORMAT_LINE结构体数组元素数
for(i = 0;i <= row_num;i++) {
j = i * 2;
line_array[i].point[0] = pointer[j];
line_array[i].point[1] = pointer[j+1];
}
return row_num;//需要Fuzzing的数量
}
/**
* 将输入字符串按左右边界字符进行拆分,将左右边界替换为\0以作为数组分割
*param in 被分割字符串
*param pointer 分割后子字符串指针数组首元素的指针
*/
static int
splitline(char *in, char **pointer){
int i, j;
int k = 0;//指针数组索引
pointer[k] = in;//数组第一个元素指向被分割字符串首地址
int n = strlen(in);
for(i = 0; i < n; i++) {
if(*(in + i) == PARAM_LEFT_EDGE) {//当发现左边界时
*(in + i) = '\0';//将边界替换成字符串结束符
k++;//索引+1
pointer[k] = in +i +1;//指向左边界对应的下一个字符地址
for(j = i + 1; j < n; j++) {//从左边界对应的下一个字符开始循环
if(*(in + j) == PARAM_RIGHT_EDGE) {//当发现右边界时
*(in + j) = '\0';//将边界替换成字符串结束符
break;//结束循环
}
}
k++;//索引+1
pointer[k] = in + j + 1;//指向右边界对应的下一个字符地址
i = j;
}
}
return k;
}
int
main(){
xf_flexed_fuzz_http_request("my_fuzzing","localhost",80,in);
return 0;
}
/*
* xf_fuzz_common.c
*
* Created on: 2014年9月12日
* Author: [email protected]
*/
#include
#include
#define FRIST_ROW_MAX_SIZE 100
/*
* 读取HTTP响应数据第一行
*/
char *read_http_response_frist_row(char *in){
static char frist_row[FRIST_ROW_MAX_SIZE];
int i;
for(i = 0;i < FRIST_ROW_MAX_SIZE; i++){
if(*(in + i) != '\r'){
frist_row[i] = *(in + i);
} else {
break;
}
}
return frist_row;
}
/*
*读取HTTP响应状态码
*/
int read_http_response_status_num(char *in){
char *frist_row = read_http_response_frist_row(in);
char status_num_str[3];
status_num_str[0] = frist_row[9],
status_num_str[1] = frist_row[10],
status_num_str[2] = frist_row[11];
int status_num = atoi(status_num_str);
return status_num;
}
该构造器直接使用"{}"完成对符合HTTP协议GET方法的HTTP Request Header数据进行SPIKE Fuzzing变量的设置,通过调用int xf_flexed_fuzz_http_request(char*name,char *target,intport, char *request)实现Fuzzing测试。
本文章对Fuzzing技术和应用SPIKE框架进行了简单介绍,目的是为了帮助读者更多的对其有所了解,也希望能够在此基础上开拓思路,构建属于自己的模糊器,当然读者也可以直接使用本文中提供的封装案例执行Fuzzing测试。后续章节将试图通过不断地对SPIKE框架进行封装,从而得到更多通用且实用的模糊器方法。