generated from Ostorlab/template_agent
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
ostorlab
committed
Oct 27, 2023
1 parent
7930532
commit cf5c359
Showing
12 changed files
with
681 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,14 @@ | ||
FROM python:3.10-alpine as base | ||
FROM base as builder | ||
RUN apk add build-base | ||
RUN mkdir /install | ||
WORKDIR /install | ||
FROM kalilinux/kali-rolling:latest | ||
RUN apt-get update && apt-get install -y python3 \ | ||
python3-pip \ | ||
metasploit-framework | ||
COPY requirement.txt /requirement.txt | ||
RUN pip install --prefix=/install -r /requirement.txt | ||
FROM base | ||
COPY --from=builder /install /usr/local | ||
RUN python3 -m pip install -r /requirement.txt | ||
COPY tools /tools | ||
RUN pip install -e /tools/pymetasploit3 | ||
RUN mkdir -p /app/agent | ||
ENV PYTHONPATH=/app | ||
COPY agent /app/agent | ||
COPY ostorlab.yaml /app/agent/ostorlab.yaml | ||
WORKDIR /app | ||
CMD ["python3", "/app/agent/template_agent.py"] | ||
CMD ["python3", "/app/agent/metasploit_agent.py"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,80 @@ | ||
# Ostorlab Template Agent | ||
<h1 align="center">Agent Metasploit</h1> | ||
|
||
This repo is a template to build an Ostorlab agent in Python. It ships with good best practices like: | ||
<p align="center"> | ||
<img src="https://img.shields.io/badge/License-Apache_2.0-brightgreen.svg"> | ||
<img src="https://img.shields.io/github/languages/top/ostorlab/agent_metasploit"> | ||
<img src="https://img.shields.io/github/stars/ostorlab/agent_metasploit"> | ||
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"> | ||
</p> | ||
|
||
* Github actions workflow | ||
* Linting checks | ||
* Static typing checks with Mypy | ||
* Running unit test with Pytest | ||
* Compute test coverage | ||
_Metasploit is a powerful penetration testing framework._ | ||
|
||
--- | ||
|
||
<p align="center"> | ||
<img src="https://github.com/Ostorlab/agent_metasploit/blob/main/images/logo.png" alt="agent-metasploit" /> | ||
</p> | ||
|
||
This repository is an implementation of [Ostorlab Agent](https://pypi.org/project/ostorlab/) for the [Metasploit Framework](https://github.com/rapid7/metasploit-framework) by Rapid7. | ||
|
||
## Getting Started | ||
To perform your first scan, simply run the following command: | ||
```shell | ||
ostorlab scan run --install --agent agent/ostorlab/metasploit ip 8.8.8.8 | ||
``` | ||
|
||
This command will download and install `agent/ostorlab/metasploit` and target the ip `8.8.8.8`. | ||
For more information, please refer to the [Ostorlab Documentation](https://github.com/Ostorlab/ostorlab/blob/main/README.md) | ||
|
||
|
||
## Usage | ||
|
||
Agent Metasploit can be installed directly from the ostorlab agent store or built from this repository. | ||
|
||
### Install directly from ostorlab agent store | ||
|
||
```shell | ||
ostorlab agent install agent/ostorlab/metasploit | ||
``` | ||
|
||
You can then run the agent with the following command: | ||
```shell | ||
ostorlab scan run --agent agent/ostorlab/metasploit ip 8.8.8.8 | ||
``` | ||
|
||
|
||
### Build directly from the repository | ||
|
||
1. To build the metasploit agent you need to have [ostorlab](https://pypi.org/project/ostorlab/) installed in your machine. if you have already installed ostorlab, you can skip this step. | ||
|
||
```shell | ||
pip3 install ostorlab | ||
``` | ||
|
||
2. Clone this repository. | ||
|
||
```shell | ||
git clone https://github.com/Ostorlab/agent_metasploit.git && cd agent_metasploit | ||
``` | ||
|
||
3. Build the agent image using ostorlab cli. | ||
|
||
```shell | ||
ostorlab agent build --file=ostorlab.yaml | ||
``` | ||
|
||
You can pass the optional flag `--organization` to specify your organisation. The organization is empty by default. | ||
|
||
4. Run the agent using on of the following commands: | ||
* If you did not specify an organization when building the image: | ||
```shell | ||
ostorlab scan run --agent agent//metasploit ip 8.8.8.8 | ||
``` | ||
* If you specified an organization when building the image: | ||
```shell | ||
ostorlab scan run --agent agent/[ORGANIZATION]/metasploit ip 8.8.8.8 | ||
``` | ||
|
||
|
||
Here are links to good resources to get started: | ||
|
||
* [Write An Agent](https://docs.ostorlab.co/tutorials/write-an-ostorlab-agent.html) | ||
* [Use Ostorlab](https://docs.ostorlab.co/tutorials/run-your-first-scan.html) | ||
* [Debugging and Testing Agents](https://docs.ostorlab.co/tutorials/debugging-agents.html) | ||
* [Ostorlab Internals](https://docs.ostorlab.co/tutorials/life-of-a-scan.html) | ||
|
||
## Ideas for Agents to build | ||
|
||
Implementation of popular tools like: | ||
|
||
* [semgrep](https://github.com/returntocorp/semgrep) for source code scanning. | ||
* [nbtscan](http://www.unixwiz.net/tools/nbtscan.html): Scans for open NETBIOS nameservers on your target’s network. | ||
* [onesixtyone](https://github.com/trailofbits/onesixtyone): Fast scanner to find publicly exposed SNMP services. | ||
* [Retire.js](http://retirejs.github.io/retire.js/): Scanner detecting the use of JavaScript libraries with known | ||
vulnerabilities. | ||
* [snallygaster](https://github.com/hannob/snallygaster): Finds file leaks and other security problems on HTTP servers. | ||
* [testssl.sh](https://testssl.sh/): Identify various TLS/SSL weaknesses, including Heartbleed, CRIME and ROBOT. | ||
* [TruffleHog](https://github.com/trufflesecurity/truffleHog): Searches through git repositories for high entropy | ||
strings and secrets, digging deep into commit history. | ||
* [cve-bin-tool](https://github.com/intel/cve-bin-tool): Scan binaries for vulnerable components. | ||
* [XSStrike](https://github.com/s0md3v/XSStrike): XSS web vulnerability scanner with generative payload. | ||
* ~~[Subjack](https://github.com/haccer/subjack): Subdomain takeover scanning tool.~~ | ||
* [DnsReaper](https://github.com/punk-security/dnsReaper): Subdomain takeover scanning tool. | ||
## License | ||
[Apache](./LICENSE) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
"""Ostorlab Agent implementation for metasploit""" | ||
import logging | ||
import socket | ||
import time | ||
from urllib import parse as urlparser | ||
|
||
from ostorlab.agent import agent, definitions as agent_definitions | ||
from ostorlab.agent.kb import kb | ||
from ostorlab.agent.message import message as m | ||
from ostorlab.agent.mixins import agent_persist_mixin as persist_mixin | ||
from ostorlab.agent.mixins import agent_report_vulnerability_mixin as vuln_mixin | ||
from ostorlab.runtimes import definitions as runtime_definitions | ||
from rich import logging as rich_logging | ||
|
||
from agent import utils | ||
from pymetasploit3 import msfrpc | ||
|
||
logging.basicConfig( | ||
format="%(message)s", | ||
datefmt="[%X]", | ||
level="INFO", | ||
force=True, | ||
handlers=[rich_logging.RichHandler(rich_tracebacks=True)], | ||
) | ||
logger = logging.getLogger(__name__) | ||
|
||
AGENT_ARGS = ["module", "RHOSTS", "VHOST", "RPORT"] | ||
SCHEME_TO_PORT = {"http": 80, "https": 443} | ||
DEFAULT_PORT = 443 | ||
MODULE_TIMEOUT = 180 | ||
|
||
|
||
class Error(Exception): | ||
"""Base custom error class.""" | ||
|
||
|
||
class ArgumentError(Error): | ||
"""Error when a required argument is missing""" | ||
|
||
|
||
class ModuleError(Error): | ||
"""Errors related to metasploit modules""" | ||
|
||
|
||
class CheckError(Error): | ||
"""Errors related to metasploit check method""" | ||
|
||
|
||
class MetasploitAgent( | ||
agent.Agent, vuln_mixin.AgentReportVulnMixin, persist_mixin.AgentPersistMixin | ||
): | ||
"""Source map agent.""" | ||
|
||
def __init__( | ||
self, | ||
agent_definition: agent_definitions.AgentDefinition, | ||
agent_settings: runtime_definitions.AgentSettings, | ||
) -> None: | ||
agent.Agent.__init__(self, agent_definition, agent_settings) | ||
vuln_mixin.AgentReportVulnMixin.__init__(self) | ||
persist_mixin.AgentPersistMixin.__init__(self, agent_settings) | ||
self.client = utils.initialize_msf_rpc() | ||
self.cid = self.client.consoles.console().cid | ||
|
||
def process(self, message: m.Message) -> None: | ||
"""Trigger Source map enumeration and emit found findings | ||
Args: | ||
message: A message containing the path and the content of the file to be processed | ||
""" | ||
module = self.args.get("module") | ||
if module is None: | ||
raise ArgumentError("Metasploit module must be specified.") | ||
|
||
vhost, rport = self._prepare_target(message) | ||
|
||
module_type, module_name = module.split("/", 1) | ||
try: | ||
selected_module = self.client.modules.use(module_type, module_name) | ||
except msfrpc.MsfRpcError as exc: | ||
raise ModuleError("Specified module does not exist") from exc | ||
|
||
logger.info("Selected metasploit module: %s", selected_module.modulename) | ||
selected_module = self._set_module_args(selected_module, vhost, rport) | ||
|
||
if module_type == "exploit": | ||
mode = "check" | ||
job = selected_module.check_exploit() | ||
elif module_type == "auxiliary": | ||
mode = "exploit" | ||
job = selected_module.execute() | ||
else: | ||
raise ArgumentError("Metasploit module should be exploit or auxiliary.") | ||
|
||
job_uuid = job["uuid"] | ||
started_timestamp = time.time() | ||
results = None | ||
while True: | ||
job_result = self.client.jobs.info_by_uuid(job_uuid) | ||
status = job_result["status"] | ||
if status == "completed": | ||
results = job_result["result"] | ||
break | ||
if status == "errored": | ||
logger.error("Encountered an unexpected error: %s", job_result["error"]) | ||
break | ||
if time.time() - started_timestamp > MODULE_TIMEOUT: | ||
raise CheckError(f"Timeout while running job: {job_uuid}") | ||
time.sleep(5) | ||
|
||
if isinstance(results, dict) and results.get("code") == "safe": | ||
return | ||
|
||
technical_detail = f"Using `{module_type}` module `{module_name}`\n" | ||
technical_detail += f"Target: {vhost}\n" | ||
|
||
if isinstance(results, dict) and results.get("code") == "vulnerable": | ||
technical_detail += f'Message: {results["message"]}' | ||
else: | ||
console_output = self.client.consoles.console( | ||
self.cid | ||
).run_module_with_output(selected_module, mode=mode) | ||
module_output = console_output.split("WORKSPACE => Ostorlab")[1] | ||
if "[-]" in module_output: | ||
return | ||
technical_detail += f"Message: {module_output}" | ||
|
||
entry = kb.KB.WEB_GENERIC | ||
entry.title = "Metasploit vulnerability detection" | ||
self.report_vulnerability( | ||
entry=entry, | ||
technical_detail=technical_detail, | ||
risk_rating=vuln_mixin.RiskRating.HIGH, | ||
) | ||
|
||
def _set_module_args( | ||
self, selected_module: msfrpc.MsfModule, vhost: str, rport: int | ||
) -> msfrpc.MsfModule: | ||
rhost = socket.gethostbyname(vhost) | ||
if "RHOSTS" not in selected_module.required: | ||
raise ArgumentError( | ||
f"Argument not implemented, accepted args: {str(selected_module.required)}" | ||
) | ||
selected_module["RHOSTS"] = rhost | ||
if "VHOST" in selected_module.options: | ||
selected_module["VHOST"] = vhost | ||
if "RPORT" in selected_module.missing_required: | ||
selected_module["RPORT"] = rport | ||
|
||
extra_args = [arg_name for arg_name in self.args if arg_name not in AGENT_ARGS] | ||
for arg in extra_args: | ||
if arg in selected_module.required: | ||
selected_module[arg] = self.args.get(arg) | ||
|
||
if len(selected_module.missing_required) > 0: | ||
raise ArgumentError( | ||
f"The following arguments are missing: {str(selected_module.missing_required)}" | ||
) | ||
|
||
return selected_module | ||
|
||
def _get_port(self, message: m.Message) -> int: | ||
"""Returns the port to be used for the target.""" | ||
if message.data.get("port") is not None: | ||
return int(message.data["port"]) | ||
elif self.args.get("port") is not None: | ||
return int(str(self.args.get("port"))) | ||
else: | ||
return DEFAULT_PORT | ||
|
||
def _prepare_target(self, message: m.Message) -> tuple[str, int]: | ||
"""Prepare targets based on type, if a domain name is provided, port and protocol are collected | ||
from the config.""" | ||
if (host := message.data.get("host")) is not None: | ||
port = self._get_port(message) | ||
return host, port | ||
elif (host := message.data.get("name")) is not None: | ||
port = self._get_port(message) | ||
return host, port | ||
elif (url := message.data.get("url")) is not None: | ||
parsed_url = urlparser.urlparse(url) | ||
host = parsed_url.netloc | ||
scheme = parsed_url.scheme | ||
port = SCHEME_TO_PORT.get(scheme) or DEFAULT_PORT | ||
return host, port | ||
else: | ||
raise NotImplementedError | ||
|
||
|
||
if __name__ == "__main__": | ||
logger.info("Starting Agent ...") | ||
MetasploitAgent.main() |
Oops, something went wrong.