瀏覽代碼

* Port [3319] [3320] to 1.2.14
* Rewrote op-cookies to be fix a security hole. (They also react to '.set hijack' now)



svn: 3321

Bryan Drewery 19 年之前
父節點
當前提交
65fc02dd7f
共有 5 個文件被更改,包括 193 次插入112 次删除
  1. 1 0
      doc/UPDATES
  2. 1 1
      src/chan.h
  3. 111 46
      src/mod/irc.mod/irc.c
  4. 3 2
      src/mod/irc.mod/irc.h
  5. 77 63
      src/mod/irc.mod/mode.c

+ 1 - 0
doc/UPDATES

@@ -28,6 +28,7 @@ Lines prefixed with '-' were disabled before release and are not finished, or ar
 * Add ghost-inspired feature: backup bots. Bots marked +B will only join channels marked +backup.
 * Fixed cmd_nohelp (for viewing undocumented cmds - mainly debugging cmds)
 * cmd_adduser sends a PRIVMSG now instead of a NOTICE
+* Rewrote op-cookies to be fix a security hole. (They also react to '.set hijack' now)
 
 1.2.13 - http://wraith.shatow.net/milestone/1.2.13
 * Fix cmd_chanset accepting invalid flags

+ 1 - 1
src/chan.h

@@ -135,7 +135,7 @@ struct chanset_t {
     int type;
   } cmode[MODES_PER_LINE_MAX];                 /* parameter-type mode changes -        */
   struct {
-    char *op;
+    memberlist *op;
   } ccmode[MODES_PER_LINE_MAX];                 /* parameter-type mode changes -        */
   /* detect floods */
   time_t floodtime[FLOOD_CHAN_MAX];

+ 111 - 46
src/mod/irc.mod/irc.c

@@ -268,62 +268,127 @@ static void cache_invite(struct chanset_t *chan, char *nick, char *host, char *h
   dprintf(DP_SERVER, "INVITE %s %s\n", nick, chan->name);
 }
 
-static char *
-makecookie(char *chn, char *bnick)
-{
-  char *buf = NULL, randstring[5] = "", ts[11] = "", *chname = NULL, *hash = NULL, tohash[50] = "", nick[NICKMAX + 1] = "";
+const char * cookie_hash(const char* chname, const memberlist* opper, const memberlist* opped, const char* ts, const char* salt) {
+  char tohash[101] = "";
 
-  chname = strdup(chn);
+  /* Only use first 3 chars of chan */
+  simple_snprintf(tohash, sizeof(tohash), "%c%c%c%c%s%c%c%c%c%c%s%s%s%s", 
+                                     settings.salt2[0], 
+                                     toupper(chname[0]),
+                                     toupper(chname[1]),
+                                     toupper(chname[2]),  
+                                     &ts[4],
+                                     salt[0], salt[1], salt[2], salt[3],
+                                     settings.salt2[15],
+                                     opper->nick,
+                                     opped->nick,
+                                     opped->userhost,
+                                     opper->userhost);
+#ifdef DEBUG
+sdprintf("chname: %s ts: %s salt: %c%c%c%c", chname, ts, salt[0], salt[1], salt[2], salt[3]);
+sdprintf("tohash: %s", tohash);
+#endif
+  return MD5(tohash);
+}
 
-  make_rand_str(randstring, 4);
+#define HASH_INDEX1(_x) (8 + (_x))
+#define HASH_INDEX2(_x) (16 + (_x))
+#define HASH_INDEX3(_x) (18 + (_x))
+
+void makecookie(char *out, size_t len, const char *chname, const memberlist* opper, const memberlist* m1, const memberlist* m2, const memberlist* m3) {
+  char randstring[5] = "", ts[11] = "";
 
+  make_rand_str(randstring, 4);
   /* &ts[4] is now last 6 digits of time */
-  sprintf(ts, "%010li", now + timesync);
+  egg_snprintf(ts, sizeof(ts), "%010li", now + timesync);
   
-  /* Only use first 3 chars of chan */
-  if (strlen(chname) > 2)
-    chname[3] = 0;
-  strtoupper(chname);
-  strlcpy(nick, bnick, sizeof(nick));
-  strtolower(nick);
-  simple_sprintf(tohash, "%c%s%s%s%s%c", settings.salt2[0], nick, chname, &ts[4], randstring, settings.salt2[15]);
-  hash = MD5(tohash);
-  buf = (char *) my_calloc(1, 20);
-  simple_sprintf(buf, "%c%c%c!%s@%s", hash[8], hash[16], hash[18], randstring, ts);
-  /* sprintf(buf, "%c/%s!%s@%X", hash[16], randstring, ts, BIT31); */
-  free(chname);
-  return buf;
+  const char *hash1 = cookie_hash(chname, opper, m1, ts, randstring);
+  const char* hash2 = m2 ? cookie_hash(chname, opper, m2, ts, randstring) : NULL;
+  const char* hash3 = m3 ? cookie_hash(chname, opper, m3, ts, randstring) : NULL;
+
+#ifdef DEBUG
+sdprintf("hash1: %s", hash1);
+if (hash2)
+sdprintf("hash2: %s", hash2);
+if (hash3)
+sdprintf("hash3: %s", hash3);
+#endif
+
+//  register size_t len = m3 ? 25 : (m2 ? 22 : 19);
+//  register char* buf = (char*) my_calloc(1, len + 1);
+
+  if (m3)
+    simple_snprintf(out, len + 1, "%c%c%c%c%c%c%c%c%c!%s@%s", 
+                         hash1[HASH_INDEX1(0)], 
+                         hash1[HASH_INDEX2(0)], 
+                         hash1[HASH_INDEX3(0)], 
+                         hash2[HASH_INDEX1(1)], 
+                         hash2[HASH_INDEX2(1)], 
+                         hash2[HASH_INDEX3(1)], 
+                         hash3[HASH_INDEX1(2)], 
+                         hash3[HASH_INDEX2(2)], 
+                         hash3[HASH_INDEX3(2)], 
+                         randstring, 
+                         ts);
+  else if (m2)
+    simple_snprintf(out, len + 1, "%c%c%c%c%c%c!%s@%s", 
+                         hash1[HASH_INDEX1(0)], 
+                         hash1[HASH_INDEX2(0)], 
+                         hash1[HASH_INDEX3(0)], 
+                         hash2[HASH_INDEX1(1)], 
+                         hash2[HASH_INDEX2(1)], 
+                         hash2[HASH_INDEX3(1)], 
+                         randstring, 
+                         ts);
+  else
+    simple_snprintf(out, len + 1, "%c%c%c!%s@%s", 
+                         hash1[HASH_INDEX1(0)], 
+                         hash1[HASH_INDEX2(0)], 
+                         hash1[HASH_INDEX3(0)], 
+                         randstring, 
+                         ts);
+#ifdef DEBUG
+sdprintf("cookie: %s", out);
+#endif
+//  return buf;
 }
 
-static int
-checkcookie(char *chn, char *bnick, char *cookie)
-{
-  char randstring[5] = "", ts[11] = "", *chname = NULL, *hash = NULL, tohash[50] = "", *p = NULL;
-  time_t optime = 0;
-
-  chname = strdup(chn);
-  p = cookie;
-  p += 4; /* has! */
-  strlcpy(randstring, p, sizeof(randstring));
-  p += 5; /* rand@ */
-  /* &ts[4] is now last 6 digits of time */
-  strlcpy(ts, p, sizeof(ts));
-  optime = atol(ts);
+/* 111222333!salt@timestamp. */
+static int checkcookie(const char *chname, const memberlist* opper, const memberlist* opped, const char *cookie, int indexHint) {
+#define TS(_x) (6 + (_x) + ((hashes << 1) + hashes)) /* x + (hashes * 3) */
+#define SALT(_x) (1 + (_x) + ((hashes << 1) + hashes)) /* x + (hashes * 3) */
+  /* How many hashes are in the cookie? */
+  const size_t hashes = cookie[3] == '!' ? 1 : (cookie[6] == '!' ? 2 : 3);
 
-  /* Only use first 3 chars of chan */
-  if (strlen(chname) > 2)
-    chname[3] = 0;
-  strtoupper(chname);
-  /* hash!rand@ts */
-  strtolower(bnick);
-  simple_sprintf(tohash, "%c%s%s%s%s%c", settings.salt2[0], bnick, chname, &ts[4], randstring, settings.salt2[15]);
-  free(chname);
-  hash = MD5(tohash);
-  if (!(hash[8] == cookie[0] && hash[16] == cookie[1] && hash[18] == cookie[2]))
-    return BC_HASH;
+#ifdef DEBUG
+sdprintf("ts from cookie: %s", &cookie[TS(0)]);
+#endif
+  /* The timestamp is already null-terminated :) */
+  const char *ts = &cookie[TS(0)];
+  const time_t optime = atol(ts);
   if (((now + timesync) - optime) > 1800)
     return BC_SLACK;
-  return 0;
+
+  const char *salt = &cookie[SALT(0)];
+  const char *hash = cookie_hash(chname, opper, opped, ts, salt);
+#ifdef DEBUG
+sdprintf("hash: %s", hash);
+#endif
+
+  /* Compare the expected hash to each of the given hashes */
+
+
+  /* indexHint, Which position of the +ooo are we? (1) could be either index (1) or (2).. but not (0). */
+  for (size_t i = indexHint; i < hashes; ++i) {
+    cookie += ((i << 1) + i); /* i * 3 */
+    if ((hash[HASH_INDEX1(i)] == cookie[0] && 
+         hash[HASH_INDEX2(i)] == cookie[1] && 
+         hash[HASH_INDEX3(i)] == cookie[2])) {
+      return 0;
+    }
+  }
+
+  return BC_HASH;
 }
 
 /*

+ 3 - 2
src/mod/irc.mod/irc.h

@@ -58,8 +58,9 @@ static void cache_debug(void);
 #endif /* CACHE */
 static void cache_invite(struct chanset_t *, char *, char *, char *, bool, bool);
 
-static char *makecookie(char *, char *);
-static int checkcookie(char *, char *, char *);
+//static char *makecookie(const char *, const memberlist*, const memberlist*, const memberlist* = NULL, const memberlist* = NULL);
+void makecookie(char*, size_t, const char *, const memberlist*, const memberlist*, const memberlist* = NULL, const memberlist* = NULL);
+static int checkcookie(const char*, const memberlist*, const memberlist*, const char*, int);
 static bool me_voice(struct chanset_t *);
 static bool any_ops(struct chanset_t *);
 static char *getchanmode(struct chanset_t *);

+ 77 - 63
src/mod/irc.mod/mode.c

@@ -7,6 +7,8 @@
  */
 
 
+#include "src/shell.h"
+
 /* Reversing this mode? */
 static bool reversing = 0;
 
@@ -122,17 +124,22 @@ flush_cookies(struct chanset_t *chan, int pri)
 {
   char out[512] = "", *p = out, post[512] = "";
   size_t postsize = sizeof(post) - 1;
+  memberlist *nicks[3] = { NULL, NULL, NULL };
 
   chan->cbytes = 0;
 
   for (unsigned int i = 0; i < (modesperline - 1); i++) {
-    if (chan->ccmode[i].op && postsize > strlen(chan->ccmode[i].op)) {
+    if (chan->ccmode[i].op && postsize > strlen(chan->ccmode[i].op->nick)) {
 
+      /* Build modes .. */
       *p++ = '+';
       *p++ = 'o';
-      postsize -= egg_strcatn(post, chan->ccmode[i].op, sizeof(post));
+
+      /* .. and params */
+      postsize -= egg_strcatn(post, chan->ccmode[i].op->nick, sizeof(post));
       postsize -= egg_strcatn(post, " ", sizeof(post));
-      free(chan->ccmode[i].op), chan->ccmode[i].op = NULL;
+      nicks[i] = chan->ccmode[i].op;
+      chan->ccmode[i].op = NULL;
     }
   }
 
@@ -146,18 +153,18 @@ flush_cookies(struct chanset_t *chan, int pri)
   if (post[0]) {
     /* remove the trailing space... */
     size_t myindex = (sizeof(post) - 1) - postsize;
-    char *cookie;
 
-    if (myindex > 0 && post[myindex - 1] == ' ')
-      post[myindex - 1] = 0;
+    if (myindex > 0 && post[--myindex] == ' ')
+      post[myindex] = 0;
+
+    //myindex is how long post is
 
     egg_strcatn(out, " ", sizeof(out));
     egg_strcatn(out, post, sizeof(out));
     egg_strcatn(out, " ", sizeof(out));
     
-    cookie = makecookie(chan->dname, conf.bot->nick);
-    egg_strcatn(out, cookie, sizeof(out));
-    free(cookie);
+    myindex += (p - out) + 2;  //(p-out)=outlen + 2 spaces
+    makecookie(&out[myindex], sizeof(out) - myindex, chan->dname, ismember(chan, botname), nicks[0], nicks[1], nicks[2]);
   }
   if (out[0]) {
     if (pri == QUICK) {
@@ -347,7 +354,8 @@ real_add_mode(struct chanset_t *chan, const char plus, const char mode, const ch
     }
   }
 
-  int type, modes, l;
+  int type, modes;
+  size_t len = 0;
   unsigned int i;
   masklist *m = NULL;
   char s[21] = "";
@@ -413,30 +421,29 @@ real_add_mode(struct chanset_t *chan, const char plus, const char mode, const ch
     /* for cookie ops, use ccmode instead of cmode */
     if (cookie) {
       for (i = 0; i < (modesperline - 1); i++)
-        if (chan->ccmode[i].op != NULL && !rfc_casecmp(chan->ccmode[i].op, op))
+        if (chan->ccmode[i].op != NULL && !rfc_casecmp(chan->ccmode[i].op->nick, mx->nick))
           return;               /* Already in there :- duplicate */
-      l = strlen(op) + 1;
-      if (chan->cbytes + l > mode_buf_len)
+      len = strlen(mx->nick) + 1;
+      if (chan->cbytes + len > mode_buf_len)
         flush_mode(chan, NORMAL);
       for (i = 0; i < (modesperline - 1); i++)
         if (!chan->ccmode[i].op) {
-          chan->ccmode[i].op = (char *) my_calloc(1, l);
-          chan->cbytes += l;    /* Add 1 for safety */
-          strcpy(chan->ccmode[i].op, op);
+          chan->ccmode[i].op = mx;
+          chan->cbytes += len;    
           break;
         }
     } else {
       for (i = 0; i < modesperline; i++)
         if (chan->cmode[i].type == type && chan->cmode[i].op != NULL && !rfc_casecmp(chan->cmode[i].op, op))
           return;               /* Already in there :- duplicate */
-      l = strlen(op) + 1;
-      if (chan->bytes + l > mode_buf_len)
+      len = strlen(op) + 1;
+      if (chan->bytes + len > mode_buf_len)
         flush_mode(chan, NORMAL);
       for (i = 0; i < modesperline; i++)
         if (chan->cmode[i].type == 0) {
           chan->cmode[i].type = type;
-          chan->cmode[i].op = (char *) my_calloc(1, l);
-          chan->bytes += l;     /* Add 1 for safety */
+          chan->cmode[i].op = (char *) my_calloc(1, len);
+          chan->bytes += len;     /* Add 1 for safety */
           strcpy(chan->cmode[i].op, op);
           break;
         }
@@ -1123,16 +1130,38 @@ gotmode(char *from, char *msg)
               if (unbans != 1 || (strncmp(modes[modecnt - 1], "-b", 2))) {
                 isbadop = BC_NOCOOKIE;
               } else {
-					                 /* hash!rand@time */
-                isbadop = checkcookie(chan->dname, u->handle, &(modes[modecnt - 1][3]));
-              }
-              if (isbadop) {
-                putlog(LOG_WARNING, "*", "%s opped in %s with bad cookie(%d): %s", m->nick, chan->dname, isbadop, msg);
-                n = i = 0;
-                switch (role) {
-                  case 0:
-                    break;
-                  case 1:
+                /* Check the hash for each opped nick and punish the opped client if it fails
+                   * Punish the opper lastly (and once)
+                 */
+                bool failure = 0;
+                for (i = 0; i < modecnt; i++) {
+                  if (msign == '+' && mmode == 'o') {
+                    mv = ismember(chan, mparam);
+
+                    const char *cookie = &(modes[modecnt - 1][3]);
+                    if ((isbadop = checkcookie(chan->dname, m, mv, cookie, i))) {
+                      //if (!failure) { /* First failure */
+                        failure = 1;
+                      //}
+
+                      /* Kick the opped client */
+                      if (randint(7) == (unsigned int) i) {
+                        if (!mv || !chan_sentkick(mv)) {
+                          if (mv)
+                            mv->flags |= SENTKICK;
+                          const size_t len = simple_snprintf(tmp, sizeof(tmp), "KICK %s %s :%s%s\r\n", chan->name, mparam, kickprefix, response(RES_BADOPPED));
+                          tputs(serv, tmp, len);
+                        }
+                      }
+                    }
+                  }
+                }
+                if (failure) { /* One of the hashes failed! */
+                  /* Did *I* do this heinous act? */
+                  if (match_my_nick(m->nick)) {
+                    detected(DETECT_HIJACK, "Possible Hijack: bad cookie");
+                  }
+                  if (randint(7) == 0) {
                     /* Kick opper */
                     if (!chan_sentkick(m)) {
                       m->flags |= SENTKICK;
@@ -1141,39 +1170,24 @@ gotmode(char *from, char *msg)
                     }
                     simple_sprintf(tmp, "%s!%s MODE %s %s", m->nick, m->userhost, chan->dname, modes[modecnt - 1]);
                     deflag_user(u, DEFLAG_BADCOOKIE, tmp, chan);
-                    break;
-                  default:
-                    n = role - 1;
-                    i = 0;
-                    while ((i < modecnt) && (n > 0)) {
-                      if (modes[i] && !strncmp(modes[i], "+o", 2))
-                        n--;
-                      if (n)
-                        i++;
-                    }
-                    if (!n) {
-                      for (i = 0; i < modecnt; i++) {
-                        if (msign == '+' && mmode == 'o') {
-                          mv = ismember(chan, mparam);
-                          if (!mv || !chan_sentkick(mv)) {
-                            if (mv)
-                              mv->flags |= SENTKICK;
-                            const size_t len = simple_snprintf(tmp, sizeof(tmp), "KICK %s %s :%s%s\r\n", chan->name, mparam, kickprefix, response(RES_BADOPPED));
-                            tputs(serv, tmp, len);
-                          }
-                        }
-                      }
-                    }
-                }
-
-                if (isbadop == BC_NOCOOKIE)
-                  putlog(LOG_WARN, "*", "Missing cookie: %s!%s MODE %s %s", m->nick, m->userhost, chan->dname, modes[modecnt - 1]);
-                else if (isbadop == BC_HASH)
-                  putlog(LOG_WARN, "*", "Invalid cookie (bad hash): %s!%s MODE %s %s", m->nick, m->userhost, chan->dname, modes[modecnt - 1]);
-                else if (isbadop == BC_SLACK)
-                  putlog(LOG_WARN, "*", "Invalid cookie (bad time): %s!%s MODE %s %s", m->nick, m->userhost, chan->dname, modes[modecnt - 1]);
-              } else
-                putlog(LOG_DEBUG, "@", "Good op: %s", modes[modecnt - 1]);
+                  }
+                  /* Do the logging last as it can slow down the KICK pushing */
+                  putlog(LOG_WARNING, "*", "%s opped in %s with bad cookie(%d): %s", m->nick, chan->dname, isbadop, msg);
+                  if (isbadop == BC_NOCOOKIE)
+                    putlog(LOG_WARN, "*", "Missing cookie: %s!%s MODE %s %s", 
+                                    m->nick, m->userhost, chan->dname, modes[modecnt - 1]);
+                  else if (isbadop == BC_HASH)
+                    putlog(LOG_WARN, "*", "Invalid cookie (bad hash): %s!%s MODE %s %s", 
+                                     m->nick, m->userhost, chan->dname, modes[modecnt - 1]);
+                  else if (isbadop == BC_SLACK)
+                    putlog(LOG_WARN, "*", "Invalid cookie (bad time): %s!%s MODE %s %s", 
+                                          m->nick, m->userhost, chan->dname, modes[modecnt - 1]);
+                } 
+#ifdef DEBUG
+                else
+                  putlog(LOG_DEBUG, "@", "Good op: %s", modes[modecnt - 1]);
+#endif
+              }
             }
 
             /* manop */