Project

General

Profile

Actions

Bug #8457

open
AM

dcerpc.iface keyword matches any interface if PFC_FIRST_FRAG is missing in the BIND request

Bug #8457: dcerpc.iface keyword matches any interface if PFC_FIRST_FRAG is missing in the BIND request

Added by Alexey Monastyrskiy about 13 hours ago.

Status:
New
Priority:
Normal
Assignee:
-
Target version:
Affected Versions:
Effort:
Difficulty:
Label:

Description

Summary

The DCE/RPC detection engine causes the dcerpc.iface keyword to unconditionally return a match if the BIND request did not have the PFC_FIRST_FRAG flag set, unless the signature uses the any_frag modifier.

This means a signature designed to alert on a specific DCE/RPC interface will alert on any DCE/RPC interface if the client omits the first-fragment flag during the BIND phase. Signatures that use the any_frag modifier are not affected.

Affected Code

rust/src/dcerpc/detect.rs, match_backuuid()

Reproduced on 8.0.4. The code is identical in 7.0.15. The behavior is also present on current main and main-8.0.x.

Background

For BIND PDUs with minor version 0, the DCE 1.1: RPC specification explicitly states that "the run-time assumes no fragmentation." Therefore, a BIND PDU with pfc_flags=0x00 is perfectly valid and compliant with the specification.

Windows endpoints accept BIND requests with pfc_flags=0x00 (confirmed with live traffic on port 135), producing a valid BIND_ACK. Similarly, Samba's server-side BIND handler (dcesrv_bind() in librpc/rpc/dcesrv_core.c) calls dcerpc_verify_ncacn_packet_header with required_flags=0.

Note: Our testing used minor version 0 BIND PDUs only. We have not tested Windows behavior for minor version 1 BIND PDUs.

What Happens

In rust/src/dcerpc/detect.rs, the match_backuuid() function iterates over the accepted UUIDs to check if the interface matches the one specified in the signature (code shown is from 8.0.4):

fn match_backuuid(
    tx: &DCERPCTransaction, state: &mut DCERPCState, if_data: &mut DCEIfaceData,
) -> u8 {
    let mut ret = 0;
    if let Some(ref bindack) = state.bindack {
        for uuidentry in bindack.accepted_uuid_list.iter() {
            ret = 1; // <--- ret is set to 1 here

            // if any_frag is not enabled, we need to match only against the first fragment
            if if_data.any_frag == 0 && (uuidentry.flags & DCERPC_UUID_ENTRY_FLAG_FF == 0) {
                SCLogDebug!("any frag not enabled");
                continue; // <--- skips the UUID check, but ret remains 1
            }

            // ... BIND_ACK result check, UUID comparison, context ID and version checks ...

If the BIND request was missing the PFC_FIRST_FRAG flag, the DCERPC_UUID_ENTRY_FLAG_FF bit is not set in uuidentry.flags. When the signature does not use any_frag (the default behavior), the if condition evaluates to true, and the code hits continue.

The continue skips the UUID comparison, but ret remains 1 from the top of the iteration. If no subsequent iteration resets ret to 0, the function returns 1 (Match) without ever comparing UUIDs. For example, if the BIND negotiated a single interface, the loop has one iteration: ret is set to 1, the UUID comparison is skipped, and the function returns a match regardless of which interface was requested. If the signature does use any_frag, the if condition evaluates to false, the UUID is checked, and the match is evaluated correctly.

Reproduction

1. Create a Suricata rule targeting a dummy interface without the any_frag modifier:

alert dcerpc any any -> any any (msg:"DCERPC BIND test"; dcerpc.iface:11111111-2222-3333-4444-555555555555; sid:1;)

2. Send a DCE/RPC BIND request for a completely different interface (e.g., 12345678-1234-1234-1234-123456789012) but clear the PFC_FIRST_FRAG flag (pfc_flags = 0x00 instead of 0x03).

3. The server accepts the BIND request and replies with a BIND_ACK.

4. Send a REQUEST PDU.

Expected: No alert, because the interface UUIDs do not match.
Actual: Suricata generates an alert for the rule.

Attached PCAP: repro_remote.pcapng — live capture against a Windows endpoint on port 135. Contains two connections: one where the BIND has pfc_flags=0x00 (frames 28-33; triggers the false positive) and one where the BIND has pfc_flags=0x03 (frames 38-43; control, no false positive).

Impact

This is a false positive issue, not an evasion issue. The bug causes dcerpc.iface to match more broadly than intended — any accepted interface satisfies any dcerpc.iface signature when PFC_FIRST_FRAG is missing. An attacker cannot use this to evade detection; the interface is still correctly bound and traffic is processed normally. The practical impact is spurious alerts: a signature written for one DCE/RPC interface will fire on unrelated interfaces if the client happens to omit the first-fragment flag in the BIND.

This only affects standalone DCE/RPC (typically port 135). DCE/RPC over SMB (typically port 445) is not affected (see below).

Note on Rejected Interfaces

A few lines down in the same function, there is a seemingly identical pattern with rejected binds:

            // if the uuid has been rejected(uuidentry->result == 1), we skip to the next uuid
            if uuidentry.result != 0 {
                SCLogDebug!("Skipping to next UUID");
                continue;
            }

If this code were reached, it would also return 1 (Match) for rejected interfaces due to the same ret = 1 initialization. However, this path seems to be dead code in 8.0.4. In rust/src/dcerpc/dcerpc.rs, the process_bindack_pdu() function explicitly breaks and skips adding the UUID to accepted_uuid_list if ack_result != 0. Because rejected interfaces are never added to the list, the loop in match_backuuid() never processes them.

Note on main and main-8.0.x branches

A recent update on main (commit 05a11e289) and cherry-picked to main-8.0.x (commit 691114e95) changed match_backuuid() to iterate over the interface list that includes rejected and unacked entries, not just accepted ones. The patch added ret = 0 before continue for the !acked || result != 0 check — but the PFC_FIRST_FRAG check (line 70-72) still has the ret = 1 + continue pattern and executes before the acked/result check. This potentially means the false positive on main and main-8.0.x triggers not only for accepted BINDs (as in released versions) but also for rejected and unacked BINDs when PFC_FIRST_FRAG is missing. We have not tested any of these scenarios on a build from main or main-8.0.x.

DCE/RPC over SMB is not affected

This bug only affects standalone DCE/RPC (ALPROTO_DCERPC, typically port 135). DCE/RPC encapsulated in SMB (ALPROTO_SMB, typically port 445) uses a completely different code path.

The C dispatcher in detect-dce-iface.c (DetectDceIfaceMatchRust) branches on f->alproto: ALPROTO_DCERPC calls match_backuuid() (the buggy function described above), while ALPROTO_SMB calls rs_smb_tx_get_dce_iface(). The SMB function uses clean exact-match logic that checks i.acked && i.ack_result 0 && i.uuid if_uuid without any fragment-flag dependency. It does not have the ret = 1 / continue pattern.

References


Files

repro_remote.pcapng (7.96 KB) repro_remote.pcapng Alexey Monastyrskiy, 04/07/2026 07:37 PM

No data to display

Actions

Also available in: PDF Atom