step by stepMySQL数据库取证
Posted 电子物证
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了step by stepMySQL数据库取证相关的知识,希望对你有一定的参考价值。
引子:钻研技术,发现问题
上面部分内容可以说只是一个引子,文中咱们简单提过general_log日志但并未作过多探讨。之前发过的几篇文章里或多或少有提到过MySQL取证,但未系统地介绍,说到MySQL取证,大致可以分为MySQL日志分析、MySQL数据恢复和仿真MySQL环境,其中仿真那部分我在上一篇的《大项目》里详细的提到了,这里只对日志分析和数据恢复做一番探讨和一下小小的总结。(这部分主要是让大家简单系统地了解MySQL日志的构成和相关知识,能力有限,想具体深入地分析请看文章底部的参考来源。成文匆忙,若发现纰漏请及时告知,谢谢)
我们先来探讨MySQL日志
根据我的实战经验以及网友分享的文章, MySQL的日志主要体现在 记录SQL操作的general日志(注Linux下本机操作还会有.mysql_history)、变更日志binlog(主要用于备份和数据恢复)、以hostname.err格式存放的错误日志和用于优化目的的slow日志。
一、通用查询日志(general query log)
通用查询日志用于记录MySQL上所有用户执行的SQL命令(但不记录返回结果),该功能类似Linux系统下的.bash_history,无论用户执行的命令和语法是否正确都将被记录。默认情况下MySQL不会开启该功能,这是因为数据库本身就占用服务器大量的读写操作,开启后会不断进行写入操作,这将大大地增加系统开销,对企业而言这也是一个不必要的负担;但是另一方开启该功能可以方便数据库管理员审计和分析MySQL中可能存在的问题,提高服务的安全性和可靠性。
查看general_log是否开启及配置信息。执行SQL命令:show variables where variable_name like "%general_log%" or variable_name = "log_output"; ,意思是通过MySQL环境变量来查看general_log是否开启、日志的保存方式及位置。其中日志的存放方式分为两种(file和table)、方案分为三种(file、table和file&table),若是file型则以文件的形式默认存放在data目录,若是table型则将所有SQL记录转存至mysql库中的general_log表中。
general_log的表结构。该表主要由6个字段构成,分别是用于记录时间的event_time、记录连接者IP和主机名的user_host、识别每次操作线程号的thread_id、区分主从(猜测)的server_id、记录操作类型的command_type和最重要地用于记录每次SQL命令的argument字段。
简单介绍数据库安全方面。前面提到过MySQL审计,我查阅了相关资料,按实现的方式可以分为 使用普通/二进制日志、数据库审计插件、网络旁路等,这里我们不做过多的介绍了,感兴趣的朋友可以看看本章底部的参考来源。在数据库安全方面还有一个重要的知识要介绍,那就是基于MySQL的提权,在取证的时候我们也要留意。
a.利用MySQL的提权方式主要分为三种,分别是udf提权、mof提权和利用漏洞提权;前两者提权方式都是基于MySQL本身就运行在较高权限下,借助其高权限来执行相关操作,如udf(用户自定义函数)是通过自写函数来实现执行命令或者后门的功能、mof(托管对象格式)则是利用系统的检测机制来执行命令,而还有一种提权方式就需要借助系统漏洞了,如CVE-2016-6664等。前两种方式的痕迹(若入侵者未删除的情况下)可以通过查看c:\windows\system32\wbem\mod和mysql\lib\plugin目录来查找相关的提权文件,以及还可以通过执行 select * from mysql.func来查询自定义函数,利用漏洞提权一般来说无法确定文件。
b.一种简单检测WEB入侵的方法。前几年一种新型基于MySQL拿webshell的方式流传于各黑客之间,这种方式就是以general_log为载体、以记录恶意代码为目的,写入到网站目录中实现getshell的。因此我们溯源取证时,可以使用SQL命令”show VARIABLES where VARIABLE_NAME like '%general%' “来查看general_log有无改动。
c.最后还有一个检测 利用SQL注入入侵数据库的思路,通过执行” select * from information_schema.COLUMNS where TABLE_NAME like "%sqlmap%" ”来查询数据库中是否存在sqlmapoutput一类 黑客用于临时存放数据的表。在数据库安全这方面资料都很多,不再赘述了,感兴趣的朋友可以自行阅读一下这方面的资料。
参考来源:
《数据库审计方案简介和功能对比》 https://blog.csdn.net/sun_ashe/article/details/95189664
《mysql数据库提权总结》https://uuzdaisuki.com/2018/07/02/mysql%E6%95%B0%E6%8D%AE%E5%BA%93%E6%8F%90%E6%9D%83%E6%80%BB%E7%BB%93/
《Windows下三种mysql提权剖析》 https://xz.aliyun.com/t/2719
二、二进制日志(binary log)
二进制日志是MySQL上最重要的日志,它记录MySQL上的所有数据的变更操作,但不包含select和show这类无修改数据的记录,往往被用于备份、审计数据库和复制主从数据库等。
(1)查看binary_log状态及配置信息。这里我使用SQL命令” show variables where variable_name like "%bin%";”来查看其状态和配置信息,其中MySQL环境变量中较为重要的有binlog_format、log_bin和sql_log_bin。log_bin是用于二进制日志是否开启的,ON表示开启、OFF表示未开启,默认是OFF;binlog_format是规定了二进制的存放方式,STATEMENT以语句的形式记录,ROW以数据修改的形式记录,MIXED以语句和数据修改混合形式记录;sql_log_bin表示是否对二进制日志进行日志记录,ON表示执行记录,OFF表示不执行记录。
(2)开启及配置binary_log。由于默认情况下binary_log并未开启,并且不像 通用查询日志那样支持用SQL命令直接开启,所以我们需要手工到mysql\my.ini里修改。其中binlog_format参数尤为重要,它影响了记录二进制日志的格式,分为三种格式: STATEMENT、ROW和MIXED。具体对其展开描述如下(以下摘自《Mysql日志详解》,作者pengliangyuan):
1.STATEMENT:记录对数据库做出修改的语句,比如,update A set test='test',如果使用statement模式,那么这条update语句将被记录到二进制日志中,使用statement模式时,优点是binlog日志量少,IO压力小,性能高,缺点是为了尽可能一致的还原操作,除了记录语句本身外,可能还需要记录一些相关信息,而且,在使用一些特定函数时,并不能保证恢复操作与记录完全一致。
2.ROW:记录对数据库做出的修改的语句所影响到的数据行以及这些行的修改,比如,update A set test='test',如果使用row模式,那么这条update所影响到的行所对应的修改,将会记录在binlog中,使用row模式时,优点是能完还原和复制被日志记录时的操作,缺点是日志量较大,IO压力比较大,性能消耗比较大。
3.MIXED:混合上述两种模式,一般使用statement方式进行记录,如果遇到一些特殊函数使用row方式进行记录,这种记录方式称为mixed。
Server_id参数一般配置为0,主要是用于区分主从数据库的;sync_binlog决定二进制日志写入磁盘时机,如果sync_binlog为0,由操作系统来决定什么时候写入磁盘;log-bin参数用于表示开启及指定二进制日志的名字等。以上工作准备完毕后,重启MySQL后看到mysql\data目录下新增了以mysql-bin开头的文件说明配置成功。
(3)分析binary_log结构。二进制日志主要由两部分文件组成,分别是二进制日志索引文件(文件名后缀为.index)和二进制日志文件(文件名后缀为.00000*),前者用于记录所有的二进制文件,后者记录数据库所有的DDL和DML(除了数据查询语句)语句事件。这里重点介绍后者的文件格式( 这部分我基本上是按照参考来源里的文章 ):
a.binlog 由 event 组成,event 是 binlog 的最小逻辑单元;
b.文件头的头四个字节为固定的 BINLOG_MAGIC (fe 62 69 6e),后三个为 ASCII 码的 bin;
c.接着的四字节是 descriptor event (FORMAT_DESCRIPTION_EVENT),记录了版本信息等;
d.文件末尾是 log-rotation event (ROTATE_EVENT),记录了下个 binlog 文件名;
上述两个even中间是各种不同的 event,每个event代表数据库中不同的操作。具体字节分析如下:
FE 62 69 6E //binlog的Magic Number,通过宏BINLOG_MAGIC定义,在open_binlog()中写入事件的头部信息。
0F //日志类型,其他比较重要的类型分别是 0x02 QUERY_EVENT;0x04 ROTATE_EVENT;0x0f FORMAT_DESCRIPTION_EVENT;0x10 XID_EVENT;0x13 TABLE_MAP_EVENT;0x1d ROWS_QUERY_EVENT;0x1e WRITE_ROWS_EVENTv2;0x1f UPDATE_ROWS_EVENTv2;0x20 DELETE_ROWS_EVENTv2;0x21 GTID_EVENT;0x22 ANONYMOUS_GTID_EVENT;0x23 PREVIOUS_GTIDS_EVENT
01 00 00 00 //server_id,0x01这里跟我们指定的1是一致的。
67 00 00 00 //代表整个event的长度,长度是103。
6B 00 00 00 //下个事件的开始位置,0x6b(107)与前一个值正好差4。
01 00 //flags,每个events中flags代表的意义大致相同。在此处 00 00 代表binlog已经关闭, 00 01代表binlog仍开启,具体详见https://dev.mysql.com/doc/internals/en/binlog-event-flag.html。
04 00 //binlog版本信息,mysql从5.0后日志都是v4版本的。
35 2E 35 2E 35 33 2D 6C 6F 67 //MySQL的版本,0x352e352e35332d6c6f67(5.5.53-log)
1. 《binlog二进制文件解析》 https://blog.csdn.net/zl1zl2zl3/article/details/89068684
2. 《MySQL Binlog解析》 https://blog.csdn.net/u013256816/article/details/53020335
3. 《MySQL Binlog解析(1)》 https://www.cnblogs.com/mysql-dba/p/9901655.html
4. 《关于binary log那些事》 https://blog.csdn.net/m48o8gewuc/article/details/72888092
(4)查看binlog日志内容。通过上节可知二进制日志无法直接被查看,因此需要借助一定的工具来实现解析,这里我搜集了几种方式:
a.使用用自带的mysqlbinlog。在命令提示符里运行mysqlbinlog并指定要查看的binlog文件即可。下图中第5行表示在二进制里的偏移字节值(4),第13行分别包含了事件的时间、数据库的server_id(1)、下一个事件的偏移字节(229)、事件类型(query)、执行语句的线程id(1)、消耗的时间和错误代码。
b.使用SQL命令查看。除了命令行外,MySQL还为用户提供了SQL查询的方式:
SHOW BINLOG EVENTS
[IN 'log_name'] //要查询的binlog文件名
[FROM pos] //指定偏移值
[LIMIT [offset,] row_count]
c.第三方工具,如binlog2sql。它提供的功能比官方多,根据不同选项,你可以得到原始SQL、回滚SQL、去除主键的INSERT SQL等。这里不做讨论,感兴趣的朋友可以去测试下。
增删改查测试及数据恢复。这里为了充分地模拟增删改查操作,我将之前学习ThinkPHP时创建的temp库先备份出一SQL脚本,然后drop掉整个temp库和删除掉data目录下所有mysqlbin文件后重启MySQL,目的是保证mysqlbin日志在模拟前不受污染。此时think_user表中存在18条数据。
a.使用sql脚本还原测试环境。创建一个空的temp库后,将sql脚本导入至MySQL,这时候会发现mysqlbin大小发生了变化。
b.进行增删改操作。接着我们用数据库管理工具对数据进行增删改操作,整个过程随意修改、增加或者删除。经过一番操作,我的think_user表里仅剩15条数据。
c.利用binary_log恢复数据。前两节都提到过log_pos(事件的偏移值),懂行的朋友想必就能猜到它的用途,这是一种基于偏移值的数据库恢复,需要我们先确定开始偏移和结束偏移的范围,这种方式十分的精确;另一种就是基于日期时间范围的数据库恢复,顾名思义就是确定好要恢复的日期时间区间。这里我就第一种方式进行演示:
1.确定偏移值的范围。由于我们要恢复的数据范围是 创建thin_user表以后且在alter之前,所以通过show binlog events的方式很容易定位到确切的偏移点。本次案例初步判断偏移值在2839附近。
2.核实偏移范围。在实战中,面对企业级的数据量,我们必须尽全力保证数据的完整性,因此需要再次核实几遍(恢复数据前必然要创建多个镜像用于测试,并且不能直接恢复至生产环境)。这里我用之前mysqlbinlog.exe工具导出的sql为参考样本,工具对其中的数据做了base64编码,因此实战中可以使用”mysqlbinlog.exe --no-defaults --base64-output=decode-rows –v mysqlbin.xxx >1.sql”来查看解密后的数据。最终以2839为起始点开始核查,发现3117处才是我们想要的数据,因此起始偏移值应该为2233、结束偏移值为3697。
3.恢复数据。处于谨慎考虑,我这里先将要从binlog中恢复的数据转存为sql,然后再批量导入。首先执行” mysqlbinlog.exe --no-defaults --start-position=2233 --stop-position=3697 mysql-bin.000001 >3.sql”,接着对结果进行核实后再执行批量导入,最终在temp库中执行select count(*) from think_user来确定数量,通过以下截图发现与开始的完全一致,因此恢复数据是成功的。
参考来源:
1. 《mysql binlog详解》 https://www.cnblogs.com/kevingrace/p/5907254.html
2. 《MySQL_通过binlog查看原始SQL语句》 https://juejin.im/post/5b6270d9f265da0f742effc7
3. 《腾讯工程师带你深入解析 MySQL binlog》 https://juejin.im/post/5a72c2daf265da3e5234d879
4. 《教你MySQL Binlog实用攻略》 https://zhuanlan.zhihu.com/p/54520097
5. 《使用binlog日志恢复MySQL数据库删除数据的方法》 https://zhuanlan.zhihu.com/p/51050426
至于错误日志和慢日志就不作介绍了,重点介绍general_log和binary_log两种。对错误日志和慢日志有兴趣的朋友请看这些文章:
1. 《Mysql 几种日志分析》 https://blog.csdn.net/u010889390/article/details/50404455
2. 《探究MySQL中的日志文件》 https://juejin.im/post/5b7c0aabf265da438415b9eb
在了解完以上MySQL日志分析的知识后,我们再回顾之前提出的问题:如何使MySQL监控工具支持ThinkPHP等程序的语句监控?通过对SQL预处理的测试,我们得知问题很有可能出在prepare上,现在对这个问题进行探讨。
在ThinkPHP执行查询操作,并打开general_log功能。这时候发现TP确实将SQL语句做了预处理,而且根据前面所学知识可知:线程id 36表示这是同一批SQL操作,整个查询过程经历了 连接到数据库、准备编译预处理、执行查询、释放预处理缓存和关闭连接。
在这个过程里,我们没有必要关注prepare类型,因为它最终还会在execute类型里携带参数完成执行。所以我们可以进一步推测出问题是出在MySQL监控工具上,掌握了MySQL监控工具的原理,完全可以自己写一个了,但先森对该工具用得很顺手了,所以想在程序的基础上进行优化处理。借此又引出了下文,关于逆向工具并添加功能的过程。
先森虽然略懂WEB安全且在这方面小有心得,但在二进制方面基本上两眼一抹黑,“滴水穿石非一日之功”,二进制方面需要扎实的计算机基础和更加完备、体系化的知识。以前虽然简单摸过IDA pro,但是完全是摸石头过河,不能真正上手实战逆向。这些天在家一边学习TP、一边研究汇编和逆向工程,刚好可以借助这个机会更好的了解IDA、洞悉逆向。
将“Seay源代码审计系统.exe”拖入到IDA主界面,程序自动停留在入口处,将Graph视图切换到text,满屏是琐碎、不可读的代码块。才疏学浅,看到这个场景,就好像从中学突然跳级到大学做高等数学,从劝退到直接自闭……
硬着头皮看了一圈,发现真的不是给人读的代码……于是想到是不是程序做混淆了或者加壳压缩导致无法逆向,于是找工具查看一下PE信息,发现是C#编写的,那这就容易了,因为C#的程序可以借助专用工具直接逆向改源码。
祭出C#逆向大杀器“dnSpy”,将所有与目标相关的程序都丢入进去(注,非.net框架自带且目标程序依赖的组件也要放进去),即可查看代码。
翻了两圈没找到“mysql监控”这款子程序,中间走了好多弯路、浪费了好多时间。这里简单把我当时的思路讲一下,当时考虑到这是主进程下面的子进程,那么如果把这个子程序运行起来再从内存里转存出来岂不是就可以得到它的真身了嘛?我认为这个思路本身上是没有问题,毕竟什么动态调试、脱壳和ESP定律都是这么来的,那么这理论上也是可行的了。于是在任务管理器里选择转存内存中该程序的所有数据。
用日志查看工具LogViewer来查看内存文件。通过搜索Windows上可执行程序的PE头特征来验证我的想法,确实搜索到几个PE头,但问题也来了,怎么才能完整导出来呢?
于是在另一款强大的进程管理工具ProcessHacker里又捣鼓了起来。当我查看到Modules选项时,发现MySQL监控工具其实是以dll形式加载的。
重新回到开始的Dnspy里,通过图中可以看到主程序里确实存在一个LoadAllPlugins方法。那现在可以挽起(小编注:此处有修正)袖子大干一场了,接着将MysqlMonitoringPlus.dll拖入到dnspy里查看源代码。
简单通读了一下工具的代码,了解到工具的大致运行原理:1.点击“更新”按钮会尝试连接到MySQL并设置general_log的相关环境,接着提取当前时间段的所有SQL记录。2.点击“下断”按钮,将当前时间赋值到局部变量,配合“更新”使用。通过对工具代码的分析,可以看到工具本身不难开发,主要用的顺手加之没太大问题,完全可以通过逆向工程修正。
我们先来到定义button2_Click方法处,“更新”按钮是这款工具的核心。将m_str_sqlstr变量中的SQL语句进行修改,根据前面所知还需要添加execute类型,右击选择“编辑方法”,将此处修改成command_type in ('Query','Execute'),意思是筛选command_type字段中符合这两个类型的所有记录,接着选择“编译”来完成修改,在左上角的“文件”里点击“保存模块”完成写入。
我们运行测试一下,发现每次更新操作后,会夹杂好多重复的无用语句,如查看变量、设置环境等,这十分的影响阅读体验及很不方便。
其实作者也考虑到了这一点,所以在语句后面追加了一条筛掉杂项语句的条件,但他的语句不完善。因此我们需要继续修改argument,这里借助MySQL的正则函数来定义“黑名单”,修改成argument not REGEXP ('^(set global|SHOW VARIABLES|SET NAMES).*|general_log'),意思是筛掉argument字段中 以设置或查看环境变量开头 且 包含general_log 关键字的所有语句。最终这一行代码就改成了:string m_str_sqlstr = "select event_time,argument from mysql.general_log where command_type in ('Query','Execute') and argument not REGEXP ('^(set global|SHOW VARIABLES|SET NAMES).*|general_log') and event_time>'" + this.var_datatime + "'";
这时候再测试就发现我们获取的内容不受杂项语句的影响,运行结果如下:
这时候想到还有一个问题,工具并不会主动清空general_log表,“冰冻三尺非一日之寒”,长此以往会导致浪费不必要的空间从而影响了MySQL的正常运行。所以我想再增加一个清空并关闭general_log的功能,核心SQL很简单,执行set global general_log=off; 和truncate table mysql.general_log。由于从未接触过C#编程,中间又在网上查阅了相关资料、简单的学习了C#的基本语法。在dnspy的左侧,随便选择一个方法右击“编辑类”,这时候就可以全面修改程序了,首先定义一个button类型的控件。
其次实例化这个控件并设置属性。在给这个控件定义一些属性,如布局在窗体上的的位置、大小和其他属性等。
接着定义一个方法,它的功能是执行清空general_log表及成功后弹出窗口。
最后回到我们定义这个按钮属性的位置,新增一行代码,功能是点击后调用我们定义的buttonReset_Click方法实现清空并关闭general_log。
这时候编译并运行修改后的插件,发现我们定义的按钮并未出现。不要担心,这是因为我们没有在窗体里添加这个控件导致的,所以在后面添加控件的底部新增一行。
最后运行并测试相关功能,完美、搞定!大家可能会看到程序的右上角按钮出现不和谐的情况,这是因为程序里的参数是按照我的最终修改版,即增加程序说明、优化功能等。
目前想要使用或者调试这个工具(准确的说是“插件”),需要先打开”Seay源代码审计系统”然后再选择mysql监控插件,这十分不方便。如果能提取出一个独立版,这最后一个问题就可以迎刃而解了。
在VS2010里新建一个C#工程,注意选择“Windows窗体应用程序”和.Net Framwork的版本为4.0,后面一个环节跟这个版本有重要关系。
由于创建的程序是用于引导dll启动的,所以需要放置在 项目文件夹/bin/debug 下,然后添加引用进来。
接着在Program.cs里编写调用dll的代码。首先在顶部新建一行using MysqlMonitoringPlus;表示引入使用某个命名空间,类似C++的头文件。最后把主函数main中程序的入口指向我们的F_MysqlMonitoring。
其中Form1.cs可以直接删除掉,因为MysqlMonitoringPlus.dll本身就是窗体程序我们不需要再新建一个,只是缺少一个调用入口而已。到目前为止,编译成品还是分离的(即一个引导用途的exe和一个功能dl),运行结果如下。
整合dll并编译进exe中。网上搜了不少关于如何将dll编译进exe的文章,也走了不少弯路,这里我找到一个傻瓜式的方案。
1.首先在vs里安装Nuget扩展。这个扩展应该个类似Linux系统上的apt软件包管理器或者是python上的pip工具。安装流程:”工具”-“扩展管理器”,在扩展管理器里搜索并安装“Nuget”,安装完成如下。
2.安装Costura.Fody插件。选择”工具”-“NuGet程序包管理器”-“程序包管理器控制台”,打开其控制台,键入” Install-Package Costura.Fody -Version 1.6.2”进行安装,成功安装后引用里多出一个Costura,这就说明安装完成了。(注意,这个插件只支持.net4.0以上。)
3.最后按之前的编译流程完成整合。这时候来到Release目录发现程序大小发生了变化,将程序拖到另一个虚拟机运行和测试,发现很OK。
最后根据自己的需求可以作进一步优化,如添加程序的图标、增添新的功能和加壳压缩体积等,这里就不作过多的介绍了。
这里还有个小插曲,明明在VS里的release版工具运行很完美(见上图),可是我把它放入到备份区测试就丢失了俩功能,是傲娇的Bug还是备胎的反抗?在仔仔细细的编译、删除、再调试的过程里,发现每次只要不在备胎区、这个程序就保证运行没问题,一旦在备胎区运行就容易出问题。正当这次准备将它丢入回收站,忽然想到我备份区还有一个MysqlMonitoringPlus.dll文件,联想到在学习C#的时候看到一篇关于将dll整合进exe的文章,里面就提到了一个思路:将dll嵌入exe后再加入一个检测功能,当exe检测不到程序根目录的dll时就会自动调用资源里的dll。说白了,这就是传说中的dll劫持,于是按照这个原理改了一个带有弹窗功能的dll来验证我的想法,效果如下。
总结:
从研究MySQL日志及取证到逆向修改工具用了几天时间,后面查阅资料整理成文章,最终用了一个星期的时间。整个过程学习到了很多,比如MySQL的取证与反取证、C#的开发逆向和二进制对抗(花指令和劫持等);这也更加坚定、明确了自己的学习目标和方向,学海无涯、不断努力,将来尽自己的微薄之力贡献社会。
以上是关于step by stepMySQL数据库取证的主要内容,如果未能解决你的问题,请参考以下文章
[py]python写一个通讯录step by step V3.0
Microsoft SQL Server 2008 MDX Step by Step中关于MDX Step-by-Step.abf损坏文件的处理