Web网站安全头号大敌XSS漏洞解决最佳实践
引言
- 跨站
- 脚本(JavaScript、Java、 VBScript、ActiveX、 Flash 或者 HTML)
- 注入
- 执行
1. 什么是 XSS 漏洞?
2. XSS 漏洞攻击原理及攻击手段
1、盗用 cookie,获取敏感信息。
2、利用植入 Flash,通过 crossdomain 权限设置进一步获取更高权限;或者利用 Java 等得到类似的操作。
3、利用 iframe、frame、XMLHttpRequest 或上述 Flash 等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。
4、利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
5、在访问量极大的一些页面上的 XSS 可以攻击一些小型网站,实现 DDoS 攻击的效果。
<script>alert('hello world')</script>
<script src='http://www.smart4j.cn/xxx.js'></script>
<input type="button" value="评论"></input>
3. XSS 分类如下:
|
|
|
|
|
|
|
|
4. XSS 漏洞分析
- 存储
- 持久性

- 回显
- 临时性

- 不存储、不处理
- 浏览器
- 临时性

5. 三种XSS漏洞对比
|
|
|
|
|
|
|
|
|
|
|
|
- 直接注入 JavaScript 代码
- 引用外部 JS 文件
- 通过 img 标签的 src 发送数据
- 构造表单诱导用户输入账密
- 构造隐藏的 form 表单自动提交
- 页面强制跳转
- 植入文字链接、图片链接
- 获取管理员或者其他用户 Cookie,冒充用户身份登录
- 构造表单诱导用户输入账号、密码,获取账密
- 跳转到其他网站,网站流量被窃取
- 植入广告、外链等
- 通过隐藏友链提升其他网站百度权重(SEO 黑帽)
7. 植入 HTML 代码攻击及危害分析
- 构造 img 标签
- 构造 a 标签
- 构造 iframe
- 构造其他 HTML 标签
- 通过 img 标签的 src 发送数据
- 通过 img 的 onerror 触发脚本代码
- 通过 a 标签被动触发脚本代码 href/onclick
- 通过 iframe 引入第三方页面
- 直接构造文字链接或图片链接
- style 属性嵌入脚本代码 background-image:url("javascript:…");(浏览器已可防范)
- 获取管理员或者其他用户 Cookie,冒充用户身份登录
- 构造表单诱导用户输入账号、密码,获取账密
- 植入广告、外链等
- 通过隐藏友链提升其他网站百度权重(SEO 黑帽)
8. XSS 简单预防策略
replaceAll("<script", "");
...
<scr<scriptipt
——> <script
转义:
{ "<", ">", """})
全角:
{ "<, ">", "\""}
针对 json 字符串的场景
如何公用的问题
9. XSS 漏洞预防策略最佳实践
- 页面限制输入长度、特殊字符限制,后端代码限制输入长度、处理特殊字符
- Filter 过滤器统一处理(自定义处理规则、使用 Apache Text、使用 Owasp AntiSamy)
- cookie 设置 httponly,一般 servlet 容器默认 httponly 为 true
- resp.setHeader("SET-COOKIE", "JSESSIONID=" + request.getSession().getId()+ "; HttpOnly");
- DENY 不允许、SAMEORIGIN 可在相同域名页面的 frame 中展示、ALLOW-FROM uri 可在指定页的 frame 中展示
- add_header X-Frame-Options SAMEORIGIN; //在nginx的 http 或 server 节点中配置即可
- 也可通过 Filter 设置 resp.setHeader("x-frame-options","SAMEORIGIN");
- OWASP ESAPI for Java
- 显示时对字符进行转义处理,各种模板都有相关语法,注意标签的正确使用示例如下:
<!-- utext 与 text 区别 -->
- 避免 .innerHTML、.outerHTML、document.write() 的使用,应使用 .textContent、.setAttribute() 等。
- Vue/React 技术栈,避免使用 v-html/dangerouslySetInnerHTML
- 尤其注意 onclick、onerror、onload、onmouseover 、eval()、setTimeout()、setInterval() 以及 a 标签的 href
- 可使用 OWASP esapi4js : esapi.js
10. 后端服务编码实践



<dependency>
<groupId>org.owasp.antisamy</groupId>
<artifactId>antisamy</artifactId>
<version>1.5.7</version>
</dependency><!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>

/**
* @program: xss-demo
* @author: Mr.Zhang
* @create: 2021-02-21 15:45
**/
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletRequest req = (HttpServletRequest) request;
String path = req.getServletPath();
//注解配置的是urlPatterns="/*"(过滤所有请求),所以这里对不需要过滤的静态资源url,作忽略处理(大家可以依照具体需求配置)
String[] exclusionsUrls = {".js", ".gif", ".jpg", ".png", ".css", ".ico"};
for (String str : exclusionsUrls) {
if (path.contains(str)) {
chain.doFilter(request, response);
return;
}
}
chain.doFilter(new XssRequestWrapper(httpServletRequest), response);
}
}
/**
* @program: xss-demo
* @author: Mr.Zhang
* @create: 2021-02-21 15:46
**/
@Slf4j
public class XssRequestWrapper extends HttpServletRequestWrapper {
public XssRequestWrapper(HttpServletRequest request) {
super(request);
}
/**
* 获取策略文件,直接使用jar中自带的ebay策略文件
*/
private static InputStream inputStream = XssRequestWrapper.class.getClassLoader()
.getResourceAsStream("antisamy-ebay.xml");
private static Policy policy = null;
static {
try {
// 使用静态代码块处理策略对象的创建
policy = Policy.getInstance(inputStream);
} catch (PolicyException e) {
e.printStackTrace();
}
}
/**
* 使用AntiSamy进行过滤数据
* @param html
* @return
*/
private String xssClean(String html) {
String cleanHTML = "";
try {
AntiSamy antiSamy = new AntiSamy();
CleanResults scan = antiSamy.scan(html, policy);
cleanHTML = scan.getCleanHTML();
} catch (ScanException e) {
e.printStackTrace();
} catch (PolicyException e) {
e.printStackTrace();
}
return cleanHTML;
}
/**
* 重写处理请求参数的方法
* @param name
* @return
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
// 判断参数有值,如果没有值,直接返回
if (values == null) {
return null;
}
// 遍历参数数组,使用AntiSamy进行过滤
int len = values.length;
String[] newValues = new String[len];
for (int i = 0; i < len; i++) {
// 过滤前的数据
log.info("使用AntiSamy进行过滤清理,过滤清理之前的数据:{}", values[i]);
// 进行过滤
newValues[i] = xssClean(values[i]);
// 过滤后的数据
log.info("使用AntiSamy进行过滤清理,过滤清理之后的数据:{}", newValues[i]);
}
//返回过滤后的结果
return newValues;
}
/**
* 重写处理json数据的方法
* @return
* @throws IOException
*/
@Override
public ServletInputStream getInputStream() throws IOException {
// 读取流
BufferedReader reader = new BufferedReader(
new InputStreamReader(super.getInputStream(), "UTF-8"));
// 获取json格式的数据
StringBuilder sb = new StringBuilder();
String inputStr;
while ((inputStr = reader.readLine()) != null) {
sb.append(inputStr);
}
// 把json转为map
Map map = JSON.parseObject(sb.toString(), Map.class);
// 过滤前
log.info("json过滤前:{}", sb.toString());
// 对map中的value值进行AntiSamy的过滤
map.keySet().forEach(k -> {
map.put(k, xssClean(map.get(k).toString()));
});
// 过滤后
String json = JSON.toJSONString(map);
log.info("json过滤后:{}", json);
// 把json数据转为流的格式进行返回
ByteArrayInputStream bais = new ByteArrayInputStream(json.getBytes());
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) { }
@Override
public int read() throws IOException {
return bais.read();
}
};
}
}
/**
* @program: xss-demo
* @author: Mr.Zhang
* @create: 2021-02-21 15:58
**/
@Configuration
public class AntiSamyConfig {
/**
* 配置xss过滤器
* @return
*/
@Bean
public FilterRegistrationBean create() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new XssFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
}
/**
* @program: xss-demo
* @author: Mr.Zhang
* @create: 2021-02-21 15:42
**/
@Data
public class User {
private int id;
private String name;
private int age;
}
/**
* @program: xss-demo
* @author: Mr.Zhang
* @create: 2021-02-21 15:43
**/
@Slf4j
@RestController
@RequestMapping("user")
public class UserController {
/**
* 表单
* @param user
* @return
*/
@PostMapping("save")
public String save(User user) {
log.info("name={}, age={}", user.getName(), user.getAge());
return JSON.toJSONString(user);
}
/**
* json数据格式请求体
* @param user
* @return
*/
@PostMapping("json")
public String saveJson(@RequestBody User user) {
log.info("user={}", user.toString());
return JSON.toJSONString(user);
}
}




11. 能不能根本上解决问题,即浏览器自动禁止外部注入恶意脚本?
resp.setHeader("Content-Security-Policy","default-src http: https:");
<meta>
标签<meta http-equiv="Content-Security-Policy" content="form-action 'self';">
|
|
|
|
|
|
|
|
|
|
|
|
THE END