引言
防御常见的Web攻击
防御XSS攻击
跨站脚本攻击(XSS)是一种常见的攻击方式,攻击者通过在页面插入恶意脚本,来盗取用户信息或者进行其他恶意操作。为了防御XSS攻击,我们需要对用户输入的内容进行转义处理。
const express = require('express');
const app = express();
//const bodyParser = require('body-parser'); // 引入body-parser模块用于解析请求体
//从Express 4.16.0版本开始,
//body-parser模块的功能已经被集成到了Express本身中,因此你可以不用单独安装body-parser,而是直接使用express.json()和express.urlencoded()中间件
app.use(express.json()); // 用于解析JSON格式的请求体
app.use(express.urlencoded({ extended: true })); // 用于解析URL编码的请求体
app.post('/submit-comment', (req, res) => {
  // 获取用户输入的评论内容
  let comment = req.body.comment;
  // 对评论内容进行HTML转义,防止XSS攻击
  comment = escapeHtml(comment);
  console.log('提交的内容:'+comment)
  // 存储评论内容到数据库等操作...
  // ...
  res.send('评论已提交');
});
// HTML转义函数
function escapeHtml(text) {
  const map = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    "'": '''
  };
  return text.replace(/[&<>"']/g, (m) => map[m]);
}
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
 
运行:
 
 
防御CSRF攻击
跨站请求伪造(CSRF)攻击是一种利用用户已登录的身份,在用户不知情的情况下进行恶意请求的攻击方式。为了防御CSRF攻击,我们可以使用CSRF令牌。
const express = require('express');
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
const app = express();
app.use(express.json()); // 用于解析JSON格式的请求体
app.use(express.urlencoded({ extended: true })); // 用于解析URL编码的请求体
// 使用cookie-parser中间件来解析cookie
app.use(cookieParser());
// 设置CSRF保护
const csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, (req, res) => {
  // 发送带有CSRF令牌的表单到客户端
  res.send(`<form action="/submit-form" method="POST">
              <input type="hidden" name="_csrf" value="${req.csrfToken()}">
              <input type="text" name="data">
              <button type="submit">提交</button>
            </form>`);
});
app.post('/submit-form', csrfProtection, (req, res) => {
  // 处理表单提交
  res.send('表单数据已提交');
});
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
 
运行:
 http://localhost:3000/form:
 
 http://localhost:3000/submit-form:
 
防御注入攻击
注入攻击,尤其是SQL注入,是攻击者通过输入恶意数据,篡改后端数据库查询的一种攻击方式。为了防御注入攻击,我们应该使用参数化查询。
MongoDB
const express = require('express');
const mongoose = require('mongoose');
const app = express();
app.use(express.json()); // 用于解析JSON格式的请求体
app.use(express.urlencoded({ extended: true }));
// 连接MongoDB数据库  注释掉用于运行测试用例
// mongoose.connect('mongodb://localhost:27017/userDB');
// 创建用户模型
const User = mongoose.model('User', new mongoose.Schema({
  username: String,
  password: String
}));
app.post('/login', async (req, res) => {
  // 获取用户输入的用户名和密码
  const { username, password } = req.body;
  try {
    // 使用Mongoose的查询方法,它会自动处理参数化查询,防止NoSQL注入
    const user = await User.findOne({ username, password }).exec();
    
    if (user) {
      res.send('登录成功');
    } else {
      res.send('用户名或密码错误');
    }
  } catch (error) {
    res.status(500).send('服务器错误');
  }
});
// app.listen(3000, () => {
//   console.log('Server is running on port 3000');
// });
module.exports = app;
 
mysql
const express = require('express');
const mysql = require('mysql');
const app = express();
app.use(express.urlencoded({ extended: true }));
// 创建数据库连接
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'your_username',
  password: 'your_password',
  database: 'your_database'
});
app.post('/login', (req, res) => {
  // 获取用户输入的用户名和密码
  const username = req.body.username;
  const password = req.body.password;
  // 使用参数化查询防止SQL注入
  const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
  connection.query(query, [username, password], (error, results) => {
    if (error) throw error;
    if (results.length > 0) {
      res.send('登录成功');
    } else {
      res.send('用户名或密码错误');
    }
  });
});
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
 
使用安全相关的中间件
使用Helmet加固HTTP头部
Helmet是一个集成了多个安全相关的HTTP头部设置的中间件,它可以帮助你设置一些安全相关的HTTP头部来增强应用的安全性。
const express = require('express');
const helmet = require('helmet');
const app = express();
// 使用Helmet中间件
app.use(helmet());
// 其他路由和中间件...
// ...
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
 
使用rate-limiter-flexible限制请求频率
为了防止恶意用户或者机器人进行暴力破解或者DDoS攻击,我们可以使用rate-limiter-flexible来限制请求的频率。
const express = require('express');
const { RateLimiterMemory } = require('rate-limiter-flexible');
const app = express();
const port = 3000;
// 配置内存中的限速器
const rateLimiter = new RateLimiterMemory({
  points: 5, // 允许每个IP在一定时间内累积的最大点数
  duration: 1, // 一个时间窗口的长度(秒)
  // 还有其他可配置的选项
});
// 限速器中间件
const rateLimiterMiddleware = (req, res, next) => {
  rateLimiter.consume(req.ip)
    .then(() => {
      next(); // 在未超出限制的情况下继续处理请求
    })
    .catch(() => {
      res.status(429).send('Too Many Requests'); // 当达到限制时发送429状态
    });
};
app.use(rateLimiterMiddleware);
app.get('/limit-query', (req, res) => {
  res.send('Hello World!');
});
app.listen(port, () => {
  console.log(`App listening at http://localhost:${port}`);
});
 
测试:
 为了自动化测试和模拟连续的请求,您可以在Linux或Mac终端中使用以下简单的bash循环命令:
for i in {1..10}; do curl http://localhost:3000/limit-query; done
 
运行结果:
 
测试用例
防御XSS攻击的测试用例
//test18.js 修改部分代码
app.post('/submit-comment', (req, res) => {
  // ...
  res.send(comment);
});
// test18.test.js
const request = require('supertest');
const app = require('../test18'); // 你的Express应用导出在test18.js文件中
describe('XSS  Attack Prevention',()=>{
  it('它应该转义HTML字符以防止XSS',async  ()=>{
    const maliciousString = '<script>alert("xss")</script>';
    const response = await request(app).post('/submit-comment').send({comment: maliciousString})
    expect(response.text).not.toContain(maliciousString);
    expect(response.text).toContain('<script>alert("xss")</script>');
  })
})
 
测试结果:
 
防御CSRF攻击的测试用例
// test18-2.test.js
const request = require('supertest');
const app = require('../test18-2'); 
describe('CSRF Attack Prevention', () => {
  test('它应该提供CSRF令牌', async () => {
    const getResponse = await request(app).get('/form');
    console.log(1111111,getResponse.text)
    expect(getResponse.text).toMatch(/name="_csrf"/);
  });
  test('它应该拒绝没有CSRF令牌的表单提交', async () => {
    const postResponse = await request(app)
      .post('/submit-form')
      .send({ data: 'test' });
    expect(postResponse.statusCode).toBe(403);
  });
});
 
测试结果:
 
防御注入攻击的测试用例
// 引入mongoose,用于操作MongoDB数据库
const mongoose = require('mongoose');
// 引入mongodb-memory-server,用于创建MongoDB内存服务器,便于进行测试
const { MongoMemoryServer } = require('mongodb-memory-server'); //版本@6.9.6
// 引入supertest,用于模拟HTTP请求
const supertest = require('supertest');
// 引入你的app,用于测试
const app = require('../test18-4');
// 定义一个变量,用于存储MongoDB内存服务器的实例
let mongoServer;
// 在所有测试用例执行前,启动MongoDB内存服务器并连接
beforeAll(async () => {
  mongoServer = new MongoMemoryServer();
  const mongoUri = await mongoServer.getUri();
  mongoose.connect(mongoUri);
});
// 在所有测试用例执行完毕后,断开数据库连接并停止MongoDB内存服务器
afterAll(async () => {
  mongoose.disconnect();
  await mongoServer.stop();
});
describe('NoSQL Injection Prevention', ()=>{
  it('它不应该允许NoSQL注入',async  ()=> {
    // 创建用户模型
    const User = mongoose.model('User2', new mongoose.Schema({
      username: String,
      password: String
    }));
    const user = new User({ username: 'user1', password: 'password1' });
    await user.save(); // 保存用户到数据库
     // 尝试使用注入攻击的方式查询用户
     const maliciousUsername = 'user1' + '{$ne: null}'; // 这是一个注入攻击的尝试
     const maliciousPassword = 'password1' + '{$ne: null}'; // 这是一个注入攻击的尝试
    const response = await supertest(app)
    .post('/login')
    .send({ username: maliciousUsername, password: maliciousPassword });
    expect(response.body.message).not.toBe('登录成功');
  })
})
 
测试结果:
 
总结
通过上述的措施,我们可以显著提高Node.js应用的安全性,从而更好地保护用户数据和服务的稳定性。当然,这些只是安全性加固的一部分,实际应用中还需要根据具体情况采取更多的安全措施。










