SQL注入的攻击目标与前后端防御措施

1. 攻击目标

  1. 攻击的是什么?
    1. 一个动态网站
      1. 什么类型的动态网站?
        1. http可以吗?(Hyper Text Transfer Protocol)

          可以

        2. https可以吗?(Hyper Text Transfer Protocol Secure)

          可以,但HTTPS往往难以进行攻击

        3. http和https有什么区别?
          • http是超文本传输协议,信息是明文传输,存在被窃听的风险

          • HTTPS协议使用SSL(Secure Sockets Layer)协议TLS(Transport Layer Security)协议进行加密传输
            并且HTTPS协议使用数字证书进行身份认证,保证通信双方的真实身份

        4. 对两者的攻击方式有什么区别?
          1. http:
            • 明文传输能够省去解密的部分,因此攻击者可以直接获取到明文信息

            • 攻击者可以直接修改删除信息

            • 攻击者能够更容易获取webshell权限

          2. https:
            • 需要对信息进行解密,因此攻击者无法直接获取到明文信息

            • 攻击者无法直接修改或删除信息

      2. 什么是webshell?
        • Webshell就是以asp、php、jsp或者cgi网页文件形式存在的一种代码执行环境

        • 由于Web服务器解析文件时,会把文件内容当做代码执行

        • 因此,攻击者可以利用Webshell执行任意代码

        • 将asp或php后门文件与网站目录下正常的网页文件混在一起,然后就可以使用浏览器来访问asp或者php后门,得到一个命令执行环境

2. SQL注入攻击方式

  1. 找到目标主机的网站
    1. 确认可与目标主机进行通信
      • 靶机与kali处于同一内网环境

      • 靶机位于公网环境,且靶机可用作安全测试

    2. 确定目标主机IP和端口号或服务类型(如HTTP、FTP等)
      • 在虚拟机中使用主机模式使kali和靶机互联

      • 在kali中使用arp-scan -l命令扫描靶机的IP地址

      • 参数-l: localnet。扫描本地网络。
      • 在kali中使用nmap -sV命令扫描靶机的开放端口

      • 参数-sV:service/version。探测靶机端口的服务/版本信息。
    3. 扫描靶机的网站目录
      • 在kali中使用gobuster dir -u 靶机IP -w 字典文件 -n命令扫描靶机的网站目录

      • 参数dir:
      • 参数-u: url。网址。web地址。
      • 参数-w: wordlist。字典。所有可能的目录名字组成的字典。
      • 参数-n: no status不显示返回的状态码。
    4. 进入靶机的网站目录
      • 先尝试直接访问IP

      • 再尝试在扫描的目录中寻找特殊目录

  2. 寻找目标网站的注入点
    1. 寻找表单
    2. 寻找url中的?+参数
  3. 使用burpsuite抓包
    1. 设置代理,由burpsuite接管浏览器的流量,代理向服务器发送请求。
    2. 保存第一个发起的数据包进行注入测试
  4. 使用sqlmap进行注入测试
    1. 使用sqlmap -r 数据包文件地址 --dbs -batch命令,进行sql注入测试,获取数据库名。
      • 参数-r: request。请求地址。
      • 参数--dbs: database。获取数据库。
      • 参数-batch: 静默模式。不显示任何信息。
    2. 使用sqlmap -r 数据包文件地址 --tables -D 库名 -batch命令,进行sql注入测试,获取数据库中的所有表名。
      • 参数-r: request。请求地址。
      • 参数-D: database。数据库。
      • 参数--tables: tables。获取表名。
      • 参数-batch: 静默模式。不显示任何信息。
    3. 使用sqlmap -r 数据包文件地址 --columns -D 库名 -T 表名 -batch命令,进行sql注入测试,获取数据库中的所有字段名。
      • 参数-r: request。请求地址。
      • 参数-D: database。数据库。
      • 参数-T: table。表。
      • 参数--columns: columns。获取字段名。
      • 参数-batch: 静默模式。不显示任何信息。
    4. 使用sqlmap -r 数据包文件地址 --dump -D 库名 -T 表名 -C 字段名 -batch命令,进行sql注入测试,获取表中的所有字段名中的值
      • 参数-r: request。请求地址。
      • 参数-D: database。数据库参数。
      • 参数-T: table。表参数。
      • 参数-C: column。字段参数。
      • 参数--dump: dump。枚举数据撞库,若成功则转储获得的数据库表项
      • 参数-batch: 静默模式。不显示任何信息。

3. 前端防御措施(格式校验)

  1. HTML标签限制(邮件)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <form>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email">
    <br>
    <label for="password">Password:</label>
    <input type="password" id="password" name="password" minlength="8">
    <br>
    <button type="submit">Submit</button>
    </form>

    Alt text

  2. JavaScript正则表达式限制

    1
    2
    3
    4
    5
    //使用正则表达式,校验Email格式是否合法,返回一个Boolean值
    function isValidEmail(email) {
    const emailRegex = /^[^@]+@[^@]+\.[^@]+$/;
    return emailRegex.test(email);
    }

    这个正则表达式组成部分包括:

  • ^:表示匹配字符串的开头。
  • [^@]+:表示匹配一个或多个非”@”字符。
  • @:表示匹配一个”@”字符。
  • [^@]+:表示匹配一个或多个非”@”字符。
  • \.:表示匹配一个”.”字符。
  • [^@]+:表示匹配一个或多个非”@”字符。
  • $:表示匹配字符串的末尾。

4. 后端防御措施(参数化查询)

  • 参数化查询是使用?占位符将 SQL 语句和用户输入的数据分开存储的查询方式。

  • 与参数化查询相对的是拼接字符串的查询方式

  • 拼接字符串的查询方式将 SQL 查询语句用户输入的数据拼接成一个完整的字符串,然后将该字符串作为一个整体传递给数据库系统进行查询。

  • 参数化查询可以防止 SQL 注入攻击,因为攻击者无法控制输入的数据。

  • 参数化查询的原理是将用户输入的数据与 SQL 语句一起进行预处理,然后将预处理后的 SQL 语句发送

  • 直接拼接字符串查询let query = “SELECT * FROM users WHERE name = '“ + userName + “';“;

  • 参数化查询let query = “SELECT * FROM users WHERE name = ?“;

以Node.js + MySQL数据库为例,编写一个简单的后端代码,实现参数化查询,以防止SQL注入攻击:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 引入mysql模块
const mysql = require('mysql');

// 创建数据库连接
const connection = mysql.createConnection({
host: 'localhost',
user: 'yourUsername',
password: 'yourPassword',
database: 'yourDatabase'
});

// 连接数据库
connection.connect(err => {
if (err) throw err;
console.log('Connected to the database.');
});

// 参数化查询的函数
function queryDatabase(query, params){
return new Promise((resolve, reject) => {
// 使用参数化查询以防止SQL注入
// query: SQL语句,其中参数被?占位符替代
// params: 一个数组,包含了要插入到SQL语句中的参数值
connection.query(query, params, (error, results, fields) => {
if (error) {
reject(error); // 发生错误,拒绝promise
} else {
resolve(results); // 成功执行,解决promise
}
});
});
}

// 示例:查询用户
function getUser(username) {
// 参数化查询:使用?作为参数的占位符
const sqlQuery = 'SELECT * FROM users WHERE username = ?';
// 调用查询函数并提供参数
queryDatabase(sqlQuery, [username])
.then(results => {
console.log(`User Data: `, results);
})
.catch(error => {
console.error('Query Error:', error);
});
}


// 关闭数据库连接,当你的程序不再需要连接数据库时可以调用
// 在一个实际应用中,你可能会在不同的时机关闭连接,或者使用连接池管理连接
connection.end(err => {
if (err) throw err;
console.log('Disconnected from the database.');
});