How CVE-2022-24785 MomentJS Path Traversal Works: Detailed Exploit Guide

CVE-2022-24785

Description

Moment.js is a JavaScript date library for parsing, validating, manipulating, and formatting dates. A path traversal vulnerability impacts npm (server) users of Moment.js between versions 1.0.1 and 2.29.1, especially if a user-provided locale string is directly used to switch moment locale. This problem is patched in 2.29.2, and the patch can be applied to all affected versions. As a workaround, sanitize the user-provided locale name before passing it to Moment.js.

Identifying the vulnerability

We decided to download an affected version of MomentJS locally via npm.

Inside of Moment-JS/node_modules/moment/src/lib/locale/locales.js there is a function named loadLocale which takes the value of name:

function loadLocale(name) { var oldLocale = null, aliasedRequire; if ( locales[name] === undefined && typeof module !== 'undefined' && module && module.exports ) { try { oldLocale = globalLocale._abbr; aliasedRequire = require; aliasedRequire('./locale/' + name); getSetGlobalLocale(oldLocale); } catch (e) { locales[name] = null; } } return locales[name];
}

The issue occurs on line 14 where the loadLocale() function dynamically requires a module based on user input (name). require() is a Node.js function used to include and load modules or JavaScript files into a Node.js application.


const aliasedRequire = require; aliasedRequire('./locale/' + name);

If we control the name parameter we could possibly pass a traversal based string:


const name = '../../someMaliciousModule'; aliasedRequire('./locale/' + name);

This could load ./locale/../../uploads/someMaliciousModule, potentially exposing sensitive files, or even leading to Remote Code Execution (RCE).

Proof of concept

We wrote a basic application which uses the vulnerable function to demonstrate the vulnerability. Below is the app.js code:

const express = require('express');
const moment = require('moment'); const app = express();
const port = 1337; app.get('/time', (req, res) => { const locale = req.query.locale || 'en'; const currentTime = moment().locale(locale).format('LLLL'); res.send(`Current time (${locale}): ${currentTime}`);
}); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`);
});

The application listens on localhost:1337 and has an endpoint /time that accepts a query parameter locale. The application assigns the value of req.query.locale to the variable locale, defaulting to 'en' if req.query.locale is not provided. For example, if the query string is ?locale=fr, then locale would be 'fr'. On the backend, the application dynamically loads a module corresponding to the specified locale using aliasedRequire();

However, passing ?locale=../../../../../../../etc/passwd for example, does not work. When using require() in Node.js, it attempts to load JavaScript modules or files. If we were to pass a value like ../../../../../../etc/passwd to require('./locale/' + somevalue), Node.js would attempt to resolve this path relative to the current working directory of the application. However, Node.js does not directly read arbitrary files like /etc/passwd through require() because it expects modules or JavaScript files to load.

Now, let’s assume the application has a file upload functionality which allows us to upload and store notes. We could use this to achieve RCE.

The path traversal combined with the ability to upload a file even .txt or note (no extension) provides us with RCE due to require();.

Is the patch secure?

Let’s review the patch code for the latest version of Moment JS.

npm install moment@latest

function isLocaleNameSane(name) { return !!(name && name.match('^[^/\\\\]*$'));
}

As of right now, there is no current way to bypass this regular expression. I’ve tried multiple techniques.

Credits

This discovery was a joint effort between me and my good friend Isira Adithya. Below will be Isiras Twitter/X, and his LinkedIn!

Twitter/X

LinkedIn

Well, that’s all.

Essentially, that’s all. It’s a very basic vulnerability! As far as I am aware, no one has covered a proof of concept for this vulnerability, so this is pretty cool for everyone to see.

A Twitter/X follow is always appreciated!

Twitter/X

READ MORE HERE