8000 Switch ncrack password encryption from rsa to age-encryption by p4trickweiss · Pull Request #3247 · secureCodeBox/secureCodeBox · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ Committing with `git commit -s` will add the sign-off at the end of the commit m
- Ochi Daiki <lbfdeatq@gmail.com>
- Kai Schäfer <kai.schaefer@claranet.com>
- Joel Saß <joel.sass@iteratec.com>
- Patrick Weiss <patrick.weiss@iteratec.com>
22 changes: 13 additions & 9 deletions scanners/ncrack/.helm-docs.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ TIMING AND PERFORMANCE:
to (time-out): maximum cracking <time> for service, regardless of success so far
-T<0-5>: Set timing template (higher is faster)
--connection-limit <number>: threshold for total concurrent connections
--stealthy-linear: try credentials using only one connection against each specified host
--stealthy-linear: try credentials using only one connection against each specified host
until you hit the same host again. Overrides all other timing options.
AUTHENTICATION:
-U <filename>: username file
Expand Down Expand Up @@ -117,20 +117,24 @@ SEE THE MAN PAGE (http://nmap.org/ncrack/man.html) FOR MORE OPTIONS AND EXAMPLES
#### Password encryption

Because **Ncrack** findings are very sensitive, you probably don't want every *secureCodeBox* user to see them. In order
to address this issue we provide an option that lets you encrypt found passwords with public key crypto. Just
generate a key pair with openssl:
to address this issue we provide an option that lets you encrypt found passwords with [age encryption](https://age-encryption.org/). Just
generate a key pair with age-keygen:

```bash
openssl genrsa -out key.pem 2048
openssl rsa -in key.pem -outform PEM -pubout -out public.pem
age-keygen -o key.txt
```

To create a public key file from the generated key execute the following command:
```bash
age-keygen -y -o public-key.txt key.txt
```

After you created the public key file you have to create a kubernetes secret from that
file:
```bash
kubectl create secret generic --from-file="public.key=public.pem" <ncrack-secret-name>
kubectl create secret generic --from-file="public.key=public-key.txt" <ncrack-secret-name>
```
Now you only need to set the value *encryptPasswords.existingSecret* to the
Now you only need to set the value *encryptPasswords.existingSecret* to the
secrets name when installing the scanner

```bash
Expand All @@ -140,7 +144,7 @@ secrets name when installing the scanner
To decrypt a password from a finding use:

```bash
base64 encryptedPassword -d | openssl pkeyutl -decrypt -inkey key.pem -out decryptedPassword.txt
echo "<encrypted-password>" | age -d -i key.txt -o decrypted.txt
```

#### Setup with custom files:
Expand All @@ -152,7 +156,7 @@ kubectl create secret generic --from-file users.txt --from-file passwords.txt nc

<b> IMPORTANT: Use an extra empty line at the end of your files, otherwise the last letter of the last line will be omitted (due to a bug in k8) </b>

Now we created a secret named "ncrack-lists".
Now we created a secret named "ncrack-lists".
Before we can use the files, we have to install the Ncrack ScanType:

```bash
Expand Down
16 changes: 10 additions & 6 deletions scanners/ncrack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,22 @@ Kubernetes: `>=v1.11.0-0`
#### Password encryption

Because **Ncrack** findings are very sensitive, you probably don't want every *secureCodeBox* user to see them. In order
to address this issue we provide an option that lets you encrypt found passwords with public key crypto. Just
generate a key pair with openssl:
to address this issue we provide an option that lets you encrypt found passwords with [age encryption](https://age-encryption.org/). Just
generate a key pair with age-keygen:

```bash
openssl genrsa -out key.pem 2048
openssl rsa -in key.pem -outform PEM -pubout -out public.pem
age-keygen -o key.txt
```

To create a public key file from the generated key execute the following command:
```bash
age-keygen -y -o public-key.txt key.txt
```

After you created the public key file you have to create a kubernetes secret from that
file:
```bash
kubectl create secret generic --from-file="public.key=public.pem" <ncrack-secret-name>
kubectl create secret generic --from-file="public.key=public-key.txt" <ncrack-secret-name>
```
Now you only need to set the value *encryptPasswords.existingSecret* to the
secrets name when installing the scanner
Expand All @@ -158,7 +162,7 @@ secrets name when installing the scanner
To decrypt a password from a finding use:

```bash
base64 encryptedPassword -d | openssl pkeyutl -decrypt -inkey key.pem -out decryptedPassword.txt
echo "<encrypted-password>" | age -d -i key.txt -o decrypted.txt
```

#### Setup with custom files:
Expand Down
6 changes: 3 additions & 3 deletions scanners/ncrack/examples/dummy-ssh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ printf "THEPASSWORDYOUCREATED\n123456\npassword\n" > passwords.txt
kubectl create secret generic --from-file users.txt --from-file passwords.txt ncrack-lists

# Install dummy-ssh app. We'll use ncrack to enumerate its ssh username and password
helm install dummy-ssh oci://ghcr.io/securecodebox/helm/dummy-ssh/ --wait
helm install dummy-ssh oci://ghcr.io/securecodebox/helm/dummy-ssh --wait

# Install the ncrack scanType and set mount the files from the ncrack-lists Kubernetes secret
cat <<EOF | helm upgrade --install ncrack oci://ghcr.io/securecodebox/helm/ncrack --values -
Expand All @@ -39,9 +39,9 @@ After that you can execute the scan in this directory:
kubectl apply -f scan.yaml
```

The scan should find credentials for username 'root' with password 'THEPASSWORDYOUCREATED'.
The scan should find credentials for username 'root' with password 'THEPASSWORDYOUCREATED'.

#### Troubleshooting:
* <b> Make sure to leave a blank line at the end of each file used in the secret!</b>
* If printf doesn't create new lines, try 'echo -e "..."'
* You can show your existing secrets with 'kubectl get secrets'
* You can show your existing secrets with 'kubectl get secrets'
6 changes: 6 additions & 0 deletions scanners/ncrack/parser/__testFiles__/key.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: the secureCodeBox authors
#
# SPDX-License-Identifier: Apache-2.0
# created: 2025-08-21T12:52:24+02:00
# public key: age14t6tm8lqrhuw8wrpfxf438gl2fvpnl02tgpf3dhex6e9jejy4feqfus7jf
AGE-SECRET-KEY-1JRMTLELHHAUZ6U7SJ7HTZKUU0QX9A09PSXDVW9TVG704G9PVANXQS94G8T
1 change: 1 addition & 0 deletions scanners/ncrack/parser/__testFiles__/public-key.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
age14t6tm8lqrhuw8wrpfxf438gl2fvpnl02tgpf3dhex6e9jejy4feqfus7jf
3 changes: 3 additions & 0 deletions scanners/ncrack/parser/__testFiles__/public-key.txt.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: the secureCodeBox authors
#
# SPDX-License-Identifier: Apache-2.0
6 changes: 0 additions & 6 deletions scanners/ncrack/parser/__testFiles__/public_key.pem

This file was deleted.

3 changes: 0 additions & 3 deletions scanners/ncrack/parser/__testFiles__/public_key.pem.license

This file was deleted.

95 changes: 95 additions & 0 deletions scanners/ncrack/parser/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions scanners/ncrack/parser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"author": "iteratec GmbH",
"license": "Apache-2.0",
"dependencies": {
"age-encryption": "^0.2.4",
"xml2js": "^0.6.2"
},
"devDependencies": {}
Expand Down
86 changes: 50 additions & 36 deletions scanners/ncrack/parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
// SPDX-License-Identifier: Apache-2.0

import { parseString } from "xml2js";
import { publicEncrypt, constants } from "node:crypto";
import { readFile } from "node:fs/promises";
import * as age from "age-encryption";

export async function parse(
fileContent,
Expand All @@ -26,44 +26,42 @@ export async function parse(
}

function transformToFindings(ncrackrun, publicKey) {
return ncrackrun.service.flatMap(({ address, port, credentials = [] }) => {
const { addr: ipAddress } = address[0]["$"];
const { protocol, portid, name: portName } = port[0]["$"];
const findings = ncrackrun.service.flatMap(
({ address, port, credentials = [] }) => {
const { addr: ipAddress } = address[0]["$"];
const { protocol, portid, name: portName } = port[0]["$"];

return credentials.map((credential) => {
let { username, password } = credential["$"];
return credentials.map(async (credential) => {
let { username, password } = credential["$"];

if (publicKey) {
password = publicEncrypt(
{
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
if (publicKey) {
password = await encryptWithAGE(password, publicKey);
}

return {
name: `Credentials for Service ${portName}://${ipAddress}:${portid} discovered via bruteforce.`,
description: "",
category: "Discovered Credentials",
location: `${portName}://${ipAddress}:${portid}`,
osi_layer: "APPLICATION",
severity: "HIGH",
mitigation:
"Use a more secure password or disable the service at " +
`${portName}://${ipAddress}:${portid}`,
attributes: {
port: portid,
ip_addresses: [ipAddress],
protocol: protocol,
service: portName,
username,
password,
},
Buffer.from(password),
).toString("base64");
}
};
});
},
);

return {
name: `Credentials for Service ${portName}://${ipAddress}:${portid} discovered via bruteforce.`,
description: "",
category: "Discovered Credentials",
location: `${portName}://${ipAddress}:${portid}`,
osi_layer: "APPLICATION",
severity: "HIGH",
mitigation:
"Use a more secure password or disable the service at " +
`${portName}://${ipAddress}:${portid}`,
attributes: {
port: portid,
ip_addresses: [ipAddress],
protocol: protocol,
service: portName,
username,
password,
},
};
});
});
return Promise.all(findings);
}

function transformXML(fileContent) {
Expand All @@ -79,5 +77,21 @@ function transformXML(fileContent) {
}

async function readPublicKey(keyLocation) {
return readFile(keyLocation);
return readFile(keyLocation, "utf-8");
}

async function encryptWithAGE(password, publicKey) {
// remove newlines
publicKey = publicKey.trim();

const e = new age.Encrypter();
e.addRecipient(publicKey);
const ciphertext = await e.encrypt(password);

/**
age encrypted files (the inputs of Decrypter.decrypt and outputs of Encrypter.encrypt) are binary files, of type Uint8Array.
There is an official ASCII "armor" format, based on PEM, which provides a way to encode an encrypted file as text.
**/
const armored = age.armor.encode(ciphertext);
return armored;
}
Loading
Loading
0