技术开发 频道

从监听器日志中挖掘信息

  【IT168 技术文章】

  提起日志,可能你会一下子想到操作系统日志,数据库日志,但今天我们不说那些日志,今天要说的是数据库监听器日志,监听器日志记录了监听器所有操作,包括成功的和不成功的,从日志中还可以发现针对监听器的攻击行为,因为监听器常常是黑客攻击Oracle数据库的首要目标,本文主要是想介绍一下从监听器日志中挖掘一些有用的信息。

  监听器日志是一个标准的文本文件,但如果直接用文本编辑器,如记事本打开它来寻找想要的信息可能有点困难,这里介绍另一种方法,使用扩展表和SQL语句来查找,将日志中的每一行填充为扩展表中的每一条记录,下面介绍具体的实现步骤。

  1、创建日志目录对象

create directory LISTENER_LOG_DIR

  
as 'C:\oracle\product\10.2.0\db_1\network\log\'

  
/

  如果你不清楚你的监听器日志放在哪里,可以使用lsnrctl status命令查看,在命令输出结果中就指出了监听器日志文件的路径。

  2、创建扩展表 

create table full_listener_log

  (

  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表了,如查询日志写入信息: 

SQL> select * from full_listener_log

  
2 where line like 'Log messages written%'

  
3 /

  然而查询出来的信息还是非常原始的,我们的目标是要找出真正有用的信息,于是要对日志文件的每一行的内容再进行分解,通常,监听器日志文件大部分行都含有下列字段:

  A)日志条目的日期和时间戳

  B)客户端连接使用的字符串

  C)客户端使用的协议信息(TCP/IP,端口号等)

  D)客户端的行为,如状态,建立连接等

  E)客户端连接字符串中的服务名

  F)客户端行为返回的代码,如果返回0,表示操作成功,否则就显示错误代码

  每个字段之间使用星号(*)进行分隔。注意不是每条日志条目都遵循这个格式,如下面的日志内容:

TNSLSNR for 32-bit Windows: Version 10.2.0.1.0 - Production on 01-9月 -2008 11:48:15

  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

  它们每一行是一个整体,无法进行分解。对于这些总结性的信息,我们可以忽略它们。还有一些条目大致遵循前面所说的格式,但有些字段没有,如下面这条监听器注册信息:

 01-9月 -2008 11:48:31 * service_register * orcl * 0

  它只包括了四个字段:时间戳,行为,服务名和返回代码。即使只有四个字段,也还是可以分解它们,只不过要注意字段的对应了。所以要建立一个标准表也不困难,下面就是创建日志分解扩展表的脚本:  

create table listener_log

  (

  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位置的日志文件名,创建好后,该表的结构如下:  

LOG_DATE DATE

  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这个表中存储的字段信息还有嵌套信息,也就是说还可以进行分解,可以再设计几个表来存储进一步分解的信息,但这里我使用一个函数来完成这件事情,它接收两个参数:查询时使用的字段值和要查询的字符串: 

1 create or replace function parse_listener_log_line

  
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字段的值: 

sql>select CONNECT_STRING from listener_log;

  返回:

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> set null ?

  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 host format a20

  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';

  输出内容为: 

LOGDATE HOST L_USER SERVICE

  
-------------------- ---------------- ----------------- ---------------

  
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 program format a70

  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');

  输出内容如下:  

PROGRAM                                                                       CNT
---------------------------------------------------------------------- C:\oracle\ora90\bin\sqlplus.exe                                          200
C:\opt\TOAD\TOAD.exe                                                        5
                                                                          
1093

  从上面的输出内容中可以看出,最常用的是sqlplus.exe,最后的1093表示自Oracle数据库安装以来总的会话数,它并不等于前面程序使用次数的和,因为还有一些会话使用的可能是其它访问机制。

  5.3.服务名使用情况

  如果你有一个RAC数据库环境,你可能会使用一些诸如负载均衡的方案,实现会话故障字自动转移,客户端连接数据库就必须使用服务名,而不能使用SID来连接数据库,下面是一个客户端连接tnsnames.ora文件的部分内容: 

ora_test =

  (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连接,而没有使用服务名:

 col sid format a15

  
select parse_listener_log_line(connect_string,'SID') sid, count(1) cnt

  
from listener_log

  
group by parse_listener_log_line(connect_string,'SID');

  输出结果:

 SID CNT

  
--------------- ----------

  ora
1

  ora02
16292

  从输出结果可以看出还是有人直接使用了SID:ora和ora02连接数据库,但实际上没有哪个SID叫做ora,这可能是有人在猜测SID,这样就可以采取进一步措施,查看究竟是哪些机器这样在连接: 

select * from listener_log where parse_listener_log_line(connect_string,'SID') = 'ora';

  输出结果为: 

LOG_DATE : 18-oct-2008 11:30:28

  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错误,但在记录这样一条日志信息时,只有四个字段,而不是常见的六个字段,我们只要明白最后一个表示返回代码,下面是一条日志示例: 

06-NOV-2007 13:45:06 * (CONNECT_DATA=(CID=(PROGRAM=)(HOST=host01)(USER=test_user01))(COMMAND=stop)(ARGUMENTS=64)(SERVICE=LISTENER)(VERSION=168821760)) *stop * 1190

  TNS
-01190: The user is not authorized to execute the requested listener command

  最后的1190就表示返回代码,下面是查询返回代码为1190的SQL语句: 

col l_user format a10

  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’;

  查询结果为: 

LOGDATE HOST L_USER SERVICE RC

  
-------------------- ---------- ---------- -------------------- -----

  
11/06/07 13:45:06 host01 test_user01 LISTENER 1190

  这里只输出了一条结果,如果我们的系统中存在test_user01这个用户,那可能不会是黑客在尝试密码,而如果出现了大量的这种记录,那可能就是被黑客盯上了。类似的错误代码还有12508,1169,11508表示有人想改变日志文件的默认目录,但没有成功,1169表示有人想执行某个命令,但他不知道密码而没有执行成功。

  小结

  如果单纯使用文本编辑器打开监听器日志文件查找其中的某些关键字,如TNS-01190,通过这种方式虽然可以找到一些有用的信息,但想要实现汇总统计的功能就必须借助数据表来实现了,本文只列出了一些简单的应用统计,其实还可以从不同角度进行查询统计,只要充分发挥想象力,还可以统计出更多有用的信息,还可以结合Oracle本身的审计功能进行综合查询。

0
相关文章