jquery - 在保护评论表单和相关 API 端点时,是否应该在浏览器、服务器或两者中对输入进行清理、验证和编码?
问题描述
我试图在没有用户身份验证的非 CMS 环境中尽可能确保评论表单的安全。
表单应该对浏览器和 curl/postman 类型的请求都是安全的。
环境
后端 - Node.js、MongoDB Atlas 和 Azure Web 应用程序。
前端 - jQuery。
下面是对我当前工作实现的详细但希望不会太压倒性的概述。
接下来是我关于实施的问题。
使用的相关库
Helmet - 通过设置各种 HTTP 标头来帮助保护 Express 应用程序,包括Content Security Policy
reCaptcha v3 - 防止垃圾邮件和其他类型的自动滥用
DOMPurify - XSS sanitizer
validator.js - 字符串验证器和清理器库
he - HTML 实体编码器/解码器
数据的一般流程是:
/*
on click event:
- get sanitized data
- perform some validations
- html encode the values
- get recaptcha v3 token from google
- send all data, including token, to server
- send token to google to verify
- if the response 'score' is above 0.5, add the submission to the database
- return the entry to the client and populate the DOM with the submission
*/
POST 请求 - 浏览器
// test input:
// <script>alert("hi!")</script><h1>hello there!</h1> <a href="">link</a>
// sanitize the input
var sanitized_input_1_text = DOMPurify.sanitize($input_1.val().trim(), { SAFE_FOR_JQUERY: true });
var sanitized_input_2_text = DOMPurify.sanitize($input_2.val().trim(), { SAFE_FOR_JQUERY: true });
// validation - make sure input is between 1 and 140 characters
var input_1_text_valid_length = validator.isLength(sanitized_input_1_text, { min: 1, max: 140 });
var input_2_text_valid_length = validator.isLength(sanitized_input_2_text, { min: 1, max: 140 });
// if validations pass
if (input_1_text_valid_length === true && input_2_text_valid_length === true) {
/*
encode the sanitized input
not sure if i should encode BEFORE adding to MongoDB
or just add to database "as is" and encode BEFORE displaying in the DOM with $("#ouput").html(html_content);
*/
var sanitized_encoded_input_1_text = he.encode(input_1_text);
var sanitized_encoded_input_2_text = he.encode(input_2_text);
// define parameters to send to database
var parameters = {};
parameters.input_1_text = sanitized_encoded_input_1_text;
parameters.input_2_text = sanitized_encoded_input_2_text;
// get token from google and send token and input to database
// see: https://developers.google.com/recaptcha/docs/v3#programmatically_invoke_the_challenge
grecaptcha.ready(function() {
grecaptcha.execute('site-key-here', { action: 'submit' }).then(function(token) {
parameters.token = token;
jquery_ajax_call_to_my_api(parameters);
});
});
}
POST 请求 - 服务器
var secret_key = process.env.RECAPTCHA_SECRET_SITE_KEY;
var token = req.body.token;
var url = `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${token}`;
// verify recaptcha token with google
var response = await fetch(url);
var response_json = await response.json();
var score = response_json.score;
var document = {};
/*
if google's response 'score' is greater than 0.5,
add submission to the database and populate client DOM with $("#output").prepend(html);
see: https://developers.google.com/recaptcha/docs/v3#interpreting_the_score
*/
if (score >= 0.5) {
// add submission to database
// return submisson to client to update the DOM
// DOM will just display this text: <h1>hello there!</h1> <a href="">link</a>
});
获取页面加载请求
逻辑/假设:
- 获取所有提交,返回客户端并使用
$("#output").html(html_content);
. - 在填充 DOM 之前不需要对值进行编码,因为值已经在数据库中编码?
来自 curl、邮递员等的 POST 请求
逻辑/假设:
- 他们没有谷歌令牌,因此无法从服务器验证它,也无法向数据库添加条目?
服务器上的头盔配置
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://somedomain.io", "https://maps.googleapis.com", "https://www.google.com", "https://www.gstatic.com"],
styleSrc: ["'self'", "fonts.googleapis.com", "'unsafe-inline'"],
fontSrc: ["'self'", "fonts.gstatic.com"],
imgSrc: ["'self'", "https://maps.gstatic.com", "https://maps.googleapis.com", "data:"],
frameSrc: ["'self'", "https://www.google.com"]
}
},
})
);
问题
我应该将值作为 HTML 编码实体添加到 MongoDB 数据库,还是“按原样”存储它们并在用它们填充 DOM 之前对其进行编码?
如果将值保存为MongoDB 中的 html 实体,这是否会使在数据库中搜索内容变得困难,因为搜索
"<h1>hello there!</h1> <a href="">link</a>
不会返回任何结果,因为数据库中的值是<h1>hello there!</h1> <a href="">link</a>
在我阅读有关保护 Web 表单的内容时,很多关于客户端实践的说法是相当多余的,因为可以在 DOM 中更改任何内容,可以禁用 JavaScript,并且可以使用 curl 或 postman 直接向 API 端点发出请求,从而绕过任何客户端方法。
话虽如此,是否应该执行清理(DOMPurify)、验证(validator.js)和编码(he):1)仅客户端 2)客户端和服务器端或 3)仅服务器端?
为了彻底,这里是另一个相关的问题:
从客户端向服务器发送数据时,以下任何组件是否执行任何自动转义或 HTML 编码?我问是因为如果他们这样做,它可能会使一些手动转义或编码变得不必要。
- jQuery ajax() 请求
- 节点.js
- 表达
- 头盔
- bodyParser(节点包)
- MongoDB本机驱动程序
- MongoDB
解决方案
您应该始终不确定您使用的每个数据在使用前是否在后端进行了清理!
见https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
推荐阅读
- python - 加载模型一次,而不是每次调用图像检测脚本
- docker - 哪个是 Antlr/Antlr4 的标准 docker 镜像?
- python - 树的直径
- ios - 需要未知模块:“未定义”
- javascript - 在链接之后附加 html(以显示链接预览)
- php - php DateTime->diff 的间歇性问题
- amazon-web-services - 从 AWS 开发工具包 v3 中的本地 json 文件加载凭证?
- mongodb - Intellishell 重复文档清理
- java - 无法在 MacOS Big Sur 上安装 NetBeans
- python - 有没有一种优雅的方法可以将别名映射到连接数据文件中的真实实体名称?