|
4 | 4 | } from './PhishingDetector';
|
5 | 5 | import { formatHostnameToUrl } from './tests/utils';
|
6 | 6 | import { PhishingDetectorResultType } from './types';
|
| 7 | +import { sha256Hash } from './utils'; |
7 | 8 |
|
8 | 9 | describe('PhishingDetector', () => {
|
9 | 10 | describe('constructor', () => {
|
@@ -1298,6 +1299,33 @@ describe('PhishingDetector', () => {
|
1298 | 1299 | );
|
1299 | 1300 | });
|
1300 | 1301 |
|
| 1302 | + it('check the hash against c2DomainBlocklist, returning the correct result without a version with sub domains', async () => { |
| 1303 | + await withPhishingDetector( |
| 1304 | + [ |
| 1305 | + { |
| 1306 | + blocklist: [], |
| 1307 | + fuzzylist: [], |
| 1308 | + c2DomainBlocklist: [ |
| 1309 | + 'a379a6f6eeafb9a55e378c118034e2751e682fab9f2d30ab13d2125586ce1947', // example.com |
| 1310 | + ], |
| 1311 | + name: 'test-config', |
| 1312 | + tolerance: 2, |
| 1313 | + }, |
| 1314 | + ], |
| 1315 | + async ({ detector }) => { |
| 1316 | + const result = detector.isMaliciousC2Domain( |
| 1317 | + 'https://sub.sub.evil.example.com', |
| 1318 | + ); |
| 1319 | + expect(result).toStrictEqual({ |
| 1320 | + name: 'test-config', |
| 1321 | + result: true, |
| 1322 | + type: PhishingDetectorResultType.C2DomainBlocklist, |
| 1323 | + version: undefined, |
| 1324 | + }); |
| 1325 | + }, |
| 1326 | + ); |
| 1327 | + }); |
| 1328 | + |
1301 | 1329 | it('should return false if URL is invalid', async () => {
|
1302 | 1330 | await withPhishingDetector(
|
1303 | 1331 | [
|
@@ -1538,6 +1566,222 @@ describe('PhishingDetector', () => {
|
1538 | 1566 | );
|
1539 | 1567 | });
|
1540 | 1568 | });
|
| 1569 | + |
| 1570 | + it('should block a specific subdomain but not the parent domain', async () => { |
| 1571 | + const blockedSubdomain = '123.pages.dev'; |
| 1572 | + const blockedSubdomainHash = sha256Hash(blockedSubdomain.toLowerCase()); |
| 1573 | + |
| 1574 | + await withPhishingDetector( |
| 1575 | + [ |
| 1576 | + { |
| 1577 | + blocklist: [], |
| 1578 | + fuzzylist: [], |
| 1579 | + c2DomainBlocklist: [blockedSubdomainHash], |
| 1580 | + name: 'subdomain-only-config', |
| 1581 | + version: 1, |
| 1582 | + tolerance: 2, |
| 1583 | + }, |
| 1584 | + ], |
| 1585 | + async ({ detector }) => { |
| 1586 | + const blockedUrl = 'https://123.pages.dev'; |
| 1587 | + const unblockedUrl = 'https://pages.dev'; |
| 1588 | + |
| 1589 | + // Expect the specific subdomain to be blocked |
| 1590 | + const blockedResult = detector.isMaliciousC2Domain(blockedUrl); |
| 1591 | + expect(blockedResult).toStrictEqual({ |
| 1592 | + name: 'subdomain-only-config', |
| 1593 | + result: true, |
| 1594 | + type: PhishingDetectorResultType.C2DomainBlocklist, |
| 1595 | + version: '1', |
| 1596 | + }); |
| 1597 | + |
| 1598 | + // Expect the parent domain to not be blocked |
| 1599 | + const unblockedResult = detector.isMaliciousC2Domain(unblockedUrl); |
| 1600 | + expect(unblockedResult).toStrictEqual({ |
| 1601 | + result: false, |
| 1602 | + type: PhishingDetectorResultType.C2DomainBlocklist, |
| 1603 | + }); |
| 1604 | + }, |
| 1605 | + ); |
| 1606 | + }); |
| 1607 | + |
| 1608 | + it('should block the parent domain and its subdomains', async () => { |
| 1609 | + const blockedDomain = 'malicious.xyz'; |
| 1610 | + const blockedDomainHash = sha256Hash(blockedDomain.toLowerCase()); |
| 1611 | + |
| 1612 | + await withPhishingDetector( |
| 1613 | + [ |
| 1614 | + { |
| 1615 | + blocklist: [], |
| 1616 | + fuzzylist: [], |
| 1617 | + c2DomainBlocklist: [blockedDomainHash], |
| 1618 | + name: 'parent-domain-config', |
| 1619 | + version: 1, |
| 1620 | + tolerance: 2, |
| 1621 | + }, |
| 1622 | + ], |
| 1623 | + async ({ detector }) => { |
| 1624 | + const blockedParentUrl = 'https://malicious.xyz'; |
| 1625 | + const blockedSubdomainUrl = 'https://123.malicious.xyz'; |
| 1626 | + |
| 1627 | + // Expect the parent domain to be blocked |
| 1628 | + const blockedParentResult = |
| 1629 | + detector.isMaliciousC2Domain(blockedParentUrl); |
| 1630 | + expect(blockedParentResult).toStrictEqual({ |
| 1631 | + name: 'parent-domain-config', |
| 1632 | + result: true, |
| 1633 | + type: PhishingDetectorResultType.C2DomainBlocklist, |
| 1634 | + version: '1', |
| 1635 | + }); |
| 1636 | + |
| 1637 | + // Expect the subdomain to also be blocked because the parent domain is blocked |
| 1638 | + const blockedSubdomainResult = |
| 1639 | + detector.isMaliciousC2Domain(blockedSubdomainUrl); |
| 1640 | + expect(blockedSubdomainResult).toStrictEqual({ |
| 1641 | + name: 'parent-domain-config', |
| 1642 | + result: true, |
| 1643 | + type: PhishingDetectorResultType.C2DomainBlocklist, |
| 1644 | + version: '1', |
| 1645 | + }); |
| 1646 | + }, |
| 1647 | + ); |
| 1648 | + }); |
| 1649 | + |
| 1650 | + it('should not block unrelated subdomains if parent domain is not blocked', async () => { |
| 1651 | + const blockedSubdomain = '123.pages.dev'; |
| 1652 | + const blockedSubdomainHash = sha256Hash(blockedSubdomain.toLowerCase()); |
| 1653 | + |
| 1654 | + await withPhishingDetector( |
| 1655 | + [ |
| 1656 | + { |
| 1657 | + blocklist: [], |
| 1658 | + fuzzylist: [], |
| 1659 | + c2DomainBlocklist: [blockedSubdomainHash], |
| 1660 | + name: 'unrelated-subdomain-config', |
| 1661 | + version: 1, |
| 1662 | + tolerance: 2, |
| 1663 | + }, |
| 1664 | + ], |
| 1665 | + async ({ detector }) => { |
| 1666 | + const blockedUrl = 'https://123.pages.dev'; |
| 1667 | + const unrelatedSubdomainUrl = 'https://456.pages.dev'; |
| 1668 | + const parentDomainUrl = 'https://pages.dev'; |
| 1669 | + |
| 1670 | + // Expect the specific subdomain to be blocked |
| 1671 | + const blockedResult = detector.isMaliciousC2Domain(blockedUrl); |
| 1672 | + expect(blockedResult).toStrictEqual({ |
| 1673 | + name: 'unrelated-subdomain-config', |
| 1674 | + result: true, |
| 1675 | + type: PhishingDetectorResultType.C2DomainBlocklist, |
| 1676 | + version: '1', |
| 1677 | + }); |
| 1678 | + |
| 1679 | + // Expect unrelated subdomain to not be blocked |
| 1680 | + const unrelatedResult = detector.isMaliciousC2Domain( |
| 1681 | + unrelatedSubdomainUrl, |
| 1682 | + ); |
| 1683 | + expect(unrelatedResult).toStrictEqual({ |
| 1684 | + result: false, |
| 1685 | + type: PhishingDetectorResultType.C2DomainBlocklist, |
| 1686 | + }); |
| 1687 | + |
| 1688 | + // Expect the parent domain to not be blocked |
| 1689 | + const parentResult = detector.isMaliciousC2Domain(parentDomainUrl); |
| 1690 | + expect(parentResult).toStrictEqual({ |
| 1691 | + result: false, |
| 1692 | + type: PhishingDetectorResultType.C2DomainBlocklist, |
| 1693 | + }); |
| 1694 | + }, |
| 1695 | + ); |
| 1696 | + }); |
| 1697 | + |
| 1698 | + it('should prioritize allowlist over blocklist even if subdomain is blocked', async () => { |
| 1699 | + const blockedSubdomain = 'blocked.example.com'; |
| 1700 | + const blockedSubdomainHash = sha256Hash(blockedSubdomain.toLowerCase()); |
| 1701 | + |
| 1702 | + await withPhishingDetector( |
| 1703 | + [ |
| 1704 | + { |
| 1705 | + allowlist: ['example.com'], |
| 1706 | + blocklist: [], |
| 1707 | + fuzzylist: [], |
| 1708 | + c2DomainBlocklist: [blockedSubdomainHash], |
| 1709 | + name: 'allowlist-over-blocklist-config', |
| 1710 | + version: 1, |
| 1711 | + tolerance: 2, |
| 1712 | + }, |
| 1713 | + ], |
| 1714 | + async ({ detector }) => { |
| 1715 | + const blockedSubdomainUrl = 'https://blocked.example.com'; |
| 1716 | + const allowlistedUrl = 'https://example.com'; |
| 1717 | + |
| 1718 | + // Expect the subdomain to be allowed because the parent domain is in the allowlist |
| 1719 | + const subdomainResult = |
| 1720 | + detector.isMaliciousC2Domain(blockedSubdomainUrl); |
| 1721 | + expect(subdomainResult).toStrictEqual({ |
| 1722 | + match: 'example.com', |
| 1723 | + name: 'allowlist-over-blocklist-config', |
| 1724 | + result: false, |
| 1725 | + type: PhishingDetectorResultType.Allowlist, |
| 1726 | + version: '1', |
| 1727 | + }); |
| 1728 | + |
| 1729 | + // Ensure the parent domain is also allowed |
| 1730 | + const allowlistedResult = detector.isMaliciousC2Domain(allowlistedUrl); |
| 1731 | + expect(allowlistedResult).toStrictEqual({ |
| 1732 | + match: 'example.com', |
| 1733 | + name: 'allowlist-over-blocklist-config', |
| 1734 | + result: false, |
| 1735 | + type: PhishingDetectorResultType.Allowlist, |
| 1736 | + version: '1', |
| 1737 | + }); |
| 1738 | + }, |
| 1739 | + ); |
| 1740 | + }); |
| 1741 | + |
| 1742 | + it('should handle URLs with multiple subdomains correctly', async () => { |
| 1743 | + const hostname = 'a.b.c.example.com'; |
| 1744 | + const domainName = 'example.com'; |
| 1745 | + const hostnameHash = sha256Hash(hostname.toLowerCase()); |
| 1746 | + const domainNameHash = sha256Hash(domainName.toLowerCase()); |
| 1747 | + |
| 1748 | + await withPhishingDetector( |
| 1749 | + [ |
| 1750 | + { |
| 1751 | + blocklist: [], |
| 1752 | + fuzzylist: [], |
| 1753 | + c2DomainBlocklist: [hostnameHash, domainNameHash], |
| 1754 | + name: 'multi-subdomain-config', |
| 1755 | + version: 1, |
| 1756 | + tolerance: 2, |
| 1757 | + }, |
| 1758 | + ], |
| 1759 | + async ({ detector }) => { |
| 1760 | + const deepSubdomainUrl = 'https://a.b.c.example.com'; |
| 1761 | + const parentDomainUrl = 'https://example.com'; |
| 1762 | + |
| 1763 | + // Expect the subdomain to be blocked because its specific hostname is on the blocklist |
| 1764 | + const deepSubdomainResult = |
| 1765 | + detector.isMaliciousC2Domain(deepSubdomainUrl); |
| 1766 | + expect(deepSubdomainResult).toStrictEqual({ |
| 1767 | + name: 'multi-subdomain-config', |
| 1768 | + result: true, |
| 1769 | + type: PhishingDetectorResultType.C2DomainBlocklist, |
| 1770 | + version: '1', |
| 1771 | + }); |
| 1772 | + |
| 1773 | + // Expect the parent domain to be blocked because it's on the blocklist |
| 1774 | + const parentDomainResult = |
| 1775 | + detector.isMaliciousC2Domain(parentDomainUrl); |
| 1776 | + expect(parentDomainResult).toStrictEqual({ |
| 1777 | + name: 'multi-subdomain-config', |
| 1778 | + result: true, |
| 1779 | + type: PhishingDetectorResultType.C2DomainBlocklist, |
| 1780 | + version: '1', |
| 1781 | + }); |
| 1782 | + }, |
| 1783 | + ); |
| 1784 | + }); |
1541 | 1785 | });
|
1542 | 1786 |
|
1543 | 1787 | type WithPhishingDetectorCallback<ReturnValue> = ({
|
|
0 commit comments