本教程包括。
- 什么是SQL注入,为什么它有如此大的破坏性
- 添加和自动测试暴露的威胁
- 修复注入漏洞
SQL注入攻击的目标是应用程序的数据库,这可能导致不可逆转的后果,导致金钱的损失,并降低用户对公司的信任。每天都有太多的应用程序数据泄露事件发生,通常是恶意代理攻击数据库。SQL注入是一个应用程序被攻击的最具破坏性的方式之一。这种攻击诱使应用程序在数据库层面上运行精心设计的恶意代码。SQL注入攻击会导致数据暴露、损坏,甚至永久丢失。
在本教程中,我将演示如何使用自动化测试来检查脆弱的应用程序入口点,以防止数据库注入攻击。
前提条件
要学习本教程,需要具备一些条件。
我们的教程是与平台无关的,但以CircleCI为例。如果你没有CircleCI账户,请**在这里注册一个免费账户。**
克隆演示项目
为了开始练习,你将需要克隆演示项目。该项目是一个简单的Node.js用户账户API应用程序,有以下端点。
GET : /users/fetch是一个用于获取所有用户的端点POST : /users/fetch是一个 端点,它接收一个用户 ,并使用它来获取一个用户POSTidPOST : /users/create是一个 端点,它接受参数 和 来创建一个新的用户记录。POSTnameemail
这个项目在一个SQLite数据库上运行,该数据库在内存中存储数据(用于演示),包含一个单一的表,名为users 。
通过运行以下命令克隆该项目。
git clone --single-branch --branch base-project https://github.com/CIRCLECI-GWP/sql-injection-testing.git
克隆过程完成后,进入项目的根目录,通过运行下面的命令安装依赖项。
cd sql-injection-testing
npm install
接下来,用这个命令运行应用程序。
npm run dev
该应用程序将开始监听一个默认的端口3000 。 打开Postman并使用http://localhost:3000/users/create 端点添加一些用户。( 你一次只能创建一个用户。)
{
"email": "john.doe@gmail.com",
"name": "John Doe"
}
通过调用GET 到http://localhost:3000/users/fetch 端点来获取用户。
你也可以通过调用POST 端点的/users/fetch ,为第一个用户发送一个id 参数来获取用户。
添加测试以检查敏感数据暴露的威胁
端点POST : /users/fetch 和POST : /users/create 是进入应用程序的两个入口点。它们接收数据,应用程序处理这些数据以给出结果。这两个端点的处理程序可以在项目根部的user.js 找到。
...
function getUser(req, res) {
let sql = `SELECT * FROM users WHERE id='${req.body.id}'`;
db.all(sql, function (err, data) {
if(err) throw err
res.json({
status : 200,
data,
message : "User record retrieved"
})
})
}
function createUser(req, res) {
let sql = `INSERT INTO users(email, name) VALUES ('${req.body.email}', '${req.body.name}')`;
db.run(sql, function (err) {
if(err) throw err
res.json({
status : 200,
message : "User successfully created"
})
})
}
这两个处理程序都运行SQL查询,直接从请求对象中获取用户数据。我们可以攻击其中一个端点以暴露其SQL注入漏洞。
我们将测试POST : http://localhost:3000/users/fetch 端点,该端点应该获取特定用户的id ,并在一个data 数组中只返回该用户。我们将使用一个精心设计的SQL注入攻击,攻击这个端点,使其暴露数据库中所有用户的数据。
首先,使用以下命令安装jest (测试运行器)和supertest (用于测试API端点)。
npm install -D jest supertest
安装完成后,创建一个文件夹__tests__ ,在这个文件夹中,创建文件injection.test.js 。添加以下代码。
const supertest = require("supertest");
const app = require("../app");
const request = supertest(app);
const db = require("../db");
let createTableSQL =
"CREATE TABLE IF NOT EXISTS `users` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , `email` VARCHAR(100) NOT NULL , `name` VARCHAR(240) NOT NULL)";
let insert =
"INSERT INTO users (name, email) VALUES ('user1@test.com','User 1'), ('user2@test.com','User 2'), ('user3@test.com','User 3')";
test("Test malicious data exposure", (done) => {
db.run(createTableSQL, function (err) {
db.run(insert, function () {
let maliciousUserId = `1' OR 1=1;--`;
request
.post("/users/fetch/")
.send({ id: maliciousUserId })
.set("Accept", "application/json")
.expect(200)
.expect("Content-Type", /json/)
.end(function (err, res) {
if (err) return done(err);
//Should not return more than one record
expect(res.body.data.length).toBeLessThan(2);
done();
});
});
});
});
这个测试案例首先创建users 表,并在其中加入3个测试用户。然后,它制作一个恶意用户id ,拦截SQL查询,并注入一个永远为真的条件。这是一个替代条件,将结果限制在只有拥有id 的用户。结果是,查询返回users 表中的所有用户。
我们可以测试API调用的结果,以确保它不包含超过一条记录。由于表中已经有3个用户,如果请求返回超过1个结果,就会失败,这表明攻击成功了。
为了完成测试设置,在package.json ,添加一个test 脚本。
"scripts": {
...
"test": "jest"
},
现在你可以通过在项目根部运行下面的代码来运行该测试。
npm run test
一旦测试运行,你将得到一个失败的结果。
注入攻击不是返回一个单一的值,或者在没有匹配的情况下根本没有,而是能够成功地迫使数据库暴露出表内包含的超过2 的用户。这种攻击对于恶意用户来说是一种危险的方式,可以暴露你数据库表中的敏感或私人数据,如电话号码、账号和信用卡信息。
修复注入漏洞
那么,如何修复这个问题呢?SQL注入通常是通过调整你的代码,检查条件来验证来自用户访问你的应用程序的数据来修复的。以上述案例为例,我们可以尝试检查用户提交的值是否是一个整数,因为id,以整数形式存储。任何恶意的SQL都不会通过这个检查,因为它们是字符串而不是整数。
然而,即使这个策略可以修复POST : http://localhost:3000/users/fetch 端点的漏洞,在我们的应用程序中处理SQL语句还有另一个推荐的最佳做法:getUser 处理程序中的SQL命令。
let sql = `SELECT * FROM users WHERE id='${req.body.id}'`;
一般认为直接将用户计算的值直接传入我们的SQL查询中是不安全的。相反,我们可以在查询中使用占位符,将其映射到数组或对象中定义的一组值。大多数数据库驱动程序和对象关系映射器(ORMS)都提供这种功能。对于我们的端点,用这段代码替换users.js 文件中的getUser 代码处理器。
function getUser(req, res) {
let sql = `SELECT * FROM users WHERE id=?`;
db.all(sql, [req.body.id], function (err, data) {
if (err) throw err;
res.json({
status: 200,
data,
message: "User record retrieved"
});
});
}
更新后的处理程序在我们的查询中使用占位符? ,然后将一个数组作为第二个参数传给db.all() ,其中包含用户的id 。当你重新运行测试时,你会得到一个通过的结果。
我们的测试现在通过了,因为恶意代码被转换为一个非有害的字符串,不匹配任何用户id 。没有用户被返回,保持我们的数据安全,避免不必要的暴露。
测试过程的自动化
本教程的目的不仅是为了运行SQL注入测试。我们希望将整个过程自动化,以便在任何时候将更新推送到代码中,这些测试就会运行,以确保我们的数据得到充分保护。
为了自动运行SQL注入测试,第一步是将项目推送到GitHub。
现在,点击CircleCI仪表板上的设置项目按钮。
通过定义一个名为.circleci/config.yml (你可能需要指定分支)的配置文件,CircleCI会自动检测到它。你可以点击设置项目,开始构建。这次构建会失败,因为你还没有设置好你的配置文件,这是我们的下一步。
在项目的根部创建一个名为.circleci 的文件夹,并在你刚刚创建的文件夹内添加一个名为config.yml 的配置文件。在这个文件中,输入以下代码。
version: 2.1
orbs:
node: circleci/node@5.0.2
jobs:
build-and-test:
executor:
name: node/default
steps:
- checkout
- node/install-packages
- run:
command: npm run test
workflows:
build-and-test:
jobs:
- build-and-test
这个配置从定义Node.js的球体开始。Node.js orb包含一组预包装的CircleCI配置,你可以用它来轻松安装Node.js和它的包管理器(npm、yarn)。
在build-and-test 工作中,我们有步骤来签出/拉出最新的代码变化,安装包和运行测试套件。
注意。 node/install-packages 步骤是在Node.js orb中预定义的。
提交所有的修改并推送到远程仓库以运行构建管道。你应该得到一个成功的构建。
通过点击构建,你可以查看测试的细节。
你可以点击单个步骤来获得更多细节。
测试如期通过,我们现在有一个自动化的管道来测试SQL注入攻击。
注意:避免在生产数据库上运行注入测试;总是在暂存环境中运行这些测试。
总结
数据库是所有数据驱动的应用程序的核心和灵魂(现在几乎所有的应用程序)。对数据库的任何攻击都是不能容忍的,所以彻底测试应用程序代码中的SQL注入攻击是一项要求。在本教程中,我们演示了为一个暴露的端点创建一个简单的注入测试,并使测试过程自动化。把你学到的东西应用到你的团队正在进行的其他项目中,将是非常值得你花时间的。
编码愉快!
Fikayo Adepoju是LinkedIn Learning(Lynda.com)的作者,全栈开发者,技术作家和技术内容创建者,精通Web和移动技术和DevOps,有超过10年的开发可扩展分布式应用的经验。他为CircleCI、Twilio、Auth0和The New Stack博客撰写了40多篇文章,并在他的个人Medium页面上发表,他喜欢将自己的知识分享给尽可能多的开发者,使他们从中受益。你也可以在Udemy上查看他的视频课程。