Skip to content

OpenVAS XML parser loses scanner-reported IP address when hostname is present #16002

Open

Description

@Whitehorse2

Bug description
The OpenVAS XML parser creates DefectDojo endpoints.

However, when the XML report contains both an IP address and an optional
hostname/FQDN, only one of these values is retained as the endpoint host.

Observed behavior:

  • If <hostname> contains a value, the DefectDojo endpoint uses the FQDN.
  • If <hostname> is empty or absent, the DefectDojo endpoint uses the IP
    address from <host>.
  • When an FQDN is present, the scanner-reported IP address is not retained
    in the endpoint.

For the first result, the created DefectDojo endpoint contains only the FQDN:

Host: host01.example.com
Port: 443
Protocol: tcp

The source IP address 192.0.2.10 is not retained.

For the second result, where the FQDN is empty, the endpoint contains the IP:

Host: 192.0.2.11
Port: 443
Protocol: tcp

This means that the endpoint identity changes depending on DNS/hostname
availability in the scan report.

Sanitized OpenVAS XML example

xml
<report>
  <results start="1" max="-1">
    <result id="00000000-0000-0000-0000-000000000001">
      <name>Example Vulnerability</name>

      <host>
        192.0.2.10
        <asset asset_id="00000000-0000-0000-0000-000000000002"/>
        <hostname>host01.example.com</hostname>
      </host>

      <port>443/tcp</port>

      <nvt oid="1.3.6.1.4.1.25623.1.0.100000">
        <type>nvt</type>
        <name>Example NVT</name>
      </nvt>

      <severity>7.5</severity>
    </result>

    <result id="00000000-0000-0000-0000-000000000003">
      <name>Example Vulnerability</name>

      <host>
        192.0.2.11
        <asset asset_id="00000000-0000-0000-0000-000000000004"/>
        <hostname/>
      </host>

      <port>443/tcp</port>

      <nvt oid="1.3.6.1.4.1.25623.1.0.100000">
        <type>nvt</type>
        <name>Example NVT</name>
      </nvt>

      <severity>7.5</severity>
    </result>
  </results>
</report>

Expected behavior
Both values from the scanner report should be preserved:

IP address: 192.0.2.10
Hostname: host01.example.com
Port: 443
Protocol: tcp

Suggested source mapping:

<host>.text                 -> IP address
<host>/<hostname>.text      -> hostname/FQDN, if present
<port>                      -> endpoint port and protocol
<nvt oid="...">             -> scanner vulnerability identity

The IP address should be the stable technical endpoint identity.
The FQDN should be retained as additional endpoint metadata or as an
endpoint alias.

An FQDN alone is not a stable technical asset identifier:

  • DNS resolution can fail temporarily.
  • Hostnames can change.
  • Multiple hostnames can point to the same IP address.
  • A later Greenbone scan may report only the IP address.

The same target can otherwise be represented differently between scans:

Scan 1: host01.example.com
Scan 2: 192.0.2.10

This can negatively affect endpoint correlation, finding deduplication,
and continuous vulnerability management workflows.

The OpenVAS XML source report already provides both values. The IP address
should therefore not be discarded when a hostname is available.

Deployment method

  • [ x] Docker Compose

Environment information

  • Operating System: Rocky 10
  • Podman Compose : 1.6.
  • DefectDojo version: 3.2.4
  • Deployment: Podman 5.8.2
  • Scan type: OpenVAS XML

Activity

  1. happymode-25 commented on Sep 30, 2026

    @happymode-25

    Confirmed, and I think the framing here may be worth adjusting. I dug into the two OpenVAS parsers and the behaviour is real, but the root cause is that v1 and v2 disagree with each other on the same file, and one of them has a dead code path.

    The mechanism

    <hostname> is nested inside <host> in Greenbone output, never a sibling of it:

    <result>
      <host>
        10.0.101.2
        <asset asset_id="..."/>
        <hostname>host01.example.com</hostname>
      </host>
      <port>443/tcp</port>
    </result>

    That matters because the two parsers reach for the host differently:

    • v1 (dojo/tools/openvas/parser_v1/xml_parser.py:27) does for field in result:, which only walks direct children of <result>. Its if field.tag == "hostname" branch at L31 therefore never fires for real GMP output. Only L37-42 (if field.tag == "host") ever sets loc_host, so v1 always uses the IP.
    • v2 (dojo/tools/openvas/parser_v2/xml_parser.py:123-132) calls field.find("hostname"), which does reach into the nested child, and prefers it (L126-129). So v2 always uses the FQDN when one is present.

    I checked this against the repo's own fixtures rather than reasoning about it:

    fixture <result> <hostname> direct child <hostname> nested in <host> results with both IP + FQDN
    unittests/scans/openvas/many_vuln.xml 44 0 44 44
    unittests/scans/openvas/report_detail_v2.xml 1 0 1 1
    unittests/scans/openvas/one_vuln.xml 1 0 1 0

    So the v1 hostname branch is unreachable on all 46 results in the fixture set, and the symptom in this report (FQDN wins, IP dropped) is v2 behaviour.

    The part that worries me more than the report does

    Feeding many_vuln.xml through both parsers, they disagree on 44 of 44 results — zero agreement. And the value v2 prefers looks like this:

    <host>192.168.1.1001</host>
      <hostname>b6b9f466d63</hostname>
    

    b6b9f466d63 is a 12-character hex string — not a valid IP and not a resolvable name. Greenbone emits container IDs in <hostname> when the scan target is a container, so v2's "prefer the FQDN" rule appears to key those endpoints on an ephemeral container ID, which changes every time the container is recreated.

    If that's right it's a worse instability than the one reported here — not "sometimes the FQDN, sometimes the IP" but "sometimes the FQDN, sometimes a value that is guaranteed to go stale". I'd treat my read of the Greenbone semantics as an inference from the fixture rather than something I've confirmed against Greenbone's docs, so please correct me if the target really is a normal host.

    Why the test suite doesn't catch it

    test_openvas_parser_csv_xml_parity only compares v2-CSV against v2-XML. The v1 test asserts "tcp://192.168.1.1001:512" in test_openvas_xml_many_vuln (the raw <host> text) and the v2 test asserts "server99", a real hostname, but against a different fixture. Both assertions are internally consistent, and nothing compares v1 against v2 on the same input — so the disagreement is invisible.

    On the "preserve both values" part

    I'd push back gently on that half, because I don't think it's a parser fix:

    • LocationData.url() (dojo/tools/locations.py) accepts exactly url, host, port, protocol, path, query, fragment, user_info. There is no ip field, and a code search for LocationData.ip / endpoint.ip / ip=ip across the tree returns nothing — no parser stores the IP separately today.
    • AbstractLocation.set_identity_hash() (dojo/location/models.py:376-377) is blake2b(str(self)). Location identity is a hash of the rendered location, so host01.example.com:443 and 192.0.2.10:443 are two different identities. Flipping the parser to prefer the IP wouldn't fix correlation for people who already imported FQDN-based data — it would create a new set of Location rows alongside the old ones on reimport.

    The correlation problem described here is real, but the fix looks like it belongs in the location model (an ip attribute, or aliasing), which is a schema and migration decision rather than an OpenVAS bug.

    What I'd like to do instead

    I initially assumed the fix was to make v1 match v2, since dojo/tools/nmap/parser.py:72-73 and 77-78 use host=fqdn or ip and v1 is the one deviating. The container-ID observation above makes me think that assumption is wrong: nmap's fqdn comes from a PTR DNS lookup, so it's a real resolvable name, whereas Greenbone's <hostname> is not reliably a name at all. The nmap convention probably shouldn't be extended to OpenVAS as-is.

    So I'd rather not guess at the direction. What I can do without prejudging it:

    1. A parity test. Run many_vuln.xml through both OpenVASParser and OpenVASParserV2 and assert they agree on host/port/protocol. It fails today on all 44 results, which makes the disagreement explicit and gives you a regression guard whichever way you resolve it. I'm happy to send this as a standalone PR independent of any behaviour change.
    2. A fix, once you've decided which side is correct. Either make v1 reach into <host> the way v2 does, or make v2 prefer the <host> text the way v1 does. The v1 L31-36 branch is dead either way and should go.

    Does the container-ID reading match what you've seen in practice? That's the piece I'd most like confirmed or corrected before I write anything.

    One note on the sanitised XML in the report: the parser does root.find("report"), so the real format nests <report> inside the document element. Worth knowing if anyone reproduces from the snippet as pasted — the host/hostname structure above is accurate though.

  2. Whitehorse2 commented on Oct 4, 2026

    @Whitehorse2
    Author

    @happymode-25 thanks for digging this up and uncovering the underlying parser disagreements and the ephemeral container ID issue.
    To add a real-world perspective to this: Having both fields (IP and FQDN) is absolutely critical for anyone operating modern infrastructure with Virtual Hosts, Reverse Proxies (like Nginx/Traefik), or Load Balancers.
    Due to SNI (Server Name Indication) routing, it is very common that a single IP address hosts dozens or hundreds of completely distinct web applications, each with its own unique vulnerability profile.

    • If the parser flattens everything to just the IP address, DefectDojo groups completely unrelated applications into the same endpoint, destroying deduplication and tracking.
    • If it picks only the FQDN, it strips away the underlying network asset identity and struggles with transient cloud environments or ephemeral container IDs as mentioned above.
      Whichever way the parser is aligned in the short term, in my opinion DefectDojo's location/endpoint model ultimately needs a structural way to store both a stable IP and its associated FQDNs/aliases to handle modern web architectures properly.
  3. Skeletor-Pirate commented on Nov 8, 2026

    @Skeletor-Pirate

    Happy to take this on if it's free. I'd follow @happymode-25's breakdown — reconciling the v1/v2 divergence and removing the dead path — plus regression samples for the FQDN+IP cases so the scanner-reported address is retained alongside the hostname, with a test per parser. Could I be assigned? I may take a few days before opening a PR while my other submissions cycle through review.

  4. happymode-25 commented on Nov 8, 2026

    @happymode-25

    Thanks @Whitehorse2 and @Skeletor-Pirate! Since I already mapped out the root cause and test vectors, I'm working on the parity test and PR now and will submit it shortly.

  5. Skeletor-Pirate commented on Nov 8, 2026

    @Skeletor-Pirate

    Saw your update — please go ahead, I'll step aside. Your write-up already did the hard part (v1/v2 divergence, the dead path, and the test vectors), so this is in better hands with it fresh in mind.

  6. happymode-25 commented on Nov 8, 2026

    @happymode-25

    Thanks @Skeletor-Pirate! I have just opened PR #16734 with the parity fix, removal of the dead path in v1, and tests ensuring scanner-reported IPs and hostnames are retained across both v1 and v2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions