大家好,今天分享关于基于C语言的Mysql数据库审计核心实现。本篇首先介绍MySQL网络协议,然后再介绍代码如何通过旁路镜像的方式实现对数据库的审计(我写的是针对pcap包,如果你希望通过旁路镜像方式,稍微改造即可)。

目前业界有非常多的数据库审计产品,基本是以流量或者探针形式对数据库进行综合审计,功能包括:审计查询、攻击检测、越权访问等。其核心基本是建立在操作语句识别及返回结果识别基础上形成的不同功能场景,本文通过对mysql包进行解析,可识别客户端查询语句及服务端返回结果内容,让读者直观了解数据库审计的核心功能如何实现。

1.MySQL网络协议分析

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

mysql数据类型包括:整数型和字符串型。

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

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

Mysql协议包报文格式如下:

mysql client c语言 c语言 mysql数据库_eclipse连接mysql

mysql报文格式

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

mysql client c语言 c语言 mysql数据库_c语言连接mysql_02

COM_INIT_DB抓包

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

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

mysql client c语言 c语言 mysql数据库_eclipse连接mysql_03

Mysql交互示意图

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

mysql client c语言 c语言 mysql数据库_mysql client c语言_04

我本地查询抓包信息

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

具体mysql状态码如下:

mysql client c语言 c语言 mysql数据库_c语言连接mysql_05

mysql状态码

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

mysql client c语言 c语言 mysql数据库_mysql client c语言_06

query语句查询

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

mysql client c语言 c语言 mysql数据库_mysql client c语言_07

select查询

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

mysql client c语言 c语言 mysql数据库_eclipse连接mysql_08

1.response返回-file_count字段信息

mysql client c语言 c语言 mysql数据库_mysql_09

2.EOF包信息

mysql client c语言 c语言 mysql数据库_eclipse连接mysql_10

3.返回数据信息

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

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

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

2.核心代码实现

本文基于C语言,通过对MySQL的协议进行解析,识别查询内容和返回结果。读者可通过搭建libpcap或者pf_ring环境,实时读取数据包内容进行识别输出(本文是读取pcap包),关于libcap、pf_ring,如果你感兴趣,我会抽时间单独写一篇文章。闲话少说,直接撸代码(代码我已注释比较详细)。

实现效果图:

mysql client c语言 c语言 mysql数据库_c语言连接mysql_11

表信息

mysql client c语言 c语言 mysql数据库_字符串_12

识别查询语句

mysql client c语言 c语言 mysql数据库_mysql_13

识别列信息和字段信息

核心代码实现:

/*============================================================================Name:DatabaseAudit.cAuthor:wangfengVersion:Copyright:YourcopyrightnoticeDescription:DatabbaseAuditinC,Ansi-style============================================================================*/#include#include#include#include#include#include#include"DatabaseAudit.h"//字节流转换为十六进制字符串voidByteToHexStr(constunsignedchar*source,char*dest,intsourceLen){shorti;unsignedcharhighByte,lowByte;for(i=0;i>4;lowByte=source[i]&0x0f;highByte+=0x30;if(highByte>0x39)dest[i*2]=highByte+0x07;elsedest[i*2]=highByte;lowByte+=0x30;if(lowByte>0x39)dest[i*2+1]=lowByte+0x07;elsedest[i*2+1]=lowByte;}return;}voidHexStrToByte(constchar*source,unsignedchar*dest,intsourceLen){shorti;unsignedcharhighByte,lowByte;for(i=0;i0x39)highByte-=0x37;elsehighByte-=0x30;if(lowByte>0x39)lowByte-=0x37;elselowByte-=0x30;dest[i/2]=(highByte<<4)|lowByte;}return;}/*返回ch字符在sign数组中的序号*/intgetIndexOfSigns(charch){if(ch>='0'&&ch<='9'){returnch-'0';}if(ch>='A'&&ch<='F'){returnch-'A'+10;}if(ch>='a'&&ch<='f'){returnch-'a'+10;}return-1;}/*十六进制数转换为十进制数*/longhexToDecNew(char*source){longsum=0;longt=1;inti,len;len=strlen(source);for(i=len-1;i>=0;i--){sum+=t*getIndexOfSigns(*(source+i));t*=16;}returnsum;}voidanalysisMysql(){//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;intoffset=0,dataOffset=54;charsrc_ip[STRSIZE],dst_ip[STRSIZE];unsignedchar*mysqlTmp=(unsignedchar*)malloc(sizeof(unsignedchar));intsrc_port,dst_port,tcp_flags;inti;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));intmysqlEof=0;if((fp=fopen("/home/roo/eclipse-workspace/DatabaseAudit/src/mysql1.pcap