第31讲 你了解Java应用开发中的注入攻击吗?
安全是软件开发领域永远的主题之一,随着新技术浪潮的兴起,安全的重要性愈发凸显出来,对于金融等行业,甚至可以说安全是企业的生命线。不论是移动设备、普通PC、小型机,还是大规模分布式系统,以及各种主流操作系统,Java作为软件开发的基础平台之一,可以说是无处不在,自然也就成为安全攻击的首要目标之一。
今天我要问你的问题是,你了解Java应用开发中的注入攻击吗?
典型回答
注入式(Inject)攻击是一类非常常见的攻击方式,其基本特征是程序允许攻击者将不可信的动态内容注入到程序中,并将其执行,这就可能完全改变最初预计的执行过程,产生恶意效果。
下面是几种主要的注入式攻击途径,原则上提供动态执行能力的语言特性,都需要提防发生注入攻击的可能。
首先,就是最常见的SQL注入攻击。一个典型的场景就是Web系统的用户登录功能,根据用户输入的用户名和密码,我们需要去后端数据库核实信息。
假设应用逻辑是,后端程序利用界面输入动态生成类似下面的SQL,然后让JDBC执行。
但是,如果我输入的input_pwd是类似下面的文本,
那么,拼接出的SQL字符串就变成了下面的条件,OR的存在导致输入什么名字都是复合条件的。
这里只是举个简单的例子,它是利用了期望输入和可能输入之间的偏差。上面例子中,期望用户输入一个数值,但实际输入的则是SQL语句片段。类似场景可以利用注入的不同SQL语句,进行各种不同目的的攻击,甚至还可以加上“;delete xxx”之类语句,如果数据库权限控制不合理,攻击效果就可能是灾难性的。
第二,操作系统命令注入。Java语言提供了类似Runtime.exec(…)的API,可以用来执行特定命令,假设我们构建了一个应用,以输入文本作为参数,执行下面的命令:
但是如果用户输入是 “input_file_name;rm –rf /*”,这就有可能出现问题了。当然,这只是个举例,Java标准类库本身进行了非常多的改进,所以类似这种编程错误,未必可以真的完成攻击,但其反映的一类场景是真实存在的。
第三,XML注入攻击。Java核心类库提供了全面的XML处理、转换等各种API,而XML自身是可以包含动态内容的,例如XPATH,如果使用不当,可能导致访问恶意内容。
还有类似LDAP等允许动态内容的协议,都是可能利用特定命令,构造注入式攻击的,包括XSS(Cross-site Scripting)攻击,虽然并不和Java直接相关,但也可能在JSP等动态页面中发生。
考点分析
今天的问题是安全领域的入门题目,我简单介绍了最常见的几种注入场景作为示例。安全本身是个非常大的主题,在面试中,面试官可能会考察安全问题,但如果不是特定安全专家岗位,了解基础的安全实践就可以满足要求了。
Java工程师未必都要成为安全专家,但了解基础的安全领域常识,有利于发现和规避日常开发中的风险。今天我会侧重和Java开发相关的安全内容,希望可以起到一个抛砖引玉的作用,让你对Java开发安全领域有个整体印象。
- 谈到Java应用安全,主要涉及哪些安全机制?
- 到底什么是安全漏洞?对于前面提到的SQL注入等典型攻击,我们在开发中怎么避免?
知识扩展
首先,一起来看看哪些Java API和工具构成了Java安全基础。很多方面我在专栏前面的讲解中已经有所涉及,可以简单归为三个主要组成部分:
第一,运行时安全机制。可以简单认为,就是限制Java运行时的行为,不要做越权或者不靠谱的事情,具体来看:
- 在类加载过程中,进行字节码验证,以防止不合规的代码影响JVM运行或者载入其他恶意代码。
- 类加载器本身也可以对代码之间进行隔离,例如,应用无法获取启动类加载器(Bootstrap Class-Loader)对象实例,不同的类加载器也可以起到容器的作用,隔离模块之间不必要的可见性等。目前,Java Applet、RMI等特性已经或逐渐退出历史舞台,类加载等机制总体上反倒在不断简化。
- 利用SecurityManger机制和相关的组件,限制代码的运行时行为能力,其中,你可以定制policy文件和各种粒度的权限定义,限制代码的作用域和权限,例如对文件系统的操作权限,或者监听某个网络端口的权限等。我画了一个简单的示意图,对运行时安全的不同层次进行了整理。
可以看到,Java的安全模型是以代码为中心的,贯穿了从类加载,如URLClassLoader加载网络上的Java类等,到应用程序运行时权限检查等全过程。
- 另外,从原则上来说,Java的GC等资源回收管理机制,都可以看作是运行时安全的一部分,如果相应机制失效,就会导致JVM出现OOM等错误,可看作是另类的拒绝服务。
第二,Java提供的安全框架API,这是构建安全通信等应用的基础。例如:
注意,这一部分API内部实现是和厂商相关的,不同JDK厂商往往会定制自己的加密算法实现。
第三, 就是JDK集成的各种安全工具,例如:
在应用实践中,如果对安全要求非常高,建议打开SecurityManager,
请注意其开销,通常只要开启SecurityManager,就会导致10% ~ 15%的性能下降,在JDK 9以后,这个开销有所改善。
理解了基础Java安全机制,接下来我们来一起探讨安全漏洞(Vulnerability)。
按照传统的定义,任何可以用来绕过系统安全策略限制的程序瑕疵,都可以算作安全漏洞。具体原因可能非常多,设计或实现中的疏漏、配置错误等,任何不慎都有可能导致安全漏洞出现,例如恶意代码绕过了Java沙箱的限制,获取了特权等。如果你想了解更多安全漏洞的信息,可以从通用安全漏洞库(CVE)等途径获取,了解安全漏洞评价标准。
但是,要达到攻击的目的,未必都需要绕过权限限制。比如利用哈希碰撞发起拒绝服务攻击(DOS,Denial-Of-Service attack),常见的场景是,攻击者可以事先构造大量相同哈希值的数据,然后以JSON数据的形式发送给服务器端,服务器端在将其构建成为Java对象过程中,通常以Hastable或HashMap等形式存储,哈希碰撞将导致哈希表发生严重退化,算法复杂度可能上升一个数量级(HashMap后续进行了改进,我在专栏第9讲介绍了树化机制),进而耗费大量CPU资源。
像这种攻击方式,无关于权限,可以看作是程序实现的瑕疵,给了攻击者以低成本进行进攻的机会。
我在开头提到的各种注入式攻击,可以有不同角度、不同层面的解决方法,例如针对SQL注入:
- 在数据输入阶段,填补期望输入和可能输入之间的鸿沟。可以进行输入校验,限定什么类型的输入是合法的,例如,不允许输入标点符号等特殊字符,或者特定结构的输入。
- 在Java应用进行数据库访问时,如果不用完全动态的SQL,而是利用PreparedStatement,可以有效防范SQL注入。不管是SQL注入,还是OS命令注入,程序利用字符串拼接生成运行逻辑都是个可能的风险点!
- 在数据库层面,如果对查询、修改等权限进行了合理限制,就可以在一定程度上避免被注入删除等高破坏性的代码。
在安全领域,有一句准则:安全倾向于 “明显没有漏洞”,而不是“没有明显漏洞”。所以,为了更加安全可靠的服务,我们最好是采取整体性的安全设计和综合性的防范手段,而不是头痛医头、脚痛医脚的修修补补,更不能心存侥幸。
一个比较普适的建议是,尽量使用较新版本的JDK,并使用推荐的安全机制和标准。如果你有看过JDK release notes,例如8u141,你会发现JDK更新会修复已知的安全漏洞,并且会对安全机制等进行增强。但现实情况是,相当一部分应用还在使用很古老的不安全版本JDK进行开发,并且很多信息处理的也很随意,或者通过明文传输、存储,这些都存在暴露安全隐患的可能。
今天我首先介绍了典型的注入攻击,然后整理了Java内部的安全机制,并探讨了到底什么是安全漏洞和典型的表现形式,以及如何防范SQL注入攻击等,希望对你有所帮助。
一课一练
关于今天我们讨论的题目你做到心中有数了吗?今天的思考题是,你知道Man-In-The-Middle(MITM)攻击吗?有哪些常见的表现形式?如何防范呢?
请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。
7月19日也就是本周四晚上8点半,我会做客极客Live,做一期主题为“1小时搞定Java面试”的直播分享,我会聊聊Java面试那些事儿,感兴趣的同学不要错过哦。
你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。
- 羊羊羊 👍(19) 💬(1)
也不是很懂,根据自己的理解讲一下,部分可能是错误的。中间人攻击原理大概是用户在正常上网的时候,同网段的恶意用户对其进行欺骗。恶意用户向局域网广播:我是路由器,然后正常用户(电脑无防御)收到以后认为恶意用户就是路由器,然后向恶意用户发送数据包,恶意用户可以截获数据包,再向路由器发送正常用户的数据包,路由器将返回的数据包在给恶意用户,恶意用户在给正常用户,恶意用户就形成了中间人的效果,可以向返回的数据包注入html代码,达到劫持用户网站的效果,不过现在大部分的网站都是https且双向认证,比较难获取到用户发送数据包中的账号密码。
2018-07-17 - 苦行僧 👍(12) 💬(1)
每次使用开源组件,经常关注使用版本修复的问题列表,其中的安全修复值得重点关注
2019-02-08 - 鸡肉饭饭 👍(8) 💬(3)
杨老师,您好,被一个安全问题困扰许久。就是开发者是否能够通过一定的手段修改jdk中的String类,并将修改后的String类进行替换,对于这个问题,应当从哪里开始寻找答案?谢谢
2018-07-17 - 王建 👍(11) 💬(0)
中间人攻击最容易理解的可能就是fiddler吧,他可以截获request重新组织request的数据,有个专业的攻击工具叫burp。
2018-07-18 - 随心而至 👍(10) 💬(0)
PreparedStatement又是如何防范sql注入的呢? preparedStatement = "SELECT * FROM users WHERE name = ?"; preparedStatement.setString(1, userName); 查询“ SELECT * FROM users where where name =?” 将被发送到数据库,数据库会对其进行编译,然后将替换setString中的userName。 如果数据库看到非法值,它将引发错误。 因此,'or'1'='1将被视为一个完整的字符串,而不是包含运算符or和=的语句, 也就是数据库将其视为值为 "' or '1'='1"的字符(虽然是个奇怪的字符串)。 总结就一句话,占位符只能存储给定类型的值,而不能存储任意SQL片段(你写片段,我也把你看成是整体,即该类型的特别输入,比如上面的"' or '1'='1"就被看成是一个特别的String) https://stackoverflow.com/questions/4333015/does-the-preparedstatement-avoid-sql-injection https://en.wikipedia.org/wiki/SQL_injection
2021-01-06 - 爪哇夜未眠 👍(10) 💬(0)
期待杨晓峰老师直播!
2018-07-17 - 会网络的老鼠 👍(4) 💬(0)
或者通过明文传输、存储,这些都存在暴露安全隐患的可能。 朗读者将 明文 读成 文明 难道只有我注意到么?
2018-11-06 - wei 👍(3) 💬(1)
String你懂双亲委派就知道,自己定义String是用不了的。
2019-05-08 - 咖啡猫口里的咖啡猫🐱 👍(2) 💬(0)
我来回答,,鸡肉饭饭的,,,数据存在immutable,mutable,两种,java没有原生immutable支持,string如果是new就是相对意义的immutable,java基本类型和string是有高效缓存池范围,OK?
2018-07-17 - 亚林 👍(1) 💬(0)
中间人攻击,现在https如果被中间人代理了,一般程序要检查https证书是否合法,Android就是在本地保存一份合法https证书去检查线上https是否合法,缺点就是每次https证书到期之前要升级一次客户端,还有就是抓包调试的时候需要去掉https证书检查,才能正常运行抓包。
2020-06-08 - 匿名 👍(1) 💬(0)
感谢老师!
2020-03-16 - William 👍(1) 💬(1)
类加载器本身也可以对代码之间进行隔离,例如,应用无法获取启动类加载器(Bootstrap Class-Loader)对象实例,不同的类加载器也可以起到容器的作用,隔离模块之间不必要的可见性等。 所以上面这段话没理解, 启动器加载器不是顶级父类加载器么? 按照“双亲委派机制”不是从父加载器看是否能够加载对象么? 怎么还无法回去对象实例了呢?
2019-12-02 - 王宁 👍(1) 💬(0)
纠结String的同学可以试一下,String应该是加载不了的。
2018-07-17 - 樱小路依然 👍(0) 💬(0)
您好老师,我这几天突然发现半年之前开发中碰到的一个问题,有一个接口是接收信息存储数据库的,然后这个过程比较长,可能要几十秒,但是我在开发时,调用这个接口的是一个linux py脚本定时器,其中限制是5s超时。之后我经常发现服务跑了一段时间就挂了,需要重启,经过检查发现是由于大量的close_wait导致。 由于py脚本超时5s断开时,向服务发送fin,然后服务回了一个ack进入close_wait状态,但是此时并没有执行完方法,然而等方法执行完对py端发送fin时,py端已经不识别了,长时间的定时任务导致大量close_wait从而使tomcat容器假死。 请问这也算是一种攻击方式吗?我在网上查找解决方案,但是大部分是说服务方需要关闭httpclient,我使用的是springboot的@RequestMapping,好像没有类似解决的参数。
2022-02-22 - paxos 👍(0) 💬(0)
可以加新内容了,log4j JNDI 注入
2021-12-19