, R$ A- u1 u2 n- E0 y 1. $hello = Filename on local machine (e.g "/tmp/phpxXuoXG") - C: U6 V' [; [ 2. $hello_size = Size in bytes of file (e.g 1024) * h- }5 E. N2 Q9 n0 _: h
3. $hello_name = The original name of the file on the remote system (e.g"c:\\temp\\hello.txt") 1 Z8 @# p7 ^6 F+ G 4. $hello_type = Mime type of uploaded file (e.g "text/plain") ) W8 u& Z& ` D: I
& ~2 B4 @* m5 a! V0 z& U7 ~ 然后,PHP程序开始处理根据"$hello"指定的文件。问题在于"$hello"不一定是一个PHP设置的变量,任何远程用户都可以指定它。如果我们使用下面的方式:8 g4 G* k6 [1 x2 N, ^0 ]
9 b( U" f* ~, F
http://vulnhost/vuln.php?hello=/etc/passwd&hello_size=10240&hello_type=text/plain&hello_name=hello.txt ; E6 F5 ]3 b8 K2 K" J5 R4 f! s' s/ m; p+ B+ ?
就导致了下面的PHP全局变量(当然POST方式也可以(甚至是Cookie)): ) f' q* D: B4 i3 PPHP代码/ @3 y0 b' s3 B: ~' L" u0 _
7 }. D: j4 f! W 1. $hello = "/etc/passwd" * d$ b* e% n, b3 A8 m
2. $hello_size = 10240 7 g, Q( F0 o' F! i. E
3. $hello_type = "text/plain" 6 b4 k$ m/ K7 t% Y 4. $hello_name = "hello.txt" ! t3 {& q( N) _$ t% k7 j$ m2 }5 n* G3 l2 W1 t- X+ A
上面的表单数据正好满足了PHP程序所期望的变量,但是这时PHP程序不再处理本应在上载者本机上的上载文件,而是处理服务器上"/etc/passwd"(通常会导致内容暴露)文件。这种攻击可以用于暴露任何敏感文件的内容。 5 F/ @5 E# B% T: z) c! T/ w5 }2 C) v
新版本的PHP使用HTTP_POST_FILES[]来决定上载文件,同时也提供了很多函数来解决这个问题,例如有一个函数用来判断某个文件是不是实际上载的文件。但是实际上肯定有很多PHP程序仍然使用旧的方法,所以也很容易受到这种攻击。& J2 |8 ~) N- U A4 I( M
4 ^% J; p, w [# o6 s
作为文件上载的攻击方法的一个变种,我们看一下下面的一段代码: 7 h0 ?7 t) W2 z, mPHP代码 # m* ^9 _, n8 ?0 x/ D b g" O2 d* O/ Q. O5 z! j- Z& @
1. <?php 0 ~7 g V; I# K! }" E, G' f6 }
2. if (file_exists($theme)) // Checks the file exists on the local system (noremote files) " D" p* \! t$ z% Y4 ` 3. include("$theme"); : P2 P# N5 |: A; M0 ]8 p) c
4. ?> - e; w+ ~+ \; c! G! f$ p( _: f: C- v ! O* h6 ?& D) v6 ^7 L! J 如果攻击者可以控制"$theme"的话,很显然它可以利用"$theme"来读取远程系统上的任何文件。攻击者的最终目标是在远程服务器上执行任意指令,但是他无法使用远程文件,因此,他必须得在远程服务器上创建一个PHP文件。这乍看起来好象是不可能的,但是文件上载帮了我们这个忙,如果攻击者先在本地机器上创建一个包含PHP代码的文件,然后创建一个包含名为"theme"的文件域的表单,最后用这个表单通过文件上载把创建的包含PHP代码的文件提交给上面的代码,PHP就会把攻击者提交的文件保存起来,并把"$theme"的值设置为攻击者提交的文件,这样file_exists()函数会检查通过,攻击者的代码也将执行。* {; S8 t1 E( U0 E" e x" c
获得执行任意指令的能力之后,攻击者显然想提升权限或者是扩大战果,而这又需要一些服务器上没有的工具集,而文件上载又一次帮了攻击者的忙。攻击者可以使用文件上载功能上载工具,把她们存在服务器上,然后利用他们执行指令的能力,使用chmod()改变文件的权限,然后执行。例如:攻击者可以绕过防火墙或IDS上载一个本地root攻击程序,然后执行,这样就获得了root权限。 & p* [ B# q5 Y - @1 }. Q/ r' m2 q, ]/ u如何通过库文件进行攻击? 4 w! P3 F5 s) d 6 _4 }# {: Y+ Z7 _) _# a @2 x3 {. A正如我们前面讨论的那样,include()和require()主要是为了支持代码库,因为我们一般是把一些经常使用的函数放到一个独立的文件中,这个独立的文件就是代码库,当需要使用其中的函数时,我们只要把这个代码库包含到当前的文件中就可以了。 2 [) `% ?% a" H0 h! ^: i, i$ [( a7 Q+ n. h
最初,人们开发和发布PHP程序的时候,为了区别代码库和主程序代码,一般是为代码库文件设置一个".inc"的扩展名,但是他们很快发现这是一个错误,因为这样的文件无法被PHP解释器正确解析为PHP代码。如果我们直接请求服务器上的这种文件时,我们就会得到该文件的源代码,这是因为当把PHP作为 Apache的模块使用时,PHP解释器是根据文件的扩展名来决定是否解析为PHP代码的。扩展名是站点管理员指定的,一般是".php", ".php3"和".php4"。如果重要的配置数据被包含在没有合适的扩展名的PHP文件中,那么远程攻击者很容易得到这些信息。" T0 ^' v( e& F* a+ u
4 w! a" T, @9 G. a1 w }$ Q/ B
最简单的解决方法就是:给每个文件都指定一个PHP文件的扩展名,这样可以很好的防止泄露源代码的问题,但是又产生了新的问题,通过请求这个文件,攻击者可能使本该在上下文环境中运行的代码独立运行,这可能导致前面讨论的全部攻击。, p5 q* @% k8 z
) L9 w! K: w- K4 J/ [下面是一个很明显的例子:. X# x0 ?* J' r. y" k1 \, a
PHP代码. w7 d" q0 ^* I8 E) |( A
0 F- ~$ @% l& m: ]. l3 M' l 1. In main.php: 1 f! M' u' c' N/ ]8 O6 T 2. <?php / X% r: T8 |2 a6 [: G2 f( J$ c6 j0 J7 ?
3. $libDir = "/libdir"; . B# U: ^# D. F5 } 4. $langDir = "$libdir/languages"; / z6 e( i# \$ y4 S# p) _ 5. ... . {6 h- l# T! v2 ?7 Q# ^ 6. include("$libdir/loadlanguage.php": $ h0 ]8 f* h6 n Z* ?/ j$ Y: G2 H
7. ?> $ F: V% Y9 N' R5 R" Q 8. " C# E) M1 ^8 s 9. In libdir/loadlanguage.php: ' M$ c- b! S. z l. o5 K* S1 k3 A
10. <?php ( d! U i& V+ U; y
11. ... @# S. {) a2 M* p
12. 3 L7 u: |' l' |
13. include("$langDir/$userLang"); $ J- c9 F. Q. z8 O. S& Z' s. F
14. ?> / b0 X$ q' Q1 n, {. f0 h% Z; Y , Q' Z4 x. h1 F* K9 ^: `3 l/ t 当"libdir/loadlanguage.php"被"main.php"调用时是相当安全的,但是因为"libdir /loadlanguage"具有".php"的扩展名,因此远程攻击者可以直接请求这个文件,并且可以任意指定"$langDir" 和"$userLang"的值。 * p# t3 u7 x2 |. b' s' l5 B; r! w: O. M N7 j3 i- @
如何通过Session文件进行攻击? " _- n5 X6 Z+ [- n c / U& J- i8 p3 C/ _8 W& iPHP 4或更新的版本提供了对sessions的支持,它的主要作用是在PHP程序中保存页与页之间的状态信息。例如,当一个用户登陆进入网站,他登陆了的这个事实以及谁登陆进入这个网站的相关信息都将被保存在session中,当他在网站中到处浏览时,所有的PHP代码都可以获得这些状态信息。 # Y4 h( c0 |. e0 J6 @- M3 s% l/ v0 j/ G, S6 V; s
事实上,当一个session启动时(实际上是在配置文件中设置为在第一次请求时自动启动),就会生成一个随机的"session id",如果远程浏览器总是在发送请求时提交这个"session id"的话,session就会一直保持。这通过Cookie很容易实现,也可以通过在每页提交一个表单变量(包含"session id")来实现。PHP程序可以用session注册一个特殊的变量,它的值会在每个PHP脚本结束后存在session文件中,也会在每个PHP脚本开始前加载到变量中。下面是一个简单的例子:5 L$ `" J' {( @2 u8 O" m
PHP代码 # q' _6 E* D4 P1 c: X& H$ f1 Y) q X: h- E7 L0 \5 |1 C0 K
1. <?php ) I/ R( \% @& K# x, r2 _/ E 2. session_destroy(); // Kill any data currently in the session 0 _9 j- K! b; ~8 U7 j; w/ n 3. $session_auth = "shaun"; - x* _, k9 l* n' J$ C9 r
4. session_register("session_auth"); // Register $session_auth as a session variable # F7 j& I$ g! }7 ^# x# y& a- M
5. ?> 0 o$ [; N& Q( \& j+ Z V0 G
0 t$ f: [2 e4 p% m6 Y
新版本的PHP都会自动把"$session_auth"的值设置为"shaun",如果它们被修改的话,以后的脚本都会自动接受修改后的值,这对无状态的Web来说的确是种很不错的工具,但是我们也应该小心。 , @# r3 v2 J5 C$ d" O# z A% m m# e1 E% z2 }! `6 r" }一个很明显的问题就是确保变量的确来自session,例如,给定上面的代码,如果后续的脚本是下面这样的话: " G7 [- T# f" R( N; i# IPHP代码, o% }) \0 P" R$ t
- t3 ^4 p7 |$ f4 E' e 1. <?php a/ X4 P; V2 x" v k* V0 _" ?
2. if (!emptyempty($session_auth)) - A4 l5 J, }( C( u
3. // Grant access to site here ! {8 H$ w- J' T$ D1 E0 F1 S2 r9 | 4. ?> / r, S" F m. y+ r) J Y% D% M. N; a- Q
. q6 h' G+ }2 N3 @, i
上面的代码假定如果"$session_auth"被赋值的话,就是从session,而不是从用户输入来赋值的,如果攻击者通过表单输入来赋值的话,他就可以获得对站点的访问权。注意攻击者必须在session注册该变量之前使用这种攻击方法,一旦变量被放进了session,就会覆盖任何表单输入。 6 A4 ] G4 u4 }% H" C4 o X' j1 T
Session数据一般是保存在文件中(位置是可配置的,一般是"/tmp"),文件名一般是类似"sess_<session id>"的形式,这个文件包含变量名称,变量类型,变量值和一些其它的数据。在多主机系统中,因为文件是以运行Web服务器的用户身份(一般是 nobody)保存的,因此恶意的站点拥有者就可以通过创建一个session文件来获得对其它站点的访问,甚至可以检查session文件中的敏感信息。 . V& k/ n, n* s8 x, z; W4 O9 [- c& f# E
Session机制也为攻击者把自己的输入保存在远程系统的文件中提供了另一个方便。对于上面的例子来说,攻击者需要在远程系统放置一个包含PHP代码的文件,如果不能利用文件上载做到的话,他通常会利用session为一个变量按照自己的意愿赋一个值,然后猜测session文件的位置,而他知道文件名是"php<session id>",所以只需猜测目录,而目录一般就是"/tmp"。% R* A: O# B! i! L; I
6 ]$ ~: |. K9 Z/ f' o, K( w: N# D+ v
另外,攻击者可以任意指定"session id"(例如"hello"),然后用这个"session id"创建一个session文件(例如"/tmp/sess_hello"),但是"session id"只能是字母和数字组合。 t$ C3 v* f5 |, B! K
! t; b+ D; a$ x如何通过数据类型进行攻击?- Y. B* m6 {# N* g% F U
$ j" s ]! `/ v, J- l+ H
PHP 具有比较松散的数据类型,变量的类型依赖于它们所处的上下文环境。例如:"$hello"开始是字符串变量,值为"",但是在求值时,就变成了整形变量"0",这有时可能会导致一些意想不到的结果。如果"$hello"的值为"000"还是为"0"是不同的,empty()返回的结果也不会为真。 " f- R |9 F- F1 Q. E$ e' [( \0 F6 ^/ b; R4 d* ?( O
PHP中的数组是关联数组,也就是说,数组的索引是字符串型的。这意味着"$hello["000"]"和"$hello[0]"也是不同的。' i( d0 s$ x5 E
9 g/ B) Z, l" Q8 X& R1 E* \& B开发程序的时候应该仔细地考虑上面的问题,例如,我们不应该在一个地方测试某个变量是否为"0",而在另外的地方使用empty()来验证。3 e) Y" x: ]& I2 }
# ]/ a# ~6 F" ~/ F
如何通过容易出错的函数进行攻击?下面是一份比较详细的容易出错的函数列表:9 }8 j9 c. U8 @! _) Y; i/ X
PHP代码 + V" f P2 A" X c3 w! Z , L& u* F+ k9 ?6 m$ l 1. <PHP代码执行> 1 s1 n: p7 A& H% C) R 2. require():读取指定文件的内容并且作为PHP代码解释 ' @( Z/ B0 R: J. S3 L 3. include():同上 % |4 v1 j. T2 Q* a7 d$ ? 4. eval():把给定的字符串作为PHP代码执行 " S& N9 v; P/ h6 g' q2 d 5. preg_replace():当与"/e"开关一起使用时,替换字符串将被解释为PHP代码 * \ M7 c- c4 _
6. # Z' S/ k4 p$ ?+ ~; B 7. <命令执行> ) A/ m8 n" P# @; U" v% F 8. exec():执行指定的命令,返回执行结果的最后一行 - }9 B5 A& L; ]. L& g
9. passthru():执行指定命令,返回所有结果到客户浏览器 6 ~4 Z6 b% x5 F( Q. x! I
10. ``:执行指定命令,返回所有结果到一个数组 * n+ I/ M1 k2 J+ `
11. system():同passthru(),但是不处理二进制数据 & [6 @/ V- n; p& C: F 12. popen():执行指定的命令,把输入或输出连接到PHP文件描述符 / V( m: i2 }- J+ @' [) g0 A3 p 13. * d; _5 E' F7 m u 14. <文件泄露> ! Q: p* \. L s4 {0 u
15. fopen():打开文件,并对应一个PHP文件描述符 ( J0 V2 Z, W( w) x
16. readfile():读取文件的内容,然后输出到客户浏览器 / a5 k; s& Z: Y 17. file():把整个文件内容读到一个数组中 3 ~& ^3 ^% B& V; L5 f. }+ u9 q 1 ?) J" M% h; O) k n" q7 C0 j- N; v 如何增强PHP的安全性? ; J* Q3 I3 A0 f( F" e' X6 l. F) C( t& A, k
我们在上面介绍的所有攻击对于缺省安装的PHP4都可以很好的实现,但是PHP的配置非常灵活,通过配置一些PHP选项,我们完全可能抵抗其中的一些攻击。下面我们按照实现的难度对一些配置进行了分类: * o: r, p; c' g5 A( y. ]/ L # D* k. H8 Z/ u*低难度, W5 [: r6 T7 U. e, p" c% g
**中低难度2 f! U1 A" X/ Y5 m* m4 A5 m4 h
***中高难度: j2 u1 J2 V* p! I5 Q3 `2 b- J
****高难度/ k8 K z/ F. e l W