-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Agent Metasploit #4
Changes from 18 commits
cf5c359
dcc3115
78e6de2
0acffe2
4579ef2
a6f7ae9
bdc682e
3af5017
60af5c7
2744ea0
e7713c3
71a8147
3f3fd2a
a3e5da2
41b22b6
faecf71
38d608f
2a8d5b3
9803d50
ac27319
c88681a
c808103
a01fd99
d626727
3b60060
65f4663
a5354cf
55c697f
3424626
a3f5ff7
f6c0c71
4ea7a3d
776a294
5f3868c
6c279f4
6314f00
1374c1d
70faa35
1909396
0933231
a9df478
365b3a1
5f46418
fa2ecc4
0c8191f
7e84eb4
03305ac
e142622
47cc4d8
2be6437
3e245c2
f7e0c67
c594ec9
fcae758
d8943c2
0e7249c
f8c10e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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"] |
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"> | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
"""Ostorlab Agent implementation for metasploit""" | ||
import json | ||
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__) | ||
|
||
SCHEME_TO_PORT = {"http": 80, "https": 443} | ||
DEFAULT_PORT = 443 | ||
MODULE_TIMEOUT = 180 | ||
|
||
|
||
class Error(Exception): | ||
"""Base custom error class.""" | ||
|
||
|
||
class ArgumentError(Error): | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Error when a required argument is missing""" | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class ModuleError(Error): | ||
"""Errors related to metasploit modules""" | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class CheckError(Error): | ||
"""Errors related to metasploit check method""" | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class MetasploitAgent( | ||
agent.Agent, vuln_mixin.AgentReportVulnMixin, persist_mixin.AgentPersistMixin | ||
): | ||
"""Metasploit 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. client is the msfrpc instance |
||
|
||
def process(self, message: m.Message) -> None: | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Trigger Agent metasploit and emit 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.") | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
vhost, rport = self._prepare_target(message) | ||
|
||
try: | ||
module_type, module_name = module.split("/", 1) | ||
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.") | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
job_uuid = job["uuid"] | ||
started_timestamp = time.time() | ||
results = None | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not the proper way to timeout. This is supppppppppper CPU intensive. |
||
|
||
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: | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return | ||
technical_detail += f"Message: {module_output}" | ||
|
||
entry = kb.KB.WEB_GENERIC | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
entry.title = selected_module.name or "Metasploit generic vulnerability entry" | ||
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 | ||
|
||
msf_options = json.loads(self.args.get("options") or "[]") | ||
for arg in msf_options: | ||
arg_name = arg["name"] | ||
if arg_name in selected_module.options: | ||
selected_module[arg_name] = arg["value"] | ||
|
||
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]: | ||
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
BlueSquare1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's raising errors because of
pymetasploit3
local moduleThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How hard would it be to add typing there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it has much value right now given the 90+ pending exploits we have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BlueSquare1 you can ignore
pymetasploit3
without ignoring everything.