Derek Fong , IT 架构师, IBM Canada
2005 年 12 月 29 日
Web 应用程序安全设计的目的是消除漏洞。仅仅基于用户凭证的已知安全设计元素(如验证和授权)可以很好地满足基本安全要求。不过,需要对收到的客户数据作进一步的审查,以便将安全边界从常用的设计元素扩展到应用程序代码。为了满足这一要求,我提供了一个新的安全设计框架,它保护了两类常见的漏洞:操作篡改和参数操纵(也称为 数据篡改)。
我将在表示层引入这个框架,负责对由浏览器发送的静态数据进行安全检查。这个框架还检测来自浏览器的操作事件,并为 Web 应用程序提供简单的浏览控制。
考虑恶意用户用代理工具劫持由浏览器发送的数据这一情况。后果可能是严重的,因为服务器会处理规定之外的数据。下面的场景更详细地展示了 Web 应用程序的漏洞。
Web 页浏览控制属于应用程序的访问策略。访问策略必须指定特定用户角色可能的浏览路径,以防止恶意用户浏览他或她无权浏览的某一页(例如,通过书签)。
与页浏览控制类似,当恶意用户试图在服务器将数据发回浏览器之前修改 HTML 内容时就会发生HTML 标记注入。HTML 标记篡改可能包含超链接、提交按钮或者其他表单标记。例如,如果一个 Web 页包含两个提交按钮,并根据特定条件只显示一个按钮,恶意用户可以使用代理工具在 Web 页显示在浏览器中之前在其中加上第二个按钮。这样用户就可以向他或她无权访问的内容提交操作。
参数操纵(即数据篡改)使恶意用户可以改变浏览器与 Web 应用服务器之间发送的数据。用户通常可以修改包含 URL 查询字符串和隐藏字段的参数。例如,如果 Web 页包含一个表示客户 ID 的隐藏字段,那么恶意用户就可以在浏览器将数据发送给服务器之前改变隐藏字段的值。
我在这里介绍的解决方案会处理 Web 应用程序漏洞,将对应用程序代码的影响降至最低,与应用程序代码松散耦合,而且可以根据将来的需求而扩展。
可以在以下情况下使用这个保护框架:
- Web 应用程序/Struts 框架要求简单的浏览控制。
- Web 应用程序需要确认用户操作事件(如返回和刷新按钮)。
- Web 应用程序要求保护页面静态数据中的漏洞,如操作、链接、按钮和隐藏字段。
为了防止恶意用户劫持 Web 页中的 HTML 静态内容,这个框架使用了服务器端验证技术。这个框架的程序模型如 图 1 所示,它描述了技术设计。
Processor
定义了保护建模为 ActionParams
的用户操作的操作。它也是处理客户请求的单元素对象。Processor
用 ParamsRepository
注册用户操作,并生成一个引用代码。它通过引用代码验证通过 http 请求发送的用户操作(Post、Get 和 Put)。
ParamsRepository
维护对所有 ActionParams
对象的引用。它提供了对在 http 会话中获取和存储 ActionParams
的基本访问操作,并提供了一个生成引用代码的机制。
ActionParams
定义了对所有所支持的查询操作和静态数据通用的接口。它实现了一个 validate
操作以验证查询操作和静态数据。可以扩展 ActionParams
以支持应用程序特有的需求。
FormActionParams
扩展了 ActionParams
以支持 HTML 表单操作和静态(隐藏)字段。它还覆盖了 validate
方法以支持表单操作验证。
本节描述客户机与框架交互以注册和验证用户操作的过程。
首先,客户机创建并向 Processor
传递 ActionParams
对象的一个实例。然后 Processor
和 ParamRepository
交互以存储 ActionParams
实例,并向客户机返回一个引用代码,如 图 2 所示。
然后 Processor
将特定 ActionParams
的引用代码从客户机转发给 ParamRepository
。Processor
与 ActionParams
实例交互以验证用户请求(请参阅 图 3)。
现在,我将讨论在实现这个框架时需要知道的设计细节。
为每个 ActionParams
生成的引用代码必须在 Web 页中是惟一的,并且应当可以在不同的页面间重复。不过,如果要扩展这个框架以支持双击检测(请参阅 其他扩展),那么引用代码必须在应用程序中是惟一的。
图 4 描绘了引用代码的生命周期。
来自浏览器的每一个 JSP 请求都会生成一组新的引用代码并将它们存储到 http 会话中。当用户向服务器提交请求时,一个引用代码会映射到会话中的 ActionParams
。当 Processor
完成了验证后,它会删除 http 会话中的所有代码。
这个框架不会向调用者抛出任何异常。可以根据应用程序的需要处理返回的错误代码。一般来说,应用程序应当在发现任何攻击时,中止用户会话并返回到欢迎/登录页。
ParamsRepository
用 http 会话存储受保护的信息。还可以加密受保护的数据并将它直接到存储到 Web 页中。向服务器提交数据时,也发送加密的数据。Processor
将解密数据并验证它是否是真正的参数。这种方式会减小会话的大小,不过,它使性能降低很多。
下面,我将描述需要保护 JSP 中静态内容的不同场景。然后展示这个保护框架如何在运行时防止漏洞。这些场景可以使您更好地理解这个框架。
场景 1:JavaServer Page scriptlet
为了定义所有超链接和表单隐藏字段的 JSP scriptlet,必须:
- 定义每一个
Processor
JSP scriptlet 的操作和参数为ActionParams
。 - 为每一个超链接定义一个以
ActionParams
为参数的Processor
JSP scriptlet,如 图 5 中的代码所示。
图 5. 保护超链接的 scriptlet - 为隐藏字段定义一个以
ActionParams
为参数的Processor
JSP scriptlet,如 图 6 中的代码所示。
图 6. 保护隐藏字段的 scriptlet
服务器装载 JSP 时,它执行这些 scriptlet 并调用 Processor
。Processor
将受保护的数据存储到 http 会话中的 ParamsRepository
中。然后 Processor
向 Web 页返回引用代码并显示为超链接参数或者隐藏参数。引用代码见 图 7 (红色部分)。
在这个场景中,用户单击 Web 页面中的 Submit,然后:
- 服务器上的处理程序(如 Struts 中的
RequestProcessor
或者ActionServlet
)调用Processor
。 - 然后
Processor
从 http 请求中提取引用代码并发送给ParamsRepository
。由于用户单击了 Submit 按钮,所以浏览器向服务器发送引用代码x2w3e
(请参阅 图 7)。 ParamsRepository
查找匹配的ActionParam
实例并将它返回给Processor
,如 图 8 所示。
图 8. 存储在 ParamsRepository 中的 ActionParam- 然后
Processor
用收到的ActionParams
验证用户提交的操作。这个场景的ActionParams
实例是一个FormActionParams
,并调用了FormActionParams
的validate
方法。由于发送给服务器的数据没有改变,因此Processor
将返回成功的代码,如 图 9 所示。
图 9. 用 ParamsRepository 中的 ActionParams 验证 FormActionParams
在这里,当用户单击 Web 页中的 Submit 时:
- 用户在浏览器将请求发送给服务器之前,将隐藏值从
12345
改为XYZ
。 - 重复 场景 2 中的第 2 步到第 4 步。
Processor
查明用户请求中的参数与ParamsRepository
中FormActionParams
实例中的不一样。Processor
返回错误代码。
如 图 10 所示,ActionParams
包含一个无效的参数 ID XYZ
,Processor
将其与 ParamsRespository
中的有效参数 ID 12345
进行比较。
图 10. Processor 验证收到的 ActionParams
这种验证防止用操作/参数操纵和 HTML 标记注入进行篡改,因为对操作/参数的任何修改都不会通过验证。
在这种场景中,当用户单击 Web 页上的 Submit 时:
- 用户在浏览器将请求发送给服务器之前,将隐藏字段标记中的引用代码值从
x2w3e
修改为hackValue
。 - 服务器端的处理程序(例如 Struts 中的
RequestProcessor
或者ActionServlet
)会调用Processor
。 Processor
从 http 请求中提取引用代码并发送给ParamsRepository
。由于用户修改了值,所以浏览器向服务器发送的引用代码是hackValue
。ParamsRepository
无法找到匹配的ActionParams
实例。- 如 图 11 所示,
Processor
无法在ParamsRepository
中找到收到的代码。
图 11. Processor 无法找到收到的引用代码 Processor
返回一个错误代码。- 由于
ParamsRepository
规定了特定 Web 页上可以进行的操作,所以这个保护框架会拒绝发送给它的所有不能识别的操作。这防止了 Web 浏览劫持(如书签)。
最后一种情况,当用户单击 Web 页中的 Submit 时:
- 用户在浏览器向服务器发送请求之前,删除了引用代码的隐藏字段。
- 服务器端的处理程序(例如 Struts 中的
RequestProcessor
或者ActionServlet
)调用Processor
。 Processor
确定从请求中不能提取代码并返回一个错误代码。
下面的代码展示了如何在 Java 中实现这个框架。
storeParams
方法将 ActionParams
存储到 http 会话里的一个映射中。代码参数标识特定的 ActionParams
实例。因为实现是与调用者隔离的,所以可以用其他类型的实现存储 ActionParams
,如 图 12 所示。
retrieveParams
方法根据引用代码返回一个 ActionParams
实例,如 图 13 所示。
validate
方法可以让 ActionParams
用另一个实例验证自身。它将操作和参数验证委派给不同的方法,如 图 14 所示。
FormActionParams
覆盖了 doValidateParams
方法以验证表单中的静态隐藏字段(请参阅 图 15)。
Processor
调用 verifyAction
方法以验证用户操作,如果验证失败,它就返回错误代码。表 1 描述了会让 Processor
发出错误代码的可能场景。
场景 | 错误代码 | 原因 |
---|---|---|
检查 http 请求中是否缺少引用代码。 | CODEMISSING_ERR | 用户向服务器发送未知请求。 |
用 verifyWithRepository 方法检查 ParamsRepository 中是否有代码。 | INVALIDCODE_ERR | 用户向服务器发送以前做过书签的请求。 |
检查用户是否篡改过 http 请求中的静态数据。 | DATATAMPERING_ERR | 用户修改了发送给服务器的数据。 |
verifyAction
方法可以用存储在信息库中的 ActionParams
验证用户操作(请参阅 图 16)。
可以容易地用 Struts 标记库集成这个保护框架,使这个框架的实现变为透明的。要在 Struts 中集成这个框架,必须继承以下 Struts 标记库。
为了保护表单和隐藏字段,要修改 Struts 的 FormTag
和 HiddenTag
以调用 Processor
。创建一个子类,它:
- 扩展 Struts
HiddenTag
类,收集所有隐藏字段参数并将它们存储到pageContext
属性中。 - 扩展 Struts
FormTag
类,覆盖doAfterBody
方法以向Processor
注册pageContext
属性和表单操作。返回的引用代码输出作为隐藏字段(请参阅 图 17)。
图 17. FormTag 的 doAfterBody 方法
为了保护链接,可以修改 Struts LinkTag
以调用 Processor
。 创建一个子类,它:
扩展 Struts LinkTag
类,覆盖 calculateURL
方法以填充 ActionParams
对象并向 Processor
注册。返回的引用代码附加到 URL 的最后,如 图 18 所示。
将保护框架集成到 Struts 后,只要在 JSP 中使用 Struts 标记库,就可以自动应用这个框架。
只要扩展 ActionParams
,这个框架就可以支持其他类型的保护。类似于 FormActionParams
,可以对 ActionParams
扩展下拉框保护,用一组有效的下拉值验证所选的值。通过维护以前处理的代码值,这个框架可以用同步的用户请求探测双击事件。如果收到具有相同代码值的请求,那么可以将以前的响应指定给这个请求。
本文描述了常见的 Web 应用程序攻击类型,并提供了一个解决这些问题的框架。这个框架覆盖了 Web 应用程序的逻辑安全方面,并保护了 Web 页中的静态数据。虽然这个框架可以减少受攻击的危险,但是设计者应当反复分析应用程序体系结构并找出所有可能的漏洞。这可让您增强或者修改这个框架以防止新的攻击形式。
学习
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
- Open Web Application Security Project (OWASP) :了解常见的 Web 应用程序漏洞。
- WebGoat :分析这个为讲解 Web 应用程序安全性而设计的 J2EE 应用程序。
- Michael Howard 和 David C. LeBlanc 所写的 Writing Secure Code, Second Edition :探讨安全对于互连的计算机(服务器、桌面个人计算机、手机、掌上设备等)的重要性。
- Web seminar on Application and Data Security :了解当今商业面临的安全问题和挑战,包括计算机窃贼们希望您永远也不知道的解决方案。
- Domino servers :避免数据库崩溃和安全问题。不要创建到文件服务器的映射目录链接或者共享的网络附属存储(Network Attached Storage,NAS)。
- Linux security solutions (developerWorks,2002 年 1 月):仔细阅读大量关于 Linux 系统的安全性问题的文档。
- developerWorks Java 技术专区 :寻找文章、教程和其他关于各种基于 Web 的解决方案的内容。
获得产品和技术
- Struts @ ApacheCon :介绍 Struts 框架。
讨论
- Cert.org :跟踪关于安全漏洞和策略的最新消息。
- BugTraq :在这个公共邮件列表中讨论漏洞和安全隐患。
- developerWorks blogs :参与 developerWorks 社区。