标准:腾讯代码安全指南
This commit is contained in:
parent
77d7018819
commit
d98cb3b45a
@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
id: 谷歌开源项目风格指南
|
|
||||||
title: 谷歌开源项目风格指南
|
|
||||||
data: 2022年5月30日
|
|
||||||
---
|
|
||||||
|
|
||||||
## 谷歌官方
|
|
||||||
|
|
||||||
仓库地址:[Google Style Guides](https://github.com/google/styleguide)
|
|
||||||
|
|
||||||
在线阅读:[Google Style Guides](https://google.github.io/styleguide/)
|
|
||||||
|
|
||||||
## 中国版
|
|
||||||
|
|
||||||
仓库地址:[Google 开源项目风格指南(中文版)](https://github.com/zh-google-styleguide/zh-google-styleguide)
|
|
||||||
|
|
||||||
阅读地址:[Google 开源项目风格指南(中文版)](https://zh-google-styleguide.readthedocs.io/en/latest/)
|
|
||||||
|
|
1770
docs/标准/腾讯代码安全指南/C,C++安全指南.md
Normal file
1770
docs/标准/腾讯代码安全指南/C,C++安全指南.md
Normal file
File diff suppressed because it is too large
Load Diff
1079
docs/标准/腾讯代码安全指南/Go安全指南.md
Normal file
1079
docs/标准/腾讯代码安全指南/Go安全指南.md
Normal file
File diff suppressed because it is too large
Load Diff
877
docs/标准/腾讯代码安全指南/JavaScript安全指南.md
Normal file
877
docs/标准/腾讯代码安全指南/JavaScript安全指南.md
Normal file
@ -0,0 +1,877 @@
|
|||||||
|
---
|
||||||
|
id: JavaScript 安全指南
|
||||||
|
title: JavaScript 安全指南
|
||||||
|
sidebar_position: 3
|
||||||
|
data: 2022年5月30日
|
||||||
|
---
|
||||||
|
## JavaScript页面类
|
||||||
|
|
||||||
|
### 1. 代码实现
|
||||||
|
|
||||||
|
#### 1.1 原生DOM API的安全操作
|
||||||
|
|
||||||
|
##### 1.1.1 HTML标签操作,限定/过滤传入变量值
|
||||||
|
|
||||||
|
- 使用`innerHTML=`、`outerHTML=`、`document.write()`、`document.writeln()`时,如变量值外部可控,应对特殊字符(`&, <, >, ", '`)做编码转义,或使用安全的DOM API替代,包括:`innerText=`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 假设 params 为用户输入, text 为 DOM 节点
|
||||||
|
// bad:将不可信内容带入HTML标签操作
|
||||||
|
const { user } = params;
|
||||||
|
// ...
|
||||||
|
text.innerHTML = `Follow @${user}`;
|
||||||
|
|
||||||
|
// good: innerHTML操作前,对特殊字符编码转义
|
||||||
|
function htmlEncode(iStr) {
|
||||||
|
let sStr = iStr;
|
||||||
|
sStr = sStr.replace(/&/g, "&");
|
||||||
|
sStr = sStr.replace(/>/g, ">");
|
||||||
|
sStr = sStr.replace(/</g, "<");
|
||||||
|
sStr = sStr.replace(/"/g, """);
|
||||||
|
sStr = sStr.replace(/'/g, "'");
|
||||||
|
return sStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { user } = params;
|
||||||
|
user = htmlEncode(user);
|
||||||
|
// ...
|
||||||
|
text.innerHTML = `Follow @${user}`;
|
||||||
|
|
||||||
|
// good: 使用安全的DOM API替代innerHTML
|
||||||
|
const { user } = params;
|
||||||
|
// ...
|
||||||
|
text.innerText = `Follow @${user}`;
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.1.2 HTML属性操作,限定/过滤传入变量值
|
||||||
|
|
||||||
|
- 使用`element.setAttribute(name, value);`时,如第一个参数值`name`外部可控,应用白名单限定允许操作的属性范围。
|
||||||
|
|
||||||
|
- 使用`element.setAttribute(name, value);`时,操作`a.href`、`ifame.src`、`form.action`、`embed.src`、`object.data`、`link.href`、`area.href`、`input.formaction`、`button.formaction`属性时,如第二个参数值`value`外部可控,应参考*JavaScript页面类规范1.3.1*部分,限定页面重定向或引入资源的目标地址。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// good: setAttribute操作前,限定引入资源的目标地址
|
||||||
|
function addExternalCss(e) {
|
||||||
|
const t = document.createElement('link');
|
||||||
|
t.setAttribute('href', e),
|
||||||
|
t.setAttribute('rel', 'stylesheet'),
|
||||||
|
t.setAttribute('type', 'text/css'),
|
||||||
|
document.head.appendChild(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
function validURL(sUrl) {
|
||||||
|
return !!((/^(https?:\/\/)?[\w\-.]+\.(qq|tencent)\.com($|\/|\\)/i).test(sUrl) || (/^[\w][\w/.\-_%]+$/i).test(sUrl) || (/^[/\\][^/\\]/i).test(sUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
let sUrl = "https://evil.com/1.css"
|
||||||
|
if (validURL(sUrl)) {
|
||||||
|
addExternalCss(sUrl);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.2 流行框架/库的安全操作
|
||||||
|
|
||||||
|
##### 1.2.1 限定/过滤传入jQuery不安全函数的变量值
|
||||||
|
|
||||||
|
- 使用`.html()`、`.append()`、`.prepend()`、`.wrap()`、`.replaceWith()`、`.wrapAll()`、`.wrapInner()`、`.after()`、`.before()`时,如变量值外部可控,应对特殊字符(`&, <, >, ", '`)做编码转义。
|
||||||
|
- 引入`jQuery 1.x(等于或低于1.12)、jQuery2.x(等于或低于2.2)`,且使用`$()`时,应优先考虑替换为最新版本。如一定需要使用,应对传入参数值中的特殊字符(`&, <, >, ", '`)做编码转义。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bad:将不可信内容,带入jQuery不安全函数.after()操作
|
||||||
|
const { user } = params;
|
||||||
|
// ...
|
||||||
|
$("p").after(user);
|
||||||
|
|
||||||
|
// good: jQuery不安全函数.html()操作前,对特殊字符编码转义
|
||||||
|
function htmlEncode(iStr) {
|
||||||
|
let sStr = iStr;
|
||||||
|
sStr = sStr.replace(/&/g, "&");
|
||||||
|
sStr = sStr.replace(/>/g, ">");
|
||||||
|
sStr = sStr.replace(/</g, "<");
|
||||||
|
sStr = sStr.replace(/"/g, """);
|
||||||
|
sStr = sStr.replace(/'/g, "'");
|
||||||
|
return sStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const user = params.user;
|
||||||
|
user = htmlEncode(user);
|
||||||
|
// ...
|
||||||
|
$("p").html(user);
|
||||||
|
```
|
||||||
|
|
||||||
|
- 使用`.attr()`操作`a.href`、`ifame.src`、`form.action`、`embed.src`、`object.data`、`link.href`、`area.href`、`input.formaction`、`button.formaction`属性时,应参考*JavaScript页面类规范1.3.1*部分,限定重定向的资源目标地址。
|
||||||
|
|
||||||
|
- 使用`.attr(attributeName, value)`时,如第一个参数值`attributeName`外部可控,应用白名单限定允许操作的属性范围。
|
||||||
|
|
||||||
|
- 使用`$.getScript(url [, success ])`时,如第一个参数值`url`外部可控(如:从URL取值拼接,请求jsonp接口),应限定可控变量值的字符集范围为:`[a-zA-Z0-9_-]+`。
|
||||||
|
|
||||||
|
##### 1.2.2 限定/过滤传入Vue.js不安全函数的变量值
|
||||||
|
|
||||||
|
- 使用`v-html`时,不允许对用户提供的内容使用HTML插值。如业务需要,应先对不可信内容做富文本过滤。
|
||||||
|
|
||||||
|
```html
|
||||||
|
// bad:直接渲染外部传入的不可信内容
|
||||||
|
<div v-html="userProvidedHtml"></div>
|
||||||
|
|
||||||
|
// good:使用富文本过滤库处理不可信内容后渲染
|
||||||
|
<!-- 使用 -->
|
||||||
|
<div v-xss-html="{'mode': 'whitelist', dirty: html, options: options}" ></div>
|
||||||
|
|
||||||
|
<!-- 配置 -->
|
||||||
|
<script>
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
options: {
|
||||||
|
whiteList: {
|
||||||
|
a: ["href", "title", "target", "class", "id"],
|
||||||
|
div: ["class", "id"],
|
||||||
|
span: ["class", "id"],
|
||||||
|
img: ["src", "alt"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
- 使用`v-bind`操作`a.href`、`ifame.src`、`form.action`、`embed.src`、`object.data`、`link.href`、`area.href`、`input.formaction`、`button.formaction`时,应确保后端已参考*JavaScript页面类规范1.3.1*部分,限定了供前端调用的重定向目标地址。
|
||||||
|
|
||||||
|
- 使用`v-bind`操作`style`属性时,应只允许外部控制特定、可控的CSS属性值
|
||||||
|
|
||||||
|
```html
|
||||||
|
// bad:v-bind允许外部可控值,自定义CSS属性及数值
|
||||||
|
<a v-bind:href="sanitizedUrl" v-bind:style="userProvidedStyles">
|
||||||
|
click me
|
||||||
|
</a>
|
||||||
|
|
||||||
|
// good:v-bind只允许外部提供特性、可控的CSS属性值
|
||||||
|
<a v-bind:href="sanitizedUrl" v-bind:style="{
|
||||||
|
color: userProvidedColor,
|
||||||
|
background: userProvidedBackground
|
||||||
|
}" >
|
||||||
|
click me
|
||||||
|
</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.3 页面重定向
|
||||||
|
|
||||||
|
##### 1.3.1 限定跳转目标地址
|
||||||
|
|
||||||
|
- 使用白名单,限定重定向地址的协议前缀(默认只允许HTTP、HTTPS)、域名(默认只允许公司根域),或指定为固定值;
|
||||||
|
|
||||||
|
- 适用场景包括,使用函数方法:`location.href`、`window.open()`、`location.assign()`、`location.replace()`;赋值或更新HTML属性:`a.href`、`ifame.src`、`form.action`、`embed.src`、`object.data`、`link.href`、`area.href`、`input.formaction`、`button.formaction`;
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bad: 跳转至外部可控的不可信地址
|
||||||
|
const sTargetUrl = getURLParam("target");
|
||||||
|
location.replace(sTargetUrl);
|
||||||
|
|
||||||
|
// good: 白名单限定重定向地址
|
||||||
|
function validURL(sUrl) {
|
||||||
|
return !!((/^(https?:\/\/)?[\w\-.]+\.(qq|tencent)\.com($|\/|\\)/i).test(sUrl) || (/^[\w][\w/.\-_%]+$/i).test(sUrl) || (/^[/\\][^/\\]/i).test(sUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
const sTargetUrl = getURLParam("target");
|
||||||
|
if (validURL(sTargetUrl)) {
|
||||||
|
location.replace(sTargetUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// good: 制定重定向地址为固定值
|
||||||
|
const sTargetUrl = "http://www.qq.com";
|
||||||
|
location.replace(sTargetUrl);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.4 JSON解析/动态执行
|
||||||
|
|
||||||
|
##### 1.4.1 使用安全的JSON解析方式
|
||||||
|
|
||||||
|
- 应使用`JSON.parse()`解析JSON字符串。低版本浏览器,应使用安全的[Polyfill封装](https://github.com/douglascrockford/JSON-js/blob/master/json2.js)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bad: 直接调用eval解析json
|
||||||
|
const sUserInput = getURLParam("json_val");
|
||||||
|
const jsonstr1 = `{"name":"a","company":"b","value":"${sUserInput}"}`;
|
||||||
|
const json1 = eval(`(${jsonstr1})`);
|
||||||
|
|
||||||
|
// good: 使用JSON.parse解析
|
||||||
|
const sUserInput = getURLParam("json_val");
|
||||||
|
JSON.parse(sUserInput, (k, v) => {
|
||||||
|
if (k === "") return v;
|
||||||
|
return v * 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
// good: 低版本浏览器,使用安全的Polyfill封装(基于eval)
|
||||||
|
<script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script>;
|
||||||
|
const sUserInput = getURLParam("json_val");
|
||||||
|
JSON.parse(sUserInput);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.5 跨域通讯
|
||||||
|
|
||||||
|
##### 1.5.1 使用安全的前端跨域通信方式
|
||||||
|
|
||||||
|
- 具有隔离登录态(如:p_skey)、涉及用户高敏感信息的业务(如:微信网页版、QQ空间、QQ邮箱、公众平台),禁止通过`document.domain`降域,实现前端跨域通讯,应使用postMessage替代。
|
||||||
|
|
||||||
|
##### 1.5.2 使用postMessage应限定Origin
|
||||||
|
|
||||||
|
- 在message事件监听回调中,应先使用`event.origin`校验来源,再执行具体操作。
|
||||||
|
|
||||||
|
- 校验来源时,应使用`===`判断,禁止使用`indexOf()`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bad: 使用indexOf校验Origin值
|
||||||
|
window.addEventListener("message", (e) => {
|
||||||
|
if (~e.origin.indexOf("https://a.qq.com")) {
|
||||||
|
// ...
|
||||||
|
} else {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// good: 使用postMessage时,限定Origin,且使用===判断
|
||||||
|
window.addEventListener("message", (e) => {
|
||||||
|
if (e.origin === "https://a.qq.com") {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置&环境
|
||||||
|
|
||||||
|
#### 2.1 敏感/配置信息
|
||||||
|
|
||||||
|
##### 2.1.1 禁止明文硬编码AK/SK
|
||||||
|
|
||||||
|
- 禁止前端页面的JS明文硬编码AK/SK类密钥,应封装成后台接口,AK/SK保存在后端配置中心或密钥管理系统
|
||||||
|
|
||||||
|
#### 2.2 第三方组件/资源
|
||||||
|
|
||||||
|
##### 2.2.1 使用可信范围内的统计组件
|
||||||
|
|
||||||
|
##### 2.2.2 禁止引入非可信来源的第三方JS
|
||||||
|
|
||||||
|
#### 2.3 纵深安全防护
|
||||||
|
|
||||||
|
##### 2.3.1 部署CSP,并启用严格模式
|
||||||
|
|
||||||
|
## Node.js后台类
|
||||||
|
|
||||||
|
### 1. 代码实现
|
||||||
|
|
||||||
|
#### 1.1 输入验证
|
||||||
|
|
||||||
|
##### 1.1.1 按类型进行数据校验
|
||||||
|
|
||||||
|
- 所有程序外部输入的参数值,应进行数据校验。校验内容包括但不限于:数据长度、数据范围、数据类型与格式。校验不通过,应拒绝。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bad:未进行输入验证
|
||||||
|
Router.get("/vulxss", (req, res) => {
|
||||||
|
const { txt } = req.query;
|
||||||
|
res.set("Content-Type", "text/html");
|
||||||
|
res.send({
|
||||||
|
data: txt,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// good:按数据类型,进行输入验证
|
||||||
|
const Router = require("express").Router();
|
||||||
|
const validator = require("validator");
|
||||||
|
|
||||||
|
Router.get("/email_with_validator", (req, res) => {
|
||||||
|
const txt = req.query.txt || "";
|
||||||
|
if (validator.isEmail(txt)) {
|
||||||
|
res.send({
|
||||||
|
data: txt,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.send({ err: 1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
关联漏洞:*纵深防护措施 - 安全性增强特性*
|
||||||
|
|
||||||
|
#### 1.2 执行命令
|
||||||
|
|
||||||
|
##### 1.2.1 使用child_process执行系统命令,应限定或校验命令和参数的内容
|
||||||
|
|
||||||
|
- 适用场景包括:`child_process.exec`, `child_process.execSync`, `child_process.spawn`, `child_process.spawnSync`, `child_process.execFile`, `child_process.execFileSync`
|
||||||
|
|
||||||
|
- 调用上述函数,应首先考虑限定范围,供用户选择。
|
||||||
|
|
||||||
|
- 使用`child_process.exec`或`child_process.execSync`时,如果可枚举输入的参数内容或者格式,则应限定白名单。如果无法枚举命令或参数,则必须过滤或者转义指定符号,包括:```|;&$()><`!```
|
||||||
|
|
||||||
|
- 使用`child_process.spawn` 或`child_process.execFile`时,应校验传入的命令和参数在可控列表内。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const Router = require("express").Router();
|
||||||
|
const validator = require("validator");
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
|
||||||
|
// bad:未限定或过滤,直接执行命令
|
||||||
|
Router.get("/vul_cmd_inject", (req, res) => {
|
||||||
|
const txt = req.query.txt || "echo 1";
|
||||||
|
exec(txt, (err, stdout, stderr) => {
|
||||||
|
if (err) { res.send({ err: 1 }) }
|
||||||
|
res.send({stdout, stderr});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// good:通过白名单,限定外部可执行命令范围
|
||||||
|
Router.get("/not_vul_cmd_inject", (req, res) => {
|
||||||
|
const txt = req.query.txt || "echo 1";
|
||||||
|
const phone = req.query.phone || "";
|
||||||
|
const cmdList = {
|
||||||
|
sendmsg: "./sendmsg "
|
||||||
|
};
|
||||||
|
if (txt in cmdList && validator.isMobilePhone(phone)) {
|
||||||
|
exec(cmdList[txt] + phone, (err, stdout, stderr) => {
|
||||||
|
if (err) { res.send({ err: 1 }) };
|
||||||
|
res.send({stdout, stderr});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.send({
|
||||||
|
err: 1,
|
||||||
|
tips: `you can use '${Object.keys(cmdList)}'`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// good:执行命令前,过滤/转义指定符号
|
||||||
|
Router.get("/not_vul_cmd_inject", (req, res) => {
|
||||||
|
const txt = req.query.txt || "echo 1";
|
||||||
|
let phone = req.query.phone || "";
|
||||||
|
const cmdList = {
|
||||||
|
sendmsg: "./sendmsg "
|
||||||
|
};
|
||||||
|
phone = phone.replace(/(\||;|&|\$\(|\(|\)|>|<|\`|!)/gi,"");
|
||||||
|
if (txt in cmdList) {
|
||||||
|
exec(cmdList[txt] + phone, (err, stdout, stderr) => {
|
||||||
|
if (err) { res.send({ err: 1 }) };
|
||||||
|
res.send({stdout, stderr});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.send({
|
||||||
|
err: 1,
|
||||||
|
tips: `you can use '${Object.keys(cmdList)}'`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
关联漏洞:高风险 - 任意命令执行
|
||||||
|
|
||||||
|
#### 1.3 文件操作
|
||||||
|
|
||||||
|
##### 1.3.1 限定文件操作的后缀范围
|
||||||
|
|
||||||
|
- 按业务需求,使用白名单限定后缀范围。
|
||||||
|
|
||||||
|
##### 1.3.2 校验并限定文件路径范围
|
||||||
|
|
||||||
|
- 应固定上传、访问文件的路径。若需要拼接外部可控变量值,检查是否包含`..`、`.`路径穿越字符。如存在,应拒绝。
|
||||||
|
- 使用`fs`模块下的函数方法时,应对第一个参数即路径部分做校验,检查是否包含路径穿越字符`.`或`..`。涉及方法包括但不限于:`fs.truncate`、`fs.truncateSync`、`fs.chown`、`fs.chownSync`、`fs.lchown`、`fs.lchownSync`、`fs.stat`、`fs.lchmodSync`、`fs.lstat`、`fs.statSync`、`fs.lstatSync`、`fs.readlink`、`fs.unlink`、`fs.unlinkSync`、`fs.rmdir`、`fs.rmdirSync`、`fs.mkdir`、`fs.mkdirSync`、`fs.readdir`、`fs.readdirSync`、`fs.openSync`、`fs.open`、`fs.createReadStream`、`fs.createWriteStream`
|
||||||
|
- 使用express框架的`sendFile`方法时,应对第一个参数即路径部分做校验,检查是否包含路径穿越字符`.`或`..`
|
||||||
|
- 校验时,应使用`path`模块处理前的路径参数值,或判断处理过后的路径是否穿越出了当前工作目录。涉及方法包括但不限于:`path.resolve`、`path.join`、`path.normalize`等
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
let filename = req.query.ufile;
|
||||||
|
let root = '/data/ufile';
|
||||||
|
|
||||||
|
// bad:未检查文件名/路径
|
||||||
|
fs.readFile(root + filename, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
return console.error(err);
|
||||||
|
}
|
||||||
|
console.log(`异步读取: ${data.toString()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// bad:使用path处理过后的路径参数值做校验,仍可能有路径穿越风险
|
||||||
|
filename = path.join(root, filename);
|
||||||
|
if (filename.indexOf("..") < 0) {
|
||||||
|
fs.readFile(filename, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
return console.error(err);
|
||||||
|
}
|
||||||
|
console.log(data.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// good:检查了文件名/路径,是否包含路径穿越字符
|
||||||
|
if (filename.indexOf("..") < 0) {
|
||||||
|
filename = path.join(root, filename);
|
||||||
|
fs.readFile(filename, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
return console.error(err);
|
||||||
|
}
|
||||||
|
console.log(data.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.3.3 安全地处理上传文件名
|
||||||
|
|
||||||
|
- 将上传文件重命名为16位以上的随机字符串保存。
|
||||||
|
- 如需原样保留文件名,应检查是否包含`..`、`.`路径穿越字符。如存在,应拒绝。
|
||||||
|
|
||||||
|
##### 1.3.4 敏感资源文件,应有加密、鉴权和水印等加固措施
|
||||||
|
|
||||||
|
- 用户上传的`身份证`、`银行卡`等图片,属敏感资源文件,应采取安全加固。
|
||||||
|
- 指向此类文件的URL,应保证不可预测性;同时,确保无接口会批量展示此类资源的URL。
|
||||||
|
- 访问敏感资源文件时,应进行权限控制。默认情况下,仅用户可查看、操作自身敏感资源文件。
|
||||||
|
- 图片类文件应添加业务水印,表明该图片仅可用于当前业务使用。
|
||||||
|
|
||||||
|
#### 1.4 网络请求
|
||||||
|
|
||||||
|
##### 1.4.1 限定访问网络资源地址范围
|
||||||
|
|
||||||
|
- 应固定程序访问网络资源地址的`协议`、`域名`、`路径`范围。
|
||||||
|
|
||||||
|
- 若业务需要,外部可指定访问网络资源地址,应禁止访问内网私有地址段及域名。
|
||||||
|
|
||||||
|
```text
|
||||||
|
// 以RFC定义的专有网络为例,如有自定义私有网段亦应加入禁止访问列表。
|
||||||
|
10.0.0.0/8
|
||||||
|
172.16.0.0/12
|
||||||
|
192.168.0.0/16
|
||||||
|
127.0.0.0/8
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.4.2 【推荐】请求网络资源,应加密传输
|
||||||
|
|
||||||
|
- 应优先选用https协议请求网络资源
|
||||||
|
|
||||||
|
关联漏洞:*高风险 - SSRF,高风险 - HTTP劫持*
|
||||||
|
|
||||||
|
#### 1.5 数据输出
|
||||||
|
|
||||||
|
##### 1.5.1 高敏感信息禁止存储、展示
|
||||||
|
|
||||||
|
- 口令、密保答案、生理标识等鉴权信息禁止展示
|
||||||
|
- 非金融类业务,信用卡cvv码及日志禁止存储
|
||||||
|
|
||||||
|
##### 1.5.2 一般敏感信息脱敏展示
|
||||||
|
|
||||||
|
- 身份证只显示第一位和最后一位字符,如:`3*********************1`
|
||||||
|
- 移动电话号码隐藏中间6位字符,如:`134***************48`
|
||||||
|
- 工作地址/家庭地址最多显示到`区`一级
|
||||||
|
- 银行卡号仅显示最后4位字符,如:`*********************8639`
|
||||||
|
|
||||||
|
##### 1.5.3 【推荐】返回的字段按业务需要输出
|
||||||
|
|
||||||
|
- 按需输出,避免不必要的用户信息泄露
|
||||||
|
- 用户敏感数据应在服务器后台处理后输出,不可以先输出到客户端,再通过客户端代码来处理展示
|
||||||
|
|
||||||
|
关联漏洞:*高风险 - 用户敏感信息泄露*
|
||||||
|
|
||||||
|
#### 1.6 响应输出
|
||||||
|
|
||||||
|
##### 1.6.1 设置正确的HTTP响应包类型
|
||||||
|
|
||||||
|
- 响应头Content-Type与实际响应内容,应保持一致。如:API响应数据类型是json,则响应头使用`application/json`;若为xml,则设置为`text/xml`。
|
||||||
|
|
||||||
|
##### 1.6.2 添加安全响应头
|
||||||
|
|
||||||
|
- 所有接口、页面,添加响应头 `X-Content-Type-Options: nosniff`。
|
||||||
|
- 所有接口、页面,添加响应头`X-Frame-Options`。按需合理设置其允许范围,包括:`DENY`、`SAMEORIGIN`、`ALLOW-FROM origin`。用法参考:[MDN文档](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options)
|
||||||
|
- 推荐使用组件: [helmet](https://www.npmjs.com/package/helmet)
|
||||||
|
|
||||||
|
##### 1.6.3 外部输入拼接到响应页面前,进行编码处理
|
||||||
|
|
||||||
|
| 场景 | 编码规则 |
|
||||||
|
| ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||||
|
| 输出点在HTML标签之间 | 需要对以下6个特殊字符进行HTML实体编码(&, <, >, ", ',/)。<br/>示例:<br/>& --> &amp;<br/>< --> &lt;<br/>>--> &gt;<br/>" --> &quot;<br/>' --> &#x27; <br/>/ --> &#x2F; |
|
||||||
|
| 输出点在HTML标签普通属性内(如href、src、style等,on事件除外) | 要对数据进行HTML属性编码。<br/>编码规则:除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为&#xHH;(以&#x开头,HH则是指该字符对应的十六进制数字,分号作为结束符) |
|
||||||
|
| 输出点在JS内的数据中 | 需要进行js编码<br/>编码规则:<br/>除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \xHH (以 \x 开头,HH则是指该字符对应的十六进制数字)<br/>Tips:这种场景仅限于外部数据拼接在js里被引号括起来的变量值中。除此之外禁止直接将代码拼接在js代码中。 |
|
||||||
|
| 输出点在CSS中(Style属性) | 需要进行CSS编码<br/>编码规则:<br/>除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \HH (以 \ 开头,HH则是指该字符对应的十六进制数字) |
|
||||||
|
| 输出点在URL属性中 | 对这些数据进行URL编码<br/>Tips:除此之外,所有链接类属性应该校验其协议。禁止JavaScript、data和Vb伪协议。 |
|
||||||
|
|
||||||
|
##### 1.6.4 响应禁止展示物理资源、程序内部代码逻辑等敏感信息
|
||||||
|
|
||||||
|
- 业务生产(正式)环境,应用异常时,响应内容禁止展示敏感信息。包括但不限于:`物理路径`、`程序内部源代码`、`调试日志`、`内部账号名`、`内网ip地址`等。
|
||||||
|
|
||||||
|
```text
|
||||||
|
// bad
|
||||||
|
Access denied for user 'xxx'@'xx.xxx.xxx.162' (using password: NO)"
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.6.5 添加安全纵深防御措施
|
||||||
|
|
||||||
|
- 部署CSP,规则中应引入最新的严格模式特性`nonce-`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// good:使用helmet组件安全地配置响应头
|
||||||
|
const express = require("express");
|
||||||
|
const helmet = require("helmet");
|
||||||
|
const app = express();
|
||||||
|
app.use(helmet());
|
||||||
|
|
||||||
|
// good:正确配置Content-Type、添加了安全响应头,引入了CSP
|
||||||
|
Router.get("/", (req, res) => {
|
||||||
|
res.header("Content-Type", "application/json");
|
||||||
|
res.header("X-Content-Type-Options", "nosniff");
|
||||||
|
res.header("X-Frame-Options", "SAMEORIGIN");
|
||||||
|
res.header("Content-Security-Policy", "script-src 'self'");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
关联漏洞:*中风险 - XSS、中风险 - 跳转漏洞*
|
||||||
|
|
||||||
|
#### 1.7 执行代码
|
||||||
|
|
||||||
|
##### 1.7.1 安全的代码执行方式
|
||||||
|
|
||||||
|
- 禁止使用 `eval` 函数
|
||||||
|
- 禁止使用`new Function("input")()` 来创建函数
|
||||||
|
- 使用 `setInteval`,`setTimeout`,应校验传入的参数
|
||||||
|
|
||||||
|
关联漏洞:*高风险 - 代码执行漏洞*
|
||||||
|
|
||||||
|
#### 1.8 Web跨域
|
||||||
|
|
||||||
|
##### 1.8.1 限定JSONP接口的callback字符集范围
|
||||||
|
|
||||||
|
- JSONP接口的callback函数名为固定白名单。如callback函数名可用户自定义,应限制函数名仅包含 字母、数字和下划线。如:`[a-zA-Z0-9_-]+`
|
||||||
|
|
||||||
|
##### 1.8.2 安全的CORS配置
|
||||||
|
|
||||||
|
- 使用CORS,应对请求头Origin值做严格过滤、校验。具体来说,可以使用“全等于”判断,或使用严格的正则进行判断。如:`^https://domain\.qq\.com$`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// good:使用全等于,校验请求的Origin
|
||||||
|
if (req.headers.origin === 'https://domain.qq.com') {
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
|
||||||
|
res.setHeader('Access-Control-Allow-Credentials', true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
关联漏洞:*中风险 - XSS,中风险 - CSRF,中风险 - CORS配置不当*
|
||||||
|
|
||||||
|
#### 1.9 SQL操作
|
||||||
|
|
||||||
|
##### 1.9.1 SQL语句默认使用预编译并绑定变量
|
||||||
|
|
||||||
|
- 应使用预编译绑定变量的形式编写sql语句,保持查询语句和数据相分离
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bad:拼接SQL语句查询,存在安全风险
|
||||||
|
const mysql = require("mysql");
|
||||||
|
const connection = mysql.createConnection(options);
|
||||||
|
connection.connect();
|
||||||
|
|
||||||
|
const sql = util.format("SELECT * from some_table WHERE Id = %s and Name = %s", req.body.id, req.body.name);
|
||||||
|
connection.query(sql, (err, result) => {
|
||||||
|
// handle err..
|
||||||
|
});
|
||||||
|
|
||||||
|
// good:使用预编译绑定变量构造SQL语句
|
||||||
|
const mysql = require("mysql");
|
||||||
|
const connection = mysql.createConnection(options);
|
||||||
|
connection.connect();
|
||||||
|
|
||||||
|
const sql = "SELECT * from some_table WHERE Id = ? and Name = ?";
|
||||||
|
const sqlParams = [req.body.id, req.body.name];
|
||||||
|
connection.query(sql, sqlParams, (err, result) => {
|
||||||
|
// handle err..
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- 对于表名、列名等无法进行预编译的场景,如:`__user_input__` 拼接到比如 `limit`, `order by`, `group by` , `from tablename`语句中。请使用以下方法:
|
||||||
|
|
||||||
|
*方案1:使用白名单校验表名/列名*
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// good
|
||||||
|
const tableSuffix = req.body.type;
|
||||||
|
if (["expected1", "expected2"].indexOf(tableSuffix) < 0) {
|
||||||
|
// 不在表名白名单中,拒绝请求
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
const sql = `SELECT * from t_business_${tableSuffix}`;
|
||||||
|
connection.query(sql, (err, result) => {
|
||||||
|
// handle err..
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
*方案2:使用反引号包裹表名/列名,并过滤 `__user_input__` 中的反引号*
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// good
|
||||||
|
let { orderType } = req.body;
|
||||||
|
// 过滤掉__user_input__中的反引号
|
||||||
|
orderType = orderType.replace("`", "");
|
||||||
|
const sql = util.format("SELECT * from t_business_feeds order by `%s`", orderType);
|
||||||
|
connection.query(sql, (err, result) => {
|
||||||
|
// handle err..
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
*方案3:将 `__user_input__` 转换为整数*
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// good
|
||||||
|
let { orderType } = req.body;
|
||||||
|
// 强制转换为整数
|
||||||
|
orderType = parseInt(orderType, 10);
|
||||||
|
const sql = `SELECT * from t_business_feeds order by ${orderType}`;
|
||||||
|
connection.query(sql, (err, result) => {
|
||||||
|
// handle err..
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.9.2 安全的ORM操作
|
||||||
|
|
||||||
|
- 使用安全的ORM组件进行数据库操作。如 `sequelize` 等
|
||||||
|
|
||||||
|
- 禁止`__user_input__`以拼接的方式直接传入ORM的各类raw方法
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
//bad: adonisjs ORM
|
||||||
|
//参考:https://adonisjs.com/docs/3.2/security-introduction#_sql_injection
|
||||||
|
const username = request.param("username");
|
||||||
|
const users = yield Database
|
||||||
|
.table("users")
|
||||||
|
.where(Database.raw(`username = ${username}`));
|
||||||
|
|
||||||
|
//good: adonisjs ORM
|
||||||
|
const username = request.param("username");
|
||||||
|
const users = yield Database
|
||||||
|
.table('users')
|
||||||
|
.where(Database.raw("username = ?", [username]));
|
||||||
|
```
|
||||||
|
|
||||||
|
- 使用ORM进行Update/Insert操作时,应限制操作字段范围
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/*
|
||||||
|
good
|
||||||
|
假设该api用于插入用户的基本信息,使用传入的req.body通过Sequelize的create方法实现
|
||||||
|
假设User包含字段:username,email,isAdmin,
|
||||||
|
其中,isAdmin将会用于是否系统管理员的鉴权,默认值为false
|
||||||
|
*/
|
||||||
|
// Sequelize: 只允许变更username、email字段值
|
||||||
|
User.create(req.body, { fields: ["username", "email"] }).then((user) => {
|
||||||
|
// handle the rest..
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
> **为什么要这么做?**
|
||||||
|
> 在上述案例中,若不限定fields值,攻击者将可传入`{"username":"boo","email":"foo@boo.com","isAdmin":true}`将自己变为`Admin`,产生垂直越权漏洞。
|
||||||
|
|
||||||
|
关联漏洞:*高风险 - SQL注入,中风险 - Mass Assignment 逻辑漏洞*
|
||||||
|
|
||||||
|
#### 1.10 NoSQL操作
|
||||||
|
|
||||||
|
##### 1.10.1 校验参数值类型
|
||||||
|
|
||||||
|
- 将HTTP参数值代入NoSQL操作前,应校验类型。如非功能需要,禁止对象(Object)类型传入。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bad:执行NOSQL操作前,未作任何判断
|
||||||
|
app.post("/", (req, res) => {
|
||||||
|
db.users.find({ username: req.body.username, password: req.body.password }, (err, users) => {
|
||||||
|
// **TODO:** handle the rest
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// good:在进入nosql前先判断`__USER_INPUT__`是否为字符串。
|
||||||
|
app.post("/", (req, res) => {
|
||||||
|
if (req.body.username && typeof req.body.username !== "string") {
|
||||||
|
return new Error("username must be a string");
|
||||||
|
}
|
||||||
|
if (req.body.password && typeof req.body.password !== "string") {
|
||||||
|
return new Error("password must be a string");
|
||||||
|
}
|
||||||
|
db.users.find({ username: req.body.username, password: req.body.password }, (err, users) => {
|
||||||
|
// **TODO:** handle the rest
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
> **为什么要这么做?**
|
||||||
|
>
|
||||||
|
> JavaScript中,从http或socket接收的数据可能不是单纯的字符串,而是被黑客精心构造的对象(Object)。在本例中:
|
||||||
|
>
|
||||||
|
> - 期望接收的POST数据:`username=foo&password=bar`
|
||||||
|
> - 期望的等价条件查询sql语句:`select * from users where username = 'foo' and password = 'bar'`
|
||||||
|
> - 黑客的精心构造的攻击POST数据:`username[$ne]=null&password[$ne]=null`或JSON格式:`{"username": {"$ne": null},"password": {"$ne": null}}`
|
||||||
|
> - 黑客篡改后的等价条件查询sql语句:`select * from users where username != null & password != null`
|
||||||
|
> - 黑客攻击结果:绕过正常逻辑,在不知道他人的username/password的情况登录他人账号。
|
||||||
|
|
||||||
|
##### 1.10.2 NoSQL操作前,应校验权限/角色
|
||||||
|
|
||||||
|
- 执行NoSQL增、删、改、查逻辑前,应校验权限
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 使用express、mongodb(mongoose)实现的删除文章demo
|
||||||
|
// bad:在删除文章前未做权限校验
|
||||||
|
app.post("/deleteArticle", (req, res) => {
|
||||||
|
db.articles.deleteOne({ article_id: req.body.article_id }, (err, users) => {
|
||||||
|
// TODO: handle the rest
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// good:进入nosql语句前先进行权限校验
|
||||||
|
app.post("/deleteArticle", (req, res) => {
|
||||||
|
checkPriviledge(ctx.uin, req.body.article_id);
|
||||||
|
db.articles.deleteOne({ article_id: req.body.article_id }, (err, users) => {
|
||||||
|
// TODO: handle the rest
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
关联漏洞:*高风险 - 越权操作,高风险 - NoSQL注入*
|
||||||
|
|
||||||
|
#### 1.11 服务器端渲染(SSR)
|
||||||
|
|
||||||
|
##### 1.11.1 安全的Vue服务器端渲染(Vue SSR)
|
||||||
|
|
||||||
|
- 禁止直接将不受信的外部内容传入`{{{ data }}}`表达式中
|
||||||
|
|
||||||
|
- 模板内容禁止被污染
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bad: 将用户输入替换进模板
|
||||||
|
const app = new Vue({
|
||||||
|
template: appTemplate.replace("word", __USER_INPUT__),
|
||||||
|
});
|
||||||
|
renderer.renderToString(app);
|
||||||
|
```
|
||||||
|
|
||||||
|
- 对已渲染的HTML文本内容(renderToString后的html内容)。如需再拼不受信的外部输入,应先进行安全过滤,具体请参考**1.6.3**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bad: 渲染后的html再拼接不受信的外部输入
|
||||||
|
return new Promise(((resolve) => {
|
||||||
|
renderer.renderToString(component, (err, html) => {
|
||||||
|
let htmlOutput = html;
|
||||||
|
htmlOutput += `${__USER_INPUT__}`;
|
||||||
|
resolve(htmlOutput);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.11.2 安全地使用EJS、LoDash、UnderScore进行服务器端渲染
|
||||||
|
|
||||||
|
- 使用render函数时,模板内容禁止被污染
|
||||||
|
|
||||||
|
lodash.Template:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// bad: 将用户输入送进模板
|
||||||
|
const compiled = _.template(`<b>${__USER_INPUT__}<%- value %></b>`);
|
||||||
|
compiled({ value: "hello" });
|
||||||
|
```
|
||||||
|
|
||||||
|
ejs:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bad: 将用户输入送进模板
|
||||||
|
const ejs = require("ejs");
|
||||||
|
const people = ["geddy", "neil", "alex"];
|
||||||
|
const html = ejs.render(`<%= people.join(", "); %>${__USER_INPUT__}`, { people });
|
||||||
|
```
|
||||||
|
|
||||||
|
- Ejs、LoDash、UnderScore提供的HTML插值模板默认形似`<%= data %>`,尽管在默认情况下`<%= data %>`存在过滤,在编写HTML插值模板时需注意:
|
||||||
|
|
||||||
|
1. 用户输入流入html属性值时,必须使用双引号包裹:`<base data-id = "<%= __USER_INPUT__ %>">`
|
||||||
|
2. 用户输入流入`<script></script>`标签或on*的html属性中时,如`<script>var id = <%= __USER_INPUT__ %></script>` ,须按照1.6.3中的做法或白名单方法进行过滤,框架/组件的过滤在此处不起作用
|
||||||
|
|
||||||
|
##### 1.11.3 在自行实现状态存储容器并将其JSON.Stringify序列化后注入到HTML时,必须进行安全过滤
|
||||||
|
|
||||||
|
#### 1.12 URL跳转
|
||||||
|
|
||||||
|
##### 1.12.1 限定跳转目标地址
|
||||||
|
|
||||||
|
- 适用场景包括:
|
||||||
|
|
||||||
|
1. 使用30x返回码并在Header中设置Location进行跳转
|
||||||
|
2. 在返回页面中打印`<script>location.href=__Redirection_URL__</script>`
|
||||||
|
|
||||||
|
- 使用白名单,限定重定向地址的协议前缀(默认只允许HTTP、HTTPS)、域名(默认只允许公司根域),或指定为固定值;
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 使用express实现的登录成功后的回调跳转页面
|
||||||
|
|
||||||
|
// bad: 未校验页面重定向地址
|
||||||
|
app.get("/login", (req, res) => {
|
||||||
|
// 若未登录用户访问其他页面,则让用户导向到该处理函数进行登录
|
||||||
|
// 使用参数loginCallbackUrl记录先前尝试访问的url,在登录成功后跳转回loginCallbackUrl:
|
||||||
|
const { loginCallbackUrl } = req.query;
|
||||||
|
if (loginCallbackUrl) {
|
||||||
|
res.redirect(loginCallbackUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// good: 白名单限定重定向地址
|
||||||
|
function isValidURL(sUrl) {
|
||||||
|
return !!((/^(https?:\/\/)?[\w\-.]+\.(qq|tencent)\.com($|\/|\\)/i).test(sUrl) || (/^[\w][\w/.\-_%]+$/i).test(sUrl) || (/^[/\\][^/\\]/i).test(sUrl));
|
||||||
|
}
|
||||||
|
app.get("/login", (req, res) => {
|
||||||
|
// 若未登录用户访问其他页面,则让用户导向到该处理函数进行登录
|
||||||
|
// 使用参数loginCallbackUrl记录先前尝试访问的url,在登录成功后跳转回loginCallbackUrl:
|
||||||
|
const { loginCallbackUrl } = req.query;
|
||||||
|
if (loginCallbackUrl && isValidUrl(loginCallbackUrl)) {
|
||||||
|
res.redirect(loginCallbackUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// good: 白名单限定重定向地址,通过返回html实现
|
||||||
|
function isValidURL(sUrl) {
|
||||||
|
return !!((/^(https?:\/\/)?[\w\-.]+\.(qq|tencent)\.com($|\/|\\)/i).test(sUrl) || (/^[\w][\w/.\-_%]+$/i).test(sUrl) || (/^[/\\][^/\\]/i).test(sUrl));
|
||||||
|
}
|
||||||
|
app.get("/login", (req, res) => {
|
||||||
|
// 若未登录用户访问其他页面,则让用户导向到该处理函数进行登录
|
||||||
|
// 使用参数loginCallbackUrl记录先前尝试访问的url,在登录成功后跳转回loginCallbackUrl:
|
||||||
|
const { loginCallbackUrl } = req.query;
|
||||||
|
if (loginCallbackUrl && isValidUrl(loginCallbackUrl)) {
|
||||||
|
// 使用encodeURI,过滤左右尖括号与双引号,防止逃逸出包裹的双引号
|
||||||
|
const redirectHtml = `<script>location.href = "${encodeURI(loginCallbackUrl)}";</script>`;
|
||||||
|
res.end(redirectHtml);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
*关联漏洞:中风险 - 任意URL跳转漏洞*
|
||||||
|
|
||||||
|
#### 1.13 Cookie与登录态
|
||||||
|
|
||||||
|
##### 1.13.1【推荐】为Cookies中存储的关键登录态信息添加http-only保护
|
||||||
|
|
||||||
|
关联漏洞:*纵深防护措施 - 安全性增强特性*
|
||||||
|
|
||||||
|
### 2. 配置&环境
|
||||||
|
|
||||||
|
#### 2.1 依赖库
|
||||||
|
|
||||||
|
##### 2.1.1 使用安全的依赖库
|
||||||
|
|
||||||
|
- 使用自动工具,检查依赖库是否存在后门/漏洞,保持最新版本
|
||||||
|
|
||||||
|
#### 2.2 运行环境
|
||||||
|
|
||||||
|
##### 2.2.1 使用非root用户运行Node.js
|
||||||
|
|
||||||
|
#### 2.3 配置信息
|
||||||
|
|
||||||
|
##### 2.3.1 禁止硬编码认证凭证
|
||||||
|
|
||||||
|
- 禁止在源码中硬编码`AK/SK`、`数据库账密`、`私钥证书`等配置信息
|
||||||
|
- 应使用配置系统或KMS密钥管理系统。
|
||||||
|
|
||||||
|
##### 2.3.2 禁止硬编码IP配置
|
||||||
|
|
||||||
|
- 禁止在源码中硬编码`IP`信息
|
||||||
|
|
||||||
|
> **为什么要这么做?**
|
||||||
|
>
|
||||||
|
> 硬编码IP可能会导致后续机器裁撤或变更时产生额外的工作量,影响系统的可靠性。
|
||||||
|
|
||||||
|
##### 2.3.3 禁止硬编码员工敏感信息
|
||||||
|
|
||||||
|
- 禁止在源代码中含员工敏感信息,包括但不限于:`员工ID`、`手机号`、`微信/QQ号`等。
|
709
docs/标准/腾讯代码安全指南/Java安全指南.md
Normal file
709
docs/标准/腾讯代码安全指南/Java安全指南.md
Normal file
@ -0,0 +1,709 @@
|
|||||||
|
---
|
||||||
|
id: Java 安全指南
|
||||||
|
title: Java 安全指南
|
||||||
|
sidebar_position: 5
|
||||||
|
data: 2022年5月30日
|
||||||
|
---
|
||||||
|
## 安卓类
|
||||||
|
|
||||||
|
### 1. 代码实现
|
||||||
|
|
||||||
|
#### 1.1 异常捕获处理
|
||||||
|
|
||||||
|
##### 1.1.1 序列化异常捕获
|
||||||
|
|
||||||
|
对于通过导出组件 intent 传递的序列化对象,必须进行 try...catch 处理,以避免数据非法导致应用崩溃。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
try {
|
||||||
|
Intent mIntent = getIntent();
|
||||||
|
//String msg = intent.getStringExtra("data");
|
||||||
|
Person mPerson = (Person)mIntent.getSerializableExtra(ObjectDemo.SER_KEY)
|
||||||
|
//textView.setText(msg);
|
||||||
|
} catch (ClassNotFoundException exp) {
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.1.2 NullPointerException 异常捕获
|
||||||
|
|
||||||
|
对于通过 intent getAction 方法获取数据时,必须进行 try...catch 处理,以避免空指针异常导致应用崩溃。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
try {
|
||||||
|
Intent mIntent = getIntent();
|
||||||
|
if mIntent.getAction().equals("StartNewWorld") {
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
// ......
|
||||||
|
} catch (NullPointerException exp) {
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.1.3 ClassCastException 异常捕获
|
||||||
|
|
||||||
|
对于通过 intent getSerializableExtra 方法获取数据时,必须进行 try...catch 处理,以避免类型转换异常导致应用崩溃。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
try {
|
||||||
|
Intent mIntent = getIntent();
|
||||||
|
Person mPerson = (Person)mIntent.getSerializableExtra(ObjectDemo.SER_KEY)
|
||||||
|
// ......
|
||||||
|
} catch (ClassCastException exp) {
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.1.4 ClassNotFoundException 异常捕获
|
||||||
|
|
||||||
|
同 1.1.3
|
||||||
|
|
||||||
|
#### 1.2 数据泄露
|
||||||
|
|
||||||
|
##### 1.2.1 logcat 输出限制
|
||||||
|
|
||||||
|
release 版本禁止在 logcat 输出信息。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
String DEBUG = "debug_version";
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// ......
|
||||||
|
if (DEBUG == "debug_version") {
|
||||||
|
Log.d("writelog", "start activity");
|
||||||
|
}
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.3 webview 组件安全
|
||||||
|
|
||||||
|
##### 1.3.1 addJavaScriptInterface 方法调用
|
||||||
|
|
||||||
|
对于设置 minsdk <= 18 的应用,禁止调用 addJavaScriptInterface 方法。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// ......
|
||||||
|
mWebView = new WebView(this);
|
||||||
|
if (Build.VERSION.SDK_INT > 18) {
|
||||||
|
mWebView.addJavascriptInterface(new wPayActivity.InJavaScriptLocalObj(this), "local_obj");
|
||||||
|
}
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.3.2 setJavaScriptEnabled 方法调用
|
||||||
|
|
||||||
|
如非必要,setJavaScriptEnabled 应设置为 false 。加载本地 html ,应校验 html 页面完整性,以避免 xss 攻击。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// ......
|
||||||
|
mWebView = new WebView(this);
|
||||||
|
mWebView.getSettings().setJavaScriptEnabled(false);
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.3.3 setAllowFileAccess 方法调用
|
||||||
|
|
||||||
|
建议禁止使用 File 域协议,以避免过滤不当导致敏感信息泄露。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// ......
|
||||||
|
mWebView = new WebView(this);
|
||||||
|
mWebView.getSettings().setAllowFileAccess(false);
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.3.4 setSavePassword 方法调用
|
||||||
|
|
||||||
|
建议 setSavePassword 的设置为 false ,避免明文保存网站密码。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// ......
|
||||||
|
mWebView = new WebView(this);
|
||||||
|
mWebView.getSettings().setSavePassword(false);
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.3.5 onReceivedSslError 方法调用
|
||||||
|
|
||||||
|
webview 组件加载网页发生证书认证错误时,不能直接调用 handler.proceed() 忽略错误,应当处理当前场景是否符合业务预期,以避免中间人攻击劫持。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// ......
|
||||||
|
mWebView = new WebView(this);
|
||||||
|
mWebView.setWebViewClient(new WebViewClient() {
|
||||||
|
@Override
|
||||||
|
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||||
|
// must check error
|
||||||
|
check_error();
|
||||||
|
handler.proceed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.4 传输安全
|
||||||
|
|
||||||
|
##### 1.4.1 自定义 HostnameVerifier 类
|
||||||
|
|
||||||
|
自定义 HostnameVerifier 类后,必须实现 verify 方法校验域名,以避免中间人攻击劫持。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// ......
|
||||||
|
HostnameVerifier hnv = new HostnameVerifier() {
|
||||||
|
@Override
|
||||||
|
public boolean verify(String hostname, SSLSession session) {
|
||||||
|
// must to do
|
||||||
|
isValid = checkHostName(hostname);
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.4.2 自定义 X509TrustManager 类
|
||||||
|
|
||||||
|
自定义 X509TrustManager 类后,必须实现 checkServerTrusted 方法校验服务器证书,以避免中间人攻击劫持。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// ......
|
||||||
|
TrustManager tm = new X509TrustManager() {
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||||
|
throws CertificateException {
|
||||||
|
// must to do
|
||||||
|
check_server_valid();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.4.3 setHostnameVerifier 方法调用
|
||||||
|
|
||||||
|
禁止调用 setHostnameVerifier 方法设置 ALLOW_ALL_HOSTNAME_VERIFIER 属性,以避免中间人攻击劫持。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// ......
|
||||||
|
SchemeRegistry schemeregistry = new SchemeRegistry();
|
||||||
|
SSLSocketFactory sslsocketfactory = SSLSocketFactory.getSocketFactory();
|
||||||
|
// set STRICT_HOSTNAME_VERIFIER
|
||||||
|
sslsocketfactory.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
|
||||||
|
// ......
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置&环境
|
||||||
|
|
||||||
|
#### 2.1 AndroidManifest.xml 配置
|
||||||
|
|
||||||
|
##### 2.1.1 PermissionGroup 属性设置
|
||||||
|
|
||||||
|
禁止设置 PermissionGroup 属性为空。
|
||||||
|
|
||||||
|
##### 2.1.2 protectionLevel 属性设置
|
||||||
|
|
||||||
|
对于自定义权限的 protectionLevel 属性设置,建议设置为 signature 或 signatureOrSystem。
|
||||||
|
|
||||||
|
##### 2.1.3 【建议】sharedUserId 权限设置
|
||||||
|
|
||||||
|
最小范围和最小权限使用 sharedUserId 设置。
|
||||||
|
|
||||||
|
##### 2.1.4 【建议】allowBackup 备份设置
|
||||||
|
|
||||||
|
如非产品功能需要,建议设置 allowBackup 为 false。
|
||||||
|
|
||||||
|
```java
|
||||||
|
<application android:allowBackup="false">
|
||||||
|
</application>
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 2.1.5 debuggable 调试设置
|
||||||
|
|
||||||
|
release 版本禁止设置 debuggable 为 true。
|
||||||
|
|
||||||
|
```java
|
||||||
|
<application android:debuggable="false">
|
||||||
|
</application>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 后台类
|
||||||
|
|
||||||
|
### 1. 代码实现
|
||||||
|
|
||||||
|
#### 1.1 数据持久化
|
||||||
|
|
||||||
|
##### 1.1.1 SQL语句默认使用预编译并绑定变量
|
||||||
|
|
||||||
|
Web后台系统应默认使用预编译绑定变量的形式创建sql语句,保持查询语句和数据相分离。以从本质上避免SQL注入风险。
|
||||||
|
|
||||||
|
如使用Mybatis作为持久层框架,应通过\#{}语法进行参数绑定,MyBatis 会创建 `PreparedStatement` 参数占位符,并通过占位符安全地设置参数。
|
||||||
|
|
||||||
|
示例:JDBC
|
||||||
|
|
||||||
|
```java
|
||||||
|
String custname = request.getParameter("name");
|
||||||
|
String query = "SELECT * FROM user_data WHERE user_name = ? ";
|
||||||
|
PreparedStatement pstmt = connection.prepareStatement( query );
|
||||||
|
pstmt.setString( 1, custname);
|
||||||
|
ResultSet results = pstmt.executeQuery( );
|
||||||
|
```
|
||||||
|
|
||||||
|
Mybatis
|
||||||
|
|
||||||
|
```java
|
||||||
|
<select id="queryRuleIdByApplicationId" parameterType="java.lang.String" resultType="java.lang.String">
|
||||||
|
select rule_id from scan_rule_sqlmap_tab where application_id=#{applicationId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
应避免外部输入未经过滤直接拼接到SQL语句中,或者通过Mybatis中的${}传入SQL语句(即使使用PreparedStatement,SQL语句直接拼接外部输入也同样有风险。例如Mybatis中部分参数通过${}传入SQL语句后实际执行时调用的是PreparedStatement.execute(),同样存在注入风险)。
|
||||||
|
|
||||||
|
##### 1.1.2 白名单过滤
|
||||||
|
|
||||||
|
对于表名、列名等无法进行预编译的场景,比如外部数据拼接到order by, group by语句中,需通过白名单的形式对数据进行校验,例如判断传入列名是否存在、升降序仅允许输入“ASC”和“DESC”、表名列名仅允许输入字符、数字、下划线等。参考示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public String someMethod(boolean sortOrder) {
|
||||||
|
String SQLquery = "some SQL ... order by Salary " + (sortOrder ? "ASC" : "DESC");`
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.2 文件操作
|
||||||
|
|
||||||
|
##### 1.2.1 文件类型限制
|
||||||
|
|
||||||
|
须在服务器端采用白名单方式对上传或下载的文件类型、大小进行严格的限制。仅允许业务所需文件类型上传,避免上传.jsp、.jspx、.class、.java等可执行文件。参考示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String file_name = file.getOriginalFilename();
|
||||||
|
String[] parts = file_name.split("\\.");
|
||||||
|
String suffix = parts[parts.length - 1];
|
||||||
|
switch (suffix){
|
||||||
|
case "jpeg":
|
||||||
|
suffix = ".jpeg";
|
||||||
|
break;
|
||||||
|
case "jpg":
|
||||||
|
suffix = ".jpg";
|
||||||
|
break;
|
||||||
|
case "bmp":
|
||||||
|
suffix = ".bmp";
|
||||||
|
break;
|
||||||
|
case "png":
|
||||||
|
suffix = ".png";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//handle error
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.2.2 禁止外部文件存储于可执行目录
|
||||||
|
|
||||||
|
禁止外部文件存储于WEB容器的可执行目录(appBase)。建议保存在专门的文件服务器中。
|
||||||
|
|
||||||
|
##### 1.2.3【建议】避免路径拼接
|
||||||
|
|
||||||
|
文件目录避免外部参数拼接。保存文件目录建议后台写死并对文件名进行校验(字符类型、长度)。建议文件保存时,将文件名替换为随机字符串。
|
||||||
|
|
||||||
|
##### 1.2.4 避免路径穿越
|
||||||
|
|
||||||
|
如因业务需要不能满足1.2.3的要求,文件路径、文件命中拼接了不可行数据,需判断请求文件名和文件路径参数中是否存在../或..\\(仅windows), 如存在应判定路径非法并拒绝请求。
|
||||||
|
|
||||||
|
#### 1.3 网络访问
|
||||||
|
|
||||||
|
##### 1.3.1 避免直接访问不可信地址
|
||||||
|
|
||||||
|
服务器访问不可信地址时,禁止访问私有地址段及内网域名。
|
||||||
|
|
||||||
|
```text
|
||||||
|
// 以RFC定义的专有网络为例,如有自定义私有网段亦应加入禁止访问列表。
|
||||||
|
10.0.0.0/8
|
||||||
|
172.16.0.0/12
|
||||||
|
192.168.0.0/16
|
||||||
|
127.0.0.0/8
|
||||||
|
```
|
||||||
|
|
||||||
|
建议通过URL解析函数进行解析,获取host或者domain后通过DNS获取其IP,然后和内网地址进行比较。
|
||||||
|
|
||||||
|
对已校验通过地址进行访问时,应关闭跟进跳转功能。
|
||||||
|
|
||||||
|
参考示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
httpConnection = (HttpURLConnection) Url.openConnection();
|
||||||
|
|
||||||
|
httpConnection.setFollowRedirects(false);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.4 XML读写
|
||||||
|
|
||||||
|
##### 1.4.1 XML解析器关闭DTD解析
|
||||||
|
|
||||||
|
读取外部传入XML文件时,XML解析器初始化过程中设置关闭DTD解析。
|
||||||
|
|
||||||
|
参考示例:
|
||||||
|
|
||||||
|
javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
|
||||||
|
```java
|
||||||
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
try {
|
||||||
|
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||||
|
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||||
|
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||||
|
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||||
|
dbf.setXIncludeAware(false);
|
||||||
|
dbf.setExpandEntityReferences(false);
|
||||||
|
……
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
org.dom4j.io.SAXReader
|
||||||
|
|
||||||
|
```java
|
||||||
|
saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||||
|
saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||||
|
saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||||
|
```
|
||||||
|
|
||||||
|
org.jdom2.input.SAXBuilder
|
||||||
|
|
||||||
|
```java
|
||||||
|
SAXBuilder builder = new SAXBuilder();
|
||||||
|
builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
|
||||||
|
builder.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||||
|
builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||||
|
Document doc = builder.build(new File(fileName));
|
||||||
|
```
|
||||||
|
|
||||||
|
org.xml.sax.XMLReader
|
||||||
|
|
||||||
|
```java
|
||||||
|
XMLReader reader = XMLReaderFactory.createXMLReader();
|
||||||
|
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||||
|
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||||
|
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||||
|
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.5 响应输出
|
||||||
|
|
||||||
|
##### 1.5.1 设置正确的HTTP响应包类型
|
||||||
|
|
||||||
|
响应包的HTTP头“Content-Type”必须正确配置响应包的类型,禁止非HTML类型的响应包设置为“text/html”。此举会使浏览器在直接访问链接时,将非HTML格式的返回报文当做HTML解析,增加反射型XSS的触发几率。
|
||||||
|
|
||||||
|
##### 1.5.2【建议】设置安全的HTTP响应头
|
||||||
|
|
||||||
|
- X-Content-Type-Options:
|
||||||
|
|
||||||
|
建议添加“X-Content-Type-Options”响应头并将其值设置为“nosniff”,可避免部分浏览器根据其“Content-Sniff”特性,将一些非“text/html”类型的响应作为HTML解析,增加反射型XSS的触发几率。
|
||||||
|
|
||||||
|
- HttpOnly:
|
||||||
|
|
||||||
|
控制用户登录鉴权的Cookie字段 应当设置HttpOnly属性以防止被XSS漏洞/JavaScript操纵泄漏。
|
||||||
|
|
||||||
|
- X-Frame-Options:
|
||||||
|
|
||||||
|
设置X-Frame-Options响应头,并根据需求合理设置其允许范围。该头用于指示浏览器禁止当前页面在frame、iframe、embed等标签中展现。从而避免点击劫持问题。它有三个可选的值:
|
||||||
|
DENY: 浏览器会拒绝当前页面加载任何frame页面;
|
||||||
|
SAMEORIGIN:则frame页面的地址只能为同源域名下的页面
|
||||||
|
ALLOW-FROM origin:可以定义允许frame加载的页面地址。
|
||||||
|
|
||||||
|
- Access-Control-Allow-Origin
|
||||||
|
|
||||||
|
当需要配置CORS跨域时,应对请求头的Origin值做严格过滤。
|
||||||
|
|
||||||
|
```java
|
||||||
|
...
|
||||||
|
String currentOrigin = request.getHeader("Origin");
|
||||||
|
if (currentOrigin.equals("https://domain.qq.com")) {
|
||||||
|
response.setHeader("Access-Control-Allow-Origin", currentOrigin);
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.5.3 外部输入拼接到response页面前进行编码处理
|
||||||
|
|
||||||
|
当响应“content-type”为“html”类型时,外部输入拼接到响应包中,需根据输出位置进行编码处理。编码规则:
|
||||||
|
|
||||||
|
| 场景 | 编码规则 |
|
||||||
|
| ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||||
|
| 输出点在HTML标签之间 | 需要对以下6个特殊字符进行HTML实体编码(&, <, >, ", ',/)。<br/>示例:<br/>& --> &amp;<br/>< --> &lt;<br/>>--> &gt;<br/>" --> &quot;<br/>' --> &#x27; <br/>/ --> &#x2F; |
|
||||||
|
| 输出点在HTML标签普通属性内(如href、src、style等,on事件除外) | 要对数据进行HTML属性编码。<br/>编码规则:除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为&#xHH;(以&#x开头,HH则是指该字符对应的十六进制数字,分号作为结束符) |
|
||||||
|
| 输出点在JS内的数据中 | 需要进行js编码<br/>编码规则:<br/>除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \xHH (以 \x 开头,HH则是指该字符对应的十六进制数字)<br/>Tips:这种场景仅限于外部数据拼接在js里被引号括起来的变量值中。除此之外禁止直接将代码拼接在js代码中。 |
|
||||||
|
| 输出点在CSS中(Style属性) | 需要进行CSS编码<br/>编码规则:<br/>除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \HH (以 \ 开头,HH则是指该字符对应的十六进制数字) |
|
||||||
|
| 输出点在URL属性中 | 对这些数据进行URL编码<br/>Tips:除此之外,所有链接类属性应该校验其协议。禁止JavaScript、data和Vb伪协议。 |
|
||||||
|
|
||||||
|
以上编码规则相对较为繁琐,可参考或直接使用业界已有成熟第三方库如ESAPI.其提供以下函数对象上表中的编码规则:
|
||||||
|
|
||||||
|
```java
|
||||||
|
ESAPI.encoder().encodeForHTML();
|
||||||
|
ESAPI.encoder().encodeForHTMLAttribute();
|
||||||
|
ESAPI.encoder().encodeForJavaScript();
|
||||||
|
ESAPI.encoder().encodeForCSS();
|
||||||
|
ESAPI.encoder().encodeForURL();
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.5.4 外部输入拼接到HTTP响应头中需进行过滤
|
||||||
|
|
||||||
|
应尽量避免外部可控参数拼接到HTTP响应头中,如业务需要则需要过滤掉“\r”、"\n"等换行符,或者拒绝携带换行符号的外部输入。
|
||||||
|
|
||||||
|
##### 1.5.5 避免不可信域名的302跳转
|
||||||
|
|
||||||
|
如果对外部传入域名进行302跳转,必须设置可信域名列表并对传入域名进行校验。
|
||||||
|
|
||||||
|
为避免校验被绕过,应避免直接对URL进行字符串匹配。应通过通过URL解析函数进行解析,获取host或者domain后和白名单进行比较。
|
||||||
|
|
||||||
|
需要注意的是,由于浏览器的容错机制,域名`https://www.qq.com\www.bbb.com`中的`\`会被替换成`/`,最终跳转到`www.qq.com`。而Java的域名解析函数则无此特性。为避免解析不一致导致绕过,建议对host中的`/`和`#`进行替换。
|
||||||
|
|
||||||
|
参考代码:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String host="";
|
||||||
|
try {
|
||||||
|
url = url.replaceAll("[\\\\#]","/"); //替换掉反斜线和井号
|
||||||
|
host = new URL(url).getHost();
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (host.endsWith(".qq.com")){
|
||||||
|
//跳转操作
|
||||||
|
}else{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 1.5.6 避免通过Jsonp传输非公开敏感信息
|
||||||
|
|
||||||
|
jsonp请求再被CSRF攻击时,其响应包可被攻击方劫持导致信息泄露。应避免通过jsonp传输非公开的敏感信息,例如用户隐私信息、身份凭证等。
|
||||||
|
|
||||||
|
##### 1.5.7 限定JSONP接口的callback字符集范围
|
||||||
|
|
||||||
|
JSONP接口的callback函数名为固定白名单。如callback函数名可用户自定义,应限制函数名仅包含 字母、数字和下划线。如:`[a-zA-Z0-9_-]+`
|
||||||
|
|
||||||
|
##### 1.5.8 屏蔽异常栈
|
||||||
|
|
||||||
|
应用程序出现异常时,禁止将数据库版本、数据库结构、操作系统版本、堆栈跟踪、文件名和路径信息、SQL 查询字符串等对攻击者有用的信息返回给客户端。建议重定向到一个统一、默认的错误提示页面,进行信息过滤。
|
||||||
|
|
||||||
|
##### 1.5.9 模板&表达式
|
||||||
|
|
||||||
|
web view层通常通过模板技术或者表达式引擎来实现界面与业务数据分离,比如jsp中的EL表达式。这些引擎通常可执行敏感操作,如果外部不可信数据未经过滤拼接到表达式中进行解析。则可能造成严重漏洞。
|
||||||
|
|
||||||
|
下列是基于EL表达式注入漏洞的演示demo:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RequestMapping("/ELdemo")
|
||||||
|
@ResponseBody
|
||||||
|
public String ELdemo(RepeatDTO repeat) {
|
||||||
|
ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
|
||||||
|
SimpleContext simpleContext = new SimpleContext();
|
||||||
|
String exp = "${"+repeat.getel()+"}";
|
||||||
|
ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp, String.class);
|
||||||
|
return valueExpression.getValue(simpleContext).toString();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
外部可通过el参数,将不可信输入拼接到EL表达式中并解析。
|
||||||
|
|
||||||
|
此时外部访问:x.x.x.x/ELdemo?el=”''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'open /Applications/Calculator.app')“ 可执行操作系统命令调出计算器。
|
||||||
|
|
||||||
|
基于以上风险:
|
||||||
|
|
||||||
|
- 应避免外部输入的内容拼接到EL表达式或其他表达式引起、模板引擎进行解析。
|
||||||
|
- 白名单过滤外部输入,仅允许字符、数字、下划线等。
|
||||||
|
|
||||||
|
#### 1.6 OS命令执行
|
||||||
|
|
||||||
|
##### 1.6.1【建议】避免不可信数据拼接操作系统命令
|
||||||
|
|
||||||
|
当不可信数据存在时,应尽量避免外部数据拼接到操作系统命令使用 `Runtime` 和 `ProcessBuilder` 来执行。优先使用其他同类操作进行代替,比如通过文件系统API进行文件操作而非直接调用操作系统命令。
|
||||||
|
|
||||||
|
##### 1.6.2 避免创建SHELL操作
|
||||||
|
|
||||||
|
如无法避免直接访问操作系统命令,需要严格管理外部传入参数,使不可信数据仅作为执行命令的参数而非命令。
|
||||||
|
|
||||||
|
- 禁止外部数据直接直接作为操作系统命令执行。
|
||||||
|
|
||||||
|
- 避免通过"cmd"、“bash”、“sh”等命令创建shell后拼接外部数据来执行操作系统命令。
|
||||||
|
|
||||||
|
- 对外部传入数据进行过滤。可通过白名单限制字符类型,仅允许字符、数字、下划线;或过滤转义以下符号:|;&$><`(反引号)\!
|
||||||
|
|
||||||
|
白名单示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private static final Pattern FILTER_PATTERN = Pattern.compile("[0-9A-Za-z_]+");
|
||||||
|
if (!FILTER_PATTERN.matcher(input).matches()) {
|
||||||
|
// 终止当前请求的处理
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.7 会话管理
|
||||||
|
|
||||||
|
##### 1.7.1 非一次有效身份凭证禁止在URL中传输
|
||||||
|
|
||||||
|
身份凭证禁止在URL中传输,一次有效的身份凭证除外(如CAS中的st)。
|
||||||
|
|
||||||
|
##### 1.7.2 避免未经校验的数据直接给会话赋值
|
||||||
|
|
||||||
|
防止会话信息被篡改,如恶意用户通过URL篡改手机号码等。
|
||||||
|
|
||||||
|
#### 1.8 加解密
|
||||||
|
|
||||||
|
##### 1.8.1【建议】对称加密
|
||||||
|
|
||||||
|
建议使用AES,秘钥长度128位以上。禁止使用DES算法,由于秘钥太短,其为目前已知不安全加密算法。使用AES加密算法请参考以下注意事项:
|
||||||
|
|
||||||
|
- AES算法如果采用CBC模式:每次加密时IV必须采用密码学安全的伪随机发生器(如/dev/urandom),禁止填充全0等固定值。
|
||||||
|
- AES算法如采用GCM模式,nonce须采用密码学安全的伪随机数
|
||||||
|
- AES算法避免使用ECB模式,推荐使用GCM模式。
|
||||||
|
|
||||||
|
##### 1.8.2【建议】非对称加密
|
||||||
|
|
||||||
|
建议使用RSA算法,秘钥2048及以上。
|
||||||
|
|
||||||
|
##### 1.8.3【建议】哈希算法
|
||||||
|
|
||||||
|
哈希算法推荐使用SHA-2及以上。对于签名场景,应使用HMAC算法。如果采用字符串拼接盐值后哈希的方式,禁止将盐值置于字符串开头,以避免哈希长度拓展攻击。
|
||||||
|
|
||||||
|
##### 1.8.4【建议】密码存储策略
|
||||||
|
|
||||||
|
建议采用随机盐+明文密码进行多轮哈希后存储密码。
|
||||||
|
|
||||||
|
#### 1.9 查询业务
|
||||||
|
|
||||||
|
##### 1.9.1 返回信息最小化
|
||||||
|
|
||||||
|
返回用户信息应遵循最小化原则,避免将业务需求之外的用户信息返回到前端。
|
||||||
|
|
||||||
|
##### 1.9.2 个人敏感信息脱敏展示
|
||||||
|
|
||||||
|
在满足业务需求的情况下,个人敏感信息需脱敏展示,如:
|
||||||
|
|
||||||
|
- 鉴权信息(如口令、密保答案、生理标识等)不允许展示
|
||||||
|
- 身份证只显示第一位和最后一位字符,如3****************1。
|
||||||
|
- 移动电话号码隐藏中间6位字符,如134******48。
|
||||||
|
- 工作地址/家庭地址最多显示到“区”一级。
|
||||||
|
- 银行卡号仅显示最后4位字符,如************8639
|
||||||
|
|
||||||
|
##### 1.9.3 数据权限校验
|
||||||
|
|
||||||
|
查询个人非公开信息时,需要对当前访问账号进行数据权限校验。
|
||||||
|
|
||||||
|
1. 验证当前用户的登录态
|
||||||
|
2. 从可信结构中获取经过校验的当前请求账号的身份信息(如:session)。禁止从用户请求参数或Cookie中获取外部传入不可信用户身份直接进行查询。
|
||||||
|
3. 验当前用户是否具备访问数据的权限
|
||||||
|
|
||||||
|
#### 1.10 操作业务
|
||||||
|
|
||||||
|
##### 1.10.1 部署CSRF防御机制
|
||||||
|
|
||||||
|
CSRF是指跨站请求伪造(Cross-site request forgery),是web常见的攻击之一。对于可重放的敏感操作请求,需部署CSRF防御机制。可参考以下两种常见的CSRF防御方式
|
||||||
|
|
||||||
|
- 设置CSRF Token
|
||||||
|
|
||||||
|
服务端给合法的客户颁发CSRF Token,客户端在发送请求时携带该token供服务端校验,服务端拒绝token验证不通过的请求。以此来防止第三方构造合法的恶意操作链接。Token的作用域可以是Request级或者Session级。下面以Session级CSRF Token进行示例
|
||||||
|
|
||||||
|
1. 登录成功后颁发Token,并同时存储在服务端Session中
|
||||||
|
|
||||||
|
```java
|
||||||
|
String uuidToken = UUID.randomUUID().toString();
|
||||||
|
map.put("token", uuidToken);
|
||||||
|
request.getSession().setAttribute("token",uuidToken );
|
||||||
|
return map;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 创建Filter
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class CsrfFilter implements Filter {
|
||||||
|
...
|
||||||
|
HttpSession session = req.getSession();
|
||||||
|
Object token = session.getAttribute("token");
|
||||||
|
String requestToken = req.getParameter("token");
|
||||||
|
if(StringUtils.isBlank(requestToken) || !requestToken.equals(token)){
|
||||||
|
AjaxResponseWriter.write(req, resp, ServiceStatusEnum.ILLEGAL_TOKEN, "非法的token");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
CSRF Token应具备随机性,保证其不可预测和枚举。另外由于浏览器会自动对表单所访问的域名添加相应的cookie信息,所以CSRF Token不应该通过Cookie传输。
|
||||||
|
|
||||||
|
- 校验Referer头
|
||||||
|
|
||||||
|
通过检查HTTP请求的Referer字段是否属于本站域名,非本站域名的请求进行拒绝。
|
||||||
|
|
||||||
|
这种校验方式需要注意两点:
|
||||||
|
|
||||||
|
1. 要需要处理Referer为空的情况,当Referer为空则拒绝请求
|
||||||
|
2. 注意避免例如qq.com.evil.com 部分匹配的情况。
|
||||||
|
|
||||||
|
##### 1.10.2 权限校验
|
||||||
|
|
||||||
|
对于非公共操作,应当校验当前访问账号进行操作权限(常见于CMS)和数据权限校验。
|
||||||
|
|
||||||
|
1. 验证当前用户的登录态
|
||||||
|
2. 从可信结构中获取经过校验的当前请求账号的身份信息(如:session)。禁止从用户请求参数或Cookie中获取外部传入不可信用户身份直接进行查询。
|
||||||
|
3. 校验当前用户是否具备该操作权限
|
||||||
|
4. 校验当前用户是否具备所操作数据的权限。避免越权。
|
||||||
|
|
||||||
|
##### 1.10.3【建议】加锁操作
|
||||||
|
|
||||||
|
对于有次数限制的操作,比如抽奖。如果操作的过程中资源访问未正确加锁。在高并发的情况下可能造成条件竞争,导致实际操作成功次数多于用户实际操作资格次数。此类操作应加锁处理。
|
457
docs/标准/腾讯代码安全指南/Python安全指南.md
Normal file
457
docs/标准/腾讯代码安全指南/Python安全指南.md
Normal file
@ -0,0 +1,457 @@
|
|||||||
|
---
|
||||||
|
id: Python 安全指南
|
||||||
|
title: Python 安全指南
|
||||||
|
sidebar_position: 5
|
||||||
|
data: 2022年5月30日
|
||||||
|
---
|
||||||
|
|
||||||
|
# 通用类
|
||||||
|
|
||||||
|
## 1. 代码实现
|
||||||
|
|
||||||
|
### 1.1 加密算法
|
||||||
|
|
||||||
|
#### 1.1.1 避免使用不安全的对称加密算法
|
||||||
|
|
||||||
|
- DES和3DES已经不再适用于现代应用程序,应改为使用AES。
|
||||||
|
|
||||||
|
### 1.2 程序日志
|
||||||
|
|
||||||
|
#### 1.2.1 【建议】对每个重要行为都记录日志
|
||||||
|
|
||||||
|
- 确保重要行为都记录日志,且可靠保存6个月以上。
|
||||||
|
|
||||||
|
#### 1.2.2 【建议】禁止将未经验证的用户输入直接记录日志
|
||||||
|
|
||||||
|
- 当日志条目包含未经净化的用户输入时会引发记录注入漏洞。恶意用户会插入伪造的日志数据,从而让系统管理员以为是系统行为。
|
||||||
|
|
||||||
|
#### 1.2.3 【建议】避免在日志中保存敏感信息
|
||||||
|
|
||||||
|
- 不能在日志保存密码(包括明文密码和密文密码)、密钥和其它敏感信息
|
||||||
|
|
||||||
|
### 1.3 系统口令
|
||||||
|
|
||||||
|
#### 1.3.1 禁止使用空口令、弱口令、已泄露口令
|
||||||
|
|
||||||
|
#### 1.3.2 口令强度要求
|
||||||
|
|
||||||
|
> 口令强度须同时满足:
|
||||||
|
>
|
||||||
|
> 1. 密码长度大于14位
|
||||||
|
> 2. 必须包含下列元素:大小写英文字母、数字、特殊字符
|
||||||
|
> 3. 不得使用各系统、程序的默认初始密码
|
||||||
|
> 4. 不能与最近6次使用过的密码重复
|
||||||
|
> 5. 不得与其他外部系统使用相同的密码
|
||||||
|
|
||||||
|
#### 1.3.3 【必须】口令存储安全
|
||||||
|
|
||||||
|
- 禁止明文存储口令
|
||||||
|
- 禁止使用弱密码学算法(如DES和3DES)加密存储口令
|
||||||
|
- 使用不可逆算法和随机salt对口令进行加密存储
|
||||||
|
|
||||||
|
#### 1.3.4 禁止传递明文口令
|
||||||
|
|
||||||
|
#### 1.3.5 禁止在不安全的信道中传输口令
|
||||||
|
|
||||||
|
## 2. 配置&环境
|
||||||
|
|
||||||
|
### 2.1 Python版本选择
|
||||||
|
|
||||||
|
#### 2.1.1 【建议】使用Python 3.6+的版本
|
||||||
|
|
||||||
|
- 新增的项目应使用 Python 3.6+
|
||||||
|
|
||||||
|
> **为什么要这么做?**
|
||||||
|
> 由于 Python 2 在 [2020 年停止维护](https://www.python.org/doc/sunset-python-2/),相关组件的漏洞不能得到及时修复与维护
|
||||||
|
|
||||||
|
### 2.2 第三方包安全
|
||||||
|
|
||||||
|
#### 2.2.2 禁止使用不安全的组件
|
||||||
|
|
||||||
|
### 2.3 配置信息
|
||||||
|
|
||||||
|
#### 2.3.1 密钥存储安全
|
||||||
|
|
||||||
|
- 在使用对称密码算法时,需要保护好加密密钥。当算法涉及敏感、业务数据时,可通过非对称算法协商加密密钥。其他较为不敏感的数据加密,可以通过变换算法等方式保护密钥。
|
||||||
|
|
||||||
|
#### 2.3.2 禁止硬编码敏感配置
|
||||||
|
|
||||||
|
- 禁止在源码中硬编码AK/SK、IP、数据库账密等配置信息
|
||||||
|
- 应使用配置系统或KMS密钥管理系统。
|
||||||
|
|
||||||
|
# 后台类
|
||||||
|
|
||||||
|
## 1. 代码实现
|
||||||
|
|
||||||
|
### 1.1 输入验证
|
||||||
|
|
||||||
|
#### 1.1.1 按类型进行数据校验
|
||||||
|
|
||||||
|
- 所有程序外部输入的参数值,应进行数据校验。校验内容包括但不限于:数据长度、数据范围、数据类型与格式。校验不通过,应拒绝。
|
||||||
|
|
||||||
|
- 推荐使用组件:[Cerberus](https://github.com/pyeve/cerberus)、[jsonschema](https://github.com/Julian/jsonschema)、[Django-Validators](https://docs.djangoproject.com/en/dev/ref/validators/)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Cerberus示例
|
||||||
|
v = Validator({'name': {'type': 'string'}})
|
||||||
|
v.validate({'name': 'john doe'})
|
||||||
|
|
||||||
|
# jsonschema示例
|
||||||
|
schema = {
|
||||||
|
"type" : "object",
|
||||||
|
"properties" : {
|
||||||
|
"price" : {"type" : "number"},
|
||||||
|
"name" : {"type" : "string"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(instance={"name" : "Eggs", "price" : 34.99}, schema=schema)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 SQL操作
|
||||||
|
|
||||||
|
#### 1.2.1 使用参数化查询
|
||||||
|
|
||||||
|
- 使用参数化SQL语句,强制区分数据和命令,避免产生SQL注入漏洞。
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 错误示例
|
||||||
|
import mysql.connector
|
||||||
|
|
||||||
|
mydb = mysql.connector.connect(
|
||||||
|
... ...
|
||||||
|
)
|
||||||
|
|
||||||
|
cur = mydb.cursor()
|
||||||
|
userid = get_id_from_user()
|
||||||
|
# 使用%直接格式化字符串拼接SQL语句
|
||||||
|
cur.execute("SELECT `id`, `password` FROM `auth_user` WHERE `id`=%s " % (userid,))
|
||||||
|
myresult = cur.fetchall()
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 安全示例
|
||||||
|
import mysql.connector
|
||||||
|
|
||||||
|
mydb = mysql.connector.connect(
|
||||||
|
... ...
|
||||||
|
)
|
||||||
|
cur = mydb.cursor()
|
||||||
|
userid = get_id_from_user()
|
||||||
|
# 将元组以参数的形式传入
|
||||||
|
cur.execute("SELECT `id`, `password` FROM `auth_user` WHERE `id`=%s " , (userid,))
|
||||||
|
myresult = cur.fetchall()
|
||||||
|
```
|
||||||
|
|
||||||
|
- 推荐使用ORM框架来操作数据库,如:使用`SQLAlchemy`。
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 安装sqlalchemy并初始化数据库连接
|
||||||
|
# pip install sqlalchemy
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
# 初始化数据库连接,修改为你的数据库用户名和密码
|
||||||
|
engine = create_engine('mysql+mysqlconnector://user:password@host:port/DATABASE')
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 引用数据类型
|
||||||
|
from sqlalchemy import Column, String, Integer, Float
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
# 定义 Player 对象:
|
||||||
|
class Player(Base):
|
||||||
|
# 表的名字:
|
||||||
|
__tablename__ = 'player'
|
||||||
|
|
||||||
|
# 表的结构:
|
||||||
|
player_id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
team_id = Column(Integer)
|
||||||
|
player_name = Column(String(255))
|
||||||
|
height = Column(Float(3, 2))
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 增删改查
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
# 创建 DBSession 类型:
|
||||||
|
DBSession = sessionmaker(bind=engine)
|
||||||
|
# 创建 session 对象:
|
||||||
|
session = DBSession()
|
||||||
|
|
||||||
|
# 增:
|
||||||
|
new_player = Player(team_id=101, player_name="Tom", height=1.98)
|
||||||
|
session.add(new_player)
|
||||||
|
# 删:
|
||||||
|
row = session.query(Player).filter(Player.player_name=="Tom").first()
|
||||||
|
session.delete(row)
|
||||||
|
# 改:
|
||||||
|
row = session.query(Player).filter(Player.player_name=="Tom").first()
|
||||||
|
row.height = 1.99
|
||||||
|
# 查:
|
||||||
|
rows = session.query(Player).filter(Player.height >= 1.88).all()
|
||||||
|
|
||||||
|
# 提交即保存到数据库:
|
||||||
|
session.commit()
|
||||||
|
# 关闭 session:
|
||||||
|
session.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.2.2 对参数进行过滤
|
||||||
|
|
||||||
|
- 将接受到的外部参数动态拼接到SQL语句时,必须对参数进行安全过滤。
|
||||||
|
|
||||||
|
```python
|
||||||
|
def sql_filter(sql, max_length=20):
|
||||||
|
dirty_stuff = ["\"", "\\", "/", "*", "'", "=", "-", "#", ";", "<", ">", "+",
|
||||||
|
"&", "$", "(", ")", "%", "@", ","]
|
||||||
|
for stuff in dirty_stuff:
|
||||||
|
sql = sql.replace(stuff, "x")
|
||||||
|
return sql[:max_length]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 执行命令
|
||||||
|
|
||||||
|
#### 1.3.1 【建议】避免直接调用函数执行系统命令
|
||||||
|
|
||||||
|
- 相关功能的实现应避免直接调用系统命令(如`os.system()`、`os.popen()`、`subprocess.call()`等),优先使用其他同类操作进行代替,比如:通过文件系统API进行文件操作而非直接调用操作系统命令
|
||||||
|
- 如评估无法避免,执行命令应避免拼接外部数据,同时进行执行命令的白名单限制。
|
||||||
|
|
||||||
|
#### 1.3.2 过滤传入命令执行函数的字符
|
||||||
|
|
||||||
|
- 程序调用各类函数执行系统命令时,如果涉及的命令由外部传入,过滤传入命令执行函数的字符。
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shlex
|
||||||
|
|
||||||
|
domain = sys.argv[1]
|
||||||
|
# 替换可以用来注入命令的字符为空
|
||||||
|
badchars = "\n&;|'\"$()`-"
|
||||||
|
for char in badchars:
|
||||||
|
domain = domain.replace(char, " ")
|
||||||
|
|
||||||
|
result = os.system("nslookup " + shlex.quote(domain))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.3.3 禁止不安全的代码执行
|
||||||
|
|
||||||
|
- 禁止使用 `eval` 函数处理存在外部输入的数据。
|
||||||
|
|
||||||
|
### 1.4 文件操作
|
||||||
|
|
||||||
|
#### 1.4.1 文件类型限制
|
||||||
|
|
||||||
|
- 通过白名单对上传或者下载的文件类型、大小进行严格校验。仅允许业务所需文件类型上传,避免上传木马、WebShell等文件。
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
|
||||||
|
ALLOWED_EXTENSIONS = ['txt','jpg','png']
|
||||||
|
|
||||||
|
def allowed_file(filename):
|
||||||
|
if ('.' in filename and
|
||||||
|
'..' not in filename and
|
||||||
|
os.path.splitext(filename)[1].lower() in ALLOWED_EXTENSIONS):
|
||||||
|
|
||||||
|
return filename
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.4.2 禁止外部文件存储于可执行目录
|
||||||
|
|
||||||
|
- 禁止外部文件存储于WEB容器的可执行目录(appBase)。建议使用 [tempfile](https://docs.python.org/3/library/tempfile.html) 库处理临时文件和临时目录。
|
||||||
|
|
||||||
|
#### 1.4.3 避免路径穿越
|
||||||
|
|
||||||
|
- 保存在本地文件系统时,必须对路径进行合法校验,避免目录穿越漏洞
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
|
||||||
|
upload_dir = '/tmp/upload/' # 预期的上传目录
|
||||||
|
file_name = '../../etc/hosts' # 用户传入的文件名
|
||||||
|
absolute_path = os.path.join(upload_dir, file_name) # /tmp/upload/../../etc/hosts
|
||||||
|
normalized_path = os.path.normpath(absolute_path) # /etc/hosts
|
||||||
|
if not normalized_path.startswith(upload_dir): # 检查最终路径是否在预期的上传目录中
|
||||||
|
raise IOError()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.4.4 禁用XML外部实体的方法
|
||||||
|
|
||||||
|
- 禁用XML外部实体的方法,来预防XXE攻击。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.4.5 禁用不安全的反序列化函数
|
||||||
|
|
||||||
|
- 禁用`yaml.unsafe_load()`函数反序列化YAML数据,来避免反序列化漏洞执行漏洞。
|
||||||
|
|
||||||
|
#### 1.4.6 【建议】避免路径拼接
|
||||||
|
|
||||||
|
- 文件目录避免外部参数拼接。保存文件目录建议后台写死并对文件名进行校验(字符类型、长度)。
|
||||||
|
|
||||||
|
#### 1.4.7 【建议】文件名hash化处理
|
||||||
|
|
||||||
|
- 建议文件保存时,将文件名替换为随机字符串。
|
||||||
|
|
||||||
|
```python
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
def random_filename(filename):
|
||||||
|
ext = os.path.splitext(filename)[1]
|
||||||
|
new_filename = uuid.uuid4().hex + ext
|
||||||
|
return new_filename
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 网络请求
|
||||||
|
|
||||||
|
#### 1.5.1 限定访问网络资源地址范围
|
||||||
|
|
||||||
|
当程序需要从用户指定的`URL地址获取网页文本内容`、`加载指定地址的图片`、`进行下载`等操作时,需要对URL地址进行安全校验:
|
||||||
|
|
||||||
|
1. 只允许HTTP或HTTPS协议
|
||||||
|
|
||||||
|
2. 解析目标URL,获取其host
|
||||||
|
|
||||||
|
3. 解析host,获取host指向的IP地址转换成long型
|
||||||
|
|
||||||
|
4. 检查IP地址是否为内网IP
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 以RFC定义的专有网络为例,如有自定义私有网段亦应加入禁止访问列表。
|
||||||
|
10.0.0.0/8
|
||||||
|
172.16.0.0/12
|
||||||
|
192.168.0.0/16
|
||||||
|
127.0.0.0/8
|
||||||
|
```
|
||||||
|
|
||||||
|
5. 请求URL
|
||||||
|
|
||||||
|
6. 如果有跳转,跳转后执行1,否则对URL发起请求
|
||||||
|
|
||||||
|
### 1.6 响应输出
|
||||||
|
|
||||||
|
#### 1.6.1 设置正确的HTTP响应包类型
|
||||||
|
|
||||||
|
响应包的HTTP头“Content-Type”必须正确配置响应包的类型,禁止非HTML类型的响应包设置为“text/html”。
|
||||||
|
|
||||||
|
#### 1.6.2 设置安全的HTTP响应头
|
||||||
|
|
||||||
|
- X-Content-Type-Options
|
||||||
|
|
||||||
|
添加“X-Content-Type-Options”响应头并将其值设置为“nosniff ”
|
||||||
|
|
||||||
|
- HttpOnly
|
||||||
|
控制用户登鉴权的Cookie字段 应当设置HttpOnly属性以防止被XSS漏洞/JavaScript操纵泄漏。
|
||||||
|
|
||||||
|
- X-Frame-Options
|
||||||
|
|
||||||
|
设置X-Frame-Options响应头,并根据需求合理设置其允许范围。该头用于指示浏览器禁止当前页面在frame、 iframe、embed等标签中展现。从而避免点击劫持问题。它有三个可选的值: DENY: 浏览器会拒绝当前页面加 载任何frame页面; SAMEORIGIN:则frame页面的地址只能为同源域名下的页面 ALLOW-FROM origin:可以定 义允许frame加载的页面地址。
|
||||||
|
|
||||||
|
#### 1.6.3 对外输出页面包含第三方数据时须进行编码处理
|
||||||
|
|
||||||
|
- 当响应“Content-Type”为“text/html”类型时,需要对响应体进行编码处理
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 推荐使用mozilla维护的bleach库来进行过滤
|
||||||
|
import bleach
|
||||||
|
bleach.clean('an <script>evil()</script> example')
|
||||||
|
# u'an <script>evil()</script> example'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.7 数据输出
|
||||||
|
|
||||||
|
#### 1.7.1 敏感数据加密存储
|
||||||
|
|
||||||
|
- 敏感数据应使用SHA2、RSA等算法进行加密存储
|
||||||
|
- 敏感数据应使用独立的存储层,并在访问层开启访问控制
|
||||||
|
- 包含敏感信息的临时文件或缓存一旦不再需要应立刻删除
|
||||||
|
|
||||||
|
#### 1.7.2 敏感信息必须由后台进行脱敏处理
|
||||||
|
|
||||||
|
- 敏感信息须再后台进行脱敏后返回,禁止接口返回敏感信息交由前端/客户端进行脱敏处理。
|
||||||
|
|
||||||
|
#### 1.7.3 高敏感信息禁止存储、展示
|
||||||
|
|
||||||
|
- 口令、密保答案、生理标识等鉴权信息禁止展示
|
||||||
|
- 非金融类业务,信用卡cvv码及日志禁止存储
|
||||||
|
|
||||||
|
#### 1.7.4 个人敏感信息脱敏展示
|
||||||
|
|
||||||
|
在满足业务需求的情况下,个人敏感信息需脱敏展示。
|
||||||
|
|
||||||
|
- 身份证只显示第一位和最后一位字符,如3****************1。
|
||||||
|
- 移动电话号码隐藏中间6位字符,如134******48。
|
||||||
|
- 工作地址/家庭地址最多显示到“区”一级。
|
||||||
|
- 银行卡号仅显示最后4位字符,如************8639
|
||||||
|
|
||||||
|
#### 1.7.5 隐藏后台地址
|
||||||
|
|
||||||
|
- 若程序对外提供了登录后台地址,应使用随机字符串隐藏地址。
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 不要采取这种方式
|
||||||
|
admin_login_url = "xxxx/login"
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 安全示例
|
||||||
|
admin_login_url = "xxxx/ranD0Str"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.8 权限管理
|
||||||
|
|
||||||
|
#### 1.8.1 默认鉴权
|
||||||
|
|
||||||
|
- 除非资源完全可对外开放,否则系统默认进行身份认证(使用白名单的方式放开不需要认证的接口或页面)。
|
||||||
|
|
||||||
|
#### 1.8.2 授权遵循最小权限原则
|
||||||
|
|
||||||
|
- 程序默认用户应不具备任何操作权限。
|
||||||
|
|
||||||
|
#### 1.8.3 避免越权访问
|
||||||
|
|
||||||
|
- 对于非公共操作,应当校验当前访问账号进行操作权限(常见于CMS)和数据权限校验。
|
||||||
|
|
||||||
|
1. 验证当前用户的登录态;
|
||||||
|
2. 从可信结构中获取经过校验的当前请求账号的身份信息(如:session),禁止从用户请求参数或Cookie中获取外部传入不可信用户身份直接进行查询;
|
||||||
|
3. 校验当前用户是否具备该操作权限;
|
||||||
|
4. 校验当前用户是否具备所操作数据的权限;
|
||||||
|
5. 校验当前操作是否账户是否预期账户。
|
||||||
|
|
||||||
|
#### 1.8.4 【建议】及时清理不需要的权限
|
||||||
|
|
||||||
|
- 程序应定期清理非必需用户的权限。
|
||||||
|
|
||||||
|
### 1.9 异常处理
|
||||||
|
|
||||||
|
#### 1.9.1 不向对外错误提示
|
||||||
|
|
||||||
|
- 应合理使用`try/except/finally` 处理系统异常,避免出错信息输出到前端。
|
||||||
|
- 对外环境禁止开启debug模式,或将程序运行日志输出到前端。
|
||||||
|
|
||||||
|
#### 1.9.2 禁止异常抛出敏感信息
|
||||||
|
|
||||||
|
### 1.10 Flask安全
|
||||||
|
|
||||||
|
#### 1.10.1 生产环境关闭调试模式
|
||||||
|
|
||||||
|
#### 1.10.2 【建议】遵循Flask安全规范
|
||||||
|
|
||||||
|
- 参考Flask文档中的安全注意事项 <https://flask.palletsprojects.com/en/latest/security/>
|
||||||
|
|
||||||
|
### 1.11 Django安全
|
||||||
|
|
||||||
|
#### 1.11.1 生产环境关闭调试模式
|
||||||
|
|
||||||
|
#### 1.11.2 【建议】保持Django自带的安全特性开启
|
||||||
|
|
||||||
|
- 保持Django自带的安全特性开启 <https://docs.djangoproject.com/en/3.0/topics/security/>
|
||||||
|
|
||||||
|
- 在默认配置下,Django自带的安全特性对XSS、CSRF、SQL注入、点击劫持等类型漏洞可以起到较好防护效果。应尽量避免关闭这些安全特性。
|
@ -1,11 +1,13 @@
|
|||||||
---
|
---
|
||||||
id: 腾讯代码安全指南
|
id: 腾讯代码安全指南
|
||||||
title: 腾讯代码安全指南
|
title: 腾讯代码安全指南
|
||||||
data: 2022年5月30日d
|
sidebar_position: 1
|
||||||
|
data: 2022年5月30日
|
||||||
---
|
---
|
||||||
|
|
||||||
面向开发人员梳理的代码安全指南,旨在梳理API层面的风险点并提供详实可行的安全编码方案。
|
面向开发人员梳理的代码安全指南,旨在梳理API层面的风险点并提供详实可行的安全编码方案。
|
||||||
|
|
||||||
|
|
||||||
仓库地址:**[secguide](https://github.com/Tencent/secguide)**
|
仓库地址:**[secguide](https://github.com/Tencent/secguide)**
|
||||||
|
|
||||||
| 规范 | 最后修订日期 |
|
| 规范 | 最后修订日期 |
|
Loading…
Reference in New Issue
Block a user