Merge pull request #2058 from kitkatt7/bouncedcc-refuse-loopback-peer
bouncedcc: refuse DCC requests from IRC aimed at loopback or link-local
This commit is contained in:
2 files changed
+62
No files matched your search
@@ -163,6 +163,24 @@ class CBounceDCCMod : public CModule {
|
|||||||
|
|
||||||
bool UseClientIP() { return GetNV("UseClientIP").ToBool(); }
|
bool UseClientIP() { return GetNV("UseClientIP").ToBool(); }
|
||||||
|
|
||||||
|
bool IsClientIP(const CString& sIP) {
|
||||||
|
for (CClient* pClient : GetUser()->GetAllClients()) {
|
||||||
|
if (pClient->GetRemoteIP().Equals(sIP)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
EModRet OnUserCTCPMessage(CCTCPMessage& Message) override {
|
||||||
CString sTarget = Message.GetTarget();
|
CString sTarget = Message.GetTarget();
|
||||||
CString sMessage = Message.GetText();
|
CString sMessage = Message.GetText();
|
||||||
@@ -255,6 +273,13 @@ class CBounceDCCMod : public CModule {
|
|||||||
unsigned long uFileSize = sMessage.Token(5, false, " ", false, "\"",
|
unsigned long uFileSize = sMessage.Token(5, false, " ", false, "\"",
|
||||||
"\"", true).ToULong();
|
"\"", 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")) {
|
if (sType.Equals("CHAT")) {
|
||||||
CNick FromNick(Nick.GetNickMask());
|
CNick FromNick(Nick.GetNickMask());
|
||||||
unsigned short uBNCPort = CDCCBounce::DCCRequest(
|
unsigned short uBNCPort = CDCCBounce::DCCRequest(
|
||||||
@@ -488,6 +513,24 @@ void CDCCBounce::Shutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Csock* CDCCBounce::GetSockObj(const CString& sHost, unsigned short uPort) {
|
Csock* CDCCBounce::GetSockObj(const CString& sHost, unsigned short uPort) {
|
||||||
|
// m_sRemoteIP is only known up front when the request came from IRC. In
|
||||||
|
// that case ZNC connects out to the remote peer itself, so the only thing
|
||||||
|
// that should be connecting here is one of the user's own clients. Hand
|
||||||
|
// Csocket a throwaway socket that is closed straight away and keep
|
||||||
|
// listening for the real client.
|
||||||
|
if (!m_sRemoteIP.empty() && !m_pModule->IsClientIP(sHost)) {
|
||||||
|
DEBUG(GetSockName() << " == GetSockObj(" << sHost << ") refused");
|
||||||
|
CString sType = m_bIsChat ? t_s("Chat", "type") : t_s("Xfer", "type");
|
||||||
|
m_pModule->PutModule(
|
||||||
|
t_f("DCC {1} Bounce ({2}): Refused connection from {3}, which is "
|
||||||
|
"not one of your clients")(sType, m_sRemoteNick, sHost));
|
||||||
|
CDCCBounce* pRefused =
|
||||||
|
new CDCCBounce(m_pModule, sHost, uPort, m_sRemoteNick, m_sRemoteIP,
|
||||||
|
m_sFileName, m_bIsChat);
|
||||||
|
pRefused->Close();
|
||||||
|
return pRefused;
|
||||||
|
}
|
||||||
|
|
||||||
Close();
|
Close();
|
||||||
|
|
||||||
if (m_sRemoteIP.empty()) {
|
if (m_sRemoteIP.empty()) {
|
||||||
|
|||||||
@@ -823,6 +823,25 @@ TEST_F(ZNCTest, BounceDCCModule) {
|
|||||||
QByteArray line2;
|
QByteArray line2;
|
||||||
client.ReadUntilAndGet(":friend!user@host PRIVMSG nick :\001DCC CHAT chat", line2);
|
client.ReadUntilAndGet(":friend!user@host PRIVMSG nick :\001DCC CHAT chat", line2);
|
||||||
EXPECT_THAT(line2.toStdString(), Not(HasSubstr("3232235521 54321")));
|
EXPECT_THAT(line2.toStdString(), Not(HasSubstr("3232235521 54321")));
|
||||||
|
|
||||||
|
// Only the user's own clients may connect to that bounce port. The test
|
||||||
|
// client is on a unix socket, so a TCP connection from anywhere is refused.
|
||||||
|
// The listener binds to the IRC socket's local address, which for a unix
|
||||||
|
// socket is "localhost" on Linux and may resolve to ::1 only.
|
||||||
|
QByteArray bouncePort = line2.mid(line2.lastIndexOf(' ') + 1);
|
||||||
|
bouncePort.chop(1); // trailing \001
|
||||||
|
QTcpSocket outsider;
|
||||||
|
outsider.connectToHost("localhost", bouncePort.toUShort());
|
||||||
|
ASSERT_TRUE(outsider.waitForConnected());
|
||||||
|
client.ReadUntil("Refused connection from");
|
||||||
|
|
||||||
|
// 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) {
|
TEST_F(ZNCTest, ChanSaverModule) {
|
||||||
|
|||||||
Reference in new issue
Block a user