x00 前言
在过去几周中,FortiGuard Labs一直在研究带有SVG(Scalable Vector Graphics)图像的Web应用。根据研究结果,我们找到了Web应用中的一些常见问题。在本文中,我们简要介绍了SVG的特点以及针对SVG图像的常见攻击面。
根据之前的研究结果,我们梳理了一些常见的SVG攻击方式,如下所示:
0x01 SVG简介
SVG的全称为 Scalable Vector Graphics(可缩放矢量图),是一种基于XML的二维矢量图格式,支持交互性及动画展示。SVG图像及具体行为由XML文本文件定义,可以通过任何文本编辑器以及绘图软件来创建并编辑。目前所有主流web浏览器都支持渲染SVG图像。
来观察一个示例,更好理解SVG图像。如下图所示,我们编写了一些代码来渲染SVG图像:
图1. simple.svg代码片段
将该图像保存为simple.svg,然后直接打开,或者将其包含在img/image/object/embed HTML标签中,如下图所示:
图2. 通过代码渲染图像
图1代码渲染生成的图像如图2所示,这是rect元素,浏览器会在x, y (100, 100)(即宽度和高度)位置渲染一个红色矩形。
0x02 使用SVG的攻击场景
虽然SVG提供了较大的灵活性,可以方便创建更多的动态web内容,但同时也引入了一些安全风险。在下文中,我们将讨论一些常见的攻击向量,我们在互联网上的一些主流站点上都观察到过这些攻击方式。
跨站脚本
我们可以通过脚本方式来访问并修改SVG文档的任何内容,这与HTML操作方式类似。默认的脚本语言为ECMAScript(与JavaScript密切相关),每个SVG元素及属性都对应已定义的DOM(Document Object Model,文档对象模型)对象。相关脚本被封装在<script>元素中。
这意味着如果web服务器允许用户上传任意SVG图像,就存在XSS(跨站脚本)安全风险。如下所示,我们将脚本存放在图像中:
图3. xss.svg代码片段
将该图像保存为xss.svg,然后直接打开,如下图所示:
图4. 直接访问该文件触发XSS
如果将该文件链接到某个HTML页面,访问该页面也可以触发,如下图所示:
图5. 通过链接文件触发XSS
JavaScript代码会在浏览器上下文中执行,这意味着攻击者可以使用该文件执行恶意行为,比如窃取用户隐私信息等。
HTML注入
在某些情况下,XSS payload会被服务端过滤,然而我们依然能够通过SVG图像的特定功能来注入HTML代码。如前文所述,SVG是基于XML的一种矢量图,因此我们无法简单将HTML内容放入其中,不然会破坏XML的语法。
为了避免这种情况,SVG提供了一个foreignObject元素,可以用来包含来自其他XML命名空间的元素。在浏览器上下文中,这部分数据很可能采用(X)HTML形式。
来看一下html.svg图像:
图6. html.svg代码片段
当我们在foreignObject内添加一个body标签以及XHTML命名空间时,可以使用xmlns属性来声明命名空间。采用这种方式,浏览器会将body标签及其所有子标签解析为属于XHTML的元素。因此,我们可以将来自SVG的任意XHTML代码渲染到页面中:
图7. HTML注入漏洞
这种方式可以运行任意HTML代码,意味着我们可以简单从SVG图像中发起类似钓鱼、绕过同源策略、CSRF之类的攻击。
XML实体:Billion Laughs Attack
由于SVG是基于XML的矢量图,因此可以支持Entity(实体)功能。Entity可以用来定义特殊字符的快捷方式,也可以声明成内部或外部实体。
我们可以通过如下方式声明内部Entity:
<!ENTITY entity-name "entity-value">
通过如下方式声明外部Entity:
<!ENTITY entity-name SYSTEM "URI/URL">
如果解析文件的XML解析器存在脆弱性,那么我们就可以滥用外部Entity功能来泄露内部数据。由于现在大家主要使用的都是现代浏览器,因此我们假设可用的解析器都经过fuzzer的严格测试,因此没那么容易被攻击。在这个前提下,这里我们主要讨论如何滥用内部Entity。
entity.svg的内部实现如下所示:
图8. entity.svg代码片段
如上图所示,我们在第2行定义lab这个Entity,然后在SVG元素中调用该实体。结果如图9所示:
图9. lab实体被加载到页面
一切非常顺利,来尝试另一个例子:entity_2.svg,如下图所示:
图10. entity_2.svg代码片段
结果如下:
图11. lab2实体被加载到页面
如上图所示,这里的文本内容被重复渲染,这表明我们可以使用Entity标签发起“ Billion Laughs ”攻击。
“ Billion Laughs ”攻击是一种DoS(拒绝服务)攻击,目标是XML文档解析器。这种攻击也被称之为XML炸弹或者指数实体攻击。
图12. billion_laughs.svg代码片段
我们的浏览器在解析这个 billion_laughs.svg数据时,只花了4~5秒就能正常响应。这是因为大多数现代浏览器已经能够能应付这种攻击,可以在渲染过程中解决该问题,因此不会造成安全风险。
拒绝服务:新型SVG “Billion Laughs”攻击
在上一节中,我们发现“ Billion Laughs ”攻击可以延缓浏览器的处理速度,浏览器需要4~5秒才能应付该攻击。不幸的是,攻击者还可以通过SVG图像,发起另一种“ Billion Laughs ”攻击,绕过这些防御措施。
这一次我们使用xlink:href来代替XML Entity。来看一下 xlink_laughs.svg所使用的payload:
图13. xlink_laughs.svg代码片段
xlink:href属性以IRI(国际资源标识)方式定义了对某个资源的引用,该链接的具体含义需根据使用该链接的每个元素的上下文来决定。
<use>元素从SVG文档中获取节点,然后将其复制到其他位置。
我们现在a0中定义circle元素,然后在a1、a2、a3……中通过xlink:href属性调用<use>元素,通过这种方式反复克隆circle。结果如下图所示:
图14. 在解析恶意SVG时,通过xlink:href发起“ Billion Laugh”攻击
需要注意的是,在最坏的情况下,大多数现代浏览器在尝试解析网站上的这张SVG图像时可能会发生崩溃,或者至少会出现无响应情况。
有趣的是,我们在测试某些开源SVG/XML过滤器时,发现这些过滤器并不能正确捕捉到图13所示的SVG图像。因此,这种错误格式的SVG图像也可能造成DoS效果。
0x03 总结
SVG图像更像HTML,而不单单是一张简单的图像。因此,我们建议web开发者尽可能不要以对象或者iframe形式加载任何SVG。Web管理员同样应当限制可以上传到站点的文件类型。
此外,任何不可信的SVG图像在被上传到服务端前都必须经过过滤处理,可以采取如下操作:
我们使用一些浏览器来直接打开这些恶意SVG文件,对比结果如下图所示:
大家可以访问我们的Github仓库下载本文使用的SVG样本。
0x04 参考资料
[1] W3C, “Scalable Vector Graphics” https://www.w3.org/TR/SVG2/ (02 September, 2019)
[2] OWASP, “The Image that called me” https://www.owasp.org/images/0/03/Mario_Heiderich_OWASP_Sweden_The_image_that_called_me.pdf (02 September, 2019)
[3] Blackhat, “Exploiting Browsers without Image Parsing Bugs” https://www.blackhat.com/docs/us-14/materials/us-14-DeGraaf-SVG-Exploiting-Browsers-Without-Image-Parsing-Bugs.pdf (02 September, 2019)
原文链接:https://www.anquanke.com/post/id/190651
我们阅读这篇文章之前,我们需要思考下,我们为什要去了解SVG,阅读了这篇文章是否可以给我们带来帮助。
1. 如果你想要一张 css或者JavaScript可以控制的图片,那么你可以考虑SVG。
2. 如果你期望图片的质量不会因为放大或缩小而降低,那么你可以考虑SVG。
3. 如果你期望网页对残障人士和视力受损的用户有更好的体验,那么你可以考虑SVG。
4. 如果你期望图片在高清设备还是低分辨率设备上,都能保持清晰和细腻的图像质量,那么你可以考虑SVG。
我们在学习SVG之前,需要先了解一下位图和矢量图。
简单来说:
详细来说:
SVG究竟什么?
是Scalable Vector Graphics的缩写,意思是可缩放矢量图形。
这是一种基于XML的二维矢量图形标准,由W3C开发的。
对于初学者来说,可以将SVG理解为一套新的HTML标签。
所以我们可以使用css和JavaScript来对标签进行操作。
很多小伙伴可能已经忘记了XML,我们回顾一下:
XML(可扩展标记语言)是一种用于描述数据的标记语言,它使用一系列简单的标记来描述数据,这些标记可以用来表示不同类型的数据元素,如标题,作者,价格等。
SVG的优势:
SVG的劣势:
设计复杂性:SVG需要具备一定的设计和制作技巧,与位图相比,SVG的设计难度较大,需要更高的技术水平。
浏览器兼容性:在一些旧版浏览器中,可能存在对SVG的兼容性问题。
渲染速度:SVG复杂度过高会降低页面渲染速度。
对于前端开发的我们,要如何去使用SVG呢?
SVG归根结底来说和JPG,PNG一样,也是一种图像格式,所以我们可以在HTML中,将SVG的路径设置为<img>的src属性。
我们也可以将SVG的代码放在HTML中,我们完全可以把SVG的代码看做成我们的HTML标签。
例如:
<svg width="500" height="500">
<circle cx="100" cy="100" r="50" fill="transparent" stroke="#000"></circle>
</svg>
我们也可以通过css的background-image或者伪元素,将SVG图片作为背景图像。
总的来说,任何需要保持清晰度,动态交互和无损缩放的图形场景,都可以考虑使用SVG。
Canvas和SVG都是用于在网页上绘制图形的工具,但它们在许多方面都存在显著的差异。以下是对Canvas和SVG的对比:
总结:Canvas和SVG各有其优势和适用场景。Canvas更适合图像密集型的游戏和需要高效渲染的应用,而SVG更适合处理矢量图形和大型渲染区域。
说了这么多,我们来开始实战,我们要在HTML中,画出一个SVG图像。
<html>
<body>
<h1>Feng SVG</h1>
<svg version="1.1" baseProfile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" stroke="red" stroke-width="4" fill="yellow" />
<circle cx="150" cy="100" r="80" fill="green" />
<text x="150" y="115" font-size="16" text-anchor="middle" fill="white">RUNOOB SVG TEST</text>
</svg>
</body>
</html>
运行后:
代码解读:
我们还可以在线设计SVG图片,我们可以直接使用该图片或者拷贝其代码复制到我们的代码中。
SVG在线编辑: c.runoob.com/more/svgedi…
示例一:
正常的矩形
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect width="300" height="100" style="fill:rgb(122,122,0);stroke-width:2;stroke:rgb(0,0,0)" />
</svg>
效果:
代码解析:
综上,这段代码会在SVG图像中绘制一个宽度为300像素、高度为100像素、填充颜色为黄色调、线条宽度为2像素、线条颜色为黑色的矩形。
示例二:
填充和边框的透明度
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect x="50" y="20" width="150" height="150" style="fill:blue;stroke:red;stroke-width:5;fill-opacity:0.5;stroke-opacity:0.5" />
</svg>
效果:
代码解析:
综上,这段代码会在一个SVG图像中绘制一个左上角坐标为(50, 20)、宽度为150像素、高度为150像素、填充颜色为蓝色、线条颜色为红色、线条宽度为5像素、填充透明度和线条透明度都为0.5的矩形。
示例三:
整个元素的透明度
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500px" height="500px">
<rect x="50" y="20" width="150" height="150" style="fill:blue;stroke:pink;stroke-width:5;opacity:0.4" />
</svg>
效果:
代码解析:
综上,这段代码会在一个500x500像素的SVG图像中绘制一个左上角坐标为(50, 20)、宽度为150像素、高度为150像素、填充颜色为蓝色、线条颜色为粉红色、线条宽度为5像素、透明度为0.4的矩形。
示例四:
圆角矩形
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500px" height="500px">
<rect x="50" y="20" rx="20" ry="20" width="150" height="160" style="fill:red;stroke:black;stroke-width:1;opacity:0.5" />
</svg>
效果:
代码解析:
综上,这段代码会在一个500x500像素的SVG图像中绘制一个左上角坐标为(50, 20)、宽度为150像素、高度为160像素、填充颜色为红色、线条颜色为黑色、线条宽度为1像素、透明度为0.5的圆角矩形。
示例:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="40" stroke="red" stroke-width="2" fill="blue" />
</svg>
效果:
代码解析:
综上,这段代码会在一个SVG图像中绘制一个圆心坐标为(100, 50)、半径为40像素、边框颜色为红色、边框宽度为2像素、填充颜色为蓝色的圆形。
示例:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500" height="500">
<ellipse cx="300" cy="80" rx="100" ry="50" style="fill:red;stroke:purple;stroke-width:2" />
</svg>
效果:
代码解析:
综上,这段代码会在一个500x500像素的SVG图像中绘制一个中心点坐标为(300, 80)、长轴半径为100像素、短轴半径为50像素的红色椭圆,线条颜色为紫色,线条宽度为2像素。
示例:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<line x1="0" y1="0" x2="200" y2="200" style="stroke:black;stroke-width:5" />
</svg>
效果:
代码解析:
综上,这段代码会在一个SVG图像中绘制一条从(0, 0)到(200, 200)的黑色线段,线条宽度为5像素。
示例一:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<polyline points="20,20 40,25 60,40 80,10 120,140 200,180" style="fill:none;stroke:black;stroke-width:3" />
</svg>
效果:
代码解析:
综上,这段代码会在一个SVG图像中绘制一个黑色的折线。这个折线有6个点,形状大致为一个不规则的六边折线。注意:因为设置了fill:none,所以该多边形线段不会进行填充,只显示其黑色边框。
示例二:
画一个五角星
<svg style="height:300px;width:300px;" xmlns="http://www.w3.org/2000/svg" version="1.1">
<polyline points="100 10,40 180,190 60,10 60,160 180" style="fill:blue;stroke:blue;stroke-width:1" />
</svg>
效果:
代码解析:
综上,这段代码会在一个300x300像素的区域内绘制一个蓝色的多边形。这个多边形有5个顶点,形状大致为一个不规则的五边形。
示例一:
<svg height="210" width="500">
<polygon points="200,10 250,190 160,210"
style="fill:red;stroke:purple;stroke-width:1"/>
</svg>
效果:
代码解析:
综上,这段代码会在一个500x210像素的SVG图像中绘制一个由三个顶点定义的多边形,填充颜色为红色,线条颜色为紫色,线条宽度为1像素。
示例二:
画一个五角星
<svg style="height:300px;width:300px;" xmlns="http://www.w3.org/2000/svg" version="1.1">
<polygon points="100 10,40 180,190 60,10 60,160 180" style="fill:none;stroke:black;stroke-width:5"/>
</svg>
效果:
代码解析:
综上,这段代码会在一个300x300像素的SVG图像中绘制一个由七个顶点定义的多边形,没有填充颜色,线条颜色为黑色,线条宽度为5像素。
路径数据:
示例:
画一个三角形
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<path d="M150 0 L75 200 L205 270 Z" />
</svg>
效果:
代码解析:
综上,这段代码将在SVG图像中绘制一个由三个直线段组成的封闭图形,其顶点坐标分别为(150, 0),(75, 200)和(205, 270)。
直接在元素属性上设置样式,比如将矩形填充色改成粉红
<svg width="400" height="400" style="border: 1px solid red;">
<rect
x="100"
y="100"
width="200"
height="100"
fill="pink"
/>
</svg>
把所有样式写在 style 属性里
<svg width="400" height="400" style="border: 1px solid red;">
<rect
x="100"
y="100"
width="200"
height="100"
style="fill: pink;"
/>
</svg>
将样式写在 <style> 标签里
<style>
.rect {
fill: pink;
}
</style>
<svg width="400" height="400" style="border: 1px solid red;">
<rect
x="100"
y="100"
width="200"
height="100"
class="rect"
/>
</svg>
将样式写在 .css 文件里,然后在页面中引入该 CSS 文件。
SVG动画可以通过多种方式实现,包括使用SMIL、CSS和JavaScript。
能让SVG不靠JavaScript与CSS就能动起来是因为使用了SMIL(Synchronized Multimedia Integration Language),是W3C的标准之一,旨在以XML格式提供多媒体的交互表现(白话点其实就是动画),是Web上动画的开路先锋,启发了Web animation与CSS animation。SVG与SMIL的开发团队合作,让SVG能利用SMIL达到如下效果:
光是这些特性就够我们组合出很多种的动画了, 使用方法也不难,只要在SVG元素内置入以下四种元素即可操作动画:
例如:
<circle cx=“56.7573”cy=“92.8179”r=“2”fill=“black”stroke=“black”stroke-width=“1”>
<set attributeName=“cy”to=“105.7318”begin=“2s”/>
</circle>
代码解析:
这段代码用于描述一个圆形,并在特定时间改变其中心点的y坐标。
综上,这段代码绘制了一个半径为2单位、填充和边框颜色均为黑色的圆形,并设置了一个动画,使圆心的y坐标在2秒后从92.8179变为105.7318。
通过设置一组 CSS 样式和关键帧,可以实现基于时间或基于事件的 SVG 动画。这种方式实现的 SVG 动画相对简单,具有易于实现、可读性好、易于维护、性能良好等优点。
下面是一个基于 CSS 的 SVG 动画示例,实现了一个圆形的旋转动画:
<svg>
<circle cx="50" cy="50" r="40" />
</svg>
<style>
circle {
fill: red;
animation: rotate 2s linear infinite;
}
@keyframes rotate {
to {
transform: rotate(360deg);
}
}
</style>
通过 JavaScript,可以对 SVG 图形进行更加自由和复杂的动画操作。JavaScript 可以对 SVG 元素的各种属性,如位置、大小、颜色、透明度、路径等进行操作,配合定时器和事件监听等方法,实现丰富多彩的 SVG动画。
下面是一个基于 JavaScript 的 SVG 动画示例,实现了一个小球自由落体,碰撞弹跳的效果:
<svg>
<circle id="ball" cx="50" cy="50" r="20" />
</svg>
<script>
let ball=document.querySelector("#ball");
let startPos=50;
let endPos=200;
let speed=3; // 设置球下落速度
let gravity=0.2; // 设置加速度
function moveBall() {
let pos=parseFloat(ball.getAttribute("cy"));
let vel=parseFloat(ball.getAttribute("data-vel")) || 0;
// 计算球的速度和位置
vel +=gravity;
pos +=vel * speed;
// 碰撞检测
if (pos + 20 > endPos) {
pos=endPos - 20;
vel=-vel * 0.8;
}
// 更新球的位置和速度
ball.setAttribute("cy", pos);
ball.setAttribute("data-vel", vel);
// 循环移动球
if (pos < endPos - 20) {
window.requestAnimationFrame(moveBall);
}
}
moveBall();
</script>
总的来说,SVG是一种强大的图形描述语言,具有可缩放性、交互性、可访问性、灵活性和跨平台兼容性等特点和优势。它可以用于创建各种复杂的二维矢量图形和富交互的Web应用,为Web设计和开发提供了更多的可能性。
随着Web技术的不断发展,相信SVG的应用范围还将不断扩大。
原文链接:https://juejin.cn/post/7322344486159106100
下这个例子显示了,在html中单击命令按钮设定svg中的矩形的填充颜色,并且调用svg的js函数FunCallByHtmlJs,产生个消息框。
在svg中,单击矩形时,设置html中的text的文本内容,并且调用html的js函数FunCallBySvgJs,产生个消息框。
svg文档以嵌入在html文档中运行。
例子在IE 6.0 + Adobe SVG Viewer 3.03中文版下测试通过。
svg文件的代码:
//文件名:Svg&HtmlInteractive.svg
<svg width="640" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="100%" onload="init(evt)" onclick="Click(evt)">
<script type="text/javascript">
var svgDoc=null ;
var svgRoot=null ;
var parentWnd=null ; //保存html的window对象
//初始化
function init(evt)
{
svgDoc=evt.target.ownerDocument ;
svgRoot=svgDoc.documentElement ; //在html中的第二种交互方式会用到
parentWnd=window.parent ; //ASV 3.0可以这么写。英文6.0版的要换种写法
if(parentWnd.document.title==null || parentWnd.document.title=='')
{
alert("请不要直接在浏览器中打开'svg'文档!");
//下面的代码作用是不提示确认关闭窗口
parentWnd.opener=null ;
parentWnd.open('', '_self') ;
parentWnd.close() ;
}
svgDoc.svgWnd=window ; //这里人为进行设定,以便在html中的第一种交互方式中可以取的到svg的window对象
}
function FunCallByHtmlJs()
{
alert('这个消息框是在html的js中调用svg的js函数产生的。') ;
}
function Click(evt)
{
var id=evt.target.id ;
if(id=='rect') //单击在矩形上,而不是背景上时
{
if(parentWnd)
{
parentWnd.txt.value='在svg中设置html中的text的文本内容' ;
parentWnd.FunCallBySvgJs() ; //调用html中的js函数
}
}
}
</script>
<rect id="background" x="0" y="0" width="100%" height="100%" fill="gray" />
<rect id="rect" x="50" y="50" width="100" height="100" fill="green" />
<text font-family="SimSun" font-size="14" fill="yellow" x="50" y="50" id="text">单击svg的矩形,设置html的text文本内容</text>
</svg>
html文件的代码:
//文件名:Svg&HtmlInteractive.html
<html>
<head>
<title>SVG与html的交互</title>
</head>
<body onload="htmInit()">
<script type=text/javascript>
var svgDoc=null;
var svgRoot=null;
var svgWnd=null; //svg的window对象
function htmInit()
{
txt.value='';
}
function FunCallBySvgJs()
{
alert('这个消息框是在svg的js中调用html的js函数产生的。');
}
function Btn1Clk()
{
//第一种方式
svgDoc=emSvg.getSVGDocument();
if (svgDoc==null) return;
svgRoot=svgDoc.documentElement;
if (svgRoot==null) return;
var rect=svgRoot.getElementById('rect');
if(rect) rect.setAttribute('fill', 'blue');
svgWnd=svgDoc.svgWnd ; //这个window对象是在svg的初始化里面添加进去的
if (svgWnd) svgWnd.FunCallByHtmlJs(); //调用svg里的js函数
}
function Btn2Clk()
{
//第二种方式
svgWnd=emSvg.window;
if(svgWnd==null) return;
svgRoot=svgWnd.svgRoot; //svgRoot在svg的js中是个全局的变量
if(svgRoot==null) return;
var rect=svgRoot.getElementById('rect');
if(rect) rect.setAttribute('fill', 'red');
svgWnd.FunCallByHtmlJs(); //调用svg里的js函数
}
</script>
<input type="button" value="设置svg中矩形的填充颜色为蓝色" onclick="Btn1Clk()" />
<input type="button" value="设置svg中矩形的填充颜色为红色" onclick="Btn2Clk()" />
<input id="txt" type="text" value="" />
<embed id="emSvg" runat="server" src="http://zg672313.blog.163.com/blog/SvgHtmlInteractive.svg" mce_src="http://zg672313.blog.163.com/blog/SvgHtmlInteractive.svg" width="100%" height="95%" wmode="transparent"/>
</body>
</html>
效果图:
另外: 在aspx 页面中,emSvg对象会找不会,应该使用 document.getElementById("emSvg") 来查找 SVG对象
*请认真填写需求信息,我们会在24小时内与您取得联系。