From c34e4bfb38e95a148c6460974115cf445e5f142b Mon Sep 17 00:00:00 2001 From: Vahagn Date: Mon, 26 Aug 2024 15:15:48 +0400 Subject: [PATCH] init --- .flake8 | 25 ++++ .github/workflows/ci.yml | 120 +++++++++------- README.md | 128 ++++++++++++++++++ checks/CheckAbapOutgoingFtpConn.py | 6 +- checks/CheckBrokenAuthCheck.py | 2 - checks/CheckCallTransformation.py | 2 + checks/CheckCrossSiteScripting.py | 6 +- checks/CheckDangerousAbapCommands.py | 4 +- checks/CheckDeleteDynpro.py | 2 + ...eckDirectoryTraversalCRstrbReadBuffered.py | 4 +- checks/CheckDirectoryTraversalCallAlerts.py | 4 +- .../CheckDirectoryTraversalDeleteDataset.py | 5 +- checks/CheckDirectoryTraversalReadDataset.py | 5 +- .../CheckDirectoryTraversalRfcRemoteFile.py | 6 +- checks/CheckDirectoryTraversalTransfer.py | 11 +- checks/CheckDosInDoLoop.py | 4 +- checks/CheckDummyAuthCheck.py | 3 - checks/CheckExecuteProcedure.py | 3 +- checks/CheckExposedSystemCalls.py | 4 +- checks/CheckGenerateSubroutinePool.py | 2 + checks/CheckGetPersistentByQuery.py | 3 +- checks/CheckHardcodedCredentials.py | 2 +- checks/CheckHardcodedITIN.py | 6 +- checks/CheckHardcodedIpAddresses.py | 19 ++- checks/CheckHardcodedUrls.py | 6 +- checks/CheckHardcodedUserAuth.py | 3 - checks/CheckOSCommandInjectionCFunction.py | 4 +- checks/CheckOSCommandInjectionCallSystem.py | 2 - checks/CheckOSCommandInjectionClientOS.py | 3 +- ...heckOSCommandInjectionOpenDatasetFilter.py | 4 +- .../CheckOSCommandInjectionRfcRemoteExec.py | 4 +- .../CheckOSCommandInjectionRfcRemotePipe.py | 4 +- checks/CheckOSCommandInjectionSxpg.py | 4 +- checks/CheckWeakHashingAlgorithms.py | 4 - checks/__init__.py | 40 +++--- config.py | 3 +- config.yml | 2 +- generate_xlsx_report.py | 18 ++- main.py | 13 +- pytest.ini | 4 + run_tests.bat | 3 + run_tests.sh | 3 + scanner.py | 5 +- tests/TestCheckAbapOutgoingFtpConn.py | 9 +- tests/TestCheckBrokenAuthCheck.py | 9 +- tests/TestCheckCallTransformation.py | 7 +- tests/TestCheckCrossSiteScripting.py | 9 +- tests/TestCheckDangerousAbapCommands.py | 9 +- tests/TestCheckDeleteDynpro.py | 7 +- ...eckDirectoryTraversalCRstrbReadBuffered.py | 10 +- .../TestCheckDirectoryTraversalCallAlerts.py | 8 +- ...estCheckDirectoryTraversalDeleteDataset.py | 9 +- .../TestCheckDirectoryTraversalReadDataset.py | 9 +- ...estCheckDirectoryTraversalRfcRemoteFile.py | 9 +- tests/TestCheckDirectoryTraversalTransfer.py | 7 +- tests/TestCheckDosInDoLoop.py | 9 +- tests/TestCheckDummyAuthCheck.py | 9 +- tests/TestCheckExecuteProcedure.py | 7 +- tests/TestCheckExposedSystemCalls.py | 9 +- tests/TestCheckGenerateSubroutinePool.py | 7 +- tests/TestCheckGetPersistentByQuery.py | 7 +- tests/TestCheckHardcodedCredentials.py | 9 +- tests/TestCheckHardcodedITIN.py | 7 +- tests/TestCheckHardcodedIpAddresses.py | 9 +- tests/TestCheckHardcodedUrls.py | 9 +- tests/TestCheckHardcodedUserAuth.py | 9 +- tests/TestCheckOSCommandInjectionCFunction.py | 9 +- .../TestCheckOSCommandInjectionCallSystem.py | 7 +- tests/TestCheckOSCommandInjectionClientOS.py | 9 +- ...heckOSCommandInjectionOpenDatasetFilter.py | 9 +- ...estCheckOSCommandInjectionRfcRemoteExec.py | 9 +- ...estCheckOSCommandInjectionRfcRemotePipe.py | 10 +- tests/TestCheckOSCommandInjectionSxpg.py | 9 +- tests/TestCheckWeakHashingAlgorithms.py | 10 +- 74 files changed, 502 insertions(+), 268 deletions(-) create mode 100644 .flake8 create mode 100644 README.md create mode 100644 pytest.ini create mode 100644 run_tests.bat create mode 100644 run_tests.sh diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..eaf56f8 --- /dev/null +++ b/.flake8 @@ -0,0 +1,25 @@ +[flake8] +# Increase the max line length to 120 characters +max-line-length = 120 + +# Ignore specific errors/warnings: +# E501: Line too long +# W291: Trailing whitespace +# E128: Continuation line under-indented for visual indent +# E126: Continuation line over-indented for hanging indent +# E127: Continuation line over-indented for visual indent +# W503: Line break occurred before a binary operator +# E266: Too many leading '#' for block comment +ignore = E501, W291, E128, E126, E127, W503, E266, W605, C901, E303 + +# Exclude some directories from checking +exclude = + .git, + __pycache__, + build, + dist, + .venv + + +# Maximum allowed complexity for functions +max-complexity = 10 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b76a33..59ed4b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,53 +1,79 @@ -stages: - - lint - - test - - build - - deploy +name: CI/CD Pipeline -variables: - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache" +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] -cache: - paths: - - .pip-cache/ +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 + - name: Lint with flake8 + run: flake8 . -lint: - stage: lint - image: python:3.9 - before_script: - - pip install flake8 - script: - - flake8 . - -test: - stage: test - image: python:3.9 - before_script: - - pip install -r requirements.txt - - pip install pytest pytest-cov - script: - - pytest tests/ --cov=./ --cov-report=xml - artifacts: - reports: - coverage_report: - coverage_format: cobertura + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-cov + - name: List directory contents + run: ls -R + - name: Run tests + run: | + export PYTHONPATH=$PYTHONPATH:$(pwd) + pytest tests/ -v --cov=./ --cov-report=xml + - name: Upload coverage report + uses: actions/upload-artifact@v2 + with: + name: coverage-report path: coverage.xml -build: - stage: build - image: python:3.9 - script: - - pip install pyinstaller - - pyinstaller --onefile main.py - artifacts: - paths: - - dist/main + build: + runs-on: ubuntu-latest + needs: [lint, test] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pyinstaller + - name: Build executable + run: pyinstaller --onefile main.py + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: abap-code-scanner + path: dist/main -deploy: - stage: deploy - image: python:3.9 - script: - - echo "Deploying application..." - # Add your deployment steps here - only: - - main # This job will only run on the main branch \ No newline at end of file + deploy: + runs-on: ubuntu-latest + needs: build + if: github.ref == 'refs/heads/main' + steps: + - name: Deploy application + run: | + echo "Deploying application..." + # Add your deployment steps here \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..805f89b --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +# ABAP Code Scanner Framework + +## Overview + +The ABAP Code Scanner Framework is a powerful tool designed to analyze ABAP (Advanced Business Application Programming) code for potential security vulnerabilities, code quality issues, and best practice violations. This framework provides a flexible and extensible way to scan ABAP code and generate comprehensive reports on various aspects of code security and quality. + +## Features + +- Multiple security checks including: + - Cross-Site Scripting (XSS) vulnerabilities + - Directory Traversal vulnerabilities + - Hardcoded credentials + - Weak cryptographic algorithms + - And many more... +- Customizable and extensible architecture +- Command-line interface for easy integration into CI/CD pipelines +- Detailed reporting in XLSX format +- Configurable scan settings + +## Upcoming Feature: Dataflow Analysis + +We are excited to announce that we are working on implementing a dataflow analysis feature. This enhancement will significantly improve the accuracy and depth of our security scans. + +### What is Dataflow Analysis? + +Dataflow analysis is a technique used to track how data moves through an application. In the context of security, it helps identify how potentially tainted data (e.g., user inputs) propagates through the system and whether it reaches sensitive sinks (e.g., database queries, output functions) without proper sanitization. + +### Planned Functionality + +Our dataflow analysis will: + +- Track parameters and their contents from the beginning of functions, reports, forms, includes, or other ABAP structures. +- Follow the data as it flows through the code, monitoring transformations and assignments. +- Identify potential injection points where tainted data might be used unsafely. +- Provide more accurate and context-aware vulnerability detection. + +This feature will enable the framework to: +- Reduce false positives by understanding the context and transformations of data. +- Detect complex vulnerabilities that simple pattern matching might miss. +- Offer more detailed and actionable reports on potential security issues. + +We're working hard to integrate this feature and will update the framework once it's ready. Stay tuned for updates! + +## Prerequisites + +- Python 3.9 or higher +- pip (Python package installer) + +## Installation + +1. Clone the repository: + ``` + git clone https://github.com/yourusername/AbapCodeScannerFramework.git + cd AbapCodeScannerFramework + ``` + +2. Install the required dependencies: + ``` + pip install -r requirements.txt + ``` + +## Usage + +To run the ABAP Code Scanner: + +``` +python main.py path/to/abap/code +``` + +Optional arguments: +- `-c`, `--config`: Path to the configuration file (default: config.yml) + +## Configuration + +The scanner can be configured using a YAML file. By default, it looks for `config.yml` in the project root. You can specify a different configuration file using the `-c` or `--config` option. + +Example configuration: + +```yaml +checks: + - CheckCrossSiteScripting + - CheckSQLInjection + - CheckDirectoryTraversal + +file_extensions: + - .abap + - .txt + +exclude_patterns: + - "**/test/**" +``` + +## Adding New Checks + +To add a new security check: + +1. Create a new Python file in the `checks` directory. +2. Define a class that inherits from a base check class. +3. Implement the required methods, including the main `run` method. +4. Add the new check to the configuration file. + +## Running Tests + +To run the test suite: + +On Windows: +``` +run_tests.bat +``` + +On Unix-like systems: +``` +./run_tests.sh +``` + +## Contributing + +Contributions to the ABAP Code Scanner Framework are welcome! Please feel free to submit pull requests, create issues or spread the word. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- Thanks to all contributors who have helped to improve this framework. +- Special thanks to the ABAP community for their invaluable resources and documentation. + diff --git a/checks/CheckAbapOutgoingFtpConn.py b/checks/CheckAbapOutgoingFtpConn.py index c5d1fe6..c603a2b 100644 --- a/checks/CheckAbapOutgoingFtpConn.py +++ b/checks/CheckAbapOutgoingFtpConn.py @@ -1,16 +1,17 @@ -# checks/check_abap_outgoing_ftp_conn.py - import re from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckAbapOutgoingFtpConn: title = "Outgoing FTP Connection" + confidence = "Definitive" severity = "Low" vulnerability_type = "Unencrypted Communications" @@ -26,4 +27,3 @@ def run(self, file_content: str) -> List[CheckResult]: line_number = file_content[:match.start()].count('\n') + 1 return [CheckResult(line_number, match.group().strip())] return [] - diff --git a/checks/CheckBrokenAuthCheck.py b/checks/CheckBrokenAuthCheck.py index 901b085..b3968e2 100644 --- a/checks/CheckBrokenAuthCheck.py +++ b/checks/CheckBrokenAuthCheck.py @@ -1,5 +1,3 @@ -# checks/check_broken_auth_check.py - import re from dataclasses import dataclass from typing import List diff --git a/checks/CheckCallTransformation.py b/checks/CheckCallTransformation.py index 452f66a..4367758 100644 --- a/checks/CheckCallTransformation.py +++ b/checks/CheckCallTransformation.py @@ -2,11 +2,13 @@ from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckCallTransformation: title = "XML Injection via \"CALL TRANSFORMATION\"" severity = "High" diff --git a/checks/CheckCrossSiteScripting.py b/checks/CheckCrossSiteScripting.py index bc41168..93ac68d 100644 --- a/checks/CheckCrossSiteScripting.py +++ b/checks/CheckCrossSiteScripting.py @@ -1,14 +1,14 @@ -# checks/CheckCrossSiteScripting.py - import re from dataclasses import dataclass from typing import List, Dict + @dataclass class CheckResult: line_number: int line_content: str + class CheckCrossSiteScripting: title = "Potential Cross-Site Scripting vulnerability" severity = "High" @@ -60,4 +60,4 @@ def run(self, file_content: str) -> List[CheckResult]: results.append(CheckResult(i, line.strip())) break # Stop searching after finding the first vulnerability in the line - return results \ No newline at end of file + return results diff --git a/checks/CheckDangerousAbapCommands.py b/checks/CheckDangerousAbapCommands.py index 719abdb..7f517bf 100644 --- a/checks/CheckDangerousAbapCommands.py +++ b/checks/CheckDangerousAbapCommands.py @@ -1,14 +1,14 @@ -# checks/check_dangerous_abap_commands.py - import re from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckDangerousAbapCommands: title = "Dangerous ABAP statements" severity = "Medium" diff --git a/checks/CheckDeleteDynpro.py b/checks/CheckDeleteDynpro.py index ae5cbd1..038a3fc 100644 --- a/checks/CheckDeleteDynpro.py +++ b/checks/CheckDeleteDynpro.py @@ -2,11 +2,13 @@ from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckDeleteDynpro: title = "Critical actions via deleting a screen" severity = "High" diff --git a/checks/CheckDirectoryTraversalCRstrbReadBuffered.py b/checks/CheckDirectoryTraversalCRstrbReadBuffered.py index c2dcac0..1db70b4 100644 --- a/checks/CheckDirectoryTraversalCRstrbReadBuffered.py +++ b/checks/CheckDirectoryTraversalCRstrbReadBuffered.py @@ -2,11 +2,13 @@ from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckDirectoryTraversalCRstrbReadBuffered: title = "Path Traversal - CALL C_RSTRB_READ_BUFFERED" severity = "Medium" @@ -23,4 +25,4 @@ def run(self, file_content: str) -> List[CheckResult]: if self.pattern2.search(call_statement): line_number = file_content[:match1.start()].count('\n') + 1 results.append(CheckResult(line_number, call_statement.strip())) - return results \ No newline at end of file + return results diff --git a/checks/CheckDirectoryTraversalCallAlerts.py b/checks/CheckDirectoryTraversalCallAlerts.py index 4d4ffb0..4cc6781 100644 --- a/checks/CheckDirectoryTraversalCallAlerts.py +++ b/checks/CheckDirectoryTraversalCallAlerts.py @@ -2,11 +2,13 @@ from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckDirectoryTraversalCallAlerts: title = "Path Traversal - CALL ALERTS" severity = "Medium" @@ -23,4 +25,4 @@ def run(self, file_content: str) -> List[CheckResult]: if self.pattern2.search(call_statement): line_number = file_content[:match1.start()].count('\n') + 1 results.append(CheckResult(line_number, call_statement.strip())) - return results \ No newline at end of file + return results diff --git a/checks/CheckDirectoryTraversalDeleteDataset.py b/checks/CheckDirectoryTraversalDeleteDataset.py index 8e56e53..dea1d81 100644 --- a/checks/CheckDirectoryTraversalDeleteDataset.py +++ b/checks/CheckDirectoryTraversalDeleteDataset.py @@ -1,15 +1,14 @@ -# checks/check_directory_traversal_read_dataset.py - import re from dataclasses import dataclass from typing import List -from enum import Enum + @dataclass class CheckResult: line_number: int line_content: str + class CheckDirectoryTraversalDeleteDataset: title = "Path Traversal - DELETE DATASET" severity = "HIGH" diff --git a/checks/CheckDirectoryTraversalReadDataset.py b/checks/CheckDirectoryTraversalReadDataset.py index 269519a..6e45c8e 100644 --- a/checks/CheckDirectoryTraversalReadDataset.py +++ b/checks/CheckDirectoryTraversalReadDataset.py @@ -1,15 +1,14 @@ -# checks/check_directory_traversal_read_dataset.py - import re from dataclasses import dataclass from typing import List -from enum import Enum + @dataclass class CheckResult: line_number: int line_content: str + class CheckDirectoryTraversalReadDataset: title = "Path Traversal - READ DATASET" severity = "HIGH" diff --git a/checks/CheckDirectoryTraversalRfcRemoteFile.py b/checks/CheckDirectoryTraversalRfcRemoteFile.py index 948e347..a8202a7 100644 --- a/checks/CheckDirectoryTraversalRfcRemoteFile.py +++ b/checks/CheckDirectoryTraversalRfcRemoteFile.py @@ -1,21 +1,19 @@ -# checks/check_directory_traversal_rfc_remote_file.py - import re from dataclasses import dataclass from typing import List -from enum import Enum + @dataclass class CheckResult: line_number: int line_content: str + class CheckDirectoryTraversalRfcRemoteFile: title = "Potential Path Traversal detected - RFC_REMOTE_FILE" severity = "HIGH" vulnerability_type = "Path Traversal" - def __init__(self): self.main_pattern = re.compile( r"(?ims)^[\s]*(\bCALL FUNCTION\b)(.+?\.)", diff --git a/checks/CheckDirectoryTraversalTransfer.py b/checks/CheckDirectoryTraversalTransfer.py index dd5a3e6..47c7df7 100644 --- a/checks/CheckDirectoryTraversalTransfer.py +++ b/checks/CheckDirectoryTraversalTransfer.py @@ -2,20 +2,24 @@ from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckDirectoryTraversalTransfer: title = "Directory traversal via \"TRANSFER\" statement" severity = "High" vulnerability_type = "Directory Traversal" def __init__(self): - self.transfer_pattern = re.compile(r'(^\s*|\.\s*)TRANSFER\s+[\S]+\s+TO\s+(\w+)\.?', re.IGNORECASE | re.MULTILINE) + self.transfer_pattern = re.compile(r'(^\s*|\.\s*)TRANSFER\s+[\S]+\s+TO\s+(\w+)\.?', + re.IGNORECASE | re.MULTILINE) self.validation_pattern = re.compile(r'CALL\s+FUNCTION\s+(\'|\`)FILE_VALIDATE_NAME(\'|\`)', re.IGNORECASE) - self.subrc_check_pattern = re.compile(r'(IF\s+sy(st)?-subrc\s*(=|EQ)\s*0|CHECK\s+sy(st)?-subrc\s*(=|EQ)\s*0)', re.IGNORECASE) + self.subrc_check_pattern = re.compile(r'(IF\s+sy(st)?-subrc\s*(=|EQ)\s*0|CHECK\s+sy(st)?-subrc\s*(=|EQ)\s*0)', + re.IGNORECASE) def run(self, file_content: str) -> List[CheckResult]: results = [] @@ -30,7 +34,8 @@ def run(self, file_content: str) -> List[CheckResult]: def is_filename_validated(self, file_content: str, filename_var: str) -> bool: # Check if FILE_VALIDATE_NAME is called with the filename variable - validation_call = re.search(rf'CALL\s+FUNCTION\s+(\'|\`)FILE_VALIDATE_NAME(\'|\`).*?{filename_var}', file_content, re.IGNORECASE | re.DOTALL) + validation_call = re.search(rf'CALL\s+FUNCTION\s+(\'|\`)FILE_VALIDATE_NAME(\'|\`).*?{filename_var}', + file_content, re.IGNORECASE | re.DOTALL) if validation_call: # Check if there's a proper subrc check after the validation validation_pos = validation_call.start() diff --git a/checks/CheckDosInDoLoop.py b/checks/CheckDosInDoLoop.py index 26e1afb..caf23d6 100644 --- a/checks/CheckDosInDoLoop.py +++ b/checks/CheckDosInDoLoop.py @@ -1,13 +1,14 @@ import re from dataclasses import dataclass from typing import List -from enum import Enum + @dataclass class CheckResult: line_number: int line_content: str + class CheckDosInDoLoop: title = "Denial of Service (DOS) in do/enddo loop." severity = "Medium" @@ -25,4 +26,3 @@ def run(self, file_content: str) -> List[CheckResult]: line_number = file_content[:match.start()].count('\n') + 1 results.append(CheckResult(line_number, match.group().strip())) return results - diff --git a/checks/CheckDummyAuthCheck.py b/checks/CheckDummyAuthCheck.py index 13d2ab0..3249901 100644 --- a/checks/CheckDummyAuthCheck.py +++ b/checks/CheckDummyAuthCheck.py @@ -1,5 +1,3 @@ -# checks/check_dummy_auth_check.py - import re from dataclasses import dataclass from typing import List @@ -54,4 +52,3 @@ def run(self, file_content: str) -> List[CheckResult]: line_number = file_content[:match.start()].count('\n') + 1 results.append(CheckResult(line_number, match.group().strip())) return results - diff --git a/checks/CheckExecuteProcedure.py b/checks/CheckExecuteProcedure.py index 77814e4..a55ffe4 100644 --- a/checks/CheckExecuteProcedure.py +++ b/checks/CheckExecuteProcedure.py @@ -2,11 +2,13 @@ from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckExecuteProcedure: title = "ADBC Injection via \"execute_procedure\"" severity = "High" @@ -22,4 +24,3 @@ def run(self, file_content: str) -> List[CheckResult]: if self.pattern.search(line): results.append(CheckResult(i, line.strip())) return results - diff --git a/checks/CheckExposedSystemCalls.py b/checks/CheckExposedSystemCalls.py index cd6c41b..2f749f1 100644 --- a/checks/CheckExposedSystemCalls.py +++ b/checks/CheckExposedSystemCalls.py @@ -1,14 +1,14 @@ -# checks/check_exposed_system_calls.py - import re from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckExposedSystemCalls: title = "Exposed System Call" severity = "High" diff --git a/checks/CheckGenerateSubroutinePool.py b/checks/CheckGenerateSubroutinePool.py index 64590c9..5023711 100644 --- a/checks/CheckGenerateSubroutinePool.py +++ b/checks/CheckGenerateSubroutinePool.py @@ -2,11 +2,13 @@ from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckGenerateSubroutinePool: title = "Critical actions via generating a subroutine pool" severity = "High" diff --git a/checks/CheckGetPersistentByQuery.py b/checks/CheckGetPersistentByQuery.py index 1b37057..bf37909 100644 --- a/checks/CheckGetPersistentByQuery.py +++ b/checks/CheckGetPersistentByQuery.py @@ -2,11 +2,13 @@ from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckGetPersistentByQuery: title = "ADBC Injection via \"get_persistent_by_query\" method" severity = "High" @@ -22,4 +24,3 @@ def run(self, file_content: str) -> List[CheckResult]: if self.pattern.search(line): results.append(CheckResult(i, line.strip())) return results - diff --git a/checks/CheckHardcodedCredentials.py b/checks/CheckHardcodedCredentials.py index bdb6d4f..7353f81 100644 --- a/checks/CheckHardcodedCredentials.py +++ b/checks/CheckHardcodedCredentials.py @@ -1,7 +1,6 @@ import re from dataclasses import dataclass from typing import List -from enum import Enum @dataclass @@ -9,6 +8,7 @@ class CheckResult: line_number: int line_content: str + class CheckHardcodedCredentials: title = "Hard-coded credentials are security-sensitive" severity = "High" diff --git a/checks/CheckHardcodedITIN.py b/checks/CheckHardcodedITIN.py index bef531d..208a933 100644 --- a/checks/CheckHardcodedITIN.py +++ b/checks/CheckHardcodedITIN.py @@ -2,18 +2,21 @@ from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckHardcodedITIN: title = "Hardcoded ITIN" severity = "High" vulnerability_type = "Information Exposure" def __init__(self): - self.pattern = re.compile(r'[^w0-8](?!999999999)(9\d{2})([ \-]?)(([7]\d|8[0-8])|[9][0-24-9])([ \-]?)(\d{4})[^w0-9]') + self.pattern = re.compile( + r'[^w0-8](?!999999999)(9\d{2})([ \-]?)(([7]\d|8[0-8])|[9][0-24-9])([ \-]?)(\d{4})[^w0-9]') def run(self, file_content: str) -> List[CheckResult]: results = [] @@ -23,4 +26,3 @@ def run(self, file_content: str) -> List[CheckResult]: if matches: results.append(CheckResult(i, line.strip())) return results - diff --git a/checks/CheckHardcodedIpAddresses.py b/checks/CheckHardcodedIpAddresses.py index b33b85a..9101bed 100644 --- a/checks/CheckHardcodedIpAddresses.py +++ b/checks/CheckHardcodedIpAddresses.py @@ -1,14 +1,14 @@ -# checks/check_hardcoded_ip_addresses.py - import re from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckHardcodedIpAddresses: title = "Using hardcoded IP addresses is security-sensitive" severity = "Minor" @@ -26,14 +26,14 @@ def __init__(self): def is_exception(self, ip: str) -> bool: exceptions = [ - r'^127\.', # Loopback - r'^255\.255\.255\.255$', # Broadcast - r'^0\.0\.0\.0$', # Non-routable + r'^127\.', # Loopback + r'^255\.255\.255\.255$', # Broadcast + r'^0\.0\.0\.0$', # Non-routable r'^2\.5\.\d{1,3}\.\d{1,3}$', # Potential OID - r'^192\.0\.2\.', # Documentation (RFC 5737) - r'^198\.51\.100\.', # Documentation (RFC 5737) - r'^203\.0\.113\.', # Documentation (RFC 5737) - r'^2001:db8::1' # IPv6 Documentation (RFC 3849) + r'^192\.0\.2\.', # Documentation (RFC 5737) + r'^198\.51\.100\.', # Documentation (RFC 5737) + r'^203\.0\.113\.', # Documentation (RFC 5737) + r'^2001:db8::1' # IPv6 Documentation (RFC 3849) ] return any(re.match(pattern, ip) for pattern in exceptions) @@ -50,4 +50,3 @@ def run(self, file_content: str) -> List[CheckResult]: results.append(CheckResult(i, line.strip())) break # Only report one issue per line return results - diff --git a/checks/CheckHardcodedUrls.py b/checks/CheckHardcodedUrls.py index 339c8c1..e775895 100644 --- a/checks/CheckHardcodedUrls.py +++ b/checks/CheckHardcodedUrls.py @@ -1,14 +1,14 @@ -# checks/check_hardcoded_urls.py - import re from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckHardcodedUrls: title = "Hardcoded URLs detected" severity = "Medium" @@ -17,7 +17,7 @@ class CheckHardcodedUrls: def __init__(self): self.pattern = re.compile( r'\s+["\']https?://\w+.*?$', - re.IGNORECASE| re.IGNORECASE| re.MULTILINE + re.IGNORECASE | re.IGNORECASE | re.MULTILINE ) def run(self, file_content: str) -> List[CheckResult]: diff --git a/checks/CheckHardcodedUserAuth.py b/checks/CheckHardcodedUserAuth.py index dfb0ff0..235069f 100644 --- a/checks/CheckHardcodedUserAuth.py +++ b/checks/CheckHardcodedUserAuth.py @@ -1,5 +1,3 @@ -# checks/check_hardcoded_user_auth.py - import re from dataclasses import dataclass from typing import List @@ -35,4 +33,3 @@ def run(self, file_content: str) -> List[CheckResult]: results.append(CheckResult(i, line.strip())) return results - diff --git a/checks/CheckOSCommandInjectionCFunction.py b/checks/CheckOSCommandInjectionCFunction.py index fe4ca1b..b51702a 100644 --- a/checks/CheckOSCommandInjectionCFunction.py +++ b/checks/CheckOSCommandInjectionCFunction.py @@ -1,14 +1,14 @@ -# checks/check_os_command_injection_c_function.py - import re from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckOSCommandInjectionCFunction: title = "Potential OS Command injection detected - C Function" severity = "High" diff --git a/checks/CheckOSCommandInjectionCallSystem.py b/checks/CheckOSCommandInjectionCallSystem.py index bf5eb8a..ce6f6aa 100644 --- a/checks/CheckOSCommandInjectionCallSystem.py +++ b/checks/CheckOSCommandInjectionCallSystem.py @@ -1,5 +1,3 @@ -# checks/check_os_command_injection_call_system.py - import re from dataclasses import dataclass from typing import List diff --git a/checks/CheckOSCommandInjectionClientOS.py b/checks/CheckOSCommandInjectionClientOS.py index 67ff96e..f2da536 100644 --- a/checks/CheckOSCommandInjectionClientOS.py +++ b/checks/CheckOSCommandInjectionClientOS.py @@ -2,11 +2,13 @@ from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckOSCommandInjectionClientOS: title = "Potential OS Command injection detected - GUI Function" severity = "High" @@ -24,4 +26,3 @@ def run(self, file_content: str) -> List[CheckResult]: line_number = file_content[:match.start()].count('\n') + 1 results.append(CheckResult(line_number, match.group().strip())) return results - diff --git a/checks/CheckOSCommandInjectionOpenDatasetFilter.py b/checks/CheckOSCommandInjectionOpenDatasetFilter.py index e6bcf6d..2bf5197 100644 --- a/checks/CheckOSCommandInjectionOpenDatasetFilter.py +++ b/checks/CheckOSCommandInjectionOpenDatasetFilter.py @@ -1,14 +1,14 @@ -# checks/check_os_command_injection_open_dataset_filter.py - import re from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckOSCommandInjectionOpenDatasetFilter: title = "Potential OS Command injection detected - OPEN DATASET FILTER" severity = "High" diff --git a/checks/CheckOSCommandInjectionRfcRemoteExec.py b/checks/CheckOSCommandInjectionRfcRemoteExec.py index 3ac05d5..aa603a3 100644 --- a/checks/CheckOSCommandInjectionRfcRemoteExec.py +++ b/checks/CheckOSCommandInjectionRfcRemoteExec.py @@ -1,14 +1,14 @@ -# checks/check_os_command_injection_rfc_remote_exec.py - import re from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckOSCommandInjectionRfcRemoteExec: title = "Potential OS Command injection detected - RFC_REMOTE_EXEC" severity = "High" diff --git a/checks/CheckOSCommandInjectionRfcRemotePipe.py b/checks/CheckOSCommandInjectionRfcRemotePipe.py index 0079a7a..74c2779 100644 --- a/checks/CheckOSCommandInjectionRfcRemotePipe.py +++ b/checks/CheckOSCommandInjectionRfcRemotePipe.py @@ -1,14 +1,14 @@ -# checks/check_os_command_injection_rfc_remote_pipe.py - import re from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckOSCommandInjectionRfcRemotePipe: title = "Potential OS Command injection detected - RFC_REMOTE_PIPE" severity = "High" diff --git a/checks/CheckOSCommandInjectionSxpg.py b/checks/CheckOSCommandInjectionSxpg.py index 9a04607..9c5e9bb 100644 --- a/checks/CheckOSCommandInjectionSxpg.py +++ b/checks/CheckOSCommandInjectionSxpg.py @@ -1,14 +1,14 @@ -# checks/check_os_command_injection_sxpg.py - import re from dataclasses import dataclass from typing import List + @dataclass class CheckResult: line_number: int line_content: str + class CheckOSCommandInjectionSxpg: title = "Potential OS Command injection detected - SXPG Function" severity = "High" diff --git a/checks/CheckWeakHashingAlgorithms.py b/checks/CheckWeakHashingAlgorithms.py index 26963c0..461008f 100644 --- a/checks/CheckWeakHashingAlgorithms.py +++ b/checks/CheckWeakHashingAlgorithms.py @@ -1,5 +1,3 @@ -# checks/check_weak_hashing_algorithms.py - import re from dataclasses import dataclass from typing import List @@ -32,5 +30,3 @@ def run(self, file_content: str) -> List[CheckResult]: if self.pattern.search(line): results.append(CheckResult(i, line.strip())) return results - - diff --git a/checks/__init__.py b/checks/__init__.py index 20b98da..30574e1 100644 --- a/checks/__init__.py +++ b/checks/__init__.py @@ -1,36 +1,34 @@ -# checks/__init__.py - -from .CheckCrossSiteScripting import CheckCrossSiteScripting +from .CheckAbapOutgoingFtpConn import CheckAbapOutgoingFtpConn from .CheckBrokenAuthCheck import CheckBrokenAuthCheck -from .CheckDirectoryTraversalCallAlerts import CheckDirectoryTraversalCallAlerts +from .CheckCallTransformation import CheckCallTransformation +from .CheckCrossSiteScripting import CheckCrossSiteScripting +from .CheckDangerousAbapCommands import CheckDangerousAbapCommands +from .CheckDeleteDynpro import CheckDeleteDynpro from .CheckDirectoryTraversalCRstrbReadBuffered import CheckDirectoryTraversalCRstrbReadBuffered +from .CheckDirectoryTraversalCallAlerts import CheckDirectoryTraversalCallAlerts +from .CheckDirectoryTraversalDeleteDataset import CheckDirectoryTraversalDeleteDataset from .CheckDirectoryTraversalReadDataset import CheckDirectoryTraversalReadDataset from .CheckDirectoryTraversalRfcRemoteFile import CheckDirectoryTraversalRfcRemoteFile -from .CheckDirectoryTraversalDeleteDataset import CheckDirectoryTraversalDeleteDataset +from .CheckDirectoryTraversalTransfer import CheckDirectoryTraversalTransfer from .CheckDosInDoLoop import CheckDosInDoLoop +from .CheckDummyAuthCheck import CheckDummyAuthCheck +from .CheckExecuteProcedure import CheckExecuteProcedure from .CheckExposedSystemCalls import CheckExposedSystemCalls -from .CheckOSCommandInjectionCallSystem import CheckOSCommandInjectionCallSystem -from .CheckOSCommandInjectionCFunction import CheckOSCommandInjectionCFunction +from .CheckGenerateSubroutinePool import CheckGenerateSubroutinePool +from .CheckGetPersistentByQuery import CheckGetPersistentByQuery from .CheckHardcodedCredentials import CheckHardcodedCredentials +from .CheckHardcodedITIN import CheckHardcodedITIN +from .CheckHardcodedIpAddresses import CheckHardcodedIpAddresses +from .CheckHardcodedUrls import CheckHardcodedUrls +from .CheckHardcodedUserAuth import CheckHardcodedUserAuth +from .CheckOSCommandInjectionCFunction import CheckOSCommandInjectionCFunction +from .CheckOSCommandInjectionCallSystem import CheckOSCommandInjectionCallSystem from .CheckOSCommandInjectionClientOS import CheckOSCommandInjectionClientOS -from .CheckDummyAuthCheck import CheckDummyAuthCheck from .CheckOSCommandInjectionOpenDatasetFilter import CheckOSCommandInjectionOpenDatasetFilter from .CheckOSCommandInjectionRfcRemoteExec import CheckOSCommandInjectionRfcRemoteExec from .CheckOSCommandInjectionRfcRemotePipe import CheckOSCommandInjectionRfcRemotePipe -from .CheckHardcodedUrls import CheckHardcodedUrls from .CheckOSCommandInjectionSxpg import CheckOSCommandInjectionSxpg -from .CheckDangerousAbapCommands import CheckDangerousAbapCommands -from .CheckAbapOutgoingFtpConn import CheckAbapOutgoingFtpConn from .CheckWeakHashingAlgorithms import CheckWeakHashingAlgorithms -from .CheckHardcodedIpAddresses import CheckHardcodedIpAddresses -from .CheckHardcodedUserAuth import CheckHardcodedUserAuth -from .CheckDeleteDynpro import CheckDeleteDynpro -from .CheckCallTransformation import CheckCallTransformation -from .CheckGetPersistentByQuery import CheckGetPersistentByQuery -from .CheckExecuteProcedure import CheckExecuteProcedure -from .CheckGenerateSubroutinePool import CheckGenerateSubroutinePool -from .CheckDirectoryTraversalTransfer import CheckDirectoryTraversalTransfer -from .CheckHardcodedITIN import CheckHardcodedITIN __all__ = [ 'CheckCrossSiteScripting', @@ -64,4 +62,4 @@ 'CheckGenerateSubroutinePool', 'CheckDirectoryTraversalTransfer', 'CheckHardcodedITIN' -] \ No newline at end of file +] diff --git a/config.py b/config.py index 4ab4176..a0e2bbb 100644 --- a/config.py +++ b/config.py @@ -2,6 +2,7 @@ import yaml + class Config: def __init__(self, config_file): with open(config_file, 'r') as f: @@ -14,4 +15,4 @@ def get_file_extensions(self): return self.config.get('file_extensions', ['.abap']) def get_exclude_patterns(self): - return self.config.get('exclude_patterns', []) \ No newline at end of file + return self.config.get('exclude_patterns', []) diff --git a/config.yml b/config.yml index 707cd24..6105a76 100644 --- a/config.yml +++ b/config.yml @@ -35,7 +35,7 @@ checks: file_extensions: - .txt - - .DECOMP + - .abap exclude_patterns: - "**/test/**" \ No newline at end of file diff --git a/generate_xlsx_report.py b/generate_xlsx_report.py index 76889f8..433db1f 100644 --- a/generate_xlsx_report.py +++ b/generate_xlsx_report.py @@ -1,8 +1,10 @@ +from dataclasses import dataclass +from typing import List + import openpyxl from openpyxl.styles import Font, PatternFill, Alignment from openpyxl.utils import get_column_letter -from typing import List, Dict -from dataclasses import dataclass + @dataclass class ScanResult: @@ -12,6 +14,7 @@ class ScanResult: message: str severity: str + def generate_xlsx_report(results: List[ScanResult], output_file: str): wb = openpyxl.Workbook() ws = wb.active @@ -25,10 +28,10 @@ def generate_xlsx_report(results: List[ScanResult], output_file: str): severity_colors = { "Critical": "FF0000", # Red - "High": "FFA500", # Orange - "Medium": "FFFF00", # Yellow - "Low": "90EE90", # Light Green - "Info": "ADD8E6" # Light Blue + "High": "FFA500", # Orange + "Medium": "FFFF00", # Yellow + "Low": "90EE90", # Light Green + "Info": "ADD8E6" # Light Blue } # Write headers @@ -68,6 +71,7 @@ def generate_xlsx_report(results: List[ScanResult], output_file: str): # Save the workbook wb.save(output_file) + # Example usage if __name__ == "__main__": # Sample data @@ -80,4 +84,4 @@ def generate_xlsx_report(results: List[ScanResult], output_file: str): ] generate_xlsx_report(sample_results, "security_scan_report.xlsx") - print("XLSX report generated successfully.") \ No newline at end of file + print("XLSX report generated successfully.") diff --git a/main.py b/main.py index 0198106..3b39d61 100644 --- a/main.py +++ b/main.py @@ -1,23 +1,22 @@ # main.py import argparse -from email.policy import default -from scanner import Scanner from config import Config from generate_xlsx_report import generate_xlsx_report, ScanResult +from scanner import Scanner def main(): parser = argparse.ArgumentParser(description="ABAP Code Scanner") - #parser.add_argument("path", default=r"C:\Users\admin\Desktop\research\SAP_ABAP_DEMO_CODE", help="Path to the ABAP code directory or file") + parser.add_argument("path", help="Path to the ABAP code directory or file") parser.add_argument("-c", "--config", help="Path to configuration file", default="config.yml") args = parser.parse_args() config = Config(args.config) scanner = Scanner(config) - results = scanner.scan(r"C:\Users\admin\Desktop\research\SAP_ABAP_DEMO_CODE") #args.path) + results = scanner.scan(args.path) # Convert scanner results to ScanResult objects, now including severity report_results = [ @@ -35,9 +34,7 @@ def main(): print("Scan complete. XLSX report generated: abap_security_scan_report.xlsx") - """ for result in results: - print(f"{result.file_path}\r\nLine:{result.line_number} - {result.check_name}: {result.message}") - """ + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..f00836d --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +python_files = Test*.py +python_classes = Test* +python_functions = test_* \ No newline at end of file diff --git a/run_tests.bat b/run_tests.bat new file mode 100644 index 0000000..47b247d --- /dev/null +++ b/run_tests.bat @@ -0,0 +1,3 @@ +@echo off +set PYTHONPATH=%PYTHONPATH%;%cd% +pytest tests/ -v --cov=./ --cov-report=xml \ No newline at end of file diff --git a/run_tests.sh b/run_tests.sh new file mode 100644 index 0000000..ee9330d --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,3 @@ +#!/bin/bash +export PYTHONPATH=$PYTHONPATH:$(pwd) +pytest tests/ -v --cov=./ --cov-report=xml \ No newline at end of file diff --git a/scanner.py b/scanner.py index 9341e4d..828ff05 100644 --- a/scanner.py +++ b/scanner.py @@ -1,9 +1,10 @@ # scanner.py -import os import importlib +import os from typing import List, NamedTuple + class ScanResult(NamedTuple): file_path: str line_number: int @@ -51,4 +52,4 @@ def _scan_file(self, file_path: str) -> List[ScanResult]: message=result.line_content, severity=check.severity )) - return results \ No newline at end of file + return results diff --git a/tests/TestCheckAbapOutgoingFtpConn.py b/tests/TestCheckAbapOutgoingFtpConn.py index e1731bf..cec5262 100644 --- a/tests/TestCheckAbapOutgoingFtpConn.py +++ b/tests/TestCheckAbapOutgoingFtpConn.py @@ -1,7 +1,7 @@ -# tests/test_check_abap_outgoing_ftp_conn.py - import unittest -from checks.CheckAbapOutgoingFtpConn import CheckAbapOutgoingFtpConn, CheckResult + +from checks.CheckAbapOutgoingFtpConn import CheckAbapOutgoingFtpConn + class TestCheckAbapOutgoingFtpConn(unittest.TestCase): @@ -57,5 +57,6 @@ def test_multiline_function_call(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 2) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckBrokenAuthCheck.py b/tests/TestCheckBrokenAuthCheck.py index 92bde07..60b6626 100644 --- a/tests/TestCheckBrokenAuthCheck.py +++ b/tests/TestCheckBrokenAuthCheck.py @@ -1,7 +1,7 @@ -# tests/test_check_broken_auth_check.py - import unittest -from checks.CheckBrokenAuthCheck import CheckBrokenAuthCheck, CheckResult + +from checks.CheckBrokenAuthCheck import CheckBrokenAuthCheck + class TestCheckBrokenAuthCheck(unittest.TestCase): @@ -78,5 +78,6 @@ def test_auth_check_with_syst_subrc(self): results = self.checker.run(code) self.assertEqual(len(results), 0) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckCallTransformation.py b/tests/TestCheckCallTransformation.py index 9cba0d7..83d097f 100644 --- a/tests/TestCheckCallTransformation.py +++ b/tests/TestCheckCallTransformation.py @@ -1,5 +1,7 @@ import unittest -from checks.CheckCallTransformation import CheckCallTransformation, CheckResult + +from checks.CheckCallTransformation import CheckCallTransformation + class TestCheckCallTransformation(unittest.TestCase): @@ -54,5 +56,6 @@ def test_call_transformation_in_comment(self): results = self.checker.run(code) self.assertEqual(len(results), 0) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckCrossSiteScripting.py b/tests/TestCheckCrossSiteScripting.py index 6462f00..0e49f69 100644 --- a/tests/TestCheckCrossSiteScripting.py +++ b/tests/TestCheckCrossSiteScripting.py @@ -1,7 +1,7 @@ -# tests/test_check_cross_site_scripting.py - import unittest -from checks.CheckCrossSiteScripting import CheckCrossSiteScripting, CheckResult + +from checks.CheckCrossSiteScripting import CheckCrossSiteScripting + class TestCheckCrossSiteScripting(unittest.TestCase): @@ -52,5 +52,6 @@ def test_false_positive(self): results = self.checker.run(code) self.assertEqual(len(results), 0) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckDangerousAbapCommands.py b/tests/TestCheckDangerousAbapCommands.py index 26fd2c7..5a1dcd3 100644 --- a/tests/TestCheckDangerousAbapCommands.py +++ b/tests/TestCheckDangerousAbapCommands.py @@ -1,7 +1,7 @@ -# tests/test_check_dangerous_abap_commands.py - import unittest -from checks.CheckDangerousAbapCommands import CheckDangerousAbapCommands, CheckResult + +from checks.CheckDangerousAbapCommands import CheckDangerousAbapCommands + class TestCheckDangerousAbapCommands(unittest.TestCase): @@ -58,5 +58,6 @@ def test_multiline_statement(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckDeleteDynpro.py b/tests/TestCheckDeleteDynpro.py index d453c6c..595bfa4 100644 --- a/tests/TestCheckDeleteDynpro.py +++ b/tests/TestCheckDeleteDynpro.py @@ -1,5 +1,7 @@ import unittest -from checks.CheckDeleteDynpro import CheckDeleteDynpro, CheckResult + +from checks.CheckDeleteDynpro import CheckDeleteDynpro + class TestCheckDeleteDynpro(unittest.TestCase): @@ -60,5 +62,6 @@ def test_delete_dynpro_in_comment(self): results = self.checker.run(code) self.assertEqual(len(results), 0) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckDirectoryTraversalCRstrbReadBuffered.py b/tests/TestCheckDirectoryTraversalCRstrbReadBuffered.py index ad12360..35cc9ed 100644 --- a/tests/TestCheckDirectoryTraversalCRstrbReadBuffered.py +++ b/tests/TestCheckDirectoryTraversalCRstrbReadBuffered.py @@ -1,7 +1,8 @@ -# tests/test_check_directory_traversal_c_rstrb_read_buffered.py - import unittest -from checks.CheckDirectoryTraversalCRstrbReadBuffered import CheckDirectoryTraversalCRstrbReadBuffered, CheckResult + +from checks.CheckDirectoryTraversalCRstrbReadBuffered import CheckDirectoryTraversalCRstrbReadBuffered + + class TestCheckDirectoryTraversalCRstrbReadBuffered(unittest.TestCase): def setUp(self): @@ -52,5 +53,6 @@ def test_case_insensitivity(self): results = self.checker.run(code) self.assertEqual(len(results), 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckDirectoryTraversalCallAlerts.py b/tests/TestCheckDirectoryTraversalCallAlerts.py index 8f85542..c49165b 100644 --- a/tests/TestCheckDirectoryTraversalCallAlerts.py +++ b/tests/TestCheckDirectoryTraversalCallAlerts.py @@ -1,5 +1,6 @@ import unittest -from checks.CheckDirectoryTraversalCallAlerts import CheckDirectoryTraversalCallAlerts, CheckResult + +from checks.CheckDirectoryTraversalCallAlerts import CheckDirectoryTraversalCallAlerts class TestCheckABAPDirectoryTraversalAlerts(unittest.TestCase): @@ -72,7 +73,7 @@ def test_multiple_calls(self): ID 'TYPE' FIELD 'DATA' ID 'RECTYP' FIELD 'U------' ID 'RC' FIELD _RC - ID 'ERRMSG' FIELD ERRMSG. + ID 'ERRMSG' FIELD ERRMSG. """ results = self.checker.run(code) self.assertEqual(len(results), 1) @@ -109,5 +110,6 @@ def test_multiline_call(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 2) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckDirectoryTraversalDeleteDataset.py b/tests/TestCheckDirectoryTraversalDeleteDataset.py index 97e6d5a..1731b48 100644 --- a/tests/TestCheckDirectoryTraversalDeleteDataset.py +++ b/tests/TestCheckDirectoryTraversalDeleteDataset.py @@ -1,7 +1,7 @@ -# tests/test_check_directory_traversal_delete_dataset.py - import unittest -from checks.CheckDirectoryTraversalDeleteDataset import CheckDirectoryTraversalDeleteDataset, CheckResult + +from checks.CheckDirectoryTraversalDeleteDataset import CheckDirectoryTraversalDeleteDataset + class TestCheckDirectoryTraversalDeleteDataset(unittest.TestCase): @@ -72,5 +72,6 @@ def test_delete_dataset_with_options(self): self.assertEqual(results[0].line_number, 3) self.assertIn("DELETE DATASET file", results[0].line_content) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckDirectoryTraversalReadDataset.py b/tests/TestCheckDirectoryTraversalReadDataset.py index e9b15cf..988c356 100644 --- a/tests/TestCheckDirectoryTraversalReadDataset.py +++ b/tests/TestCheckDirectoryTraversalReadDataset.py @@ -1,7 +1,7 @@ -# tests/test_check_directory_traversal_read_dataset.py - import unittest -from checks.CheckDirectoryTraversalReadDataset import CheckDirectoryTraversalReadDataset, CheckResult + +from checks.CheckDirectoryTraversalReadDataset import CheckDirectoryTraversalReadDataset + class TestCheckDirectoryTraversalReadDataset(unittest.TestCase): @@ -97,5 +97,6 @@ def test_case_insensitivity(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckDirectoryTraversalRfcRemoteFile.py b/tests/TestCheckDirectoryTraversalRfcRemoteFile.py index cb01ce7..ac3c2d9 100644 --- a/tests/TestCheckDirectoryTraversalRfcRemoteFile.py +++ b/tests/TestCheckDirectoryTraversalRfcRemoteFile.py @@ -1,7 +1,7 @@ -# tests/test_check_directory_traversal_rfc_remote_file.py - import unittest -from checks.CheckDirectoryTraversalRfcRemoteFile import CheckDirectoryTraversalRfcRemoteFile, CheckResult + +from checks.CheckDirectoryTraversalRfcRemoteFile import CheckDirectoryTraversalRfcRemoteFile + class TestCheckDirectoryTraversalRfcRemoteFile(unittest.TestCase): @@ -71,5 +71,6 @@ def test_multiline_call(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckDirectoryTraversalTransfer.py b/tests/TestCheckDirectoryTraversalTransfer.py index c386b02..b620f97 100644 --- a/tests/TestCheckDirectoryTraversalTransfer.py +++ b/tests/TestCheckDirectoryTraversalTransfer.py @@ -1,5 +1,7 @@ import unittest -from checks.CheckDirectoryTraversalTransfer import CheckDirectoryTraversalTransfer, CheckResult + +from checks.CheckDirectoryTraversalTransfer import CheckDirectoryTraversalTransfer + class TestCheckDirectoryTraversalTransfer(unittest.TestCase): @@ -68,5 +70,6 @@ def test_transfer_in_comment(self): results = self.checker.run(code) self.assertEqual(len(results), 0) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckDosInDoLoop.py b/tests/TestCheckDosInDoLoop.py index cc80f74..f6c9b93 100644 --- a/tests/TestCheckDosInDoLoop.py +++ b/tests/TestCheckDosInDoLoop.py @@ -1,7 +1,7 @@ -# tests/test_check_dos_in_do_loop.py - import unittest -from checks.CheckDosInDoLoop import CheckDosInDoLoop, CheckResult + +from checks.CheckDosInDoLoop import CheckDosInDoLoop + class TestCheckDosInDoLoop(unittest.TestCase): @@ -77,5 +77,6 @@ def test_multiline_do_statement(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckDummyAuthCheck.py b/tests/TestCheckDummyAuthCheck.py index bb17c3e..926ca40 100644 --- a/tests/TestCheckDummyAuthCheck.py +++ b/tests/TestCheckDummyAuthCheck.py @@ -1,7 +1,7 @@ -# tests/test_check_dummy_auth_check.py - import unittest -from checks.CheckDummyAuthCheck import CheckDummyAuthCheck, CheckResult + +from checks.CheckDummyAuthCheck import CheckDummyAuthCheck + class TestCheckDummyAuthCheck(unittest.TestCase): @@ -74,5 +74,6 @@ def test_multiline_auth_check(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckExecuteProcedure.py b/tests/TestCheckExecuteProcedure.py index 1781dc2..5186687 100644 --- a/tests/TestCheckExecuteProcedure.py +++ b/tests/TestCheckExecuteProcedure.py @@ -1,5 +1,7 @@ import unittest -from checks.CheckExecuteProcedure import CheckExecuteProcedure, CheckResult + +from checks.CheckExecuteProcedure import CheckExecuteProcedure + class TestCheckExecuteProcedure(unittest.TestCase): @@ -50,5 +52,6 @@ def test_method_in_string(self): results = self.checker.run(code) self.assertEqual(len(results), 1) # Note: This detects the method call even in strings + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckExposedSystemCalls.py b/tests/TestCheckExposedSystemCalls.py index aa8749e..8e64a93 100644 --- a/tests/TestCheckExposedSystemCalls.py +++ b/tests/TestCheckExposedSystemCalls.py @@ -1,7 +1,7 @@ -# tests/test_check_exposed_system_calls.py - import unittest -from checks.CheckExposedSystemCalls import CheckExposedSystemCalls, CheckResult + +from checks.CheckExposedSystemCalls import CheckExposedSystemCalls + class TestCheckExposedSystemCalls(unittest.TestCase): @@ -83,5 +83,6 @@ def test_multiline_system_call(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckGenerateSubroutinePool.py b/tests/TestCheckGenerateSubroutinePool.py index c409774..ba17a38 100644 --- a/tests/TestCheckGenerateSubroutinePool.py +++ b/tests/TestCheckGenerateSubroutinePool.py @@ -1,5 +1,7 @@ import unittest -from checks.CheckGenerateSubroutinePool import CheckGenerateSubroutinePool, CheckResult + +from checks.CheckGenerateSubroutinePool import CheckGenerateSubroutinePool + class TestCheckGenerateSubroutinePool(unittest.TestCase): @@ -56,5 +58,6 @@ def test_mid_line_occurrence(self): results = self.checker.run(code) self.assertEqual(len(results), 0) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckGetPersistentByQuery.py b/tests/TestCheckGetPersistentByQuery.py index 3c7298a..91164d5 100644 --- a/tests/TestCheckGetPersistentByQuery.py +++ b/tests/TestCheckGetPersistentByQuery.py @@ -1,5 +1,7 @@ import unittest -from checks.CheckGetPersistentByQuery import CheckGetPersistentByQuery, CheckResult + +from checks.CheckGetPersistentByQuery import CheckGetPersistentByQuery + class TestCheckGetPersistentByQuery(unittest.TestCase): @@ -50,5 +52,6 @@ def test_method_in_string(self): results = self.checker.run(code) self.assertEqual(len(results), 1) # Note: This detects the method call even in strings + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckHardcodedCredentials.py b/tests/TestCheckHardcodedCredentials.py index 50906aa..ff1d50e 100644 --- a/tests/TestCheckHardcodedCredentials.py +++ b/tests/TestCheckHardcodedCredentials.py @@ -1,7 +1,7 @@ -# tests/test_check_hardcoded_credentials.py - import unittest -from checks.CheckHardcodedCredentials import CheckHardcodedCredentials, CheckResult + +from checks.CheckHardcodedCredentials import CheckHardcodedCredentials + class TestCheckHardcodedCredentials(unittest.TestCase): @@ -71,5 +71,6 @@ def test_multiline_assignment(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 2) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckHardcodedITIN.py b/tests/TestCheckHardcodedITIN.py index 31f843e..76a1c50 100644 --- a/tests/TestCheckHardcodedITIN.py +++ b/tests/TestCheckHardcodedITIN.py @@ -1,5 +1,7 @@ import unittest -from checks.CheckHardcodedITIN import CheckHardcodedITIN, CheckResult + +from checks.CheckHardcodedITIN import CheckHardcodedITIN + class TestCheckHardcodedITIN(unittest.TestCase): @@ -51,5 +53,6 @@ def test_no_itin(self): results = self.checker.run(code) self.assertEqual(len(results), 0) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckHardcodedIpAddresses.py b/tests/TestCheckHardcodedIpAddresses.py index c106c60..f09bc2c 100644 --- a/tests/TestCheckHardcodedIpAddresses.py +++ b/tests/TestCheckHardcodedIpAddresses.py @@ -1,7 +1,7 @@ -# tests/test_check_hardcoded_ip_addresses.py - import unittest -from checks.CheckHardcodedIpAddresses import CheckHardcodedIpAddresses, CheckResult + +from checks.CheckHardcodedIpAddresses import CheckHardcodedIpAddresses + class TestCheckHardcodedIpAddresses(unittest.TestCase): @@ -49,5 +49,6 @@ def test_ip_in_string(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckHardcodedUrls.py b/tests/TestCheckHardcodedUrls.py index c3a3f2b..49c000a 100644 --- a/tests/TestCheckHardcodedUrls.py +++ b/tests/TestCheckHardcodedUrls.py @@ -1,7 +1,7 @@ -# tests/test_check_hardcoded_urls.py - import unittest -from checks.CheckHardcodedUrls import CheckHardcodedUrls, CheckResult + +from checks.CheckHardcodedUrls import CheckHardcodedUrls + class TestCheckHardcodedUrls(unittest.TestCase): @@ -59,5 +59,6 @@ def test_multiple_urls_one_line(self): self.assertIn("http://example1.com", results[0].line_content) self.assertIn("https://example2.com", results[0].line_content) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckHardcodedUserAuth.py b/tests/TestCheckHardcodedUserAuth.py index 17aecf4..dbdb73e 100644 --- a/tests/TestCheckHardcodedUserAuth.py +++ b/tests/TestCheckHardcodedUserAuth.py @@ -1,7 +1,7 @@ -# tests/test_check_hardcoded_user_auth.py - import unittest -from checks.CheckHardcodedUserAuth import CheckHardcodedUserAuth, CheckResult + +from checks.CheckHardcodedUserAuth import CheckHardcodedUserAuth + class TestCheckHardcodedUserAuth(unittest.TestCase): @@ -72,5 +72,6 @@ def test_case_insensitivity(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckOSCommandInjectionCFunction.py b/tests/TestCheckOSCommandInjectionCFunction.py index ca08650..71a4677 100644 --- a/tests/TestCheckOSCommandInjectionCFunction.py +++ b/tests/TestCheckOSCommandInjectionCFunction.py @@ -1,7 +1,7 @@ -# tests/test_check_os_command_injection_c_function.py - import unittest -from checks.CheckOSCommandInjectionCFunction import CheckOSCommandInjectionCFunction, CheckResult + +from checks.CheckOSCommandInjectionCFunction import CheckOSCommandInjectionCFunction + class TestCheckOSCommandInjectionCFunction(unittest.TestCase): @@ -69,5 +69,6 @@ def test_no_field_parameter(self): results = self.checker.run(code) self.assertEqual(len(results), 0) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckOSCommandInjectionCallSystem.py b/tests/TestCheckOSCommandInjectionCallSystem.py index 1b77f0c..08386c4 100644 --- a/tests/TestCheckOSCommandInjectionCallSystem.py +++ b/tests/TestCheckOSCommandInjectionCallSystem.py @@ -1,7 +1,6 @@ -# tests/test_check_os_command_injection_call_system.py - import unittest -from checks.CheckOSCommandInjectionCallSystem import CheckOSCommandInjectionCallSystem, CheckResult + +from checks.CheckOSCommandInjectionCallSystem import CheckOSCommandInjectionCallSystem class TestCheckOSCommandInjectionCallSystem(unittest.TestCase): @@ -78,4 +77,4 @@ def test_multiline_call(self): if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckOSCommandInjectionClientOS.py b/tests/TestCheckOSCommandInjectionClientOS.py index cc88a90..810f55e 100644 --- a/tests/TestCheckOSCommandInjectionClientOS.py +++ b/tests/TestCheckOSCommandInjectionClientOS.py @@ -1,7 +1,7 @@ -# tests/test_check_os_command_injection_client_os.py - import unittest -from checks.CheckOSCommandInjectionClientOS import CheckOSCommandInjectionClientOS, CheckResult + +from checks.CheckOSCommandInjectionClientOS import CheckOSCommandInjectionClientOS + class TestCheckOSCommandInjectionClientOS(unittest.TestCase): @@ -83,5 +83,6 @@ def test_multiline_call(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 3) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckOSCommandInjectionOpenDatasetFilter.py b/tests/TestCheckOSCommandInjectionOpenDatasetFilter.py index 4af3b1f..790c0d3 100644 --- a/tests/TestCheckOSCommandInjectionOpenDatasetFilter.py +++ b/tests/TestCheckOSCommandInjectionOpenDatasetFilter.py @@ -1,7 +1,7 @@ -# tests/test_check_os_command_injection_open_dataset_filter.py - import unittest -from checks.CheckOSCommandInjectionOpenDatasetFilter import CheckOSCommandInjectionOpenDatasetFilter, CheckResult + +from checks.CheckOSCommandInjectionOpenDatasetFilter import CheckOSCommandInjectionOpenDatasetFilter + class TestCheckOSCommandInjectionOpenDatasetFilter(unittest.TestCase): @@ -70,5 +70,6 @@ def test_filter_without_open_dataset(self): results = self.checker.run(code) self.assertEqual(len(results), 0) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckOSCommandInjectionRfcRemoteExec.py b/tests/TestCheckOSCommandInjectionRfcRemoteExec.py index fe29205..7102acd 100644 --- a/tests/TestCheckOSCommandInjectionRfcRemoteExec.py +++ b/tests/TestCheckOSCommandInjectionRfcRemoteExec.py @@ -1,7 +1,7 @@ -# tests/test_check_os_command_injection_rfc_remote_exec.py - import unittest -from checks.CheckOSCommandInjectionRfcRemoteExec import CheckOSCommandInjectionRfcRemoteExec, CheckResult + +from checks.CheckOSCommandInjectionRfcRemoteExec import CheckOSCommandInjectionRfcRemoteExec + class TestCheckOSCommandInjectionRfcRemoteExec(unittest.TestCase): @@ -80,5 +80,6 @@ def test_multiline_call(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckOSCommandInjectionRfcRemotePipe.py b/tests/TestCheckOSCommandInjectionRfcRemotePipe.py index 597a422..560446f 100644 --- a/tests/TestCheckOSCommandInjectionRfcRemotePipe.py +++ b/tests/TestCheckOSCommandInjectionRfcRemotePipe.py @@ -1,14 +1,13 @@ -# tests/test_check_os_command_injection_rfc_remote_pipe.py - import unittest -from checks.CheckOSCommandInjectionRfcRemotePipe import CheckOSCommandInjectionRfcRemotePipe, CheckResult + +from checks.CheckOSCommandInjectionRfcRemotePipe import CheckOSCommandInjectionRfcRemotePipe + class TestCheckOSCommandInjectionRfcRemotePipe(unittest.TestCase): def setUp(self): self.checker = CheckOSCommandInjectionRfcRemotePipe() - def test_vulnerable_rfc_remote_pipe(self): code = """ CALL FUNCTION 'RFC_REMOTE_PIPE' DESTINATION DEST @@ -81,5 +80,6 @@ def test_multiline_call(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckOSCommandInjectionSxpg.py b/tests/TestCheckOSCommandInjectionSxpg.py index 7596b65..162dcbf 100644 --- a/tests/TestCheckOSCommandInjectionSxpg.py +++ b/tests/TestCheckOSCommandInjectionSxpg.py @@ -1,7 +1,7 @@ -# tests/test_check_os_command_injection_sxpg.py - import unittest -from checks.CheckOSCommandInjectionSxpg import CheckOSCommandInjectionSxpg, CheckResult + +from checks.CheckOSCommandInjectionSxpg import CheckOSCommandInjectionSxpg + class TestCheckOSCommandInjectionSxpg(unittest.TestCase): @@ -79,5 +79,6 @@ def test_multiline_call(self): self.assertEqual(len(results), 1) self.assertEqual(results[0].line_number, 1) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/TestCheckWeakHashingAlgorithms.py b/tests/TestCheckWeakHashingAlgorithms.py index 06158cd..c2831d8 100644 --- a/tests/TestCheckWeakHashingAlgorithms.py +++ b/tests/TestCheckWeakHashingAlgorithms.py @@ -1,14 +1,13 @@ -# tests/test_check_weak_hashing_algorithms.py - import unittest -from checks.CheckWeakHashingAlgorithms import CheckWeakHashingAlgorithms, CheckResult + +from checks.CheckWeakHashingAlgorithms import CheckWeakHashingAlgorithms + class TestCheckWeakHashingAlgorithms(unittest.TestCase): def setUp(self): self.checker = CheckWeakHashingAlgorithms() - def test_all_weak_algorithms(self): weak_algorithms = [ "MD2", "MD4", "MD5", "MD6", "HAVAL128", "HMACMD5", @@ -52,5 +51,6 @@ def test_no_false_positives(self): results = self.checker.run(code) self.assertEqual(len(results), 0) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main()