Skip to content

Commit 610c645

Browse files
authored
dev_4.5
1 parent 7ee72a8 commit 610c645

1 file changed

Lines changed: 60 additions & 17 deletions

File tree

dorkeye.py

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,20 @@
5555

5656

5757
def _sigint_handler(signum, frame):
58+
# NOTE: sys.stderr.write() is async-signal-safe; console.print() is NOT
59+
# (Rich holds a threading.Lock internally — calling it from a signal handler
60+
# while the main thread already holds that lock causes a deadlock, which is
61+
# more likely on Android/Termux due to its scheduler).
5862
global _last_interrupt_time, _skip_current, _exit_requested
5963
now = time.monotonic()
6064
if now - _last_interrupt_time < 1.5:
6165
_exit_requested = True
62-
console.print("\n[bold red][!!] Double Ctrl+C detected — exiting...[/bold red]")
66+
sys.stderr.write("\n[!!] Double Ctrl+C detected — exiting...\n")
67+
sys.stderr.flush()
6368
else:
6469
_skip_current = True
65-
console.print("\n[yellow][~] Ctrl+C — skipping current task...[/yellow]")
70+
sys.stderr.write("\n[~] Ctrl+C — skipping current task...\n")
71+
sys.stderr.flush()
6672
_last_interrupt_time = now
6773

6874

@@ -533,13 +539,15 @@ def _measure_baseline_latency(self, url: str) -> Optional[float]:
533539
for _ in range(_BASELINE_SAMPLES):
534540
if self.circuit_breaker.is_dead(url):
535541
return None
542+
if _exit_requested or _skip_current:
543+
return None
536544
t0 = time.monotonic()
537545
response = self._get(url)
538546
elapsed = time.monotonic() - t0
539547
if response is None:
540548
return None
541549
times.append(elapsed)
542-
time.sleep(0.3)
550+
_interruptible_sleep(0.3)
543551
baseline = sum(times) / len(times)
544552
if baseline > _MAX_BASELINE_S:
545553
return None
@@ -588,14 +596,16 @@ def _probe_parameter(self, url: str, param_name: str, baseline_content: str) ->
588596
baseline_status: Optional[int] = None
589597

590598
for i in range(_PROBE_SAMPLES):
599+
if _exit_requested or _skip_current:
600+
return False
591601
r = self._get(url)
592602
if r is None:
593603
return False
594604
if i == 0:
595605
baseline_status = r.status_code
596606
sim = difflib.SequenceMatcher(None, baseline_content, r.text).ratio()
597607
noise_samples.append(1.0 - sim)
598-
time.sleep(0.2)
608+
_interruptible_sleep(0.2)
599609

600610
if not noise_samples:
601611
return False
@@ -636,6 +646,8 @@ def _test_error_based(self, url: str, param_name: str) -> Dict:
636646
for payload in payloads:
637647
if self.circuit_breaker.is_dead(url):
638648
break
649+
if _exit_requested or _skip_current:
650+
break
639651
test_url = self._inject_payload(url, param_name, payload)
640652
response = self._get(test_url)
641653
if response is None:
@@ -652,7 +664,7 @@ def _test_error_based(self, url: str, param_name: str) -> Dict:
652664
return result
653665

654666
if self.stealth:
655-
time.sleep(random.uniform(1.5, 3))
667+
_interruptible_sleep(random.uniform(1.5, 3))
656668

657669
return result
658670

@@ -677,22 +689,26 @@ def _test_boolean_blind(self, url: str, param_name: str, baseline_len: int) -> D
677689
for payload, payload_type in bool_payloads:
678690
if self.circuit_breaker.is_dead(url):
679691
break
692+
if _exit_requested or _skip_current:
693+
break
680694

681695
test_url = self._inject_payload(url, param_name, payload)
682696
samples: List[int] = []
683697

684698
for _ in range(_BOOL_SAMPLES):
699+
if _exit_requested or _skip_current:
700+
break
685701
r = self._get(test_url)
686702
if r is not None:
687703
samples.append(len(r.text))
688704
if self.stealth:
689-
time.sleep(random.uniform(0.5, 1))
705+
_interruptible_sleep(random.uniform(0.5, 1))
690706

691707
if len(samples) >= 2:
692708
(true_groups if payload_type == "true" else false_groups).append(samples)
693709

694710
if self.stealth:
695-
time.sleep(random.uniform(1, 2))
711+
_interruptible_sleep(random.uniform(1, 2))
696712

697713
if not true_groups or not false_groups:
698714
return result
@@ -771,6 +787,8 @@ def _test_time_based_blind(self, url: str, param_name: str) -> Dict:
771787
for _ in range(_TIMEBASED_CONFIRM):
772788
if self.circuit_breaker.is_dead(url):
773789
break
790+
if _exit_requested or _skip_current:
791+
break
774792
t_c = time.monotonic()
775793
r_c = self._get(neutral_url)
776794
elapsed_c = time.monotonic() - t_c
@@ -780,7 +798,7 @@ def _test_time_based_blind(self, url: str, param_name: str) -> Dict:
780798
else:
781799
confirm_times.append(elapsed_c)
782800

783-
time.sleep(0.5)
801+
_interruptible_sleep(0.5)
784802

785803
if confirm_failures > 0:
786804
continue
@@ -841,6 +859,8 @@ def test_sqli(self, url: str) -> Dict:
841859
if self.circuit_breaker.is_dead(url):
842860
result["message"] = "Host became unreachable during testing"
843861
break
862+
if _exit_requested or _skip_current:
863+
break
844864

845865
if not self._probe_parameter(url, param_name, baseline_content):
846866
continue
@@ -870,7 +890,7 @@ def test_sqli(self, url: str) -> Dict:
870890
)
871891

872892
if self.stealth:
873-
time.sleep(random.uniform(2, 4))
893+
_interruptible_sleep(random.uniform(2, 4))
874894

875895
if confidence_scores:
876896
avg = sum(confidence_scores) / len(confidence_scores)
@@ -905,6 +925,8 @@ def test_post_sqli(self, url: str, post_data: Dict[str, str]) -> Dict:
905925
for param_name in post_data.keys():
906926
if self.circuit_breaker.is_dead(url):
907927
break
928+
if _exit_requested or _skip_current:
929+
break
908930

909931
payload_dict = post_data.copy()
910932
payload_dict[param_name] = str(post_data[param_name]) + "'"
@@ -938,7 +960,7 @@ def test_post_sqli(self, url: str, post_data: Dict[str, str]) -> Dict:
938960
pass
939961

940962
if self.stealth:
941-
time.sleep(random.uniform(2, 4))
963+
_interruptible_sleep(random.uniform(2, 4))
942964

943965
if confidence_scores:
944966
avg = sum(confidence_scores) / len(confidence_scores)
@@ -966,6 +988,8 @@ def test_json_sqli(self, url: str, json_data: Dict[str, str]) -> Dict:
966988
for key in json_data.keys():
967989
if self.circuit_breaker.is_dead(url):
968990
break
991+
if _exit_requested or _skip_current:
992+
break
969993

970994
payload_dict = json_data.copy()
971995
payload_dict[key] = payload_dict[key] + "'"
@@ -997,7 +1021,7 @@ def test_json_sqli(self, url: str, json_data: Dict[str, str]) -> Dict:
9971021
pass
9981022

9991023
if self.stealth:
1000-
time.sleep(random.uniform(2, 4))
1024+
_interruptible_sleep(random.uniform(2, 4))
10011025

10021026
if confidence_scores:
10031027
avg = sum(confidence_scores) / len(confidence_scores)
@@ -1177,7 +1201,10 @@ def __init__(self, config: Dict, output_file: str = None):
11771201
self._total_results_at_last_extended_delay: int = 0
11781202

11791203
def _hash_url(self, url: str) -> str:
1180-
return hashlib.md5(url.encode()).hexdigest()
1204+
# usedforsecurity=False: required on Python 3.9+ when OpenSSL runs in
1205+
# FIPS mode (some custom Android ROMs enforce this) — md5 is not used
1206+
# for any cryptographic purpose here, only as a fast dedup key.
1207+
return hashlib.md5(url.encode(), usedforsecurity=False).hexdigest()
11811208

11821209
def is_duplicate(self, url: str) -> bool:
11831210
h = self._hash_url(url)
@@ -1263,22 +1290,31 @@ def search_dork(self, dork: str, count: int,
12631290
break
12641291

12651292
# Start producer
1293+
# NOTE: stop_event allows the consumer to signal the producer
1294+
# to abort early (skip/exit/count-reached). Without this, on a
1295+
# retry the previous producer thread would stay alive pushing
1296+
# items into an orphaned queue — wasting RAM/CPU on Android.
12661297
result_queue: queue.Queue = queue.Queue()
1298+
stop_event = threading.Event()
12671299

1268-
def _producer(dork=dork, batch_size=batch_size):
1300+
def _producer(dork=dork, batch_size=batch_size,
1301+
q=result_queue, stop=stop_event):
12691302
try:
12701303
for item in DDGS().text(dork, max_results=batch_size):
1271-
result_queue.put(item)
1304+
if stop.is_set():
1305+
break
1306+
q.put(item)
12721307
except Exception:
12731308
pass
12741309
finally:
1275-
result_queue.put(_DONE)
1310+
q.put(_DONE)
12761311

12771312
threading.Thread(target=_producer, daemon=True).start()
12781313

12791314
# Consume; poll every 0.25 s so Ctrl+C is always responsive
12801315
while True:
12811316
if _exit_requested or _skip_current:
1317+
stop_event.set() # signal producer to abort
12821318
break
12831319
try:
12841320
r = result_queue.get(timeout=0.25)
@@ -1315,6 +1351,7 @@ def _producer(dork=dork, batch_size=batch_size):
13151351
self.stats[f"category_{entry['category']}"] += 1
13161352
progress.update(task, completed=min(total_fetched, count))
13171353
if total_fetched >= count:
1354+
stop_event.set() # producer no longer needed
13181355
break
13191356

13201357
if total_fetched >= count:
@@ -1370,7 +1407,13 @@ def analyze_results(self, results: List[Dict]) -> List[Dict]:
13701407
"status_code": analysis["status_code"],
13711408
})
13721409
progress.advance(task1)
1373-
time.sleep(random.uniform(1, 2) if self.config.get("stealth_mode", False) else 0.5)
1410+
_interruptible_sleep(random.uniform(1, 2) if self.config.get("stealth_mode", False) else 0.5)
1411+
1412+
# Reset _skip_current dopo il loop file analysis per evitare che
1413+
# propaghi nel blocco SQLi con un messaggio fuorviante
1414+
if _skip_current and not _exit_requested:
1415+
console.print("[yellow][~] Ctrl+C — file analysis skipped.[/yellow]")
1416+
_skip_current = False
13741417

13751418
if self.config.get("sqli_detection", False) and urls_to_test_sqli:
13761419
task2 = progress.add_task("[cyan]Testing for [red]SQLi[cyan]...", total=len(urls_to_test_sqli))
@@ -1392,7 +1435,7 @@ def analyze_results(self, results: List[Dict]) -> List[Dict]:
13921435
)
13931436
progress.advance(task2)
13941437
if self.config.get("stealth_mode", False):
1395-
time.sleep(random.uniform(3, 6))
1438+
_interruptible_sleep(random.uniform(3, 6))
13961439
return results
13971440

13981441
def run_search(self, dorks: List[str], count: int):

0 commit comments

Comments
 (0)