您当前的位置:首页 > 知乎文章

特殊场景的sql注入思路

时间:2022-02-28 10:47:21  知乎原文链接  作者:网盾网络安全培训

上一篇介绍了sql注入的基础知识以及手动注入方法,但是在实际的环境中往往不会像靶场中那样简单。今天我就来为大家介绍一种特殊场景的sql注入思路。

用户名与密码分开验证的情况

第一个场景我们以We Chall平台的Training: MySQL II 一题为例。

进入题目可以看到有一段提示,以及一个登录框:

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

提示的信息大概意思就是,这道题与MySQL I相同,但是这次我们需要使用更高级的注入才能骗过这种身份认证,这次的任务就是以管理员身份登录,并且为我们提供了源代码。

提示中提到的MySQL I我这里也简单说一下,就是一个我们之前提到过的,输入用户名admin' #跳过密码验证的基础注入技巧。

那么我们来看这回的MySQL II有什么不同吧,既然他给我们提供了源代码,我们就先来看看吧:

<?php
/* TABLE STRUCTURE
CREATE TABLE IF NOT EXISTS users (
userid INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
password CHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL
) ENGINE=myISAM;
*/
# Username and Password sent?
if ( ('' !== ($username = Common::getPostString('username'))) && (false !== ($password = Common::getPostString('password', false))) ) {
auth2_onLogin($chall, $username, $password);
}
/** * Get the database for this challenge.
* @return GDO_Database
*/
function auth2_db()
{ if (false === ($db = gdo_db_instance('localhost', WCC_AUTH_BYPASS2_USER, WCC_AUTH_BYPASS2_PASS, WCC_AUTH_BYPASS2_DB))) {
die('Database error 0815_2!');
}
$db->setLogging(false);
$db->setEMailOnError(false); return $db;
}
/**
* Exploit this! It is the same as MySQL-I, but with an additional check, marked with ### * @param WC_Challenge $chall
* @param unknown_type $username
* @param unknown_type $password
* @return boolean
*/
function auth2_onLogin(WC_Challenge $chall, $username, $password)
{
$db = auth2_db();
$password = md5($password);
$query = "SELECT * FROM users WHERE username='$username'";
if (false === ($result = $db->queryFirst($query))) {
echo GWF_HTML::error('Auth2', $chall->lang('err_unknown'), false);
return false;
}
#############################
### This is the new check ###
if ($result['password'] !== $password) {
echo GWF_HTML::error('Auth2', $chall->lang('err_password'), false);
return false;
} # End of the new code ###
#############################
echo GWF_HTML::message('Auth2', $chall->lang('msg_welcome_back', array(htmlspecialchars($result['username']))), false);
if (strtolower($result['username']) === 'admin') {
$chall->onChallengeSolved(GWF_Session::getUserID());
}
return true;
}
?>
<form action="index.php" method="post">
<table>
<tr>
<td><?php echo $chall->lang('username'); ?>:</td>
<td><input type="text" name="username" value="" /></td>
</tr>
<tr>
<td><?php echo $chall->lang('password'); ?>:</td>
<td><input type="password" name="password" value="" /></td>
</tr>
<tr>
<td></td>
<td><input type="submit" name="login" value="<?php echo $chall->lang('btn_login'); ?>" /></td>
</tr>
</table>
</form>

代码很长,看起来好像很复杂的样子。没有关系,我们只需要关注他是如何验证的部分就可以了,这里我也很贴心的帮大家把验证部分的代码截出来并带大家一起分析一下:

function auth2_onLogin(WC_Challenge $chall, $username, $password)
{
$db = auth2_db();
$password = md5($password); #将密码进行md5加密
$query = "SELECT * FROM users WHERE username='$username'"; #sql查询语句
if (false === ($result = $db->queryFirst($query))) { #判断是否未查询到数据
echo GWF_HTML::error('Auth2', $chall->lang('err_unknown'), false);
return false;
} #如果没查询到数据提示用户名未知,并返回false
if ($result['password'] !== $password) { #判断查询到的密码与输入的是否不一致
echo GWF_HTML::error('Auth2', $chall->lang('err_password'), false);
return false;
} #如果输入的密码与查询出的不一致提示密码错误,并返回false
echo GWF_HTML::message('Auth2', $chall->lang('msg_welcome_back', array(htmlspecialchars($result['username']))), false);
#检查都通过提示欢迎回来,并显示登陆的用户名。
if (strtolower($result['username']) === 'admin') {
$chall->onChallengeSolved(GWF_Session::getUserID());
} #判断如果查询出的用户名为‘admin’则判断解题成功。
return true;
}

通过分析代码,我们可以看到,这道题的sql查询语句本来就是只查询用户名,然后再将密码与查询出的密码对比,而不是通过判断sql语句是否返回数据来判断是否验证成功的,这样我们就无法通过常规的截断sql查询语句来绕过密码验证。

这里我们就可以利用另一种方法:通过联合查询(union select)构造我们自定的用户名与密码来骗过程序。

通过分析源代码最开始告诉我们的表结构我们知道了表中一共有3个字段:userid,username,password。那么我们联合查询时同样查询对应的三个字段,将用户名与密码构造为我们自定的,然后将union前的语句查询值变为假即可使返回值变为我们自定的内容。

我们在本地环境中尝试搭建一个与题目相同的表,查询一下试试,看看是否可以任意构造用户名与密码。

class="ztext-empty-paragraph">

我们可以看到我向表中随意插入了4条记录,并且用户admin的密码为nicaicai,这时我们尝试构造一个用户名为:ATL,密码为:Ocean的用户试试。

sql查询语句构造为:

SELECT * FROM users WHERE username='1' union select 1,'ATL','Ocean';

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

我们可以看到确实返回了一条我们构造的用户记录。通过这种方法,我们就可以让程序以为我们构造的数据是从数据库中查询出的数据,从而让我们可以使用任意的身份登录了。

那么在这里的具体解题方法就是是在username栏填写:1' union select 1,'admin',md5('123');#

那么为什么要这样填写呢?我们来分析一下这样填写sql语句会变成怎样的:

SELECT * FROM users WHERE username='1' union select 1,'admin',md5('123');#'

首先union前查询的username可以随便填写,只要在表中没有的值即可,其次因为代码中密码在判断前会进行md5加密,所以要在构造时将我们的密码也进行md5加密。最后#是为了将最后面的单引号注释掉。

这样我们在username一栏填写:1' union select 1,'admin',md5('123');#

password一栏填写:123

就可以完成题目了:

class="ztext-empty-paragraph">

上一篇      下一篇    删除文章    编辑文章
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
推荐资讯
相关文章
    无相关信息
栏目更新
栏目热门