XSS的攻击原理与防御原理

xss又称跨站脚本攻击,原称为css(Cross-Site Scripting),因为和层叠样式表(Cascading Style Sheets)重名,所以又称为xss(x一般有未知的含义,还有扩展的含义)。

XSS的攻击原理

xss攻击涉及到了攻击者,用户和web server。主要是利用了网站本身设计的不严谨性,攻击者通过对网页插入恶意的攻击脚本,导致当用户在浏览网页的时候,嵌入其中的攻击脚本就会被执行,从而达到恶意攻击用户的特殊目的。攻击者通过xss攻击,可以获取到用户的cookie,然后发送给攻击者想要攻击的网站,因为跨站了,所以也称为跨站脚本攻击。

XSS的分类

根据攻击的来源,xss攻击的分类主要分为:反射型xss、存储型xss和DOM型xss三种。

反射型xss

反射型xss,也叫“非持久型xss”。用户点击攻击链接,触发了恶意脚本,服务器解析后响应,在返回的响应内容中出现攻击者的xss代码,被浏览器执行。一来一去,xss攻击脚本被web server反射回来给浏览器执行,所以称为反射型xss。

反射型xss的攻击步骤:

1、攻击者构造出特殊的URL,其中包含恶意代码;

2、用户打开带有恶意代码的URL时,网站服务端将恶意代码从URL中取出,拼接在HTML中返回给浏览器;

3、用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行;

4、恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

特点:

1、攻击脚本非持久性,没有保存在web server中,而是直接出现在了URL地址中;

2、反射型xss漏洞常见于通过URL传递参数的功能,如网站搜索、跳转等;

3、由于需要用户主动打开恶意的URL才能生效,攻击者往往会结合多种手段诱导用户点击。一般通过邮件、社交软件等方式直接发送攻击URL,通过用户的点击来达到攻击目的的。

POST的内容也可以触发反射型xss,只不过其触发条件比较苛刻,需要构造表单提交页面,并引导用户点击,所以非常少见。

存储型xss

存储型xss,也叫“持久型xss”,相比反射型xss,存储型xss是把恶意脚本保存到了web server中的,这种攻击具有较强的稳定性和持久性,危害性也更大。这样每一个访问特定网页的用户,都会受到攻击。

存储型xss的攻击步骤:

1、攻击者将恶意代码提交到目标网站的数据库中;

2、用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在HTML中返回给浏览器;

3、用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行;

4、恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

特点:

1、攻击脚本持久性,保存在web server中;

2、这种攻击常见于带有用户保存数据的网站功能,一般通过论坛发帖、商品评论、用户私信等功能(所有能够向web server输入内容的地方),将攻击脚本存储到web server中。

有时候反射型xss和存储型xss是同时使用的,比如:先通过对一个攻击url进行编码(来绕过xss filter),提交到web server(存储在web server中),然后用户在浏览页面时,如果点击该url,就会触发一个xss攻击。当然用户点击该url时,也可能会触发一个CSRF(Cross site request forgery)攻击。

DOM型xss

DOM(Document Object Model) --based 漏洞是基于文档对象模型的一种漏洞,通过修改页面的DOM节点而形成的xss漏洞。

DOM型xss的攻击步骤:

1、攻击者构造出特殊的URL,其中包含恶意代码。

2、用户打开带有恶意代码的URL。

3、用户浏览器接收到响应后解析执行,前端JavaScript取出URL中的恶意代码并执行。

4、恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。

特点:

1、攻击脚本不与服务端交互的,只与客户端上的js交互,攻击脚本放到了js中执行,然后显示出来;

2、DOM型xss也是一种反射型xss。

小结

反射型xss跟存储型xss的区别是:存储型xss非持久性,攻击脚本存在服务器里反射型xss持久性,攻击脚本存在URL里

DOM型xss跟前两种xss的区别:DOM型xss,是通过修改页面的DOM节点来形成xss的,取出和执行恶意代码由浏览器端完成,属于前端JavaScript自身的安全漏洞,而其他两种xss都属于服务端的安全漏洞

类型 存储区 插入点
存储型 XSS 后端数据库 HTML
反射型 XSS URL HTML
DOM型 XSS 后端数据库/前端存储/URL 前端 JavaScript

XSS漏洞的检测

xss探针

xss探针可检测出网站有没有对xss漏洞做最基础的防御。

在测试xss的位置写入代码,查看页面源码,看看哪些代码被过滤或者转义了。

'';!--"=&{()}

xss语句

除了xss探针以外,还可以输入最简单的测试语句


如果插入的语句原封不动的呈现在了浏览器中,那么说明:

  • 代码没有被过滤,存在xss;
  • 代码没有被执行,因为没有闭合类似textarea标签,可以查看下源码。

常用的xss检测语句


//用分号,也可以分号+空格(回车一起使用)

nmask






xss

使用GIthub上的终极xss工具

?传送门

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//\x3csVg/\x3e

它能够检测到存在于HTML属性、HTML文字内容、HTML注释、跳转链接、内联JavaScript字符串、内联CSS 样式表等多种上下文中的XSS漏洞,也能检测 eval()setTimeout()setInterval()Function()innerHTMLdocument.write()等DOMXSS漏洞,并且能绕过一些XSS过滤器。

只要在网站的各输入框中提交这个字符串,或者把它拼接到URL参数上,就可以进行检测了。

自动化扫描工具

除了手动检测之外,还可以使用自动扫描工具寻找xss漏洞,例如 Arachni、Mozilla HTTP Observatory、w3af 等。

XSS产生的原因

xss存在的根本原因是,对URL中的参数,对用户输入提交给web server的内容,没有进行充分的过滤。如果我们能够在web程序中,对用户提交的URL中的参数,和提交的所有内容,进行充分的过滤,将所有的不合法的参数和输入内容过滤掉,那么就不会导致在用户的浏览器中执行攻击者自己定制的脚本。

但是,其实充分而完全的过滤,实际上是无法实现的。因为攻击者有各种各样的神奇的,你完全想象不到的方式来绕过服务器端的过滤,最典型的就是对URL和参数进行各种的编码,比如escape,encodeURI,encodeURIComponent,8进制,10进制,16进制,来绕过xss过滤。那么我们如何来防御xss呢?

XSS攻击的防御

XSS 攻击有两大要素:

1、攻击者提交恶意代码。

2、浏览器执行恶意代码。

比较常规的思路是:对输入和URL参数进行过滤,对输出进行编码。也就是对提交的所有内容进行过滤,对url中的参数进行过滤,过滤掉会导致脚本执行的相关内容。然后对动态输出到页面的内容进行html编码,使脚本无法在浏览器中执行。虽然对输入过滤可以被绕过,但是也还是会拦截很大一部分的xss攻击

XSS filter

对输入和URL参数进行过滤(黑白名单),常用的xss filter的实现代码:

public class XssFilter implements Filter {

    public void init(FilterConfig config) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest)request);
        chain.doFilter(xssRequest, response);
    }

    public void destroy() {}
}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    HttpServletRequest orgRequest = null;

    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        orgRequest = request;
    }
    /**
     * 覆盖getParameter方法,将参数名和参数值都做xss过滤。
* 如果需要获得原始的值,则通过super.getParameterValues(name)来获取
* getParameterNames,getParameterValues和getParameterMap也可能需要覆盖 */ @Override public String getParameter(String name) { String value = super.getParameter(xssEncode(name)); if (value != null) { value = xssEncode(value); } return value; } /** * 覆盖getHeader方法,将参数名和参数值都做xss过滤。
* 如果需要获得原始的值,则通过super.getHeaders(name)来获取
* getHeaderNames 也可能需要覆盖 */ @Override public String getHeader(String name) { String value = super.getHeader(xssEncode(name)); if (value != null) { value = xssEncode(value); } return value; } /** * 将容易引起xss漏洞的半角字符直接替换成全角字符 * * @param s * @return */ private static String xssEncode(String s) { if (s == null || s.isEmpty()) { return 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; case '#': sb.append('#');// 全角井号 break; case '%': // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c processUrlEncoder(sb, s, i); break; default: sb.append(c); break; } } return sb.toString(); } public static void processUrlEncoder(StringBuilder sb, String s, int index){ if(s.length() >= index + 2){ if(s.charAt(index+1) == '3' && (s.charAt(index+2) == 'c' || s.charAt(index+2) == 'C')){ // %3c, %3C sb.append('<'); return; } if(s.charAt(index+1) == '6' && s.charAt(index+2) == '0'){ // %3c (0x3c=60) sb.append('<'); return; } if(s.charAt(index+1) == '3' && (s.charAt(index+2) == 'e' || s.charAt(index+2) == 'E')){ // %3e, %3E sb.append('>'); return; } if(s.charAt(index+1) == '6' && s.charAt(index+2) == '2'){ // %3e (0x3e=62) sb.append('>'); return; } } sb.append(s.charAt(index)); } /** * 获取最原始的request * * @return */ public HttpServletRequest getOrgRequest() { return orgRequest; } /** * 获取最原始的request的静态方法 * * @return */ public static HttpServletRequest getOrgRequest(HttpServletRequest req) { if (req instanceof XssHttpServletRequestWrapper) { return ((XssHttpServletRequestWrapper) req).getOrgRequest(); } return req; } }

然后在web.xml中配置该filter:


        xssFilter
        com.xxxxxx.filter.XssFilter
    
    
        xssFilter
        /*
    

主要的思路就是将容易导致XSS攻击的边角字符替换成全角字符<>是脚本执行和各种html标签需要的,比如

双写绕

alert(1)

替换绕过

过滤 alert 用prompt,confirm,top['alert'](1)代替绕过过滤() 用``代替绕过过滤空格 用%0a(换行符),%0d(回车符),/**/代替绕过小写转大写情况下 字符ſ大写后为S(ſ不等于s)

%00截断绕过

xss

编码绕过

实体编码
javascript:alert(1) 十六进制
javascript:alert(1) 十进制

unicode编码
javascrip\u0074:alert(1)

url编码
javascrip%74:alert(1)

fromCharCode方法绕过

String.fromCharCode(97, 108, 101, 114, 116, 40, 34, 88, 83, 83, 34, 41, 59)
eval(FromCharCode(97,108,101,114,116,40,39,120,115,115,39,41))

javascript伪协议绕过

无法闭合双引号的情况下,就无法使用onclick等事件,只能伪协议绕过,或者调用外部js

换行绕过正则匹配

onmousedown
=alert(1)

注释符

// 单行注释
 注释多行内容
 注释多行内容
<-- --> 注释多行内容
<-- --!> 注释多行内容
--> 单行注释后面内容
/* */ 多行注释
有时还可以利用浏览器的容错性,不需要注释

闭合标签空格绕过


@符号绕过url限制

https://[email protected]/j.js

其实访问的是@后面的内容

")逃逸函数后接分号

");alert(1)//

\绕过转义限制

\")
alert(1) //

XSS练习平台

以下是几个XSS攻击小游戏,开发者在网站上故意留下了一些常见的 XSS 漏洞。玩家在网页上提交相应的输入,完成 XSS 攻击即可通关。

alert(1) to win   prompt(1) to win   XSS game  XSS Challenges

参考资料

  • 前端安全系列(一):如何防止XSS攻击?
  • 浅谈跨站脚本攻击与防御
  • 面试问题如何预防xss攻击
  • xss攻击原理与解决方法
  • XSS攻击及预防
  • OWASP Top 10 - 2017

你可能感兴趣的:(web安全,XSS,跨站脚本攻击)