Arbitrary File Read in Mailtrain by RTFM
A while ago I reported some security issues in Mailtrain, an open source self hosted newsletter app running on Node.js. This is a very brief write-up on one of the issues I found, which highlights why it’s important as a developer to understand how third-party libraries used by your application actually works.
Jsdom and the jsdom.env API
Mailtrain supports sending HTML email and allows administrators to create their own HTML templates. To implement this functionality, the application uses Jsdom and the jsdom.env API. Jsdom is a JavaScript implementation of the WHATWG DOM and HTML standards, for use with Node.js. The jsdom.env API is jsdom’s “easy mode” which is described like this in its GitHub readme:
“jsdom.env is an API that allows you to throw a bunch of stuff at it, and it will generally do the right thing.”
Calling jsdom.env flexible is something of an understatement, as its whole purpose is that you can just pass something to it and it will automatically detect what it is and then perform its magic accordingly.
When editing a campaign HTML template in Mailtrain, the content of the template is passed to the system in a parameter called html. This content is then passed to the prepareHtml() function of the application, which in turn passes it to jsdom.env.
This got me interested as I hoped to be able to get the application to execute the injected HTML and JavaScript on the server-side. Unfortunately this was not possible, although stored XSS was trivial as there are no restrictions on the input. As admin is the only role available within the application, this was not terribly exciting as it would basically just amount to self-XSS. However, in the description above it also states that URLs and file names can be used.
As it turns out, simply passing a URL or a path to a file in the html parameter will cause the server to make a request to a server or read a file on the file system, and then store the result within the database. The database entry will then be read and used as a template when the campaign email is sent out, resulting in SSRF or arbitrary file read from the server.
This was obviously something the Mailtrain developers had not considered, and the issue has been fixed so that the input is now checked before being passed to jsdom.env.
As previously mentioned, the only available role in Mailtrain is admin and only admins can create and update campaigns. There is however (at least) one stored XSS that is exploitable by an unauthenticated user and which could be leveraged to make the admin perform the necessary actions on behalf of the attacker. But that is another story.