关于Web安全的问题,是一个老生常谈的问题,作为离用户最近的一层,我们大前端应该把手伸的更远一点。

我们最常见的Web安全攻击有以下几种:

  1. XSS 跨站脚本攻击
  2. CSRF 跨站请求伪造
  3. URL 跳转漏洞
  4. ClickJacking 点击劫持/UI-覆盖攻击
  5. SQL Injection SQL注入
  6. OS Command Injection OS命令注入

一、XSS

XSS (Cross Site Script),中文是跨站脚本攻击;其原本缩写是 CSS,但为了和层叠样式表(Cascading Style Sheet)有所区分,因而在安全领域叫做 XSS。

恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

XSS攻击可以分为3类:

  • 反射型 - 非持久型 Reflected XSS
  • 存储型 - 持久型 Stored XSS
  • 基于DOM或本地的XSS DOM-based or local XSS


1. 反射型

反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器,这种攻击方式往往需要攻击者诱使用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。

假装我是一个恶意链接(Click Me~)

node
1
2
3
4
5
6
7
8
9
10
11
const http = require('http');

// 启http服务
const server = http.createServer((req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
res.write('<script>while(true)alert("反射型 XSS 攻击")</script>');
res.end();
});

server.listen('8000');

这样就产生了反射型 XSS 攻击。攻击者可以注入任意的恶意脚本进行攻击,可能注入恶作剧脚本,或者注入能获取用户隐私数据(如cookie)的脚本,这取决于攻击者的目的。

2. 存储型

存储型 XSS 会把用户输入的数据 “存储” 在服务器端,当浏览器请求数据时,脚本从服务器上传回并执行。这种 XSS 攻击具有很强的稳定性。

比较常见的一个场景是攻击者在社区或论坛上写下一篇包含恶意 JavaScript 代码的文章或评论,文章或评论发表后,所有访问该文章或评论的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码。

html
1
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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>存储型 XSS</title>
</head>

<body>
<div>Try Me:<input type="text" id="input"></div>
<button id="btn">Submit</button>

<script>
const input = document.getElementById('input');
const btn = document.getElementById('btn');
let val;

input.addEventListener('change', e => {
val = e.target.value;
}, false);

btn.addEventListener('click', e => {
fetch('http://localhost:8000/save', {
method: 'POST',
body: val
});
}, false);
</script>
</body>

</html>
node
1
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
const http = require('http');

let userInput = '';

function handleReequest (req, res) {
const method = req.method;
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')

if (method === 'POST' && req.url === '/save') {
let body = '';
req.on('data', chunk => {
body += chunk;
});
req.on('end', () => {
if (body) {
userInput = body;
}
res.end();
});
} else {
res.writeHead(200, { 'Content-Type': 'text/html; charset=UTF-8' });
res.write(userInput);
res.end();
}
}

// 启http服务
const server = http.createServer((req, res)=> {
handleReequest(req, res)
});
server.listen('8000');

3. 基于DOM

基于DOM或本地的XSS其实是一种特殊类型的反射型XSS,它是基于DOM文档对象模型的一种漏洞。可以通过DOM来动态修改页面内容,从客户端获取DOM中的数据并在本地执行。基于这个特性,就可以利用JS脚本来实现XSS漏洞的利用。

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<input type="text" id="input">
<button id="btn">Submit</button>
<div id="div"></div>
<script>
const input = document.getElementById('input');
const btn = document.getElementById('btn');
const div = document.getElementById('div');
let val;

input.addEventListener('change', (e) => {
val = e.target.value;
}, false);

btn.addEventListener('click', () => {
div.innerHTML = `<a href=${val}>Try Me~</a>`
}, false);
</script>
</body>

总结:
XSS攻击的本质就是,利用一切手段在目标用户的浏览器中执行攻击脚本。

防范:
对于一切用户的输入、输出、客户端的输出内容视为不可信,在数据添加到DOM或者执行了DOM API的时候,我们需要对内容进行HtmlEncode或JavaScriptEncode,以预防XSS攻击。

现在主流的浏览器内置了防范 XSS 的措施,例如 内容安全策略(CSP)。但对于开发者来说,也应该寻找可靠的解决方案来防止 XSS 攻击。

二、CSRF

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。

通常情况下,CSRF 攻击是攻击者借助受害者的 Cookie 骗取服务器的信任,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器,从而在并未授权的情况下执行在权限保护之下的操作。

关于浏览器的Cookie策略请参考HTTP Cookie


假设有一个BBS站点http://www.a.com:

  1. 当用户登录之后,会设置如下 cookie:
    res.setHeader('Set-Cookie', ['user=william; expires=Fri, 23 Mar 2019 00:00:00 GMT;'])

  2. 当登录后的用户发起 http://www.a.com/delete?id=666666 请求时,会删除 id 为 666666 的帖子。

  3. CSRF攻击者准备的网站B:<img src="http://www.a.com/delete?id=666666">

  4. 当登录用户访问攻击者的网站B时,会向 www.a.com 发起一个删除用户帖子的请求。此时若用户在切换到 www.a.com 的帖子页面刷新,会发现ID 为 666666 的帖子已经被删除。

在站点没有做做任何 CSRF 防御的情况下,要完成一次CSRF攻击,受害者必须依次完成两个步骤:

  • 登录受信任网站A,并在本地生成Cookie。
  • 在不登出A的情况下,访问危险网站B。


2. CSRF攻击的防范

1.验证 HTTP Referer 字段
2.添加 Token 验证
3.限制 Cookie
4.验证码

尽管CSRF听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。但往往同XSS一同作案!


三、URL跳转漏洞

借助未验证的URL跳转,将应用程序引导到不安全的第三方区域,从而导致的安全问题。

当用户点击后,经过服务器或者浏览器解析后,将会跳到恶意的网站中。

http://a.baidu.com/index?act=go&url=http://evil.cn/
http://b.baidu.com/safecheck.html?id=1&url=http://evil.cn/
http://c.baidu.com/f/user/passport?jumpUrl=http://evil.cn/

1. 实现方式

  • META标签内跳转
  • Javascript跳转
  • Header头跳转

通过以GET或者POST的方式接收将要跳转的URL,然后通过上面的几种方式的其中一种来跳转到目标URL。一方面,由于用户的输入会进入Meta,javascript,http头所以都可能发生相应上下文的漏洞,如xss等等,但是同时,即使只是对于URL跳转本身功能方面就存在一个缺陷,因为会将用户浏览器从可信的站点导向到不可信的站点,同时如果跳转的时候带有敏感数据一样可能将敏感数据泄漏给不可信的第三方。

2. 防御方案

① referer的限制
如果确定传递URL参数进入的来源,我们可以通过该方式实现安全限制,保证该URL的有效性,避免恶意用户自己生成跳转链接

② 加入有效性验证Token
我们保证所有生成的链接都是来自于我们可信域的,通过在生成的链接里加入用户不可控的Token对生成的链接进行校验,可以避免用户生成自己的恶意链接从而被利用,但是如果功能本身要求比较开放,可能导致有一定的限制。


四、ClickJacking

ClickJacking点击劫持,也叫UI覆盖攻击,攻击者会利用一个或多个透明或不透明的层来诱骗用户支持点击按钮的操作,而实际的点击确实用户看不到的一个按钮,从而达到在用户不知情的情况下实施攻击。

1. iframe覆盖

这种攻击方式的关键在于可以实现页中页的<iframe>标签,并且可以使用css样式表将他不可见。

防止点击劫持有两种主要方法:

  1. X-FRAME-OPTIONS
    X-FRAME-OPTIONS是微软提出的一个http响应首部,指示浏览器不允许从其他域进行取景,专门用来防御利用iframe嵌套的点击劫持攻击。并且在IE8、Firefox3.6、Chrome4以上的版本均能很好的支持。
    DENY : 拒绝任何域加载
    SAMEORIGIN : 允许同源域下加载
    ALLOW-FROM : 可以定义允许frame加载的页面地址

  2. 顶层判断
    在UI中采用防御性代码,以确保当前帧是最顶层的窗口,如:
    top != self || top.location != self.location || top.location != location

2. 图片覆盖

图片覆盖攻击(Cross Site Image Overlaying),攻击者使用一张或多张图片,利用图片的style或者能够控制的CSS,将图片覆盖在网页上,形成点击劫持。当然图片本身所带的信息可能就带有欺骗的含义,这样不需要用户点击,也能达到欺骗的目的。

html
1
2
3
<a href="http://www.a.com/delete?id=666666">
<img src="~~~" style="~~~" />
</a>

解决方案: 在防御图片覆盖攻击时,需要检查用户提交的HTML代码中,img标签的style属性是否可能导致浮出。


五、SQL Injection

SQL 注入漏洞(SQL Injection)是 Web 开发中最常见的一种安全漏洞。可以用它来从数据库获取敏感信息,或者利用数据库的特性执行添加用户,导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限。

1. 原理

SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有细致地过滤用户输入的数据,致使非法数据侵入系统。

根据相关技术原理,SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤,从而执行了非法的数据查询。基于此,SQL注入的产生原因通常表现在以下几方面:

① 不当的类型处理;
② 不安全的数据库配置;
③ 不合理的查询集处理;
④ 不当的错误处理;
⑤ 转义字符处理不合适;
⑥ 多个提交处理不当。

2. 攻击

当应用程序使用输入内容来构造动态sql语句以访问数据库时,会发生sql注入攻击。如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生sql注入。sql注入可能导致攻击者使用应用程序登陆在数据库中执行命令。相关的SQL注入可以通过测试工具pangolin进行。

3. 防护

① 永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和双”-“进行转换等。
② 永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
③ 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
④ 不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
⑤ 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装
⑥ sql注入的检测方法一般采取辅助软件或网站平台来检测,软件一般采用sql注入检测工具jsky,网站平台就有亿思网站安全平台检测工具MDCSOFT SCAN等。采用MDCSOFT-IPS可以有效的防御SQL注入,XSS攻击等。


六、OS命令注入

OS 注入攻击是指程序提供了直接执行 Shell 命令的函数的场景,当攻击者不合理使用,且开发者对用户参数未考虑安全因素的话,就会执行恶意的命令调用,被攻击者利用。

OS 命令注入其实原理和 SQL 注入是类似的,只是场景不一样而已。
在 Node.js 中可以使用 exec() 执行命令通过传入一段字符串命令,并把一个错误或命令处理结果回传至回调函数中。

1. exec

node
1
2
3
4
let userInput = "user input";
child_process.exec('ls -l ' + userInput, (err, data) => {
console.log(data);
});

攻击者可以使用一个分号”;”来结束命令,并开始一个新的调用,他们可以使用反引号或$()来运行子命令。还有很多潜在的滥用。

在child_process.exec引擎下,将调用执行”/bin/sh”。而不是目标程序。已发送的命令只是被传递给一个新的”/bin/ sh’进程来执行shell。 child_process.exec的名字有一定误导性 - 这是一个bash的解释器,而不是启动一个程序。这意味着,如果直接执行用户输入的参数,所有的shell字符可能会产生毁灭性的后果。

2. execFile/spawn

在 Node.js 中除了 exec() 之外,还有 execFile() 和 spawn() 两个方法也可以用来执行系统命令。它们和 exec() 的区别是后者是直接将一个命令字符串传给 /bin/sh 执行,而前者是提供了一个数组作为参数容器,最后参数会被直接传到 C 的命令执行方法 execve() 中,不容易执行额外的参数。

child_process.execFile
1
2
3
4
let path = "user input";
child_process.execFile('/bin/ls', ['-l', path], (err, result) => {
console.log(result)
});
child_process.spawn
1
2
3
4
5
6

let path = "user input";
let ls = child_process.spawn('/bin/ls', ['-l', path])
ls.stdout.on('data', data => {
console.log(data.toString());
});

注意:使用spawn或execFile并不总是安全的。例如,运行 /bin/find,并传入用户输入参数仍有可能导致系统被攻陷。 find命令有一些选项,允许读/写任意文件。

  • 避免使用child_process.exec,当需要包含用户输入的参数时更是如此,请牢记。
  • 尽量避免让用户传入参数,使用选择项比让用户直接输入字符串要好得多。
  • 必须允许用户输入参数的情况下,请广泛参考该命令的参数,确定哪些选项是安全的,并建立一个白名单。


参考文章: