【IT168 技术文章】
提起日志,可能你会一下子想到操作系统日志,数据库日志,但今天我们不说那些日志,今天要说的是数据库监听器日志,监听器日志记录了监听器所有操作,包括成功的和不成功的,从日志中还可以发现针对监听器的攻击行为,因为监听器常常是黑客攻击Oracle数据库的首要目标,本文主要是想介绍一下从监听器日志中挖掘一些有用的信息。
监听器日志是一个标准的文本文件,但如果直接用文本编辑器,如记事本打开它来寻找想要的信息可能有点困难,这里介绍另一种方法,使用扩展表和SQL语句来查找,将日志中的每一行填充为扩展表中的每一条记录,下面介绍具体的实现步骤。
1、创建日志目录对象
as 'C:\oracle\product\10.2.0\db_1\network\log\'
/
如果你不清楚你的监听器日志放在哪里,可以使用lsnrctl status命令查看,在命令输出结果中就指出了监听器日志文件的路径。
2、创建扩展表
(
line varchar2(4000))
organization external (
type oracle_loader
default directory LISTENER_LOG_DIR
access parameters (
records delimited by newline
nobadfile
nologfile
nodiscardfile
)
location ('listener.log')
)
reject limit unlimited
/
如果有多个监听器,那么在location处,要做相应的修改,这里的日志文件名要与lsnrctl status输出的保持一致。
现在已经可以查询full_listener_log表了,如查询日志写入信息:
2 where line like 'Log messages written%'
3 /
然而查询出来的信息还是非常原始的,我们的目标是要找出真正有用的信息,于是要对日志文件的每一行的内容再进行分解,通常,监听器日志文件大部分行都含有下列字段:
A)日志条目的日期和时间戳
B)客户端连接使用的字符串
C)客户端使用的协议信息(TCP/IP,端口号等)
D)客户端的行为,如状态,建立连接等
E)客户端连接字符串中的服务名
F)客户端行为返回的代码,如果返回0,表示操作成功,否则就显示错误代码
每个字段之间使用星号(*)进行分隔。注意不是每条日志条目都遵循这个格式,如下面的日志内容:
Copyright (c) 1991
系统参数文件为C:\oracle\product\10.2.0\db_1\network\admin\listener.ora
写入C:\oracle\product\10.2.0\db_1\network\log\listener.log的日志信息
写入C:\oracle\product\10.2.0\db_1\network\trace\listener.trc的跟踪信息
跟踪级别当前为0
它们每一行是一个整体,无法进行分解。对于这些总结性的信息,我们可以忽略它们。还有一些条目大致遵循前面所说的格式,但有些字段没有,如下面这条监听器注册信息:
它只包括了四个字段:时间戳,行为,服务名和返回代码。即使只有四个字段,也还是可以分解它们,只不过要注意字段的对应了。所以要建立一个标准表也不困难,下面就是创建日志分解扩展表的脚本:
(
log_date date,
connect_string varchar2(300),
protocol_info varchar2(300),
action varchar2(15),
service_name varchar2(15),
return_code number(10)
)
organization external (
type oracle_loader
default directory LISTENER_LOG_DIR
access parameters
(
records delimited by newline
nobadfile
nologfile
nodiscardfile
fields terminated by "*" lrtrim
missing field values are null
(
log_date char(30) date_format
date mask "DD-MON-YYYY HH24:MI:SS",
connect_string,
protocol_info,
action,
service_name,
return_code
)
)
location ('listener.log')
)
reject limit unlimited
/
和前一个扩展表创建脚本一样,可能你要修改一下localtion位置的日志文件名,创建好后,该表的结构如下:
CONNECT_STRING VARCHAR2(300)
PROTOCOL_INFO VARCHAR2(300)
ACTION VARCHAR2(15)
SERVICE_NAME VARCHAR2(15)
RETURN_CODE NUMBER(10)
下面对其中两个有嵌套信息的字段的含义进行说明:
a) CONNECT_STRING
它包含的内容是数据库连接信息,连接字符串又可以拆分成多个组件:
SID – Oracle SID,只有当用户使用SID进行连接时才有该值,否则就为空。
CID – 它下面又包括多个组件:
(1) PROGRAM 客户端使用的程序名字
(2) HOST 客户端主机名称
(3) USER 操作系统用户名,在客户端就是以该用户登陆向数据库发出命令的
SERVER – 显示客户端使用的连接类型,包括DEDICATED或SHARED两种。
SERVICE_NAME –如果客户端使用的是服务名而不是SID进行连接,这里显示的就是所使用的服务名了。
COMMAND – 用户发出的命令,也有可能是其它进程发出的命令,用户可能发出如“lsnrctl status”命令查看监听器的状态,pmon进程可能发出“service_update”命令用它载入的数据更新监听器,总的来说,COMMAND命令可以有下面几个:services,status,stop,service_register,service_update,service_died。
SERVICE – 只有发出了监听器控制命令如status,services后它才会出现,它又包括了下面的信息:
(1) DESCRIPTION
(1.1) ADDRESS
(1.1.1) PROTOCOL
(1.1.2) HOST
(1.1.3) PORT
FAILOVER_MODE – 只有当客户端使用了故障转移连接字符串它才会出现,它又包括了四个元素:
(1) TYPE 故障转移的类型,如BASIC。
(2) METHOD 故障转移的方法,如PRESELECT。
(3) RETRIES 客户端重试的次数。
(4) DELAY 在连接尝试间的延迟。
值得注意的是,在监听器日志文件中,不是上面所说的每一个元素都会出现,如客户端使用服务名连接数据库时,就不会出现SID,出现这种情况时,对应的字段内容就为空,如(SID=),等号后面没有任何值,这个时候就要注意在查询结果时你要知道它的含义。
b) PROTOCOL_INFO
协议信息,它又包括了下面的元素:
PROTOCOL – 客户端连接时使用的协议,如TCP,IPC。
HOST – 客户端主机的IP地址。
PORT – 监听器连接使用的端口号(注意不是监听器监听的端口号)。
3、创建分析函数
到此为止,我们以及把监听器日志装入一个扩展表(full_listener_log),然后又将日志信息进行了分解,并大致分解为6个字段,使用扩展表(listener_log)进行存储,但在listener_log这个表中存储的字段信息还有嵌套信息,也就是说还可以进行分解,可以再设计几个表来存储进一步分解的信息,但这里我使用一个函数来完成这件事情,它接收两个参数:查询时使用的字段值和要查询的字符串:
2 (
3 p_in varchar2, // 你要在哪个字段中查询
4 p_param in varchar2 //你需要查询的字符串
5 )
6 return varchar2
7 as
8 l_begin number(3);
9 l_end number(3);
10 l_val varchar2(2000);
11 begin
12 if p_param not in (
13 'SID',
14 'SERVICE_NAME',
15 'PROGRAM',
16 'SERVICE',
17 'HOST',
18 'USER',
19 'PROTOCOL',
20 'TYPE',
21 'METHOD',
22 'RETRIES',
23 'DELAY',
24 'PORT',
25 'COMMAND'
26 ) then //定义要查询的字符的有效范围
27 raise_application_error (-20001,'Invalid Parameter Value '||p_param);
28 end if;
29 l_begin := instr (upper(p_in), '('||p_param||'=');
30 l_begin := instr (upper(p_in), '=', l_begin);
31 l_end := instr (upper(p_in), ')', l_begin);
32 l_val := substr (p_in, l_begin+1, l_end - l_begin - 1);
33 return l_val;
34* end; //定义要查询的实际的值是否存在,而不是括号和等号的位置
4、测试分析函数
至此,准备工作已经就绪,可以开始进行数据挖掘了,但开始之前先测试一下前面这个函数是否可以正常工作,我们查询一下CONNECT_STRING字段的值:
返回:
--------------------------------------------------------------------- (CONNECT_DATA=(SID=proprd1)(CID=(PROGRAM=)(HOST=__jdbc__)(USER=)))
(CONNECT_DATA=(CID=(PROGRAM=)(HOST=prolin01)(USER=oraprol))(COMMAND=status)(ARG
UMENTS=64)(SERVICE=LISTENER_PROLIN01)(VERSION=168821760))
(CONNECT_DATA=(SID=proprd1)(CID=(PROGRAM=)(HOST=__jdbc__)(USER=)))
(CONNECT_DATA=(SID=proprd1)(CID=(PROGRAM=)(HOST=__jdbc__)(USER=)))
下面我使用这个分析函数来分析一下变量USER的值,实际上,从前面返回的结果已经看出,有的行USER变量有值,有的行USER变量没有值,按照前面函数给出的定义,要给函数提供两个参数:CONNECT_STRING和’USER’,如:
SQL> select parse_listener_log_line(connect_string,'USER')
2 from listener_log;
PARSE_LISTENER_LOG_LINE(CONNEC
-------------------------------------
?
?
?
?
?
?
oraprol
?
?
这里使用?代替空值显示,从函数返回的结果看,它能够正常分析出给定变量的值。
5、开始挖掘
从监听器日志文件中能挖掘出些什么有用的信息呢?下面就列出几个挖掘的方向,如果你受到启发,完全可以进一步进行挖掘。
5.1.查看监听器停止情况
因为监听器每次启动和停止都会在日志中记录,所以可以从日志中挖掘出所有的监听器停止信息,无论是正常停止还是异常停止,在监听器日志中的CONNECT_STRING部分的COMMAND字段都会显示为“stop”,下面是具体的SQL语句:
col l_user format a20
col service format a15
col logdate format a20
select to_char(log_date,'mm/dd/yy hh24:mi:ss') logdate,
parse_listener_log_line(connect_string,'HOST') host,
parse_listener_log_line(connect_string,'USER') l_user,
parse_listener_log_line(connect_string,'SERVICE') service
from listener_log
where parse_listener_log_line(connect_string, 'COMMAND') = 'stop';
输出内容为:
-------------------- ---------------- ----------------- ---------------
10/16/05 05:35:41 test_host01 test_user01 LISTENER
10/27/05 21:04:50 prolin01 oraprol LISTENER
才输出内容可以看出,在10/16/05 05:35:41,用户test_user01在主机test_host01上对LISTENER这个监听器执行过stop命令。
5.2.查看程序使用情况
如果想知道都有些什么程序访问过数据库,可以使用动态视图v$session得到当前会话的程序名称,但对于历史记录可能只有通过监听器日志来查找了,在监听器日志的CONNECT_STRING字段中包含了一个PROGRAM变量,它的值就是客户端连接数据库时使用的程序名称了,下面是挖掘程序使用情况的SQL语句:
col cmt format 999,999
select parse_listener_log_line(connect_string,'PROGRAM') program,
count(1) cnt
from listener_log
group by parse_listener_log_line(connect_string,'PROGRAM');
输出内容如下:
---------------------------------------------------------------------- C:\oracle\ora90\bin\sqlplus.exe 200
C:\opt\TOAD\TOAD.exe 5
1093
从上面的输出内容中可以看出,最常用的是sqlplus.exe,最后的1093表示自Oracle数据库安装以来总的会话数,它并不等于前面程序使用次数的和,因为还有一些会话使用的可能是其它访问机制。
5.3.服务名使用情况
如果你有一个RAC数据库环境,你可能会使用一些诸如负载均衡的方案,实现会话故障字自动转移,客户端连接数据库就必须使用服务名,而不能使用SID来连接数据库,下面是一个客户端连接tnsnames.ora文件的部分内容:
(DESCRIPTION =
(LOAD_BALANCE = on)
(FAILOVER = on)
(enable = broken)
(ADDRESS = (PROTOCOL = TCP)(HOST = host1)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = host2)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = host3)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = ora)
(FAILOVER_MODE =
(TYPE = SELECT)
(METHOD = BASIC)
(RETRIES = 120)
(DELAY = 2)
)
)
)
注意这里使用的服务名是ora,有三个节点host1,host2,host3,在这个例子中,如果节点host1失效了,连接到它的会话就会自动转移到其它有效节点,如果使用SID连接的话,就不能自动转移,并且在CONNECT_STRING字段中的SID变量就会不为空,如果使用服务名连接,就会为空。使用下面的SQL语句查询还有多少人在使用SID连接,而没有使用服务名:
select parse_listener_log_line(connect_string,'SID') sid, count(1) cnt
from listener_log
group by parse_listener_log_line(connect_string,'SID');
输出结果:
--------------- ----------
ora 1
ora02 16292
从输出结果可以看出还是有人直接使用了SID:ora和ora02连接数据库,但实际上没有哪个SID叫做ora,这可能是有人在猜测SID,这样就可以采取进一步措施,查看究竟是哪些机器这样在连接:
输出结果为:
CONNECT_STRING :
(CONNECT_DATA=(SID=ORA01)(CID=(PROGRAM=)(HOST=__jdbc__)(USER=)))
PROTOCOL_INFO : (ADDRESS=(PROTOCOL=tcp)(HOST=192.168.1.105)(PORT=3164))
ACTION : establish
SERVICE_NAME : ORA
RETURN_CODE : 12505
这里为了方便阅读,我将字段竖向显示了,从中可以看出客户端的IP地址为192.168.1.105,使用的是TCP协议进行连接,主机名是_jdbc_,这通常表明它是一个JDBC瘦客户端,而且还有日期和时间,这样就可以进行目标定位了,从IP地址看,这应该是来自内网的一名用户,于是可以找到使用IP的人员,告诉他在什么时候,企图使用SID ora连接数据库,但他失败了,相信对方一定很吃惊!
5.4.监听器密码
如果为监听器设置了密码,那么客户端在连接时也必须提供密码才能连接,如果黑客或不怀好意的人尝试进行字典破解,那么破解过程会在监听器日志中留下记录,当使用了不正确的密码进行连接时,会在日志中记录下TNS-01190错误,但在记录这样一条日志信息时,只有四个字段,而不是常见的六个字段,我们只要明白最后一个表示返回代码,下面是一条日志示例:
TNS-01190: The user is not authorized to execute the requested listener command
最后的1190就表示返回代码,下面是查询返回代码为1190的SQL语句:
col service format a20
col logdate format a20
col host format a10
col RC format a5
select to_char(log_date,'mm/dd/yy hh24:mi:ss') logdate,
parse_listener_log_line(connect_string,'HOST') host,
parse_listener_log_line(connect_string,'USER') l_user,
parse_listener_log_line(connect_string,'SERVICE') service,
action RC
from listener_log
where parse_listener_log_line(connect_string, 'COMMAND') = 'stop' and rc=’1190’;
查询结果为:
-------------------- ---------- ---------- -------------------- -----
11/06/07 13:45:06 host01 test_user01 LISTENER 1190
这里只输出了一条结果,如果我们的系统中存在test_user01这个用户,那可能不会是黑客在尝试密码,而如果出现了大量的这种记录,那可能就是被黑客盯上了。类似的错误代码还有12508,1169,11508表示有人想改变日志文件的默认目录,但没有成功,1169表示有人想执行某个命令,但他不知道密码而没有执行成功。
小结
如果单纯使用文本编辑器打开监听器日志文件查找其中的某些关键字,如TNS-01190,通过这种方式虽然可以找到一些有用的信息,但想要实现汇总统计的功能就必须借助数据表来实现了,本文只列出了一些简单的应用统计,其实还可以从不同角度进行查询统计,只要充分发挥想象力,还可以统计出更多有用的信息,还可以结合Oracle本身的审计功能进行综合查询。