-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Nikto boefje that scans for outdated software #3409
base: main
Are you sure you want to change the base?
Changes from all commits
16726b1
bc3a7c8
09882a6
022ac4e
7572d1e
a1765f1
6f84904
0ce1f35
0e057bb
50e85ae
6af750e
7b62c25
45c000f
ac6ca62
5ae981e
60570dd
09ea726
1322be6
d57565a
0712f4d
b3a3252
f8d3294
4d790c7
d4f92b7
6d39582
723fd55
13ca4de
cb8d136
0e74dac
2d96eff
b050735
9954e62
0ace647
6858fc7
56df40a
491247d
ef25331
c6e90f1
c6647da
0c169ba
346e170
8d5cba0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
FROM perl:5.40 | ||
|
||
WORKDIR /app | ||
RUN apt update | ||
RUN apt install -y git | ||
RUN apt install -y nodejs | ||
|
||
RUN git clone https://github.com/sullo/nikto | ||
|
||
ARG BOEFJE_PATH=./boefjes/plugins/kat_nikto | ||
COPY $BOEFJE_PATH ./ | ||
|
||
ENTRYPOINT [ "node", "./" ] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"id": "nikto", | ||
"name": "Nikto", | ||
"description": "Uses Nikto", | ||
"consumes": [ | ||
"HostnameHTTPURL" | ||
], | ||
"environment_keys": [ | ||
"PROXYHOST", | ||
"PROXYPORT", | ||
"PROXYUSER", | ||
"PROXYPASS", | ||
"USERAGENT" | ||
], | ||
"scan_level": 3, | ||
"oci_image": "openkat/nikto", | ||
"oci_arguments": [] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Nikto | ||
|
||
Nikto 2.5 is an Open Source (GPL) web server scanner which performs comprehensive tests against web servers for multiple items, including over 7,000 potentially dangerous files/programs, checks for outdated versions of over 1250 servers, and version specific problems on over 270 servers. It also checks for server configuration items such as the presence of multiple index files, HTTP server options, and will attempt to identify installed web servers and software. Scan items and plugins are frequently updated and can be automatically updated. | ||
|
||
(taken from [CIRT.net](https://cirt.net/Nikto2)) | ||
|
||
### Input OOIs | ||
|
||
Nikto expects an IpAddress or a Hostname OOI. | ||
|
||
### Output OOIs | ||
|
||
Nikto outputs the following the found outdated software and a finding explaining that the software is outdated. | ||
|
||
**Cat name**: Kitty |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import fs from "node:fs"; | ||
import { execSync } from "node:child_process"; | ||
|
||
/** | ||
* @param {string} scheme | ||
* @returns {string} | ||
*/ | ||
function get_config_content(scheme) { | ||
const IS_USING_PROXY = !!process.env.PROXYHOST; | ||
|
||
// Setup config file | ||
try { | ||
let config_contents = | ||
"PROMPTS=no\nUPDATES=no\nCLIOPTS=-404code=301,302,307,308 -o ./output.json"; | ||
|
||
if (scheme == "https") config_contents += " -ssl"; | ||
if (IS_USING_PROXY) config_contents += " -useproxy"; | ||
config_contents += "\n"; | ||
|
||
if (IS_USING_PROXY) { | ||
const PROXY_HOST = process.env.PROXYHOST; | ||
const PROXY_PORT = process.env.PROXYPORT || "8080"; | ||
const PROXY_USER = process.env.PROXYUSER || ""; | ||
const PROXY_PASS = process.env.PROXYPASS || ""; | ||
|
||
config_contents += `PROXYHOST=${PROXY_HOST}\n`; | ||
config_contents += `PROXYPORT=${PROXY_PORT}\n`; | ||
config_contents += `PROXYUSER=${PROXY_USER}\n`; | ||
config_contents += `PROXYPASS=${PROXY_PASS}\n`; | ||
} | ||
|
||
if (process.env.USERAGENT) | ||
config_contents += `USERAGENT=${process.env.USERAGENT}\n`; | ||
|
||
return config_contents; | ||
} catch (e) { | ||
throw new Error("Something went wrong writing to the config file.\n" + e); | ||
} | ||
} | ||
|
||
/** | ||
* @param {Object} boefje_meta Information about the task | ||
* @param {Object} boefje_meta.arguments | ||
* @param {Object} boefje_meta.arguments.input | ||
* @param {string} boefje_meta.arguments.input.object_type | ||
* @param {"http" | "https"} boefje_meta.arguments.input.scheme | ||
* @param {number} boefje_meta.arguments.input.port | ||
* @param {Object} boefje_meta.arguments.input.netloc | ||
* @param {string} boefje_meta.arguments.input.netloc.name | ||
* @returns {(string | string[])[][]} | ||
*/ | ||
export default function (boefje_meta) { | ||
// Depending on what OOI triggered this task, the hostname / address will be in a different location | ||
const hostname = boefje_meta.arguments.input.netloc.name; | ||
|
||
const config_contents = get_config_content( | ||
boefje_meta.arguments.input.scheme, | ||
); | ||
fs.writeFileSync("./nikto.conf", config_contents); | ||
|
||
// Running nikto and outputting to a file | ||
try { | ||
execSync(`./nikto/program/nikto.pl -h ${hostname} -config ./nikto.conf`, { | ||
stdio: "inherit", | ||
}); | ||
} catch (e) { | ||
throw new Error( | ||
"Something went wrong running the nikto command.\n" + | ||
e + | ||
"\n" + | ||
config_contents, | ||
); | ||
} | ||
|
||
const raws = []; | ||
|
||
// Reading the file created by nikto | ||
try { | ||
var file_contents = fs.readFileSync("./output.json").toString(); | ||
raws.push([["boefje/nikto-output"], file_contents]); | ||
} catch (e) { | ||
throw new Error( | ||
"Something went wrong reading the file from the nikto command.\n" + e, | ||
); | ||
} | ||
|
||
// Looking if outdated software has been found | ||
try { | ||
const data = JSON.parse(file_contents); | ||
for (const vulnerability of data["vulnerabilities"]) | ||
if (vulnerability["id"].startsWith("6")) | ||
raws.push([["openkat/finding"], "KAT-OUTDATED-SOFTWARE"]); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
|
||
return raws; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import json | ||
from collections.abc import Iterable | ||
|
||
from boefjes.job_models import NormalizerOutput | ||
from octopoes.models import Reference | ||
from octopoes.models.ooi.findings import Finding, KATFindingType | ||
from octopoes.models.ooi.software import Software, SoftwareInstance | ||
|
||
|
||
def scan_outdated_software(data: dict, ooi_ref: Reference) -> Iterable[NormalizerOutput]: | ||
for scan in data: | ||
for vulnerability in scan["vulnerabilities"]: | ||
# If the scanned vulnerability has to do with outdated software | ||
if vulnerability["id"].startswith("6"): | ||
# Example of `vulnerability["msg"]` | ||
# @SOFTWARE/@RUNNING_VER appears to be outdated (current is at least @CURRENT_VER) | ||
software_name, found_version = vulnerability["msg"].split()[0].split("/") | ||
|
||
software = Software(name=software_name, version=found_version) | ||
software_instance = SoftwareInstance(ooi=ooi_ref, software=software.reference) | ||
yield software | ||
yield software_instance | ||
|
||
finding_type = KATFindingType(id="KAT-OUTDATED-SOFTWARE") | ||
yield finding_type | ||
yield Finding( | ||
finding_type=finding_type.reference, | ||
ooi=software_instance.reference, | ||
description=vulnerability["msg"], | ||
) | ||
|
||
|
||
def run(input_ooi: dict, raw: bytes) -> Iterable[NormalizerOutput]: | ||
data = json.loads(raw) | ||
|
||
ooi_ref = Reference.from_str(input_ooi["primary_key"]) | ||
|
||
yield from scan_outdated_software(data, ooi_ref) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"id": "kat_nikto_normalize", | ||
"consumes": [ | ||
"boefje/nikto-output" | ||
], | ||
"produces": [ | ||
"Software", | ||
"SoftwareInstance" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { execSync } from "node:child_process"; | ||
import run from "./main.js"; | ||
|
||
/** | ||
* @param {string} inp The string input to base64 | ||
* @returns {string} | ||
*/ | ||
function b64encode(inp) { | ||
return Buffer.from(inp).toString("base64"); | ||
} | ||
|
||
function main() { | ||
const input_url = process.argv[process.argv.length - 1]; | ||
|
||
// Getting the boefje input | ||
try { | ||
var boefje_input = JSON.parse( | ||
execSync(`curl --request GET --url ${input_url}`).toString(), | ||
); | ||
} catch (error) { | ||
console.error(`Getting boefje input went wrong with URL: ${input_url}`); | ||
throw new Error(error); | ||
} | ||
|
||
Object.assign(process.env, boefje_input["boefje_meta"]["environment"]); | ||
|
||
let out = undefined; | ||
let output_url = boefje_input.output_url; | ||
try { | ||
// Getting the raw files | ||
const raws = run(boefje_input.boefje_meta); | ||
out = { | ||
status: "COMPLETED", | ||
files: raws.map((x) => ({ | ||
content: b64encode(x[1]), | ||
tags: x[0], | ||
})), | ||
}; | ||
} catch (error) { | ||
out = { | ||
status: "FAILED", | ||
files: [ | ||
{ | ||
content: b64encode("Boefje caught an error: " + error.message), | ||
tags: ["error/boefje"], | ||
}, | ||
], | ||
}; | ||
} | ||
|
||
// Example command | ||
/* | ||
curl --request POST \ | ||
--url http://boefje:8000/api/v0/tasks/7342e8dd-b945-4185-aaec-787205b7b664 \ | ||
--header 'Content-Type: application/json' \ | ||
--data '{"status":"COMPLETED","files":[{"content":"BASE_64_ENCODED_CONTENT","tags":[]}]}' | ||
*/ | ||
const out_json = JSON.stringify(out); | ||
const cmd = `curl --request POST --url ${output_url} --header "Content-Type: application/json" --data '${out_json}'`; | ||
|
||
execSync(cmd); | ||
} | ||
|
||
main(); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "kat-nikto", | ||
"version": "1.0.0", | ||
"type": "module", | ||
"main": "oci_adapter.js", | ||
"author": "cynalytics", | ||
"dependencies": { | ||
"@types/node": "^22.1.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"title": "Arguments", | ||
"type": "object", | ||
"properties": { | ||
"PROXYHOST": { | ||
"title": "PROXYHOST", | ||
"maxLength": 256, | ||
"type": "string" | ||
}, | ||
"PROXYPORT": { | ||
"title": "PROXYPORT", | ||
"maxLength": 256, | ||
"type": "string" | ||
}, | ||
"PROXYUSER": { | ||
"title": "PROXYUSER", | ||
"maxLength": 256, | ||
"type": "string" | ||
}, | ||
"PROXYPASS": { | ||
"title": "PROXYPASS", | ||
"maxLength": 256, | ||
"type": "string" | ||
}, | ||
"USERAGENT": { | ||
"title": "USERAGENT", | ||
"maxLength": 256, | ||
"type": "string" | ||
} | ||
Comment on lines
+5
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps you could use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are config variables required for Nikto. If we would make the boefje's schema less complex, then it would move the complexity to the code of the boefje. I believe since the user chooses to activate the Nikto boefje, they would know that Nikto requires these variables which would make them self-explaining. |
||
}, | ||
"required": [] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[ | ||
{ | ||
"host": "example.com", | ||
"ip": "178.128.108.228", | ||
"port": "80", | ||
"banner": "", | ||
"vulnerabilities": [ | ||
{ | ||
"id": "600575", | ||
"method": "HEAD", | ||
"url": "/", | ||
"msg": "nginx/1.18.0 appears to be outdated (current is at least 1.25.3)." | ||
}, | ||
{ | ||
"id": "999103", | ||
"references": "https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/missing-content-type-header/", | ||
"method": "GET", | ||
"url": "/", | ||
"msg": "The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type." | ||
} | ||
] | ||
} | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See earlier remark