Skip to content
Merged
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
62 changes: 58 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- slack/notify:
event: fail
template: basic_fail_1
test:
test-nuclei:
working_directory: ~/openaev
docker:
- image: cimg/python:3.13
Expand All @@ -62,6 +62,54 @@ jobs:
working_directory: ~/openaev/nuclei
name: Tests for nuclei injector
command: python -m unittest
test-nmap:
working_directory: ~/openaev
docker:
- image: cimg/python:3.13
steps:
- checkout
- setup_remote_docker
- run:
working_directory: ~/openaev/nmap
name: Install dependencies for nmap injector
command: pip install -r requirements.txt
- run:
working_directory: ~/openaev/nmap
name: Overwrite pyoaev with correct version from CI
command: |
if [ "${CIRCLE_BRANCH}" = "main" ]; then
pip install --force-reinstall git+https://github.com/OpenAEV-Platform/client-python.git@main
else
pip install --force-reinstall git+https://github.com/OpenAEV-Platform/client-python.git@release/current
fi
- run:
working_directory: ~/openaev/nmap
name: Tests for nmap injector
command: python -m unittest
test-http-query:
working_directory: ~/openaev
docker:
- image: cimg/python:3.13
steps:
- checkout
- setup_remote_docker
- run:
working_directory: ~/openaev/http-query
name: Install dependencies for http-query injector
command: pip install -r src/requirements.txt
- run:
working_directory: ~/openaev/http-query
name: Overwrite pyoaev with correct version from CI
command: |
if [ "${CIRCLE_BRANCH}" = "main" ]; then
pip install --force-reinstall git+https://github.com/OpenAEV-Platform/client-python.git@main
else
pip install --force-reinstall git+https://github.com/OpenAEV-Platform/client-python.git@release/current
fi
- run:
working_directory: ~/openaev/http-query
name: Tests for http-query injector
command: python -m unittest
build_1:
working_directory: ~/openaev
docker:
Expand Down Expand Up @@ -245,7 +293,9 @@ workflows:
jobs:
- ensure_formatting
- linter
- test
- test-nmap
- test-nuclei
- test-http-query
- build_1:
filters:
tags:
Expand All @@ -256,7 +306,9 @@ workflows:
requires:
- ensure_formatting
- linter
- test
- test-nmap
- test-nuclei
- test-http-query
filters:
branches:
only:
Expand All @@ -265,7 +317,9 @@ workflows:
requires:
- ensure_formatting
- linter
- test
- test-nmap
- test-nuclei
- test-http-query
filters:
branches:
only:
Expand Down
32 changes: 32 additions & 0 deletions .github/workflows/validate-pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: "Validate PR Title"

on:
pull_request:
types: [ opened, edited, reopened, ready_for_review, synchronize ]

jobs:
validate-pr-title:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

steps:
- name: Check PR title format
shell: bash
run: |
TITLE="${{ github.event.pull_request.title }}"
echo "PR title: $TITLE"

# Regex for:
# [category/subcategory] type(scope?): description (#123?)
PATTERN='^\[([a-z]+(/[a-z]+)*)\] (feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(\([a-z]+\))?: [a-z].*( \(#[0-9]+\))$'

if [[ ! "$TITLE" =~ $PATTERN ]]; then
echo "❌ Invalid PR title."
echo "Required format:"
echo "[category] type(scope?): description (#123)"
exit 1
fi

echo "✅ PR title is valid."
2 changes: 1 addition & 1 deletion aws/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: '3'
services:
injector-aws:
image: openaev/injector-aws:2.0.4
image: openaev/injector-aws:2.0.5
environment:
- OPENAEV_URL=http://localhost
- OPENAEV_TOKEN=ChangeMe
Expand Down
2 changes: 1 addition & 1 deletion aws/src/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pyoaev==2.0.4
pyoaev==2.0.5
pacu>=1.5.0
awscli>=1.29.0
11 changes: 11 additions & 0 deletions http-query/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,14 @@ python3 openaev_http.py
## Behavior

This injector enables new inject contracts, allowing for API calls of the Get, Post, and Put types.

### Passing headers with the request
The contracts created in OpenAEV include an optional "Headers" parameter. While this field is technically a free-form
text input, the contents passed to it are expected to be in a certain format: `key=value` and together separated with a comma `,`.

Note the pattern supports whitespace within the keys and values.

Example:
```plaintext
content-type=application/json,x-custom-header=value for the header
```
2 changes: 1 addition & 1 deletion http-query/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: '3'
services:
injector-http-query:
image: openaev/injector-http-query:2.0.4
image: openaev/injector-http-query:2.0.5
environment:
- OPENAEV_URL=http://localhost
- OPENAEV_TOKEN=ChangeMe
Expand Down
Empty file.
42 changes: 42 additions & 0 deletions http-query/src/helpers/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class HTTPHelpers:
@staticmethod
def parse_headers(headers_str):
if isinstance(headers_str, list):
return headers_str
headers_list = []
for kv in headers_str.split(","):
if "=" in kv:
k, v = kv.split("=", 1)
headers_list.append({"key": k.strip(), "value": v.strip()})
return headers_list

@staticmethod
def parse_parts(parts_str):
if isinstance(parts_str, list):
return parts_str
parts_list = []
for kv in parts_str.split("&"):
if "=" in kv:
k, v = kv.split("=", 1)
parts_list.append({"key": k.strip(), "value": v.strip()})
return parts_list

@staticmethod
def request_data_parts_body(request_data):
parts = HTTPHelpers.parse_parts(
request_data["injection"]["inject_content"]["parts"]
)
keys = list(map(lambda p: p["key"], parts))
values = list(map(lambda p: p["value"], parts))
return dict(zip(keys, values))

@staticmethod
def response_parsing(response):
success = 200 <= response.status_code < 300
success_status = "SUCCESS" if success else "ERROR"
return {
"url": response.url,
"code": response.status_code,
"status": success_status,
"message": response.text,
}
37 changes: 11 additions & 26 deletions http-query/src/openaev_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
HTTP_RAW_PUT_CONTRACT,
HttpContracts,
)
from helpers.helpers import HTTPHelpers
from pyoaev.helpers import OpenAEVConfigHelper, OpenAEVInjectorHelper


Expand Down Expand Up @@ -43,24 +44,6 @@ def __init__(self):
self.config, open("img/icon-http.png", "rb")
)

@staticmethod
def _request_data_parts_body(request_data):
parts = request_data["injection"]["inject_content"]["parts"]
keys = list(map(lambda p: p["key"], parts))
values = list(map(lambda p: p["value"], parts))
return dict(zip(keys, values))

@staticmethod
def _response_parsing(response):
success = 200 <= response.status_code < 300
success_status = "SUCCESS" if success else "ERROR"
return {
"url": response.url,
"code": response.status_code,
"status": success_status,
"message": response.text,
}

def attachments_to_files(self, request_data):
documents = request_data["injection"].get("inject_documents", [])
attachments = list(filter(lambda d: d["document_attached"] is True, documents))
Expand All @@ -73,7 +56,9 @@ def attachments_to_files(self, request_data):

def http_execution(self, data: Dict):
# Build headers
inject_headers = data["injection"]["inject_content"].get("headers", [])
inject_headers = HTTPHelpers.parse_headers(
data["injection"]["inject_content"].get("headers", [])
)
headers = {}
for header_definition in inject_headers:
headers[header_definition["key"]] = header_definition["value"]
Expand All @@ -93,35 +78,35 @@ def http_execution(self, data: Dict):
# Get
if inject_contract == HTTP_GET_CONTRACT:
response = session.get(url=url, headers=headers)
return self._response_parsing(response)
return HTTPHelpers.response_parsing(response)
# Post
if inject_contract == HTTP_RAW_POST_CONTRACT:
body = data["injection"]["inject_content"]["body"]
response = session.post(
url=url, headers=headers, data=body, files=http_files
)
return self._response_parsing(response)
return HTTPHelpers.response_parsing(response)
# Put
if inject_contract == HTTP_RAW_PUT_CONTRACT:
body = data["injection"]["inject_content"]["body"]
response = session.put(
url=url, headers=headers, data=body, files=http_files
)
return self._response_parsing(response)
return HTTPHelpers.response_parsing(response)
# Form Post
if inject_contract == HTTP_FORM_POST_CONTRACT:
body = self._request_data_parts_body(data)
body = HTTPHelpers.request_data_parts_body(data)
response = session.post(
url=url, headers=headers, data=body, files=http_files
)
return self._response_parsing(response)
return HTTPHelpers.response_parsing(response)
# Form Put
if inject_contract == HTTP_FORM_PUT_CONTRACT:
body = self._request_data_parts_body(data)
body = HTTPHelpers.request_data_parts_body(data)
response = session.put(
url=url, headers=headers, data=body, files=http_files
)
return self._response_parsing(response)
return HTTPHelpers.response_parsing(response)
# Nothing supported
return {
"code": 400,
Expand Down
2 changes: 1 addition & 1 deletion http-query/src/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pyoaev==2.0.4
pyoaev==2.0.5
Empty file added http-query/test/__init__.py
Empty file.
Empty file.
33 changes: 33 additions & 0 deletions http-query/test/helpers/test_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from unittest import TestCase

from src.helpers.helpers import HTTPHelpers


class HTTPHelpersTest(TestCase):
def test_parse_headers_with_string(self):
input_str = (
"Content-Type=application/x-www-form-urlencoded,Accept=application/json"
)
expected = [
{"key": "Content-Type", "value": "application/x-www-form-urlencoded"},
{"key": "Accept", "value": "application/json"},
]
result = HTTPHelpers.parse_headers(input_str)
self.assertEqual(result, expected)

def test_parse_parts_with_string(self):
input_str = "msg=test&user=alice"
expected = [
{"key": "msg", "value": "test"},
{"key": "user", "value": "alice"},
]
result = HTTPHelpers.parse_parts(input_str)
self.assertEqual(result, expected)

def test_parse_headers_empty_string(self):
result = HTTPHelpers.parse_headers("")
self.assertEqual(result, [])

def test_parse_parts_empty_string(self):
result = HTTPHelpers.parse_parts("")
self.assertEqual(result, [])
6 changes: 5 additions & 1 deletion injector_common/injector_common/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ def extract_targets(
)

elif selector_key == "manual":
targets = [t.strip() for t in content[TARGETS_KEY].split(",") if t.strip()]
targets = list(
dict.fromkeys(
[t.strip() for t in content[TARGETS_KEY].split(",") if t.strip()]
)
)

else:
raise ValueError("No targets provided for this injection")
Expand Down
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.0.4",
"pyoaev==2.0.5",
]
[project.optional-dependencies]
dev = [
Expand Down
2 changes: 1 addition & 1 deletion nmap/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: '3'
services:
injector-nmap:
image: openaev/injector-nmap:2.0.4
image: openaev/injector-nmap:2.0.5
environment:
- OPENAEV_URL=http://localhost
- OPENAEV_TOKEN=ChangeMe
Expand Down
2 changes: 1 addition & 1 deletion nmap/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pyoaev==2.0.4
pyoaev==2.0.5
../injector_common
Loading
Loading