SQL 安全
注入式(Inject)攻击是一类非常常见的攻击方式,其基本特征是程序允许攻击者将不可信的动态内容注入到程序中,并将其执行,这就可能完全改变最初预计的执行过程,产生恶意效果。下面是几种主要的注入式攻击途径,原则上提供动态执行能力的语言特性,都需要提防发生注入攻击的可能。
首先,就是最常见的 SQL 注入攻击。一个典型的场景就是 Web 系统的用户登录功能,根据用户输入的用户名和密码,我们需要去后端数据库核实
信息。假设应用逻辑是,后端程序利用界面输入动态生成类似下面的 SQL,然后让 JDBC 执行。
Select * from use_info where username = “input_usr_name” and password = “input_pwd”
input_pwd => “ or “”=”
Select * from use_info where username = “input_usr_name” and password = “” or “” = “”
Java安全机制
谈到 Java 应用安全,主要涉及哪些安全机制?到底什么是安全漏洞?对于前面提到的 SQL 注入等典型攻击,我们在开发中怎么避免?
第一,运行时安全机制。可以简单认为,就是限制 Java 运行时的行为,不要做越权或者不靠谱的事情,具体来看:在类加载过程中,进行字节码验证,以防止不合规的代码影响 JVM 运行或者载入其他恶意代码。类加载器本身也可以对代码之间进行隔离,例如,应用无法获取启动类加载器(Bootstrap Class-Loader)对象实例,不同的类加载器也可以起到容器的作用,隔离模块之间不必要的可见性等。
目前,Java Applet、RMI 等特性已经或逐渐退出历史舞台,类加载等机制总体上反倒在不断简化。利用 SecurityManger 机制和相关的组件,限制代码的运行时行为能力,其中,你可以定制 policy 文件和各种粒度的权限定义,限制代码的作用域和权限,例如对文件系统的操作权限,或者监听某个网络端口的权限等。
我画了一个简单的示意图,对运行时安全的不同层次进行了整理。
第二,Java 提供的安全框架 API,这是构建安全通信等应用的基础。例如:加密、解密 API。授权、鉴权 API。
安全通信相关的类库,比如基本 HTTPS 通信协议相关标准实现,如TLS 1.3;或者附属的类似证书撤销状态判断(OSCP)等协议实现。注意,这一部分 API 内部实现是和厂商相关的,不同 JDK 厂商往往会定制自己的加密算法实现。
第三, 就是 JDK 集成的各种安全工具,例如:keytool,这是个强大的工具,可以管理安全场景中不可或缺的秘钥、证书等,并且可以管理 Java 程序使用的 keystore 文件。jarsigner,用于对 jar 文件进行签名或者验证。
如何写出安全的 Java 代码?
这个问题可能有点宽泛,我们可以用特定类型的安全风险为例,如拒绝服务(DoS)攻击,分析 Java 开发者需要重点考虑的点。DoS 是一种常见的网络攻击,有人也称其为“洪水攻击”。
最常见的表现是,利用大量机器发送请求,将目标网站的带宽或者其他资源耗尽,导致其无法响应正常用户的请求。我认为,从 Java 语言的角度,更加需要重视的是程序级别的攻击,也就是利用 Java、JVM 或应用程序的瑕疵,进行低成本的 DoS 攻击,这也是想要写出安全的 Java 代码所必须考虑的。
例如:如果使用的是早期的 JDK 和 Applet 等技术,攻击者构建合法但恶劣的程序就相对容易,例如,将其线程优先级设置为最高,做一些看起来无害但空耗资源的事情。幸运的是类似技术已经逐步退出历史舞台,在 JDK 9 以后,相关模块就已经被移除。
哈希碰撞攻击,就是个典型的例子,对方可以轻易消耗系统有限的 CPU 和线程资源。从这个角度思考,类似加密、解密、图形处理等计算密集型任务,都要防范被恶意滥用,以免攻击者通过直接调用或者间接触发方式,消耗系统资源。
利用 Java 构建类似上传文件或者其他接受输入的服务,需要对消耗系统内存或存储的上限有所控制,因为我们不能将系统安全依赖于用户的合理使用。
其中特别注意的是涉及解压缩功能时,就需要防范Zip bomb等特定攻击。
另外,Java 程序中需要明确释放的资源有很多种,比如文件描述符、数据库连接,甚至是再入锁,任何情况下都应该保证资源释放成功,否则即使平时能够正常运行,也可能被攻击者利用而耗尽某类资源,这也算是可能的 DoS 攻击来源。
所以可以看出,实现安全的 Java 代码,需要从功能设计到实现细节,都充分考虑可能的安全影响。