Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sherlock-project
GitHub Repository: sherlock-project/sherlock
Path: blob/master/tests/test_validate_targets.py
761 views
1
import pytest
2
import re
3
import rstr
4
5
from sherlock_project.sherlock import sherlock
6
from sherlock_project.notify import QueryNotify
7
from sherlock_project.result import QueryResult, QueryStatus
8
9
10
FALSE_POSITIVE_ATTEMPTS: int = 2 # Since the usernames are randomly generated, it's POSSIBLE that a real username can be hit
11
FALSE_POSITIVE_QUANTIFIER_UPPER_BOUND: int = 15 # If a pattern uses quantifiers such as `+` `*` or `{n,}`, limit the upper bound (0 to disable)
12
FALSE_POSITIVE_DEFAULT_PATTERN: str = r'^[a-zA-Z0-9]{7,20}$' # Used in absence of a regexCheck entry
13
14
15
def set_pattern_upper_bound(pattern: str, upper_bound: int = FALSE_POSITIVE_QUANTIFIER_UPPER_BOUND) -> str:
16
"""Set upper bound for regex patterns that use quantifiers such as `+` `*` or `{n,}`."""
17
def replace_upper_bound(match: re.Match) -> str: # type: ignore
18
lower_bound: int = int(match.group(1)) if match.group(1) else 0 # type: ignore
19
nonlocal upper_bound
20
upper_bound = upper_bound if lower_bound < upper_bound else lower_bound # type: ignore # noqa: F823
21
return f'{{{lower_bound},{upper_bound}}}'
22
23
pattern = re.sub(r'(?<!\\)\{(\d+),\}', replace_upper_bound, pattern) # {n,} # type: ignore
24
pattern = re.sub(r'(?<!\\)\+', f'{{1,{upper_bound}}}', pattern) # +
25
pattern = re.sub(r'(?<!\\)\*', f'{{0,{upper_bound}}}', pattern) # *
26
27
return pattern
28
29
def false_positive_check(sites_info: dict[str, dict[str, str]], site: str, pattern: str) -> QueryStatus:
30
"""Check if a site is likely to produce false positives."""
31
status: QueryStatus = QueryStatus.UNKNOWN
32
33
for _ in range(FALSE_POSITIVE_ATTEMPTS):
34
query_notify: QueryNotify = QueryNotify()
35
username: str = rstr.xeger(pattern)
36
37
result: QueryResult | str = sherlock(
38
username=username,
39
site_data=sites_info,
40
query_notify=query_notify,
41
)[site]['status']
42
43
if not hasattr(result, 'status'):
44
raise TypeError(f"Result for site {site} does not have 'status' attribute. Actual result: {result}")
45
if type(result.status) is not QueryStatus: # type: ignore
46
raise TypeError(f"Result status for site {site} is not of type QueryStatus. Actual type: {type(result.status)}") # type: ignore
47
status = result.status # type: ignore
48
49
if status in (QueryStatus.AVAILABLE, QueryStatus.WAF):
50
return status
51
52
return status
53
54
55
def false_negative_check(sites_info: dict[str, dict[str, str]], site: str) -> QueryStatus:
56
"""Check if a site is likely to produce false negatives."""
57
status: QueryStatus = QueryStatus.UNKNOWN
58
query_notify: QueryNotify = QueryNotify()
59
60
result: QueryResult | str = sherlock(
61
username=sites_info[site]['username_claimed'],
62
site_data=sites_info,
63
query_notify=query_notify,
64
)[site]['status']
65
66
if not hasattr(result, 'status'):
67
raise TypeError(f"Result for site {site} does not have 'status' attribute. Actual result: {result}")
68
if type(result.status) is not QueryStatus: # type: ignore
69
raise TypeError(f"Result status for site {site} is not of type QueryStatus. Actual type: {type(result.status)}") # type: ignore
70
status = result.status # type: ignore
71
72
return status
73
74
@pytest.mark.validate_targets
75
@pytest.mark.online
76
class Test_All_Targets:
77
78
@pytest.mark.validate_targets_fp
79
def test_false_pos(self, chunked_sites: dict[str, dict[str, str]]):
80
"""Iterate through all sites in the manifest to discover possible false-positive inducting targets."""
81
pattern: str
82
for site in chunked_sites:
83
try:
84
pattern = chunked_sites[site]['regexCheck']
85
except KeyError:
86
pattern = FALSE_POSITIVE_DEFAULT_PATTERN
87
88
if FALSE_POSITIVE_QUANTIFIER_UPPER_BOUND > 0:
89
pattern = set_pattern_upper_bound(pattern)
90
91
result: QueryStatus = false_positive_check(chunked_sites, site, pattern)
92
assert result is QueryStatus.AVAILABLE, f"{site} produced false positive with pattern {pattern}, result was {result}"
93
94
@pytest.mark.validate_targets_fn
95
def test_false_neg(self, chunked_sites: dict[str, dict[str, str]]):
96
"""Iterate through all sites in the manifest to discover possible false-negative inducting targets."""
97
for site in chunked_sites:
98
result: QueryStatus = false_negative_check(chunked_sites, site)
99
assert result is QueryStatus.CLAIMED, f"{site} produced false negative, result was {result}"
100
101
102