https://github.com/plsyssec/lockdown-node-example
https://github.com/plsyssec/lockdown-node-example
Last synced: 11 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/plsyssec/lockdown-node-example
- Owner: PLSysSec
- Created: 2018-07-03T22:04:23.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2018-10-23T10:32:33.000Z (over 7 years ago)
- Last Synced: 2025-03-02T01:44:05.032Z (over 1 year ago)
- Language: JavaScript
- Size: 40 KB
- Stars: 0
- Watchers: 9
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Lockdown
Lockdown is an extension to server-side runtimes that maintains the integrity of web applications. With Lockdown,
the runtime checks the hash of each file before executing it against a list of hashes generated before the
application is deployed to a production server. This integrity check prevents Remote Code Execution from File Upload
vulnerabilities that are common with server-side scripting languages.
# Lockdown + Node.js
We have implemented Lockdown for Node.js by adding the hash checking at the module level before compiling the
code, and verifying that it matches one of the hashes that the user provides to node in a hash file. The hash file
can be specified by using either the flag `--lockdown-hashfile=` or the environment variable `LOCKDOWN_HASHFILE`.
Using either of the two methods enables Lockdown hash checking by default. The hash file must contain a list of SHA256
hashes of any code that will be executed by node (taking into account the special function wrapper that node
automatically adds to every module).
We additionally provide a special hash generation mode that can be enabled with the flag `--lockdown-gen-hashes`
which outputs the hashes without enforcing the hash checking. We use this flag to generate the hashes for the
core node modules which we explain in the hash generation section.
# Lockdown Node.js example
This repository contains a simple example to demonstrate how to work with a Lockdown-enabled node. The code
simply takes command line arguments and outputs even, odd, or NaN for each input. Our example depends on 4
popular npm packages in total which we need to generate hashes for. Some packages are dynamically required
(also referred to as lazy loading) depending on the input. The code needs at least a single input to require
`is-number`, and only if one of the inputs is indeed a number we require `is-even`.
Even though our example only depends on 4 packages, our `node_modules` directory already has 14 different
modules and a total of 34 .js files. Some npm packages come with their test files (e.g. minimist) which
we do not want to include in our TCB. We discuss in the next section some hash generation approaches
and explain what we used for this example.
## Hash generation
Generating hashes for all the files that an application depends on while keeping the TCB as small as possible is
challenging and there are different approaches each with their own shortcomings.
One approach, for example, is to write a script that requires each and every .js file our example depends
on and run it in the hash generation mode but this is very challenging as the number of files grows rapidly
with more dependencies. Additionally, if we simply require the modules themselves we could miss dynamically
required dependencies in these modules. This approach, however, is suitable for generating hashes for
the internal node modules such as `fs`.
Another approach which we use for this example is using Webpack with our own custom loader to only
generate the hashes for the files our code depends on without any extra files. Webpack take an entry
file and uses static analysis to find all the dependencies recursively. We will combine this approach
with the previous approach to get both the internal hashes as well as our code hashes. There are
some challenges when dealing with dependencies in the form of expressions that cannot be resolved
statically which we discuss in a separate document.
### Internal node modules
- We run node in the hash generation mode using the flag `--lockdown-gen-hashes` and require all
the node internal modules:
```console
$ node --lockdown-gen-hashes modules.js
Lockdown :: events.js :: f6c4ec1f5d3899866462b65a6746b77eeba6d4736836e8c94d258e9bb1fb210a
Lockdown :: internal/async_hooks.js :: 1b5cb9e96f6941ee9f32f3df38d925f5a482e13965f82f9545fa7793af9c7024
Lockdown :: internal/errors.js :: d44471a1b02a00406a59ffbf9889e06ab62e00c1f0fade194885e8eea5970d36
Lockdown :: util.js :: 4ebae400554d23853f576e4173167c766dc1c88fa3a8cad62a9c982037d8d03a
Lockdown :: internal/encoding.js :: 00e06e0efdeac644d8278dfe67195766e6b19928cc7f266bf334ac731ad81227
...
```
- These modules do not change often so we only need to generate their hashes once. In fact, these hashes
could be made available publicly online or bundled together with node
### Application and node_modules (Webpack)
- We rely on Webpack to resolve all the dependencies and use a custom loader that generates hashes for each
dependency. A simple Webpack configuration file for this example code is provided in this repository
- Our example has a total of 35 .js files (index.js and 34 in `node_modules`) but Webpack found
only 19 .js files that our code needs and generated hashes for them
- We combine the hashes generated by Webpack with the hashes for the internal node modules in one file and
run node with this file
## Running the code with Lockdown
- We can either use the flag `--lockdown-hashfile=` or the environment variable `LOCKDOWN_HASHFILE` to provide
the hash file as shown below:
```console
$ node --lockdown-hashfile=./hashlock index.js a 1
The input a is NaN
The input 1 is odd
$ LOCKDOWN_HASHFILE=./hashlock node index.js a 1
The input a is NaN
The input 1 is odd
```
## Integrity violation
- When one of the files that our code depends on changes (e.g. an attacker injects malicious code), node is able
to detect that and crash
- Below is an example of a hash violation detected by node for one of the files in our `node_modules`:
```console
$ node --lockdown-hashfile=./hashlock index.js a 1
The input a is NaN
Lockdown :: hash not found for file: ./node_modules/is-odd/index.js
node[31105]: ../src/node_contextify.cc:649:static void node::contextify::ContextifyScript::New(const v8::FunctionCallbackInfo&): Assertion `false' failed.
1: 0x8b93f0 node::Abort() [node]
2: 0x8b94df [node]
3: 0x8ec598 node::contextify::ContextifyScript::New(v8::FunctionCallbackInfo const&) [node]
4: 0xb3db6f [node]
5: 0xb3fb72 v8::internal::Builtin_HandleApiCall(int, v8::internal::Object**, v8::internal::Isolate*) [node]
6: 0x30c0cfe041bd
Aborted (core dumped)
```