#基于C的MySQL数据库审计核心实现#

一、背景介绍

      目前基于合规性要求,多数用户已经部署或打算部署数据库审计产品,市面审计产品通过旁路形式,对多种数据库进行综合审计(常见的oracle、mysql、sqlserver、db2等都有较好的支持),其核心原理是采集网络数据流量,通过已有的协议树进行匹配解析,最终获取请求语句信息、返回结果信息等内容。

       本文基于C语言,通过对Mysql的协议深度解析,识别网络流量中的请求信息及返回结果信息,通过 Console的形式对外输出。由于环境有限,本文是解析已有的pcap数据包文件,后期可通过扩展libpcap进行旁路采集,利用已有的mysql协议解析代码实现mysql的实时审计。关于libpcap使用,本文不过多介绍。

      在对mysql解析之前,如需要对mysql协议加深了解,请参考MYSQL手册mysql协议分析,本文不过多对mysql协议进行介绍。

     本文还有很多未完善之处,待后续迭代。

二、Mysql网络协议解析

      在读者对第三节代码阅读时,建议读者先了解mysql的网络协议,以便更好读懂代码,如果读者非常了解mysql相关协议,可跳过该节。

      mysql数据类型包括:整数型和字符串型。整数型分为定长型,如:int<1>、int<2>、int<3>、int<4>等。变长型INT<lenenc>,所存储字节数大小取决于第一个字节的数值大小。

     字符串型分为固定长度的字符串、NULL结束的字符串、可变的字符串、长度编码字符串、EOF。

Mysql协议包报文格式如下:

基于C的MySQL数据库审计核心实现(内有干货)

例如COM_INIT_DB(切换数据库)整包字节序列为:07 00 00 00 02,如下图:

基于C的MySQL数据库审计核心实现(内有干货)

图中标识“74 72 73 61 70 70”十六进制转文本后为trsapp,即我们所使用的数据库名。

热身完毕后,我们来看Mysql是怎样交互的。如下图:

基于C的MySQL数据库审计核心实现(内有干货)

      首先mysql是基于TCP协议,所以在建立连接与断开连接时需要三次握手和四次挥手。当客户端连接服务端时,首先TCP三次握手建立连接,随后客户端将用户信息(用户名、密码等)发送与服务端,服务端确认后返回ok_pack/error_pack包信息, OK后,客户端会发送指令至服务端,首先是use database信息,服务端返回ok信息,随后进行select、insert等指令操作。如下图:

基于C的MySQL数据库审计核心实现(内有干货)

客户端要与服务端断开时,发送COM_QUIT(0x01 )数据包,随后进行tcp四次挥手结束。

具体mysql状态码如下:

基于C的MySQL数据库审计核心实现(内有干货)

当我们发送query查询语句时,其流程如下:

基于C的MySQL数据库审计核心实现(内有干货)

客户端发起query查询后,如下图:

基于C的MySQL数据库审计核心实现(内有干货)

mysql返回内容格式为:field_cout字段信息+EOF+返回数据信息,如下图:

基于C的MySQL数据库审计核心实现(内有干货)

1.response返回-file_count字段信息

基于C的MySQL数据库审计核心实现(内有干货)

2.EOF包信息

基于C的MySQL数据库审计核心实现(内有干货)

3.返回数据信息

       EOF Packet,表示结果数据完毕,若此包中的MORE_RESULT不为0 ,则说明接下来还有结果信息,又返回源头继续开始。

如果过程中读到任何一个error包后,读取将结束,并抛出错误。

     以上是mysql协议核心介绍。想必你已经相对了解mysql的协议交互过程啦,如想深入了解,请参考mysql文献资料。

三、效果图及源代码

3.1 navicat请求效果图

3.2 数据包解析效果图

/*
 ============================================================================
 Name        : DatabaseAudit.c
 Author      : wangfeng
 Version     :
 Copyright   : Your copyright notice
 Description : DatabbaseAudit in C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netinet/in.h>
#include <arpa/inet.h>
#include<time.h>
#include "DatabaseAudit.h"



//字节流转换为十六进制字符串
void ByteToHexStr(const unsigned char* source, char* dest, int sourceLen)
{
    short i;
    unsigned char highByte, lowByte;

    for (i = 0; i < sourceLen; i++)
    {
        highByte = source[i] >> 4;
        lowByte = source[i] & 0x0f ;

        highByte += 0x30;

        if (highByte > 0x39)
                dest[i * 2] = highByte + 0x07;
        else
                dest[i * 2] = highByte;

        lowByte += 0x30;
        if (lowByte > 0x39)
            dest[i * 2 + 1] = lowByte + 0x07;
        else
            dest[i * 2 + 1] = lowByte;
    }
    return ;
}


void HexStrToByte(const char* source, unsigned char* dest, int sourceLen)
{
    short i;
    unsigned char highByte, lowByte;
    for (i = 0; i < sourceLen; i += 2)
    {
        highByte = toupper(source[i]);
        lowByte  = toupper(source[i + 1]);

        if (highByte > 0x39)
            highByte -= 0x37;
        else
            highByte -= 0x30;

        if (lowByte > 0x39)
            lowByte -= 0x37;
        else
            lowByte -= 0x30;

        dest[i / 2] = (highByte << 4) | lowByte;
    }
    return ;
}


/* 返回ch字符在sign数组中的序号 */
int getIndexOfSigns(char ch)
{
    if(ch >= '0' && ch <= '9')
    {
        return ch - '0';
    }
    if(ch >= 'A' && ch <='F')
    {
        return ch - 'A' + 10;
    }
    if(ch >= 'a' && ch <= 'f')
    {
        return ch - 'a' + 10;
    }
    return -1;
}

/* 十六进制数转换为十进制数 */
long hexToDecNew(char *source)
{
    long sum = 0;
    long t = 1;
    int i, len;

    len = strlen(source);
    for(i=len-1; i>=0; i--)
    {
        sum += t * getIndexOfSigns(*(source + i));
        t *= 16;
    }

    return sum;
}


void analysisMysql(){

	//define
	//pcap_file_head *pcapFileHead;

	pcap_head *pcapHead;

	frame_head *frameHead;

	ip_head *ipHead;

	tcp_head *tcpHead;

	mysql_request_head * mysqlHead, * mysqlCHHead;
	mysql_request_eof  *mysqlRequestEof;

	int offset=0,dataOffset=54;

	char src_ip[STRSIZE], dst_ip[STRSIZE] ;

	unsigned char *mysqlTmp = (unsigned char *)malloc(sizeof(unsigned char));

	int src_port, dst_port, tcp_flags;
	int i;
	FILE *fp; //文件

	//初始化,动态内存分配
	//pcapFileHead = ( pcap_file_head *)malloc(sizeof( pcap_file_head));

	pcapHead  = ( pcap_head *)malloc(sizeof( pcap_head));

	frameHead = ( frame_head *)malloc(sizeof(frame_head));

	ipHead = (ip_head *)malloc(sizeof(ip_head));

	tcpHead = (tcp_head *)malloc(sizeof(tcp_head));

	mysqlHead = (mysql_request_head *)malloc(sizeof(mysql_request_head));

	mysqlCHHead =(mysql_request_head *)malloc(sizeof(mysql_request_head));

	mysqlRequestEof=(mysql_request_eof *)malloc(sizeof(mysql_request_eof));

	int mysqlEof= 0;

	if((fp=fopen("/home/roo/eclipse-workspace/DatabaseAudit/src/mysql1.pcap","rb"))==NULL){

		 printf("error: can not open pcap file\n");

		 exit(0);
	}

	offset = 24; //pcap文件头结构 24个字节
	//fseek函数是 用来设定文件的当前读写位置.
	while(fseek(fp, offset, SEEK_SET) == 0) //遍历数据包(fseek 文件随机定位 文件/偏移值/从0开始)
	{
	  dataOffset=54;
	  /**
	   * ---------pcap数据帧-------
	   */
		if(fread(pcapHead, sizeof(pcap_head), 1, fp) != 1){
			break;
		 }
		offset += 16 + pcapHead->caplen; //数据包长度
		/**
		  * ---------数据帧-------
		 */
		if(fread(frameHead,sizeof(frame_head), 1, fp)!= 1){
			break;
		}
		printf("destination: %02x:%02x:%02x:%02x:%02x:%02x \n",frameHead->dstMAC[0], frameHead->dstMAC[1],frameHead->dstMAC[2],frameHead->dstMAC[3],frameHead->dstMAC[4], frameHead->dstMAC[5]);
		printf("src: %02x:%02x:%02x:%02x:%02x:%02x \n",frameHead->srcMAC[0], frameHead->srcMAC[1],frameHead->srcMAC[2],frameHead->srcMAC[3],frameHead->srcMAC[4], frameHead->srcMAC[5]);
		printf("frame type: 0x%x---------%x\n", ntohs(frameHead->frameType),frameHead->frameType);
		/**
		 * ---------IP帧-------
		 */

		if(fread(ipHead,sizeof(ip_head), 1, fp)!= 1){
					break;
		}
		inet_ntop(AF_INET, (void *)&(ipHead->SrcIP), src_ip, 16); //将数值格式转化为点分十进制的ip地址格式
		inet_ntop(AF_INET, (void *)&(ipHead->DstIP), dst_ip, 16);//将数值格式转化为点分十进制的ip地址格式
		printf("src_ip:::%s    dst_ip:::::%s  Flag_Segment::::: %x \n",src_ip,dst_ip,ipHead->Flag_Segment);

		/**
			* ---------TCP帧-------
		 */
		if(fread(tcpHead, sizeof(tcp_head), 1, fp)!= 1){
			break;
		}
		 //网络字节序和主机字节序的转换
		src_port = ntohs(tcpHead->SrcPort);
		//printf("-------%d-------%d\n",tcp_header->SrcPort,src_port);
		dst_port = ntohs(tcpHead->DstPort);

		tcp_flags = tcpHead->Flags;
		printf("src_port:::%d    dst_port:::%d  tcp_flags::: %x \n",src_port,dst_port,tcp_flags);

		/**
		* ---------MYSQL帧-------
		*/

		if(fread(mysqlHead, sizeof(mysql_request_head), 1, fp)!= 1){
			break;
		}
		dataOffset+=sizeof(mysql_request_head);
		printf("packetLength:%x \npacketNumber:%d \nlen:%d\n",mysqlHead->packetLength[0],mysqlHead->packetNumber,pcapHead->caplen);
		//遍历column信息,应再判断上一个数据包是否为query请求[返回信息]
		if(mysqlHead->packetLength[0] == 0x01 && mysqlHead->packetLength[1] == 0x00 && mysqlHead->packetLength[2] == 0x00 && mysqlHead->packetNumber == 0x1){

			//读取列数
			if(fread(mysqlTmp, sizeof(unsigned char), 1, fp)!= 1){
				break;
			}
			dataOffset+=sizeof(unsigned char);
			//遍历该tcp会话下的所有mysql的ColumnDefinition包
			//https://blog.csdn.net/dawn_sf/article/details/80800183
			//https://www.callmejiagu.com/2018/10/29/%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%B7%B1%E7%9A%84%E6%95%B0%E6%8D%AE%E5%BA%93%E9%A9%B1%E5%8A%A8%E2%80%94%E2%80%94MySQL%E5%8D%8F%E8%AE%AEResult%E5%8C%85%E8%A7%A3%E6%9E%90%EF%BC%88%E5%85%AD%EF%BC%89/
			//https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnDefinition
			printf("列定义信息 \n");
			for(i=0;i<*mysqlTmp;i++){
				if(fread(mysqlCHHead, sizeof(mysql_request_head), 1, fp)!= 1){ //读取mysql头部
					break;
				}
				dataOffset+=sizeof(mysql_request_head);
				//printf("%d--",sizeof(mysqlCHHead->packetLength[0]));
				//动态创建对应mysqldata内存
				unsigned char *mysqlData = (unsigned char *)malloc(mysqlCHHead->packetLength[0] * sizeof(unsigned char));
				if(fread(mysqlData, (mysqlCHHead->packetLength[0] * sizeof(unsigned char)), 1, fp)!= 1){ //读取mysql数据
					break;
				}
				printf("%s\n",mysqlData);
				dataOffset+=mysqlCHHead->packetLength[0] * sizeof(unsigned char);
			   free(mysqlData);
			//	fseek(fp, (mysqlCHHead->packetLength[0]), SEEK_CUR); //下一个mysql包
			}



			//再读取一个eof包表示ColumnDefinition包流结束.
			//fe 00 00 22 00
			//读取mysql--eof
			if(fread(mysqlRequestEof, sizeof(mysql_request_eof), 1, fp)!= 1){
				break;
			}
			dataOffset+=sizeof(mysql_request_eof);

			//printf("%x---%x--%x--%x--%x",mysqlRequestEof->header,mysqlRequestEof->warnings[0],mysqlRequestEof->warnings[1],mysqlRequestEof->status_flags[0],mysqlRequestEof->status_flags[1]);
			//判断 eof
			if(mysqlRequestEof->header == 0xfe){

				mysqlEof++;

			}
			printf("查询返回信息\n");
			while(dataOffset<=pcapHead->caplen){
				//获取查询的结果信息
				if(fread(mysqlCHHead, sizeof(mysql_request_head), 1, fp)!= 1){
					  break;
				 }
				dataOffset+=sizeof(mysql_request_head);
				//获取请求信息
				char pLenArray[6];
				sprintf(pLenArray,"%02x%02x%02x",mysqlCHHead->packetLength[2],mysqlCHHead->packetLength[1],mysqlCHHead->packetLength[0]);
			   long pLen =hexToDecNew(pLenArray);//读取对应的row内容大小
				unsigned char *mysqlRowData = (unsigned char *)malloc(pLen * sizeof(unsigned char));
				dataOffset+=pLen * sizeof(unsigned char);
				for(i=0;i<pLen;i++){
					if(fread(mysqlRowData, (sizeof(unsigned char)), 1, fp)!= 1){ //读取mysql rom数据
						continue;
					}
					printf("%s",mysqlRowData);
				}
				printf("\n");
				free(mysqlRowData);
			}
			free(mysqlCHHead);
		}else{
			printf("查询信息:\n");
			unsigned char *mysqlData_new = (unsigned char *)malloc(mysqlHead->packetLength[0] * sizeof(unsigned char));
			if(fread(mysqlData_new, (mysqlHead->packetLength[0] *sizeof(unsigned char)), 1, fp)!= 1){
				break;
			}
			printf("%s\n",mysqlData_new);
			free(mysqlData_new);
		}
	}
	fclose(fp);

}

int main(void) {
	analysisMysql();
	return EXIT_SUCCESS;
}

 

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

登峰造Geek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值