This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
<audio id="audio" title="第31讲 | 你了解Java应用开发中的注入攻击吗" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5e/d5/5e56561effa236190ddd37bcfb3aebd5.mp3"></audio>
安全是软件开发领域永远的主题之一随着新技术浪潮的兴起安全的重要性愈发凸显出来对于金融等行业甚至可以说安全是企业的生命线。不论是移动设备、普通PC、小型机还是大规模分布式系统以及各种主流操作系统Java作为软件开发的基础平台之一可以说是无处不在自然也就成为安全攻击的首要目标之一。
今天我要问你的问题是你了解Java应用开发中的注入攻击吗
## 典型回答
注入式Inject攻击是一类非常常见的攻击方式其基本特征是程序允许攻击者将不可信的动态内容注入到程序中并将其执行这就可能完全改变最初预计的执行过程产生恶意效果。
下面是几种主要的注入式攻击途径,原则上提供动态执行能力的语言特性,都需要提防发生注入攻击的可能。
首先就是最常见的SQL注入攻击。一个典型的场景就是Web系统的用户登录功能根据用户输入的用户名和密码我们需要去后端数据库核实信息。
假设应用逻辑是后端程序利用界面输入动态生成类似下面的SQL然后让JDBC执行。
```
Select * from use_info where username = “input_usr_name” and password = “input_pwd”
```
但是如果我输入的input_pwd是类似下面的文本
```
“ or “”=”
```
那么拼接出的SQL字符串就变成了下面的条件OR的存在导致输入什么名字都是复合条件的。
```
Select * from use_info where username = “input_usr_name” and password = “” or “” = “”
```
这里只是举个简单的例子它是利用了期望输入和可能输入之间的偏差。上面例子中期望用户输入一个数值但实际输入的则是SQL语句片段。类似场景可以利用注入的不同SQL语句进行各种不同目的的攻击甚至还可以加上“;delete xxx”之类语句如果数据库权限控制不合理攻击效果就可能是灾难性的。
第二操作系统命令注入。Java语言提供了类似Runtime.exec(…)的API可以用来执行特定命令假设我们构建了一个应用以输入文本作为参数执行下面的命令
```
ls la input_file_name
```
但是如果用户输入是 “input_file_name;rm rf /*”这就有可能出现问题了。当然这只是个举例Java标准类库本身进行了非常多的改进所以类似这种编程错误未必可以真的完成攻击但其反映的一类场景是真实存在的。
第三XML注入攻击。Java核心类库提供了全面的XML处理、转换等各种API而XML自身是可以包含动态内容的例如XPATH如果使用不当可能导致访问恶意内容。
还有类似LDAP等允许动态内容的协议都是可能利用特定命令构造注入式攻击的包括XSSCross-site Scripting攻击虽然并不和Java直接相关但也可能在JSP等动态页面中发生。
## 考点分析
今天的问题是安全领域的入门题目,我简单介绍了最常见的几种注入场景作为示例。安全本身是个非常大的主题,在面试中,面试官可能会考察安全问题,但如果不是特定安全专家岗位,了解基础的安全实践就可以满足要求了。
Java工程师未必都要成为安全专家但了解基础的安全领域常识有利于发现和规避日常开发中的风险。今天我会侧重和Java开发相关的安全内容希望可以起到一个抛砖引玉的作用让你对Java开发安全领域有个整体印象。
<li>
谈到Java应用安全主要涉及哪些安全机制
</li>
<li>
到底什么是安全漏洞对于前面提到的SQL注入等典型攻击我们在开发中怎么避免
</li>
## 知识扩展
首先一起来看看哪些Java API和工具构成了Java安全基础。很多方面我在专栏前面的讲解中已经有所涉及可以简单归为三个主要组成部分
第一运行时安全机制。可以简单认为就是限制Java运行时的行为不要做越权或者不靠谱的事情具体来看
<li>
在类加载过程中进行字节码验证以防止不合规的代码影响JVM运行或者载入其他恶意代码。
</li>
<li>
类加载器本身也可以对代码之间进行隔离例如应用无法获取启动类加载器Bootstrap Class-Loader对象实例不同的类加载器也可以起到容器的作用隔离模块之间不必要的可见性等。目前Java Applet、RMI等特性已经或逐渐退出历史舞台类加载等机制总体上反倒在不断简化。
</li>
<li>
利用SecurityManger机制和相关的组件限制代码的运行时行为能力其中你可以定制policy文件和各种粒度的权限定义限制代码的作用域和权限例如对文件系统的操作权限或者监听某个网络端口的权限等。我画了一个简单的示意图对运行时安全的不同层次进行了整理。
</li>
<img src="https://static001.geekbang.org/resource/image/b4/54/b48e754c6ebb11b6934f4697b7091854.png" alt="" />
可以看到Java的安全模型是以代码为中心的贯穿了从类加载如URLClassLoader加载网络上的Java类等到应用程序运行时权限检查等全过程。
- 另外从原则上来说Java的GC等资源回收管理机制都可以看作是运行时安全的一部分如果相应机制失效就会导致JVM出现OOM等错误可看作是另类的拒绝服务。
第二Java提供的安全框架API这是构建安全通信等应用的基础。例如
<li>
加密、解密API。
</li>
<li>
授权、鉴权API。
</li>
<li>
安全通信相关的类库比如基本HTTPS通信协议相关标准实现如[TLS 1.3](http://openjdk.java.net/jeps/332);或者附属的类似证书撤销状态判断([OSCP](https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol))等协议实现。
</li>
注意这一部分API内部实现是和厂商相关的不同JDK厂商往往会定制自己的加密算法实现。
第三, 就是JDK集成的各种安全工具例如
<li>
[keytool](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html)这是个强大的工具可以管理安全场景中不可或缺的秘钥、证书等并且可以管理Java程序使用的keystore文件。
</li>
<li>
[jarsigner](https://docs.oracle.com/javase/9/tools/jarsigner.htm#JSWOR-GUID-925E7A1B-B3F3-44D2-8B49-0B3FA2C54864)用于对jar文件进行签名或者验证。
</li>
在应用实践中如果对安全要求非常高建议打开SecurityManager
```
-Djava.security.manager
```
请注意其开销通常只要开启SecurityManager就会导致10% ~ 15%的性能下降在JDK 9以后这个开销有所改善。
理解了基础Java安全机制接下来我们来一起探讨安全漏洞[Vulnerability](https://en.wikipedia.org/wiki/Vulnerability_(computing)))。
按照传统的定义,任何可以用来**绕过系统安全策略限制**的程序瑕疵都可以算作安全漏洞。具体原因可能非常多设计或实现中的疏漏、配置错误等任何不慎都有可能导致安全漏洞出现例如恶意代码绕过了Java沙箱的限制获取了特权等。如果你想了解更多安全漏洞的信息可以从[通用安全漏洞库](https://cve.mitre.org/)CVE等途径获取了解安全漏洞[评价](https://www.first.org/cvss/calculator/3.0)标准。
但是要达到攻击的目的未必都需要绕过权限限制。比如利用哈希碰撞发起拒绝服务攻击DOSDenial-Of-Service attack常见的场景是攻击者可以事先构造大量相同哈希值的数据然后以JSON数据的形式发送给服务器端服务器端在将其构建成为Java对象过程中通常以Hastable或HashMap等形式存储哈希碰撞将导致哈希表发生严重退化算法复杂度可能上升一个数量级HashMap后续进行了改进我在[专栏第9讲](http://time.geekbang.org/column/article/8053)介绍了树化机制进而耗费大量CPU资源。
像这种攻击方式,无关于权限,可以看作是程序实现的瑕疵,给了攻击者以低成本进行进攻的机会。
我在开头提到的各种注入式攻击可以有不同角度、不同层面的解决方法例如针对SQL注入
<li>
在数据输入阶段,填补期望输入和可能输入之间的鸿沟。可以进行输入校验,限定什么类型的输入是合法的,例如,不允许输入标点符号等特殊字符,或者特定结构的输入。
</li>
<li>
在Java应用进行数据库访问时如果不用完全动态的SQL而是利用PreparedStatement可以有效防范SQL注入。不管是SQL注入还是OS命令注入程序利用字符串拼接生成运行逻辑都是个可能的风险点
</li>
<li>
在数据库层面,如果对查询、修改等权限进行了合理限制,就可以在一定程度上避免被注入删除等高破坏性的代码。
</li>
在安全领域,有一句准则:安全倾向于 “明显没有漏洞”,而不是“没有明显漏洞”。所以,为了更加安全可靠的服务,我们最好是采取整体性的安全设计和综合性的防范手段,而不是头痛医头、脚痛医脚的修修补补,更不能心存侥幸。
一个比较普适的建议是尽量使用较新版本的JDK并使用推荐的安全机制和标准。如果你有看过JDK release notes例如[8u141](http://www.oracle.com/technetwork/java/javase/8u141-relnotes-3720385.html)你会发现JDK更新会修复已知的安全漏洞并且会对安全机制等进行增强。但现实情况是相当一部分应用还在使用很古老的不安全版本JDK进行开发并且很多信息处理的也很随意或者通过明文传输、存储这些都存在暴露安全隐患的可能。
今天我首先介绍了典型的注入攻击然后整理了Java内部的安全机制并探讨了到底什么是安全漏洞和典型的表现形式以及如何防范SQL注入攻击等希望对你有所帮助。
## 一课一练
关于今天我们讨论的题目你做到心中有数了吗今天的思考题是你知道Man-In-The-MiddleMITM攻击吗有哪些常见的表现形式如何防范呢
请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。
7月19日也就是本周四晚上8点半我会做客极客Live做一期主题为“1小时搞定Java面试”的直播分享我会聊聊Java面试那些事儿感兴趣的同学不要错过哦。
你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。

View File

@@ -0,0 +1,152 @@
<audio id="audio" title="第32讲 | 如何写出安全的Java代码" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/82/3f/82ffa6a3a0f3b2af8d6733a8ed38dd3f.mp3"></audio>
在上一讲中我们已经初步接触了Java安全今天我们将一起探讨更多Java开发中可能影响到安全的场合。很多安全问题在特定的上下文存在着不同的定义尽管本质是相似或一致的这是由于Java平台自身的特性所带来特有的问题。今天这一讲我将侧重于Java开发者的角度谈代码安全而不是讲广义的安全风险。
今天我要问你的问题是如何写出安全的Java代码
## 典型回答
这个问题可能有点宽泛我们可以用特定类型的安全风险为例如拒绝服务DoS攻击分析Java开发者需要重点考虑的点。
DoS是一种常见的网络攻击有人也称其为“洪水攻击”。最常见的表现是利用大量机器发送请求将目标网站的带宽或者其他资源耗尽导致其无法响应正常用户的请求。
我认为从Java语言的角度更加需要重视的是程序级别的攻击也就是利用Java、JVM或应用程序的瑕疵进行低成本的DoS攻击这也是想要写出安全的Java代码所必须考虑的。例如
<li>
如果使用的是早期的JDK和Applet等技术攻击者构建合法但恶劣的程序就相对容易例如将其线程优先级设置为最高做一些看起来无害但空耗资源的事情。幸运的是类似技术已经逐步退出历史舞台在JDK 9以后相关模块就已经被移除。
</li>
<li>
上一讲中提到的哈希碰撞攻击就是个典型的例子对方可以轻易消耗系统有限的CPU和线程资源。从这个角度思考类似加密、解密、图形处理等计算密集型任务都要防范被恶意滥用以免攻击者通过直接调用或者间接触发方式消耗系统资源。
</li>
<li>
利用Java构建类似上传文件或者其他接受输入的服务需要对消耗系统内存或存储的上限有所控制因为我们不能将系统安全依赖于用户的合理使用。其中特别注意的是涉及解压缩功能时就需要防范[Zip bomb](https://en.wikipedia.org/wiki/Zip_bomb)等特定攻击。
</li>
<li>
另外Java程序中需要明确释放的资源有很多种比如文件描述符、数据库连接甚至是再入锁任何情况下都应该保证资源释放成功否则即使平时能够正常运行也可能被攻击者利用而耗尽某类资源这也算是可能的DoS攻击来源。
</li>
所以可以看出实现安全的Java代码需要从功能设计到实现细节都充分考虑可能的安全影响。
## 考点分析
关于今天的问题以典型的DoS攻击作为切入点将问题聚焦在Java开发中我介绍了Java应用设计、实现的注意事项后面还会介绍更加全面的实践。
其实安全问题实际就是软件的缺陷软件安全并不存在一劳永逸的秘籍既离不开设计、架构中的风险分析也离不开编码、测试等阶段的安全实践手段。对于面试官来说考察安全问题除了对特定安全领域知识的考察更多是要看面试者的Java编程基本功和知识的积累。
所以我会在后面会循序渐进探讨Java安全编程这里面没有什么黑科技只有规范的开发标准很多安全问题其实是态度问题取决于你是否真的认真对待它。
<li>
我将以一些典型的代码片段为出发点分析一些非常容易被忽略的安全风险并介绍安全问题频发的热点场景如Java序列化和反序列化。
</li>
<li>
从软件生命周期的角度,探讨设计、开发、测试、部署等不同阶段,有哪些常见的安全策略或工具。
</li>
## 知识扩展
首先,我们一起来看一段不起眼的条件判断代码,这里可能有什么问题吗?
```
// a, b, c都是int类型的数值
if (a + b &lt; c) {
// …
}
```
你可能会纳闷,这是再常见不过的一个条件判断了,能有什么安全隐患?
这里的隐患是数值类型需要防范溢出,否则这不仅仅可能会带来逻辑错误,在特定情况下可能导致严重的安全漏洞。
从语言特性来说Java和JVM提供了很多基础性的改进相比于传统的C、C++等语言,对于数组越界等处理要完善的多,原生的避免了[缓冲区溢出](https://en.wikipedia.org/wiki/Buffer_overflow)等攻击方式提高了软件的安全性。但这并不代表完全杜绝了问题Java程序可能调用本地代码也就是JNI技术错误的数值可能导致C/C++层面的数据越界等问题,这是很危险的。
所以,上面的条件判断,需要判断其数值范围,例如,写成类似下面结构。
```
if (a &lt; c b)
```
再来看一个例子,请看下面的一段异常处理代码:
```
try {
// 业务代码
} catch (Exception e) {
throw new RuntimeException(hostname + port + “ doesnt response”);
}
```
这段代码将敏感信息包含在异常消息中试想如果是一个Web应用异常也没有良好的包装起来很有可能就把内部信息暴露给终端客户。古人曾经告诫我们“言多必失”是很有道理的虽然其本意不是指软件安全但尽量少暴露信息也是保证安全的基本原则之一。即使我们并不认为某个信息有安全风险我的建议也是如果没有必要不要暴露出来。
这种暴露还可能通过其他方式发生,比如某著名的编程技术网站,就被曝光过所有用户名和密码。这些信息都是明文存储,传输过程也未必进行加密,类似这种情况,暴露只是个时间早晚的问题。
对于安全标准特别高的系统甚至可能要求敏感信息被使用后要立即明确在内存中销毁以免被探测或者避免在发生core dump时意外暴露。
第三Java提供了序列化等创新的特性广泛使用在远程调用等方面但也带来了复杂的安全问题。直到今天序列化仍然是个安全问题频发的场景。
针对序列化,通常建议:
<li>
敏感信息不要被序列化在编码中建议使用transient关键字将其保护起来。
</li>
<li>
反序列化中建议在readObject中实现与对象构件过程相同的安全检查和数据检查。
</li>
另外在JDK 9中Java引入了过滤器机制以保证反序列化过程中数据都要经过基本验证才可以使用。其原理是通过黑名单和白名单限定安全或者不安全的类型并且你可以进行定制然后通过环境变量灵活进行配置 更加具体的使用你可以参考 [ObjectInputFilter](https://docs.oracle.com/javase/9/docs/api/java/io/ObjectInputFilter.html)。
通过前面的介绍你可能注意到很多安全问题都是源于非常基本的编程细节类似Immutable、封装等设计都存在着安全性的考虑。从实践的角度让每个人都了解和掌握这些原则有必要但并不太现实有没有什么工程实践手段可以帮助我们排查安全隐患呢
**开发和测试阶段**
在实际开发中各种功能点五花八门未必能考虑的全面。我建议没有必要所有都需要自己去从头实现尽量使用广泛验证过的工具、类库不管是来自于JDK自身还是Apache等第三方组织都在社区的反馈下持续地完善代码安全。
开发过程中应用代码规约标准是避免安全问题的有效手段。我特别推荐来自孤尽的《阿里巴巴Java开发手册》以及其配套工具充分总结了业界在Java等领域的实践经验将规约实践系统性地引入国内的软件开发可以有效提高代码质量。
当然,凡事都是有代价的,规约会增加一定的开发成本,可能对迭代的节奏产生一定影响,所以对于不同阶段、不同需求的团队,可以根据自己的情况对规约进行适应性的调整。
落实到实际开发流程中以OpenJDK团队为例我们应用了几个不同角度的实践
<li>
在早期设计阶段,就由安全专家组对新特性进行风险评估。
</li>
<li>
开发过程中尤其是code review阶段应用OpenJDK自身定制的代码规范。
</li>
<li>
利用多种静态分析工具如[FindBugs](http://findbugs.sourceforge.net/)、[Parfait](https://labs.oracle.com/pls/apex/f?p=labs:49:::::P49_PROJECT_ID:13)等,帮助早期发现潜在安全风险,并对相应问题采取零容忍态度,强制要求解决。
</li>
<li>
甚至OpenJDK会默认将任何编译等警告都当作错误对待并体现在CI流程中。
</li>
<li>
在代码check-in等关键环节利用hook机制去调用规则检查工具以保证不合规代码不能进入OpenJDK代码库。
</li>
关于静态分析工具的选择,我们选取的原则是“足够好”。没有什么工具能够发现所有问题,所以在保证功能的前提下,影响更大的是分析效率,换句话说是代码分析的噪音高低。不管分析有多么的完备,如果太多误报,就会导致有用信息被噪音覆盖,也不利于后续其他程序化的处理,反倒不利于排查问题。
以上这些是为了保证JDK作为基础平台的苛刻质量要求在实际产品中你需要斟酌具体什么程度的要求是合理的。
**部署阶段**
JDK自身的也是个软件难免会存在实现瑕疵我们平时看到JDK更新的安全漏洞补丁其实就是在修补这些漏洞。我最近还注意到某大厂后台被曝出了使用的JDK版本存在序列化相关的漏洞。类似这种情况大多数都是因为使用的JDK是较低版本算是可以通过部署解决的问题。
如果是安全敏感型产品建议关注JDK在加解密方面的[路线图](https://java.com/en/jre-jdk-cryptoroadmap.html),同样的标准也应用于其他语言和平台,很多早期认为非常安全的算法,已经被攻破,及时地升级基础软件是安全的必要条件。
攻击和防守是不对称的,只要有一个严重漏洞,对于攻击者就足够了,所以,不能对黑盒形式的部署心存侥幸,这并不能保证系统的安全,攻击者可以利用对软件设计的猜测,结合一系列手段,探测出漏洞。
今天我以DoS等典型攻击方式为例分析了其在Java平台上的特定表现并从更多安全编码的细节帮你体会安全问题的普遍性最后我介绍了软件开发周期中的安全实践希望能对你的工作有所帮助。
## 一课一练
关于今天我们讨论的题目你做到心中有数了吗你在开发中遇到过Java特定的安全问题吗是怎么解决的呢
请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。
别忘了今晚8点半我会做客“极客Live”和你一起聊聊Java面试那些事儿。在“极客时间”App内点击“极客Live”即可加入直播今晚我们不见不散。
你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。