endpointscanner 7.2.3__tar.gz → 7.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: endpointscanner
3
- Version: 7.2.3
3
+ Version: 7.2.4
4
4
  Summary: Website endpoint reconnaissance tool and rate limit tester that can bypass simple captchas and WAFs.
5
5
  Project-URL: Homepage, https://github.com/SphericalFlower52811/endpointscanner
6
6
  Project-URL: Issues, https://github.com/SphericalFlower52811/endpointscanner/issues
@@ -14,9 +14,10 @@ Requires-Dist: beautifulsoup4
14
14
  Requires-Dist: playwright
15
15
  Requires-Dist: playwright-stealth
16
16
  Requires-Dist: httpx[http2]>=0.27.0
17
+ Requires-Dist: colorama>=0.4.6
17
18
  Dynamic: license-file
18
19
 
19
- # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.3)
20
+ # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.4)
20
21
 
21
22
  A fast automated website reconnaissance tool that extracts endpoints, files, and even external links from websites. Automates IDOR and broken access control vulnerability testing through replacing variables with 1 in endpoints. Has a built in rate limit tester that can test on any endpoint, and can bypass simple WAFs/captchas and client-side SPAs.
22
23
 
@@ -191,14 +192,19 @@ Version 7.2.2 just changed wording and description of the tool to be more clear.
191
192
 
192
193
  Version 7.2.3 added one more sensitive endpoint and fixed bug where some paths would be / from extra files.
193
194
 
195
+ Version 7.2.4 added:
196
+
197
+ - fixing pdf keys like /Btn and /Widget used in jsPDF, as they were previously detected as endpoints
198
+ - better SPA separation
199
+ - added more asset formats (.avif, .mp3, .mp4, .webm, .wav)
200
+ - Added text at start saying Endpointscanner 7.2.4 (i wanted it to have the vibe of most open source tools but the 3d text was just too much)
201
+
194
202
  ## Plans for next version and the future
195
203
 
196
204
  Version 7.3 is planned to have:
197
205
 
198
- - More JS Stacks to detect
206
+ - More JS Stacks to detect (and more accurate)
199
207
  - Detecting what type of captcha was used if the script is blocked.
200
- - Fix pdf keys that look like endpoints (e.g. /Btn, /Widget)
201
- - Fix bug in scanning extra file: openid configuration. (currently no verification that the file is in the config format, meaning on SPAs it will be mistaken as a real file.)
202
208
 
203
209
  Future plans (May be added in the next version):
204
210
 
@@ -1,4 +1,4 @@
1
- # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.3)
1
+ # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.4)
2
2
 
3
3
  A fast automated website reconnaissance tool that extracts endpoints, files, and even external links from websites. Automates IDOR and broken access control vulnerability testing through replacing variables with 1 in endpoints. Has a built in rate limit tester that can test on any endpoint, and can bypass simple WAFs/captchas and client-side SPAs.
4
4
 
@@ -173,14 +173,19 @@ Version 7.2.2 just changed wording and description of the tool to be more clear.
173
173
 
174
174
  Version 7.2.3 added one more sensitive endpoint and fixed bug where some paths would be / from extra files.
175
175
 
176
+ Version 7.2.4 added:
177
+
178
+ - fixing pdf keys like /Btn and /Widget used in jsPDF, as they were previously detected as endpoints
179
+ - better SPA separation
180
+ - added more asset formats (.avif, .mp3, .mp4, .webm, .wav)
181
+ - Added text at start saying Endpointscanner 7.2.4 (i wanted it to have the vibe of most open source tools but the 3d text was just too much)
182
+
176
183
  ## Plans for next version and the future
177
184
 
178
185
  Version 7.3 is planned to have:
179
186
 
180
- - More JS Stacks to detect
187
+ - More JS Stacks to detect (and more accurate)
181
188
  - Detecting what type of captcha was used if the script is blocked.
182
- - Fix pdf keys that look like endpoints (e.g. /Btn, /Widget)
183
- - Fix bug in scanning extra file: openid configuration. (currently no verification that the file is in the config format, meaning on SPAs it will be mistaken as a real file.)
184
189
 
185
190
  Future plans (May be added in the next version):
186
191
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: endpointscanner
3
- Version: 7.2.3
3
+ Version: 7.2.4
4
4
  Summary: Website endpoint reconnaissance tool and rate limit tester that can bypass simple captchas and WAFs.
5
5
  Project-URL: Homepage, https://github.com/SphericalFlower52811/endpointscanner
6
6
  Project-URL: Issues, https://github.com/SphericalFlower52811/endpointscanner/issues
@@ -14,9 +14,10 @@ Requires-Dist: beautifulsoup4
14
14
  Requires-Dist: playwright
15
15
  Requires-Dist: playwright-stealth
16
16
  Requires-Dist: httpx[http2]>=0.27.0
17
+ Requires-Dist: colorama>=0.4.6
17
18
  Dynamic: license-file
18
19
 
19
- # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.3)
20
+ # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.4)
20
21
 
21
22
  A fast automated website reconnaissance tool that extracts endpoints, files, and even external links from websites. Automates IDOR and broken access control vulnerability testing through replacing variables with 1 in endpoints. Has a built in rate limit tester that can test on any endpoint, and can bypass simple WAFs/captchas and client-side SPAs.
22
23
 
@@ -191,14 +192,19 @@ Version 7.2.2 just changed wording and description of the tool to be more clear.
191
192
 
192
193
  Version 7.2.3 added one more sensitive endpoint and fixed bug where some paths would be / from extra files.
193
194
 
195
+ Version 7.2.4 added:
196
+
197
+ - fixing pdf keys like /Btn and /Widget used in jsPDF, as they were previously detected as endpoints
198
+ - better SPA separation
199
+ - added more asset formats (.avif, .mp3, .mp4, .webm, .wav)
200
+ - Added text at start saying Endpointscanner 7.2.4 (i wanted it to have the vibe of most open source tools but the 3d text was just too much)
201
+
194
202
  ## Plans for next version and the future
195
203
 
196
204
  Version 7.3 is planned to have:
197
205
 
198
- - More JS Stacks to detect
206
+ - More JS Stacks to detect (and more accurate)
199
207
  - Detecting what type of captcha was used if the script is blocked.
200
- - Fix pdf keys that look like endpoints (e.g. /Btn, /Widget)
201
- - Fix bug in scanning extra file: openid configuration. (currently no verification that the file is in the config format, meaning on SPAs it will be mistaken as a real file.)
202
208
 
203
209
  Future plans (May be added in the next version):
204
210
 
@@ -3,3 +3,4 @@ beautifulsoup4
3
3
  playwright
4
4
  playwright-stealth
5
5
  httpx[http2]>=0.27.0
6
+ colorama>=0.4.6
@@ -8,6 +8,7 @@ import time
8
8
  import json
9
9
  from playwright.sync_api import sync_playwright #headless browser to solve captcha
10
10
  from playwright_stealth import Stealth #ensure strict firewalls do not block the playwright browser
11
+ from colorama import Fore, Style, init
11
12
 
12
13
  HEADER = {
13
14
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
@@ -18,6 +19,21 @@ USELESSSTUFF = {
18
19
  "localhost", "127.0.0.1", "0.0.0.0",
19
20
  "w3.org", "schema.org", "xml.org", "://microsoft.com", "/../"
20
21
  }
22
+ #jspdf makes all those for no reason
23
+ JSPDF_SIGNATURE_KEYS = {
24
+ "/ASCII85Decode", "/ASCII85Encode", "/ASCIIHexDecode", "/ASCIIHexEncode",
25
+ "/Annot", "/Btn", "/CIDSystemInfo", "/Ch", "/FlateDecode", "/FlateEncode",
26
+ "/Form", "/I", "/Image", "/Outlines", "/Pattern", "/Sig", "/Tx", "/Widget", "/XObject"
27
+ }
28
+
29
+ S_HEADER = {
30
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
31
+ "Cache-Control": "no-store",
32
+ "Pragma": "no-cache",
33
+ "If-None-Match": "",
34
+ "If-Modified-Since": ""
35
+ }
36
+
21
37
  unsorted_paths = []
22
38
  e_files = []
23
39
  start_test_time = None
@@ -347,6 +363,13 @@ def main():
347
363
  parser.add_argument("-ro", "--raw-output", action="store_true", help="Do not sort out endpoints after finding them. Will leave out sensitive endpoints whether they are exposed or not.")
348
364
  parser.add_argument("-rh", "--ratelimit-header", type=str, default=None, help="Custom headers. Must be seperated by a pipe(|), or newlines. Example use: Cookies: {ExampleCookie: example} | Accept: application/json, text/plain, */*. If the custom header contains double quotes, please use single quotes instead of double quotes to pass this flag.")
349
365
  args = parser.parse_args()
366
+ if not args.only_res:
367
+ init(autoreset=True)
368
+ print()
369
+ print("-" * 40)
370
+ print(f"{Style.BRIGHT}Endpointscanner {Fore.LIGHTMAGENTA_EX}v7.2.4")
371
+ print("-" * 40)
372
+ print()
350
373
  #ratelimit type always defaults to get
351
374
  if (args.ratelimit_type != 'GET' or args.ratelimit_var or args.ratelimit_body or args.ratelimit_header) and args.ratelimit is None:
352
375
  passedrateargs = []
@@ -592,7 +615,7 @@ def main():
592
615
  #openid
593
616
  try:
594
617
  oidc_res = requests.get(urljoin(target, "/.well-known/openid-configuration"), headers=E_HEADER, impersonate="chrome120", timeout=4)
595
- if oidc_res.status_code == 200 and "{" in oidc_res.text:
618
+ if oidc_res.status_code == 200 and "authorization_endpoint" in oidc_res.text and "issuer" in oidc_res.text:
596
619
  e_files.append("/.well-known/openid-configuration")
597
620
  paths = re.findall(r'https?://[^/]+(/[^"\']*)', oidc_res.text)
598
621
  for path in paths:
@@ -661,6 +684,10 @@ def main():
661
684
  start_test_time = time.perf_counter()
662
685
  args.scan_timeout = 5.0 #5min more
663
686
  matches = re.findall(p, respo.text)
687
+ detected_library_keys = set(matches).intersection(JSPDF_SIGNATURE_KEYS)
688
+ if len(detected_library_keys) >= 3:
689
+ matches = [m for m in matches if m not in JSPDF_SIGNATURE_KEYS]
690
+
664
691
  for m in matches:
665
692
  m_clean = re.sub(r'(\$\{.*?\}|:[a-zA-Z0-9]+)', '1', m)
666
693
  m_clean = m_clean.strip()
@@ -782,6 +809,9 @@ def main():
782
809
  current_filename = js_url
783
810
  for p in patterns:
784
811
  matches = re.findall(p, js_res.text)
812
+ detected_library_keys = set(matches).intersection(JSPDF_SIGNATURE_KEYS)
813
+ if len(detected_library_keys) >= 3:
814
+ matches = [m for m in matches if m not in JSPDF_SIGNATURE_KEYS]
785
815
  for m in matches:
786
816
  m_clean = re.sub(r'(\$\{.*?\}|:[a-zA-Z0-9]+)', '1', m)
787
817
  m_clean = m_clean.strip()
@@ -831,8 +861,13 @@ def main():
831
861
  start_test_time = time.perf_counter()
832
862
  args.scan_timeout = 5.0 #5min more
833
863
  try:
864
+ DOWNLOAD_XML_HEADERS = HEADER.copy()
865
+ DOWNLOAD_XML_HEADERS["Cache-Control"] = "no-cache"
866
+ DOWNLOAD_XML_HEADERS["Pragma"] = "no-cache"
867
+ DOWNLOAD_XML_HEADERS["If-None-Match"] = ""
868
+ DOWNLOAD_XML_HEADERS["If-Modified-Since"] = ""
834
869
  target_xml_url = urljoin(target, xmlfile)
835
- x_res = requests.get(target_xml_url, headers=HEADER, impersonate="chrome120", timeout=4)
870
+ x_res = requests.get(target_xml_url, headers=DOWNLOAD_XML_HEADERS, impersonate="chrome120", timeout=4)
836
871
 
837
872
  if x_res.status_code == 200 and "<loc" in x_res.text.lower():
838
873
  locs = re.findall(r'<loc>https?://[^/]+(/[^<]+)</loc>', x_res.text, re.IGNORECASE)
@@ -862,9 +897,11 @@ def main():
862
897
  results_services, results_ext, results_subd = [], [], []
863
898
  results_frameworks, results_assets = [], []
864
899
  unsorted = []
900
+ assets_suffix = "" if args.show_assets else " (Hidden, use --show-assets to show)"
901
+ dead_suffix = "" if args.show_404s else " (Hidden, use --show-404s to show)"
865
902
  if not args.raw_output:
866
903
  if not args.only_res:
867
- print(f"Total paths to test: {len(found_paths)}")
904
+ print(f"Total paths to test: {len(found_paths)} (Scraped: {len(found_paths) - len(SENSITIVE_ENDPOINT)} | Built-in: {len(SENSITIVE_ENDPOINT)})")
868
905
  print("Testing endpoints...")
869
906
  for path in sorted(found_paths):
870
907
  display_path = discovered_in_js.get(path, path)
@@ -910,7 +947,7 @@ def main():
910
947
 
911
948
  r = requests.get(
912
949
  urljoin(target, path),
913
- headers=HEADER,
950
+ headers=S_HEADER,
914
951
  cookies=session_cookies,
915
952
  timeout=5,
916
953
  allow_redirects=False,
@@ -925,8 +962,8 @@ def main():
925
962
  service_markers = ["/api", "/v1", "/v2", "socket.io", "engine.io", "/graphql", "/webhook", "/rpc", "/actuator", "/swagger", "/v3/api-docs", "/rest/", "/ws", "/metrics"]
926
963
  is_machine_path = any(marker in path.lower() for marker in service_markers)
927
964
 
928
- media_extensions = ('.png', '.jpg', '.jpeg', '.svg', '.webp', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.swf')
929
- framework_extensions = ('.js', '.css', '.json', '.txt', '.xml', '.map')
965
+ media_extensions = ('.png', '.jpg', '.jpeg', '.svg', '.webp', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.swf', '.mp4', '.mp3', '.avif', '.webm', '.wav')
966
+ framework_extensions = ('.js', '.css', '.json', '.txt', '.xml', '.map', '.rels', '.md')
930
967
 
931
968
  is_media_asset = path.lower().endswith(media_extensions)
932
969
  is_framework_asset = any(path.lower().endswith(ext) for ext in framework_extensions)
@@ -937,7 +974,13 @@ def main():
937
974
  if not args.tidy and r.status_code == 405:
938
975
  statag = ' [405]'
939
976
  is_transport = any(p in path.lower() for p in ["socket.io", "engine.io", "/rpc", "/webhook", "/graphql"])
940
- if (is_shell or is_home_redirect) and not is_transport:
977
+ if (is_shell or is_home_redirect) and (is_framework_asset or is_media_asset or "." in path) and not is_transport:
978
+ if not args.tidy:
979
+ results_dead.append(f"404 Not Found (React Shell): {path}{statag}")
980
+ else:
981
+ results_dead.append(f"404 Not Found: {path}{statag}")
982
+ continue
983
+ if (is_shell or is_home_redirect) and not is_transport and not is_framework_asset and not is_media_asset:
941
984
  if path in discovered_in_js:
942
985
  if not args.tidy:
943
986
  results_200.append(f"{display_path}{statag} [Client-Side Route, Requires Login]")
@@ -960,7 +1003,7 @@ def main():
960
1003
  results_services.append(f"{display_path}{statag}")
961
1004
 
962
1005
  else:
963
- if "text/html" in content_type:
1006
+ if "text/html" in content_type and not ((is_shell or is_home_redirect) and (is_framework_asset or "." in path)):
964
1007
  if not args.tidy:
965
1008
  results_200.append(f"{display_path}{statag} [Access no matter what]")
966
1009
  else:
@@ -968,7 +1011,13 @@ def main():
968
1011
  elif is_media_asset:
969
1012
  results_assets.append(f"{display_path}{statag}")
970
1013
  elif is_framework_asset:
971
- results_frameworks.append(f"{display_path}{statag}")
1014
+ if is_shell or is_home_redirect:
1015
+ if not args.tidy:
1016
+ results_dead.append(f"404 Not Found (React Shell Fake File): {path}{statag}")
1017
+ else:
1018
+ results_dead.append(f"404 Not Found: {path}{statag}")
1019
+ else:
1020
+ results_frameworks.append(f"{display_path}{statag}")
972
1021
  else:
973
1022
  if not args.tidy:
974
1023
  results_frameworks.append(f"{display_path}{statag} [Non-Standard File]")
@@ -1015,6 +1064,7 @@ def main():
1015
1064
  start_test_time = time.perf_counter()
1016
1065
  args.scan_timeout = 5.0 #5min more
1017
1066
  else:
1067
+ #UNSORTED_PATHS IS FOR RAW OUTPUT FLAG. FOR UNSORTED AFTER SCAN TIMEOUT IT IS THE 'UNSORTED' LIST.
1018
1068
  for path in sorted(found_paths):
1019
1069
  display_path = discovered_in_js.get(path, path)
1020
1070
  pure_path = display_path.split(" [Original:")[0]
@@ -1085,17 +1135,23 @@ def main():
1085
1135
  print("WARNING: There should never be no inaccessble paths on a website.\nThis is most likely a false positive a fault on the script's end.\nReport this to the owner of the script immediately, whether it has found endpoints or not, and what site the script has been tested on.")
1086
1136
  if unsorted:
1087
1137
  print("\n----UNSORTED (Scan timed out)----")
1088
- for p in unsorted_paths: print(f" {p}")
1138
+ for p in unsorted: print(f" {p}")
1089
1139
  else:
1090
1140
  if args.scan_timeout:
1091
1141
  if not args.only_res:
1092
1142
  print("\nAll paths sorted out.")
1093
1143
 
1094
1144
  print(f"\n--- Scan Summary ---")
1095
- if args.show_assets:
1096
- print(f"Total Accessible Pages: {len(results_200)}\nTotal Services: {len(results_services)}\nTotal External References: {len(results_ext)}\nTotal Source Code/Files: {len(results_frameworks)}\nTotal Redirects: {len(results_30x)}\nTotal Assets: {len(results_assets)}\nTotal Inaccessible: {len(results_dead)}")
1097
- else:
1098
- print(f"Total Accessible Pages: {len(results_200)}\nTotal Services: {len(results_services)}\nTotal External References: {len(results_ext)}\nTotal Source Code/Files: {len(results_frameworks)}\nTotal Redirects: {len(results_30x)}\nTotal Assets: {len(results_assets)} (Hidden, use --show-assets to show)\nTotal Inaccessible: {len(results_dead)}")
1145
+ summary_report = (
1146
+ f"Total Accessible Pages: {len(results_200)}\n"
1147
+ f"Total Services: {len(results_services)}\n"
1148
+ f"Total External References: {len(results_ext)}\n"
1149
+ f"Total Source Code/Files: {len(results_frameworks)}\n"
1150
+ f"Total Redirects: {len(results_30x)}\n"
1151
+ f"Total Assets: {len(results_assets)}{assets_suffix}\n"
1152
+ f"Total Inaccessible: {len(results_dead)}{dead_suffix}"
1153
+ )
1154
+ print(summary_report)
1099
1155
  if unsorted:
1100
1156
  print(f"Total Unsorted: {len(unsorted)}")
1101
1157
  if args.show_source:
@@ -1168,12 +1224,7 @@ def main():
1168
1224
  f.write("All paths sorted out.\n")
1169
1225
 
1170
1226
  f.write(f"\n--- Scan Summary ---\n")
1171
- f.write(f"Total Accessible Pages: {len(results_200)}\n")
1172
- f.write(f"Total Services: {len(results_services)}\n")
1173
- f.write(f"Total External References: {len(results_ext)}\n")
1174
- f.write(f"Total Source Code/Files: {len(results_frameworks)}\n")
1175
- f.write(f"Total Redirects: {len(results_30x)}\n")
1176
- f.write(f"Total Inaccessible: {len(results_dead)}\n")
1227
+ f.write(summary_report)
1177
1228
  if unsorted:
1178
1229
  f.write(f"Total Unsorted: {len(unsorted)}")
1179
1230
  if args.show_source:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "endpointscanner"
7
- version = "7.2.3"
7
+ version = "7.2.4"
8
8
  readme = "README.md"
9
9
  description = "Website endpoint reconnaissance tool and rate limit tester that can bypass simple captchas and WAFs."
10
10
  requires-python = ">=3.9"
@@ -33,7 +33,8 @@ dependencies = [
33
33
  "beautifulsoup4",
34
34
  "playwright",
35
35
  "playwright-stealth",
36
- "httpx[http2]>=0.27.0"
36
+ "httpx[http2]>=0.27.0",
37
+ "colorama>=0.4.6"
37
38
  ]
38
39
 
39
40
  [project.scripts]
File without changes