威胁模型
这是根据我网站上的快速参考页松散地讨论数据库和Webapp安全的问题。 该页面变得笨拙,并且使读者无法轻松地与我或其他人进行交互。
威胁模型
所有安全分析都必须从检查威胁模型开始。 威胁模型要求您回答四个问题:
- 我要保护的是什么?
- 从谁?
- 多长时间?
- 以及(净)成本是多少?
我要保护什么?
这是显而易见的起点……您的第一个答案可能是错误的! 我的意思是,您可能会回答“数据库密码”,但这并不完全正确。 您实际上要保护的是以该用户身份访问数据库–攻击者可能无需密码即可进入数据库,例如SQL注入。
但是,等等,那也不完全正确! 我们真正关心的是阻止攻击者使用该访问权造成损害,学习敏感信息等。 在这一点上,我们应该列举我们的实际关注点,例如,我们的数据库可能包含
- 用户内容
- 财务信息
- 用户认证和授权
- 日志
- 静态内容
我们访问和使用此信息的方式各不相同
- 用户内容– 需要持续的读/写访问权限
- 财务信息– 需要预言(用于批准),并且可以将详细信息留给履行流程
- 用户身份验证和授权– 用户登录时需要一个oracle(用于批准和授权),但以后再不需要(oracle)
- 日志– 需要正在进行的仅追加访问(oracle)
- 静态内容– 启动时需要只读访问权限(oracle)
(所有访问权限都以维护需求为模。)
oracle是一个独立方法,它采用(可选)值并返回true或false 。 一般而言,它可以返回任何独立的,不变的对象。 oracle的一个好的实现选择是数据库中的存储过程,一个更好的选择是使用独立数据库对另一个webapp进行REST调用。
甲骨文的两个例子:
用户身份验证:使用一个使用用户名和密码并返回一个布尔值的oracle值,该值指示其是否有效。 (或者:成功后返回完整的authn / authz结构。)非oracle方法是使应用程序对用户和密码表进行查询,并比较密码本身。
信用卡身份验证:使用一个预告片,该预告片将获取信用卡信息和购买金额并返回确认号或错误指示。 该应用程序可以依靠oracle保留以前提供的值的副本(而不是CVV!),因此用户不必每次都填写相同的信息。 。 非Oracle方法是应用程序将信息本身捆绑在一起。
我在这里提出的观点是,确定需要保护的内容是一个体系结构问题,而一些远见卓识会对威胁模型产生巨大影响。 您希望尽可能少地接触不受信任的用户(例如,Web应用程序),而小的更改可能会带来很大的不同。
最后但并非最不重要的一点是,应该保护另一件事:您的声誉。 不是公司的-开发人员的。 当您接到公司总裁打来的电话,要求知道为什么公司将成为夜间新闻的主角时,您怎么说? 您无法防御所有攻击,但是当有人要求知道为什么您没有采取基本步骤来保护系统时,您就不会无言以对。
从谁?
大家。
好吧,我开玩笑。 但是还有比您最初想的要广泛得多的列表。
- 员工流于糊涂。 我们都做到了。 他们已经具有合法访问权限。
- 心怀不满的员工,尤其是即将离职的员工。 他们已经拥有合法的使用权和动力。
- 脚本小子。 我们倾向于认为它们并不复杂,但是它们可能正在运行由专家编写的破解工具。 如果您的网站相当安全,他们可能会转向更轻松的目标。
- 高级持续威胁(ATP)。 这些人有很强的动力和强大的技术技能。 假设他们会进来。
该列表并非详尽无遗,列出其他“潜在攻击者”作为练习是留给读者的。
多长时间?
冒着被暴露的风险,存在三大类
- 必须保护直到相对不久的特定日期的信息或访问权,然后才是公开知识,例如公司财务报告。
- 随时间推移价值下降的信息或访问。
- 必须永远受到保护的信息或访问,例如机密的法律和医疗文件。
第一类很简单,因为已知最知名的算法和攻击,并且攻击者的工作时间有限。
最后一类很困难,因为我们知道无法预测未来的攻击。 10年前不可能实现的某些事情现在已经行之有效。 一个很好的建议:我们不保留的东西就是我们不需要保护的东西。 保持尽可能少但不要少。
对该博客条目的范围进行全面分析,但这是一个不容忽视的重要问题。
净价是多少?
“成本”是一个灵活的概念,因为存在许多间接成本和推断成本。 例如,使人们更难以完成工作需要付出什么代价……或者由于系统在漏洞发生后的几天内不会宕机,它会更便宜吗? 人们沮丧地离开站点的成本是多少,而当您的站点遭到破坏成为全国新闻之后人们没有集体离开站点的收益又是什么呢?
最重要的是,这最终是一个非技术性的问题。 您所能做的就是确定直接和间接的问题,并让将要做出的决定权得到最终确定。
把它放在一起
最重要的是,威胁模型最终是业务决策。 我们可以提供分析和建议,但最终决定必须来自于上级。
也就是说,我们可以主动采取许多行动。 讨论将解决其中一些问题。
SQL注入
什么是SQL注入?
SQL注入是攻击者将任意SQL命令插入系统的能力。
样本攻击
看下面的代码:
ResultSet rs = stmt.execute('select * from users where username='' + username +'' and password='' + password + ''');
可能出什么问题了? 假设我们使用以下值:
String username = 'bob' or 1=1; --';
String password = 'dont care';
当我们调用较早的代码时,生成的代码是
select * from users where username='bob' or 1=1; --'and password='dont care'
这将列出所有用户。 一些Web框架将列出系统中的所有用户。 如果返回了多个记录,则编写更仔细的应用程序将发出警报。 这很容易修复
String username = 'bob' or 1=1 order by userid limit 1; --';
String password = 'dont care';
生产
select * from users where username='bob' or 1=1order by userid limit 1; --' and password='dont care'
“ order by”节确保我们看到系统中的第一个用户。 通常是管理员–攻击者不会忘记的事情。
错误的方法
许多没有经验的程序员试图通过显式清理用户提供的输入来解决此问题。
ResultSet rs = stmt.execute('select * from users where username='' +username.replaceAll(''', '''') +'' and password='' + password.replaceAll(''', '''') + ''');
这可能在1980年代奏效,但当今世界使用的字符数超过了ASCII。 正确地识别引号字符是不平凡的问题,应留给其他人解决。 JDBC编写器通常为此使用特定于数据库的方法,但是它们可能与数据库不同步,并且当然是特定于数据库的。
准备好的声明和占位符
解决此问题的标准方法是使用准备好的语句和占位符。 这代替了代码
ResultSet rs = stmt.execute('select * from users where username='' +username + '' and password='' + password + ''');
与
PreparedStatment stmt = conn.prepareStatement('select * from users where username=? and password=? limit 1');
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.execute()
局限性
有时候,准备好的语句是不合适的。 一个常见的示例是多插入语句。 这些可以比多个准备好的语句调用快得多。
多插入语句的一个示例是
insert into squares(x, y)values (1, 1),(2, 4),(3, 9),(4, 16),(5, 25);
通常,不应将其与用户提供的数据一起使用。 如果绝对需要,请使用JDBC提供程序提供的特定于数据库的方法,而不是滚动自己的解决方案。
成本效益分析
使用准备好的语句占位符的成本/收益分析是无关紧要的,这只是您要做的事情之一。
存储过程中的SQL注入
什么是存储过程和CallableStatements?
存储过程是数据库中保留的部分代码。 最常见的形式是类似SQL的脚本语言,但支持其他语言-PERL,tcl,ruby,java等。
重要的是要记住,数据库触发器中使用了存储过程–即使您使用休眠状态进行所有工作,也应注意它们。
错误的方法
错误的方法是创建动态SQL查询而不进行清理。
DELIMITER $$
DROP PROCEDURE IF EXISTS SP_AUTHENTICATE$$
CREATE PROCEDURE SP_AUTHENTICATE(IN username VARCHAR(20),IN password VARCHAR(20),OUT success INT)
BEGINSET @query = CONCAT('SELECT COUNT(credentials.username) INTO @succFROM credentialsWHERE credentials.username = \'', username,'\' AND credentials.password = \'', password, '\'');PREPARE stmt FROM @query;EXECUTE stmt;SELECT @succ;SET success = @succ;
END;
$$
DELIMITER ;
(注意:此代码片段是一个示例,来自下面的参考。)
除了非常适度的封装外,此存储过程与我们之前看到的“错误答案”没有任何关系。
旁注:这是一个oracle的示例。 它返回有关用户身份验证的最少信息–“竖起大拇指”或“竖起大拇指”。 由于调用者已经知道用户名和密码,所以此实现中没有信息泄漏,但是更可靠的实现也可以验证用户帐户是否已禁用等。
存储过程和参数化
第一种安全的方法是直接执行SQL,而不是创建动态SQL。
第二种安全方法是在存储过程中进行参数化。 这直接等效于Java准备好的语句和占位符。
DELIMITER $$
DROP PROCEDURE IF EXISTS SP_AUTHENTICATE$$
CREATE PROCEDURE SP_AUTHENTICATE(IN username VARCHAR(20),IN password VARCHAR(20),OUT success INT)
BEGINSET @query = 'SELECT COUNT(credentials.username) INTO @succFROM credentialsWHERE credentials.username = ? AND credentials.password = ?';PREPARE stmt FROM @query;SET @usernm = username;SET @pass = password;EXECUTE stmt USING @usernm, @pass;SELECT @succ;SET success = @succ;
END;
$$
DELIMITER ;
PLPSQL消毒
如果您愿意与特定的数据库供应商联系,则还有另一种选择。 实际上,这通常不是问题-休眠为您提供了一些数据库透明性,但是存储过程将始终与数据库紧密联系。
在plpsql (PostgreSQL)中,有两个命令可用于清理输入: quote_ident和quote_literal 。 毫无疑问,其他存储过程语言中也有类似的命令。
更新上面的错误答案,我们有:
DELIMITER $$
DROP PROCEDURE IF EXISTS SP_AUTHENTICATE$$
CREATE PROCEDURE SP_AUTHENTICATE(IN username VARCHAR(20),IN password VARCHAR(20),OUT success INT)
BEGINSET @query = CONCAT('SELECT COUNT(credentials.username) INTO @succFROM credentialsWHERE credentials.username = ', quote_literal(username),'AND credentials.password = ', quote_literal(password));PREPARE stmt FROM @query;EXECUTE stmt;SELECT @succ;SET success = @succ;
END;
$$
DELIMITER ;
直接SQL
最终的安全方法是使用具有最小参数大小的直接SQL调用。 CERT网站上提到了这一点,但是我会犹豫使用它,因为很容易偶然引入不安全的代码。
DELIMITER $$
DROP PROCEDURE IF EXISTS SP_AUTHENTICATE$$
CREATE PROCEDURE SP_AUTHENTICATE(IN username VARCHAR(8),IN password VARCHAR(20),OUT success INT)
BEGINSELECT COUNT(credentials.username) INTO successFROM credentialsWHERE credentials.username = username AND credentials.password = password;
END;
$$
DELIMITER ;
成本效益分析
存储过程比裸SQL查询更难利用,但这常常给人一种错误的安全感。 对于敏感信息(用户身份验证,审核日志记录),应将其视为强制性的;在所有其他情况下,强烈建议使用此方法。
架构所有权
什么是DDL,DML,DCL和TCL?
SQL包含四种不同类型的语句。
数据定义语言
数据定义语言(DDL)语句定义数据库结构。 可以将其视为建造仓库但将钥匙交给承租人的房东。
声明:
- 创建– 创建表,视图,索引等。
- alter – 更改表,视图,索引,列等。
- drop – 删除表,视图,索引等
- 截断– 从表中删除所有记录
- comment – 向表,列,视图等添加注释。
- 重命名– 重命名表,视图等。
数据处理语言
数据操作语言(DML)语句在DDL创建的结构内管理数据。 可以将其视为仓库的租户-它可以使用仓库,但不能拆除墙壁。
声明:
- 选择– 检索数据
- insert – 将新数据插入表中
- 更新– 更新表中的现有数据
- 删除– 从表中删除数据
- 调用– 调用PL / SQL或其他存储过程
- 解释计划– 解释如何执行查询
- 锁定表– 锁定表以限制并发
数据控制语言
数据控制语言(CDL)语句控制对数据和架构的访问权限。 可以将其视为门上的锁,允许在仓库内移动墙壁等。
声明:
- 授予– 向用户授予其他特权
- 撤消– 删除用户权限
交易控制语言
事务控制语言(TCL)语句用于控制事务。
声明:
- 提交– 保存已完成的工作
- 回滚– 撤消已完成的工作
- savepoint – 标记一个可以在以后回滚而不必回滚整个事务的点
- 设置交易– 设置交易选项
使用不同的数据库用户进行模式和数据所有权
该模式应由一个数据库用户(例如app_owner)拥有,而数据应由另一个数据库用户(例如app_user)拥有 。
所有者应:
- 能够运行DDL和DCL语句
- 可以说没有能力运行DML语句
- 永远不会通过webapp访问
用户应
- 能够运行DML和TCL语句
- 没有能力运行DDL或DCL语句
- 可通过webapp访问
成本效益分析
分离模式和数据库的所有权有一个非常有利的成本/收益比。 创建和维护数据库时,成本会稍高一些,但实际上,它消除了Web入侵者破坏数据库架构本身的能力。 另一方面,数据仍然可以被删除。
用户认证和授权信息
用户身份验证( authn )是我们如何知道用户就是他声称的身份的人。 至少是用户名和密码,但是如果使用两因素身份验证,则可以包括更多用户名和密码。
用户授权( authz )是我们允许用户执行的操作。
这些是非常不同的问题,应这样对待。 某些架构,例如,如果站点使用siteminder或类似工具,则它根本无法访问身份验证信息-它只能添加身份验证。
什么是用户身份验证/身份验证信息? 它是
- 用户名和/或电子邮件
- 密码
- 单点登录(SSO)标识
- 安全令牌(用于双重身份验证)
- 安全图片/短语(用于证明您的网站对用户合法)
- 团体和角色
什么是用户身份验证/身份验证信息?
- 联系信息
- 内容订阅
- 或验证或授权用户不需要的其他任何内容。
错误的方法
将所有内容(用户身份验证/身份验证,静态内容和动态内容)放入单个数据库架构中。
很快
这简单。
这是自动生成工具的默认行为。
这是非常非常错误的,因为任何破解您的Web应用程序的人都还破解了您的用户authn / authz数据。 充其量您将获得拒绝服务。 在最坏的情况下,他们可以伪装成其他用户,可以添加自己的高度特权帐户等。
单独的架构和连接池
最快的解决方案是为用户authn / authz数据创建一个单独的架构,并在访问该数据时使用专用的数据源(或Hibernate会话)。 从标准数据源(或Hibernate会话)应该无法读取该模式。 这为您提供了一个不错的防火墙,但它并不完美。
一个看似更健壮的解决方案是对用户authn / auhtz数据使用单独的数据库,而不仅仅是单独的模式。 这似乎可以保护您免受权限配置错误的影响,该权限配置允许动态内容数据源访问用户数据源。
可悲的是,在某些RDMBS中,模式和数据库之间没有明确的区别,并且如果授予必要的权限,则与一个“数据库”的连接仍可以访问另一个“数据库”。 您无法确定,除非您具有用于用户authn / authz和动态内容的单独的数据库实例。 如果您的体系结构具有专用于用户authn / authz的服务器,那么这可能不是一个不适当的负担。 对于虚拟服务器或云设计而言,这并非不合理。
基于容器的身份验证
更好的解决方案是基于容器的身份验证。 将用户authn / authz完全从Web应用程序中拉出-到Web应用程序获取请求时,HttpServletRequest已经填充了所有必要的信息。 您的Web应用无法访问容器的身份验证信息。 (对上述注释进行模数化-如果容器与动态内容位于相同的架构中,您将不会有任何收获。)
这种方法的一种变体是放在Web应用程序前面的身份验证过滤器,例如,来自Spring Security的那些过滤器。 这是一种不同的机制,但具有相同的目的,即在用户数据和动态内容之间保持非常清晰的区分。
小故障–添加和更新用户
这里有一个小故障-如果您的Web应用程序无法访问用户的authn / authz表,如何添加或更新用户信息?
第一种方法是创建一个单独的处理该问题的Web应用程序。 您的主Web应用程序可以根据需要透明地重定向到第二个Web应用程序。 好处是您可以拥有一致的外观和感觉,缺点是您将用户的authn / authz信息再次暴露给了weeb。
第二种方法是创建一个单独的REST服务来处理此问题。 您的Web应用程序可以提供用户界面,但可以调用REST服务而不是标准业务层。 REST服务可以在您的防火墙内。
第三种方法是将其完全推迟到容器中。 这样可以确保最大程度的分离,但是很难保持一致的外观。
相关链接
https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=70288108
参考: 数据库和Webapp安全性,第1部分:威胁模型 , 数据库和Webapp安全性,第2部分:SQL注入 , 数据库和Webapp安全性,第3部分:存储过程中的SQL注入 , 数据库和Webapp安全性,第4部分:模式所有权 , 数据库Webapp安全性,第5部分: JCG合作伙伴 Bear Giles在Invariant Properties博客上的用户身份验证 。
翻译自: https://www.javacodegeeks.com/2012/11/database-and-webapp-security.html