先展示代码
Document
这是一段完整的代码,其中主要的运行作用是就是xss过滤,当然只有这么一个功能在网页上打开是没有东西的; 我们先看下进入循环,先输入 我们设一个a,给a定义一个数组,然后在a中进行for循环,挑选其中最大的数进行删除,但是,我们看下结果: 先看代码 内容大致一样,不同的点就是将#后的提出来之后放入了一个数组中,然后在数组中进行循环删除这个可以算是上一个问题的解决方案 因为我们的代码会找 将触发元素删除只触发一次这样就是进入循环绕过了 不进循环,那我们就要在插入的语句在没有进入循环的时候提前运行 在DOM树构建过程中,遇到不同的Token有不同的处理方式。具体的判断是在 在处理Token的时候,还会用到 1 1 而当处理script的闭标签时,除了弹出相应item,还会暂停当前的DOM树构建,进入JS的执行环境。换句话说,在文档中的script标签会阻塞DOM的构造。JS环境里对DOM操作又会导致回流,为DOM树构造造成额外影响。 那么img失败的原因 打断点调试一下 此时再点击单步调试就会来到我们的代码的执行环境了。此外,这里还有一个细节就是 那么很明显, 在 神奇的事情发生了,直接弹出了窗口,点击确定以后,调试器才会走到下一行代码。而且,这个地方如果只有一个 触发流程 上文提到了一个叫 当我们没有正确闭合标签的时候,如 这里有一个非常明显的判断 这个函数是继承自父类 实验 我们可以将过滤的代码注释,并添加相关代码来验证这个事件的触发时间。 同时,我们将注入代码也再套嵌一层 img和其他payload的失败原因在于sanitizer执行的时间早于事件代码的执行时间,sanitizer将恶意代码清除了。 套嵌的svg之所以成功,是因为当页面为
运行的过程呢就是将url
#号后边的所有都提取出来放入一个建立的
我们要做的是绕过这个xss过滤,两种方法: 一、进入循环,摸清循环的规则,钻空子; 二、不进入循环
第一种方法
去看下运行结果
这里明显是少了src
证明它确实是进行了删除,但是并没有删除完,如果按我们正常的流程走它其实只会剩下img
这个标签但是并不是;
这其实是一个很有意思的案例,我们可以把它简单的举例出来:a = [2, 3, 6, 9, 7, 1]
for i in a:
print(max(a), end=',')
a.remove(max(a))
它只输出了三个值就结束了循环,我们了可以想想其中的过程,第一次循环的时候数值按顺序往下走,找到最大的值然后挑出来删除掉,这时候数组中就剩下了2,3,6,7,1;第二次循环一样,剩下2,3,6,1;第三次循环剩下2,3,1;注意这时候我们已经循环了三次了,而我们的数组中只剩下了三个值,没有办法进行第四次循环,这时候循环就结束了;(可能我表达的不是很清楚,各位可以自己在python中进行断片运行下,一步一步走能看出来);
同样的道理,在上边的html中也是这样的道理,边运行边循环,删掉了第一个之后,下一个字段就顶替了第一个字段,循环就结束了,所以这样的话我们的输入就只有src被删除掉了,onerror留了下来,但是这样虽然有,但是实际意义也并不是很大,那我们就要利用它的边运行边删除的特性来将我们这整段话插入进去;
那我们就往img
继续添加参数,看看会怎样->我们往里边插入了两个xxx=aaaaa bbbbb=cccccc
然后回车看看会有什么变化
发现留下了xxx 和 onerror 那我们就调换下位置
这样就绕过了,我们看运行后的代码就能看见它其实就是把第一个和第三个元素删除掉了,
跟上边那个选最大的并删除掉其实是一样的,这样的话就是进入循环删除无用信息后绕过循环两个循环绕过
第一种方法
那么我们可以先插入一段看下结果
结果显而易见 全删了,那么就先开始第一种方式-------进循环
进入循环的话现在我们就找一个能帮我们的,例如:form
以及form下的img
就像是这样:
form
就相当于代码中的el
,而img
就相当于其中的el.attributes
这样代码循环就会将我们插入的img下的内容删掉,并且我们的form也能触发的话,可以说是两全其美;
那我们就浅尝一下,给img随便一个值。输入;
内容依然是被删除了,前提我们要做数组,而input是可以组成数组的,有一个触发的方法有一个是onfocus,
但是他却不是form表单下,而是在input表单下而且是要把焦点放在input事件下,刚好有个全局属性叫tabindex;是可以用来聚焦的,
我们可以借用tabindex将焦点放到input中,然后用onfocus来触发form可能有戏
输入
这样就绕过成功了,但是因为一直用的tabindex将聚焦在input上所以这个弹窗会一直弹出来那么我们在加上删除的语句
第二种方法
HTMLTreeBuilder::ProcessToken(AtomicHTMLToken* token)
中进行的。AtomicHTMLToken
是代表Token的数据结构,包含了确定Token类型的字段,确定Token名字的字段等等。Token类型共有7种,kStartTag
代表开标签,kEndTag
代表闭标签,kCharacter
代表标签内的文本。所以一个会被解析成3个不同种类的Token,分别是
kStartTag
、kCharacter
和kEndTag
。在处理Token的过程中,还有一个InsertionMode
的概念,用于判断和辅助处理一些异常情况。HTMLElementStack
,一个栈的结构。当解析器遇到开标签时,会创建相应元素并附加到其父节点,然后将token和元素构成的Item压入该栈。遇到一个闭标签的时候,就会一直弹出栈直到遇到对应元素构成的item为止,这也是一个处理文档异常的办法。比如
会被浏览器正确识别成
正是借助了栈的能力。
可以发现即使代码已经执行到最后一步,但在没有退出JS环境以前依然还没有弹窗。
appendChild
被注释并不影响代码的执行,证明即使img元素没有被添加到DOM树也不影响相关资源的加载和事件的触发。alert(1)
是在页面上script标签中的代码全部执行完毕以后才被调用的。这里涉及到浏览器渲染的另外一部分内容:在DOM树构建完成以后,就会触发DOMContentLoaded
事件,接着加载脚本、图片等外部文件,全部加载完成之后触发load
事件 。
继续用断点调试svg payload为何成功。root.innerHtml = data
断下来后,点击单步调试。
,那么结果将同img一样,直到script标签结束以后才能执行相关的代码,这样的代码放到挑战里也将失败(测试单个svg时要注意,不能像img一样注释掉appendChild
那一行)。HTMLElementStack
的结构用来帮助构建DOM树,它有多个出栈函数。其中,除了PopAll
以外,大部分出栈函数最终会调用到PopCommon
函数。这两个函数代码如下:void HTMLElementStack::PopAll() {
root_node_ = nullptr;
head_element_ = nullptr;
body_element_ = nullptr;
stack_depth_ = 0;
while (top_) {
Node& node = *TopNode();
auto* element = DynamicTo
,就可能调用到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. ) 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
SVGElement
的,可以看到代码中的DispatchEvent(*Event::Create(event_type_names::kLoad));
确实触发了load事件,而前面的判断只要满足是svg元素以及对load
事件编写了相关代码即可,也就是说在这里执行了我们写的onload=alert(1)
的代码。 window.addEventListener("DOMContentLoaded", (event) => {
console.log('DOMContentLoaded')
});
window.addEventListener("load", (event) => {
console.log('load')
});
可以看到结果不出所料,最内层的svg先触发,然后再到下一层,而且是在DOM树构建完成以前就触发了相关事件;最外层的svg则得等到DOM树构建完成才能触发。小结
root.innerHtml
赋值的时候浏览器进入DOM树构建过程;在这个过程中会触发非最外层svg标签的load
事件,最终成功执行代码。所以,sanitizer执行的时间点在这之后,无法影响我们的payload