Skip to content

Commit

Permalink
Merge pull request #1957 from blacklanternsecurity/better-api-key-rot…
Browse files Browse the repository at this point in the history
…ation

Support api key rotation for censys and passivetotal
  • Loading branch information
TheTechromancer authored Nov 18, 2024
2 parents 83f8e10 + 0d8681d commit 45fc1f1
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 26 deletions.
22 changes: 9 additions & 13 deletions bbot/modules/censys.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,33 @@ class censys(subdomain_enum_apikey):
"author": "@TheTechromancer",
"auth_required": True,
}
options = {"api_id": "", "api_secret": "", "max_pages": 5}
options = {"api_key": "", "max_pages": 5}
options_desc = {
"api_id": "Censys.io API ID",
"api_secret": "Censys.io API Secret",
"api_key": "Censys.io API Key in the format of 'key:secret'",
"max_pages": "Maximum number of pages to fetch (100 results per page)",
}

base_url = "https://search.censys.io/api"

async def setup(self):
self.api_id = self.config.get("api_id", "")
self.api_secret = self.config.get("api_secret", "")
self.auth = (self.api_id, self.api_secret)
self.max_pages = self.config.get("max_pages", 5)
return await super().setup()

async def ping(self):
url = f"{self.base_url}/v1/account"
resp = await self.helpers.request(url, auth=self.auth)
resp = await self.api_request(url)
d = resp.json()
assert isinstance(d, dict), f"Invalid response from {url}: {resp}"
quota = d.get("quota", {})
used = int(quota.get("used", 0))
allowance = int(quota.get("allowance", 0))
assert used < allowance, "No quota remaining"

def prepare_api_request(self, url, kwargs):
api_id, api_secret = self.api_key.split(":", 1)
kwargs["auth"] = (api_id, api_secret)
return url, kwargs

async def query(self, query):
results = set()
cursor = ""
Expand All @@ -52,11 +53,10 @@ async def query(self, query):
}
if cursor:
json_data.update({"cursor": cursor})
resp = await self.helpers.request(
resp = await self.api_request(
url,
method="POST",
json=json_data,
auth=self.auth,
)

if resp is None:
Expand Down Expand Up @@ -96,7 +96,3 @@ async def query(self, query):
break

return results

@property
def auth_secret(self):
return self.api_id and self.api_secret
20 changes: 9 additions & 11 deletions bbot/modules/passivetotal.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,34 @@ class passivetotal(subdomain_enum_apikey):
"author": "@TheTechromancer",
"auth_required": True,
}
options = {"username": "", "api_key": ""}
options_desc = {"username": "RiskIQ Username", "api_key": "RiskIQ API Key"}
options = {"api_key": ""}
options_desc = {"api_key": "PassiveTotal API Key in the format of 'username:api_key'"}

base_url = "https://api.passivetotal.org/v2"

async def setup(self):
self.username = self.config.get("username", "")
self.api_key = self.config.get("api_key", "")
self.auth = (self.username, self.api_key)
return await super().setup()

async def ping(self):
url = f"{self.base_url}/account/quota"
j = (await self.api_request(url, auth=self.auth)).json()
j = (await self.api_request(url)).json()
limit = j["user"]["limits"]["search_api"]
used = j["user"]["counts"]["search_api"]
assert used < limit, "No quota remaining"

def prepare_api_request(self, url, kwargs):
api_username, api_key = self.api_key.split(":", 1)
kwargs["auth"] = (api_username, api_key)
return url, kwargs

async def abort_if(self, event):
# RiskIQ is famous for their junk data
return await super().abort_if(event) or "unresolved" in event.tags

async def request_url(self, query):
url = f"{self.base_url}/enrichment/subdomains?query={self.helpers.quote(query)}"
return await self.api_request(url, auth=self.auth)
return await self.api_request(url)

def parse_results(self, r, query):
for subdomain in r.json().get("subdomains", []):
yield f"{subdomain}.{query}"

@property
def auth_secret(self):
return self.username and self.api_key
5 changes: 4 additions & 1 deletion bbot/test/test_step_2/module_tests/test_module_censys.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@


class TestCensys(ModuleTestBase):
config_overrides = {"modules": {"censys": {"api_id": "api_id", "api_secret": "api_secret"}}}
config_overrides = {"modules": {"censys": {"api_key": "api_id:api_secret"}}}

async def setup_before_prep(self, module_test):
module_test.httpx_mock.add_response(
url="https://search.censys.io/api/v1/account",
match_headers={"Authorization": "Basic YXBpX2lkOmFwaV9zZWNyZXQ="},
json={
"email": "[email protected]",
"login": "nope",
Expand All @@ -17,6 +18,7 @@ async def setup_before_prep(self, module_test):
)
module_test.httpx_mock.add_response(
url="https://search.censys.io/api/v2/certificates/search",
match_headers={"Authorization": "Basic YXBpX2lkOmFwaV9zZWNyZXQ="},
match_content=b'{"q": "names: blacklanternsecurity.com", "per_page": 100}',
json={
"code": 200,
Expand Down Expand Up @@ -45,6 +47,7 @@ async def setup_before_prep(self, module_test):
)
module_test.httpx_mock.add_response(
url="https://search.censys.io/api/v2/certificates/search",
match_headers={"Authorization": "Basic YXBpX2lkOmFwaV9zZWNyZXQ="},
match_content=b'{"q": "names: blacklanternsecurity.com", "per_page": 100, "cursor": "NextToken"}',
json={
"code": 200,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@


class TestPassiveTotal(ModuleTestBase):
config_overrides = {"modules": {"passivetotal": {"username": "[email protected]", "api_key": "asdf"}}}
config_overrides = {"modules": {"passivetotal": {"api_key": "[email protected]:asdf"}}}

async def setup_before_prep(self, module_test):
module_test.httpx_mock.add_response(
url="https://api.passivetotal.org/v2/account/quota",
match_headers={"Authorization": "Basic am9uQGJscy5mYWtlZG9tYWluOmFzZGY="},
json={"user": {"counts": {"search_api": 10}, "limits": {"search_api": 20}}},
)
module_test.httpx_mock.add_response(
url="https://api.passivetotal.org/v2/enrichment/subdomains?query=blacklanternsecurity.com",
match_headers={"Authorization": "Basic am9uQGJscy5mYWtlZG9tYWluOmFzZGY="},
json={"subdomains": ["asdf"]},
)

Expand Down

0 comments on commit 45fc1f1

Please sign in to comment.