Node.js+Express+Koa2开发接口学习笔记(六)
预防sql注入
sql注入
- 最原始、最简单的攻击,从有了web2.0就有了sql注入攻击
- 攻击方式:输入一个sql片段,最终拼接成一段攻击代码
- 预防措施:使用mysql的escape函数处理输入内容即可
例如登录,访问http://localhost:8080/login.html
正常情况下是输入正确的用户名和密码来进行登录,这个登录是根据传入的username和password参数拼接sql语句来实现的。
// src/controller/user.js
const login = ({ username, password }) => {
const sql = `
select username,realname from users where username='${username}' and password='${password}'
`;
return exec(sql).then((rows) => {
return rows[0] || {};
});
};
-- 拼接出来的语句
select username,realname from users where username='zhangsan' and password='123456';
因为username和password是传进去的,如果传进去的是(zhangsan'-- ),拼接语句就变成了
-- 拼接出来的语句
select username,realname from users where username='zhangsan'-- ' and password='123456';
在sql中 --
+空格是用来写注释语句的,可以看到上面的拼接语句密码处已经是无效的了。在登录页输入(zhangsan'-- )竟然可以登录成功,并且可以新建博客等操作。
如何预防?
在mysql.js文件中导出mysql
的escape
函数
const mysql = require("mysql");
const { MYSQL_CONF } = require("../conf/db");
...
module.exports = {
exec,
escape: mysql.escape,
};
然后在 src/controller/user.js使用该函数对参数进行处理,以及修改去掉拼接语句处的单引号
const { exec, escape } = require("../db/mysql");
const login = ({ username, password }) => {
username = escape(username);
password = escape(password);
const sql = `
select username,realname from users where username=${username} and password=${password}
`;
console.log("sql", sql);
return exec(sql).then((rows) => {
return rows[0] || {};
});
};
module.exports = {
login,
};
在登录页输入(zhangsan'-- )已经提示登录错误了,说明预防sql注入成功。通过escape
函数后得到的拼接语句变成了
select username,realname from users where username='zhangsan\'-- ' and password='1'
('--)
被escape
去掉特殊化,只当作普通字符串,被整体包裹起来而非起到注释作用。
XSS攻击
- 攻击方式:在页面展示内容中掺杂js代码,以获取网页信息
- 预防措施:转换生成js的特殊字符
& -> &
< -> <
> -> >
" -> "
' -> '
/ -> /
例如在新建博客页面中,在标题处输入以下文本
<script>alert(document.cookie)</script>
它会在创建成功后返回管理页面执行脚本,弹出获取到的信息。
安装工具:npm i xss --save
,使用这个工具可以转换生成js的特殊字符
在controller->blog.js
const xss = require("xss");
const newBlog = (blogData = {}) => {
const title = xss(blogData.title);
const content = xss(blogData.content);
...
}
在新建博客页面中,在标题或内容处输入刚才的文本,可能会由于sql存储字符串长度限制报错,但是在控制台可以看到语句经过xss转义变成了不具有攻击能力
的语句:
<script>alert(document.cookie)</script>
用户加密
- 万一数据库被用户攻破,最不应该泄漏的就是用户信息
- 攻击方式:获取用户名和密码,再去尝试登录其他系统
- 预防措施:将密码加密,即使拿到密码也不知道明文
在utils->crypt.js,通过node提供的crypto库编写加密函数
const crypto = require("crypto");
// 密钥
const SECRET_KEY = "WJiol_8776#";
// md5加密
function md5(content) {
let md5 = crypto.createHash("md5");
return md5.update(content).digest("hex");
}
// 加密函数
function genPassword(password) {
const str = `password=${password}&key=${SECRET_KEY}`;
return md5(str);
}
module.exports = {
genPassword,
};
md5加密生出来的字符串是32位长度,需要去修改数据库中password的长度。
通过genPassword('123')
可以看到加密后的密码为524ab85686df0e52ada43b11b53cce35,为了方便登录验证,将数据库中密码为123的其中一位用户密码改为加密后的密码。
在controller->login.js中将接收到的password参数进行加密
const { exec, escape } = require("../db/mysql");
const { genPassword } = require("../utils/crypt");
const login = ({ username, password }) => {
username = escape(username);
// 生成加密密码
password = genPassword(password);
password = escape(password);
const sql = `
select username,realname from users where username=${username} and password=${password}`;
return exec(sql).then((rows) => {
return rows[0] || {};
});
};
module.exports = {
login,
};
分别验证被加密以及未加密的用户登录。