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
27 changes: 27 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,30 @@ jobs:
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 @@ -271,6 +295,7 @@ workflows:
- linter
- test-nmap
- test-nuclei
- test-http-query
- build_1:
filters:
tags:
Expand All @@ -283,6 +308,7 @@ workflows:
- linter
- test-nmap
- test-nuclei
- test-http-query
filters:
branches:
only:
Expand All @@ -293,6 +319,7 @@ workflows:
- linter
- test-nmap
- test-nuclei
- test-http-query
filters:
branches:
only:
Expand Down
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
```
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
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, [])