From 8aef9873fccbd99e66ec891ad8da0890ccfc5a05 Mon Sep 17 00:00:00 2001 From: Alhuda Khan Date: Tue, 20 Oct 2026 15:18:02 +0530 Subject: [PATCH] bouncedcc: refuse DCC requests from IRC aimed at loopback or link-local The address in an incoming DCC CHAT/SEND is chosen by the remote IRC user, and ZNC is the side that connects to it, relaying traffic to whoever connects to the bounce listener. Refuse requests that point at 0/8, 127/8, 169.254/16, 224/3 or values above 32 bits (which CUtils::GetIP would otherwise mask down to one of those). --- modules/bouncedcc.cpp | 18 ++++++++++++++++++ test/integration/tests/modules.cpp | 8 ++++++++ 2 files changed, 26 insertions(+) diff --git a/modules/bouncedcc.cpp b/modules/bouncedcc.cpp index cf0b5093..72a647df 100644 --- a/modules/bouncedcc.cpp +++ b/modules/bouncedcc.cpp @@ -163,6 +163,17 @@ class CBounceDCCMod : public CModule { bool UseClientIP() { return GetNV("UseClientIP").ToBool(); } + // The address in a DCC request from IRC is chosen by the remote peer, and + // ZNC is the one connecting to it, so don't let it point at ZNC's own + // host or at link-local services. + static bool IsAllowedPeerIP(unsigned long uLongIP) { + if (uLongIP > 0xFFFFFFFFUL) return false; + const unsigned long uFirst = uLongIP >> 24; + if (uFirst == 0 || uFirst == 127 || uFirst >= 224) return false; + if ((uLongIP >> 16) == 0xA9FE) return false; // 169.254.0.0/16 + return true; + } + EModRet OnUserCTCPMessage(CCTCPMessage& Message) override { CString sTarget = Message.GetTarget(); CString sMessage = Message.GetText(); @@ -255,6 +266,13 @@ class CBounceDCCMod : public CModule { unsigned long uFileSize = sMessage.Token(5, false, " ", false, "\"", "\"", true).ToULong(); + if ((sType.Equals("CHAT") || sType.Equals("SEND")) && + !IsAllowedPeerIP(uLongIP)) { + PutModule(t_f("DCC {1} from {2} refused: bad address {3}")( + sType, Nick.GetNick(), CString(uLongIP))); + return HALTCORE; + } + if (sType.Equals("CHAT")) { CNick FromNick(Nick.GetNickMask()); unsigned short uBNCPort = CDCCBounce::DCCRequest( diff --git a/test/integration/tests/modules.cpp b/test/integration/tests/modules.cpp index fe315901..c90efd86 100644 --- a/test/integration/tests/modules.cpp +++ b/test/integration/tests/modules.cpp @@ -765,6 +765,14 @@ TEST_F(ZNCTest, BounceDCCModule) { QByteArray line2; client.ReadUntilAndGet(":friend!user@host PRIVMSG nick :\001DCC CHAT chat", line2); EXPECT_THAT(line2.toStdString(), Not(HasSubstr("3232235521 54321"))); + + // Peer must not be able to make ZNC connect to its own loopback + ircd.Write(":friend!user@host PRIVMSG nick :\001DCC CHAT chat 2130706433 6379\001"); + client.ReadUntil("refused: bad address 2130706433"); + ircd.Write(":friend!user@host PRIVMSG nick :\001DCC SEND f 2852039166 80 1\001"); + client.ReadUntil("refused: bad address 2852039166"); + ircd.Write(":friend!user@host PRIVMSG nick :\001DCC CHAT chat 6425673729 22\001"); + client.ReadUntil("refused: bad address 6425673729"); } TEST_F(ZNCTest, ChanSaverModule) {