网络安全防范
# 输入校验安全正则
- 正则表达式。正则表达式应该限制^开头和$结尾,以免部分匹配而被绕过。
- 白名单思想。因为用户的输入集合是无限的,如果仅仅从黑名单进行过滤,会存在被绕过的可能。所以应将用户的输入类型、字符集合、长度限制在安全的范围之内。
# 日期
日期格式通常为:2018-12-21,2018-12-21 11:34:22,2017/12/24,2017/12/24 11:33:22
(^\d\{4\}\[-/\]\d\{2\}\[-/\]\d\{2\}$)|(^\d\{4\}\[-/\]\d\{2\}\[-/\]\d\{2\}\s+\d\{2\}\:\d\{2\}($|\:\d\{2\}$))
# 域名
域名都由英文字母和数字组成,每一个标号不超过 63 个字符,也不区分大小写字母。标号中除连字符(-)外不能使用其他的标点符号,完整域名总共不超过 255 个字符
^(?=^.\{3,255\}$)\[a-zA-Z0-9\]\[-a-zA-Z0-9\]\{0,62\}(\.\[a-zA-Z0-9\]\[-a-zA-Z0-9\]\{0,62\})+(.)?$
# IP 地址
IP 地址的长度为 32 位,分为 4 段,每段数字范围为 0~255,段与段之间用英文句点“.”隔开
// IPv4
^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$
// IPv6
(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))
2
3
4
5
6
# 邮箱地址
Email 地址由"@"号分成邮箱名和网址两部份,其中邮箱名由单词字符:大小写字母、数字及下划线组成,并且可以出现"."号
^\[.0-9a-zA-Z_\]\{1,18\}@(\[0-9a-zA-Z-\]\{1,13\}\.)\{1,\}\[a-zA-Z\]\{1,3\}$
# 中文字符
[\u4e00-\u9fa5]
# SQL 注入
# 原理
如果程序根据用户输入的参数动态生成 SQL 语句并执行,黑客可通过传入恶意参数值注入自己定义的语句,使数据库执行任意自己需要的指令,实现数据窃取或入侵破坏。
# 危险
- 导致拖库、敏感信息泄露
- 数据被篡改、删除
- 数据库主机服务器被入侵
# 防护
- 对于 SQL 注入,最稳妥和保险的方法只有使用预编译语句然后绑定变量。通过使用占位符,保持查询语句和数据相分离。查询语句结构由占位符定义,SQL 语句发送给数据库并做好准备,然后准备好的语句与参数值相结合。这样就防止了查询被更改,因为参数值与已编译的语句相结合,而不是 SQL 字符串。从根本上避免了用户输入的恶意参数当作 SQL 语句执行。
- 当实在是有 like 、having、group by、order by 、limited 、offset 等动态查询时才考虑白名单输入过滤、转义等方法。
- 弱类型语言,使用变量之前声明变量类型。
预编译 Nodejs 示例
sequelize
.query(
'SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $1',
{ bind: ["active"], type: sequelize.QueryTypes.SELECT }
)
.then((projects) => {
console.log(projects);
});
sequelize
.query(
'SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $status',
{ bind: { status: "active" }, type: sequelize.QueryTypes.SELECT }
)
.then((projects) => {
console.log(projects);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
输入转义 Nodejs 示例
var mysql = require("mysql");
var connection = mysql.createConnection({
//省略
});
connection.connect();
var userId = "some user provided value";
//使用escape进行转义
var sql = "SELECT * FROM users WHERE id = " + connection.escape(userId); //转义可能恶意的userID
connection.query(sql, function(error, results, fields) {
if (error) throw error;
// 省略
});
//或者使用?占位符
connection.query("SELECT * FROM users WHERE id = ?", [userId], function(
error,
results,
fields
) {
if (error) throw error;
// ...
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# XSS 跨站攻击
# 原理
如果 Web 页面在动态展示数据时使用了用户的输入内容,但是未做输入过滤和输出转义,导致黑客可通过参数传入恶意代码,当用户浏览页面时恶意代码会被执行。 跨站脚本攻击有三种攻击形式:
反射型跨站脚本攻击 攻击者会通过社会工程学手段,发送一个 URL 连接给用户打开,在用户打开页面的同时,浏览器会执行页面中嵌入的恶意脚本。
存储型跨站脚本攻击 攻击者利用 web 应用程序提供的录入或修改数据功能,将数据存储到服务器或用户 cookie 中,当其他用户浏览展示该数据的页面时,浏览器会执行页面中嵌入的恶意脚本。所有浏览者都会受到攻击。
DOM 型跨站脚本攻击 由于 html 页面中,定义了一段 JS,根据用户的输入,显示一段 html 代码,攻击者可以在输入时,插入一段恶意脚本,最终展示时,会执行恶意脚本。 DOM 跨站和以上两个跨站攻击的差别是,DOM 跨站是纯页面脚本的输出,只有规范使用 Javascript,才可以防御。
# 危险
- 盗取用户 cookie,伪造用户身份登录。
- 控制用户浏览器。
- 结合浏览器及其插件漏洞,下载病毒木马到浏览者的计算机上执行。
- 衍生 URL 跳转漏洞。
- 让官方网站出现钓鱼页面。
- 蠕虫攻击
总而言之,前端脚本能实现的所有功能都会被跨站脚本利用。
# 需防护场景
将 GET 参数值按原值输出到页面中(包括 HTTP 头、HTML 标签、Javascript、CSS 等处),必须做反射 XSS 防护。 将用户提交的文本内容存储在后台并在前端展示的场景。如用户注册(姓名、产品名、签名、个人简介)、评论、反馈、UGC 发表,文件名,必须做存储型 XSS 的防护。 若涉及 URL 目的跳转的逻辑((例如 location.replace、location.href 等),或者涉及使用 innerHTML、document.write、eval 等敏感函数,必须做 DOM 型 XSS 防护。 返回 JSON 的接口务必将 Content-Type 响应头设置为 application/json 或 text/plain。 涉及对页面内容进行多次编码的处理注意避免由于反编码等操作而导致的 mXSS,即将原本无害编码内容又反编码为有害内容而导致的 XSS。 注意避免使用存在 XSS 漏洞前端 JS 组件,如小于等于 1.11.3 的 Jquery 版本。
# 反射 XSS 防护方案
对输入参数进行类型、字符集、长度的限制
增加 HTTP 安全头字段:X-Content-Type-Options 和 X-XSS-Protection
增加 X-Content-Type-Options:nosniff 的 HTTP 安全头字段,可避免部分版本 IE 浏览器无视 Content-Type 设置执行 XSS Payload。 增加 X-XSS-Protection:1; mode=block 的 HTTP 安全头字段可提示浏览器发现 XSS Payload 时不要渲染文档。
- 编码防护
# DOM 型 XSS 防范
危险方式: 从 url 取值,使用 document.write 写入
<select>
<script>
document.write(
"<OPTION value=1>" +
document.location.href.substring(
document.location.href.indexOf("default=") + 8
) +
"</OPTION>"
);
document.write("<OPTION value=2>English</OPTION>");
</script>
</select>
2
3
4
5
6
7
8
9
10
11
12
防范
function VaildURL(sUrl) {
return /^(https?:\/\/)?[\w\-.]+\.(cd|taobao)\.com($|\/|\\)/i.test(sUrl) ||
/^[\w][\w\/\.\-_%]+$/i.test(sUrl) ||
/^[\/\\][^\/\\]/i.test(sUrl)
? true
: false;
}
var url = getParam("url");
url = ValidURL(url); //调用前进行过滤
location.href = url;
2
3
4
5
6
7
8
9
10
# 点击劫持
# 原理
攻击者在恶意站点实现了一个和信任站点极其相似的恶意页面,然后在恶意页面的上层覆盖了一个信任站点的合法页面(通常采用 iframe 的方式),并将合法页面设置成透明态,诱导用户点击页面,这时实际触发的是合法页面上的事件,从而导致用户敏感信息泄露、实施转账、添加权限或者删除记录等敏感操作
# 防范
- 检测自己的网页是否为顶层页面,否则跳转
Window !== top && (top.location.href = window.location.href);
- 设置 http 头部 X-Frame-Options 选项
设置允许网页嵌套的范围: DENY:浏览器会拒绝当前页面加载任何 frame 页面; SAMEORIGIN:frame 页面的地址只能为同源域名下的页面; ALLOW-FROM origin:允许 frame 加载的页面地址;
- Nodejs 添加安全头部
var express = require("express");
var helmet = require("helmet");
var app = express();
app.use(helmet());
2
3
4
# 命令注入
# 原理
如果程序根据用户输入的参数动态生成系统命令并执行,黑客可通过传入恶意参数值注入自己定义的命令,从而控制服务器
# 危险
使用输入参数拼接系统命令进行执行的场景包括但不限于 system(),eval(),exec()等函数,需要对输入的参数进行转义或者白名单过滤。
# 防护
- 功能设计应避免直接执行命令,能不直接执行命令,就不直接执行命令
- 如果功能需要,执行命令执行命令应尽量使用普通权限账户,避免使用 root 权限而引入本地提权问题。
- 对参数可输入的字符范围做白名单限制 。比如允许[a-z][A-Z][0-9].*-等有限安全的字符。
- 转义替换这些敏感字符|;&$><`!
- 使用转义 API,PHP:escapeshellarg(); escapeshellcmd();
# CSRF 漏洞
# 原理
CSRF(Cross-site request forgery)跨站请求伪造,是指挟持用户在当前已登录的 Web 应用程序上执行非本意的操作。恶意网站通过一些技术手段(例如插入一张图片,src 地址是操作请求)欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作(如发消息、删消息、点赞关注,甚至财产操作如转账和购买商品)。
# 需防护场景
对用户数据进行查询、操作的接口。
# 防护
- 检查 Referer 字段
通过检查 HTTP 请求的 Referer 字段是否属于本站域名,非本站域名的请求进行拒绝。陷阱:一是要需要处理 Referer 为空的情况,二是要处理例如 cd.sassx.xxx.com 部分匹配的情况。
- 添加校验 token
服务器生成一个伪随机数作为 token,附加在表单的隐藏字段下发给用户。当客户端通过表单提交请求时,这个 token 也一并提交上去以供后台校验,拒绝掉校验不通过的请求;注意避免 token 可构造情况。
- 合理使用框架的 CSRF 防护功能
//######服务端代码######
var express = require('express'), // express框架
bodyparser = require('body-parser'), // 使用POST,需要引入此中间件来解析URL编码体
cookieParser = require('cookie-parser'), // cookie中间件,csurf需依赖它
csrf = require('csurf'); // csurf中间件
// 设置路由中间件
var csrfProtection = csrf({cookie:true}),
parseForm = bodyParser.urlencodeed({extended:false});
// 创建epxress app
var app = express();
// cookies
// 需要它,因为 csrfProtection 中"cookie"设置的是true,需要依赖cookie
app.use(cookieParser());
app.get('/form', csrfProtection, function (req, res) {
// 把 csrfToken 传递给视图
res.render('send', { csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, function (req, res) {
res.send('data is being processed')
})
//######客户端代码######
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
Favorite color: <input type="text" name="favoriteColor">
<button type="submit">Submit</button>
</form>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34