H5安全开发实践教程

978-7-115-55721-6
作者: 教育部教育管理信息中心
译者:
编辑: 罗芬

图书目录:

详情

本书为教育部信息化新核心课程(NCC)融媒体专业系列教材,从前端、通信及服务器端这3个方面对H5开发中的安全技术和技巧进行介绍。 本书共3篇,第1篇主要介绍在H5应用前端开发中对用户输入信息进行验证的方法;第2篇主要介绍H5应用通信方面的安全防护技术;第3篇主要介绍H5应用服务器端的安全防护技术,并结合两个实例对H5应用的安全防护进行整体介绍。 本书适合作为高校教材,供计算机、软件工程、信息安全、网络安全、通信工程、大数据等相关专业的师生阅读。此外,本书也适合从事开发工作的读者阅读,以提高其安全开发技术水平。

图书摘要

信息化新核心课程(NCC) 融媒体专业系列教材

H5

安全开发实践教程



教育部教育管理信息中心◎组编

赵宇◎编著




人 民 邮 电 出 版 社

北 京



定价:69.90元

读者服务热线:(010)81055410 印装质量热线:(010)81055316

反盗版热线:(010)81055315

广告经营许可证:京东市监广登字20170147号

内 容 提 要

信息化新核心课程系列教材编写指导委员会

信息化新核心课程融媒体专业系列教材专家组

出版说明

教育部教育管理信息中心

前言

编者

2021年3月

绪论

0.1 H5安全简介

0.1.1 什么是H5

1.融媒体

2.信息系统

图0-1 某企业的信息系统网站登录页面

扩展阅读

响应式网站:响应式网站区别于传统的以静态页面为主的网站,它可以自动适应不同终端设备的访问,方便用户阅读、导航浏览及进行控制操作,从而提高了用户体验。

B/S架构:这是互联网兴起后的一种网络访问架构。B/S架构将所有系统实现的核心功能全部集中到服务器上,将浏览器作为客户端供用户进行访问和操作。

0.1.2 H5面临的安全威胁

1.H5安全问题的成因

2.H5安全问题的主要表现

图0-2 用户登录网站A和网站B

图0-3 用户受到CSRF攻击

提示

XSS攻击和CSRF攻击都是跨站点攻击,它们的相同点是不攻击服务器,攻击的都是正常进行网站信息访问的用户。XSS攻击属于实现CSRF攻击的诸多途径之一。

0.1.3 H5开发中常见的安全问题

1.代码开发引入的安全问题

2.交互设计产生的安全问题

0.2 如何开发相对安全的H5应用

0.2.1 H5应用构架安全分析

0.2.2 H5应用设计和开发的安全原则

1.与权限管理有关的原则

2.与安全防护有关的原则

3.心理可承受程度原则

4.输入审核原则

5.输出过滤原则

0.2.3 H5安全开发中的代码调试与程序测试

1.代码调试

2.程序测试

提示

程序压力测试应当在实际应用环境下进行,否则程序压力测试无法得到真实的反馈信息,从而无法有的放矢地对程序进行优化处理或结构调整。

3.代码审计

➊ 针对所发现的代码安全漏洞做进一步分析;

➋ 结合技术和业务特性,给出安全漏洞等级评估建议。

➊ 针对等级较高的安全漏洞进行技术分析;

➋ 指导用户完成漏洞修复;

➌ 对漏洞修复结果进行再次扫描确认;

➍ 定制安全编程培训服务。

4.等级保护

0.2.4 H5代码编写规范

1.缩进排版

图0-4 某网站首页的部分代码1

图0-5 某系统首页的部分代码2

图0-6 专业编辑器的代码排版效果

2.区分字母的大小写

3.变量与文件的命名

注释

[1]框架:是软件框架的简称,软件框架是为了实现某个业界标准或完成某种特定基本应用功能的组件规范,同时也指为完成组件规范而提供的软件产品。

[2]漏洞:漏洞是由于计算机网络硬件、软件、协议中存在的设计或实践缺陷造成的。漏洞使得攻击者能够利用其在未授权的状态对系统进行访问、操作。

[3]跨站点脚本:本应缩写为CSS,但层叠样式表“Cascading Style Sheet”的缩写也为CSS,为示区别,故通常将其缩写成XSS。

[4]阻塞点:信息安全中的阻塞点是网络系统对外连接的通道,是监控连接的控制点。

[5]攻击面:软件环境中可能会被未授权用户(攻击者)输入或提取数据而受到攻击的点位。

[6]口令:口令即通常所说的登录密码,它相当于一把钥匙,在网络中通常用于用户登录。严格意义上的密码 是一种用来混淆信息的技术,通过这样的技术可以将正常的(可识别的)信息转变为无法轻易被识别的信息。

第1篇 献给雅典娜的木马——前端安全防护

希腊神话“特洛伊战争”中那只“献给雅典娜的木马”,对特洛伊人而言,是导致城毁人亡的“输入性错误”——隐藏在木马肚子中的希腊士兵半夜打开城门,与城外的希腊军队里应外合,将久攻不破的特洛伊城攻破。

同样地,H5应用的前端防护也必须重视对输入信息的严格审核,避免系统被别有用心者从前端“攻陷”。就当今信息的重要性而言,信息泄露的后果很有可能堪比“特洛伊灾难”。

第1章 H5输入安全

在前端页面中,用户的信息输入是否安全对整个H5应用的安全有很大的影响。在H5应用中,如果没有对用户输入的信息进行安全防护,H5应用就会像特洛伊城一样被别有用心者“攻陷”。特洛伊人如果对木马进行必要的检查,就会发现藏在木马中的希腊士兵,从而避免惨剧的发生。

本章主要介绍如何对H5前端页面内用户的信息输入进行安全验证,帮助读者了解关于信息输入的安全防护手段,并能够将其应用到程序开发中。本章主要内容如图1-1所示。

图1-1 本章主要内容

1.1 H5前端安全

H5前端的主要支撑技术是HTML5、CSS及JavaScript,因此对H5前端安全漏洞的防范也主要是针对HTML5、CSS及JavaScript展开的。

1.1.1 HTML5与安全

HTML5是在HTML 4的基础上“进化”而来的,虽然HTML5的发展经历了不少曲折,但对整个行业和应用开发者来说,它代表了未来几年所能够依赖的技术。无论是移动电话、游戏控制台、汽车仪表板,还是物联网设备等都用得上HTML5,因此其应用是非常广泛的。

作为H5应用框架的重要组成部分,HTML5在H5应用框架中的作用主要体现在网页的结构(Structure)上,其决定了前端网页的结构和内容。HTML5常见的安全问题主要包括以下两个方面。

1.浏览器对HTML5的支持所产生的安全问题

浏览器对HTML5的支持不是绝对安全的。如2013年3月,一位开发者就发现了HTML5的一个漏洞。这个漏洞是浏览器允许网站利用垃圾数据对客户端展开“轰炸”,甚至可在短时间内将硬盘塞满。当时Safari、Chrome、IE等多款主流浏览器均受到了此漏洞的影响。

2.HTML5设计开发中产生的安全问题

HTML5的代码虽然简单,但是在设计过程中如果存在设计缺陷或者未考虑适用的环境就会引发安全问题。

(1)存在设计缺陷

HTML5在安全方面的设计缺陷主要是指开发者在开发的过程中没有对相关的安全问题加以考虑或考虑不周,而引发安全问题。如果在设计过程中开发者没有考虑到对用户输入的信息进行特殊字符限制,或者没有对用户的输入信息进行转义处理,就可能产生易被XSS攻击的安全漏洞。

(2)未考虑适用的环境

在HTML5开发中应该考虑应用的适配环境——PC、平板电脑、手机等设备,如要考虑用户在不同设备所使用浏览器的默认设置等。用户在登录系统时经常会被问及是否要保存用户名、登录密码等信息,这其实是源自浏览器的表单自动填充功能:当用户点击“提交”按钮时,浏览器会捕捉并记录用户所填的信息,当用户再次浏览该网页时,浏览器就会自动为用户填充相应信息。此功能还可为用户记录下曾经查询过的关键词,使用户无须重新输入关键词就能直接进行之前做过的查询操作。这个功能很人性化,在网络中用各种搜索引擎进行电商物品查询时非常实用。但如果将此功能应用到用户的用户名、登录密码这些涉及用户账户安全的信息中,就会产生安全问题。如用户A在登录时将自己的用户名、登录密码保存到了浏览器中,那么稍后使用同一台计算机、同一个浏览器的用户B就可能在不知道用户A的登录密码的情况下,利用该浏览器的表单自动填充功能登录用户A的账户,从而出现非授权登录的安全隐患。

1.1.2 CSS与安全

层叠样式表(Cascading Style Sheets,CSS)是一种用来表现HTML或XML等文件的样式,是在HTML标准之外创造出的样式(Style),用来解决不同浏览器之间页面展示效果的问题。作为H5应用框架的重要组成部分,CSS决定了网页的表现样式,解决了网页“是什么样子”的问题。

在很多开发者的认知中,CSS就是设置样式的,所以对CSS没有过多安全方面的考虑。实际上,CSS不仅可以静态地修饰网页,还可以结合脚本代码对网页元素进行修饰。从本质上看,CSS在应用开发中的作用更接近于脚本,而且同脚本一样,其适用范围也覆盖了整个页面。目前,CSS的功能有删除、添加、修改页面信息,根据页面信息发起请求,响应多种用户交互等。虽然上述功能原则上不会引起安全漏洞,但不能因此就忽视有些CSS代码已经具有的修改本地存储和运行挖矿程序[1]等功能。

1.CSS并不安全

到目前为止,由于CSS危及安全的例子还不是很多,但由CSS引发的安全漏洞通常比较隐蔽。如曾经在Chrome、Firefox等浏览器中出现过一个旁路攻击[2]的CSS漏洞,它能够对跨来源框架的视觉内容进行泄露。出现这个漏洞的原因是2016年CSS3 Web标准中引入了名为“mix-blend-mode”的特性,它允许Web开发者将Web组件叠加在一起,并添加了控制混合效果。当用户访问存在该CSS漏洞的网站时,就会被别有用心者通过跨域iframe获取用户的信息,如用户名、照片等信息。别有用心者获取用户信息的整个过程无须与用户进行额外的互动。

还有一些CSS安全问题正在(有可能)形成,图1-2所示的是GitHub上的关于CSS Keylogger的介绍。这个插件可利用CSS属性收集器,在加载网页背景图像的时候,从外部服务器请求资源。把CSS Keylogger这个插件安装到Chrome浏览器上后,当用户打开一个使用了控制组件框架网站的网页时,点击浏览器窗口右上角的“C”图标,输入口令,Express服务器将会捕捉到用户所输入的口令。

图1-2 CSS Keylogger在GitHub的说明

从公开的资料看,CSS Keylogger目前只是以浏览器插件的形式存在,需要人工安装,其行为还处于用户控制之下。CSS Keylogger的出现引起了巨大反响,有人建议浏览器厂商修复漏洞,有人对其进行研究,但这都回避不了一个问题,即CSS正在变得不安全。

2.如何防范CSS的安全问题

对于CSS的安全问题,目前还没有相对成熟的防范方法,但在CSS引用时可以从技术方面加以防范。

(1)特别注意外部资源的请求

要仔细查看那些用于请求外部资源的代码,认真查看其请求的资源是否是安全的。特别要查看那些外部统一资源定位符(Uniform Resource Locator,URL)的请求,因为这些外部URL所指向的资源具有不确定性。CSS文件中引用的外部资源一旦被第三方删除,就会使当前页面的实现效果达不到预期需求;或者因CSS文件所引用的外部资源被替换,使得与CSS Keylogger类似的插件在用户毫无察觉的情况下被引入,从而出现安全问题。

(2)外部资源的本地化

为解决请求的外部资源不确定的问题,务必将CSS文件中请求的外部资源下载到本地服务器后再使用,而不是简单地直接指向第三方链接。不只是CSS文件,但凡涉及外部资源且不可控的JavaScript文件、图片文件都应当如此处理。

(3)规避不能本地化的外部资源

有些资源是难以避免外部请求的,如Google的font资源。如果要使安全保障得更好一些,最好尽量避免引入这些资源。没有Google的font资源,其结果仅仅是网页字体的显示效果差一些而已。

(4)慎重对待第三方CSS代码

开发者经常会使用第三方生成的CSS代码。这些第三方代码在投入使用之前,开发者应认真阅读其中的每一行代码,尽管因排版等原因使得代码的可阅读性可能很差。

(5)特别注意脚本的应用

要仔细查看源代码中那些包含脚本代码的部分,认真分析其所运行的脚本是否安全。

(6)精简代码

如有时间、精力,尽量删减CSS中不被引用的代码和内部注释信息。

1.1.3 JavaScript与安全

JavaScript与Java从字面上看很有渊源,但实际上,Java是Sun公司的编程语言,而JavaScript是Netscape公司和Sun公司联合推出的。从运行方式、定位及数据类型等方面来看,JavaScript和Java是两种完全不同的语言。JavaScript作为一种网络脚本语言,已经被广泛应用于H5应用的开发,解决了“做什么”的问题。

1.JavaScript的安全问题

JavaScript解决的是“做什么”的问题,因此JavaScript代码若出现安全问题,其危害性远比在HTML和CSS中所出现的安全问题大。JavaScript的安全问题主要体现在以下两个方面。

(1)JavaScript代码泄露问题

由于JavaScript代码可以被下载、解读,因此很容易被别有用心者加以利用,其在编写中经常会出现安全问题。

为防止JavaScript代码被别有用心者解读,使用代码混淆的方法对代码进行处理是目前比较好的解决办法之一。通过代码混淆,JavaScript代码如同被加密一样变得难以读懂,这就增加了解读代码的难度。严格来说,代码混淆只是让代码的可读性变差,起不到防止代码被下载、解读的作用,因此建议在对JavaScript代码进行混淆的同时,应将其中涉及敏感信息的代码放到服务器中运行。

扩展阅读

代码混淆是一种以别人看不懂为目的的技术手段。实际上,不仅JavaScript代码需要进行代码混淆,HTML代码和CSS代码同样需要进行代码混淆。需要指出的是,代码混淆无法从根本上解决安全问题,只是提高了代码解读的成本。此外,代码混淆会增加程序运行的开销,降低应用的运行效率。

(2)开发中的安全问题

JavaScript代码在开发设计中如果存在不严谨的地方,也会导致安全问题。JavaScript常见的安全问题包括前文提及的XSS攻击、CSRF攻击,还包括URL重定向、客户端JavaScript cookies引用及JavaScript劫持等。

在开发过程中,由于需要考虑跨浏览器兼容问题,以及如何满足AJAX更好的特性需求等,很多开发者在使用JavaScript开发时都会引入第三方的JavaScript代码库。这些被引入的代码库的成熟度一般较高,存在的安全漏洞相对较少,引入这些代码库也避免了开发者编写同样功能的代码时可能引入的安全问题。不可否认,由于这些代码库进行了代码压缩,致使代码的可读性较差,人工阅读、审核代码变得极为困难,使一些安全问题难以发现。因此在实际工作中,通常采用的方法是使用JavaScript自动检测工具进行检测,并通过人工进行核验和修复。

2.JavaScript在安全中的作用

JavaScript在H5的框架中主要负责控制网页的行为,如用于生成动态HTML页面,对浏览器事件做出响应,读/写HTML标签,识别客户端浏览器信息,在信息被提交到服务器前进行验证,对cookies进行创建、修改等操作,以及基于Node.js技术编程等。因此,考虑到安全防护、设计完善的JavaScript代码会对H5应用的安全防护起着重要的作用。

对前端防护而言,JavaScript代码可以对用户输入的信息实现有效核查,以确保输入信息的有效性、合规性。下面以代码1-1为例,介绍在用户登录页面中如何实现用户输入信息的验证,以确保用户输入的信息不能为空。

代码1-1的第24~40行是嵌入HTML文件的JavaScript代码,该代码实现的主要功能是判断所输入的用户名(Username)与密码(Password)是否为空。其实现机制为:当用户点击“submit”后,在用户输入的信息提交给服务器前判断信息是否为空。

在代码1-1中,document.getElementById(ID)语句的功能:获得网页中相应的ID信息后,通过字符串比较得到是否为空的判断结果,并进行相应的处理或反馈。如在用户名信息为空的情况下直接点击“submit”时,该用户登录页面就会弹出“Please input Username”(请输入用户名)的提示,如图1-3所示。

图1-3 请输入用户名的提示

此外,除了使用JavaScript代码来确保非空输入以外,在实际开发中其实还有很多非常好的解决方法,这里不赘述。

3.调试工具的应用

由于JavaScript在报错这方面做得不是很好,因此在编写Web页面代码的时候,熟练使用调试工具,对页面的修改有很大的帮助。比较好用的调试工具有Firefox浏览器中的Firebug。下面以代码1-1的调试为例,介绍Firefox浏览器中的调试器和控制台的应用。

➊ 启用浏览器,访问代码1-1编写的网页后,按快捷键F12,或通过“菜单”中的“Web开发者中的调试器”,打开图1-4所示的调试器。

➋ 浏览器的底部出现3个分栏,在左侧栏的“来源”中选择当前的页面,中间栏就会出现当前页面的源代码及相对应的行号。

➌ 双击图1-4所示中间栏的if(form.uid==null || form.uid.value=="")对应的行号28,在右侧栏的断点处会自动增加该行作为调试的断点。

图1-4 Firefox浏览器的调试器

➍ 如果希望监视变量或者表达式,则可在相应的位置添加相应的监视内容。

➎ 设置完毕后,在打开的这个网页的用户名和密码输入框中输入相应的信息,点击“提交查询”按钮,浏览器在运行JavaScript代码时会自动停在断点处让开发者进行观察、调试,直至问题解决。

此外,开发者可以通过控制台,直接对变量或者表达式进行查询验证,如图1-5所示。

图1-5 使用控制台进行代码调试

思考与提示

本书中非特意提到的浏览器均默认为Firefox浏览器。调试器是Firefox浏览器中Web开发者中的工具(Chrome浏览器中也有功能与之类似的调试器,打开其调试器的快捷键也是F12,读者可以根据需求进行选择)。需要说明的是,本书除了介绍浏览器的Web开发者之外,还会介绍一些其他的调试工具。

1.1.4 H5输入安全验证

在对漏洞进行分析后发现,大多数漏洞产生的原因在于H5应用中没有对输入的信息,特别是没有对用户输入的信息进行安全检查与验证。当这些信息作为参数被直接提交给系统后,就有可能产生安全问题。因此,在进行前端开发时,对信息进行验证将会有效减少诸如XSS、SQL注入等安全漏洞的产生。

对于输入信息,通常采取两种方式进行安全验证,即黑名单验证和白名单验证。

(1)黑名单验证

开发者先建立一个黑名单列表,如单引号、双引号、反斜线等字符作为非法输入字符,将这些字符放入一个名单列表中,完成黑名单列表的制作。接着进行黑名单验证,将输入的信息与黑名单中的信息进行对比,通过对比结果来判断输入的信息是否安全。

(2)白名单验证

白名单的概念与“黑名单”相对应,白名单中存放的是被允许执行的规则等信息。当用户输入的信息符合白名单的规则时,就会被允许通过。在用户输入的信息不可预知,但输入的信息又符合规则的情况下,往往采用白名单验证。

在开发过程中,通常会采取以白名单验证为主,结合黑名单验证的方式进行安全验证。在H5前端利用白名单、黑名单进行的安全验证,其主要实现路径如下:

➊ 利用input标签的各种属性进行安全验证;

➋ 利用form标签作为阻塞点进行安全验证;

➌ 利用用户键盘输入操作进行安全验证。

1.2 利用input标签属性的安全验证

input标签是HTML5的重要标签之一,主要用于信息的录入。由于H5应用的使用者(普通用户和别有用心者)的使用目的未知,使用者的操作熟练程度未知、使用者录入的信息不可知,因此,如果没有针对input标签的安全验证,就非常容易造成XSS漏洞、SQL注入漏洞、文件上传漏洞等安全问题。通常情况下,可通过JavaScript对input标签进行安全验证,或通过input标签的自身属性进行安全验证。在这两种方法中,通过input标签自带的属性进行安全验证更为简单和安全。因此,熟练掌握和巧妙应用input标签所具有的属性对用户输入信息的验证工作有着较大的帮助。input标签有很多常用属性,如表1-1所示。

表1-1 input标签常用属性

续表

1.2.1 利用autocomplete属性防止隐私泄露

在1.1.1小节中,就浏览器的表单自动填充功能进行过分析,指出了其中可能存在的问题。解决这个安全问题的方法有两种。

1.手动关闭浏览器的自动填充功能

访问时,手动关闭浏览器的自动填充功能后,所有用户信息都不能进行自动填充,因而这种方法在有效避免口令自动填充的安全隐患的同时,也给用户带来不能自动填充信息的不便。

2.通过程序提示浏览器关闭自动填充功能

在网页中通过代码主动向浏览器声明某些需要保密的表单不要被记录,这就可以保证用户享受自动填充便利的同时,也解决了用户隐私的安全问题。

在保证了诸如口令等用户隐私信息不被自动填充的前提下,第2种方法显然更为人性化、更合理,且亦具有良好的可操作性。实现该功能最简单的一种方法就是利用input标签中,HTML5标准下新增的autocomplete属性,如代码1-2所示。

在代码1-2中,涉及用户输入的部分为第9~11行:

<input type="text" name="uid" placeholder="username" />

<input type="password" name="pwd" placeholder="password" autocomplete="off" />

它们的差别在于name为“pwd”的input标签使用了autocomplete属性,且取值为“off ”,即声明不允许浏览器对此标签使用表单自动填充功能,以保证该标签中的历史信息不会被浏览器所记录。name为“uid”的input标签则没有类似的声明,即输入过的信息会被浏览器记录下来。如图1-6所示,“a.com”和“b.com”都是在浏览器中输入过的用户名信息,则这些信息会被当作该网页input标签的信息显示出来。

正确设置autocomplete属性可以将安全风险降低到可接受的程度。在这里需要说明的是,在使用input标签时,对于口令、身份证号、银行卡号,以及用户名、电话这些涉及个人隐私和财产安全的信息,应当将autocomplete取值为“off ”,对其他信息的input标签而言,将autocomplete取值为“on”,即表示允许浏览器使用自动填充功能,这会便于用户使用。

图1-6 表单自动填充功能的效果

思考与提示

图1-7所示为某运维审计系统的用户基本信息输入界面(系统本身是基于HTML 4标准开发的)。其页面上需要进行各种信息的输入,如果使用HTML5标准进行代码编写,每个input标签的autocomplete将如何进行设置?读者可以尝试编写input标签的相关代码。

图1-7 某运维审计系统的用户基本信息输入界面

1.2.2 利用required属性对输入信息进行非空核查

当用户进行信息提交的时候,如果不对用户输入的信息是否为空进行判断,在信息处理时可能会造成H5应用崩溃或信息泄露等。

input标签的required属性主要用于判断页面中当前input标签是否有输入信息。当required的值为“required”时,如果input标签中的信息为空,浏览器则自动向用户发出输入信息的提示。代码1-3就是实现这一功能的示例,其效果如图1-8所示,浏览器向用户发出了“请填写此字段”的提示。

图1-8 需要输入用户名的信息提示

在代码1-3中,判断输入的信息是否为空值的功能是由第15~18行代码通过required属性的设置来实现的,对比同样功能的JavaScript代码(见代码1-1中的第 24~40行)可以发现,在确保实现相同效果的情况下,应用HTML5的input标签属性能够有效减少代码数量,同时也间接地避免了应用JavaScript代码可能引入的安全隐患。

需要开发者注意的是,由于目前各浏览器对HTML5的支持并不统一,request属性没有被所有浏览器支持,如IE 8。如果有IE 8应用的场景,就必须采用JavaScript进行编写。这在使用其他HTML5新标签和标签的新属性时也应当加以考虑。有关浏览器兼容的问题,会在2.3.2小节中进行介绍。

1.2.3 利用type属性对输入信息进行验证

input标签中type属性的主要作用是设置input标签的输入类型。HTML5为type属性增加了一些新的属性值,新增的属性值主要用于判断输入的邮件、URL之类的单一属性数据的格式是否正确,因此熟练掌握这些属性值并加以运用,对提高代码效率、增强安全性是十分必要的。Input标签type属性值如表1-2所示。

表1-2 input标签type属性值

续表

很多H5应用会将邮箱地址作为用户名,因此在用户登录应用时就必须对邮箱地址进行验证,这一功能可以通过input标签的type属性来实现。代码1-4所示为通过设置input标签的type属性值为“email”,对用作用户名的邮箱地址进行格式验证。

在代码1-4中,第15~17行代码的功能就是利用属性值为“email”的type属性对input标签中的用户名进行验证。通过“email”这个type属性值进行白名单验证,能够有效判断邮箱地址的格式是否正确。如图1-9所示,当输入“a.com”后,浏览器通过对邮箱地址的格式进行检查,向用户发出“请输入电子邮件地址。”的提示。

此外,该属性值还可以利用黑名单,对有可能引起XSS注入的字符发出“请输入电子邮件地址。”的提示,防止XSS注入的发生。

图1-9 用户登录页面

思考与提示

input标签的type属性虽然有一定的安全验证功能,但仅凭type属性进行验证,容易造成误判或疏漏。如针对type属性值为“email”的input标签,虽然输入“a.com”“@ com”“<a@com”“a@com&”都会被视为非法邮箱地址,但如果输入“a@com”就可以通过验证。这也是很多开发者仍倾向于使用JavaScript进行验证的原因。

1.2.4 利用正则表达式对输入信息进行验证

一般利用type属性值对输入信息进行验证,但由于type属性值无法对各种类型的输入信息进行验证,因此需要利用正则表达式对可以被规范化的输入信息进行验证。

正则表达式是用于描述搜索模式的特殊文本字符串,正则表达式可以被视为某种通配符,正则表达式常被用来验证、检索、替换一些符合某个模式(通配规则)的文本。如^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$就是正则表达式。在正则表达式中,包含下面两种字符。

1.普通字符

在正则表达式中,仅能够描述自身的字符被称为普通字符,如所有的字母和数字。换言之,普通字符只能够匹配字符串中与其相同的字符。

2.特殊字符

在正则表达式中,有些字符被规定不按照字符自身的值进行匹配,而具有特殊的语义。如“.”被规定可以匹配任意的单个字符,而不是仅仅匹配“.”。这些字符被称为特殊字符,也称元字符。

特殊字符使正则表达式具有了验证、检索、替换的功能。正则表达式中常用的特殊字符如表1-3所示。

表1-3 正则表达式中常用的特殊字符

续表

思考与提示

思考在浏览器的控制台中输入下面的代码,会有什么样的结果?

在Firefox浏览器的控制台中输入:

var str='a b c';//使用空格切割字符串

alert(str.split(' '));//()方法

var reg=/\S/g;//使用任何非空白字符查询

alert(str.match(reg));

var reg=/[\w]/g; //使用字母、数字、下划线进行匹配查询

alert(str.match(reg));

常用的正则表达式如表1-4所示。

表1-4 常用的正则表达式

图1-10所示为一个用户注册的示例页面,其实现代码如代码1-5所示。在页面中提示为“telephone”的input标签设置手机号码的输入规则。手机号码虽然是由11位数字组成的,但由于13×、18×、14×、15×、17×等各个号段之间并不是连续的,因此还需要利用正则表达式进行验证。

图1-10 代码1-5编写的页面的效果展示

代码1-5中第22~25行代码的功能就是利用正则表达式对用户输入的手机号码进行验证。

需要说明的是,name为“number”的input标签,其中的type属性值设置为“text”“number”和“tel”时,均可以通过正则表达式对手机号码的输入进行验证,但实际效果都有所欠缺。

如果type值设置为“text”,用户在移动设备输入手机号码时,系统将自动弹出英文键盘,而不是预期的数字键盘,需要用户自己将英文键盘切换到数字键盘,就会使用户体验不佳。

如果type值设置为“number”,用户在移动设备输入手机号码时,系统虽然会自动弹出数字键盘,但是对手机号码的验证还需要相应的JavaScript代码,以便对输入信息中的小数点进行判断处理。此外,在iOS操作系统中弹出的不是九宫格形式的数字键盘。所以,这也不是一个好的解决方法。

如果type值设置为“tel”,用户在移动设备输入手机号码时,系统将自动弹出带字母的九宫格键盘,视觉上远不如纯数字的九宫格键盘让用户感到舒服。

权衡上述3种取值的结果,本示例最后还是将input标签的type属性值设置为“tel”。

代码1-5中还运用了input标签的min、max及patten等多个属性。这些属性同样可以对输入信息进行验证,利用这些验证功能能够实现:name对“uid”的input标签的输入进行非空核查;name对“email”的input标签中输入的邮箱地址的格式进行核查;name对“age”的input标签中输入3~99岁的年龄进行核查。由此可以看出,利用input标签的type属性,可以使验证变得更加简单、高效,提高了网页的安全性。

思考与提示

这里需要提醒读者的是,从另一个角度来看代码1-5,会发现它不是非常安全,原因在于:代码1-5虽然突出了name为“tel”的input标签的安全验证,但对name为“uid”的input标签仅使用了代码<input type= "text" name= "uid" placeholder="username" autocomplete="off" required ="required" autofocus="on" />,而没有对XSS进行安全防护,容易形成安全漏洞。

举这个例子的目的是提示读者:在特别关注某一安全问题的时候,不要放松对其他安全问题的防护,要从多个角度,系统地考虑代码的安全性。

1.2.5 利用file属性对文件上传进行防护

文件上传是网页中最为常见的功能需求之一,如果开发者在开发过程中因对用户文件上传部分的控制力不足或处理存在缺陷,就可能会产生文件上传漏洞,引发文件上传的安全问题。文件上传漏洞是指别有用心者利用文件上传功能将可执行文件上传到服务器上并加以执行,从而获得网站非法控制权等的行为。

代码1-6利用input标签的file属性来实现文件上传,开发者在代码中没有对文件上传中的安全进行考虑,因此会出现如下安全问题。

1.没有对上传文件的类型进行控制

由于没有对上传文件的类型进行控制,使得如.sh、.exe、.php、.asp、.js等可执行文件被上传到服务器,因此可能引发非法控制服务器的情况出现。

2.没有对上传文件的大小进行控制

由于没有对上传文件的大小进行控制,使上传大文件成为可能,因此可能出现因无意或者恶意上传大文件,而造成服务器瘫痪的情况。

若要修复代码1-6中存在的安全漏洞,就要在实现文件上传功能时,对上传文件的类型和上传文件的大小进行限制。代码1-7在一定程度上解决了这两个方面的问题。

代码1-7对文件上传功能进行了以下安全防护。

代码1-7第14~16行代码利用了input标签的accept属性对上传文件的类型选择进行了约束,即只能上传.jpeg文件,无法上传可执行文件。

需要指出的是,代码1-7中对文件类型的约束仅仅是初步的。在运行代码1-7的过程中,网页上传的文件是依据文件扩展名,利用openfiledailog来进行选择,其无法判别上传文件的类型与文件内容是否匹配。如果用户在图1-11所示的文件类型下拉列表中选择“所有文件”,那么文件类型的约束就会失效。

在<input id="userfile" type="file" name="userfile" accept="image/ jpeg" onchange="checkSize()"/>中,onchange在上传前的这个阻塞点调用了JavaScript脚本对文件大小进行了约束:当确认有文件被选中后,利用代码document. getElementById("userfile").files[0].size;获得图片大小;通过代码if(imagSize<1024*1024*3){alert(“图片大小合格”);return true;对文件是否超过规定大小进行判断,如果图片大小不合格就会出现图1-12所示的提示。

图1-11 文件打开对话框

图1-12 图片大小不合格的提示

此外,代码1-7使用了外部CSS,为文件上传的表单装饰了一个边框,相关的CSS代码如代码1-8所示。

文件上传漏洞覆盖了前端、服务器等方面,代码1-7只是进行了初步的防护,远不能满足安全防护的需求。比较完整的文件上传漏洞防护示例将在6.2节中进行介绍。

1.3 利用form标签作为阻塞点进行安全防护

input标签负责用户输入信息的收集工作,form标签则负责将input标签收集到的信息提交给服务器。正是将用户输入的信息提交给服务器的这个动作,才形成了安全上的一个阻塞点。开发者可以利用form标签的这个阻塞点对信息的安全性、合规性进行验证。如果放弃这个阻塞点,就有可能让“藏有敌人的木马进入特洛伊城”。

form通常有3个基本组成部分:form标签,包含处理form表单信息所要使用的URL和将信息提交到服务器的方法;标签域,包含input标签等;按钮,包括submit按钮等。form标签的表现形式通常如下所示。

form标签的常用属性如表1-5所示。

表1-5 form标签的常用属性

通常,form标签的处理是通过表单内部的<input type="submit"/>将信息提交到服务器。在form标签进行信息传输之前,要特别注意检查包含在form标签输入域内部的输入信息是否符合要求,否则就有可能出现安全漏洞。

在代码1-7中,第11~18行代码是form标签的标准使用方式,其中,<form id="form1" name="form1" action="" method="get" enctype="multipart/ form-data">规定了form标签的id、name,以及服务器上服务的名称和数据提交的方式。只有当文件被加载的时候,才会激活代码<input id="userfile" type="file" name="userfile" accept="image/jpeg" onchange="checkSize()" />。

代码1-9是对代码1-7中的核心代码(第11~18行代码)的改进,其在代码1-7中的第11行语句<form id="form1" name="form1" action="" method="get" enctype="multipart/form-data" >的基础上,增加了代码onsubmit= "return checkSize(),取消了代码1-7中第14~15行代码<input id="userfile" type=" file" name="userfile" accept="image/jpeg" onchange="checkSize()" />的onchange="checkSize()。其作用在于,取消代码1-7中读取文件后的阻塞点,将对文件大小的检查迁移到了form标签进行信息提交前的阻塞点。经过验证,用form标签信息提交前的阻塞点进行信息验证同样可以满足功能需求。

1.4 利用键盘输入操作进行安全防护

用户的信息是需要通过键盘输入来实现的。因此除了利用input标签的属性和form标签提交前的阻塞点对输入信息进行验证之外,对键盘输入的信息进行监控也是一个对用户所输入的信息进行验证的好方法。

1.4.1 键盘事件简介

键盘操作中最常需要的功能就是辨识输入键的种类和识别键盘是否被触发。键盘上的按键可以分为两类,一类是包括26个大小写英文字符和数字在内的ASCII值范围内的按键,另一类是键盘上的功能键。键盘操作包括3类事件:

➊ onkeydown,在用户按下任何键盘按键时触发;

➋ onkeypress,在用户按下并放开任何字母键、数字键时触发,但无法识别系统按钮(如方向键和功能键);

➌ onkeyup,当用户释放任何先前按下的键盘按键时触发。

三者的触发顺序:onkeydown最先执行,其次是onkeypress,最后是onkeyup。其中,onkeydown 和onkeypress会影响onkeyup的执行。

代码1-10是键盘事件处理的示例,其功能主要是说明onkeydown、onkeypress、onkeyup在用户输入过程中的响应。

在代码1-10中,当text1输入字符串后会产生以下连锁反应:

➊ 通过onkeypress响应(阻塞点1)在text2内同步显示text1的信息,以及在num2中显示text1的字符串长度;

➋ 通过onkeydown响应(阻塞点2)在text3内同步显示text1的信息,以及在num3中显示text1的字符串长度;

➌ 通过onkeyup响应(阻塞点3)在text4内同步显示text1的信息,以及在num4中显示text1的字符串长度。

在代码1-10中,num5会产生2个关于键盘操作的阻塞点:onkeypress(阻塞点4)用来判别当前输入的是否为数字,而onblur(阻塞点5)则是当num失去焦点后被激活。

此外,当text5输入字符后,通过oninput(阻塞点6)所指向的JavaScript代码,使得text7中同步text5的信息。而text6的信息发生改变后会激活onchange(阻塞点7),会在text7中同步text6的信息,其显示如图1-13所示。

从代码1-10的onkeypress、onkeydown、onkeyup可以看出,当用户进行信息输入,确切说是按下按键、按住按键、松开按键的时候,都是对用户输入信息是否为空、用户输入信息是否包含敏感字符、用户输入信息是否符合预期要求等进行判断的阻塞点。这些阻塞点对前端的安全防护非常重要。

图1-13 代码1-10的页面展示

此外,代码1-10中使用了许多JavaScript函数,具体的函数及其作用如表1-6所示。

表1-6 代码1-10 中使用的JavaScript函数及其作用

1.4.2 键盘输入安全验证

对用户输入信息进行安全验证时,使用键盘输入进行验证的优点主要体现在以下几个方面。

1.实时性强

无论是通过input标签的属性验证,还是利用form标签作为阻塞点验证,其本质都是利用提交用户信息之前的阻塞点作为验证的触发点。而键盘输入安全验证则完全依赖于键盘(含软键盘)操作进行触发,这样就会在用户输入信息的同时进行验证,具有很强的实时性。

2.效率高

由于键盘输入安全验证采用的是实时验证,因此同一时间内只能有一个标签进行验证。对多项输入信息的页面而言,将验证时间分散到多个标签中,可提高验证效率。

3.利用JavaScript代码验证

与采用input标签的type属性进行验证的方式不同,键盘输入信息验证通常是利用JavaScript来完成的,因此开发者能够通过键盘输入操作对输入的信息进行实时控制。

1.5 输入安全防护实例

前文所涉及的示例大多数是基于单一标签属性所进行的安全防护,但在实际生产环境中,情况会因需求而变得复杂,需要更多的防护来保证H5应用的安全。

1.5.1 允许多种类信息输入的防护实例

在生产环境中,为方便用户,应用往往会提供普通字符串、邮箱地址、手机号码等不同选项供用户作为用户名认证选项,用户只需选择其中的任意一项作为用户名认证选项即可,图1-14所示的登录页面就是如此。有些应用的登录还采用了如微信、微博等第三方认证登录的方式作为用户名认证选项,如图1-15所示。

图1-14 中国铁路12306登录页面(局部)

图1-14提供的登录方式,用户只能选择其一进行登录,即用户无法既选择“邮箱”作为用户名登录,又选择“手机号”作为用户名登录,这是因为开发者无法使用input标签的type属性对两类不同属性的值同时进行验证。也就是说,当用户使用input标签的type属性值为email时,可以验证邮箱地址,但无法验证手机号码;而当用户使用input标签的type属性值为tel或者number时,可以验证手机号码,但无法验证邮箱地址。如果用户要使用input标签type属性值为text,并利用pattern正则表达式进行验证时,所需设计的正则表达式会相当复杂,其结果是不仅要面对未来运维中会出现的各种问题,而且维护也相当困难。因此,建议使用JavaScript对输入的信息进行验证,如代码1-11所示。

图1-15 搜狐网登录页面(局部)

代码1-11应用了form标签的一个事件:onsubmit事件。当表单中的“确认”按钮被点击后将触发这个事件,调用JavaScript代码中的function toVaild(),使input标签中输入的信息在提交到服务器之前就可以在客户端被预先处理。

JavaScript代码中的function toVaild()(代码1-11中的第26~41行)定义了3个正则表达式。用于邮箱地址验证的正则表达式如下所示。

用于手机号码验证的正则表达式如下所示。

r e g e x _ p h o n e =/ ^ ( 1 3 [ 0 - 9 ] | 1 4 [ 5 | 7 ] | 1 5 [ 0 | 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/;

用于XSS字符串验证的正则表达式如下所示。

regex_xss=/^[^%\*\^~\'\"\/\\\<\>\|]+$/;

通过regex_mail.test(uid)、regex_phone.test(uid)及regex_xss.test(uid)等JavaScript代码进行验证,在实现对邮箱地址和手机号码进行验证的同时,防止了XSS漏洞的出现。

思考与提示

如果示例中登录的用户名规则改为“可以采用邮箱地址、手机号码、用户名当中的任意一种的格式”,那么将如何对用户输入的信息进行审核?请修改代码1-11。

1.5.2 双因子认证防护实例

双因子认证(Two-Factor Authentication,2FA)是指结合口令和实物(信用卡、SMS手机、令牌或指纹等生物标志)两种因子对用户进行认证的方法。简单地说就是除了口令,还要用另一种非口令形式的验证因子来确认使用者的身份,如短信验证、邮件验证、生物识别等。其优点在于:别有用心者无法直接通过用户名对其中的口令进行暴力破解。

代码1-11虽然对用户输入的用户名进行了验证与防护,但如果有人想要恶意破解用户认证,采用枚举的方式就可以破解。如先设定一组6位的数字密码,然后通过枚举类似银行卡卡号的方式进行破解。这种恶意破解所造成的后果是极为严重的,因此在考虑用户名认证方式的时候,可以考虑采用组合鉴别的方式认证。

此外,验证码也是一种重要的验证因子,即Completely Automated Public Turing test to tell Computers and Humans Apart。验证码通常是随机生成的。当用户访问登录页面或者点击随机码图形时,系统都会产生一个新的验证码,供用户填写。系统能够借此区分用户是手动登录系统,还是使用脚本登录系统,在一定程度上能防止有人通过自动脚本进行恶意破解,包括防止诸如恶意破解口令、“刷票”“论坛灌水”等行为。防范脚本自动破解登录认证的手段有多种,比较常见的防范方法有:快速拼图验证法,登录页面如图1-16所示;物品选择验证法,登录页面如图1-17所示;扫描二维码验证法,登录页面如图1-18所示;手机验证码验证法,登录页面如图1-19所示;验证码验证法,登录页面如图1-20所示。

图1-16 快速拼图验证登录页面

图1-17 物品选择验证登录页面

图1-18 扫描二维码验证登录页面

图1-19 手机验证码验证登录页面

图1-20 验证码验证登录页面

生成随机验证码的程序veryfication.php是用PHP编写的,并存放在服务器中,具体代码如代码1-12所示。开发者在前端的网页中可以采用代码<img class="formcontrol span4" id="code" name="code" src="veryfication.php" onclick="create_code()" title="点击刷新" onclick=”change_rand();"/>从服务器调用veryfication.php,并通过对验证码的验证来阻止别有用心者利用脚本对应用中的用户名和密码进行暴力破解。

对代码1-12内的关键代码的相关解释如下。

➊ function show_code_jpg传递的参数及其作用分别为:$num用于传递需要几位验证码;$w用于传递图形的宽度;$h用于传递图形的高度;$r$g$b用于传递颜色。

➋ $_SESSION['yzm_code']=$code;的作用是将验证码写入session。

➌ header ("Content-type: image/PNG”")的作用是输出PNG格式的验证码图片。

➍ imagesetpixel ($im, rand(0, $w), rand (0, $h), $black)的作用是为验证码图片增加干扰点。

➎ imageline($im, 0, $y1, $w, $y3, IMG_COLOR_STYLED)的作用是为验证码图片增加干扰线。

➏ imagedestroy($im)的作用是销毁验证码图片。

思考与提示

生成随机数字验证码的函数完全可以用JavaScrip进行编写,代码1-12用PHP编写的目的在于提示读者:H5不仅包含了HTML5、CSS、JavaScrip,还融入了包括PHP在内的很多技术,其已经实现了将不同的技术按照需求(对公司和创业者而言,技术并不是他们首要关注的因素,他们经常会优先考虑成本和需求等)进行融合,而不是仅局限于“HCJ”的组合。在一定程度上,验证码的确可以降低自动脚本对用户认证破解的风险,但如果被别有用心者发现验证码的获取可以通过脚本实现,就会使验证码失去对脚本自动破解认证的防范作用,验证将形同虚设。

图1-21所示的是某品牌早期安全产品的登录页面,实现代码如代码1-13所示。

通过对登录页面代码(代码1-13)的解读可发现,其中有一段代码如下。

document.getElementById("APCPic").src="getImg?ID="+response;

该段代码的功能是为页面生成随机码图形,并将这个随机码图形以数字的形式返回给页面。如果用户在Chrome浏览器中按快捷键F12对登录页面进行调试跟踪,就可以清晰地看到页面是如何以字符串形式获得验证码(附加码)的,如图1-22所示(注:漏洞曝光后已经得到了公司的有效修复)。

图1-21 某品牌早期安全产品的登录页面

图1-22 查看页面是如何获得验证码(附加码)的

思考与提示

之所以在前文介绍Firefox浏览器的调试器,而在这里使用Chrome浏览器的调试器,是因为希望读者能够熟悉每一款浏览器,并能够使用浏览器调试工具进行调试。因为H5在不同浏览器中所表现出的结果不一定是相同的,开发者进行应用开发时应该有能力将应用与每一款流行的浏览器进行适配。

随着光学字符识别(Optical Character Recognition,OCR)技术的发展和大数据的应用,过去由于干扰处理、字符变形等原因无法通过非人工手段识别的验证码能够被自动识别出来,导致自动脚本被恶意破解成了需要解决的、很重要的安全问题。目前,字符、数字随机图形化验证码的方法已经不是一个相对安全的验证方法,因此越来越多的应用采取了邮件验证、语音验证、短信验证,以及其他多种验证方法进行验证。

思考与提示

认真、深入地读代码,认真调试代码是避免发生安全问题的好办法。安全绝不是一蹴而就、一劳永逸的,以往被视为安全的技术,也可能变成安全隐患。

相关图书

TypeScript全栈开发
TypeScript全栈开发
Java EE企业级应用开发实战(Spring Boot+Vue+Element)
Java EE企业级应用开发实战(Spring Boot+Vue+Element)
Vue.js全平台前端实战
Vue.js全平台前端实战
Flutter内核源码剖析
Flutter内核源码剖析
智能前端技术与实践
智能前端技术与实践
从0到1:ES6快速上手
从0到1:ES6快速上手

相关文章

相关课程