XPATH注入的介绍与代码防御
软件未正确对XML中使用的特殊元素进行无害化处理,导致攻击者能够在终端系统处理XML的语法、内容或命令之前对其进行修改。在XML中,特殊元素可能包括保留字或字符,例如“<”、“>”、“"”和“&”,它们可能用于添加新数据或修改XML语法。我们发现用户可控制的输入并未由应用程序正确进行无害化处理,就在XPath查询中使用。例如,假定XML文档包含“user”名称的元素,每个元素各包含3个子元素-“name”、“password”和“account”。这时下列XPath表达式会产生名称为“jsmith”、密码为“Demo1234”的用户的帐号(如果没有这样的用户,便是空字符串):
string(//user[name/text()=''jsmith''andpassword/text()=''Demo1234'']/account/text())
以下是应用程序的示例(采用MicrosoftASP.NET和C#):
XmlDocumentXmlDoc=newXmlDocument();
XmlDoc.Load("...");
...
XPathNavigatornav=XmlDoc.CreateNavigator();
XPathExpressionexpr=nav.Compile("string(//user[name/text()=''"+TextBox1.Text+"''andpassword/text()=''"+TextBox2.Text+"'']/account/text())");
Stringaccount=Convert.ToString(nav.Evaluate(expr));
if(account=="")
{
//name+passwordpairisnotfoundintheXMLdocument-loginfailed.
}
else
{
//accountfound->Loginsucceeded.Proceedintotheapplication
}
使用此类代码时,攻击者可以注入XPath表达式(非常类似SQL注入),例如,提供以下值作为用户名:
''or1=1or''''=''
此数据会导致原始XPath的语义发生更改,使其始终返回XML文档中的第一个帐号。
这意味着虽然攻击者未提供任何有效用户名或密码,但仍将登录(作为XML文档中列出的第一个用户)。
0x02主要修复思路
未对用户输入正确执行危险字符清理,可能导致恶意攻击者可以访问存储在敏感数据资源中的信息。缓解修复:
假设所有输入都是恶意的。使用黑名单和白名单的适当组合以确保系统仅处理有效和预期的输入。
0x03Asp.Net
我们可以使用验证控件,将输入验证添加到“Web表单”页面。验证控件提供适用于所有常见类型的标准验证的易用机制-例如,测试验证日期是否有效,或验证值是否在范围内-以及进行定制编写验证的方法。此外,验证控件还使我们能够完整定制向用户显示错误信息的方式。验证控件可搭配“Web表单”页面的类文件中处理的任何控件使用,其中包括HTML和Web服务器控件。
为了确保用户输入仅包含有效值,我们可以使用以下其中一种验证控件:
a.“RangeValidator”:检查用户条目(值)是否在指定的上下界限之间。您可以检查配对数字、字母字符和日期内的范围。
b.“RegularExpressionValidator”:检查条目是否与正则表达式定义的模式相匹配。此类型的验证使您能够检查可预见的字符序列,如社会保险号码、电子邮件地址、电话号码、邮政编码等中的字符序列。
重要注意事项:验证控件不会阻止用户输入或更改页面处理流程,它们只会设置错误状态,并产生错误消息。程序员的职责是,在执行进一步的应用程序特定操作前,测试代码中控件的状态。有两种方法可检查用户输入的有效性:
1.测试常规错误状态
在您的代码中,测试页面的IsValid属性。该属性会将页面上所有验证控件的IsValid属性值汇总(使用逻辑AND)。如果将其中一个验证控件设置为无效,那么页面属性将会返回false。
2.测试个别控件的错误状态
在页面的“验证器”集合中循环,该集合包含对所有验证控件的引用。然后,您就可以检查每个验证控件的IsValid属性。
0x03J2EE
1输入数据验证
虽然为了用户的方便,可以提供“客户端”层的数据验证,但必须在“服务器”层(也就是Servlet)执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用JavaScript。一份好的设计通常需要Web应用程序框架,以提供服务器端实用程序例程,从而验证以下内容:[1]必需字段[2]字段数据类型(缺省情况下,所有HTTP请求参数都是“字符串”)[3]字段长度[4]字段范围[5]字段选项[6]字段模式[7]cookie值[8]HTTP响应好的做法是将以上例程作为“验证器”实用程序类中的静态方法实现。以下部分描述验证器类的一个示例。
[1]必需字段“始终”检查字段不为空,并且其长度要大于零,不包括行距和后面的空格
以下是如何验证必需字段的示例:
//Javaexampletovalidaterequiredfields
publicClassValidator{
...
publicstaticbooleanvalidateRequired(Stringvalue){
booleanisFieldValid=false;
if(value!=null&&value.trim().length()>0){
isFieldValid=true;
}
returnisFieldValid;
}
}
StringfieldValue=request.getParameter("fieldName");
if(Validator.validateRequired(fieldValue)){
//fieldValueisvalid,continueprocessingrequest
}
[2]输入的Web应用程序中的字段数据类型和输入参数欠佳
所有HTTP请求参数或cookie值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确。使用Java基本包装程序类,来检查是否可将字段值安全地转换为所需的基本数据类型。以下是验证数字字段(int类型)的方式的示例:
//Javaexampletovalidatethatafieldisanintnumber
publicClassValidator{
...
publicstaticbooleanvalidateInt(Stringvalue){
booleanisFieldValid=false;
try{
Integer.parseInt(value);
isFieldValid=true;
}catch(Exceptione){
isFieldValid=false;
}
returnisFieldValid;
}
...
}
...
//checkiftheHTTPrequestparameterisoftypeint
StringfieldValue=request.getParameter("fieldName");
if(Validator.validateInt(fieldValue)){
//fieldValueisvalid,continueprocessingrequest
...
}
好的做法是将所有HTTP请求参数转换为其各自的数据类型。例如,开发者应将请求参数的“integerValue”存储在请求属性中,并按以下示例所示来使用:
//ExampletoconverttheHTTPrequestparametertoaprimitivewrapperdatatype
//andstorethisvalueinarequestattributeforfurtherprocessing
StringfieldValue=request.getParameter("fieldName");
if(Validator.validateInt(fieldValue)){
//convertfieldValuetoanInteger
IntegerintegerValue=Integer.getInteger(fieldValue);
//storeintegerValueinarequestattribute
request.setAttribute("fieldName",integerValue);
}
//Usetherequestattributeforfurtherprocessing
IntegerintegerValue=(Integer)request.getAttribute("fieldName");
以下是应用程序应处理的主要Java数据类型(如上所述):
-Byte
-Short
-Integer
-Long
-Float
-Double
-Date
[3]字段长度“始终”确保输入参数(HTTP请求参数或cookie值)有最小长度和/或最大长度的限制
以下是验证userName字段的长度是否在8至20个字符之间的示例:
//Exampletovalidatethefieldlength
publicClassValidator{
...
publicstaticbooleanvalidateLength(Stringvalue,intminLength,intmaxLength){
StringvalidatedValue=value;
if(!validateRequired(value)){
validatedValue="";
}
return(validatedValue.length()>=minLength&&
validatedValue.length()<=maxLength);
}
...
}
...
StringuserName=request.getParameter("userName");
if(Validator.validateRequired(userName)){
if(Validator.validateLength(userName,8,20)){
//userNameisvalid,continuefurtherprocessing
...
}
}
[4]字段范围
始终确保输入参数是在由功能需求定义的范围内。以下是验证输入numberOfChoices是否在10至20之间的示例:
//Exampletovalidatethefieldrange
publicClassValidator{
publicstaticbooleanvalidateRange(intvalue,intmin,intmax){
return(value>=min&&value<=max);
}
}
StringfieldValue=request.getParameter("numberOfChoices");
if(Validator.validateRequired(fieldValue)){
if(Validator.validateInt(fieldValue)){
intnumberOfChoices=Integer.parseInt(fieldValue);
if(Validator.validateRange(numberOfChoices,10,20)){
//numberOfChoicesisvalid,continueprocessingrequest.
}
}
}
[5]字段选项
Web应用程序通常会为用户显示一组可供选择的选项(例如,使用SELECTHTML标记),但不能执行服务器端验证以确保选定的值是其中一个允许的选项。请记住,恶意用户能够轻易修改任何选项值。始终针对由功能需求定义的受允许的选项来验证选定的用户值。以下是针对允许的选项列表来验证用户选择的示例:
//Exampletovalidateuserselectionagainstalistofoptions
publicClassValidator{
...
publicstaticbooleanvalidateOption(Object[]options,Objectvalue){
booleanisValidValue=false;
try{
Listlist=Arrays.asList(options);
if(list!=null){
isValidValue=list.contains(value);
}
}catch(Exceptione){
}
returnisValidValue;
}
}
//Allowedoptions
String[]options={"option1","option2","option3");
//Verifythattheuserselectionisoneoftheallowedoptions
StringuserSelection=request.getParameter("userSelection");
if(Validator.validateOption(options,userSelection)){
//validuserselection,continueprocessingrequest
}
[6]字段模式
始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果userName字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]$。Java1.3或更早的版本不包含任何正则表达式包。建议将“Apache正则表达式包”(请参阅以下“资源”)与Java1.3一起使用,以解决该缺乏支持的问题。以下是执行正则表达式验证的示例:
//Exampletovalidatethatagivenvaluematchesaspecifiedpattern
//usingtheApacheregularexpressionpackage
importorg.apache.regexp.RE;
importorg.apache.regexp.RESyntaxException;
publicClassValidator{
...
publicstaticbooleanmatchPattern(Stringvalue,Stringexpression){
booleanmatch=false;
if(validateRequired(expression)){
REr=newRE(expression);
match=r.match(value);
}
returnmatch;
}
}
//VerifythattheuserNamerequestparameterisalpha-numeric
StringuserName=request.getParameter("userName");
if(Validator.matchPattern(userName,"^[a-zA-Z0-9]$")){
//userNameisvalid,continueprocessingrequest
}
Java1.4引进了一种新的正则表达式包(java.util.regex)。以下是使用新的Java1.4正则表达式包的Validator.matchPattern修订版:
//Exampletovalidatethatagivenvaluematchesaspecifiedpattern
//usingtheJava1.4regularexpressionpackage
importjava.util.regex.Pattern;
importjava.util.regexe.Matcher;
publicClassValidator{
publicstaticbooleanmatchPattern(Stringvalue,Stringexpression){
booleanmatch=false;
if(validateRequired(expression)){
match=Pattern.matches(expression,value);
}
returnmatch;
}
}
[7]cookie值
使用javax.servlet.http.Cookie对象来验证cookie值。适用于cookie值的相同的验证规则(如上所述)取决于应用程序需求(如验证必需值、验证长度等)。以下是验证必需cookie值的示例:
//Exampletovalidatearequiredcookievalue
//FirstretrieveallavailablecookiessubmittedintheHTTPrequest
Cookie[]cookies=request.getCookies();
if(cookies!=null){
//findthe"user"cookie
for(inti=0;i if(cookies[i].getName().equals("user")){
//validatewww.wang027.comthecookievalue
if(Validator.validateRequired(cookies[i].getValue()){
//validcookievalue,continueprocessingrequest
}
}
}
}
[8]HTTP响应
[8-1]过滤用户输入要保护应用程序免遭跨站点脚本编制的攻击,开发者应通过将敏感字符转换为其对应的字符实体来清理HTML。这些是HTML敏感字符:<>"''%;)(&+以下为以下示例通过将敏感字符转换为其对应的字符实体来过滤指定字符串:
//Exampletofiltersensitivedatatopreventcross-sitescripting
publicClassValidator{
...
publicstaticStringfilter(Stringvalue){
if(value==null){
returnnull;
}
StringBufferresult=newStringBuffer(value.length());
for(inti=0;i switch(value.charAt(i)){
case''<'':
result.append("<");
break;
case''>'':
result.append(">");
break;
case''"'':
result.append(""");
break;
case''\'''':
result.append("'");
break;
case''%'':
result.append("%");
break;
case'';'':
result.append(";");
break;
case''('':
result.append("(");
break;
case'')'':
result.append(")");
break;
case''&'':
result.append("&");
break;
case''+'':
result.append("+");
break;
default:
result.append(value.charAt(i));
break;
}
returnresult;
}
...
}
...
//FiltertheHTTPresponseusingValidator.filter
PrintWriterout=response.getWriter();
//setoutputresponse
out.write(Validator.filter(response));
out.close();
JavaServletAPI2.3引进了“过滤器”,它支持拦截和转换HTTP请求或响应。以下为使用Validator.filter来用“Servlet过滤器”清理响应的示例:
//ExampletofilterallsensitivecharactersintheHTTPresponseusingaJavaFilter.
//Thisexampleisforillustrationpurposessinceitwillfilterallcontentintheresponse,includingHTMLtags!
publicclassSensitiveCharsFilterimplementsFilter{
...
publicvoiddoFilter(ServletRequestrequest,
ServletResponseresponse,
FilterChainchain)
throwsIOException,ServletException{
PrintWriterout=response.getWriter();
ResponseWrapperwrapper=newResponseWrapper((HttpServletResponse)response);
chain.doFilter(request,wrapper);
CharArrayWritercaw=newCharArrayWriter();
caw.write(Validator.filter(wrapper.toString()));
response.setContentType("text/html");
response.setContentLength(caw.toString().length());
out.write(caw.toString());
out.close();
}
publicclassCharResponseWrapperextendsHttpServletResponseWrapper{
privateCharArrayWriteroutput;
publicStringtoString(){
returnoutput.toString();
}
publicCharResponseWrapper(HttpServletResponseresponse){
Superwww.baiyuewang.net(response);
output=newCharArrayWriter();
}
publicPrintWritergetWriter(){
returnnewPrintWriter(output);
}
}
}
}
[8-2]保护cookie
在cookie中存储敏感数据时,确保使用Cookie.setSecure(布尔标志)在HTTP响应中设置cookie的安全标志,以指导浏览器应该使用安全协议(如HTTPS或SSL)发送cookie。
以下为保护“用户”cookie的示例:
//Exampletosecureacookie,i.e.instructthebrowserto
//sendthecookieusingasecureprotocol
Cookiecookie=newCookie("user","sensitive");
cookie.setSecure(true);
response.addCookie(cookie);
0x04PHP
1输入数据验证
虽然为方便用户而在客户端层上提供数据验证,但仍必须始终在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用Javascript。需要Web应用程序框架,以提供服务器端实用程序例程
[1]必需字段
“始终”检查字段不为空,并且其长度要大于零,不包括行距和后面的空格。如何验证必需字段的示例:
//PHPexampletovalidaterequiredfields
functionvalidateRequired($input){
$pass=false;
if(strlen(trim($input))>0){
$pass=true;
}
return$pass;
}
if(validateRequired($fieldName)){
//fieldNameisvalid,continueprocessingrequest
}
[2]字段数据类型
输入的Web应用程序中的字段数据类型和输入参数欠佳。例如,所有HTTP请求参数或cookie值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确。
[3]字段长度
始终”确保输入参数(HTTP请求参数或cookie值)有最小长度和/或最大长度的限制。
[4]字段范围
始终确保输入参数是在由功能需求定义的范围内。
[5]字段选项
Web应用程序通常会为用户显示一组可供选择的选项(例如,使用SELECTHTML标记),但不能执行服务器端验证以确保选定的值是其中一个允许的选项。请记住,恶意用户能够轻易修改任何选项值。始终针对由功能需求定义的受允许的选项来验证选定的用户值。
[6]字段模式
始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果userName字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]+$
[7]cookie值
适用于cookie值的相同的验证规则(如上所述)取决于应用程序需求(如验证必需值、验证长度等)。
[8]HTTP响应
[8-1]过滤用户输入要保护应用程序免遭跨站点脚本编制的攻击,开发者应通过将敏感字符转换为其对应的字符实体来清理HTML。这些是HTML敏感字符:<>"''%;)(&+PHP包含一些自动化清理实用程序函数,如htmlentities():
$input=htmlentities($input,ENT_QUOTES,''UTF-8'');
此外,为了避免“跨站点脚本编制”的UTF-7变体,您应该显式定义响应的Content-Type头,例如:
header(''Content-Type:text/html;charset=UTF-8'');
?>
[8-2]保护cookie
在cookie中存储敏感数据且通过SSL来传输时,请确保先在HTTP响应中设置cookie的安全标志。这将会指示浏览器仅通过SSL连接来使用该cookie。为了保护cookie,您可以使用以下代码示例:
<$php
$value="some_value";
$time=time()+3600;
$path="/application/";
$domain=".example.com";
$secure=1;
setcookie("CookieName",$value,$time,$path,$domain,$secure,TRUE);
?>
|
|