You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

138 lines
11 KiB
Markdown

2 years ago
# 答疑篇:安全篇思考题答案合集
你好,我是朱晔。
今天我们继续一起分析这门课“安全篇”模块的第27~30讲的课后思考题。这些题目涉及了数据源头、安全兜底、数据和代码、敏感数据相关的4大知识点。
接下来,我们就一一具体分析吧。
### [27 | 数据源头:任何客户端的东西都不可信任](https://time.geekbang.org/column/article/235700)
**问题1**在讲述用户标识不能从客户端获取这个要点的时候我提到开发同学可能会因为用户信息未打通而通过前端来传用户ID。那我们有什么好办法来打通不同的系统甚至不同网站的用户标识吗
答:打通用户在不同系统之间的登录,大致有以下三种方案。
第一种把用户身份放在统一的服务端每一个系统都需要到这个服务端来做登录状态的确认确认后在自己网站的Cookie中保存会话这就是单点登录的做法。这种方案要求所有关联系统都对接一套中央认证服务器中央保存用户会话在未登录的时候跳转到中央认证服务器进行登录或登录状态确认。因此这种方案适合一个公司内部的不同域名下的网站。
第二种把用户身份信息直接放在Token中在客户端任意传递Token由服务端进行校验如果共享密钥话甚至不需要同一个服务端进行校验无需采用中央认证服务器相对比较松耦合典型的标准是JWT。这种方案适合异构系统的跨系统用户认证打通而且相比单点登录的方案用户体验会更好一些。
第三种如果需要打通不同公司系统的用户登录状态那么一般都会采用OAuth 2.0的标准中的授权码模式,基本流程如下:
1. 第三方网站客户端转到授权服务器上送ClientID、重定向地址RedirectUri等信息。
2. 用户在授权服务器进行登录并且进行授权批准(授权批准这步可以配置为自动完成)。
3. 授权完成后,重定向回到之前客户端提供的重定向地址,附上授权码。
4. 第三方网站服务端通过授权码+ClientID+ClientSecret去授权服务器换取Token。这里的Token包含访问Token和刷新Token访问Token过期后用刷新Token去获得新的访问Token。
因为我们不会对外暴露ClientSecret也不会对外暴露访问Token同时使用授权码换取Token的过程是服务端进行的客户端拿到的只是一次性的授权码所以这种模式比较安全。
**问题2**还有一类和客户端数据相关的漏洞非常重要那就是URL地址中的数据。在把匿名用户重定向到登录页面的时候我们一般会带上redirectUrl这样用户登录后可以快速返回之前的页面。黑客可能会伪造一个活动链接由真实的网站+钓鱼的redirectUrl构成发邮件诱导用户进行登录。用户登录时访问的其实是真的网站所以不容易察觉到redirectUrl是钓鱼网站登录后却来到了钓鱼网站用户可能会不知不觉就把重要信息泄露了。这种安全问题我们叫做开放重定向问题。你觉得从代码层面应该怎么预防开放重定向问题呢
答:要从代码层面预防开放重定向问题,有以下三种做法可供参考:
* 第一种固定重定向的目标URL。
* 第二种可采用编号方式指定重定向的目标URL也就是重定向的目标URL只能是在我们的白名单内的。
* 第三种,用合理充分的校验方式来校验跳转的目标地址,如果是非己方地址,就告知用户跳转有风险,小心钓鱼网站的威胁。
### [28 | 安全兜底:涉及钱时,必须考虑防刷、限量和防重](https://time.geekbang.org/column/article/237060)
**问题1**防重、防刷都是事前手段,如果我们的系统正在被攻击或利用,你有什么办法及时发现问题吗?
答:对于及时发现系统正在被攻击或利用,监控是较好的手段,关键点在于报警阈值怎么设置。我觉得可以对比昨天同时、上周同时的量,发现差异达到一定百分比报警,而且报警需要有升级机制。此外,有的时候大盘很大的话,活动给整个大盘带来的变化不明显,如果进行整体监控可能出了问题也无法及时发现,因此可以考虑对于活动做独立的监控报警。
**问题2**任何三方资源的使用一般都会定期对账,如果在对账中发现我们系统记录的调用量低于对方系统记录的使用量,你觉得一般是什么问题引起的呢?
答:我之前遇到的情况是,在事务内调用外部接口,调用超时后本地事务回滚本地就没有留下数据。更合适的做法是:
1. 请求发出之前先记录请求数据提交事务,记录状态为未知。
2. 发布调用外部接口的请求,如果可以拿到明确的结果,则更新数据库中记录的状态为成功或失败。如果出现超时或未知异常,不能假设第三方接口调用失败,需要通过查询接口查询明确的结果。
3. 写一个定时任务补偿数据库中所有未知状态的记录,从第三方接口同步结果。
值得注意的是对账的时候一定要对两边不管哪方数据缺失都可能是因为程序逻辑有bug需要重视。此外任何涉及第三方系统的交互都建议在数据库中保持明细的请求/响应报文方便在出问题的时候定位Bug根因。
### [29 | 数据和代码:数据就是数据,代码就是代码](https://time.geekbang.org/column/article/237139)
**问题1**在讨论SQL注入案例时最后那次测试我们看到sqlmap返回了4种注入方式。其中布尔盲注、时间盲注和报错注入我都介绍过了。你知道联合查询注入是什么吗
联合查询注入也就是通过UNION来实现我们需要的信息露出一般属于回显的注入方式。我们知道UNION可以用于合并两个SELECT查询的结果集因此可以把注入脚本来UNION到原始的SELECT后面。这样就可以查询我们需要的数据库元数据以及表数据了。
注入的关键点在于:
* 第一UNION的两个SELECT语句的列数和字段类型需要一致。
* 第二需要探查UNION后的结果和页面回显呈现数据的对应关系。
**问题2**在讨论XSS的时候对于Thymeleaf模板引擎我们知道如何让文本进行HTML转义显示。FreeMarker也是Java中很常用的模板引擎你知道如何处理转义吗
其实现在大多数的模板引擎都使用了黑名单机制而不是白名单机制来做HTML转义这样更能有效防止XSS漏洞。也就是默认开启HTML转义如果某些情况你不需要转义可以临时关闭。
比如,[FreeMarker](https://freemarker.apache.org/docs/dgui_misc_autoescaping.html)2.3.24以上版本默认对HTML、XHTML、XML等文件类型输出格式设置了各种转义规则你可以使用?no\_esc
```
<#-- 假设默认是HTML输出 -->
${'<b>test</b>'} <#-- 输出: &lt;b&gt;test&lt;/b&gt; -->
${'<b>test</b>'?no_esc} <#-- 输出: <b>test</b> -->
```
或noautoesc指示器
```
${'&'} <#-- 输出: &amp; -->
<#noautoesc>
${'&'} <#-- 输出: & -->
...
${'&'} <#-- 输出: & -->
</#noautoesc>
${'&'} <#-- 输出: &amp; -->
```
来临时关闭转义。又比如,对于模板引擎[Mustache](https://mustache.github.io/mustache.5.html),可以使用三个花括号而不是两个花括号,来取消变量自动转义:
```
模板:
* {{name}}
* {{company}}
* {{{company}}}
数据:
{
"name": "Chris",
"company": "<b>GitHub</b>"
}
输出:
* Chris
*
* &lt;b&gt;GitHub&lt;/b&gt;
* <b>GitHub</b>
```
### [30 | 如何正确保存和传输敏感数据?](https://time.geekbang.org/column/article/239150)
**问题1**虽然我们把用户名和密码脱敏加密保存在数据库中,但日志中可能还存在明文的敏感数据。你有什么思路在框架或中间件层面,对日志进行脱敏吗?
如果我们希望在日志的源头进行脱敏那么可以在日志框架层面做。比如对于logback日志框架我们可以自定义MessageConverter通过正则表达式匹配敏感信息脱敏。
需要注意的是,这种方式有两个缺点。
第一,正则表达式匹配敏感信息的格式不一定精确,会出现误杀漏杀的现象。一般来说,这个问题不会很严重。要实现精确脱敏的话,就只能提供各种脱敏工具类,然后让业务应用在日志中记录敏感信息的时候,先手动调用工具类进行脱敏。
第二如果数据量比较大的话脱敏操作可能会增加业务应用的CPU和内存使用甚至会导致应用不堪负荷出现不可用。考虑到目前大部分公司都引入了ELK来集中收集日志并且一般而言都不允许上服务器直接看文件日志因此我们可以考虑在日志收集中间件中比如logstash写过滤器进行脱敏。这样可以把脱敏的消耗转义到ELK体系中不过这种方式同样有第一点提到的字段不精确匹配导致的漏杀误杀的缺点。
**问题2**你知道HTTPS双向认证的目的是什么吗流程上又有什么区别呢
单向认证一般用于Web网站浏览器只需要验证服务端的身份。对于移动端App如果我们希望有更高的安全性可以引入HTTPS双向认证也就是除了客户端验证服务端身份之外服务端也验证客户端的身份。
单向认证和双向认证的流程区别,主要包括以下三个方面。
第一不仅仅服务端需要有CA证书客户端也需要有CA证书。
第二双向认证的流程中客户端校验服务端CA证书之后客户端会把自己的CA证书发给服务端然后服务端需要校验客户端CA证书的真实性。
第三客户端给服务端的消息会使用自己的私钥签名服务端可以使用客户端CA证书中的公钥验签。
这里还想补充一点对于移动应用程序考虑到更强的安全性我们一般也会把服务端的公钥配置在客户端中这种方式的叫做SSL Pinning。也就是说由客户端直接校验服务端证书的合法性而不是通过证书信任链来校验。采用SSL Pinning由于客户端绑定了服务端公钥因此我们无法通过在移动设备上信用根证书实现抓包。不过这种方式的缺点是需要小心服务端CA证书过期后续证书注意不要修改公钥。
好了以上就是咱们整个《Java 业务开发常见错误100例》这门课的30讲正文的思考题答案或者解题思路了。
关于这些题目,以及背后涉及的知识点,如果你还有哪里感觉不清楚的,欢迎在评论区与我留言,也欢迎你把今天的内容分享给你的朋友或同事,一起交流。