From 82bfa34af56b1a5019daf6a0b1f3c5573f178052 Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Thu, 5 Sep 2024 14:37:28 -0300 Subject: [PATCH 01/13] add:framework-zap --- Authentication_ZAP_GT_CRIVO/.gitignore | 170 +++++++++ Authentication_ZAP_GT_CRIVO/Makefile | 20 + Authentication_ZAP_GT_CRIVO/README.md | 109 ++++++ Authentication_ZAP_GT_CRIVO/copy.sh | 35 ++ .../docker-compose.yml | 48 +++ Authentication_ZAP_GT_CRIVO/example.json | 8 + .../framework/Dockerfile | 38 ++ .../framework/autentication/__init__.py | 0 .../framework/autentication/autentication.py | 58 +++ .../framework/base_context/context_base.yaml | 83 +++++ .../framework/context/__init__.py | 0 .../framework/context/context.py | 100 +++++ .../framework/elements/__init__.py | 0 .../framework/elements/elements.py | 92 +++++ .../framework/keywords/__init__.py | 0 .../framework/keywords/keywords.py | 31 ++ .../keywords/regex_words/invalid_urls.txt | 2 + .../keywords/regex_words/type_elements.txt | 3 + .../keywords/regex_words/valid_elements.txt | 7 + .../keywords/regex_words/valid_urls.txt | 1 + Authentication_ZAP_GT_CRIVO/framework/main.py | 344 ++++++++++++++++++ .../framework/params/__init__.py | 0 .../framework/params/params.py | 30 ++ .../framework/requirements.txt | 29 ++ .../framework/urls_login/__init__.py | 0 .../framework/urls_login/urls_login.py | 15 + .../framework/user_data/__init__.py | 0 .../framework/user_data/user_data.py | 13 + Authentication_ZAP_GT_CRIVO/server/app.py | 74 ++++ Authentication_ZAP_GT_CRIVO/server/dockerfile | 28 ++ .../server/requirements.txt | 7 + .../server/templates/index.html | 35 ++ 32 files changed, 1380 insertions(+) create mode 100644 Authentication_ZAP_GT_CRIVO/.gitignore create mode 100644 Authentication_ZAP_GT_CRIVO/Makefile create mode 100644 Authentication_ZAP_GT_CRIVO/README.md create mode 100755 Authentication_ZAP_GT_CRIVO/copy.sh create mode 100644 Authentication_ZAP_GT_CRIVO/docker-compose.yml create mode 100644 Authentication_ZAP_GT_CRIVO/example.json create mode 100644 Authentication_ZAP_GT_CRIVO/framework/Dockerfile create mode 100644 Authentication_ZAP_GT_CRIVO/framework/autentication/__init__.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/base_context/context_base.yaml create mode 100644 Authentication_ZAP_GT_CRIVO/framework/context/__init__.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/context/context.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/elements/__init__.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/elements/elements.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/keywords/__init__.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt create mode 100644 Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/type_elements.txt create mode 100644 Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt create mode 100644 Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_urls.txt create mode 100644 Authentication_ZAP_GT_CRIVO/framework/main.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/params/__init__.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/params/params.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/requirements.txt create mode 100644 Authentication_ZAP_GT_CRIVO/framework/urls_login/__init__.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/user_data/__init__.py create mode 100644 Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py create mode 100644 Authentication_ZAP_GT_CRIVO/server/app.py create mode 100644 Authentication_ZAP_GT_CRIVO/server/dockerfile create mode 100644 Authentication_ZAP_GT_CRIVO/server/requirements.txt create mode 100644 Authentication_ZAP_GT_CRIVO/server/templates/index.html diff --git a/Authentication_ZAP_GT_CRIVO/.gitignore b/Authentication_ZAP_GT_CRIVO/.gitignore new file mode 100644 index 0000000..195d58c --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/.gitignore @@ -0,0 +1,170 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# arquivos +user_data_test.py +tests/ +notas.txt +contexto.py +test.py +Command_container.txt \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/Makefile b/Authentication_ZAP_GT_CRIVO/Makefile new file mode 100644 index 0000000..e9b3605 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/Makefile @@ -0,0 +1,20 @@ +generate_key: + @echo "Generating API key..." + @python3 -c 'import random; import string; key = "".join(random.choices(string.ascii_lowercase + string.digits, k=26)); print(key)' > apikey.txt + +update_env: + @key=$$(cat apikey.txt); \ + echo "Key to be used: $$key"; \ + if grep -q '^ZAP_API_KEY=' file.env; then \ + sed -i "s/^ZAP_API_KEY=.*/ZAP_API_KEY=$$key/" file.env; \ + else \ + echo "ZAP_API_KEY=$$key" >> file.env; \ + fi + @echo "file.env updated successfully!" + rm apikey.txt + + +run: generate_key update_env + @echo "Starting services with new API key..." + docker compose --env-file file.env up --build --force-recreate -d + ./copy.sh $(DIR) diff --git a/Authentication_ZAP_GT_CRIVO/README.md b/Authentication_ZAP_GT_CRIVO/README.md new file mode 100644 index 0000000..2f7a68c --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/README.md @@ -0,0 +1,109 @@ +# Authentication_ZAP_GT_CRIVO + +Este framework foi projetado para automatizar o processo de testes de segurança em aplicações web, utilizando autenticação para aumentar a superfície de ataque. Ele permite que o usuário teste diversas aplicações sem a necessidade de interagir diretamente com o conteúdo da aplicação. O framework automatiza o processo de captura de metadados, extração de elementos-chave e autenticação nas aplicações. Após a autenticação, o framework processa os metadados e cria um arquivo de contexto que pode ser utilizado posteriormente em testes de segurança. + +## ZAP + +O Zed Attack Proxy (ZAP) é uma ferramenta utilizada para testes de segurança em aplicações web. O framework usa uma imagem Docker do ZAP, que inclui um proxy e uma API que roda na porta `8080`. A ideia central é conectar o framework ao proxy do ZAP no momento em que uma aplicação web é instanciada. Dessa forma, o ZAP analisa a aplicação utilizando varreduras passivas e ativas durante os testes de penetração. + +O framework interage com o ZAP enviando comandos e recebendo dados por meio da API. Os dados necessários para criar o arquivo de contexto da aplicação são extraídos dos alertas gerados pelo ZAP, que indicam possíveis vulnerabilidades. Esses alertas são configurados para que, ao serem acionados, necessitem de evidências, que podem ser fornecidas pelo usuário ou pelos desenvolvedores da ferramenta. Nesta versão, estamos utilizando os alertas definidos pelos próprios desenvolvedores do ZAP. + +## Framework + +O framework conecta-se à API do ZAP para realizar testes e capturar metadados. Inicialmente, ele verifica a existência de arquivos de configuração no diretório reservado no volume. Assim que identificados, o fluxo de execução começa. Cada arquivo de configuração é analisado e seus campos são interpretados utilizando a biblioteca Pydantic. Se um arquivo não contiver as informações essenciais definidas na classe de configuração, ele será desconsiderado. + +### Exemplo de classe de configuração: + +```python +class User(BaseModel): + context: str + url: List[str] + url_login: Optional[str] = None + exclude_urls: List[str] = [] + report_title: Optional[str] = "Report" + login: str + password: str +``` + +Apenas dois atributos são opcionais: a URL da página de login e o título do relatório. Se a URL de login não for fornecida, o framework utiliza o spider do ZAP para tentar identificar as URLs da aplicação a partir da URL base. No entanto, nem sempre é possível encontrar a página de login automaticamente, por isso é recomendável fornecer essa URL sempre que possível. Se o spider falhar em encontrar a URL de login, um erro será lançado. + +O framework utiliza o proxy do ZAP para acessar as aplicações web. Ele faz isso configurando o navegador Firefox com certificados de segurança e o proxy, permitindo que o ZAP capture os metadados das requisições durante a autenticação. O Selenium é utilizado para interagir com as aplicações web de duas formas: + +### 1. Captura dos elementos de autenticação + +O Selenium instancia o navegador na página de login e lista todos os elementos da página. Em seguida, ele filtra apenas os elementos do formulário de login. Se a página não retornar nenhum elemento do formulário, como ocorre em algumas aplicações que utilizam técnicas para ocultar esses dados na DOM, o framework pode não ser capaz de capturá-los. + +Quando possível, o framework identifica os campos de login e senha utilizando expressões regulares (regex). As regex são configuradas para ignorar maiúsculas, minúsculas, pontuações e símbolos, cobrindo assim uma maior variedade de elementos. Se o framework não conseguir encontrar os campos necessários, o usuário pode adicionar regex personalizadas no arquivo `framework/keywords/regex_words/valid_elements.txt`. + +### 2. Realização do login na aplicação web + +Após identificar os elementos de login, o Selenium envia as credenciais pelo proxy do ZAP. O framework então verifica se o login foi bem-sucedido observando se o formulário ainda está presente e se o ZAP detecta a autenticação através do alerta `Authentication Request Identified`. + +Com a autenticação realizada, o framework captura os metadados relevantes para criar o contexto da aplicação, incluindo o corpo da requisição (request body) do POST de autenticação e o tipo de gerenciamento de sessão da aplicação. Esses dados são usados para gerar um arquivo YAML com o plano de automação da aplicação, que será utilizado para instanciar a aplicação e finalizar o processo de criação do contexto. + +## Web Server + +O servidor web foi projetado para criar uma interface entre o framework e o usuário. Ele roda na porta `8000` e expõe o volume no localhost, permitindo que o usuário visualize os arquivos gerados. + +## Docker Compose + +O `docker-compose` foi configurado para permitir que as três aplicações interajam entre si. Uma sub-rede em modo *bridge* foi criada e as dependências entre os containers foram definidas para garantir a ordem correta de execução. Primeiro, o container do ZAP é iniciado, seguido pelo container do framework, e por último o container do servidor web. + +## Arquivo de Configuração + +O arquivo de configuração deve estar em formato JSON. Abaixo está um exemplo de como organizá-lo: + +```json +{ + "context": "Test_DVWA", + "url": ["http://192.168.15.95/dvwa/"], + "url_login": "http://192.168.15.95/dvwa/login.php", + "report_title": "Report_dvwa", + "login": "admin", + "password": "admin" +} +``` + +O Pydantic fará o *parser* do arquivo e extrairá os atributos necessários para a criação do contexto. Um exemplo será disponibilizado na raiz do repositório, chamado `example.json`. + +## Makefile + +O `Makefile` realiza operações importantes para o funcionamento correto do framework: + +1. Gera a chave da API que será utilizada pelo ZAP, definindo-a como uma variável de ambiente. A cada execução, a chave é gerada aleatoriamente. +2. Executa o script `copy.sh`, que copia os arquivos de configuração do diretório especificado para o volume do arcabouço. + +### copy.sh + +Este script recebe como parâmetro o diretório dos arquivos de configuração e copia-os para o volume, evitando duplicações a cada execução. Apenas arquivos `.json` são copiados, e se algum arquivo não passar no *parser* do Pydantic, ele será ignorado. + +## Entrada (Input) + +Antes de iniciar o framework, é necessário criar um arquivo de configuração da aplicação a ser testada, como mostrado acima. Após criar um ou mais arquivos de configuração, armazene-os em um único diretório, cujo caminho será utilizado como parâmetro do `Makefile`. + +## Saída (Output) + +O framework gera um arquivo com o plano dos *scans* passivos e o contexto da aplicação. + +## Como Usar + +Para iniciar o framework, utilize o comando: + +```bash +make run DIR="diretório dos arquivos" +``` + +O `docker-compose` irá subir três containers: o ZAP, o framework e o servidor web. Os containers rodam em *background*. Para visualizar os logs, use os comandos: + +```bash +docker logs -f authentication_zap_gt_crivo-zaproxy-1 +docker logs -f authentication_zap_gt_crivo-framework-1 +``` + +Após os testes, os resultados serão armazenados no volume do *compose* e poderão ser acessados pelo servidor web na URL `localhost:8000`. + +Para finalizar a aplicação, use: + +```bash +docker compose down +``` diff --git a/Authentication_ZAP_GT_CRIVO/copy.sh b/Authentication_ZAP_GT_CRIVO/copy.sh new file mode 100755 index 0000000..8195065 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/copy.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Verifica se foi passado somente 1 argumento +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Argumento do diretório que será copiado +ORIGEM=$1 + +DIR_DESTINO="input_config" +# volume no compose +DESTINO="/shared_data/$DIR_DESTINO" + + +# Verifica se o diretório de origem existe +if [ ! -d "$ORIGEM" ]; then + exit 1 +fi + +# Verifica se o diretório de destino existe se não existir, criar +docker compose exec framework bash -c "mkdir -p $DESTINO && rm -rf $DESTINO/*" + +# comando que apaga todos os arquivos de configuração se houver antes de copiar os novos. + +# Copia apenas o conteúdo .json do diretório de origem para o volume +for file in "$ORIGEM"/*.json; do + if [ -f "$file" ]; then + docker compose cp "$file" framework:"$DESTINO/$(basename "$file")" + fi +done + + +echo "Conteúdo copiado de $ORIGEM para $DESTINO com sucesso." diff --git a/Authentication_ZAP_GT_CRIVO/docker-compose.yml b/Authentication_ZAP_GT_CRIVO/docker-compose.yml new file mode 100644 index 0000000..13ad74e --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/docker-compose.yml @@ -0,0 +1,48 @@ +services: + zaproxy: + image: zaproxy/zap-stable + command: ["zap.sh", "-config", "api.key=${ZAP_API_KEY}", "-daemon", "-host", "0.0.0.0", "-port", "8080", "-config", "api.addrs.addr.name=.*", "-config", "api.addrs.addr.regex=true"] + ports: + - "8080:8080" + networks: + - zapnet + environment: + - ZAP_API_KEY=${ZAP_API_KEY} + volumes: + - shared_data:/shared_data + + framework: + build: ./framework + environment: + - FIREFOX=/usr/local/bin/geckodriver + - ZAP_API_KEY=${ZAP_API_KEY} + - ZAP_PROXY_ADDRESS=zaproxy + networks: + - zapnet + depends_on: + - zaproxy + volumes: + - ./framework:/app + - shared_data:/shared_data + + server: + build: ./server + environment: + - UPLOAD_FOLDER=/shared_data + ports: + - "8000:8000" + networks: + - zapnet + depends_on: + - zaproxy + - framework + volumes: + - ./server:/app + - shared_data:/shared_data + +networks: + zapnet: + driver: bridge + +volumes: + shared_data: diff --git a/Authentication_ZAP_GT_CRIVO/example.json b/Authentication_ZAP_GT_CRIVO/example.json new file mode 100644 index 0000000..d8e8bdb --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/example.json @@ -0,0 +1,8 @@ +{ + "context": "", + "url": [], + "url_login": "", + "report_title": "Report", + "login": "", + "password": "" +} \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/Dockerfile b/Authentication_ZAP_GT_CRIVO/framework/Dockerfile new file mode 100644 index 0000000..765a98b --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/Dockerfile @@ -0,0 +1,38 @@ +# Base image - colocar versão mais recente +FROM python:3.12.5-slim + +# Install necessary dependencies +RUN apt-get update && \ + apt-get install -y \ + wget \ + unzip \ + libxi6 \ + libgconf-2-4 \ + default-jdk \ + python3-pip \ + firefox-esr \ + curl && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Install geckodriver +RUN wget https://github.com/mozilla/geckodriver/releases/download/v0.29.1/geckodriver-v0.29.1-linux64.tar.gz -O /tmp/geckodriver.tar.gz && \ + tar -xzf /tmp/geckodriver.tar.gz -C /usr/local/bin/ && \ + rm /tmp/geckodriver.tar.gz + +# Install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy your application code to the container +COPY . /app +WORKDIR /app + + +# Set environment variable for ZAP API key - tirar daqui +ENV FIREFOX="/usr/local/bin/geckodriver" +ENV ZAP_PROXY_ADDRESS="zaproxy" + +# Command to start the application +CMD sleep 30 && python3 main.py +# CMD sleep 10000 \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/autentication/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/autentication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py b/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py new file mode 100644 index 0000000..3294c5c --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py @@ -0,0 +1,58 @@ +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By + + +def find_element_by_attribute(driver, attribute, value): + """ + Encontra um elemento no site com base no atributo especificado. + """ + try: + if attribute == "name": + element = driver.find_element(By.NAME, value) + elif attribute == "type": + element = driver.find_element(By.CSS_SELECTOR, f"input[type='{value}']") + elif attribute == "placeholder": + element = driver.find_element( + By.CSS_SELECTOR, f"input[placeholder='{value}']" + ) + else: + raise ValueError(f"Atributo inválido: {attribute}") + print(f"Elemento encontrado com sucesso.") + return element + except NoSuchElementException: + print(f"Elemento não encontrado com base no atributo '{attribute}'.") + return None + + +# Função para validar se o elemento está visível +def validate_by_attribute(driver, attribute, value): + try: + if attribute == "name": + if driver.find_element(By.NAME, value).is_displayed(): + pass + elif attribute == "type": + if driver.find_element( + By.CSS_SELECTOR, f"input[type='{value}']" + ).is_displayed(): + pass + elif attribute == "placeholder": + if driver.find_element( + By.CSS_SELECTOR, f"input[placeholder='{value}']" + ).is_displayed(): + pass + print("failed to log in !") + driver.refresh() + return + except: + print("login done !") + + +def check_credentials(request, credencial_login, credencial_passsword): + + # Verifica os casos em que o login e senha são iguais (admin, admin) + if credencial_login == credencial_passsword: + # Conta o número de ocorrências da palavra no texto + return request.count(credencial_login) >= 2 + else: + # Verifica se ambas as palavras estão presentes no texto + return credencial_login in request and credencial_passsword in request diff --git a/Authentication_ZAP_GT_CRIVO/framework/base_context/context_base.yaml b/Authentication_ZAP_GT_CRIVO/framework/base_context/context_base.yaml new file mode 100644 index 0000000..5e5f80f --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/base_context/context_base.yaml @@ -0,0 +1,83 @@ +--- +env: + contexts: + - name: "" + urls: + - "" + includePaths: + - "" + excludePaths: [] + authentication: + method: "" + parameters: + loginPageUrl: "" + loginRequestUrl: "" + loginRequestBody: "" + verification: + method: "response" + pollFrequency: 60 + pollUnits: "requests" + pollUrl: "" + pollPostData: "" + sessionManagement: + method: "" + parameters: {} + technology: + exclude: [] + users: + - name: "" + credentials: + password: "" + username: "" + parameters: + failOnError: true + failOnWarning: false + progressToStdout: true + vars: {} +jobs: +- parameters: + scanOnlyInScope: true + enableTags: false + disableAllRules: false + rules: [] + name: "passiveScan-config" + type: "passiveScan-config" +- parameters: + context: "" + user: "" + url: "" + maxDuration: 0 + maxDepth: 0 + maxChildren: 0 + name: "spider" + type: "spider" + tests: + - onFail: "INFO" + statistic: "automation.spider.urls.added" + site: "" + operator: ">=" + value: 100 + name: "At least 100 URLs found" + type: "stats" +- parameters: + context: "" + user: "" + url: "" + maxDuration: 60 + maxCrawlDepth: 10 + numberOfBrowsers: 8 + inScopeOnly: true + runOnlyIfModern: false + name: "spiderAjax" + type: "spiderAjax" + tests: + - onFail: "INFO" + statistic: "spiderAjax.urls.added" + site: "" + operator: ">=" + value: 100 + name: "At least 100 URLs found" + type: "stats" +- parameters: {} + name: "passiveScan-wait" + type: "passiveScan-wait" \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/context/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/context/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/context/context.py b/Authentication_ZAP_GT_CRIVO/framework/context/context.py new file mode 100644 index 0000000..705463b --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/context/context.py @@ -0,0 +1,100 @@ +from ruamel.yaml.comments import CommentedMap +from keywords import keywords + + +def replace_words( + text, + login, + password, + credential_login="{%username%}", + credential_password="{%password%}", +): + if login == password: + new_request = text.replace(login, credential_login, 1).replace( + password, credential_password, 1 + ) + else: + if "%40" in text: + login = login.replace("@", "%40") + new_request = text.replace(login, credential_login, 1).replace( + password, credential_password, 1 + ) + return new_request + + +def build_yaml( + context, + alert_count, + request_body, + credential_login, + credential_password, + context_name, + base_url, + base_url_login, +): + + if alert_count > 0: + # marcar contexto como auto detect. + context["env"]["contexts"][0]["sessionManagement"]["method"] = "autodetect" + print("Gerenciamento de sessão definido como auto detecção") + + request_text = replace_words(request_body, credential_login, credential_password) + + context["env"]["contexts"][0]["authentication"]["parameters"][ + "loginRequestBody" + ] = request_text + + # Validação de autenticação + context["env"]["contexts"][0]["authentication"]["verification"][ + "method" + ] = "autodetect" + + # Usuario + context["env"]["contexts"][0]["users"][0]["name"] = credential_login + context["env"]["contexts"][0]["users"][0]["credentials"][ + "password" + ] = credential_password + context["env"]["contexts"][0]["users"][0]["credentials"][ + "username" + ] = credential_login + + context["env"]["contexts"][0]["name"] = context_name + context["env"]["contexts"][0]["urls"] = base_url + context["env"]["contexts"][0]["includePaths"] = [] + # autenticação + context["env"]["contexts"][0]["authentication"]["parameters"][ + "loginPageUrl" + ] = base_url_login + context["env"]["contexts"][0]["authentication"]["parameters"][ + "loginRequestUrl" + ] = base_url_login + + # Criar novas entradas para a verificação logo abaixo de 'method' + verification_context = context["env"]["contexts"][0]["authentication"][ + "verification" + ] + new_verification_entries = CommentedMap() + new_verification_entries["method"] = "autodetect" + + if keywords.parameter_types_found["form"]: + context["env"]["contexts"][0]["authentication"]["method"] = "form" + + if keywords.parameter_types_found["json"]: + + new_verification_entries["method"] = "json" + + # Atualizar os dados de verificação com a nova ordem + context["env"]["contexts"][0]["authentication"][ + "verification" + ] = new_verification_entries + + +def update_jobs(jobs, new_context, new_user, new_url): + for job in jobs: + if "parameters" in job: + if "context" in job["parameters"]: + job["parameters"]["context"] = new_context + if "user" in job["parameters"]: + job["parameters"]["user"] = new_user + if "url" in job["parameters"]: + job["parameters"]["url"] = new_url diff --git a/Authentication_ZAP_GT_CRIVO/framework/elements/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/elements/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/elements/elements.py b/Authentication_ZAP_GT_CRIVO/framework/elements/elements.py new file mode 100644 index 0000000..0910dc8 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/elements/elements.py @@ -0,0 +1,92 @@ +from selenium import webdriver +import time +from selenium.webdriver.common.by import By +from selenium.common.exceptions import NoSuchAttributeException +from selenium.webdriver.common.keys import Keys +import re +from keywords import keywords + +DEFAULT_TIME = 3 +password_regex = r'\b[\w.-]*password[\w.-]*\b' + +def find_password(element): + return any(re.search(password_regex, str(value), re.IGNORECASE) for _, value in element.items()) + + +def filtered_dict(driver, evidence, struct_login): + # driver = webdriver.Firefox(service=s, options=firefox_options) + driver.get(evidence) + # pegar os elementos + time.sleep(DEFAULT_TIME) + elements = driver.find_elements(By.XPATH, "//*") + + # leve macro para burlar aplicações com uma janela de pop-up + if not elements: + webdriver.ActionChains(driver).send_keys(Keys.ESCAPE).perform() + elements = driver.find_elements(By.XPATH, "//*") + + array_elements = [] + + for element in elements: + element_info = {} + try: + attributes_to_gather = [ + ("name", element.get_attribute("name")), + ("type", element.get_attribute("type")), + ("placeholder", element.get_attribute("placeholder")), + ("id", element.get_attribute("id")), + ] + element_info = {key: value for key, value in attributes_to_gather if value} + except NoSuchAttributeException: + pass + + if element_info: + array_elements.append(element_info) + + print(array_elements) + print("\n") + # regex match + # Filtrando os dicionários + filtered_dictionaries = [] + for d in array_elements: + for _, value in d.items(): + # Verifica se o valor corresponde à regex + for regex in keywords.valid_values_elements: + if re.search(regex, value): + # se a correspondencia do elemento não estiver no dicionário, adicionar + if d not in filtered_dictionaries: + filtered_dictionaries.append(d) + break + + print(filtered_dictionaries) + + # Se após a filtragem o dicionario possuir elementos, criar as tuplas para usar na autenticação. + if filtered_dictionaries != []: + + if len(filtered_dictionaries) > 2: + login = [] + for index, element in enumerate(filtered_dictionaries): + if find_password(element): + if (filtered_dictionaries[index-1] in login): + pass + else: + login.append(filtered_dictionaries[index-1]) + if (element in login): + pass + else: + login.append(element) + filtered_dictionaries = login + + authentication_data = { + # palavras reservadas em italico + "name": tuple(element.get("name", "") for element in filtered_dictionaries), + "type": tuple(element.get("type", "") for element in filtered_dictionaries), + "placeholder": tuple( + element.get("placeholder", "") for element in filtered_dictionaries + ), + } + + dicionario_autenticado = {evidence: authentication_data} + struct_login.append(dicionario_autenticado) + + driver.quit() diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/keywords/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py b/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py new file mode 100644 index 0000000..911899e --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py @@ -0,0 +1,31 @@ +import os + +# Caminho da pasta onde estão os arquivos .txt +folder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "regex_words/") + +# Obtém todos os arquivos .txt na pasta +files = [f for f in os.listdir(folder_path) if f.endswith(".txt")] +files = sorted(files) + + +def read_files(file): + with open(f"{folder_path}{file}", "r") as f: + return [line.strip() for line in f] + + +keywords = {} + +for file in files: + key = file.split(".")[0] + keywords[key] = read_files(file) + + +invalid_values_urls = keywords["invalid_urls"] # invalid_urls.txt +type_elements = keywords["type_elements"] # type_elements.txt +valid_values_elements = keywords["valid_elements"] # valid_elements.txt +url_words = keywords["valid_urls"] # valid_urls.txt + + +password_field_identifiers = ("password", "senha", "txtPassword") # * +parameters_types = ("form", "json") +parameter_types_found = {"form": 0, "json": 0, "script": 0} diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt new file mode 100644 index 0000000..c9b5fa4 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt @@ -0,0 +1,2 @@ +\b[\w.-]*css[\w.-]*\b +\b[\w.-]*png[\w.-]*\b \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/type_elements.txt b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/type_elements.txt new file mode 100644 index 0000000..7117601 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/type_elements.txt @@ -0,0 +1,3 @@ +name +type +placeholder \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt new file mode 100644 index 0000000..c8157e6 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt @@ -0,0 +1,7 @@ +(?i)\b[\w.-]*senha[\w.-]*\b +(?i)\b[\w.-]*user[\w.-]*\b +(?i)\b[\w.-]*email[\w.-]*\b +(?i)\b[\w.-]*password[\w.-]*\b +(?i)\b[\w.-]*idLoginForm[\w.-]*\b +(?i)\b[\w.-]*uid[\w.-]*\b +(?i)\b[\w.-]*name[\w.-]*\b \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_urls.txt b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_urls.txt new file mode 100644 index 0000000..e9f12b6 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_urls.txt @@ -0,0 +1 @@ +login \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/main.py b/Authentication_ZAP_GT_CRIVO/framework/main.py new file mode 100644 index 0000000..4dd13b2 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/main.py @@ -0,0 +1,344 @@ +from context.context import build_yaml, update_jobs +from user_data.user_data import User +from urls_login import urls_login +from elements.elements import filtered_dict +from keywords import keywords +from autentication import autentication +from params import params +import time +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.common.keys import Keys +import os +from zapv2 import ZAPv2 +from ruamel.yaml import YAML +from pathlib import Path +import re + + +DIRECTORY_PATH = "/shared_data/input_config" + + +def wait_for_directory(directory_path=DIRECTORY_PATH, interval=1): + directory_path = Path(directory_path) + + print(f"Aguardando o diretório ou arquivos dentro de: {directory_path}") + while not (directory_path.exists() and any(directory_path.iterdir())): + time.sleep(interval) + + if directory_path.exists(): + if any(directory_path.iterdir()): + print(f"Arquivos encontrados no diretório: {directory_path}") + return + else: + print(f"Diretório encontrado, mas está vazio: {directory_path}") + return + + +def main(file_path): + + file_path = Path(file_path) + + json_data = file_path.read_text() + + # Validando os dados JSON usando model_validate_json + user = User.model_validate_json(json_data) + + print(user) + print("\n") + + # --------------------------------------------------- BROWSER CONFIG --------------------------------------------------- + FIREFOX = os.getenv("FIREFOX") + ZAP_API_KEY = os.getenv("ZAP_API_KEY") + ZAP_PROXY_ADDRESS = os.getenv("ZAP_PROXY_ADDRESS") + ZAP_PROXY_PORT = 8080 + + zap = ZAPv2( + apikey=ZAP_API_KEY, + proxies={ + "http": f"http://{ZAP_PROXY_ADDRESS}:{ZAP_PROXY_PORT}", + "https": f"http://{ZAP_PROXY_ADDRESS}:{ZAP_PROXY_PORT}", + }, + ) + proxyServerUrl = f"{ZAP_PROXY_ADDRESS}:{ZAP_PROXY_PORT}" + firefox_options = webdriver.FirefoxOptions() + firefox_options.add_argument("--ignore-certificate-errors") + firefox_options.add_argument(f"--proxy-server={proxyServerUrl}") + firefox_options.add_argument("--headless") + + s = Service(FIREFOX) + firefox_profile = webdriver.FirefoxProfile() + firefox_profile.set_preference("network.proxy.type", 1) + firefox_profile.set_preference("network.proxy.http", ZAP_PROXY_ADDRESS) + firefox_profile.set_preference("network.proxy.http_port", ZAP_PROXY_PORT) + firefox_profile.set_preference("network.proxy.ssl", ZAP_PROXY_ADDRESS) + firefox_profile.set_preference("network.proxy.ssl_port", ZAP_PROXY_PORT) + firefox_profile.update_preferences() + + firefox_options.profile = firefox_profile + + # ---------------------- BROWSER CONFIG ---------------------- + + """ + Criar uma flag que executa determinado trecho de codigo + se foi passado a url de login e caso contrário, executa + outro trecho de codigo. + """ + + """ + 0 - url de login não foi passada + 1 - url de login foi passada + + Valor será setado ao verificar se a estrutura + em user_data é vazia ou não. + """ + if user.url_login == "": + FLAG_LOGIN = 0 + print("url de login não foi setada") + else: + FLAG_LOGIN = 1 + print("url de login setada") + + DEFAULT_TIME = 3 + + zap.core.new_session(overwrite=True) + + struct_login = [] + + driver = webdriver.Firefox(service=s, options=firefox_options) + if FLAG_LOGIN: + filtered_dict(driver, user.url_login, struct_login) + + time.sleep(DEFAULT_TIME) + + print(struct_login) + if FLAG_LOGIN == 0: + # Primeira url será utilizada para realizar o crawler na página com o spider + driver.get(user.url[0]) + + time.sleep(DEFAULT_TIME) + scanid = zap.spider.scan(user.url[0]) + + time.sleep(DEFAULT_TIME) + while int(zap.spider.status(scanid)) < 100: + # Loop until the spider has finished + print("Spider progress %: {}".format(zap.spider.status(scanid))) + time.sleep(DEFAULT_TIME) + + print("Spider completed") + + driver.quit() + + urls_found = zap.core.urls() + + print(urls_found) + general_results = urls_login.find_urls_login(urls_found, keywords.url_words) + print(general_results) + evidences_urls_login = [] + for url in general_results: + if not any( + re.search(pattern, url) for pattern in keywords.invalid_values_urls + ): # match das palavras do array (regex group) + evidences_urls_login.append(url) + + print(evidences_urls_login) + for evidence in evidences_urls_login: + driver = webdriver.Firefox(service=s, options=firefox_options) + filtered_dict(driver, evidence, struct_login) + + print(struct_login) + + print(len(struct_login)) + + # Autenticação + "-----------------------------------------------------------------------------------------------------------------------------------" + + zap.core.new_session(overwrite=True) + + # Inicializar o driver do Selenium + # Criar função + # conteudo da struct login + driver = webdriver.Firefox(service=s, options=firefox_options) + if len(struct_login) == 1: + url_authentication = list(struct_login[0].keys())[0] + driver.get(url_authentication) + elif len(struct_login) > 1: + pass + else: + # lançar assert + pass + + time.sleep(DEFAULT_TIME) + + for authentication_dictionary in struct_login: + for _, elements in authentication_dictionary.items(): + for type, field in elements.items(): + login_element, password_element = field + print(login_element, password_element) + # Encontrar os campos de login e senha (mudar) + username_field = autentication.find_element_by_attribute( + driver, type, login_element + ) + if username_field is None: + continue # Se não encontrou o campo de username, pula para o próximo elemento + password_field = autentication.find_element_by_attribute( + driver, type, password_element + ) + if password_field is None: + continue # Se não encontrou o campo de password, pula para o próximo elemento + + username_field.send_keys(user.login) + password_field.send_keys(user.password) + + time.sleep(DEFAULT_TIME) + + # Tentar enviar o formulário + try: + password_field.send_keys(Keys.RETURN) + + time.sleep(1) + autentication.validate_by_attribute(driver, type, login_element) + except Exception: + pass + + time.sleep(DEFAULT_TIME) + break + + time.sleep(DEFAULT_TIME) + # Finalizar o driver + driver.quit() + + # "-----------------------------------------------------------------------------------------------------------------------------------" + + # Pegar todos os alertas da aplicação + alerts = zap.alert.alerts() + print(f"Alerts: {len(alerts)}") + print( + alerts + ) # se não retornar alerta, lançar assert informando a possibilidade do scan passivo ter parado de funcionar + # Alertas no momento que o post foi passado, isso inclui as aplicações que foram passadas de forma errada + # Lembrar que pegar o ultimo ainda é o mais sensato (evitar casos que manda a credencial que queremos, mas de uma forma errada) tem que testar. + alert_autentication = [] + # Depurar para encontrar outras formas de identificação de autenticação pelo proxy (rodar pelo UI - getboo, gruyere) + for alert in alerts: + if alert["name"] == "Authentication Request Identified": + # alerta de autenticação + alert_autentication.append(alert) + + # Pegar os ids de todas as mensagens de autenticação, ou de possiveis autenticação + messageId = [] + for alert in alert_autentication: + messageId.append(alert["messageId"]) + + # verifica todos os alertas candidatos e filtra o que foi passado as credenciais de forma correta. + for id in messageId: + request_autenticated = zap.core.message(id) + # print(request_autenticated) + if autentication.check_credentials( + request_autenticated["requestBody"], + user.login, + user.password, + ): + print( + f'O response da aplicação no momento do login é:\n{request_autenticated["requestBody"]}' + ) + break + + request_body = request_autenticated["requestBody"] + + # Verifica que o zap encontrou o gerenciamento de sessão + alert_count = len( + [ + alert + for alert in alerts + if alert["name"] == "Session Management Response Identified" + ] + ) + + BASE_CONTEXT = "base_context/context_base.yaml" + + params.Define_type_authentication( + zap, keywords.parameter_types_found, url_authentication, request_body + ) + + yaml = YAML() + + with open(BASE_CONTEXT, "r") as file: + context = yaml.load(file) + + build_yaml( + context, + alert_count, + request_body, + user.login, + user.password, + user.context, + user.url, + url_authentication, + ) + + new_context = user.context + new_user = user.login + new_url = url_authentication + + # Chama a função para atualizar os valores + update_jobs(context["jobs"], new_context, new_user, new_url) + + OUTPUT = f"context_{user.context}" + + # Salvar em um novo arquivo YAML + with open(f"../shared_data/{OUTPUT}.yaml", "w") as file: + yaml.dump(context, file) + + print("Arquivo YAML modificado e salvo com sucesso !") + + time.sleep(DEFAULT_TIME) + + # mudar para o path da raiz + output_yaml = f"../shared_data/{OUTPUT}.yaml" + print(output_yaml) + + scanid = zap.automation.run_plan(output_yaml, ZAP_API_KEY) + + print(f"scanid plano = {scanid}") + + while zap.automation.plan_progress(scanid)["finished"] == "": + time.sleep(1) + + print(zap.automation.plan_progress(scanid)["finished"]) + + # rodar o spider autenticado + id_context = zap.context.context(user.context)["id"] + print(f"nome contexto = {user.context} id_context = {id_context}") + + id_user = zap.users.users_list(id_context)[0]["id"] + print(f"id_user = {id_user}") + + scanid = zap.spider.scan_as_user(id_context, id_user, apikey=ZAP_API_KEY) + print(f"scanid spider = {scanid}") + + while int(zap.spider.status(scanid)) < 100: + # Loop until the spider has finished + print("Spider progress %: {}".format(zap.spider.status(scanid))) + time.sleep(DEFAULT_TIME) + + time.sleep(1) + FILE_CONTEXT = f"{user.context}.context" + + # mudar para o path da raiz + print( + zap.context.export_context( + user.context, f"/shared_data/{FILE_CONTEXT}", apikey=ZAP_API_KEY + ) + ) + + +if __name__ == "__main__": + wait_for_directory() + arq_config = os.listdir(DIRECTORY_PATH) + for arq in arq_config: + arq = os.path.join(DIRECTORY_PATH, arq) + try: + main(arq) + except: + pass diff --git a/Authentication_ZAP_GT_CRIVO/framework/params/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/params/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/params/params.py b/Authentication_ZAP_GT_CRIVO/framework/params/params.py new file mode 100644 index 0000000..83dfda5 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/params/params.py @@ -0,0 +1,30 @@ +import re +from keywords import keywords + + +def Define_type_authentication( + zap, parameter_types_found, BASE_URL_LOGIN, request_body +): + session_parameters = zap.params.params(BASE_URL_LOGIN) + session_parameters = session_parameters[0]["Parameter"] + + for params in session_parameters: + name = params.get("name") + param_type = params.get("type") + + if ( + name in keywords.password_field_identifiers + and param_type in keywords.parameters_types + ): + parameter_types_found[param_type] = 1 + break + + all_zero = all(value == 0 for value in parameter_types_found.values()) + + if all_zero: + r = r"{\s*[^{}]*\s*}" + if re.search(r, request_body): + parameter_types_found["json"] = 1 + + else: + pass diff --git a/Authentication_ZAP_GT_CRIVO/framework/requirements.txt b/Authentication_ZAP_GT_CRIVO/framework/requirements.txt new file mode 100644 index 0000000..0eae7e9 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/requirements.txt @@ -0,0 +1,29 @@ +annotated-types==0.7.0 +attrs==23.2.0 +beautifulsoup4==4.12.3 +bs4==0.0.2 +certifi==2024.7.4 +charset-normalizer==3.3.2 +exceptiongroup==1.2.1 +h11==0.14.0 +idna==3.7 +outcome==1.3.0.post0 +pydantic==2.8.2 +pydantic_core==2.20.1 +PySocks==1.7.1 +PyYAML==6.0.1 +requests==2.32.3 +ruamel.yaml==0.18.6 +ruamel.yaml.clib==0.2.8 +selenium==4.22.0 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +soupsieve==2.5 +trio==0.26.0 +trio-websocket==0.11.1 +typing_extensions==4.12.2 +urllib3==2.2.2 +websocket-client==1.8.0 +wsproto==1.2.0 +zaproxy==0.3.2 diff --git a/Authentication_ZAP_GT_CRIVO/framework/urls_login/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/urls_login/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py b/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py new file mode 100644 index 0000000..c6ac3c0 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py @@ -0,0 +1,15 @@ +def find_urls_login(lista_strings, lista_palavras): + # Lista para armazenar as strings que contêm alguma das palavras + urls_encontradas = [] + + # Itera sobre cada string na lista de strings + for string in lista_strings: + # Itera sobre cada palavra na lista de palavras + for palavra in lista_palavras: + # Verifica se a palavra está presente na string + if palavra in string: + # Adiciona a string à lista de resultados e sai do loop interno + urls_encontradas.append(string) + break + + return urls_encontradas diff --git a/Authentication_ZAP_GT_CRIVO/framework/user_data/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/user_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py b/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py new file mode 100644 index 0000000..dba9ec7 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel +from typing import List, Optional +from pathlib import Path + + +class User(BaseModel): + context: str + url: List[str] + url_login: Optional[str] = None + exclude_urls: List[str] = [] + report_title: Optional[str] = "Report" + login: str + password: str diff --git a/Authentication_ZAP_GT_CRIVO/server/app.py b/Authentication_ZAP_GT_CRIVO/server/app.py new file mode 100644 index 0000000..40f24be --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/server/app.py @@ -0,0 +1,74 @@ +from flask import Flask, render_template, request, redirect, url_for, send_from_directory +import os + +# Configurações básicas +app = Flask(__name__) + +# Caminho base para a pasta de upload +# BASE_DIR = 'shared_data' +app.config['UPLOAD_FOLDER'] = os.getenv('UPLOAD_FOLDER', '/shared_data') + +# verifica se o diretório de upload existe +if not os.path.exists(app.config['UPLOAD_FOLDER']): + os.makedirs(app.config['UPLOAD_FOLDER']) + +# Função para listar arquivos e diretórios em um caminho dado +def list_files_in_directory(path): + try: + # Listar arquivos e diretórios + items = os.listdir(path) + files = [] + directories = [] + + for item in items: + full_path = os.path.join(path, item) + if os.path.isdir(full_path): + directories.append(item) + else: + files.append(item) + + return directories, files + except FileNotFoundError: + return [], [] + +# Página principal com navegação por diretórios +@app.route('/', defaults={'path': ''}) +@app.route('/') +def index(path): + full_path = os.path.join(app.config['UPLOAD_FOLDER'], path) + + if not os.path.exists(full_path): + return 'Diretório não encontrado.', 404 + + # Listar diretórios e arquivos + directories, files = list_files_in_directory(full_path) + + # Retorna a página com os arquivos e subdiretórios + return render_template('index.html', directories=directories, files=files, current_path=path) + +# Rota para download de arquivos +@app.route('/uploads//') +@app.route('/uploads/', defaults={'path': ''}) +def download_file(path, filename): + full_path = os.path.join(app.config['UPLOAD_FOLDER'], path) + return send_from_directory(full_path, filename) + +# Rota para deletar um arquivo + +# Rota para fazer upload de arquivos +@app.route('/upload', methods=['POST']) +def upload_file(): + current_path = request.form.get('path', '') # Captura o caminho atual do diretório + if 'file' not in request.files: + return 'Nenhum arquivo encontrado.' + file = request.files['file'] + if file.filename == '': + return 'Nenhum arquivo selecionado.' + if file: + # Caminho completo para salvar o arquivo no diretório atual + upload_path = os.path.join(app.config['UPLOAD_FOLDER'], current_path) + file.save(os.path.join(upload_path, file.filename)) + return redirect(url_for('index', path=current_path)) + +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', port=8000) diff --git a/Authentication_ZAP_GT_CRIVO/server/dockerfile b/Authentication_ZAP_GT_CRIVO/server/dockerfile new file mode 100644 index 0000000..e947865 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/server/dockerfile @@ -0,0 +1,28 @@ +# Use a imagem oficial do Python como base +FROM python:3.12.5-slim + +# Define o diretório de trabalho dentro do contêiner +WORKDIR /app + +# Copia o arquivo requirements.txt para o contêiner +COPY requirements.txt . + +# Instala as dependências Python +RUN pip install --no-cache-dir -r requirements.txt + +# Copia o restante do código da aplicação para o contêiner +COPY . . + +# Configura a variável de ambiente do Flask +ENV FLASK_APP=app.py +ENV FLASK_RUN_HOST=0.0.0.0 +ENV FLASK_RUN_PORT=8000 + +# Define o diretório de upload para a aplicação Flask (apontando para o volume compartilhado) +ENV UPLOAD_FOLDER=/shared_data + +# Expõe a porta 8000 para acesso externo +EXPOSE 8000 + +# Comando para iniciar a aplicação Flask +CMD ["flask", "run"] diff --git a/Authentication_ZAP_GT_CRIVO/server/requirements.txt b/Authentication_ZAP_GT_CRIVO/server/requirements.txt new file mode 100644 index 0000000..1ac347b --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/server/requirements.txt @@ -0,0 +1,7 @@ +blinker==1.8.2 +click==8.1.7 +Flask==3.0.3 +itsdangerous==2.2.0 +Jinja2==3.1.4 +MarkupSafe==2.1.5 +Werkzeug==3.0.3 \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/server/templates/index.html b/Authentication_ZAP_GT_CRIVO/server/templates/index.html new file mode 100644 index 0000000..a683fde --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/server/templates/index.html @@ -0,0 +1,35 @@ + + + + + + Servidor de Arquivos + + +

Upload e Download de Arquivos

+ + +
+ + + +
+ +
    + {% if current_path %} + + voltar + {% endif %} + {% for directory in directories %} +
  • {{ directory }}/
  • + {% endfor %} +
+ +

Arquivos Disponíveis para Download

+
    + {% for file in files %} +
  • {{ file }}
  • + {% endfor %} +
+ + From 1ab1fa3dbc86cebbb08c5584d35e287f4640673f Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Mon, 23 Dec 2024 14:46:28 -0300 Subject: [PATCH 02/13] update: refactor authentication.py --- .../framework/autentication/autentication.py | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py b/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py index 3294c5c..ce41228 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py +++ b/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py @@ -1,10 +1,14 @@ from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.by import By +import logging +logging.basicConfig(level=logging.INFO, format='%(message)s') def find_element_by_attribute(driver, attribute, value): """ - Encontra um elemento no site com base no atributo especificado. + + Find an element on the website based on the specified attribute. + """ try: if attribute == "name": @@ -17,15 +21,19 @@ def find_element_by_attribute(driver, attribute, value): ) else: raise ValueError(f"Atributo inválido: {attribute}") - print(f"Elemento encontrado com sucesso.") + logging.info(f"Element found successfully.") return element except NoSuchElementException: - print(f"Elemento não encontrado com base no atributo '{attribute}'.") + logging.info(f"Element not found based on attribute '{attribute}'.") return None -# Função para validar se o elemento está visível def validate_by_attribute(driver, attribute, value): + """ + + Validate if an element with the given attribute and value is displayed on the page. + + """ try: if attribute == "name": if driver.find_element(By.NAME, value).is_displayed(): @@ -40,19 +48,22 @@ def validate_by_attribute(driver, attribute, value): By.CSS_SELECTOR, f"input[placeholder='{value}']" ).is_displayed(): pass - print("failed to log in !") + logging.info("Failed to log in!") driver.refresh() return except: - print("login done !") + logging.info("Login successful!") def check_credentials(request, credencial_login, credencial_passsword): + """ + + This function receives a string and two credentials and checks if both credentials are present in the request body string. + The function can check if the credentials are the same by verifying the number of occurrences in the string. + And it checks if both credentials are present in the string. - # Verifica os casos em que o login e senha são iguais (admin, admin) + """ if credencial_login == credencial_passsword: - # Conta o número de ocorrências da palavra no texto return request.count(credencial_login) >= 2 else: - # Verifica se ambas as palavras estão presentes no texto return credencial_login in request and credencial_passsword in request From 299431ef2f5171b6379e32066f9a4852769bca07 Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Mon, 23 Dec 2024 14:47:57 -0300 Subject: [PATCH 03/13] update: refactor context.py --- .../framework/context/context.py | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/Authentication_ZAP_GT_CRIVO/framework/context/context.py b/Authentication_ZAP_GT_CRIVO/framework/context/context.py index 705463b..76fd595 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/context/context.py +++ b/Authentication_ZAP_GT_CRIVO/framework/context/context.py @@ -1,7 +1,10 @@ +import logging + from ruamel.yaml.comments import CommentedMap from keywords import keywords + def replace_words( text, login, @@ -9,6 +12,12 @@ def replace_words( credential_login="{%username%}", credential_password="{%password%}", ): + """ + + This function is responsible for replacing the login and password keywords with the default credentials accepted by ZAP. + The replace_words function has a case where it treats %40 as @, which was highlighted in some tests where the request with @ was returned by replacing it with the characters %40. + + """ if login == password: new_request = text.replace(login, credential_login, 1).replace( password, credential_password, 1 @@ -32,11 +41,15 @@ def build_yaml( base_url, base_url_login, ): - + """ + + This function receives the context information as a parameter and constructs the YAML file responsible for the application's automation plan. + + """ if alert_count > 0: # marcar contexto como auto detect. context["env"]["contexts"][0]["sessionManagement"]["method"] = "autodetect" - print("Gerenciamento de sessão definido como auto detecção") + logging.info("Session management set to auto-detection") request_text = replace_words(request_body, credential_login, credential_password) @@ -44,12 +57,12 @@ def build_yaml( "loginRequestBody" ] = request_text - # Validação de autenticação + # Authentication validation context["env"]["contexts"][0]["authentication"]["verification"][ "method" ] = "autodetect" - # Usuario + # User crendetials context["env"]["contexts"][0]["users"][0]["name"] = credential_login context["env"]["contexts"][0]["users"][0]["credentials"][ "password" @@ -61,7 +74,7 @@ def build_yaml( context["env"]["contexts"][0]["name"] = context_name context["env"]["contexts"][0]["urls"] = base_url context["env"]["contexts"][0]["includePaths"] = [] - # autenticação + # Authentication context["env"]["contexts"][0]["authentication"]["parameters"][ "loginPageUrl" ] = base_url_login @@ -69,7 +82,6 @@ def build_yaml( "loginRequestUrl" ] = base_url_login - # Criar novas entradas para a verificação logo abaixo de 'method' verification_context = context["env"]["contexts"][0]["authentication"][ "verification" ] @@ -89,7 +101,13 @@ def build_yaml( ] = new_verification_entries + def update_jobs(jobs, new_context, new_user, new_url): + """ + + Instantiates jobs defined by default in the application's automation plan file. + + """ for job in jobs: if "parameters" in job: if "context" in job["parameters"]: From 61c99afdc364f046e2d9cd9d8428360a1c6499d8 Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Mon, 23 Dec 2024 14:50:19 -0300 Subject: [PATCH 04/13] update: refactor module keywords, merge elements.py in keywords --- .../framework/elements/__init__.py | 0 .../{elements => keywords}/elements.py | 64 +++++++++++-------- .../framework/keywords/keywords.py | 60 +++++++++-------- .../keywords/regex_words/invalid_urls.txt | 4 +- .../keywords/regex_words/valid_elements.txt | 3 +- 5 files changed, 74 insertions(+), 57 deletions(-) delete mode 100644 Authentication_ZAP_GT_CRIVO/framework/elements/__init__.py rename Authentication_ZAP_GT_CRIVO/framework/{elements => keywords}/elements.py (63%) diff --git a/Authentication_ZAP_GT_CRIVO/framework/elements/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/elements/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Authentication_ZAP_GT_CRIVO/framework/elements/elements.py b/Authentication_ZAP_GT_CRIVO/framework/keywords/elements.py similarity index 63% rename from Authentication_ZAP_GT_CRIVO/framework/elements/elements.py rename to Authentication_ZAP_GT_CRIVO/framework/keywords/elements.py index 0910dc8..b142050 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/elements/elements.py +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/elements.py @@ -1,29 +1,45 @@ -from selenium import webdriver +import logging +import re import time -from selenium.webdriver.common.by import By + +from selenium import webdriver from selenium.common.exceptions import NoSuchAttributeException +from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys -import re -from keywords import keywords -DEFAULT_TIME = 3 -password_regex = r'\b[\w.-]*password[\w.-]*\b' +from keywords.keywords import RegexSets + + +logging.basicConfig(level=logging.INFO, format='%(message)s') + +DEFAULT_TIME = 1 +regex_sets = RegexSets() +password_regex = regex_sets.valid_values_elements[0] +valid_values_elements = regex_sets.valid_values_elements def find_password(element): return any(re.search(password_regex, str(value), re.IGNORECASE) for _, value in element.items()) def filtered_dict(driver, evidence, struct_login): - # driver = webdriver.Firefox(service=s, options=firefox_options) + """ + + The function will receive an instance of a page and an empty structure that will be filled. + For each "evidence" (page with a potential authentication form), the function will extract all elements from the page and use regex to verify if the elements are indeed authentication fields. + The function will filter the elements from the page and return a dictionary with the filtered elements. + This function runs in a loop, meaning there can be multiple pages that are not candidates in the queue. Therefore, the page does not use the ZAP proxy. Because of this, the driver is closed as soon as the elements are extracted. + + """ + driver.get(evidence) - # pegar os elementos time.sleep(DEFAULT_TIME) elements = driver.find_elements(By.XPATH, "//*") - # leve macro para burlar aplicações com uma janela de pop-up if not elements: + elements_popup = driver.find_elements(By.XPATH, "//*") webdriver.ActionChains(driver).send_keys(Keys.ESCAPE).perform() elements = driver.find_elements(By.XPATH, "//*") + elements.extend(elements_popup) array_elements = [] @@ -42,26 +58,20 @@ def filtered_dict(driver, evidence, struct_login): if element_info: array_elements.append(element_info) - - print(array_elements) - print("\n") - # regex match - # Filtrando os dicionários + logging.info(array_elements) + filtered_dictionaries = [] - for d in array_elements: - for _, value in d.items(): - # Verifica se o valor corresponde à regex - for regex in keywords.valid_values_elements: + for einfo in array_elements: + for _, value in einfo.items(): + for regex in valid_values_elements: if re.search(regex, value): - # se a correspondencia do elemento não estiver no dicionário, adicionar - if d not in filtered_dictionaries: - filtered_dictionaries.append(d) + if einfo not in filtered_dictionaries: + filtered_dictionaries.append(einfo) break - print(filtered_dictionaries) - - # Se após a filtragem o dicionario possuir elementos, criar as tuplas para usar na autenticação. - if filtered_dictionaries != []: + logging.info(filtered_dictionaries) + + if filtered_dictionaries: if len(filtered_dictionaries) > 2: login = [] @@ -78,7 +88,6 @@ def filtered_dict(driver, evidence, struct_login): filtered_dictionaries = login authentication_data = { - # palavras reservadas em italico "name": tuple(element.get("name", "") for element in filtered_dictionaries), "type": tuple(element.get("type", "") for element in filtered_dictionaries), "placeholder": tuple( @@ -88,5 +97,6 @@ def filtered_dict(driver, evidence, struct_login): dicionario_autenticado = {evidence: authentication_data} struct_login.append(dicionario_autenticado) - + + driver.quit() diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py b/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py index 911899e..d47f652 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py @@ -1,31 +1,35 @@ import os -# Caminho da pasta onde estão os arquivos .txt -folder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "regex_words/") - -# Obtém todos os arquivos .txt na pasta -files = [f for f in os.listdir(folder_path) if f.endswith(".txt")] -files = sorted(files) - - -def read_files(file): - with open(f"{folder_path}{file}", "r") as f: - return [line.strip() for line in f] - - -keywords = {} - -for file in files: - key = file.split(".")[0] - keywords[key] = read_files(file) - - -invalid_values_urls = keywords["invalid_urls"] # invalid_urls.txt -type_elements = keywords["type_elements"] # type_elements.txt -valid_values_elements = keywords["valid_elements"] # valid_elements.txt -url_words = keywords["valid_urls"] # valid_urls.txt - - -password_field_identifiers = ("password", "senha", "txtPassword") # * +class RegexSets: + """ + + The class is responsible for loading the regex files that will be used to identify elements on the page. + + """ + def __init__(self): + self.folder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "regex_words/") + self.keywords = {} + self.load_files() + + def load_files(self): + # Obtém todos os arquivos .txt na pasta + files = [f for f in os.listdir(self.folder_path) if f.endswith(".txt")] + files = sorted(files) + + for file in files: + key = file.split(".")[0] + self.keywords[key] = self.read_files(file) + + self.invalid_values_urls = self.keywords.get("invalid_urls", []) + self.type_elements = self.keywords.get("type_elements", []) + self.valid_values_elements = self.keywords.get("valid_elements", []) + self.url_words = self.keywords.get("valid_urls", []) + + def read_files(self, file): + with open(f"{self.folder_path}{file}", "r") as f: + return [line.strip() for line in f] + + +password_field_identifiers = ("password", "senha", "txtPassword") parameters_types = ("form", "json") -parameter_types_found = {"form": 0, "json": 0, "script": 0} +parameter_types_found = {"form": 0, "json": 0, "script": 0} \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt index c9b5fa4..9b685a6 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt @@ -1,2 +1,4 @@ \b[\w.-]*css[\w.-]*\b -\b[\w.-]*png[\w.-]*\b \ No newline at end of file +\b[\w.-]*png[\w.-]*\b +\b[\w.-]*pdf[\w.-]*\b +\b[\w.-]*jpg[\w.-]*\b diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt index c8157e6..5ed9aa8 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt @@ -1,7 +1,8 @@ +(?i)\b[\w.-]*password[\w.-]*\b (?i)\b[\w.-]*senha[\w.-]*\b (?i)\b[\w.-]*user[\w.-]*\b +(?i)\b[\w.-]*login[\w.-]*\b (?i)\b[\w.-]*email[\w.-]*\b -(?i)\b[\w.-]*password[\w.-]*\b (?i)\b[\w.-]*idLoginForm[\w.-]*\b (?i)\b[\w.-]*uid[\w.-]*\b (?i)\b[\w.-]*name[\w.-]*\b \ No newline at end of file From 0f8635db979aecf0bb7ef2bf7596d0e6603d0850 Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Mon, 23 Dec 2024 14:51:22 -0300 Subject: [PATCH 05/13] update: refactor params.py --- .../framework/params/params.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Authentication_ZAP_GT_CRIVO/framework/params/params.py b/Authentication_ZAP_GT_CRIVO/framework/params/params.py index 83dfda5..b2cb4ed 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/params/params.py +++ b/Authentication_ZAP_GT_CRIVO/framework/params/params.py @@ -2,9 +2,15 @@ from keywords import keywords -def Define_type_authentication( + +def Define_authentication_type( zap, parameter_types_found, BASE_URL_LOGIN, request_body ): + """ + + Function to define the authentication type based on the request body of the application: json, form, etc. + + """ session_parameters = zap.params.params(BASE_URL_LOGIN) session_parameters = session_parameters[0]["Parameter"] @@ -25,6 +31,3 @@ def Define_type_authentication( r = r"{\s*[^{}]*\s*}" if re.search(r, request_body): parameter_types_found["json"] = 1 - - else: - pass From 57355f0b9b9d5f6d95e67aa8f79e4a983393c412 Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Mon, 23 Dec 2024 14:52:03 -0300 Subject: [PATCH 06/13] update: refactor urls_login.py --- .../framework/urls_login/urls_login.py | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py b/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py index c6ac3c0..b806a8c 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py +++ b/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py @@ -1,15 +1,18 @@ -def find_urls_login(lista_strings, lista_palavras): - # Lista para armazenar as strings que contêm alguma das palavras - urls_encontradas = [] +def find_urls_login(list_of_strings, lista_of_words): + """ + + List to store the strings that contain any of the words + Iterate over each string in the list of strings + Iterate over each word in the list of words + Check if the word is present in the string + Add the string to the list of results and exit the inner loop + + """ + found_urls = [] - # Itera sobre cada string na lista de strings - for string in lista_strings: - # Itera sobre cada palavra na lista de palavras - for palavra in lista_palavras: - # Verifica se a palavra está presente na string - if palavra in string: - # Adiciona a string à lista de resultados e sai do loop interno - urls_encontradas.append(string) + for string in list_of_strings: + for word in lista_of_words: + if word in string: + found_urls.append(string) break - - return urls_encontradas + return found_urls From 5cea303cf6b4d5940e66c6bf0bc5b5be248b3da8 Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Mon, 23 Dec 2024 14:52:43 -0300 Subject: [PATCH 07/13] update: refactor user_data.py --- .../framework/user_data/user_data.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py b/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py index dba9ec7..be74b79 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py +++ b/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py @@ -3,7 +3,12 @@ from pathlib import Path -class User(BaseModel): +class Configuration(BaseModel): + """ + + Class used to parse the JSON that the user must provide with the application data to perform the automation. + + """ context: str url: List[str] url_login: Optional[str] = None From b92217c1dd197401072b3029dc26a017e310f96e Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Mon, 23 Dec 2024 14:55:17 -0300 Subject: [PATCH 08/13] update: fix dockerfile --- Authentication_ZAP_GT_CRIVO/framework/Dockerfile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Authentication_ZAP_GT_CRIVO/framework/Dockerfile b/Authentication_ZAP_GT_CRIVO/framework/Dockerfile index 765a98b..5027924 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/Dockerfile +++ b/Authentication_ZAP_GT_CRIVO/framework/Dockerfile @@ -1,4 +1,3 @@ -# Base image - colocar versão mais recente FROM python:3.12.5-slim # Install necessary dependencies @@ -28,11 +27,9 @@ RUN pip install --no-cache-dir -r requirements.txt COPY . /app WORKDIR /app - -# Set environment variable for ZAP API key - tirar daqui +# Set environment variables ENV FIREFOX="/usr/local/bin/geckodriver" ENV ZAP_PROXY_ADDRESS="zaproxy" -# Command to start the application +# Command to start the application after zap started CMD sleep 30 && python3 main.py -# CMD sleep 10000 \ No newline at end of file From 90537d21ccb7032eb421886042a69dd9c6940108 Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Mon, 23 Dec 2024 14:56:28 -0300 Subject: [PATCH 09/13] update: refactor main.py --- Authentication_ZAP_GT_CRIVO/framework/main.py | 263 +++++++----------- 1 file changed, 96 insertions(+), 167 deletions(-) diff --git a/Authentication_ZAP_GT_CRIVO/framework/main.py b/Authentication_ZAP_GT_CRIVO/framework/main.py index 4dd13b2..267d637 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/main.py +++ b/Authentication_ZAP_GT_CRIVO/framework/main.py @@ -1,38 +1,29 @@ -from context.context import build_yaml, update_jobs -from user_data.user_data import User -from urls_login import urls_login -from elements.elements import filtered_dict -from keywords import keywords -from autentication import autentication -from params import params +import os +import logging +import re import time +from pathlib import Path + +from ruamel.yaml import YAML from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.keys import Keys -import os from zapv2 import ZAPv2 -from ruamel.yaml import YAML -from pathlib import Path -import re - - -DIRECTORY_PATH = "/shared_data/input_config" - -def wait_for_directory(directory_path=DIRECTORY_PATH, interval=1): - directory_path = Path(directory_path) +from autentication import autentication +from context.context import build_yaml, update_jobs +from keywords.elements import filtered_dict +from keywords.keywords import RegexSets, parameter_types_found +from params import params +from urls_login import urls_login +from user_data.user_data import Configuration - print(f"Aguardando o diretório ou arquivos dentro de: {directory_path}") - while not (directory_path.exists() and any(directory_path.iterdir())): - time.sleep(interval) +""" +The default time is used to wit while selenium or zap is working, this delay is a reng sefety to wait a frameowrk to finish the work. +""" +DEFAULT_TIME = 3 - if directory_path.exists(): - if any(directory_path.iterdir()): - print(f"Arquivos encontrados no diretório: {directory_path}") - return - else: - print(f"Diretório encontrado, mas está vazio: {directory_path}") - return +logging.basicConfig(level=logging.INFO, format='%(message)s') def main(file_path): @@ -41,17 +32,22 @@ def main(file_path): json_data = file_path.read_text() - # Validando os dados JSON usando model_validate_json - user = User.model_validate_json(json_data) + user = Configuration.model_validate_json(json_data) + + logging.info("User configuration loaded successfully.") + + regex_sets = RegexSets() - print(user) - print("\n") + invalid_values_urls = regex_sets.invalid_values_urls + url_words = regex_sets.url_words - # --------------------------------------------------- BROWSER CONFIG --------------------------------------------------- FIREFOX = os.getenv("FIREFOX") ZAP_API_KEY = os.getenv("ZAP_API_KEY") + if FIREFOX is None or ZAP_API_KEY is None: + raise EnvironmentError("FIREFOX e ZAP_API_KEY devem estar definidos nas variáveis de ambiente.") + ZAP_PROXY_ADDRESS = os.getenv("ZAP_PROXY_ADDRESS") - ZAP_PROXY_PORT = 8080 + ZAP_PROXY_PORT = int(os.getenv("ZAP_PROXY_PORT")) zap = ZAPv2( apikey=ZAP_API_KEY, @@ -60,10 +56,10 @@ def main(file_path): "https": f"http://{ZAP_PROXY_ADDRESS}:{ZAP_PROXY_PORT}", }, ) - proxyServerUrl = f"{ZAP_PROXY_ADDRESS}:{ZAP_PROXY_PORT}" + proxy_server_url = f"{ZAP_PROXY_ADDRESS}:{ZAP_PROXY_PORT}" firefox_options = webdriver.FirefoxOptions() firefox_options.add_argument("--ignore-certificate-errors") - firefox_options.add_argument(f"--proxy-server={proxyServerUrl}") + firefox_options.add_argument(f"--proxy-server={proxy_server_url}") firefox_options.add_argument("--headless") s = Service(FIREFOX) @@ -77,29 +73,12 @@ def main(file_path): firefox_options.profile = firefox_profile - # ---------------------- BROWSER CONFIG ---------------------- - - """ - Criar uma flag que executa determinado trecho de codigo - se foi passado a url de login e caso contrário, executa - outro trecho de codigo. - """ - - """ - 0 - url de login não foi passada - 1 - url de login foi passada - - Valor será setado ao verificar se a estrutura - em user_data é vazia ou não. - """ if user.url_login == "": FLAG_LOGIN = 0 - print("url de login não foi setada") + logging.info("url de login não foi setada") else: FLAG_LOGIN = 1 - print("url de login setada") - - DEFAULT_TIME = 3 + logging.info("url de login setada") zap.core.new_session(overwrite=True) @@ -108,81 +87,36 @@ def main(file_path): driver = webdriver.Firefox(service=s, options=firefox_options) if FLAG_LOGIN: filtered_dict(driver, user.url_login, struct_login) - time.sleep(DEFAULT_TIME) - print(struct_login) if FLAG_LOGIN == 0: - # Primeira url será utilizada para realizar o crawler na página com o spider - driver.get(user.url[0]) - - time.sleep(DEFAULT_TIME) - scanid = zap.spider.scan(user.url[0]) - - time.sleep(DEFAULT_TIME) - while int(zap.spider.status(scanid)) < 100: - # Loop until the spider has finished - print("Spider progress %: {}".format(zap.spider.status(scanid))) - time.sleep(DEFAULT_TIME) - - print("Spider completed") - - driver.quit() - - urls_found = zap.core.urls() - - print(urls_found) - general_results = urls_login.find_urls_login(urls_found, keywords.url_words) - print(general_results) - evidences_urls_login = [] - for url in general_results: - if not any( - re.search(pattern, url) for pattern in keywords.invalid_values_urls - ): # match das palavras do array (regex group) - evidences_urls_login.append(url) - - print(evidences_urls_login) - for evidence in evidences_urls_login: - driver = webdriver.Firefox(service=s, options=firefox_options) - filtered_dict(driver, evidence, struct_login) - - print(struct_login) - - print(len(struct_login)) - - # Autenticação - "-----------------------------------------------------------------------------------------------------------------------------------" + struct_login = spider_scan(driver, zap, user, urls_login, url_words, invalid_values_urls, s, firefox_options, struct_login) + + logging.info("Struct login: %s", struct_login) zap.core.new_session(overwrite=True) - # Inicializar o driver do Selenium - # Criar função - # conteudo da struct login driver = webdriver.Firefox(service=s, options=firefox_options) if len(struct_login) == 1: url_authentication = list(struct_login[0].keys())[0] driver.get(url_authentication) - elif len(struct_login) > 1: - pass else: - # lançar assert - pass + raise ValueError("Failed to create the list of URLs: struct_login does not contain exactly one element.") time.sleep(DEFAULT_TIME) for authentication_dictionary in struct_login: for _, elements in authentication_dictionary.items(): - for type, field in elements.items(): + for element_type, field in elements.items(): login_element, password_element = field - print(login_element, password_element) - # Encontrar os campos de login e senha (mudar) + logging.info(login_element, password_element) username_field = autentication.find_element_by_attribute( - driver, type, login_element + driver, element_type, login_element ) if username_field is None: continue # Se não encontrou o campo de username, pula para o próximo elemento password_field = autentication.find_element_by_attribute( - driver, type, password_element + driver, element_type, password_element ) if password_field is None: continue # Se não encontrou o campo de password, pula para o próximo elemento @@ -192,55 +126,38 @@ def main(file_path): time.sleep(DEFAULT_TIME) - # Tentar enviar o formulário try: password_field.send_keys(Keys.RETURN) - time.sleep(1) - autentication.validate_by_attribute(driver, type, login_element) - except Exception: - pass + time.sleep(DEFAULT_TIME) + autentication.validate_by_attribute(driver, element_type, login_element) + except Exception as e: + raise RuntimeError(f"Failed to submit the login form: {e}") time.sleep(DEFAULT_TIME) + # there exists a list of element types to test, if we find the first one, we can break the loop and continue. break - time.sleep(DEFAULT_TIME) - # Finalizar o driver driver.quit() - # "-----------------------------------------------------------------------------------------------------------------------------------" - # Pegar todos os alertas da aplicação alerts = zap.alert.alerts() - print(f"Alerts: {len(alerts)}") - print( - alerts - ) # se não retornar alerta, lançar assert informando a possibilidade do scan passivo ter parado de funcionar + logging.info(f"Number of Alerts: {len(alerts)}") + # se não retornar alerta, lançar assert informando a possibilidade do scan passivo ter parado de funcionar # Alertas no momento que o post foi passado, isso inclui as aplicações que foram passadas de forma errada # Lembrar que pegar o ultimo ainda é o mais sensato (evitar casos que manda a credencial que queremos, mas de uma forma errada) tem que testar. - alert_autentication = [] - # Depurar para encontrar outras formas de identificação de autenticação pelo proxy (rodar pelo UI - getboo, gruyere) - for alert in alerts: - if alert["name"] == "Authentication Request Identified": - # alerta de autenticação - alert_autentication.append(alert) - - # Pegar os ids de todas as mensagens de autenticação, ou de possiveis autenticação - messageId = [] - for alert in alert_autentication: - messageId.append(alert["messageId"]) + messageId = [alert["messageId"] for alert in alerts if alert["name"] == "Authentication Request Identified"] # verifica todos os alertas candidatos e filtra o que foi passado as credenciais de forma correta. - for id in messageId: - request_autenticated = zap.core.message(id) - # print(request_autenticated) + for message in messageId: + request_autenticated = zap.core.message(message) if autentication.check_credentials( request_autenticated["requestBody"], user.login, user.password, ): - print( - f'O response da aplicação no momento do login é:\n{request_autenticated["requestBody"]}' + logging.info( + f"The application's response during login: {request_autenticated["requestBody"]}" ) break @@ -257,8 +174,8 @@ def main(file_path): BASE_CONTEXT = "base_context/context_base.yaml" - params.Define_type_authentication( - zap, keywords.parameter_types_found, url_authentication, request_body + params.Define_authentication_type( + zap, parameter_types_found, url_authentication, request_body ) yaml = YAML() @@ -290,55 +207,67 @@ def main(file_path): with open(f"../shared_data/{OUTPUT}.yaml", "w") as file: yaml.dump(context, file) - print("Arquivo YAML modificado e salvo com sucesso !") - - time.sleep(DEFAULT_TIME) + logging.info("YAML file saved successfully!") - # mudar para o path da raiz output_yaml = f"../shared_data/{OUTPUT}.yaml" - print(output_yaml) + logging.info(f" Yaml path: ",output_yaml) - scanid = zap.automation.run_plan(output_yaml, ZAP_API_KEY) - print(f"scanid plano = {scanid}") - while zap.automation.plan_progress(scanid)["finished"] == "": - time.sleep(1) +def wait_for_directory(directory_path): + directory_path = Path(directory_path) - print(zap.automation.plan_progress(scanid)["finished"]) + if directory_path.exists(): + if any(directory_path.iterdir()): + logging.info(f"Files found in directory: {directory_path}") + return + else: + logging.info(f"Empty Directory: {directory_path}") + return - # rodar o spider autenticado - id_context = zap.context.context(user.context)["id"] - print(f"nome contexto = {user.context} id_context = {id_context}") - id_user = zap.users.users_list(id_context)[0]["id"] - print(f"id_user = {id_user}") +def spider_scan(driver, zap, user, urls_login, url_words, invalid_values_urls, s, firefox_options, struct_login): + # Primeira url será utilizada para realizar o crawler na página com o spider + driver.get(user.url[0]) - scanid = zap.spider.scan_as_user(id_context, id_user, apikey=ZAP_API_KEY) - print(f"scanid spider = {scanid}") + time.sleep(DEFAULT_TIME) + scanid = zap.spider.scan(user.url[0]) while int(zap.spider.status(scanid)) < 100: # Loop until the spider has finished - print("Spider progress %: {}".format(zap.spider.status(scanid))) + logging.info("Spider progress %: {}".format(zap.spider.status(scanid))) time.sleep(DEFAULT_TIME) - time.sleep(1) - FILE_CONTEXT = f"{user.context}.context" + logging.info("Spider completed") - # mudar para o path da raiz - print( - zap.context.export_context( - user.context, f"/shared_data/{FILE_CONTEXT}", apikey=ZAP_API_KEY - ) - ) + driver.quit() + + urls_found = zap.core.urls() + + logging.info(urls_found) + general_results = urls_login.find_urls_login(urls_found, url_words) + logging.info(general_results) + evidences_urls_login = [] + for url in general_results: + if not any( + re.search(pattern, url) for pattern in invalid_values_urls + ): # match das palavras do array (regex group) + evidences_urls_login.append(url) + + logging.info(evidences_urls_login) + for evidence in evidences_urls_login: + driver = webdriver.Firefox(service=s, options=firefox_options) + filtered_dict(driver, evidence, struct_login) if __name__ == "__main__": - wait_for_directory() + DIRECTORY_PATH = "/shared_data/input_config" + wait_for_directory(DIRECTORY_PATH) arq_config = os.listdir(DIRECTORY_PATH) for arq in arq_config: arq = os.path.join(DIRECTORY_PATH, arq) try: main(arq) - except: - pass + except Exception as e: + logging.error(f"An error occurred while processing the file {arq}: {e}") + raise RuntimeError(f"Failed to process the file {arq}") from e \ No newline at end of file From 4ae37b6c01e3ed00b8c4f4d0bdd1e27a86d944eb Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Mon, 23 Dec 2024 14:58:52 -0300 Subject: [PATCH 10/13] update: refactor makefile, copy.sh, docker-compose, requirements. delete server and refactor readme --- Authentication_ZAP_GT_CRIVO/Makefile | 1 + Authentication_ZAP_GT_CRIVO/README.md | 176 +++++++++++++----- Authentication_ZAP_GT_CRIVO/copy.sh | 36 ++-- .../docker-compose.yml | 18 +- .../framework/requirements.txt | 6 - Authentication_ZAP_GT_CRIVO/server/app.py | 74 -------- Authentication_ZAP_GT_CRIVO/server/dockerfile | 28 --- .../server/requirements.txt | 7 - .../server/templates/index.html | 35 ---- 9 files changed, 146 insertions(+), 235 deletions(-) delete mode 100644 Authentication_ZAP_GT_CRIVO/server/app.py delete mode 100644 Authentication_ZAP_GT_CRIVO/server/dockerfile delete mode 100644 Authentication_ZAP_GT_CRIVO/server/requirements.txt delete mode 100644 Authentication_ZAP_GT_CRIVO/server/templates/index.html diff --git a/Authentication_ZAP_GT_CRIVO/Makefile b/Authentication_ZAP_GT_CRIVO/Makefile index e9b3605..14970a0 100644 --- a/Authentication_ZAP_GT_CRIVO/Makefile +++ b/Authentication_ZAP_GT_CRIVO/Makefile @@ -18,3 +18,4 @@ run: generate_key update_env @echo "Starting services with new API key..." docker compose --env-file file.env up --build --force-recreate -d ./copy.sh $(DIR) + rm file.env diff --git a/Authentication_ZAP_GT_CRIVO/README.md b/Authentication_ZAP_GT_CRIVO/README.md index 2f7a68c..fc53b50 100644 --- a/Authentication_ZAP_GT_CRIVO/README.md +++ b/Authentication_ZAP_GT_CRIVO/README.md @@ -1,21 +1,111 @@ # Authentication_ZAP_GT_CRIVO -Este framework foi projetado para automatizar o processo de testes de segurança em aplicações web, utilizando autenticação para aumentar a superfície de ataque. Ele permite que o usuário teste diversas aplicações sem a necessidade de interagir diretamente com o conteúdo da aplicação. O framework automatiza o processo de captura de metadados, extração de elementos-chave e autenticação nas aplicações. Após a autenticação, o framework processa os metadados e cria um arquivo de contexto que pode ser utilizado posteriormente em testes de segurança. +This framework was designed to automate the process of security testing in web applications, utilizing authentication to expand the attack surface. It allows the user to test multiple applications without the need to interact directly with the application's content. The framework automates the process of metadata capture, extraction of key elements, and authentication within the applications. After authentication, the framework processes the metadata and generates a web application configuration file that can later be used for security testing. ## ZAP -O Zed Attack Proxy (ZAP) é uma ferramenta utilizada para testes de segurança em aplicações web. O framework usa uma imagem Docker do ZAP, que inclui um proxy e uma API que roda na porta `8080`. A ideia central é conectar o framework ao proxy do ZAP no momento em que uma aplicação web é instanciada. Dessa forma, o ZAP analisa a aplicação utilizando varreduras passivas e ativas durante os testes de penetração. +The Zed Attack Proxy (ZAP) is a tool used for security testing in web applications. The framework uses a Docker image of ZAP, which includes a proxy and an API running on port 8080. The main idea is to connect the framework to the ZAP proxy at the moment a web application is instantiated. This way, ZAP analyzes the application using both passive and active scans during security tests. -O framework interage com o ZAP enviando comandos e recebendo dados por meio da API. Os dados necessários para criar o arquivo de contexto da aplicação são extraídos dos alertas gerados pelo ZAP, que indicam possíveis vulnerabilidades. Esses alertas são configurados para que, ao serem acionados, necessitem de evidências, que podem ser fornecidas pelo usuário ou pelos desenvolvedores da ferramenta. Nesta versão, estamos utilizando os alertas definidos pelos próprios desenvolvedores do ZAP. +The framework interacts with ZAP by sending commands and receiving data through the API. The necessary data to create the application's context file is extracted from the alerts generated by ZAP, which indicate potential vulnerabilities. These alerts are configured to require evidence when triggered, which can be provided by the user or the tool's developers. In this version, the framework uses the alerts defined by ZAP's own developers. + +### Context + +A context is metadata of an application that can be used to perform security tests. This metadata may include information such as user credentials to be used for authentication, details about the application's session management, data on the type of authentication requests the application supports, URLs associated with the application to be tested, URLs to be excluded from testing, and technologies to be covered during the tests. + +A context can be named and saved in a file format, which can later be instantiated for future vulnerability tests on the same application. + +### Automate plan + +An automation plan is a YAML file that uses the context metadata to perform actions beyond just creating the mentioned file. In addition to generating the context file, the plan can also define jobs to create an automated execution pipeline, eliminating the need for the user to manually import the file into the tool and select the desired type of test. This plan enables the inclusion of various types of vulnerability test executions in ZAP, covering all preliminary tests on a web application in an automated manner. + +The developed framework generates the automation plan with some predefined jobs so that, by using this automation file, the user can either generate the file based on the context or perform a full scan. ## Framework -O framework conecta-se à API do ZAP para realizar testes e capturar metadados. Inicialmente, ele verifica a existência de arquivos de configuração no diretório reservado no volume. Assim que identificados, o fluxo de execução começa. Cada arquivo de configuração é analisado e seus campos são interpretados utilizando a biblioteca Pydantic. Se um arquivo não contiver as informações essenciais definidas na classe de configuração, ele será desconsiderado. +The framework connects to the ZAP API to perform tests and capture metadata. Initially, it checks for the existence of configuration files in the designated volume directory. Once identified, the execution flow begins. Each configuration file is parsed, and its fields are interpreted using the Pydantic library. If a file does not contain the essential information defined in the configuration class, it will be disregarded. + +The code is organized into modules, and the process for each of them will be explained below. + +### Authentication + +The `authentication.py` module contains functions to locate and validate elements on a web page based on specific attributes. Additionally, it verifies the presence of credentials in the request body of an authentication request. + +The `find_element_by_attribute` function is used to locate an element on a web page based on a specified attribute. It accepts three arguments: `driver`, which is the instance of Selenium WebDriver; `attribute`, the attribute to search for (e.g., "name," "type," "placeholder"); and `value`, the value of the attribute to look for. The function attempts to find the corresponding element and returns the found element (`WebElement`) if it exists, or `None` if the element is not found. If the specified attribute is invalid, the function raises a `ValueError`. + +The `validate_by_attribute` function validates whether an element with the specified attribute and value is visible on the page. The function attempts to find the corresponding element and checks if it is visible on the page. If the element is found and visible, the function logs a failure message for login, refreshes the page to clear partially found credentials, and returns `False`. If the element is not found, the function logs a success message for login and returns `True`. + +The `check_credentials` function receives a request string and two credentials (login and password) and verifies if both credentials are present in the request body. If the login and password credentials are identical, the function checks if the credential appears at least twice in the string, indicating that the login and password are the same. Otherwise, the function checks if both credentials are present in the string. The function returns `True` if the credentials are found as expected, or `False` otherwise. + +### Context + +The `replace_words` function is responsible for replacing login and password keywords with the default credentials accepted by ZAP. It takes as arguments the text to be modified, the login and password to be replaced, and the default credentials (`credential_login` and `credential_password`). The function handles a special case where `%40` is replaced by `@`, as observed in some tests where requests using `@` were returned with it replaced by `%40`. If the login and password are identical, the function replaces the first occurrence of both with the default credentials. Otherwise, if `%40` is present in the text, the login is modified by substituting `@` with `%40` before making the replacement. The function returns the modified text, which is the standard input for ZAP. + +The `build_yaml` function takes context information as parameters and constructs the YAML file responsible for the application's automation plan. Parameters include the context, alert count, request body, login and password credentials, the name of the file to be generated with the context, the base URL, and the base login URL. If the alert count is greater than zero, the function sets the application's session management to auto-detection for context creation. Afterward, the `replace_words` function is called to replace the login and password keywords in the request body with the default credentials. The modified request body is then used to update the YAML context. + +The `update_jobs` function is used to update the automation plan file with jobs that can be manually specified by the user, such as Spider, Ajax Spider, Full Scan, and other functionalities provided by ZAP. + +### Keywords +#### elements + + +The `elements.py` module contains functions for identifying password fields in web page elements and for filtering page elements based on evidence of authentication forms. + +The `find_password` function is used to check whether an element in a form contains a password field. It takes an element as an argument and uses a regular expression to determine if any value of the element matches a password pattern. The function returns True if a password field is found; otherwise, it returns False. + +The `filtered_dict` function takes a page instance, a page potentially containing an authentication form, and an empty structure to be populated. It extracts all elements from the page and uses regular expressions to verify if the elements are authentication fields. The function filters these page elements and returns a dictionary with the filtered elements. + +The function operates in a loop, which means that multiple non-candidate pages may exist in the queue. Because of this, the page does not use the ZAP proxy. Consequently, the driver is closed as soon as the elements are extracted. If no elements are found initially, the function attempts to find elements again after sending the ESCAPE key to handle potential pop-ups. The elements found are then added to a list, array_elements, which is populated with information about the elements' attributes, such as name, type, placeholder, and id. + +#### keywords + +The `keywords.py` module contains the `RegexSets` class, which is responsible for loading regular expression files that will be used to identify elements on a page. The `RegexSets` class includes methods to load and read these files, as well as to store the loaded regular expression sets. + +The `RegexSets` class is initialized with the path to the folder where the regex files are located. The `__init__` method sets the folder path and calls the `load_files` method to load the regex files. The `load_files` method retrieves all `.txt` files in the specified folder, sorts them, and reads them using the `read_files` method. For each file, the key is set as the file name (excluding the extension), and the file's content is stored in the `keywords` dictionary. The loaded regex sets are then assigned to specific class attributes: `invalid_values_urls`, `type_elements`, `valid_values_elements`, and `url_words`. + +The `read_files` method is responsible for reading the content of a text file. It takes the file name as an argument, opens the file, and reads each line, removing any extra whitespace. The method returns a list of lines read from the file. + +In addition to the `RegexSets` class, the module defines some global variables: +- **`password_field_identifiers`**: A tuple containing identifiers for password fields. +- **`parameters_types`**: A tuple containing parameter types (`form` and `json`). +- **`parameter_types_found`**: A dictionary that tracks the count of parameter types found (`form`, `json`, and `script`). -### Exemplo de classe de configuração: + +### Params + +The `define_authentication_type` function is responsible for determining the authentication type based on the application's request body. It takes as arguments an instance of ZAP, the `requestbody`, and the login and password credentials. + +The function checks if the request body contains the login and password credentials and, based on this, sets the authentication type to either `form` or `json`. If the credentials are present in the request body, the function sets the authentication type to `form`. Otherwise, it sets it to `json`. + +### Urls_Login + +The `find_urls_login` function takes two lists as parameters: `list_of_strings` and `list_of_words`. It iterates over each string in the `list_of_strings`, and for each string, it iterates over each word in the `list_of_words`. If a word is found within the string, the string is added to a results list called `found_urls`, and the inner iteration is terminated. + +At the end, the function returns the `found_urls` list, which contains all the strings that include at least one of the words from the `list_of_words`. + +### User_data + +The `user_data` module contains the class responsible for parsing the JSON provided by the user to perform authentication and application identification during testing with ZAP. + +### **Configuration in `user_data.py`** + +The `Configuration` class is a subclass of Pydantic's `BaseModel`, used to parse a JSON file that the user must provide with application data to enable automation. The class includes the following attributes: + +- **`context`**: A string representing the title of the configuration, which will be imported as the testing context for the application. +- **`url`**: A list of strings containing the URLs related to the configuration. +- **`url_login`**: An optional string representing the login URL. If the user provides this login URL, the automation will use it. Otherwise, the module will perform a heuristic search to find the URL with the authentication form. +- **`exclude_urls`**: A list of strings containing URLs to be excluded. The default value is an empty list. +- **`report_title`**: An optional string representing the title of the ZAP test report. The default value is `"Report"`. +- **`login`**: A string representing the user's login. +- **`password`**: A string representing the user's password. + +This class structures the necessary information for conducting tests through ZAP. + +## configuration file + +The configuration file must be in JSON format. Below is an example of how it should be organized: ```python -class User(BaseModel): +class Configuration(BaseModel): context: str url: List[str] url_login: Optional[str] = None @@ -25,84 +115,68 @@ class User(BaseModel): password: str ``` -Apenas dois atributos são opcionais: a URL da página de login e o título do relatório. Se a URL de login não for fornecida, o framework utiliza o spider do ZAP para tentar identificar as URLs da aplicação a partir da URL base. No entanto, nem sempre é possível encontrar a página de login automaticamente, por isso é recomendável fornecer essa URL sempre que possível. Se o spider falhar em encontrar a URL de login, um erro será lançado. +Pydantic will parse the file and extract the necessary attributes for creating the context. An example will be provided in the root of the repository, named `example.json`. -O framework utiliza o proxy do ZAP para acessar as aplicações web. Ele faz isso configurando o navegador Firefox com certificados de segurança e o proxy, permitindo que o ZAP capture os metadados das requisições durante a autenticação. O Selenium é utilizado para interagir com as aplicações web de duas formas: +## Main -### 1. Captura dos elementos de autenticação +The `main.py` file is responsible for orchestrating the entire process of security test automation in web applications. It performs various steps, from reading and validating the user's configuration to generating a YAML context file for ZAP (Zed Attack Proxy). The `main` function has several responsibilities. -O Selenium instancia o navegador na página de login e lista todos os elementos da página. Em seguida, ele filtra apenas os elementos do formulário de login. Se a página não retornar nenhum elemento do formulário, como ocorre em algumas aplicações que utilizam técnicas para ocultar esses dados na DOM, o framework pode não ser capaz de capturá-los. +The main module reads the JSON configuration file provided by the user and validates the JSON using the `Configuration` class from the `user_data` module. After that, it sets up variables and initializes ZAP by loading regular expressions using the `RegexSets` class from the `keywords` module, setting the required environment variables such as `FIREFOX` and `ZAP_API_KEY`, and starting a new ZAP session using the `zapv2` library. -Quando possível, o framework identifica os campos de login e senha utilizando expressões regulares (regex). As regex são configuradas para ignorar maiúsculas, minúsculas, pontuações e símbolos, cobrindo assim uma maior variedade de elementos. Se o framework não conseguir encontrar os campos necessários, o usuário pode adicionar regex personalizadas no arquivo `framework/keywords/regex_words/valid_elements.txt`. +Selenium is also configured in the main module, setting up Firefox to use ZAP's proxy and ignoring certificate errors. A Firefox profile is defined to use ZAP's proxy. -### 2. Realização do login na aplicação web +The module checks whether a login URL was provided by the user. If the URL is not provided, the module performs a scan with ZAP's spider to find potential login URLs using the `spider_scan` function. When a URL with an authentication form is found, the module instantiates the `filtered_dict` function from the `elements` module to extract and filter authentication elements from the login page. After creating the structure to perform the login, the main module submits the login credentials to the identified authentication fields and validates whether the login was successful using the `validate_by_attribute` function from the `autentication` module. -Após identificar os elementos de login, o Selenium envia as credenciais pelo proxy do ZAP. O framework então verifica se o login foi bem-sucedido observando se o formulário ainda está presente e se o ZAP detecta a autenticação através do alerta `Authentication Request Identified`. - -Com a autenticação realizada, o framework captura os metadados relevantes para criar o contexto da aplicação, incluindo o corpo da requisição (request body) do POST de autenticação e o tipo de gerenciamento de sessão da aplicação. Esses dados são usados para gerar um arquivo YAML com o plano de automação da aplicação, que será utilizado para instanciar a aplicação e finalizar o processo de criação do contexto. - -## Web Server - -O servidor web foi projetado para criar uma interface entre o framework e o usuário. Ele roda na porta `8000` e expõe o volume no localhost, permitindo que o usuário visualize os arquivos gerados. +After authentication is completed by instantiating the WebDriver with ZAP's proxy, the module captures alerts generated by ZAP during the authentication process, verifies whether the credentials were sent correctly using the `check_credentials` function from the `autentication` module, defines the type of authentication based on the request body using the `Define_authentication_type` function from the `params` module, constructs the YAML context file using the `build_yaml` function from the `context` module, and updates the jobs in the YAML file using the `update_jobs` function from the `context` module. Finally, the configuration is saved to a YAML file that is generated in the shared directory. ## Docker Compose -O `docker-compose` foi configurado para permitir que as três aplicações interajam entre si. Uma sub-rede em modo *bridge* foi criada e as dependências entre os containers foram definidas para garantir a ordem correta de execução. Primeiro, o container do ZAP é iniciado, seguido pelo container do framework, e por último o container do servidor web. - -## Arquivo de Configuração - -O arquivo de configuração deve estar em formato JSON. Abaixo está um exemplo de como organizá-lo: - -```json -{ - "context": "Test_DVWA", - "url": ["http://192.168.15.95/dvwa/"], - "url_login": "http://192.168.15.95/dvwa/login.php", - "report_title": "Report_dvwa", - "login": "admin", - "password": "admin" -} -``` - -O Pydantic fará o *parser* do arquivo e extrairá os atributos necessários para a criação do contexto. Um exemplo será disponibilizado na raiz do repositório, chamado `example.json`. +The `docker-compose` was configured to allow the two applications to interact with each other. A *bridge* mode subnet was created, and dependencies between the containers were defined to ensure the correct execution order. First, the ZAP container is started, followed by the framework container. ## Makefile -O `Makefile` realiza operações importantes para o funcionamento correto do framework: +The `Makefile` performs important operations for the proper functioning of the framework: -1. Gera a chave da API que será utilizada pelo ZAP, definindo-a como uma variável de ambiente. A cada execução, a chave é gerada aleatoriamente. -2. Executa o script `copy.sh`, que copia os arquivos de configuração do diretório especificado para o volume do arcabouço. +1. Generates the API key to be used by ZAP, setting it as an environment variable (the key is randomly generated with each execution). +2. Executes the `copy.sh` script, which copies the configuration files from the specified directory to the framework's volume. ### copy.sh -Este script recebe como parâmetro o diretório dos arquivos de configuração e copia-os para o volume, evitando duplicações a cada execução. Apenas arquivos `.json` são copiados, e se algum arquivo não passar no *parser* do Pydantic, ele será ignorado. -## Entrada (Input) +This script takes the configuration files' directory as a parameter and copies them to the volume, avoiding duplications with each execution. Only `.json` files are copied, and if any file fails the Pydantic parser, it will be ignored. + +## Input -Antes de iniciar o framework, é necessário criar um arquivo de configuração da aplicação a ser testada, como mostrado acima. Após criar um ou mais arquivos de configuração, armazene-os em um único diretório, cujo caminho será utilizado como parâmetro do `Makefile`. +Before starting the framework, it is necessary to create a configuration file for the application to be tested, as shown above. After creating one or more configuration files, store them in a single directory, whose path will be used as a parameter for the `Makefile`. -## Saída (Output) +## Output -O framework gera um arquivo com o plano dos *scans* passivos e o contexto da aplicação. +The framework generates a YAML file with the plan for passive scans and the application's context. -## Como Usar +## How to use -Para iniciar o framework, utilize o comando: +To start the framework, use the command: ```bash -make run DIR="diretório dos arquivos" +make run DIR="directory of the files" ``` -O `docker-compose` irá subir três containers: o ZAP, o framework e o servidor web. Os containers rodam em *background*. Para visualizar os logs, use os comandos: +The `docker-compose` will bring up two containers: ZAP and the framework. The containers run in the *background*. To view the logs, use the commands: ```bash docker logs -f authentication_zap_gt_crivo-zaproxy-1 docker logs -f authentication_zap_gt_crivo-framework-1 ``` -Após os testes, os resultados serão armazenados no volume do *compose* e poderão ser acessados pelo servidor web na URL `localhost:8000`. +The framework application can be managed using the command: + +```bash +docker exec -it authentication_zap_gt_crivo-framework-1 /bin/bash +``` + +All information related to the configuration files or the automation plans that are generated is located in the `shared_data` folder at the root of the application's compose. -Para finalizar a aplicação, use: +To stop the application, use: ```bash docker compose down diff --git a/Authentication_ZAP_GT_CRIVO/copy.sh b/Authentication_ZAP_GT_CRIVO/copy.sh index 8195065..5ca9646 100755 --- a/Authentication_ZAP_GT_CRIVO/copy.sh +++ b/Authentication_ZAP_GT_CRIVO/copy.sh @@ -1,35 +1,35 @@ #!/bin/bash +set -eu -# Verifica se foi passado somente 1 argumento +# Check if only 1 argument was passed if [ "$#" -ne 1 ]; then - echo "Usage: $0 " + echo "Usage: $0 " exit 1 fi -# Argumento do diretório que será copiado -ORIGEM=$1 +# Argument for the directory to be copied +SOURCE=$1 -DIR_DESTINO="input_config" -# volume no compose -DESTINO="/shared_data/$DIR_DESTINO" +DEST_DIR="input_config" +# volume in compose +DESTINATION="/shared_data/$DEST_DIR" - -# Verifica se o diretório de origem existe -if [ ! -d "$ORIGEM" ]; then +# Check if the source directory exists +if [ ! -d "$SOURCE" ]; then + echo "Error: Source directory '$SOURCE' does not exist." exit 1 fi -# Verifica se o diretório de destino existe se não existir, criar -docker compose exec framework bash -c "mkdir -p $DESTINO && rm -rf $DESTINO/*" +# Check if the destination directory exists, if not, create it +docker compose exec framework bash -c "mkdir -p $DESTINATION && rm -rf $DESTINATION/*" -# comando que apaga todos os arquivos de configuração se houver antes de copiar os novos. +# command that deletes all configuration files if any before copying the new ones. -# Copia apenas o conteúdo .json do diretório de origem para o volume -for file in "$ORIGEM"/*.json; do +# Copy only the .json content from the source directory to the volume +for file in "$SOURCE"/*.json; do if [ -f "$file" ]; then - docker compose cp "$file" framework:"$DESTINO/$(basename "$file")" + docker compose cp "$file" framework:"$DESTINATION/$(basename "$file")" fi done - -echo "Conteúdo copiado de $ORIGEM para $DESTINO com sucesso." +echo "Content copied from $SOURCE to $DESTINATION successfully." diff --git a/Authentication_ZAP_GT_CRIVO/docker-compose.yml b/Authentication_ZAP_GT_CRIVO/docker-compose.yml index 13ad74e..1fa9bc1 100644 --- a/Authentication_ZAP_GT_CRIVO/docker-compose.yml +++ b/Authentication_ZAP_GT_CRIVO/docker-compose.yml @@ -1,6 +1,6 @@ services: zaproxy: - image: zaproxy/zap-stable + image: zaproxy/zap-weekly command: ["zap.sh", "-config", "api.key=${ZAP_API_KEY}", "-daemon", "-host", "0.0.0.0", "-port", "8080", "-config", "api.addrs.addr.name=.*", "-config", "api.addrs.addr.regex=true"] ports: - "8080:8080" @@ -17,6 +17,7 @@ services: - FIREFOX=/usr/local/bin/geckodriver - ZAP_API_KEY=${ZAP_API_KEY} - ZAP_PROXY_ADDRESS=zaproxy + - ZAP_PROXY_PORT=8080 networks: - zapnet depends_on: @@ -24,21 +25,6 @@ services: volumes: - ./framework:/app - shared_data:/shared_data - - server: - build: ./server - environment: - - UPLOAD_FOLDER=/shared_data - ports: - - "8000:8000" - networks: - - zapnet - depends_on: - - zaproxy - - framework - volumes: - - ./server:/app - - shared_data:/shared_data networks: zapnet: diff --git a/Authentication_ZAP_GT_CRIVO/framework/requirements.txt b/Authentication_ZAP_GT_CRIVO/framework/requirements.txt index 0eae7e9..a74c2c7 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/requirements.txt +++ b/Authentication_ZAP_GT_CRIVO/framework/requirements.txt @@ -1,12 +1,8 @@ -annotated-types==0.7.0 attrs==23.2.0 beautifulsoup4==4.12.3 bs4==0.0.2 -certifi==2024.7.4 -charset-normalizer==3.3.2 exceptiongroup==1.2.1 h11==0.14.0 -idna==3.7 outcome==1.3.0.post0 pydantic==2.8.2 pydantic_core==2.20.1 @@ -16,14 +12,12 @@ requests==2.32.3 ruamel.yaml==0.18.6 ruamel.yaml.clib==0.2.8 selenium==4.22.0 -six==1.16.0 sniffio==1.3.1 sortedcontainers==2.4.0 soupsieve==2.5 trio==0.26.0 trio-websocket==0.11.1 typing_extensions==4.12.2 -urllib3==2.2.2 websocket-client==1.8.0 wsproto==1.2.0 zaproxy==0.3.2 diff --git a/Authentication_ZAP_GT_CRIVO/server/app.py b/Authentication_ZAP_GT_CRIVO/server/app.py deleted file mode 100644 index 40f24be..0000000 --- a/Authentication_ZAP_GT_CRIVO/server/app.py +++ /dev/null @@ -1,74 +0,0 @@ -from flask import Flask, render_template, request, redirect, url_for, send_from_directory -import os - -# Configurações básicas -app = Flask(__name__) - -# Caminho base para a pasta de upload -# BASE_DIR = 'shared_data' -app.config['UPLOAD_FOLDER'] = os.getenv('UPLOAD_FOLDER', '/shared_data') - -# verifica se o diretório de upload existe -if not os.path.exists(app.config['UPLOAD_FOLDER']): - os.makedirs(app.config['UPLOAD_FOLDER']) - -# Função para listar arquivos e diretórios em um caminho dado -def list_files_in_directory(path): - try: - # Listar arquivos e diretórios - items = os.listdir(path) - files = [] - directories = [] - - for item in items: - full_path = os.path.join(path, item) - if os.path.isdir(full_path): - directories.append(item) - else: - files.append(item) - - return directories, files - except FileNotFoundError: - return [], [] - -# Página principal com navegação por diretórios -@app.route('/', defaults={'path': ''}) -@app.route('/') -def index(path): - full_path = os.path.join(app.config['UPLOAD_FOLDER'], path) - - if not os.path.exists(full_path): - return 'Diretório não encontrado.', 404 - - # Listar diretórios e arquivos - directories, files = list_files_in_directory(full_path) - - # Retorna a página com os arquivos e subdiretórios - return render_template('index.html', directories=directories, files=files, current_path=path) - -# Rota para download de arquivos -@app.route('/uploads//') -@app.route('/uploads/', defaults={'path': ''}) -def download_file(path, filename): - full_path = os.path.join(app.config['UPLOAD_FOLDER'], path) - return send_from_directory(full_path, filename) - -# Rota para deletar um arquivo - -# Rota para fazer upload de arquivos -@app.route('/upload', methods=['POST']) -def upload_file(): - current_path = request.form.get('path', '') # Captura o caminho atual do diretório - if 'file' not in request.files: - return 'Nenhum arquivo encontrado.' - file = request.files['file'] - if file.filename == '': - return 'Nenhum arquivo selecionado.' - if file: - # Caminho completo para salvar o arquivo no diretório atual - upload_path = os.path.join(app.config['UPLOAD_FOLDER'], current_path) - file.save(os.path.join(upload_path, file.filename)) - return redirect(url_for('index', path=current_path)) - -if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0', port=8000) diff --git a/Authentication_ZAP_GT_CRIVO/server/dockerfile b/Authentication_ZAP_GT_CRIVO/server/dockerfile deleted file mode 100644 index e947865..0000000 --- a/Authentication_ZAP_GT_CRIVO/server/dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# Use a imagem oficial do Python como base -FROM python:3.12.5-slim - -# Define o diretório de trabalho dentro do contêiner -WORKDIR /app - -# Copia o arquivo requirements.txt para o contêiner -COPY requirements.txt . - -# Instala as dependências Python -RUN pip install --no-cache-dir -r requirements.txt - -# Copia o restante do código da aplicação para o contêiner -COPY . . - -# Configura a variável de ambiente do Flask -ENV FLASK_APP=app.py -ENV FLASK_RUN_HOST=0.0.0.0 -ENV FLASK_RUN_PORT=8000 - -# Define o diretório de upload para a aplicação Flask (apontando para o volume compartilhado) -ENV UPLOAD_FOLDER=/shared_data - -# Expõe a porta 8000 para acesso externo -EXPOSE 8000 - -# Comando para iniciar a aplicação Flask -CMD ["flask", "run"] diff --git a/Authentication_ZAP_GT_CRIVO/server/requirements.txt b/Authentication_ZAP_GT_CRIVO/server/requirements.txt deleted file mode 100644 index 1ac347b..0000000 --- a/Authentication_ZAP_GT_CRIVO/server/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -blinker==1.8.2 -click==8.1.7 -Flask==3.0.3 -itsdangerous==2.2.0 -Jinja2==3.1.4 -MarkupSafe==2.1.5 -Werkzeug==3.0.3 \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/server/templates/index.html b/Authentication_ZAP_GT_CRIVO/server/templates/index.html deleted file mode 100644 index a683fde..0000000 --- a/Authentication_ZAP_GT_CRIVO/server/templates/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - Servidor de Arquivos - - -

Upload e Download de Arquivos

- - -
- - - -
- -
    - {% if current_path %} - - voltar - {% endif %} - {% for directory in directories %} -
  • {{ directory }}/
  • - {% endfor %} -
- -

Arquivos Disponíveis para Download

-
    - {% for file in files %} -
  • {{ file }}
  • - {% endfor %} -
- - From 542d21e12daf57450cd72dea64ab7e628fd5db4f Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Tue, 24 Dec 2024 18:14:27 -0300 Subject: [PATCH 11/13] update: apllie format pep8 --- .../framework/autentication/autentication.py | 13 ++++----- .../framework/context/context.py | 16 +++++------ .../framework/keywords/elements.py | 27 ++++++++++--------- .../framework/keywords/keywords.py | 12 ++++++--- .../framework/params/params.py | 3 +-- .../framework/urls_login/urls_login.py | 8 +++--- .../framework/user_data/user_data.py | 1 + 7 files changed, 43 insertions(+), 37 deletions(-) diff --git a/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py b/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py index ce41228..062b866 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py +++ b/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py @@ -2,13 +2,14 @@ from selenium.webdriver.common.by import By import logging -logging.basicConfig(level=logging.INFO, format='%(message)s') +logging.basicConfig(level=logging.INFO, format="%(message)s") + def find_element_by_attribute(driver, attribute, value): """ - + Find an element on the website based on the specified attribute. - + """ try: if attribute == "name": @@ -30,9 +31,9 @@ def find_element_by_attribute(driver, attribute, value): def validate_by_attribute(driver, attribute, value): """ - + Validate if an element with the given attribute and value is displayed on the page. - + """ try: if attribute == "name": @@ -57,7 +58,7 @@ def validate_by_attribute(driver, attribute, value): def check_credentials(request, credencial_login, credencial_passsword): """ - + This function receives a string and two credentials and checks if both credentials are present in the request body string. The function can check if the credentials are the same by verifying the number of occurrences in the string. And it checks if both credentials are present in the string. diff --git a/Authentication_ZAP_GT_CRIVO/framework/context/context.py b/Authentication_ZAP_GT_CRIVO/framework/context/context.py index 76fd595..588bfcf 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/context/context.py +++ b/Authentication_ZAP_GT_CRIVO/framework/context/context.py @@ -4,7 +4,6 @@ from keywords import keywords - def replace_words( text, login, @@ -13,10 +12,10 @@ def replace_words( credential_password="{%password%}", ): """ - + This function is responsible for replacing the login and password keywords with the default credentials accepted by ZAP. The replace_words function has a case where it treats %40 as @, which was highlighted in some tests where the request with @ was returned by replacing it with the characters %40. - + """ if login == password: new_request = text.replace(login, credential_login, 1).replace( @@ -42,9 +41,9 @@ def build_yaml( base_url_login, ): """ - + This function receives the context information as a parameter and constructs the YAML file responsible for the application's automation plan. - + """ if alert_count > 0: # marcar contexto como auto detect. @@ -74,7 +73,7 @@ def build_yaml( context["env"]["contexts"][0]["name"] = context_name context["env"]["contexts"][0]["urls"] = base_url context["env"]["contexts"][0]["includePaths"] = [] - # Authentication + # Authentication context["env"]["contexts"][0]["authentication"]["parameters"][ "loginPageUrl" ] = base_url_login @@ -101,12 +100,11 @@ def build_yaml( ] = new_verification_entries - def update_jobs(jobs, new_context, new_user, new_url): """ - + Instantiates jobs defined by default in the application's automation plan file. - + """ for job in jobs: if "parameters" in job: diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/elements.py b/Authentication_ZAP_GT_CRIVO/framework/keywords/elements.py index b142050..dab1c45 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/keywords/elements.py +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/elements.py @@ -10,25 +10,29 @@ from keywords.keywords import RegexSets -logging.basicConfig(level=logging.INFO, format='%(message)s') +logging.basicConfig(level=logging.INFO, format="%(message)s") DEFAULT_TIME = 1 regex_sets = RegexSets() password_regex = regex_sets.valid_values_elements[0] valid_values_elements = regex_sets.valid_values_elements + def find_password(element): - return any(re.search(password_regex, str(value), re.IGNORECASE) for _, value in element.items()) + return any( + re.search(password_regex, str(value), re.IGNORECASE) + for _, value in element.items() + ) def filtered_dict(driver, evidence, struct_login): """ - + The function will receive an instance of a page and an empty structure that will be filled. For each "evidence" (page with a potential authentication form), the function will extract all elements from the page and use regex to verify if the elements are indeed authentication fields. The function will filter the elements from the page and return a dictionary with the filtered elements. This function runs in a loop, meaning there can be multiple pages that are not candidates in the queue. Therefore, the page does not use the ZAP proxy. Because of this, the driver is closed as soon as the elements are extracted. - + """ driver.get(evidence) @@ -59,7 +63,7 @@ def filtered_dict(driver, evidence, struct_login): if element_info: array_elements.append(element_info) logging.info(array_elements) - + filtered_dictionaries = [] for einfo in array_elements: for _, value in einfo.items(): @@ -70,18 +74,18 @@ def filtered_dict(driver, evidence, struct_login): break logging.info(filtered_dictionaries) - + if filtered_dictionaries: - + if len(filtered_dictionaries) > 2: login = [] for index, element in enumerate(filtered_dictionaries): if find_password(element): - if (filtered_dictionaries[index-1] in login): + if filtered_dictionaries[index - 1] in login: pass else: - login.append(filtered_dictionaries[index-1]) - if (element in login): + login.append(filtered_dictionaries[index - 1]) + if element in login: pass else: login.append(element) @@ -97,6 +101,5 @@ def filtered_dict(driver, evidence, struct_login): dicionario_autenticado = {evidence: authentication_data} struct_login.append(dicionario_autenticado) - - + driver.quit() diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py b/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py index d47f652..c3b90d5 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py @@ -1,13 +1,17 @@ import os + class RegexSets: """ - + The class is responsible for loading the regex files that will be used to identify elements on the page. - + """ + def __init__(self): - self.folder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "regex_words/") + self.folder_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "regex_words/" + ) self.keywords = {} self.load_files() @@ -32,4 +36,4 @@ def read_files(self, file): password_field_identifiers = ("password", "senha", "txtPassword") parameters_types = ("form", "json") -parameter_types_found = {"form": 0, "json": 0, "script": 0} \ No newline at end of file +parameter_types_found = {"form": 0, "json": 0, "script": 0} diff --git a/Authentication_ZAP_GT_CRIVO/framework/params/params.py b/Authentication_ZAP_GT_CRIVO/framework/params/params.py index b2cb4ed..9c8d84b 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/params/params.py +++ b/Authentication_ZAP_GT_CRIVO/framework/params/params.py @@ -2,12 +2,11 @@ from keywords import keywords - def Define_authentication_type( zap, parameter_types_found, BASE_URL_LOGIN, request_body ): """ - + Function to define the authentication type based on the request body of the application: json, form, etc. """ diff --git a/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py b/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py index b806a8c..5f98c89 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py +++ b/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py @@ -1,16 +1,16 @@ def find_urls_login(list_of_strings, lista_of_words): """ - + List to store the strings that contain any of the words Iterate over each string in the list of strings Iterate over each word in the list of words Check if the word is present in the string Add the string to the list of results and exit the inner loop - - """ + + """ found_urls = [] - for string in list_of_strings: + for string in list_of_strings: for word in lista_of_words: if word in string: found_urls.append(string) diff --git a/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py b/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py index be74b79..c16ab21 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py +++ b/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py @@ -9,6 +9,7 @@ class Configuration(BaseModel): Class used to parse the JSON that the user must provide with the application data to perform the automation. """ + context: str url: List[str] url_login: Optional[str] = None From 4ce587f6a3f72013c0009a571d2a280ceff638ae Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Tue, 24 Dec 2024 19:05:25 -0300 Subject: [PATCH 12/13] update: documentation real example json --- Authentication_ZAP_GT_CRIVO/README.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Authentication_ZAP_GT_CRIVO/README.md b/Authentication_ZAP_GT_CRIVO/README.md index fc53b50..5154077 100644 --- a/Authentication_ZAP_GT_CRIVO/README.md +++ b/Authentication_ZAP_GT_CRIVO/README.md @@ -102,17 +102,16 @@ This class structures the necessary information for conducting tests through ZAP ## configuration file -The configuration file must be in JSON format. Below is an example of how it should be organized: - -```python -class Configuration(BaseModel): - context: str - url: List[str] - url_login: Optional[str] = None - exclude_urls: List[str] = [] - report_title: Optional[str] = "Report" - login: str - password: str +The configuration file must be in JSON format. Below is an real example: + +```json + context: "context_bWAPP" + url: "192.168.15.3/bWAPP/" + url_login: "192.168.15.3/bWAPP/login" + exclude_urls: [] + report_title: "Report_bWAPP" + login: "admin" + password: "admin" ``` Pydantic will parse the file and extract the necessary attributes for creating the context. An example will be provided in the root of the repository, named `example.json`. From 2202323e9df5ebe3808aba0d918827c2ef1a7657 Mon Sep 17 00:00:00 2001 From: Sacramento-20 Date: Tue, 24 Dec 2024 19:45:25 -0300 Subject: [PATCH 13/13] update: change name of function --- Authentication_ZAP_GT_CRIVO/framework/main.py | 2 +- Authentication_ZAP_GT_CRIVO/framework/params/params.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Authentication_ZAP_GT_CRIVO/framework/main.py b/Authentication_ZAP_GT_CRIVO/framework/main.py index 267d637..49a99e7 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/main.py +++ b/Authentication_ZAP_GT_CRIVO/framework/main.py @@ -174,7 +174,7 @@ def main(file_path): BASE_CONTEXT = "base_context/context_base.yaml" - params.Define_authentication_type( + params.define_authentication_type( zap, parameter_types_found, url_authentication, request_body ) diff --git a/Authentication_ZAP_GT_CRIVO/framework/params/params.py b/Authentication_ZAP_GT_CRIVO/framework/params/params.py index 9c8d84b..0d3f140 100644 --- a/Authentication_ZAP_GT_CRIVO/framework/params/params.py +++ b/Authentication_ZAP_GT_CRIVO/framework/params/params.py @@ -2,7 +2,7 @@ from keywords import keywords -def Define_authentication_type( +def define_authentication_type( zap, parameter_types_found, BASE_URL_LOGIN, request_body ): """