Skip to content

Commit 9585609

Browse files
Merge pull request #2310 from blacklanternsecurity/crt-postgres
CRT.sh - Use direct Postgres db connection
2 parents 712929d + 23095b9 commit 9585609

File tree

2 files changed

+62
-26
lines changed

2 files changed

+62
-26
lines changed

bbot/modules/crt.py

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import time
2+
import asyncpg
3+
14
from bbot.modules.templates.subdomain_enum import subdomain_enum
25

36

@@ -11,30 +14,53 @@ class crt(subdomain_enum):
1114
"author": "@TheTechromancer",
1215
}
1316

14-
base_url = "https://crt.sh"
17+
deps_pip = ["asyncpg"]
18+
19+
db_host = "crt.sh"
20+
db_port = 5432
21+
db_user = "guest"
22+
db_name = "certwatch"
1523
reject_wildcards = False
1624

1725
async def setup(self):
18-
self.cert_ids = set()
26+
self.db_conn = None
1927
return await super().setup()
2028

2129
async def request_url(self, query):
22-
params = {"q": f"%.{query}", "output": "json"}
23-
url = self.helpers.add_get_params(self.base_url, params).geturl()
24-
return await self.api_request(url, timeout=self.http_timeout + 30)
25-
26-
async def parse_results(self, r, query):
27-
results = set()
28-
j = r.json()
29-
for cert_info in j:
30-
if not type(cert_info) == dict:
31-
continue
32-
cert_id = cert_info.get("id")
33-
if cert_id:
34-
if hash(cert_id) not in self.cert_ids:
35-
self.cert_ids.add(hash(cert_id))
36-
domain = cert_info.get("name_value")
37-
if domain:
38-
for d in domain.splitlines():
39-
results.add(d.lower())
30+
if not self.db_conn:
31+
self.db_conn = await asyncpg.connect(
32+
host=self.db_host, port=self.db_port, user=self.db_user, database=self.db_name
33+
)
34+
35+
sql = """
36+
WITH ci AS (
37+
SELECT array_agg(DISTINCT sub.NAME_VALUE) NAME_VALUES
38+
FROM (
39+
SELECT DISTINCT cai.CERTIFICATE, cai.NAME_VALUE
40+
FROM certificate_and_identities cai
41+
WHERE plainto_tsquery('certwatch', $1) @@ identities(cai.CERTIFICATE)
42+
AND cai.NAME_VALUE ILIKE ('%.' || $1)
43+
LIMIT 50000
44+
) sub
45+
GROUP BY sub.CERTIFICATE
46+
)
47+
SELECT DISTINCT unnest(NAME_VALUES) as name_value FROM ci;
48+
"""
49+
start = time.time()
50+
results = await self.db_conn.fetch(sql, query)
51+
end = time.time()
52+
self.verbose(f"SQL query executed in: {end - start} seconds with {len(results):,} results")
4053
return results
54+
55+
async def parse_results(self, results, query):
56+
domains = set()
57+
for row in results:
58+
domain = row["name_value"]
59+
if domain:
60+
for d in domain.splitlines():
61+
domains.add(d.lower())
62+
return domains
63+
64+
async def cleanup(self):
65+
if self.db_conn:
66+
await self.db_conn.close()

bbot/test/test_step_2/module_tests/test_module_crt.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,22 @@
33

44
class TestCRT(ModuleTestBase):
55
async def setup_after_prep(self, module_test):
6-
module_test.module.abort_if = lambda e: False
7-
for t in self.targets:
8-
module_test.httpx_mock.add_response(
9-
url="https://crt.sh?q=%25.blacklanternsecurity.com&output=json",
10-
json=[{"id": 1, "name_value": "asdf.blacklanternsecurity.com\nzzzz.blacklanternsecurity.com"}],
11-
)
6+
class AsyncMock:
7+
async def fetch(self, *args, **kwargs):
8+
print("mock_fetch", args, kwargs)
9+
return [
10+
{"name_value": "asdf.blacklanternsecurity.com"},
11+
{"name_value": "zzzz.blacklanternsecurity.com"},
12+
]
13+
14+
async def close(self):
15+
pass
16+
17+
async def mock_connect(*args, **kwargs):
18+
print("mock_connect", args, kwargs)
19+
return AsyncMock()
20+
21+
module_test.monkeypatch.setattr("asyncpg.connect", mock_connect)
1222

1323
def check(self, module_test, events):
1424
assert any(e.data == "asdf.blacklanternsecurity.com" for e in events), "Failed to detect subdomain"

0 commit comments

Comments
 (0)