Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions injector_common/injector_common/data_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Dict


class DataHelpers:

@staticmethod
def get_injector_contract_id(data: Dict) -> str:
try:
return data["injection"]["inject_injector_contract"]["injector_contract_id"]
except KeyError as e:
raise ValueError("Invalid data: missing injector contract id") from e

@staticmethod
def get_content(data: Dict) -> Dict:
try:
return data["injection"]["inject_content"]
except KeyError as e:
raise ValueError("Invalid data: missing inject content") from e

@staticmethod
def get_inject_id(data: Dict) -> str:
try:
return data["injection"]["inject_id"]
except KeyError as e:
raise ValueError("Invalid data: missing inject id") from e
2 changes: 1 addition & 1 deletion injector_common/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name="injector_common"
version="1.0.0"

dependencies = [
"pyoaev==2.1.0",
"pyoaev==2.1.3",
]
[project.optional-dependencies]
dev = [
Expand Down
3 changes: 3 additions & 0 deletions netexec/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
config.yml
src/__pycache__
__pycache__
2 changes: 2 additions & 0 deletions netexec/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
config.yml
__pycache__
61 changes: 61 additions & 0 deletions netexec/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Builder
FROM python:3.13-alpine AS builder

# System dependencies required by netexec
RUN apk add --no-cache \
gcc \
musl-dev \
libffi-dev \
openssl-dev \
cargo

# Install Poetry
RUN pip install --no-cache-dir poetry==2.1.3

# Copy shared injector code
WORKDIR /opt/injector_common
COPY --from=injector_common ./ ./

# Copy injector source
ARG installdir=/opt/injector
ADD . ${installdir}
WORKDIR ${installdir}

RUN poetry build

# Runner
FROM python:3.13-alpine AS runner

# Runtime deps only
RUN apk add --no-cache \
gcc \
musl-dev \
python3-dev \
libffi-dev \
openssl-dev \
cargo \
git

WORKDIR /opt/injector_common
COPY --from=injector_common ./ ./

ARG installdir=/opt/injector
WORKDIR ${installdir}
COPY --from=builder ${installdir}/dist ./dist

RUN pip3 install --no-cache-dir "$(ls dist/*.whl)[prod]"

# Optional: override client-python version
ARG PYOAEV_GIT_BRANCH_OVERRIDE
RUN if [ -n "${PYOAEV_GIT_BRANCH_OVERRIDE}" ]; then \
echo "Forcing specific version of client-python" && \
pip install pip3-autoremove && \
pip-autoremove pyoaev -y && \
pip install git+https://github.com/OpenAEV-Platform/client-python@${PYOAEV_GIT_BRANCH_OVERRIDE} ; \
fi

# Install netexec
RUN pip install --no-cache-dir git+https://github.com/Pennyw0rth/NetExec.git

# Default command
CMD ["python3", "-m", "netexec.openaev_netexec"]
222 changes: 222 additions & 0 deletions netexec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# OpenAEV NetExec Injector

## Table of Contents

- [OpenAEV NetExec Injector](#openaev-netexec-injector)
- [Prerequisites](#prerequisites)
- [Configuration variables](#configuration-variables)
- [OpenAEV environment variables](#openaev-environment-variables)
- [Base injector environment variables](#base-injector-environment-variables)
- [Deployment](#deployment)
- [Docker Deployment](#docker-deployment)
- [Manual Deployment](#manual-deployment)
- [Behavior](#behavior)
- [Template Selection](#template-selection)
- [Target Selection](#target-selection)
- [Resources](#resources)

---

## Prerequisites

This injector uses [NetExex](https://www.netexec.wiki/smb-protocol/enumeration).

This injector communicates with the OpenAEV platform through **RabbitMQ**, using the configuration provided by OpenAEV.

To function properly, the injector **must be able to reach the RabbitMQ service** (hostname and port) defined in the
OpenAEV configuration.

## Configuration

Configuration values can be provided either:

* via `docker-compose.yml` (Docker deployment), or
* via `config.yml` (manual deployment).

### OpenAEV Environment Variables

The following parameters are required to connect the injector to the OpenAEV platform:

| Parameter | `config.yml` | Docker Variable | Mandatory | Description |
|---------------|--------------|-----------------|-----------|-----------------------------------------------------|
| OpenAEV URL | `url` | `OPENAEV_URL` | Yes | Base URL of the OpenAEV platform. |
| OpenAEV Token | `token` | `OPENAEV_TOKEN` | Yes | Admin API token configured in the OpenAEV platform. |

### Injector Environment Variables

The following parameters control the injector runtime behavior:

| Parameter | `config.yml` | Docker Variable | Default | Mandatory | Description |
|---------------|--------------|----------------------|---------|-----------|---------------------------------------------------------|
| Injector ID | `id` | `INJECTOR_ID` | — | Yes | Unique `UUIDv4` identifying this injector instance. |
| Injector Name | `name` | `INJECTOR_NAME` | — | Yes | Human-readable name of the injector. |
| Log Level | `log_level` | `INJECTOR_LOG_LEVEL` | `info` | Yes | Logging verbosity: `debug`, `info`, `warn`, or `error`. |

## Deployment

### Docker Deployment

Build the Docker image using the provided `Dockerfile`.

```shell
docker build --build-context injector_common=../injector_common . -t openaev/injector-netexec:latest
```

Then configure the environment variables in `docker-compose.yml` and start the injector:

```shell
docker compose up -d
```

> ✅ The Docker image **already contains NetExec**. No further installation is needed inside the container.

### Manual Deployment

1. Create a `config.yml` file based on `config.yml.sample`
2. Adjust the configuration values to match your environment

#### Prerequisites

* **NetExec must be installed locally** and accessible via the command line (`netexec` command).
* You can install it
from: [https://www.netexec.wiki/getting-started/installation](https://www.netexec.wiki/getting-started/installation)

* Python package manager **Poetry** (version 2.1 or later)
👉 [https://python-poetry.org/](https://python-poetry.org/)

#### Installation

**Production environment**

```shell
poetry install --extras prod
```

**Development environment**

For development, you should also clone the `pyoaev` repository following the instructions provided in the OpenAEV
documentation.

```shell
poetry install --extras dev
```

## Development

This project follows strict code formatting rules to ensure consistency and readability across the OpenAEV ecosystem.

Before submitting any **Pull Request**, contributors **must** format the codebase using **isort** and **black**.

### Code Formatting

The following tools are required (already included in the development dependencies):

* **isort** – import sorting
* **black** – code formatter

Run them from the project root:

```shell
poetry run isort --profile black .
poetry run black .
```

Both commands must complete **without errors or changes** before opening a PR.

> ⚠️ Pull Requests that do not respect formatting rules may be rejected or require additional review cycles.

#### Run the Injector

```shell
poetry run python -m netexec.openaev_netexec
```

#### Test it

SMB de test

```
podman run -d --name smb-test --network openaev-dev_default -p 445:445 dperson/samba -u "testuser;testpass" -s "share;/share;yes;no;no;testuser"
```

Run command to test

```
netexec smb smb-test -u testuser -p testpass --shares
```

## Behavior

The NetExec injector performs **contract-based network reconnaissance and authentication checks** by dynamically
building and executing **NetExec commands** based on the contract configuration and user inputs.

Each execution translates contract fields (targets, credentials, and options) into a NetExec command and runs it against
the selected targets.

NetExec is used as a **client-only tool**: it connects to remote services (such as SMB) and never exposes or hosts
services itself.

## Supported Contracts

The injector currently supports **SMB-based contracts**, focused on authentication checks and safe enumeration actions.

### SMB Authentication Contract

This contract allows validating credentials and performing controlled SMB enumeration using NetExec.

Supported SMB actions include:

* Share enumeration
* User enumeration
* Group enumeration
* Session enumeration
* Logged-on user discovery

Only **non-destructive, read-only actions** are exposed.

## Target Selection

Targets are resolved using the `target_selector` field defined in the contract.

### When the target type is **Assets**

| Selected Property | Asset Field Used |
|-------------------|-------------------------------|
| Seen IP | `endpoint_seen_ip` |
| Local IP | First entry in `endpoint_ips` |
| Hostname | `endpoint_hostname` |

### When the target type is **Manual**

Targets are provided directly as **comma-separated IP addresses or hostnames**.

## Options

The injector supports the following SMB options, mapped directly to NetExec arguments:

* `--shares` — Enumerate SMB shares
* `--users` — Enumerate users
* `--groups` — Enumerate groups
* `--sessions` — List active SMB sessions
* `--loggedon-users` — List currently logged-on users

If no option is explicitly selected, the injector defaults to a **safe enumeration action** (typically `--shares`) to
ensure meaningful output.

## Example Executions

Basic SMB share enumeration:

```bash
netexec smb 192.168.1.50 --shares
```

SMB authentication test with credentials:

```bash
netexec smb 192.168.1.50 -u admin -p Password123
```
## Resources

* [NetExec GitHub Repository](https://github.com/Pennyw0rth/NetExec)
* [NetExec Documentation](https://github.com/Pennyw0rth/NetExec/wiki)
9 changes: 9 additions & 0 deletions netexec/config.yml.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
openaev:
url: 'http://localhost:3001'
token: 'ChangeMe'

injector:
id: 'changeme'
name: 'NetExec'
log_level: 'info'

15 changes: 15 additions & 0 deletions netexec/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
services:
injector-netexec:
image: openaev/openaev-netexec:${INJECTOR_VERSION}
environment:
- OPENAEV_URL=${OPENAEV_URL}
- OPENAEV_TOKEN=${OPENAEV_TOKEN}
- INJECTOR_ID=${INJECTOR_ID}
- INJECTOR_NAME=NetExec
- INJECTOR_LOG_LEVEL=debug
restart: always
networks:
- openaev-dev_default
networks:
openaev-dev_default:
external: true
Empty file added netexec/netexec/__init__.py
Empty file.
Empty file.
32 changes: 32 additions & 0 deletions netexec/netexec/configuration/config_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from pydantic import Field
from pyoaev.configuration import ConfigLoaderOAEV, Configuration, SettingsLoader

from netexec.configuration.injector_config_override import InjectorConfigOverride
from netexec.contracts_netexec import NetExecContracts


class ConfigLoader(SettingsLoader):
openaev: ConfigLoaderOAEV = Field(
default_factory=ConfigLoaderOAEV, description="OpenAEV platform configuration"
)
injector: InjectorConfigOverride = Field(
default_factory=InjectorConfigOverride,
description="NetExec injector configuration",
)

def to_daemon_config(self) -> Configuration:
return Configuration(
config_hints={
# OpenAEV configuration (flattened)
"openaev_url": {"data": str(self.openaev.url)},
"openaev_token": {"data": self.openaev.token},
# Injector configuration (flattened)
"injector_id": {"data": self.injector.id},
"injector_name": {"data": self.injector.name},
"injector_type": {"data": "openaev_netexec_injector"},
"injector_contracts": {"data": NetExecContracts.build()},
"injector_log_level": {"data": self.injector.log_level},
"injector_icon_filepath": {"data": self.injector.icon_filepath},
},
config_base_model=self,
)
Loading
Loading