aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Grin (Karlson2k) <k2k@narod.ru>2022-05-13 15:15:04 +0300
committerEvgeny Grin (Karlson2k) <k2k@narod.ru>2022-05-13 15:18:49 +0300
commit5fdb9effcb4ad4d6110b36255e73ec59b7a47994 (patch)
tree3cb2841628e410037bcb2277d353eadb27591976
parent3c63be9051816682ce18b77c4ba912bbfcf56469 (diff)
downloadlibmicrohttpd-5fdb9effcb4ad4d6110b36255e73ec59b7a47994.tar.gz
libmicrohttpd-5fdb9effcb4ad4d6110b36255e73ec59b7a47994.zip
digestauth: added detection for possibly fabricated nonces
-rw-r--r--src/include/microhttpd.h2
-rw-r--r--src/microhttpd/digestauth.c219
2 files changed, 153 insertions, 68 deletions
diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h
index b18c11a4..6bf594cc 100644
--- a/src/include/microhttpd.h
+++ b/src/include/microhttpd.h
@@ -96,7 +96,7 @@ extern "C"
96 * they are parsed as decimal numbers. 96 * they are parsed as decimal numbers.
97 * Example: 0x01093001 = 1.9.30-1. 97 * Example: 0x01093001 = 1.9.30-1.
98 */ 98 */
99#define MHD_VERSION 0x00097511 99#define MHD_VERSION 0x00097512
100 100
101/* If generic headers don't work on your platform, include headers 101/* If generic headers don't work on your platform, include headers
102 which define 'va_list', 'size_t', 'ssize_t', 'intptr_t', 'off_t', 102 which define 'va_list', 'size_t', 'ssize_t', 'intptr_t', 'off_t',
diff --git a/src/microhttpd/digestauth.c b/src/microhttpd/digestauth.c
index 3154088f..c7d13866 100644
--- a/src/microhttpd/digestauth.c
+++ b/src/microhttpd/digestauth.c
@@ -211,6 +211,30 @@ enum MHD_DigestAuthResult
211}; 211};
212 212
213/** 213/**
214 * The result of nonce-nc map array check.
215 */
216enum MHD_CheckNonceNC_
217{
218 /**
219 * The nonce and NC are OK (valid and NC was not used before).
220 */
221 MHD_DAUTH_NONCENC_OK = MHD_DAUTH_OK,
222
223 /**
224 * The 'nonce' was overwritten with newer 'nonce' in the same slot or
225 * NC was already used.
226 * The validity of the 'nonce' was not be checked.
227 */
228 MHD_DAUTH_NONCENC_STALE = MHD_DAUTH_NONCE_STALE,
229
230 /**
231 * The 'nonce' is wrong, it was not generated before.
232 */
233 MHD_DAUTH_NONCENC_WRONG = MHD_DAUTH_NONCE_WRONG,
234};
235
236
237/**
214 * Context passed to functions that need to calculate 238 * Context passed to functions that need to calculate
215 * a digest but are orthogonal to the specific 239 * a digest but are orthogonal to the specific
216 * algorithm. 240 * algorithm.
@@ -592,6 +616,36 @@ lookup_sub_value (char *dest,
592 616
593 617
594/** 618/**
619 * Extract timestamp from the given nonce.
620 * @param nonce the nonce to check
621 * @param noncelen the lenght of the nonce, zero for autodetect
622 * @param[out] ptimestamp the pointer to store extracted timestamp
623 * @return true if timestamp was extracted,
624 * false if nonce does not have valid timestamp.
625 */
626static bool
627get_nonce_timestamp (const char *const nonce,
628 size_t noncelen,
629 uint64_t *const ptimestamp)
630{
631 mhd_assert ((0 == noncelen) || (strlen (nonce) == noncelen));
632 if (0 == noncelen)
633 noncelen = strlen (nonce);
634
635 if ( (NONCE_STD_LEN (SHA256_DIGEST_SIZE) != noncelen) &&
636 (NONCE_STD_LEN (MD5_DIGEST_SIZE) != noncelen) )
637 return false;
638
639 if (TIMESTAMP_CHARS_LEN !=
640 MHD_strx_to_uint64_n_ (nonce + noncelen - TIMESTAMP_CHARS_LEN,
641 TIMESTAMP_CHARS_LEN,
642 ptimestamp))
643 return false;
644 return true;
645}
646
647
648/**
595 * Super-fast xor-based "hash" function 649 * Super-fast xor-based "hash" function
596 * 650 *
597 * @param data the data to calculate hash for 651 * @param data the data to calculate hash for
@@ -638,51 +692,96 @@ get_nonce_nc_idx (size_t arr_size,
638 692
639 693
640/** 694/**
641 * Check nonce-nc map array with either new nonce counter 695 * Check nonce-nc map array with the new nonce counter.
642 * or a whole new nonce.
643 * 696 *
644 * @param connection The MHD connection structure 697 * @param connection The MHD connection structure
645 * @param nonce A pointer that referenced a zero-terminated array of nonce 698 * @param nonce A pointer that referenced a zero-terminated array of nonce
646 * @param noncelen the lenth of @a nonce, in characters 699 * @param noncelen the length of @a nonce, in characters
647 * @param nc The nonce counter, zero to add the nonce to the array 700 * @param nc The nonce counter
648 * @return #MHD_YES if successful, #MHD_NO if invalid (or we have no NC array) 701 * @return #MHD_DAUTH_NONCENC_OK if successful,
702 * #MHD_DAUTH_NONCENC_STALE if nonce is stale (or no nonce-nc array
703 * is available),
704 * #MHD_DAUTH_NONCENC_WRONG if nonce was not recodered in nonce-nc map
705 * array, while it should.
649 */ 706 */
650static bool 707static enum MHD_CheckNonceNC_
651check_nonce_nc (struct MHD_Connection *connection, 708check_nonce_nc (struct MHD_Connection *connection,
652 const char *nonce, 709 const char *nonce,
653 size_t noncelen, 710 size_t noncelen,
711 uint64_t nonce_time,
654 uint64_t nc) 712 uint64_t nc)
655{ 713{
656 struct MHD_Daemon *daemon = MHD_get_master (connection->daemon); 714 struct MHD_Daemon *daemon = MHD_get_master (connection->daemon);
657 struct MHD_NonceNc *nn; 715 struct MHD_NonceNc *nn;
658 uint32_t mod; 716 uint32_t mod;
659 bool ret; 717 enum MHD_CheckNonceNC_ ret;
660 718
661 mhd_assert (noncelen == strlen (nonce)); 719 mhd_assert (strlen (nonce) == noncelen);
662 mhd_assert (0 != nc); 720 mhd_assert (0 != nc);
663 if (MAX_NONCE_LENGTH < noncelen) 721 if (MAX_NONCE_LENGTH < noncelen)
664 return false; /* This should be impossible, but static analysis 722 return MHD_DAUTH_NONCENC_WRONG; /* This should be impossible, but static analysis
665 tools have a hard time with it *and* this also 723 tools have a hard time with it *and* this also
666 protects against unsafe modifications that may 724 protects against unsafe modifications that may
667 happen in the future... */ 725 happen in the future... */
668 mod = daemon->nonce_nc_size; 726 mod = daemon->nonce_nc_size;
669 if (0 == mod) 727 if (0 == mod)
670 return false; /* no array! */ 728 return MHD_DAUTH_NONCENC_STALE; /* no array! */
671 if (nc + 64 < nc) 729 if (nc + 64 < nc)
672 return false; /* Overflow, unrealistically high value */ 730 return MHD_DAUTH_NONCENC_STALE; /* Overflow, unrealistically high value */
673 731
674 /*
675 * Look for the nonce, if it does exist and its corresponding
676 * nonce counter is less than the current nonce counter by 1,
677 * then only increase the nonce counter by one.
678 */
679 nn = &daemon->nnc[get_nonce_nc_idx (mod, nonce, noncelen)]; 732 nn = &daemon->nnc[get_nonce_nc_idx (mod, nonce, noncelen)];
680 733
681 MHD_mutex_lock_chk_ (&daemon->nnc_lock); 734 MHD_mutex_lock_chk_ (&daemon->nnc_lock);
682 735
736 mhd_assert (0 == nn->nonce[noncelen]); /* The old value must be valid */
737
683 if ( (0 != memcmp (nn->nonce, nonce, noncelen)) || 738 if ( (0 != memcmp (nn->nonce, nonce, noncelen)) ||
684 (0 != nn->nonce[noncelen]) ) 739 (0 != nn->nonce[noncelen]) )
685 ret = false; /* Nonce does not match, fail */ 740 { /* The nonce in the slot does not match nonce from the client */
741 if (0 == nn->nonce[0])
742 { /* The slot was never used, while the client's nonce value should be
743 * recorded when it was generated by MHD */
744 ret = MHD_DAUTH_NONCENC_WRONG;
745 }
746 else if (0 != nn->nonce[noncelen])
747 { /* The value is the slot is wrong */
748 ret = MHD_DAUTH_NONCENC_STALE;
749 }
750 else
751 {
752 uint64_t slot_ts; /**< The timestamp in the slot */
753 if (! get_nonce_timestamp (nn->nonce, 0, &slot_ts))
754 {
755 mhd_assert (0); /* The value is the slot is wrong */
756 ret = MHD_DAUTH_NONCENC_STALE;
757 }
758 else
759 {
760 /* Unsigned value, will be large if nonce_time is less than slot_ts */
761 const uint64_t ts_diff = TRIM_TO_TIMESTAMP (nonce_time - slot_ts);
762 if ((REUSE_TIMEOUT * 1000) >= ts_diff)
763 {
764 /* The nonce from the client may not have been placed in the slot
765 * because another nonce in that slot has not yet expired. */
766 ret = MHD_DAUTH_NONCENC_STALE;
767 }
768 else if (TRIM_TO_TIMESTAMP (UINT64_MAX) / 2 >= ts_diff)
769 {
770 /* Too large value means that nonce_time is less than slot_ts.
771 * The nonce from the client may have been overwritten by the newer
772 * nonce. */
773 ret = MHD_DAUTH_NONCENC_STALE;
774 }
775 else
776 {
777 /* The nonce from the client should be generated after the nonce
778 * in the slot has been expired, the nonce must be recorded, but
779 * it's not. */
780 ret = MHD_DAUTH_NONCENC_WRONG;
781 }
782 }
783 }
784 }
686 else if (nc > nn->nc) 785 else if (nc > nn->nc)
687 { 786 {
688 /* 'nc' is larger, shift bitmask and bump limit */ 787 /* 'nc' is larger, shift bitmask and bump limit */
@@ -699,7 +798,7 @@ check_nonce_nc (struct MHD_Connection *connection,
699 else 798 else
700 nn->nmask = 0; /* big jump, unset all bits in the mask */ 799 nn->nmask = 0; /* big jump, unset all bits in the mask */
701 nn->nc = nc; 800 nn->nc = nc;
702 ret = true; 801 ret = MHD_DAUTH_NONCENC_OK;
703 } 802 }
704 else if (nc < nn->nc) 803 else if (nc < nn->nc)
705 { 804 {
@@ -710,23 +809,18 @@ check_nonce_nc (struct MHD_Connection *connection,
710 { 809 {
711 /* Out-of-order nonce, but within 64-bit bitmask, set bit */ 810 /* Out-of-order nonce, but within 64-bit bitmask, set bit */
712 nn->nmask |= (UINT64_C (1) << (nn->nc - nc - 1)); 811 nn->nmask |= (UINT64_C (1) << (nn->nc - nc - 1));
713 ret = true; 812 ret = MHD_DAUTH_NONCENC_OK;
714 } 813 }
715 else 814 else
716 /* 'nc' was already used or too old (more then 64 values ago) */ 815 /* 'nc' was already used or too old (more then 64 values ago) */
717 ret = false; 816 ret = MHD_DAUTH_NONCENC_STALE;
718 } 817 }
719 else /* if (nc == nn->nc) */ 818 else /* if (nc == nn->nc) */
720 /* 'nc' was already used */ 819 /* 'nc' was already used */
721 ret = false; 820 ret = MHD_DAUTH_NONCENC_STALE;
722 821
723 MHD_mutex_unlock_chk_ (&daemon->nnc_lock); 822 MHD_mutex_unlock_chk_ (&daemon->nnc_lock);
724#ifdef HAVE_MESSAGES 823
725 if (! ret)
726 MHD_DLOG (daemon,
727 _ ("Stale nonce received. If this happens a lot, you should "
728 "probably increase the size of the nonce array.\n"));
729#endif
730 return ret; 824 return ret;
731} 825}
732 826
@@ -852,36 +946,6 @@ calculate_nonce (uint64_t nonce_time,
852 946
853 947
854/** 948/**
855 * Extract timestamp from the given nonce.
856 * @param nonce the nonce to check
857 * @param noncelen the lenght of the nonce, zero for autodetect
858 * @param[out] ptimestamp the pointer to store extracted timestamp
859 * @return true if timestamp was extracted,
860 * false if nonce does not have valid timestamp.
861 */
862static bool
863get_nonce_timestamp (const char *const nonce,
864 size_t noncelen,
865 uint64_t *const ptimestamp)
866{
867 mhd_assert ((0 == noncelen) || (strlen (nonce) == noncelen));
868 if (0 == noncelen)
869 noncelen = strlen (nonce);
870
871 if ( (NONCE_STD_LEN (SHA256_DIGEST_SIZE) != noncelen) &&
872 (NONCE_STD_LEN (MD5_DIGEST_SIZE) != noncelen) )
873 return false;
874
875 if (TIMESTAMP_CHARS_LEN !=
876 MHD_strx_to_uint64_n_ (nonce + noncelen - TIMESTAMP_CHARS_LEN,
877 TIMESTAMP_CHARS_LEN,
878 ptimestamp))
879 return false;
880 return true;
881}
882
883
884/**
885 * Check whether it is possible to use slot in nonce-nc map array. 949 * Check whether it is possible to use slot in nonce-nc map array.
886 * 950 *
887 * Should be called with mutex held to avoid external modification of 951 * Should be called with mutex held to avoid external modification of
@@ -1380,17 +1444,38 @@ digest_auth_check_all (struct MHD_Connection *connection,
1380 return MHD_DAUTH_WRONG_HEADER; /* invalid nc value */ 1444 return MHD_DAUTH_WRONG_HEADER; /* invalid nc value */
1381 } 1445 }
1382 1446
1383 /* 1447 if (1)
1384 * Checking if that combination of nonce and nc is sound
1385 * and not a replay attack attempt. Refuse if nonce was not
1386 * generated previously.
1387 */
1388 if (! check_nonce_nc (connection,
1389 nonce,
1390 nonce_len,
1391 nci))
1392 { 1448 {
1393 return MHD_DAUTH_NONCE_STALE; 1449 enum MHD_CheckNonceNC_ nonce_nc_check;
1450 /*
1451 * Checking if that combination of nonce and nc is sound
1452 * and not a replay attack attempt. Refuse if nonce was not
1453 * generated previously.
1454 */
1455 nonce_nc_check = check_nonce_nc (connection,
1456 nonce,
1457 nonce_len,
1458 nonce_time,
1459 nci);
1460 if (MHD_DAUTH_NONCENC_STALE == nonce_nc_check)
1461 {
1462#ifdef HAVE_MESSAGES
1463 MHD_DLOG (daemon,
1464 _ ("Stale nonce received. If this happens a lot, you should "
1465 "probably increase the size of the nonce array.\n"));
1466#endif
1467 return MHD_DAUTH_NONCE_STALE;
1468 }
1469 else if (MHD_DAUTH_NONCENC_WRONG == nonce_nc_check)
1470 {
1471#ifdef HAVE_MESSAGES
1472 MHD_DLOG (daemon,
1473 _ ("Received nonce that technically valid, but was not "
1474 "generated by MHD. This may indicate an attack attempt.\n"));
1475#endif
1476 return MHD_DAUTH_NONCE_WRONG;
1477 }
1478 mhd_assert (MHD_DAUTH_NONCENC_OK == nonce_nc_check);
1394 } 1479 }
1395 1480
1396 if (1) 1481 if (1)