Insights Blog Attack Scenario: SDP Response Spoofing ...

Attack Scenario: SDP Response Spoofing / Racing the SECC

Written by Mahmoud Jadaan
Agenda
Series: V2G Security Research

Part 1: EV Charging & the V2G Protocol Stack — threat model and protocol overview

Part 2: Lab Environment: Docker-based V2G Security Lab — setting up the test environment

Part 3: TLS Downgrade

Part 4: SDP Response Spoofingyou are here

Part 5: Certificate Validation

Part 6: Protocol Namespace Downgrade

Agenda
Series: V2G Security Research

Part 1: EV Charging & the V2G Protocol Stack — threat model and protocol overview

Part 2: Lab Environment: Docker-based V2G Security Lab — setting up the test environment

Part 3: TLS Downgrade

Part 4: SDP Response Spoofingyou are here

Part 5: Certificate Validation

Part 6: Protocol Namespace Downgrade

 

Attack Scenario: SDP Response Spoofing / Racing the SECC

We continue our hands-on look at V2G security with another network-layer attack. This one builds on the lab setup introduced in Part 2 and assumes familiarity with the SDP-based session establishment described in Part 1.

As we saw in the previous attack, the EVCC broadcasts an SDP request over UDP to ff02::1 (all-nodes multicast) on port 15118. What if a rogue actor is listening on the same network and replies faster than the legitimate SECC?

This will be our test scenario; we will craft a fake SDP response before the real SECC replies, redirecting the EVCC to an attacker-controlled address. The EVCC will then attempt to connect to the attacker’s address, and we will observe whether it accepts the spoofed response or waits for the legitimate SECC.

To do that, we will need to listen on UDP port 15118 for the EVCC’s multicast SDP request and reply immediately with a crafted SECC_ResponseMessage containing the attacker’s IPv6 address and port, with SecurityProtocol = 0x10 (no TLS).

The attacker container ships a ready-made script for exactly this, attack1_sdp_spoof.py, which binds to UDP port 15118, waits for a valid V2GTP SDP request, and immediately fires back a crafted response pointing to the attacker’s own address and port. It takes three arguments: - --iface (the network interface to listen on) - --fake-ip (the attacker’s own link-local IPv6) - --fake-port (the TCP port the fake SECC will listen on)

Like the SECC and EVCC, the attacker container is assigned a static MAC address in docker-compose.yml (02:00:00:00:00:03). Because IPv6 link-local addresses are derived from the MAC via EUI-64, this gives the attacker a deterministic and permanent link-local address of fe80::ff:fe00:3, no need to query it at runtime. The --fake-ip argument in the launch sequence below is therefore hardcoded to that value.

The critical thing to consider is the ordering of execution: the attacker must be listening before the EVCC sends its first SDP multicast. The EVCC only waits 2 seconds after boot before sending SDP, so starting the attacker after docker compose up is always too late.

First ensure you are in the project root. Run the cleanup commands to remove any containers and volumes from a previous run:

# All commands must be run from the project root.
# Cleanup: stop and remove any containers and volumes from a previous run
docker compose down -v
docker rm -f attacker 2>/dev/null || true

We are ready now. This requires three terminals open in the project root.

Important: SECC_SCENARIO must be exported in every terminal before running any command. Docker Compose reads the environment of whichever shell invokes it.

In Terminal 1, start the SECC in the foreground, it will show live logs:

export SECC_SCENARIO=attack_sdp_spoof
docker compose up secc

Once the SECC is up, in Terminal 2 start the attacker’s spoof script:

export SECC_SCENARIO=attack_sdp_spoof
docker compose --profile attack run --name attacker attacker \
python /app/scripts/attack1_sdp_spoof.py \
--iface eth0 --fake-ip "fe80::ff:fe00:3" --fake-port 61341

Wait until Terminal 2 prints Listening on eth0 UDP :15118 for SDP requests

Then, in Terminal 3, start the EVCC. It sleeps 2 seconds then sends the SDP multicast:

export SECC_SCENARIO=attack_sdp_spoof
docker compose up evcc

Now all containers are up and running. The EVCC will send its SDP request, the attacker will reply first, and the EVCC will attempt to connect to the attacker’s address and port.

Shortly after, in the EVCC logs we can see the following:

This is the spoofed response sent by attack1_sdp_spoof.py. We can now stop all containers with ctrl + c to flush the pcap files, then analyse the captures:

docker compose down
docker rm -f attacker 2>/dev/null || true

 

Traffic Analysis

Let’s look at the captures. Inside the attack_sdp_spoof folder, the EVCC capture (evcc_20260703_085312.pcap) is the right source for this attack: tcpdump runs on the EVCC’s own eth0 and captures every packet the EVCC sends and receives, including the attacker’s unicast SDP response arriving before the real SECC can reply.

The three participants in this capture are:

Role IPv6 Link-Local MAC
EVCC (victim) fe80::ff:fe00:2 02:00:00:00:00:02
Attacker (rogue spoofer) fe80::ff:fe00:3 02:00:00:00:00:03
Real SECC (legitimate station) fe80::ff:fe00:1 02:00:00:00:00:01

Frame 19 — SDP Request

After the EVCC’s 2 s startup delay, we can see it multicasts the SDP request in frame 19, requesting “Secured with TLS”:

We can see that every device on the IPv6 link receives this frame simultaneously, including both the real SECC and the attacker.

Frames 20–22 — Attacker reacts first

Within a few microseconds of the SDP request, the attacker’s kernel sends a Neighbor Solicitation to resolve the EVCC’s MAC address (frame 20). The real SECC has not yet reacted at this point.

Once the NDP exchange completes, the attacker sends its spoofed SDP response without TLS, pointing to its own address and port (frame 22). This arrives at the EVCC before the real SECC can respond, as shown below.

Frames 23–24 — EVCC tries to connect to the attacker

Upon receiving the SDP response, the EVCC immediately tries to establish a TCP connection to the attacker’s address and port by sending a SYN packet. The attacker’s kernel has nothing listening on that port, so it responds with RST, which the EVCC logs as ConnectionRefusedError. This is expected in this demonstration.

In a full MitM scenario the attacker would run a TCP proxy on port 61341, forwarding the session to the real SECC while intercepting every V2G message in transit, yielding a complete man-in-the-middle with no authentication or integrity protection in the protocol to detect it.

That wraps up the SDP spoofing scenario. In the next article, we will cover certificate validation: specifically, which classes of certificate defect the EVCC catches and which ones it silently accepts.