Skip to content
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

Feature/disallowed domains in csp #2624

Merged
merged 31 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b696abd
fix architecture check
noamblitz Mar 7, 2024
2d10f73
fix set clearence leven from boefje detail
noamblitz Mar 8, 2024
d026b18
bit: check for disallowed domains in csp
noamblitz Mar 8, 2024
2baa2c6
Merge branch 'main' into feature/disallowed-domains-in-csp
noamblitz Mar 8, 2024
260ea01
fix integration tests
noamblitz Mar 8, 2024
cac231d
Merge branch 'feature/disallowed-domains-in-csp' of https://github.co…
noamblitz Mar 8, 2024
b5b371e
url shorteners optional
noamblitz Mar 11, 2024
347c6f9
Merge branch 'main' into feature/disallowed-domains-in-csp
noamblitz Mar 11, 2024
3413c0a
add unit tests
noamblitz Mar 11, 2024
60fc2af
Update octopoes/bits/ask_disallowed_domains/question_schema.json
noamblitz Mar 12, 2024
4bc3696
Update octopoes/bits/disallowed_csp_hostnames/disallowed_csp_hostname…
noamblitz Mar 12, 2024
296599b
Update octopoes/tests/test_disallowed_csp_hostnames.py
noamblitz Mar 12, 2024
96cb734
Update octopoes/bits/disallowed_csp_hostnames/disallowed_csp_hostname…
noamblitz Mar 12, 2024
18eb10c
more specific checks
noamblitz Mar 12, 2024
7100992
Merge branch 'feature/disallowed-domains-in-csp' of https://github.co…
noamblitz Mar 12, 2024
aff388a
update unit tests
noamblitz Mar 12, 2024
dd5849b
allow all dicts for config
noamblitz Mar 12, 2024
a19f6ee
Update jsonSchemaToForm.js, add boolean type support
underdarknl Mar 12, 2024
49baf89
Update jsonSchemaToForm.js
underdarknl Mar 12, 2024
eb71b57
Merge branch 'main' into feature/disallowed-domains-in-csp
underdarknl Mar 13, 2024
27d6451
Merge branch 'main' into feature/disallowed-domains-in-csp
underdarknl Mar 13, 2024
eccdbc7
Merge branch 'main' into feature/disallowed-domains-in-csp
noamblitz Mar 14, 2024
2905bfe
Merge branch 'main' into feature/disallowed-domains-in-csp
ammar92 Mar 20, 2024
1ae9aba
Fixed merge conflict
ammar92 Mar 20, 2024
24e9dae
Merge branch 'main' into feature/disallowed-domains-in-csp
stephanie0x00 Mar 20, 2024
31980f8
stricter config types
noamblitz Mar 28, 2024
81417f3
Merge branch 'main' into feature/disallowed-domains-in-csp
underdarknl Mar 29, 2024
fc7f22c
Merge branch 'main' into feature/disallowed-domains-in-csp
underdarknl Mar 29, 2024
0b01ec8
Merge branch 'main' into feature/disallowed-domains-in-csp
noamblitz Apr 2, 2024
86004ef
Merge branch 'main' into feature/disallowed-domains-in-csp
underdarknl Apr 3, 2024
35fbde6
Merge branch 'main' into feature/disallowed-domains-in-csp
underdarknl Apr 4, 2024
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
10 changes: 10 additions & 0 deletions boefjes/boefjes/plugins/kat_answer_parser/normalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,13 @@ def run(normalizer_meta: NormalizerMeta, raw: bytes | str) -> Iterable[OOI]:
bit_id="port-classification-ip",
config=json.loads(raw),
)

if "/bit/disallowed-csp-hostnames" in mime_types:
noamblitz marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(raw, bytes):
raw = raw.decode()

yield Config(
ooi=normalizer_meta.raw_data.boefje_meta.input_ooi,
bit_id="disallowed-csp-hostnames",
config=json.loads(raw),
)
Original file line number Diff line number Diff line change
Expand Up @@ -467,5 +467,11 @@
"risk": "low",
"impact": "All Certificate Authorities may issue certificates for you domain.",
"recommendation": "Set a CAA record to limit which CA's are allowed to issue certs."
},
"KAT-DISALLOWED-DOMAIN-IN-CSP": {
"description": "This CSP header contains domains that are not allowed, If the website contains a cross-site scripting vulnerability, then JavaScript code can be injected into the web page hosted on these domains which can host files for anyone.",
"risk": "medium",
"impact": "Disallowed domains are domains that are for example 'world writable', this opens up the possibility for an atacker to host malicious files on a csp whitelisted domain.",
"recommendation": "Remove the offending hostname from the CSP header."
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from octopoes.models.ooi.web import URL, HostnameHTTPURL, IPAddressHTTPURL, WebScheme


def run(url: URL, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]:
def run(url: URL, additional_oois: list, config: dict) -> Iterator[OOI]:
if url.raw.scheme == "http" or url.raw.scheme == "https":
port = url.raw.port
if port is None:
Expand Down
Empty file.
21 changes: 21 additions & 0 deletions octopoes/bits/ask_disallowed_domains/ask_disallowed_domains.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import json
from collections.abc import Iterator
from pathlib import Path
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.network import Network
from octopoes.models.ooi.question import Question


def run(
input_ooi: Network,
additional_oois: list,
config: dict[str, Any],
) -> Iterator[OOI]:
network = input_ooi

with (Path(__file__).parent / "question_schema.json").open() as f:
schema = json.load(f)

yield Question(ooi=network.reference, schema_id=schema["$id"], json_schema=json.dumps(schema))
10 changes: 10 additions & 0 deletions octopoes/bits/ask_disallowed_domains/bit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from bits.definitions import BitDefinition
from octopoes.models.ooi.network import Network

BIT = BitDefinition(
id="ask-disallowed-domains",
consumes=Network,
parameters=[],
min_scan_level=0,
module="bits.ask_disallowed_domains.ask_disallowed_domains",
)
21 changes: 21 additions & 0 deletions octopoes/bits/ask_disallowed_domains/question_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "/bit/disallowed-csp-hostnames",
"type": "object",
"default": {},
"Port Configuration": "Root Schema",
"required": [],
"properties": {
"disallowed_hostnames": {
noamblitz marked this conversation as resolved.
Show resolved Hide resolved
"description": "Comma separated list of disallowed hostnames in CSP headers",
"type": "string",
"pattern": "^(\\s*(,*)[^,]+,?\\s*)*$",
"default": "github.com,google.com"
},
"disallow_url_shorteners": {
"description": "Do you want to disallow url shorteners in csp headers?",
"type": "boolean",
"default": true
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from collections.abc import Iterator
from pathlib import Path
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.network import Network
Expand All @@ -10,7 +11,7 @@
def run(
input_ooi: Network,
additional_oois: list,
config: dict[str, str],
config: dict[str, Any],
) -> Iterator[OOI]:
network = input_ooi

Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/check_csp_header/check_csp_header.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ipaddress
import re
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI, Reference
from octopoes.models.ooi.findings import Finding, KATFindingType
Expand All @@ -9,7 +10,7 @@
NON_DECIMAL_FILTER = re.compile(r"[^\d.]+")


def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]:
header = input_ooi
if header.key.lower() != "content-security-policy":
return
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.findings import CVEFindingType, Finding
from octopoes.models.types import HTTPHeader


def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]:
header = input_ooi
if header.key.lower() != "server":
return
Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/check_hsts_header/check_hsts_header.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import datetime
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI, Reference
from octopoes.models.ooi.findings import Finding, KATFindingType
from octopoes.models.types import HTTPHeader


def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]:
header = input_ooi
if header.key.lower() != "strict-transport-security":
return
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.findings import FindingType, RiskLevelSeverity


def run(input_ooi: FindingType, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: FindingType, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]:
value_set = False
if not input_ooi.risk_severity:
input_ooi.risk_severity = RiskLevelSeverity.PENDING
Expand Down
Empty file.
10 changes: 10 additions & 0 deletions octopoes/bits/disallowed_csp_hostnames/bit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from bits.definitions import BitDefinition
from octopoes.models.ooi.web import HTTPHeaderHostname

BIT = BitDefinition(
id="disallowed-csp-hostnames",
consumes=HTTPHeaderHostname,
parameters=[],
module="bits.disallowed_csp_hostnames.disallowed_csp_hostnames",
config_ooi_relation_path="HTTPHeaderHostname.hostname.network",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import logging
from collections.abc import Iterator
from typing import Any

from link_shorteners import link_shorteners_list

from octopoes.models import OOI
from octopoes.models.ooi.findings import Finding, KATFindingType
from octopoes.models.ooi.web import HTTPHeaderHostname

LINK_SHORTENERS = link_shorteners_list()

logger = logging.getLogger(__name__)


def get_disallowed_hostnames_from_config(config, config_key, default):
disallowed_hostnames = config.get(config_key, None)
if disallowed_hostnames is None:
return default
return list(disallowed_hostnames.strip().split(",")) if disallowed_hostnames else []


def run(input_ooi: HTTPHeaderHostname, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]:
header_hostname = input_ooi
header = header_hostname.header

if header.tokenized.key.lower() != "content-security-policy":
return

disallow_url_shorteners = config.get("disallow_url_shorteners", True) if config else True

hostname = header_hostname.hostname.tokenized.name
disallowed_domains = link_shorteners_list() if disallow_url_shorteners else []
disallowed_hostnames_from_config = get_disallowed_hostnames_from_config(config, "disallowed_hostnames", [])

disallowed_domains.extend(disallowed_hostnames_from_config)

if hostname.lower() in disallowed_domains:
ft = KATFindingType(id="KAT-DISALLOWED-DOMAIN-IN-CSP")
f = Finding(
ooi=input_ooi.reference,
finding_type=ft.reference,
)
yield ft
yield f
3 changes: 2 additions & 1 deletion octopoes/bits/dns_alias_resolving/dns_alias_resolving.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.dns.records import DNSCNAMERecord
Expand All @@ -7,7 +8,7 @@


def run(
hostname: Hostname, additional_oois: list[DNSCNAMERecord | ResolvedHostname], config: dict[str, str]
hostname: Hostname, additional_oois: list[DNSCNAMERecord | ResolvedHostname], config: dict[str, Any]
) -> Iterator[OOI]:
cname_records = [ooi for ooi in additional_oois if isinstance(ooi, DNSCNAMERecord)]
resolved_hostnames = [ooi for ooi in additional_oois if isinstance(ooi, ResolvedHostname)]
Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/dns_resolving/dns_resolving.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.dns.records import DNSAAAARecord, DNSARecord
from octopoes.models.ooi.dns.zone import Hostname, ResolvedHostname


def run(hostname: Hostname, additional_oois: list[DNSARecord | DNSAAAARecord], config: dict[str, str]) -> Iterator[OOI]:
def run(hostname: Hostname, additional_oois: list[DNSARecord | DNSAAAARecord], config: dict[str, Any]) -> Iterator[OOI]:
for record in additional_oois:
yield ResolvedHostname(
hostname=hostname.reference,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.certificate import X509Certificate
Expand All @@ -9,7 +10,7 @@
THRESHOLD = datetime.timedelta(weeks=2)


def run(input_ooi: X509Certificate, additional_oois: list[Website], config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: X509Certificate, additional_oois: list[Website], config: dict[str, Any]) -> Iterator[OOI]:
# only applies to OOIs referencing the certificate
if input_ooi.expired:
ft = KATFindingType(id="KAT-CERTIFICATE-EXPIRED")
Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/https_availability/https_availability.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.findings import Finding, KATFindingType
from octopoes.models.ooi.network import IPAddress, IPPort
from octopoes.models.ooi.web import Website


def run(input_ooi: IPAddress, additional_oois: list[IPPort | Website], config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: IPAddress, additional_oois: list[IPPort | Website], config: dict[str, Any]) -> Iterator[OOI]:
websites = [website for website in additional_oois if isinstance(website, Website)]

open_ports = [port.port for port in additional_oois if isinstance(port, IPPort)]
Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/https_redirect/https_redirect.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.findings import Finding, KATFindingType
from octopoes.models.ooi.web import HostnameHTTPURL, HTTPHeader


def run(input_ooi: HostnameHTTPURL, additional_oois: list[HTTPHeader], config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: HostnameHTTPURL, additional_oois: list[HTTPHeader], config: dict[str, Any]) -> Iterator[OOI]:
header_keys = [header.key.lower() for header in additional_oois if isinstance(header, HTTPHeader)]

# only check for http urls
Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/internetnl/internetnl.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.dns.zone import Hostname
from octopoes.models.ooi.findings import Finding, KATFindingType
from octopoes.models.ooi.web import Website


def run(input_ooi: Hostname, additional_oois: list[Finding | Website], config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: Hostname, additional_oois: list[Finding | Website], config: dict[str, Any]) -> Iterator[OOI]:
# only websites have to comply with the internetnl rules
websites = [websites for websites in additional_oois if isinstance(websites, Website)]
if not websites:
Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/ipv6_nameservers/ipv6_nameservers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.dns.records import DNSAAAARecord, DNSARecord, DNSNSRecord
from octopoes.models.ooi.dns.zone import Hostname
from octopoes.models.ooi.findings import Finding, KATFindingType


def run(hostname: Hostname, additional_oois: list[DNSAAAARecord | DNSARecord], config: dict[str, str]) -> Iterator[OOI]:
def run(hostname: Hostname, additional_oois: list[DNSAAAARecord | DNSARecord], config: dict[str, Any]) -> Iterator[OOI]:
dns_ns_records = [dns_ns_record for dns_ns_record in additional_oois if isinstance(dns_ns_record, DNSNSRecord)]
dns_aaaa_records = [
dns_aaaa_record for dns_aaaa_record in additional_oois if isinstance(dns_aaaa_record, DNSAAAARecord)
Expand Down
2 changes: 1 addition & 1 deletion octopoes/bits/ipv6_webservers/ipv6_webservers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


def run(
hostname: Hostname, additional_oois: list[DNSAAAARecord | DNSARecord | DNSNSRecord], config: dict[str, str]
hostname: Hostname, additional_oois: list[DNSAAAARecord | DNSARecord | DNSNSRecord], config: dict
) -> Iterator[OOI]:
dns_a_records = [dns_a_record for dns_a_record in additional_oois if isinstance(dns_a_record, DNSARecord)]
dns_aaaa_records = [
Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/missing_caa/missing_caa.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import Iterator
from typing import Any

import tldextract

Expand All @@ -8,7 +9,7 @@
from octopoes.models.ooi.findings import Finding, KATFindingType


def run(input_ooi: Hostname, additional_oois: list[DNSCAARecord | NXDOMAIN], config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: Hostname, additional_oois: list[DNSCAARecord | NXDOMAIN], config: dict[str, Any]) -> Iterator[OOI]:
caa_records = [ooi for ooi in additional_oois if isinstance(ooi, DNSCAARecord)]
nxdomains = (ooi for ooi in additional_oois if isinstance(ooi, NXDOMAIN))

Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/missing_certificate/missing_certificate.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from collections.abc import Iterator
from typing import Any

from octopoes.models import OOI
from octopoes.models.ooi.findings import Finding, KATFindingType
from octopoes.models.ooi.web import Website


def run(input_ooi: Website, additional_oois, config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: Website, additional_oois, config: dict[str, Any]) -> Iterator[OOI]:
if input_ooi.ip_service.tokenized.service.name.lower() != "https":
return

Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/missing_dkim/missing_dkim.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import Iterator
from typing import Any

import tldextract

Expand All @@ -9,7 +10,7 @@
from octopoes.models.ooi.findings import Finding, KATFindingType


def run(input_ooi: Hostname, additional_oois: list[DKIMExists | NXDOMAIN], config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: Hostname, additional_oois: list[DKIMExists | NXDOMAIN], config: dict[str, Any]) -> Iterator[OOI]:
dkim_exists = [ooi for ooi in additional_oois if isinstance(ooi, DKIMExists)]
nxdomains = (ooi for ooi in additional_oois if isinstance(ooi, NXDOMAIN))

Expand Down
3 changes: 2 additions & 1 deletion octopoes/bits/missing_dmarc/missing_dmarc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import Iterator
from typing import Any

import tldextract

Expand All @@ -9,7 +10,7 @@
from octopoes.models.ooi.findings import Finding, KATFindingType


def run(input_ooi: Hostname, additional_oois: list[DMARCTXTRecord | NXDOMAIN], config: dict[str, str]) -> Iterator[OOI]:
def run(input_ooi: Hostname, additional_oois: list[DMARCTXTRecord | NXDOMAIN], config: dict[str, Any]) -> Iterator[OOI]:
dmarc_records = [ooi for ooi in additional_oois if isinstance(ooi, DMARCTXTRecord)]
nxdomains = (ooi for ooi in additional_oois if isinstance(ooi, NXDOMAIN))

Expand Down
Loading