当前位置:  首页  >  服务器技术  >  Linux/unix  >  Linux教程

SQL另类注入之绕过后台登陆验证

要这段时间在学习Linux的基础应用,玩腻了脚本注入后总觉得黑客方面老是停留在整天玩注入漏洞,或者其他的脚本漏洞技术也没法得到提高。所以静下心来给自己安排了个学习计划,先从Linux学起,完了再学习编程以及业余时间深入研究下脚本及数据库。最近在学

这段时间在学习Linux的基础应用,玩腻了脚本注入后总觉得黑客方面老是停留在整天玩注入漏洞,或者其他的脚本漏洞技术也没法得到提高。所以静下心来给自己安排了个学习计划,先从Linux学起,完了再学习编程以及业余时间深入研究下脚本及数据库。

最近在学习之余还是不忘和各位群友闲谈扯蛋,也从他们身上学到了不少经验和技巧,前不久就从自然哪里学到一个另类的SQL注入技巧。当时是自然给出 的一个例子,直接用一个语句绕过后台登陆验证进入到了后台。当时看了觉得挺不错,所以下来就深入的研究了下其原理,对其也有一定的了解。

好了,废话不多说,下面直接进入正题吧。一提到绕过后台登陆验证直接进入网站后台管理系统,想必大家都能想到经典的万能密码:\'or\'=\'or\'吧,今天要给大家分享的一个技巧也和这个差不多,不过是这个方法比较另类。

在讲该技巧之前我们还是先来简单回顾下经典的\'or\'=\'or\'原理,更详细的原理大家可以网上搜索相关资料了解下。我们都知道后台登陆验证一般的 方式都是将用户在登录口输入的账号密码拿去与数据库中的记录做验证,并且要求输入的账号密码要等于数据库中某条记录的账号密码,验证通过则程序就会给用户 一个sssion,然后进入后台,否则就返回到登陆口。而对于\'or\'=\'or\'漏洞,我们先来看以下代码:

<%
pwd = request.form("pwd") 获取用户输入的密码,再把值赋给pwd
name = request.form("name") 获取用户输入的用户名再把值赋给name
都没有进行任何过滤
Set rs = Server.CreateObject("ADODB.Connection")
sql = "select * from Manage_User where UserName=\'" & name & "\' And PassWord=\'"&encrypt(pwd)&"\'" 将用户名和密码放入查询语句中查询数据库,
Set rs = conn.Execute(sql) 执行SQL语句,执行后并得到rs对象结果,“真”或“假”
If Not rs.EOF = True Then 如果是真则执行以下代码
Session("Name") = rs("UserName") 将UserName的属性赋给Name的Session自定义变量
Session("pwd") = rs("PassWord") 将PassWord的属性赋给pwd的Session自定义变量
Response.Redirect("Manage.asp")了 利用Response对象的Redirect方法重定向Manage.asp
Else 否则执行以下代码
Response.Redirect "Loginsb.asp?msg=您输入了错误的帐号或口令,请再次输入!"
End If
%>

以上就是一个典型的\'or\'=\'or\'漏洞例子,针对以上例子我们只需要在用户名处提交\'or\'=\'or\',这样就使得SQL语句变 成:select * from Manage_User where UserName=\'’or‘=\'or\'\' And PassWord=\'123456\'。执行后得到rs对象的结果为真,这样就能顺利的进入后台了。

为了避免出现这个漏洞,现在基本上的后台验证都不会使用这类方式,而是取得用户输入的账号和密码,在SQL中先将用户名与数据库中的记录做对比,若 数据库中某条记录的用户名等于用户输入的用户名,则取出该条记录中的密码,然后再与用户输入的密码对比,正确就通过,不正确就返回。例如一下代码:

<%
pwd = request.form("pwd") 获取用户输入的密码,再把值赋给pwd
name = request.form("name") 获取用户输入的用户名再把值赋给name
都没有进行任何过滤
Set rs = Server.CreateObject("ADODB.Connection")
sql = "select * from Manage_User where UserName=\'" & name & "\'" 将用户名和密码放入查询语句中查询数据库,
Set rs = conn.Execute(sql) 执行SQL语句,执行后并得到rs对象结果,“真”或“假”
If Not rs.EOF = True Then 如果是真则执行以下代码
password=rs("password") 取得密码数据
if password=md5(pwd) then
Session("Name") = rs("UserName") 将UserName的属性赋给Name的Session自定义变量
Session("pwd") = rs("PassWord") 将PassWord的属性赋给pwd的Session自定义变量
Response.Redirect("Manage.asp")了 利用Response对象的Redirect方法重定向Manage.asp
else
response.write "密码错误!!!!"
end if
Else 否则执行以下代码
Response.Redirect "Loginsb.asp?msg=您输入了错误的帐号或口令,请再次输入!"
End If
%>

通过以上例子可知道,密码的验证不再是直接在SQL语句中做验证了,而是根据用户名,取出对应的密码,然后再与用户输入的做对比。这样一来就造成了 我们不能使用\'or\'=\'or\'绕过了。有的朋友在这里可能有疑问了,若我们提交\'or\'=\'or\'那么SQL语句就变成:select * from Manage_User where UserName=\'\'or\'=\'or\'\',这样一来得到的结果也应该是真啊,为什么就不能绕过呢?

其实就算SQL查询的地方得到的值是真,可别忘了后面还有密码的验证,若我们提交以上SQL,得到账号是真的,那么后面根据账号去数据库中取出来的密码与用户提交的密码是绝对通不过的。

好了,对于\'or\'=\'or\'漏洞的分析先就暂且说到这里,以上的均为我个人对该漏洞的理解,并不代表就是完全正确的,若有不当的地方还望大家多多指教,接下来就是我们的重头戏了。

昨天在帮流年看站的时候就遇到了一个旁站,当时是想注入,后来流年拿下服务器后顺便就将程序打包下来我研究了下,经过两个多小时的分析和测试,终于可以成功绕过了那套系统的后台登陆验证,接下来就结合程序代码,将我的整个分析过程给大家详细讲解下。

程序类型是国外的一个小型商城系统,在检测的时候我就顺便构造了个关键词用google搜索下发现还有不少的网站。加之又是国外的,所决定要深入研究下,不过国外程序员在写程序的时候习惯和国内还是有不少的差异,往往国外的目录文件和程序结构分枝都很深,这点还让我在分析源代码的时候被代码牵着鼻子到处转……
在拿到程序原本是首先看前台的注入的,但在打开数据库找到管理密码后我就放弃了从前台寻找注入的打算,因为管理的密码根本没法破解,就算找到注入用处也不 大。因此要寻找程序的致命弱点只有往网后台或者无需验证的上传页面方向去了,然后查看了下所有的上传都需要后台验证才能上传。到此,唯一的路就是想法突破 后台了。

首先我们来看看后台登陆口(login.asp)的代码:

<%
if request.form("Submit") = " Login " then
if trim(request("yanzheng"))=session("ValidCode") then
if DoLogin(request.form("LoginId"),request.form("Password")) = 1 then
response.redirect("index.asp")
end if
else

response.Redirect("login.asp?p=login")
end if
end if
%>

通过以上代码可以看出,login.asp页面验证登陆是将用户输入的账号和密码交给DoLogin函数验证,在DoLogin函数中,验证通过将返回一个值为1(通过验证进入后台),反之不等于则重定向到登陆页面。我们现在来看看DoLogin函数的内容。

private function DoLogin(login, pass)
set rsLogin = server.createobject("ADODB.recordset")
rsLogin.cursortype = 3

strSQL = "Select admin_id, admin_salt, admin_password FROM admin_users Where admin_login = \'" & login & "\'"
rsLogin.open strSQL, adoCon
response.Write strSQL
if not rsLogin.eof then
correctPass = rsLogin("admin_password")
controlPass = hashEncode(pass & rsLogin("admin_salt"))
if correctPass = controlPass then
DoLogin = 1
session("admin_user_id") = rsLogin("admin_id")
session("session_id") = session.SessionID
session("order_flag") = 1
else
DoLogin = 0
end if
else
DoLogin = 0
end if

rsLogin.close
set rsLogin = nothing
end function

“private function DoLogin(login, pass)”中的login何pass分别就是在上面的login.asp中的request.form("LoginId")和 request.form("Password"),从以上代码中可以看到用户输入的账号和密码没有经过任何的过滤就带入到SQL语句中查询了。查询后的 验证方式就和上文中提及的验证方式大同小异了。

现在知道用户输入的账号和密码没有经过过滤(这点很重要),接下来我们还需要清楚管理员表的结构,这里我给大家截图上来,管理员表名是admin_users,结构如下图:

javascript:window.open(this.src); alt="" src="/Article/UploadFiles/201011/20101114120845112.jpg" onload="return imgzoom(this,550);" border=0>

可以看到管理员表中有四个字段,分别是ID号、用户名、以及salt和密码,密码在第四列。有了这些信息后我们就要开始着手构造SQL语句绕过后台登陆了。

通过对登陆验证代码的分析,我们可以将整个验证过程简单描述为:验证函数取得用户输入的账号和密码,然后从管理员表中查询某条用户名等于用户输入用 户名的记录,如果查询后得知管理表中存在某条记录,则继续取出该条记录的密码,然后再将此密码与用户输入的密码进行对比(用户输入的密码要经过加密),若 密码验证成功则通过后台验证。

现在我们的突破策略就是:提交某个SQL语句,让程序从管理员表中查询某条用户名等于用户输入用户名的记录结果为真,这样程序就会继续验证密码,然后我们再让程序从上步查询中得到一个密码的密文,这样一来就会顺利的通过验证了。

突破程序查询用户名得到一个真值比较容易能做到,但后面的让程序同时得到一个密码的密文我想还真不容易办到,而且还要在一步中完成。但是办法确实是有的,这里我就直接给大家贴出来,这个办法也是我从自然兄那里学来的,说实在的我真是非常佩服想出这个办法的牛人。

在这里我们需要使用union联合查询语句来绕过用户名的验证,并让查询同时得到一个密码密文,假设现在的这个网站系统密码是使用md5加密,在用 户名的地方填写: \'union select 1,2,3,\'225cdc811adfe8d4\' from admin_user where \'a\'=\'a,然后密码我们输入:hack,这样就能顺利绕过了后台验证了。

union联合查询大家都不陌生,这里我就不详细说union联合查询的原理的,其实我也理解不是很深,没法用简洁的seo/\'>语言来真确表达,只是大概知道其意义。至于为什么要使用这样一个语句,大家可以参考上面我用红色字体表示的描述,再结合下面的说明自己体会。会网页编程的朋友应该不难理解,新手朋友就需要多下点功夫了。

在以上语句中要说的就两点,一是密文:225cdc811adfe8d4,这个正是密码hack的16为小写md5值,另外union select 后面跟的数据位数是4位,这个要取决于admin_users表的结构,见上文中admin_users表的截图,而密文 225cdc811adfe8d4一定要放在第四位上,这个也取决于admin_users表的结构。并且还要加上单引号,因为 225cdc811adfe8d4是字符,这个懂SQL语句的朋友应该清楚。

当我们提交\'union select 1,2,3,\'225cdc811adfe8d4\' from admin_user where \'a\'=\'a后我们再来分析下验证程序,首先SQL语句会变成:Select admin_id, admin_salt, admin_password FROM admin_users Where admin_login = \'\'union select 1,2,3,\'225cdc811adfe8d4\' from admin_user where \'a\'=\'a\' 仔细分析这个SQL语句可以得知,最终的执行结果是真,这样就解决了绕过用户名验证阶段,进入密码验证阶段。并且以上的语句执行后同时在返回的记录集中还 能得到一个密码密文为\'225cdc811adfe8d4\'的值,这样在密码验证阶段中,就会将这个值与使用md5加密hack字符的结果对比,结果是一 样的,最终就顺利的通过全部的验证了。可能这个对大多数朋友来说还是比较难理解,其实我也并未完完全全的理解,也只是理解了个大概,要想完完全全理解的话 重点就在union联合查询的原理上了,能完完全全搞清楚union联合查询的原理话,这个应该就能很容易理解了。

吐了个 "CAO" !
扫码关注 PHP1 官方微信号
PHP1.CN | 中国最专业的PHP中文社区 | PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | PHP问答
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved PHP1.CN 第一PHP社区 版权所有