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!
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!
READ MORE HERE