forked from rapid7/metasploit-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TotaJS CMS Code Injection in Widget Creation
- Loading branch information
Showing
2 changed files
with
475 additions
and
0 deletions.
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
documentation/modules/exploit/multi/http/totaljsms_widget_exec.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
# CVE-2019-15954: Total.js CMS 12 Widget Remote Code Execution | ||
|
||
## Introduction | ||
|
||
Total.js is a Node.js Framework for building e-commerce applications, REST services, real-time apps, or apps for Internet of Things (IoT), etc. Total.js CMS is a Content Management System (application) that is part of the Total.js framework. A commercial version is also available, and can be seen used world-wide. | ||
|
||
In Total.js CMS, a user with admin permission may be able to create a widget, and extend CMS functionalities for visitors. However, this can also be abused to upload JavaScript code that will be evaluated server side. As a result, it is possible to embed malicious JavaScript in the new widget, and gain remote code execution. | ||
|
||
## Technical Analysis | ||
|
||
In the CVE advisory, we know that the vulnerability is associated with widget creation, so this is where we start the analysis. To do this, I looked for the keyword "New widget" because that is on the widget creation page, and very quickly I found the HTML page for that, as well as the JavaScript located at: | ||
|
||
* cms/themes/admin/public/forms/widgets.html | ||
* cms/schemas/widgets.js | ||
|
||
The widgets.html file is what you actually look at when you're adding a new widget from the GUI. After filling out the fields, you would click on the "Save" button, which in HTML is this: | ||
|
||
```html | ||
<button name="submit">@(SAVE)</button> | ||
``` | ||
|
||
And the button function is handled by the following code: | ||
|
||
```javascript | ||
exports.submit = function(com) { | ||
SETTER('loading', 'show'); | ||
AJAX('POST [url]api/widgets/ REPEAT', GETR('widgets.form'), function(response) { | ||
SETTER('loading', 'hide', 1000); | ||
if (response.success) { | ||
SETTER('snackbar', 'success', '@(Widget has been saved successfully.)'); | ||
EXEC('widgets/refresh'); | ||
com.hide(); | ||
} | ||
}); | ||
}; | ||
``` | ||
|
||
The following URI is important because it tells us the route: | ||
|
||
```javascript | ||
AJAX('POST [url]api/widgets/ REPEAT' ... | ||
``` | ||
The route map can be found in admin.js, and our code indicates we are looking at this route: | ||
```javascript | ||
// MODEL: /schema/widgets.js | ||
// ... Other routes ... | ||
ROUTE('POST #admin/api/widgets/ *Widget --> @save'); | ||
// ... Other routes... | ||
``` | ||
The JavaScript comment actually reveals which JS file is responsible for the widgets routes, so clearly we need to be looking at widgets.js. The route also indicates we should be looking at a `save` function, which links to `setSave`, which starts the saving process. | ||
During the saving process, it goes through a refreshing stage (in the `refresh` function). Although there is a lot going on, the most interesting line is this: | ||
```javascript | ||
var obj = compile(item.body); // Line 309 (widgets.js) | ||
``` | ||
The `compile` function parses the source code for the new widget. Apparently, the JavaScript tag is a bit customized, for example, this isn't the standard JavaScript tag prefix, it is more specific to Total.JS: | ||
```javascript | ||
var body = html.substring(beg, end); | ||
var beg = body.indexOf('>') + 1; | ||
var type = body.substring(0, beg); | ||
|
||
body = body.substring(beg); | ||
raw = raw.replace(type + body + '</script>', ''); | ||
|
||
body = body.trim(); | ||
|
||
if (type.indexOf('html') !== -1 || type.indexOf('plain') !== -1) | ||
body_template = body; | ||
else if (type.indexOf('total') !== -1 || type.indexOf('totaljs') !== -1) | ||
body_total = body; | ||
else if (type.indexOf('editor') !== -1) | ||
body_editor = body; | ||
else | ||
body_script = body; | ||
``` | ||
After parsing, the code could be stored in a few different ways. Specifically we want to watch where these are going in code: | ||
```javascript | ||
// Around line 258 in widgets.js | ||
obj.js = body_script; | ||
// ... code ... | ||
obj.editor = body_editor; | ||
// ... code ... | ||
obj.template = body_template; | ||
// ... code ... | ||
obj.total = body_total; | ||
// ... code ... | ||
``` | ||
So that's pretty much for the `compile` function, and back to the `refresh` function. Now that we have the parsed code, let's see what `refresh` is doing with the object members we're interested in watching. Well, there are some interesting ones, for example, this is what happens to `obj.total`: | ||
```javascript | ||
if (obj.total) { | ||
var o = new WidgetInstace(); | ||
try { | ||
(new Function('exports', obj.total))(o); | ||
} catch (e) { | ||
WARNING.message = 'Widget <b>{0}</b> exception: <b>{1}</b>'.format(item.name, e.message); | ||
ADMIN.notify(WARNING); | ||
} | ||
obj.total = o; | ||
rebuild = true; | ||
} | ||
``` | ||
As you can see here, if we have a JavaScript code block that starts like this: | ||
```javascript | ||
<script total> | ||
// ... something ... | ||
</script> | ||
``` | ||
Then that code goes to `obj.total`, and that gets executed as a new function. To mimic that code execution, open up the Developer's Tools in your browser, enter the following (which is basically what the code above is doing): | ||
```javascript | ||
function WidgetInstance() {} | ||
var o = new WidgetInstance(); | ||
(new Function('exports', 'console.log("Hello World!");'))(o); | ||
``` | ||
And you should see that `console.log` is executed (which represents the user-provided script): | ||
``` | ||
> function WidgetInstance() {} | ||
var o = new WidgetInstance(); | ||
(new Function('exports', 'console.log("Hello World!");'))(o); | ||
> VM33:3 Hello World! | ||
``` |
Oops, something went wrong.