Ashing's Blog

想学的太多 懂得的太少

0%

WEB安全—CSP策略

0x01 内容安全策略(Content Security Policy)

1 简介

  • CSP 是一个附加的安全层,用于帮助检测和缓解某些类型的攻击,包括跨站脚本 (XSS) 和数据注入等攻击
  • CSP 是一种白名单策略,该策略可让网站管理员指定客户端允许加载的各类可信任资源,当有从非白名单允许的 JS 脚本出现在页面中,浏览器会阻止脚本的执行。
  • 手册:https://developer.mozilla.org/zh-CN/docs/Web/Security/CSP

2 开启CSP:启用后不符合CSP的外部资源就会被阻止加载

  • HTTP头信息的Content-Security-Policy字段
1
header("Content-Security-Policy: default-src https:; report-uri /csp-violation-report-endpoint/");
  • 网页的<meta>标签
1
2
3
4
5
6
7
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">

脚本:只信任当前域名
<object>标签:不信任任何URL,即不加载任何资源
样式表:只信任cdn.example.org和third-party.org
框架(frame):必须使用HTTPS协议加载
其他资源:没有限制

0x02 CSP限制选项

1 资源加载限制

default-src:用来设置默认值

1
Content-Security-Policy: default-src 'self' //限制所有的外部资源,都只能从当前域名加载
  • 如果同时设置某个单项限制(比如font-src)和 default-src,前者会覆盖后者,即字体文件会采用 font-src 的值,其他资源依然采用 default-src 的值
1
2
3
4
5
6
7
8
9
10
11
script-src:外部脚本
style-src:样式表
img-src:图像
media-src:媒体文件(音频和视频)
font-src:字体文件
object-src:插件(比如 Flash)
child-src:框架
frame-ancestors:嵌入的外部资源(比如frame、iframe、embed和applet)
connect-src:HTTP 连接(通过 XHR、WebSockets、EventSource等)
worker-src:worker脚本
manifest-src:manifest 文件

2 URL限制

1
2
3
frame-ancestors:限制嵌入框架的网页
base-uri:限制<base#href>
form-action:限制<form#action>

3 其他限制

1
2
3
4
block-all-mixed-content:HTTPS 网页不得加载 HTTP 资源(浏览器已经默认开启)
upgrade-insecure-requests:自动将网页上所有加载外部资源的 HTTP 链接换成 HTTPS 协议
plugin-types:限制可以使用的插件格式
sandbox:浏览器行为的限制,比如不能有弹出窗口等。

report-uri、report-to

  • 记录注入行为,并报告给指定地址。例如:
1
2
Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
//浏览器会使用POST方法,发送一个JSON对象。将注入行为报告给/my_amazing_csp_report_parser这个 URL。
  • 尽管report-to指令旨在取代已弃用的report-uri指令, report-uri已经从Web标准中删除,但在大多数浏览器中仍不受支持。因此,可以同时指定report-to和report-uri。

0x03 选项值

  • 每个选项可以选择选项值,这些值构成了白名单
1
2
3
4
5
6
主机名:example.org,https://example.com:443
路径名:example.org/resources/js/
通配符:*.example.org,*://*.example.com:*(表示任意协议、任意子域名、任意端口)
协议名:https:、data:
关键字'self':当前域名,需要加引号
关键字'none':禁止加载任何外部资源,需要加引号
  • 多个值可以并列,并用空格来分隔

    • 如果同一个限制选项使用多次,只有第一次会生效。
    • 如果不设置某个限制选项,就是默认允许任何值。
1
Content-Security-Policy: script-src 'self' https://apis.google.com

script-src的特殊值

  • 除了常规值,script-src还可以设置一些特殊值。
1
2
'unsafe-inline':允许执行页面内嵌的<script>标签和事件监听函数 
'unsage-eval':允许将字符串当作代码执行,比如使用evalsetTimeoutsetIntervalFunction等函数。

nonce值和hash值

  • 除了script-src选项,nonce值和hash值还可以用在style-src选项,控制页面内嵌的样式表。

  • nonce值:每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个token,才会执行。这个字符串可以在后端实现,每次请求都重新生成,这样就可以无视哪个域是可信的,保证所加载的任何资源都是可信的,并且还能拦截后面插入的 script。

1
2
3
4
<?php
Header("Content-Security-Policy: script-src 'nonce-".$random." '");
?>
<script nonce="<?php echo $random?>">
1
2
3
4
5
6
7
服务器发送网页的时候,告诉浏览器一个随机生成的token。
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

页面内嵌脚本,必须有这个token才能执行。
<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
// some code
</script>
  • hash值:列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。
1
2
3
4
服务器给出一个允许执行的代码的hash值:(注意,计算hash值的时候,script标签不算在内。)
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
下面的代码就会允许执行,因为hash值相符:
<script>alert('Hello, world.');</script>

strict-dynamic

  • SD 意味着可信 js 生成的 js 代码是可信的。这个 CSP 规则主要是用来适应各种各样的现代前端框架,通过这个规则,可以大幅度避免因为适应框架而变得松散的 CSP 规则。
1
Content-Security-Policy: default-src 'self'; script-src 'strict-dynamic'

0x04 注意

(1)script-src和object-src是必设的,除非设置了default-src

  • 因为攻击者只要能注入脚本,其他限制都可以规避。而object-src必设是因为 Flash 里面可以执行外部脚本
  • script-src和object-src源列表不能包含含有攻击者可控制response的安全相关部分的源地址,或包含不安全的库。否则可以被绕过

(2)script-src不能使用unsafe-inline关键字(除非伴随一个nonce值),也不能允许设置data:URL。否则,可以被绕过.
(3)必须特别注意 JSONP 的回调函数

0x05 CSP实验

1 CSP测试

  • localhost.js
1
document.write("<h1>This is a local js.</h1>");
  • index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>

<head>
<meta charset="utf-8">
<title>内容安全策略 (CSP)</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.bootcss.com; child-src https:">
<script src="//cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
<script src="/localhost.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/weui/0.4.0/style/weui.min.css"/>
</head>

<body>
<img src="https://raw.githubusercontent.com/ashin9/MDPic/master/secblog/20180706110443.png" height="300" width="300">
<iframe src="http://ashin9.github.io" width="200" height="200"></iframe>
<iframe src="https://ashin9.github.io" width="200" height="200"></iframe>
</body>
</html>
  • 利用meta标签对页面开启CSP
1
2
3
4
5
6
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.bootcss.com; child-src https:">
脚本:只信任当前域名
插件:不信任任何URL,即不加载任何资源
样式表:只信任cdn.bootcss.com
框架(frame):必须使用HTTPS协议
其他资源:没有限制
  • 引用外部和当前域的js
1
2
<script src="//cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
<script src="/localhost.js"></script>
  • 引用外部的样式
1
<link rel="stylesheet" href="//cdn.bootcss.com/weui/0.4.0/style/weui.min.css"/>
  • HTTPS协议和HTTP协议的frame
1
2
<iframe src="http://ashin9.github.io" width="200" height="200"></iframe>
<iframe src="https://ashin9.github.io" width="200" height="200"></iframe>
  • 其他图片资源
1
<img src="https://raw.githubusercontent.com/ashin9/MDPic/master/secblog/20180706110443.png" height="300" width="300">

结果

  • 外部资源引用失败,控制台给出了报错信息
  • HTTP协议的frame加载失败,控制台给出了报错信息
  • 其他可以加载的img资源和本地的js脚本都加载成功

2 report-uri测试

  • 尽管report-to指令旨在取代已弃用的report-uri指令, report-uri已经从Web标准中删除,但在大多数浏览器中仍不受支持。因此,可以同时指定report-to和report-uri。

  • 在支持的浏览器中report-to,report-uri指令将被忽略。

  • HTTP头信息的Content-Security-Policy字段开启CSP

1
2
3
<?php
header("Content-Security-Policy: default-src 'none'; style-src cdn.example.com; report-uri /csp_report");
?>
  • 代码中利用的策略是限制外部非cdn.example.com域名下的样式表,并把报告发送到/csp_report目录。
1
2
3
4
5
6
7
8
9
10
11
<html>

<head>
<meta charset="utf-8">
<title>内容安全策略 (CSP)</title>
<link rel="stylesheet" href="//cdn.bootcss.com/weui/0.4.0/style/weui.min.css"/>
</head>

<body>
</body>
</html>
  • 在代码引用cdn.bootcss.com/weui/0.4.0/style/weui.min.css查看实验结果。

  • 由上图可以看到发送出了JSON格式的POST数据。

3 script-src特殊值测试

unsafe-inline
  • 当script-src设置为unsafe-inline时允许内联脚本和内联事件处理程序。
  • 内联脚本和内联事件处理程序就是在html中利用<script>标签加入的JS代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
header("Content-Security-Policy: script-src 'unsafe-inline';");
?>

<html>

<head>
<meta charset="utf-8">
<title>内容安全策略 (CSP)</title>
</head>
<body>
<script>
document.write("<h1>允许执行页面内嵌的标签和事件监听函数.</h1>");
</script>
<script src="/localhost.js"></script>
</body>
</html>
  • 在上面的代码中,引入了一个内联的js脚本和一个外置的js脚本,运行代码结果如下。
  • 内联的js脚本是可以执行的,但是外置的js脚本被警报了。

unsage-eval

将script-src值设置为unsafe-eval,这里还要开启内置事件的开关unsafe-inline才能执行内置事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
header("Content-Security-Policy: script-src 'unsafe-eval' 'unsafe-inline'");
?>

<html>

<head>
<meta charset="utf-8">
<title>内容安全策略 (CSP)</title>
</head>
<body>
<script>
eval('document.write("<h1>unsafe-eval测试.</h1>")');
</script>
</body>
</html>

  • 执行结果是可以看到利用eval函数将字符串当作代码执行了。
nonce值
  • header头设置了script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa’这里的nonce值对相应的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
header("Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'");
?>

<html>o

<head>
<meta charset="utf-8">
<title>内容安全策略 (CSP)</title>
</head>
<body>
<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
document.write("<h1>script nonce 测试.</h1>");
document.write("<h1>nonce=EDNnf03nceIOfn39fn3e9h3sdfa.</h1>");
</script>
</body>
</html>

  • script进行判断,如果script的nonce值与之相同,就可以执行相应的Js脚本。