1.5 响应输出
1.5.1【必须】设置正确的HTTP响应包类型
响应包的HTTP头“Content-Type”必须正确配置响应包的类型,禁止非HTML类型的响应包设置为“text/html”。此举会使浏览器在直接访问链接时,将非HTML格式的返回报文当做HTML解析,增加反射型XSS的触发几率。
1.5.2【建议】设置安全的HTTP响应头
- X-Content-Type-Options:
建议添加“X-Content-Type-Options”响应头并将其值设置为“nosniff”,可避免部分浏览器根据其“Content-Sniff”特性,将一些非“text/html”类型的响应作为HTML解析,增加反射型XSS的触发几率。
- HttpOnly:
控制用户登录鉴权的Cookie字段 应当设置HttpOnly属性以防止被XSS漏洞/JavaScript操纵泄漏。
- X-Frame-Options:
设置X-Frame-Options响应头,并根据需求合理设置其允许范围。该头用于指示浏览器禁止当前页面在frame、iframe、embed等标签中展现。从而避免点击劫持问题。它有三个可选的值: DENY: 浏览器会拒绝当前页面加载任何frame页面; SAMEORIGIN:则frame页面的地址只能为同源域名下的页面 ALLOW-FROM origin:可以定义允许frame加载的页面地址。
Access-Control-Allow-Origin
当需要配置CORS跨域时,应对请求头的Origin值做严格过滤。
...
String currentOrigin = request.getHeader("Origin");
if (currentOrigin.equals("https://domain.qq.com")) {
response.setHeader("Access-Control-Allow-Origin", currentOrigin);
}
...
1.5.3【必须】外部输入拼接到response页面前进行编码处理
当响应“content-type”为“html”类型时,外部输入拼接到响应包中,需根据输出位置进行编码处理。编码规则:
场景 | 编码规则 |
---|---|
输出点在HTML标签之间 | 需要对以下6个特殊字符进行HTML实体编码(&, <, >, “, ‘,/)。 示例: & —> & < —> < >—> > “ —> " ‘ —> ' / —> / |
输出点在HTML标签普通属性内(如href、src、style等,on事件除外) | 要对数据进行HTML属性编码。 编码规则:除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为&#xHH;(以&#x开头,HH则是指该字符对应的十六进制数字,分号作为结束符) |
输出点在JS内的数据中 | 需要进行js编码 编码规则: 除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \xHH (以 \x 开头,HH则是指该字符对应的十六进制数字) Tips:这种场景仅限于外部数据拼接在js里被引号括起来的变量值中。除此之外禁止直接将代码拼接在js代码中。 |
输出点在CSS中(Style属性) | 需要进行CSS编码 编码规则: 除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \HH (以 \ 开头,HH则是指该字符对应的十六进制数字) |
输出点在URL属性中 | 对这些数据进行URL编码 Tips:除此之外,所有链接类属性应该校验其协议。禁止JavaScript、data和Vb伪协议。 |
以上编码规则相对较为繁琐,可参考或直接使用业界已有成熟第三方库如ESAPI.其提供一下函数对象上表中的编码规则:
ESAPI.encoder().encodeForHTML();
ESAPI.encoder().encodeForHTMLAttribute();
ESAPI.encoder().encodeForJavaScript();
ESAPI.encoder().encodeForCSS();
ESAPI.encoder().encodeForURL();
1.5.4【必须】外部输入拼接到HTTP响应头中需进行过滤
应尽量避免外部可控参数拼接到HTTP响应头中,如业务需要则需要过滤掉“\r”、”\n”等换行符,或者拒绝携带换行符号的外部输入。
1.5.5【必须】避免不可信域名的302跳转
如果对外部传入域名进行302跳转,必须设置可信域名列表并对传入域名进行校验。
为避免校验被绕过,应避免直接对URL进行字符串匹配。应通过通过URL解析函数进行解析,获取host或者domain后和白名单进行比较。
需要注意的是,由于浏览器的容错机制,域名https://www.qq.com\www.bbb.com
中的\
会被替换成/
,最终跳转到www.qq.com
。而Java的域名解析函数则无此特性。为避免解析不一致导致绕过,建议对host中的/
和#
进行替换。
参考代码:
String host="";
try {
url = url.replaceAll("[\\\\#]","/"); //替换掉反斜线和井号
host = new URL(url).getHost();
} catch (MalformedURLException e) {
e.printStackTrace();
}
if (host.endsWith(".qq.com")){
//跳转操作
}else{
return;
}
1.5.6【必须】避免通过Jsonp传输非公开敏感信息
jsonp请求再被CSRF攻击时,其响应包可被攻击方劫持导致信息泄露。应避免通过jsonp传输非公开的敏感信息,例如用户隐私信息、身份凭证等。
1.5.7【必须】限定JSONP接口的callback字符集范围
JSONP接口的callback函数名为固定白名单。如callback函数名可用户自定义,应限制函数名仅包含 字母、数字和下划线。如:[a-zA-Z0-9_-]+
1.5.8【必须】屏蔽异常栈
应用程序出现异常时,禁止将数据库版本、数据库结构、操作系统版本、堆栈跟踪、文件名和路径信息、SQL 查询字符串等对攻击者有用的信息返回给客户端。建议重定向到一个统一、默认的错误提示页面,进行信息过滤。
1.5.9【必须】模板&表达式
web view层通常通过模板技术或者表达式引擎来实现界面与业务数据分离,比如jsp中的EL表达式。这些引擎通常可执行敏感操作,如果外部不可信数据未经过滤拼接到表达式中进行解析。则可能造成严重漏洞。
下列是基于EL表达式注入漏洞的演示demo:
@RequestMapping("/ELdemo")
@ResponseBody
public String ELdemo(RepeatDTO repeat) {
ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
SimpleContext simpleContext = new SimpleContext();
String exp = "${"+repeat.getel()+"}";
ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp, String.class);
return valueExpression.getValue(simpleContext).toString();
}
外部可通过el参数,将不可信输入拼接到EL表达式中并解析。
此时外部访问:x.x.x.x/ELdemo?el=”’’.getClass().forName(‘java.lang.Runtime’).getMethod(‘exec’,’’.getClass()).invoke(‘’.getClass().forName(‘java.lang.Runtime’).getMethod(‘getRuntime’).invoke(null),’open /Applications/Calculator.app’)“ 可执行操作系统命令调出计算器。
基于以上风险:
- 应避免外部输入的内容拼接到EL表达式或其他表达式引起、模板引擎进行解析。
- 白名单过滤外部输入,仅允许字符、数字、下划线等。