Skip to content

Commit 06cbb67

Browse files
authored
Merge pull request #42 from JupiterOne/ingest-log4j-vulns
Create Ingest Log4j Vulnerabilities script
2 parents 24315a8 + fa03f38 commit 06cbb67

File tree

8 files changed

+4464
-0
lines changed

8 files changed

+4464
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Build
2+
on: ['push']
3+
4+
jobs:
5+
docker:
6+
runs-on: ubuntu-latest
7+
8+
steps:
9+
- name: Check out source code
10+
uses: actions/checkout@v2
11+
12+
- name: Detect Dockerfile changes
13+
uses: dorny/paths-filter@v2
14+
id: filter
15+
with:
16+
filters: |
17+
projectchanged:
18+
- 'ingest-log4j-vulns/**'
19+
20+
- name: Should Build?
21+
if: steps.filter.outputs.projectchanged == 'true'
22+
run: |
23+
echo "Project changed. Need to update Docker image."
24+
echo "need_docker_build=true" >> $GITHUB_ENV
25+
26+
- name: Login to DockerHub Registry
27+
if: env.need_docker_build
28+
run: echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin docker.io
29+
30+
- name: Build the latest Docker image
31+
if: env.need_docker_build
32+
run: |
33+
cd ingest-log4j-vulns
34+
pkgver="$(jq -r .version package.json)"
35+
echo "pkgver=$pkgver" >> $GITHUB_ENV
36+
docker build --file Dockerfile --tag jupiterone/ingest-log4j-vulns:latest --tag jupiterone/ingest-log4j-vulns:$pkgver .
37+
38+
- name: Push the latest Docker image
39+
if: env.need_docker_build
40+
run: |
41+
docker push jupiterone/ingest-log4j-vulns:latest
42+
docker push jupiterone/ingest-log4j-vulns:${{ env.pkgver }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@ node_modules
33
.DS_Store
44
dist
55

6+
j1-scope-key
7+
results
8+
69
results.json
710
bulkDelete.json

ingest-log4j-vulns/Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# syntax=docker/dockerfile:1.2
2+
3+
FROM node:14-alpine
4+
5+
RUN apk update && apk upgrade && apk add wget bash
6+
7+
COPY . .
8+
9+
RUN wget https://github.com/ossie-git/log4shell_sentinel/releases/download/v1.0.0/log4shell_sentinel_v1.0.0-linux-amd64.tar.gz
10+
11+
# This should produce just the binary
12+
RUN tar -zxf log4shell_sentinel_v1.0.0-linux-amd64.tar.gz
13+
14+
# This puts it on our $PATH so our shell script works as expected
15+
RUN mv log4shell_sentinel /bin
16+
17+
RUN npm i
18+
19+
CMD ["./scan-for-log4j.sh", "/scan"]

ingest-log4j-vulns/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# JupiterOne - Script to Ingest Log4J Vulnerabilities
2+
3+
This automation example ingests the output of [log4shell_sentinel][1], a
4+
cross-platform tool that scans local filesystems and emits CSV output. This
5+
ingestion script is intended for distribution/deployment to all hosts in your
6+
environment that you would like to scan and remediate for log4j vulnerabilities.
7+
8+
## Dependencies / Installation
9+
10+
You will need to:
11+
1. Clone this repo, containing the shell and Node scripts in this example
12+
2. Run `npm install` to install dependencies
13+
3. Install an [OS/arch-appropriate binary of log4shell_sentinel][2] on each target
14+
host.
15+
16+
The ingestion script assumes `log4shell_sentinel` is available locally, and is
17+
in your system's `$PATH`.
18+
19+
You will need to export the following ENV vars for ingestion:
20+
21+
* `J1_ACCOUNT`
22+
* `J1_ACCESS_TOKEN`
23+
24+
## Usage
25+
26+
`sudo ./scan-for-log4j.sh` - by default, scan the entire filesystem, including container images (recommended)
27+
28+
`./scan-for-log4j.sh ./some/target/path` - scan only target path, additionally do not use superuser privs
29+
30+
### Usage with Docker
31+
32+
`docker run -v /target/file/path:/scan -e J1_ACCOUNT="$J1_ACCOUNT" -e J1_ACCESS_TOKEN="$J1_ACCESS_TOKEN" -e HOST_IDENTIFIER="$(hostname)" --rm jupiterone/ingest-log4j-vulns`
33+
34+
Use `-v /:/scan` to scan the entire filesystem (recommended).
35+
36+
NOTES:
37+
* This does not run as root and does not scan container images.
38+
* The `HOST_IDENTIFIER` env var is needed since this information is not available inside the running Docker container.
39+
* If desired, you may also specify `-e HOST_IP="some.ip.addr"` to provide the outer hosts' IP address.
40+
41+
## Expected Workflow:
42+
43+
The following suggested workflow can be used to identify and remediate Log4j
44+
vulnerabilities across your entire fleet of hosts.
45+
46+
Step 1: Deployment
47+
48+
Deploy this software to your hosts via MDM, Ansible, Chef, etc.
49+
50+
Step 2: Scanning
51+
52+
Periodically scan your hosts by creating a CRON job that runs every hour.
53+
54+
`0 * * * * /path/to/scan-for-log4j.sh`
55+
56+
or
57+
58+
`0 * * * * docker run -v /target/file/path:/scan -e J1_ACCOUNT="$J1_ACCOUNT" -e J1_ACCESS_TOKEN="$J1_ACCESS_TOKEN" --rm jupiterone/ingest-log4j-vulns`
59+
60+
Step 3: Monitoring in JupiterOne
61+
62+
Issue queries like the following:
63+
64+
* `Find log4j_vulnerability as v ORDER BY v._createdOn ASC` - vulnerable hosts, oldest findings first
65+
* `Find log4j_vulnerability as v return v.hostname, count(v) as vulns ORDER BY vulns DESC` - show vulnerable hosts, rank ordered by number of vulnerabilities
66+
67+
Step 4: Remediate Hosts
68+
69+
As you work to remediate hosts, the above query results will automatically return fewer results over time as these hosts' passing scans report in.
70+
71+
72+
73+
[1]: https://github.com/ossie-git/log4shell_sentinel
74+
[2]: https://github.com/ossie-git/log4shell_sentinel/releases/tag/v1.0.0
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
const { JupiterOneClient } = require("@jupiterone/jupiterone-client-nodejs")
2+
const { v4: uuid } = require('uuid');
3+
const fs = require("fs")
4+
5+
6+
const authenticate = async () => {
7+
console.log('Authenticating...');
8+
9+
const input = {
10+
accessToken: process.env.J1_ACCESS_TOKEN,
11+
account: process.env.J1_ACCOUNT,
12+
dev: process.env.J1_DEV_ENABLED
13+
};
14+
15+
const j1 = new JupiterOneClient(input);
16+
17+
await j1.init();
18+
19+
console.log('Successfully authenticated...');
20+
21+
return j1;
22+
};
23+
24+
const j1UniqueKeyFileLocation = `${process.cwd()}/j1-scope-key`
25+
26+
const getScope = () => {
27+
let scope;
28+
if (!fs.existsSync(j1UniqueKeyFileLocation)) {
29+
scope = uuid()
30+
fs.writeFileSync(j1UniqueKeyFileLocation, scope, 'utf8')
31+
} else {
32+
scope = fs.readFileSync(j1UniqueKeyFileLocation, 'utf8')
33+
}
34+
35+
return scope;
36+
}
37+
38+
// TODO: make this as a config value so shell script and node program reference same file location
39+
const inputDataFilePath = "./results"
40+
41+
const sentinelDictionary = {
42+
0: "ip",
43+
1: "hostname",
44+
2: "appname",
45+
3: "team",
46+
4: "ignore (y/n)",
47+
5: "comments",
48+
6: "md5hash",
49+
7: "timestamp",
50+
8: "container",
51+
9: "image",
52+
10: "fullpath",
53+
11: "version",
54+
}
55+
56+
const main = async () => {
57+
// Bail early if file doesn't exist
58+
// This indicates a problem upstream
59+
if (!fs.existsSync(inputDataFilePath)) return
60+
61+
// utf8 guarantees our output is returned to us as a string
62+
const input = fs.readFileSync(inputDataFilePath, "utf8")?.trim()
63+
64+
const j1 = await authenticate()
65+
const scope = getScope();
66+
67+
// Ensure at least we have an array contains empty string
68+
const lines = input?.length ? input.split(/\n/) : []
69+
70+
// Create entities to upload to J1
71+
const entities = lines.map((line) => {
72+
const sentinelDataProps = line?.toString().split(",") ?? []
73+
74+
return {
75+
_key: uuid(),
76+
_type: 'log4j_vulnerability',
77+
_class: 'Finding',
78+
displayName: sentinelDataProps[11],
79+
[sentinelDictionary[0]]: process.env.HOST_IP || sentinelDataProps[0],
80+
[sentinelDictionary[1]]: process.env.HOST_IDENTIFIER || sentinelDataProps[1],
81+
[sentinelDictionary[6]]: sentinelDataProps[6],
82+
[sentinelDictionary[7]]: sentinelDataProps[7],
83+
[sentinelDictionary[8]]: sentinelDataProps[8] === 'true',
84+
[sentinelDictionary[9]]: sentinelDataProps[9],
85+
[sentinelDictionary[10]]: sentinelDataProps[10],
86+
[sentinelDictionary[11]]: sentinelDataProps[11],
87+
}
88+
})
89+
90+
// Note: entities of 0 isn't necessarily a bad thing..
91+
// It's still needed to clear out existing data in the event
92+
// that there were previous vulnerabilities and they have since
93+
// been remediated.
94+
console.log('Entities Uploading :>> ', entities.length);
95+
96+
await j1.bulkUpload({syncJobOptions: {scope}, entities})
97+
if (entities.length) {
98+
console.log('Entities may be found with a J1QL query like "Find log4j_vulnerability"')
99+
}
100+
}
101+
102+
main().catch(console.error)

0 commit comments

Comments
 (0)