Skip to content

Commit

Permalink
Merge pull request #79 from OWASP/dev
Browse files Browse the repository at this point in the history
Dev RELEASE: v0.17.1
  • Loading branch information
dmdhrumilmistry authored Apr 18, 2024
2 parents ce7086c + 9eb292d commit 73f61e6
Show file tree
Hide file tree
Showing 10 changed files with 399 additions and 192 deletions.
13 changes: 7 additions & 6 deletions .github/workflows/dev-push.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: "Dev Release: Build and Push OWASP OFFAT Docker Images to DockerHub"
name: "Build and Push Dev/main OWASP OFFAT Docker Images to DockerHub"

on:
push:
branches:
- "main"
- "dev"

jobs:
Expand All @@ -24,31 +25,31 @@ jobs:
uses: docker/build-push-action@v3
with:
context: ./src/
file: ./src/DockerFiles/base-Dockerfile
file: ./src/DockerFiles/wolfi-base-Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-base:dev
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-base:${{ github.head_ref || github.ref_name }}
platforms: linux/amd64,linux/arm64
- name: Build and push offat docker image
uses: docker/build-push-action@v3
with:
context: ./src/
file: ./src/DockerFiles/dev/cli-Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat:dev
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat:${{ github.head_ref || github.ref_name }}
platforms: linux/amd64,linux/arm64
- name: Build and push offat-api docker image
uses: docker/build-push-action@v3
with:
context: ./src/
file: ./src/DockerFiles/dev/backend-api-Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-api:dev
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-api:${{ github.head_ref || github.ref_name }}
platforms: linux/amd64,linux/arm64
- name: Build and push offat-api-worker docker image
uses: docker/build-push-action@v3
with:
context: ./src/
file: ./src/DockerFiles/dev/backend-api-worker-Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-api-worker:dev
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-api-worker:${{ github.head_ref || github.ref_name }}
platforms: linux/amd64,linux/arm64
37 changes: 36 additions & 1 deletion .github/workflows/release-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,46 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Build and publish version tag image
- name: Build and push offat-base docker image
uses: docker/build-push-action@v3
with:
context: ./src/
file: ./src/DockerFiles/base-Dockerfile
file: ./src/DockerFiles/wolfi-base-Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-base:${{ github.event.release.tag_name }}
platforms: linux/amd64,linux/arm64
- name: Build and push offat docker image
uses: docker/build-push-action@v3
with:
context: ./src/
file: ./src/DockerFiles/main/cli-Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat:${{ github.event.release.tag_name }}
platforms: linux/amd64,linux/arm64
- name: Build and push offat-api docker image
uses: docker/build-push-action@v3
with:
context: ./src/
file: ./src/DockerFiles/main/backend-api-Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-api:${{ github.event.release.tag_name }}
platforms: linux/amd64,linux/arm64
- name: Build and push offat-api-worker docker image
uses: docker/build-push-action@v3
with:
context: ./src/
file: ./src/DockerFiles/main/backend-api-worker-Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-api-worker:${{ github.event.release.tag_name }}
platforms: linux/amd64,linux/arm64

# Build and publish latest tag image
- name: Build and push offat-base docker image
uses: docker/build-push-action@v3
with:
context: ./src/
file: ./src/DockerFiles/wolfi-base-Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-base:latest
platforms: linux/amd64,linux/arm64
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ OWASP OFFAT (OFFensive Api Tester) is created to automatically test API for comm

## Demo

[![asciicast](https://asciinema.org/a/LFXLILNkf7Gce5uCuJydplbEd.svg)](https://asciinema.org/a/LFXLILNkf7Gce5uCuJydplbEd)
[![asciicast](https://asciinema.org/a/9MSwl7UafIVT3iJn13OcvWXeF.svg)](https://asciinema.org/a/9MSwl7UafIVT3iJn13OcvWXeF)

> Note: The columns for 'data_leak' and 'result' in the table represent independent aspects. It's possible for there to be a data leak in the endpoint, yet the result for that endpoint may still be marked as 'Success'. This is because the 'result' column doesn't necessarily reflect the overall test result; it may indicate success even in the presence of a data leak.
## Security Checks

Expand Down
59 changes: 59 additions & 0 deletions src/DockerFiles/wolfi-base-Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
############################
# Builder Stage
############################
# use chainguard hardened images with SBOM
FROM cgr.dev/chainguard/wolfi-base as builder

WORKDIR /offat

ARG version=3.12

ENV LANG=C.UTF-8
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PATH="/offat/.venv/bin:$PATH"


RUN apk add python-${version} py${version}-pip && \
chown -R nonroot.nonroot /offat

# install poetry and copy lock file
RUN python -m pip install poetry
COPY pyproject.toml poetry.lock README.md ./
COPY offat ./offat

# poetry config
ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache

RUN --mount=type=cache,target=$POETRY_CACHE_DIR poetry install -E api --without dev

############################
# runtime stage
############################
FROM cgr.dev/chainguard/wolfi-base as runtime

WORKDIR /offat

ARG version=3.12

ENV LANG=C.UTF-8
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PATH="/offat/.venv/bin:$PATH"
ENV VIRTUAL_ENV=/offat/.venv

RUN apk add python-${version} py${version}-pip && \
chown -R nonroot.nonroot /offat


# copy venv from builder image
COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}

# copy necessary files
COPY offat ./offat
COPY README.md CODE_OF_CONDUCT.md DISCLAIMER.md pyproject.toml .

USER nonroot
12 changes: 10 additions & 2 deletions src/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
build-local-images:
build-slim-local-images:
@docker build -f DockerFiles/base-Dockerfile -t dmdhrumilmistry/offat-base .
@docker build -f DockerFiles/cli-Dockerfile -t dmdhrumilmistry/offat .
# @docker build -f DockerFiles/main/cli-Dockerfile -t dmdhrumilmistry/offat .

build-local-image:
@docker build -f DockerFiles/wolfi-base-Dockerfile -t dmdhrumilmistry/offat-base . --no-cache --progress=plain

scan-vulns:
@trivy image dmdhrumilmistry/offat-base --scanners vuln

local: build-local-image scan-vulns
10 changes: 6 additions & 4 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ Automatically Tests for vulnerabilities after generating tests from openapi spec
- API for Automating tests and Integrating Tool with other platforms/tools
- CLI tool
- Proxy Support
- Dockerized Project for Easy Usage
- Secure Dockerized Project for Easy Usage
- Open Source Tool with MIT License

## Demo

[![asciicast](https://asciinema.org/a/9MSwl7UafIVT3iJn13OcvWXeF.svg)](https://asciinema.org/a/9MSwl7UafIVT3iJn13OcvWXeF)

> Note: The columns for 'data_leak' and 'result' in the table represent independent aspects. It's possible for there to be a data leak in the endpoint, yet the result for that endpoint may still be marked as 'Success'. This is because the 'result' column doesn't necessarily reflect the overall test result; it may indicate success even in the presence of a data leak.
## PyPi Downloads

| Period | Count |
Expand Down Expand Up @@ -262,14 +264,14 @@ offat -f swagger_file.json -p http://localhost:8080 --no-ssl -o output.json
type: str
```

> If you're using Termux or windows, then use `pip` instead of `pip3`.
> If you're using Termux or windows, then use `pip` instead of `pip3`.
> Few features are only for linux os, hence they might not work on windows and require admin priviliges.
### Open In Google Cloud Shell
- Temporary Session
- Temporary Session
[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/OWASP/OFFAT.git&ephemeral=true&show=terminal&cloudshell_print=./DISCLAIMER.md)
- Perisitent Session
- Perisitent Session
[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/OWASP/OFFAT.git&ephemeral=false&show=terminal&cloudshell_print=./DISCLAIMER.md)
## Have any Ideas 💡 or issue
Expand Down
91 changes: 91 additions & 0 deletions src/offat/report/summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
OWASP OFFAT summarizer class module
"""
from rich.table import Table, Column


class ResultSummarizer:
"""class for summarizing results"""

@staticmethod
def get_counts(results: list[dict], filter_errors: bool = False) -> dict[str, int]:
"""
Processes results and returns test summary of errored, succeeded, failed
and data leak endpoint results count.
Args:
results (list): OFFAT results list of dict
filter_errors (bool): filters errored results before processing count
if True. Default value: False
Returns:
dict: name (str) as key and its associated count (int)
"""
if filter_errors:
results = list(filter(lambda result: result.get('error', False), results))

error_count = 0
data_leak_count = 0
failed_count = 0
success_count = 0
for result in results:
error_count += 1 if result.get('error', False) else 0
data_leak_count += 1 if result.get('data_leak', False) else 0

if result.get('result'):
success_count += 1
else:
failed_count += 1

count_dict = {
'errors': error_count,
'data_leaks': data_leak_count,
'failed': failed_count,
'success': success_count,
}

return count_dict

@staticmethod
def generate_count_summary(
results: list[dict],
filter_errors: bool = False,
output_format: str = 'table',
table_title: str | None = None,
) -> Table | str:
"""
Processes results and returns test summary of errored, succeeded, failed
and data leak endpoint results count.
Args:
results (list): OFFAT results list of dict
filter_errors (bool): filters errored results before processing count
if True. Default value: False
output_format (str): expected output format (table, markdown)
Returns:
rich.Table | str : returns summary in expected format
"""
count_summary = ResultSummarizer.get_counts(
results=results, filter_errors=filter_errors
)
match output_format:
case 'markdown':
output = ''
if table_title:
output += f'**{table_title}**\n'

for key, count in count_summary.items():
output += f'{key:<15}:\t{count}\n'

case _: # table format
output = Table(
Column(header='⚔️', overflow='fold', justify='center'),
Column(header='Endpoints Count', overflow='fold'),
title=table_title,
)

for key, count in count_summary.items():
output.add_row(*[key, str(count)])

return output
23 changes: 16 additions & 7 deletions src/offat/tester/tester_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from .test_generator import TestGenerator
from .test_runner import TestRunner
from ..report.generator import ReportGenerator
from ..logger import logger
from ..report.summary import ResultSummarizer
from ..logger import logger, console
from ..parsers import SwaggerParser, OpenAPIv3Parser


Expand Down Expand Up @@ -177,7 +178,7 @@ def generate_and_run_tests(
)

# OS Command Injection Fuzz Test
test_name = 'Checking for OS Command Injection Vulnerability with fuzzed params and checking response body:'
test_name = 'Checking for OS Command Injection Vulnerability with fuzzed params and checking response body:' # noqa: E501
logger.info(test_name)
os_command_injection_tests = test_generator.os_command_injection_fuzz_params_test(
api_parser
Expand All @@ -191,7 +192,7 @@ def generate_and_run_tests(
)

# XSS/HTML Injection Fuzz Test
test_name = 'Checking for XSS/HTML Injection Vulnerability with fuzzed params and checking response body:'
test_name = 'Checking for XSS/HTML Injection Vulnerability with fuzzed params and checking response body:' # noqa: E501
logger.info(test_name)
os_command_injection_tests = test_generator.xss_html_injection_fuzz_params_test(
api_parser
Expand Down Expand Up @@ -233,7 +234,7 @@ def generate_and_run_tests(
)

# Mass Assignment / BOPLA
test_name = 'Checking for Mass Assignment Vulnerability with fuzzed params and checking response status codes:'
test_name = 'Checking for Mass Assignment Vulnerability with fuzzed params and checking response status codes:' # noqa: E501
logger.info(test_name)
bopla_tests = test_generator.bopla_fuzz_test(
api_parser, success_codes=[200, 201, 301]
Expand Down Expand Up @@ -268,7 +269,7 @@ def generate_and_run_tests(
)

# BOLA path test with fuzzed + user data + trailing slash
test_name = 'Checking for BOLA in PATH with trailing slash id using fuzzed and user provided params:'
test_name = 'Checking for BOLA in PATH with trailing slash id using fuzzed and user provided params:' # noqa: E501
logger.info(test_name)
bola_trailing_slash_path_user_data_tests = test_generator.test_with_user_data(
test_data_config,
Expand All @@ -284,7 +285,7 @@ def generate_and_run_tests(
)

# OS Command Injection Fuzz Test
test_name = 'Checking for OS Command Injection Vulnerability with fuzzed & user params and checking response body:'
test_name = 'Checking for OS Command Injection Vulnerability with fuzzed & user params and checking response body:' # noqa: E501
logger.info(test_name)
os_command_injection_with_user_data_tests = test_generator.test_with_user_data(
test_data_config,
Expand All @@ -300,7 +301,7 @@ def generate_and_run_tests(
)

# XSS/HTML Injection Fuzz Test
test_name = 'Checking for XSS/HTML Injection Vulnerability with fuzzed & user params and checking response body:'
test_name = 'Checking for XSS/HTML Injection Vulnerability with fuzzed & user params and checking response body:' # noqa: E501
logger.info(test_name)
os_command_injection_with_user_data_tests = test_generator.test_with_user_data(
test_data_config,
Expand Down Expand Up @@ -335,12 +336,20 @@ def generate_and_run_tests(
results=results,
report_format=output_file_format,
report_path=output_file,
capture_failed=capture_failed,
)

ReportGenerator.generate_report(
results=results,
report_format='table',
report_path=None,
capture_failed=capture_failed,
)

result_summary = ResultSummarizer.generate_count_summary(
results, table_title='Results Summary'
)

console.print(result_summary)

return results
Loading

0 comments on commit 73f61e6

Please sign in to comment.