LK最近在对已有项目进行安全漏洞防御,现在将一些心得分享出来和大家一起讨论.主要是从下面几个方面进行漏洞修复。
1. 跨站脚本攻击漏洞(xss)
2. 敏感数据未加密传输
3. 验证机制缺陷
4. Cookie中未设HttpOnly标识
5. 危险的HTTP方法未禁用
概念
xss又叫css(cross site scripting,跨站脚本攻击),xss攻击通过在web页面插入恶意脚本,当页面被浏览时,恶意脚本会被执行,实现攻击用户的目的。
类型
1.存储型/持久型
存储型指恶意脚本会被存储在服务器端,如数据库中或者文件中。例如留言板等很容易因为输入检验不严谨导致被攻击。
2.反射型/非持久型
反射型一般是带有恶意脚本参数的URL,是一次性的。
3.DOM型
基于DOM文档对象模型的xss漏洞,客户端的恶意脚本程序可以通过DOM来动态修改页面内容,从客户端获取DOM中的数据并在本地执行。可能触发DOM型xss的属性有document.write、innerHTML、location、window.name、document.referer等。
构造方法
1.利用html标记<>进行操作
2.利用html标签属性
利用html标签支持js伪协议形式,如src,href,background、value、action、lowsrc、bgsound、dynsrc等,进行xss注入。如:
3.事件利用
可以利用html中某些动作事件,绑定恶意脚本。常用的事件有mouseover、onclick、onfocus、onerror、onload、onchange等,具体见:html事件属性
.4.利用css跨站
css样式表可作为恶意脚本载体,不需要嵌入html中,可以通过link或者@import进行引用,比较隐蔽,不过不同浏览器之间不能通用。
5.规避过滤规则
可利用大小写混淆、拼接和拆分(空格回车和tab绕过)、字符编码等方式,如:
ipt>alert(1) ipt>
//语句必须完整,有分号或者标签对。
//可以将代码用ASCII码替换,也可十进制、十六进制、八进制编码
xss
//拆分,可绕过长度限制
6.DOM方法利用
var s=document.createElement("script");
s.src="http://xxx/xxx.js";
document.getElementsByTagName("head")[0].appendChild(s);
7.location方法利用
利用location加JavaScript伪代码,将“符号”、“变量名”、“函数名” 都变成字符串,在字符串中可以使用js编码,构造payload。
防御
1前端检验
前端页面引入检测脚本------------httphijack1.1.0.js
这是git上前端大神写的对其进行了小小的修改,与h5和node相关的部分暂且不用
他主要检测了以下几方面攻击
1)所有内联事件执行的代码
2)href 属性 javascript: 内嵌的代码
3)静态脚本文件内容
4)动态添加的脚本文件内容
5)document-write添加的内容
6)iframe嵌套
具体脚本如下:
'use strict';
(function(window) {
var httphijack = function() {},
inlineEventMap = {}, //内联事件扫描记录
inlineEventId = 0, //内联事件扫描ID
scanInlineElement = false; //是否需要扫描内联事件
// 安全域,白名单
var safeList = [
/([a-zA-Z|a-zA-Z\d])+(\.)+(yy|duowan|yystatic|baidu|hiido|qq|baidu|gclick|minisplat|baidustatic|huanjuyun|sina|1931)+(\.)+[A-Za-z]{2,14}/i, //*.yy.com
/((https|http):\/\/)+([a-zA-Z|a-zA-Z\d])+(\.)+(yy|duowan|yystatic|baidu|hiido|qq|baidu|gclick|minisplat|baidustatic|huanjuyun|sina|1931)+(\.)+[A-Za-z]{2,14}/i, //http开头
/([a-zA-Z|a-zA-Z\d])+(\.)+(yy|duowan|yystatic|baidu|hiido|qq|baidu|gclick|minisplat|baidustatic|huanjuyun|sina|1931)+(:[0-9]{1,4})+(\.)+[A-Za-z]{2,14}/i, //帶端口的請求
/[a-zA-Z0-9]\:\/\/[a-zA-Z0-9_/]*/i //手机相关
];
// 危险域,黑名单
// var dangerList = [];
// 过滤class关键词
var filterClassName = [
'BAIDU_DUP_wrapper', //百度推广
'BAIDU_DSPUI_FLOWBAR'
];
// 过滤name关键词
var filterProName = [
'text',
'#text',
'IFRAME',
'SCRIPT',
'IMG'
];
// 过滤id关键词
var filterNodeId = [
'1qa2ws'
];
var inlineEventList = [
'alert',
'location'
];
// reset console
if (!console) {
window.console = {
log: function() {
return true;
}
};
}
/**
* 统计上报函数
* @param {[type]} url 拦截脚本地址
* @param {[type]} className 拦截插入元素className
* @param {[type]} eName 内联事件名称
* @param {[type]} fUrl ifrmae乔套url
*/
function hiidoStat(url, className, eName, fUrl) {
var hiidoParam = {
'eventid': 10010793,
'bak1': url,
'bak2': className,
'bak3': eName,
'parm1': fUrl
};
if( url!="" || url!=null ){
console.log("拦截脚本地址"+url)
}
if( url!="" || url!=null ){
console.log("拦截插入元素"+className)
}
if( url!="" || url!=null ){
console.log("内联事件名称"+eName)
}
if( url!="" || url!=null ){
console.log("frmae乔套url"+fUrl)
}
// h5Report(url, className, eName, fUrl);
window.on_security_interdiction && window.on_security_interdiction.call(window, hiidoParam);
}
/**
* h5性能检测统计
* @param {[type]} url 拦截脚本地址
* @param {[type]} className 拦截插入元素className
* @param {[type]} eName 内联事件名称
* @param {[type]} iframeUrl ifrmae乔套url
*/
// function h5Report(url, className, eName, iframeUrl) {
// var databody = {},
// queryStr = '';
//
// databody.url = url ? url : '';
// databody.classname = className ? className : '';
// databody.name = eName ? eName : '';
// databody.iframeurl = iframeUrl ? iframeUrl : '';
// databody.pathname = window.location.pathname;
// databody.hostname = window.location.hostname;
// databody.ua = navigator.userAgent;
//
// for (var n in databody) {
// if (databody[n] !== '') {
// queryStr += n + '=' + databody[n] + '&';
// }
// }
//
// (new Image).src = 'http://h5.yy.com/hostage/report?' + queryStr;
// }
/**
* 过滤指定关键字
* @param {[Array]} 过滤词库
* @param {[String]} value [需要验证的字符串]
* @return {[Boolean]} [false -- 验证不通过,true -- 验证通过]
*/
function filter(list, value) {
if (list === safeList) {
if (typeof(value) === 'undefined' || value === '') {
return true;
}
} else {
if (typeof(value) === 'undefined' || value === '') {
return false;
}
}
var length = list.length,
i = 0;
for (; i < length; i++) {
// 建立黑名单正则
var reg = new RegExp(list[i]);
// 存在黑名单中,拦截
if (reg.test(value.replace('https://', '').replace('http://', ''))) {
return true;
}
}
return false;
}
// 内联事件劫持
function inlineEventFilter() {
var i = 0,
obj = null;
for (obj in document) {
if (/^on./.test(obj)) {
interceptionInlineEvent(obj, i++);
}
}
}
/**
* 内联事件拦截
* @param {[String]} eventName [内联事件名]
* @param {[Number]} eventID [内联事件id]
* @return {[type]} [description]
*/
function interceptionInlineEvent(eventName, eventID) {
var isClick = (eventName === 'onclick');
document.addEventListener(eventName.substr(2), function(e) {
scanElement(e.target, isClick, eventName, eventID);
}, true);
}
/**
* 扫描元素是否存在内联事件
* @param {[DOM]} elem [DOM元素]
* @param {[Boolean]} isClick [是否是内联点击事件]
* @param {[String]} eventName [内联 on* 事件名]
* @param {[Number]} eventID [给每个内联 on* 事件一个id]
*/
function scanElement(elem, isClick, eventName, eventID) {
var flag = elem.isScan,
code = '', // 扫描内联代码
hash = 0;
// 跳过已扫描的事件
if (!flag) {
flag = elem.isScan = ++inlineEventId;
}
hash = (flag << 8) | eventID;
if (hash in inlineEventMap) {
return;
}
inlineEventMap[hash] = true;
// 非元素节点
if (elem.nodeType !== Node.ELEMENT_NODE) {
return;
}
//扫描包括 a iframe img video div 等所有可以写内联事件的元素
if (elem[eventName]) {
code = elem.getAttribute(eventName);
if (code && filter(inlineEventList, code)) {
// 注销事件
elem[eventName] = null;
hiidoStat('', '', code, '');
console.log('拦截可疑内联事件:' + code);
}
}
// 扫描 的脚本
if (isClick && elem.tagName === 'A' && elem.protocol === 'javascript:') {
code = elem.href.substr(11);
if (filter(inlineEventList, code)) {
// 注销代码
elem.href = 'javascript:void(0)';
hiidoStat('', '', code, '');
console.log('拦截可疑事件:' + code);
}
}
// 递归扫描上级元素
scanElement(elem.parentNode);
}
/**
* 主动防御 MutationEvent
* 使用 MutationObserver 进行静态插入脚本的拦截
* @return {[type]} [description]
*/
// function interceptionStaticScript() {
// var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
// // 该构造函数用来实例化一个新的 Mutation 观察者对象 Mutation 观察者对象能监听在某个范围内的 DOM 树变化
// if (!MutationObserver) return;
// var observer = new MutationObserver(function(mutations) {
// mutations.forEach(function(mutation) {
// var nodes = mutation.addedNodes;
//
// // 逐个遍历
// for (var i = 0; i < nodes.length; i++) {
// var node = nodes[i];
// // 扫描 script 与 iframe
// if (node.tagName === 'SCRIPT' || node.tagName === 'IFRAME') {
// // 拦截到可疑iframe
// if (node.tagName === 'IFRAME' && node.src && !filter(safeList, node.src)) {
// node.parentNode && node.parentNode.removeChild(node);
// hiidoStat('', 'insertIFRMAETag', '', node.src);
// // console.log('拦截到可疑iframe', node.src);
//
// } else if (node.src) {
// // 只放行白名单
// if (!filter(safeList, node.src)) {
// node.parentNode && node.parentNode.removeChild(node);
// hiidoStat(node.src, 'insertScriptTag', '', '');
// // console.log('拦截可疑静态脚本:', node.src);
// }
// }
// }
// }
// });
// });
//
// // 传入目标节点和观察选项
// // 如果 target 为 document 或者 document.documentElement
// // 则当前文档中所有的节点添加与删除操作都会被观察到d
// observer.observe(document, {
// subtree: true,
// childList: true
// });
// }
/**
* 使用 DOMNodeInserted 进行动态脚本拦截监
* 此处无法拦截,只能监测
* @return {[type]} [description]
*/
function interceptionDynamicScript() {
document.addEventListener('DOMNodeInserted', function(e) {
var node = e.target;
if (!filter(safeList, node.src) || filter(filterClassName, node.className) || filter(filterProName, node.name) || filter(filterNodeId, node.id)) {
node.parentNode.removeChild(node);
hiidoStat(node.src ? node.src : '', node.className ? node.className : '', node.name ? node.name : '', '');
console.log('拦截可以创建节点:'+ node.nodeName + ',id为:'+(node.id?node.id:''));
}
}, true);
}
/**
* 重写单个 window 窗口的 document.write 属性
* @param {[BOM]} window [浏览器window对象]
* @return {[type]} [description]
*/
function resetDocumentWrite(window) {
var overWrite = window.document.write;
window.document.write = function(string) {
if (filter(filterClassName, string) || filter(filterProName, string) || filter(filterNodeId, string)) {
hiidoStat('', string, '', '');
// console.log('拦截可疑模块:', string);
return;
}
overWrite.apply(document, arguments);
};
}
/**
* 重写单个 window 窗口的 setAttribute 属性
* @param {[BOM]} window [浏览器window对象]
* @return {[type]} [description]
*/
function resetSetAttribute(window) {
var overWrite = window.Element.prototype.setAttribute;
window.Element.prototype.setAttribute = function(name, value) {
if (this.tagName === 'SCRIPT' && /^src$/i.test(name)) {
if (!filter(safeList, value)) {
hiidoStat(value, '', '', '');
// console.log('拦截可疑模块:', value);
return;
}
}
overWrite.apply(this, arguments);
};
}
/**
* 使用 MutationObserver 对生成的 iframe 页面进行监控,
* 防止调用内部原生 setAttribute 及 document.write
* @return {[type]} [description]
*/
function defenseIframe() {
// 先保护当前页面
installHook(window);
}
/**
* 实现单个 window 窗口的 setAttribute保护
* @param {[BOM]} window [浏览器window对象]
* @return {[type]} [description]
*/
function installHook(window) {
resetSetAttribute(window);
resetDocumentWrite(window);
// MutationObserver 的不同兼容性写法
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
if (!MutationObserver) return;
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var nodes = mutation.addedNodes;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
// 给生成的 iframe 里环境也装上重写的钩子
if (node.tagName === 'IFRAME') {
node.contentWindow && installHook(node.contentWindow);
}
}
});
});
observer.observe(document, {
subtree: true,
childList: true
});
}
/**
* 使用 Object.defineProperty,锁住call和apply,使之无法被重写
* @return {[type]} [description]
*/
function lockCallAndApply() {
// 锁住 call
try {
Object.defineProperty(Function.prototype, 'call', {
value: Function.prototype.call,
// 当且仅当仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变
writable: false,
// 当且仅当该属性的 configurable 为 true 时,该属性才能够被改变,也能够被删除
configurable: false,
enumerable: true
});
// 锁住 apply
Object.defineProperty(Function.prototype, 'apply', {
value: Function.prototype.apply,
writable: false,
configurable: false,
enumerable: true
});
} catch (e) {
console && console.log(e);
}
}
/**
* 操作cookie的方法
*/
var s__cookie = {
set: function(key, val) {
var date = new Date();
date.setTime(date.getTime() + 60 * 1000); //格式化为cookie识别的时间
document.cookie = key + '=' + val + ';expires=' + date.toGMTString(); //设置cookie
},
get: function(key) {
var getCookie = document.cookie.replace(/[ ]/g, '');
var arrCookie = getCookie.split(';');
var tips;
for (var i = 0; i < arrCookie.length; i++) {
var arr = arrCookie[i].split('=');
if (key == arr[0]) {
tips = arr[1];
break;
}
}
return tips;
}
};
/**
* 重定向iframe url(页面被iframe包裹)
*/
function redirectionIframeSrc() {
var flag = 'type';
if (self !== top) {
var parentUrl = document.referrer,
length = safeList.length,
i = 0;
for (; i < length; i++) {
// 建立白名单正则
var reg = new RegExp(safeList[i], 'i');
// 存在白名单中,放行
if (reg.test(parentUrl)) {
return;
}
}
var url = location.href;
var parts = url.split('#');
if (location.search) {
parts[0] += '&' + flag + '=3';
} else {
parts[0] += '?' + flag + '=3';
}
try {
if (!s__cookie.get('HtpLocTmp')) {
top.location.href = parts.join('#');
// cookie记录这次跳转的时间点
s__cookie.set('HtpLocTmp', '1');
}
hiidoStat('', '', '', parentUrl);
// console.log('页面被嵌入iframe中:', parentUrl);
} catch (e) {
hiidoStat('', '', '', parentUrl);
// console.log('页面被嵌入iframe中, 重定向失败');
}
}
}
// 初始化方法
httphijack.init = function() {
interceptionDynamicScript();
scanInlineElement && inlineEventFilter();
// interceptionStaticScript();
lockCallAndApply();
defenseIframe();
redirectionIframeSrc();
};
if (typeof define === 'function' && define.amd) {
define('httphijack', [], function() {
return httphijack;
});
} else {
window.httphijack = httphijack;
}
// 不支持 IE8-
if (navigator.appName == 'Microsoft Internet Explorer' && (navigator.appVersion.split(';')[1].replace(/[ ]/g, '') == 'MSIE6.0' || navigator.appVersion.split(';')[1].replace(/[ ]/g, '') == 'MSIE7.0' || navigator.appVersion.split(';')[1].replace(/[ ]/g, '') == 'MSIE8.0')) {
return;
} else {
if (!(/localhost/i).test(location.host) || (navigator.appName === 'Microsoft Internet Explorer' && (navigator.appVersion.match(/7./i) !== '7.' || navigator.appVersion.match(/8./i) !== '8.'))) {
httphijack.init();
}
}
})(window);
有兴趣的话可以在git上搜里面有详细的功能介绍
2.后端过滤
xss防御的重点是后段,因为前端能做的真的很少
后段我是采用过滤器实现的
/**
* 处理xss攻击
* @author yrz
*
*/
public class RequestFilter extends OncePerRequestFilter{
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 将request通过自定义的装饰类进行装饰
XssRequestWrapper xssRequest = new XssRequestWrapper((HttpServletRequest) request);
filterChain.doFilter(xssRequest, response);
}
}
package com.sinosoft.filter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class XssRequestWrapper extends HttpServletRequestWrapper{
private HttpServletRequest request;
public XssRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
/**
* 重写getParameter方法
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value == null) {
return null;
}
value = format(value);
return value;
}
/**
* 重写getParameterMap
*/
@Override
@SuppressWarnings("unchecked")
public Map<String, String[]> getParameterMap() {
HashMap<String, String[]> paramMap = (HashMap<String, String[]>) super.getParameterMap();
paramMap = (HashMap<String, String[]>) paramMap.clone();
for (Iterator iterator = paramMap.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<String, String[]> entry = (Map.Entry<String, String[]>) iterator.next();
String [] values = entry.getValue();
for (int i = 0; i < values.length; i++) {
if(values[i] instanceof String){
values[i] = format(values[i]);
}
}
entry.setValue(values);
}
return paramMap;
}
/**
* 重写getParameterValues
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = format(values[i]);
}
return encodedValues;
}
/**
* 重写getHeader
*/
@Override
public String getHeader(String name) {
// TODO Auto-generated method stub
return format(super.getHeader(name));
}
public String filter(String message) {
if (message == null)
return (null);
message = format(message);
return message;
}
/**
* @desc 统一处理特殊字符的方法,替换掉sql和js的特殊字符
* @param name 要替换的字符
*/
private String format(String name) {
return xssEncode(name);
}
/**
* 将容易引起xss & sql漏洞的半角字符直接替换成全角字符
*
* @param s
* @return
*/
private static String xssEncode(String s) {
if (s == null || s.isEmpty()) {
return s;
}else{
s = stripXSSAndSql(s);
}
StringBuilder sb = new StringBuilder(s.length() + 16);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '>':
sb.append(">");// 转义大于号
break;
case '<':
sb.append("<");// 转义小于号
break;
// case '\'':
// sb.append("'");// 转义单引号
// break;
// case '\"':
// sb.append(""");// 转义双引号
// break;
// case '&':
// sb.append("&");// 转义&
// break;
// case '#':
// sb.append("#");// 转义#
// break;
default:
sb.append(c);
break;
}
}
return sb.toString();
}
/**
*
* 防止xss跨脚本攻击(替换,根据实际情况调整)
*/
public static String stripXSSAndSql(String value) {
if (value != null) {
// NOTE: It's highly recommended to use the ESAPI library and
// uncomment the following line to
// avoid encoded attacks.
// value = ESAPI.encoder().canonicalize(value);
// Avoid null characters
/** value = value.replaceAll("", "");***/
// Avoid anything between script tags
Pattern scriptPattern = Pattern.compile("<[\r\n| | ]*script[\r\n| | ]*>(.*?)[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid anything in a src="http://www.yihaomen.com/article/java/..." type of e-xpression
scriptPattern = Pattern.compile("src[\r\n| | ]*=[\r\n| | ]*[\\\"|\\\'](.*?)[\\\"|\\\']", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Remove any lonesome tag
scriptPattern = Pattern.compile("[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Remove any lonesome