diff options
author | Evgeny Grin (Karlson2k) <k2k@narod.ru> | 2022-05-13 15:15:04 +0300 |
---|---|---|
committer | Evgeny Grin (Karlson2k) <k2k@narod.ru> | 2022-05-13 15:18:49 +0300 |
commit | 5fdb9effcb4ad4d6110b36255e73ec59b7a47994 (patch) | |
tree | 3cb2841628e410037bcb2277d353eadb27591976 | |
parent | 3c63be9051816682ce18b77c4ba912bbfcf56469 (diff) | |
download | libmicrohttpd-5fdb9effcb4ad4d6110b36255e73ec59b7a47994.tar.gz libmicrohttpd-5fdb9effcb4ad4d6110b36255e73ec59b7a47994.zip |
digestauth: added detection for possibly fabricated nonces
-rw-r--r-- | src/include/microhttpd.h | 2 | ||||
-rw-r--r-- | src/microhttpd/digestauth.c | 219 |
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 | */ | ||
216 | enum 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 | */ | ||
626 | static bool | ||
627 | get_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 | */ |
650 | static bool | 707 | static enum MHD_CheckNonceNC_ |
651 | check_nonce_nc (struct MHD_Connection *connection, | 708 | check_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 | */ | ||
862 | static bool | ||
863 | get_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) |