From 60015198dc8ed44efbfe36b3356924582f1be7ab Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Wed, 14 Jan 2026 16:03:21 +0100 Subject: [PATCH 1/5] Create odoo-mysql docker app --- sample-apps/odoo-mysql/Dockerfile | 42 +++ sample-apps/odoo-mysql/Makefile | 56 ++++ sample-apps/odoo-mysql/README.md | 136 ++++++++++ sample-apps/odoo-mysql/app.py | 301 ++++++++++++++++++++++ sample-apps/odoo-mysql/docker-compose.yml | 102 ++++++++ sample-apps/odoo-mysql/entrypoint.sh | 24 ++ sample-apps/odoo-mysql/odoo.conf | 14 + sample-apps/odoo-mysql/pyproject.toml | 24 ++ 8 files changed, 699 insertions(+) create mode 100644 sample-apps/odoo-mysql/Dockerfile create mode 100644 sample-apps/odoo-mysql/Makefile create mode 100644 sample-apps/odoo-mysql/README.md create mode 100644 sample-apps/odoo-mysql/app.py create mode 100644 sample-apps/odoo-mysql/docker-compose.yml create mode 100755 sample-apps/odoo-mysql/entrypoint.sh create mode 100644 sample-apps/odoo-mysql/odoo.conf create mode 100644 sample-apps/odoo-mysql/pyproject.toml diff --git a/sample-apps/odoo-mysql/Dockerfile b/sample-apps/odoo-mysql/Dockerfile new file mode 100644 index 000000000..66f104c49 --- /dev/null +++ b/sample-apps/odoo-mysql/Dockerfile @@ -0,0 +1,42 @@ +FROM odoo:16.0 + +USER root + +# Install system dependencies for MySQL client and Python packages +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + default-libmysqlclient-dev \ + build-essential \ + pkg-config \ + python3-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Python packages +RUN pip3 install --no-cache-dir \ + mysqlclient \ + requests \ + sentry-sdk + +# Copy custom addons from the correct path +COPY ./sample-apps/odoo-mysql/addons /mnt/extra-addons + +# Copy Odoo configuration +COPY ./sample-apps/odoo-mysql/odoo.conf /etc/odoo/ + +# Copy entrypoint script +COPY ./sample-apps/odoo-mysql/entrypoint.sh /usr/local/bin/aikido-entrypoint.sh +RUN chmod +x /usr/local/bin/aikido-entrypoint.sh + +USER odoo + +# Initialize Aikido protection environment variables +ENV AIKIDO_DEBUG=true \ + AIKIDO_BLOCK=true \ + AIKIDO_TOKEN="AIK_secret_token" \ + AIKIDO_REALTIME_ENDPOINT="http://localhost:5000/" \ + AIKIDO_ENDPOINT="http://localhost:5000/" \ + AIKIDO_DISABLE=0 + +# Use custom entrypoint that initializes Aikido +ENTRYPOINT ["/usr/local/bin/aikido-entrypoint.sh"] +CMD ["odoo"] diff --git a/sample-apps/odoo-mysql/Makefile b/sample-apps/odoo-mysql/Makefile new file mode 100644 index 000000000..a97f1ac57 --- /dev/null +++ b/sample-apps/odoo-mysql/Makefile @@ -0,0 +1,56 @@ +PORT = 8114 +PORT_DISABLED = 8115 + +.PHONY: build +build: + @echo "Building Odoo Docker image..." + docker-compose build + +.PHONY: run +run: build + @echo "Running Odoo with Aikido on port $(PORT)" + docker-compose up odoo + +.PHONY: runZenDisabled +runZenDisabled: build + @echo "Running Odoo without Aikido on port $(PORT_DISABLED)" + docker-compose --profile disabled up odoo_disabled + +.PHONY: up +up: build + @echo "Starting all services in detached mode..." + docker-compose up -d + +.PHONY: down +down: + @echo "Stopping all services..." + docker-compose down + +.PHONY: logs +logs: + @echo "Showing logs..." + docker-compose logs -f odoo + +.PHONY: clean +clean: + @echo "Cleaning up containers and volumes..." + docker-compose down -v + +.PHONY: restart +restart: down up + +.PHONY: shell +shell: + @echo "Opening shell in Odoo container..." + docker-compose exec odoo /bin/bash + +.PHONY: install +install: + @echo "Installation handled via Docker - run 'make build' to build the image" + +.PHONY: health-check +health-check: + @echo "Checking Odoo health on port $(PORT)..." + @curl -f http://localhost:$(PORT) > /dev/null 2>&1 && echo "Port $(PORT): OK" || echo "Port $(PORT): FAILED" + @echo "Checking Odoo health on port $(PORT_DISABLED) (if running)..." + @curl -f http://localhost:$(PORT_DISABLED) > /dev/null 2>&1 && echo "Port $(PORT_DISABLED): OK" || echo "Port $(PORT_DISABLED): Not running or FAILED" diff --git a/sample-apps/odoo-mysql/README.md b/sample-apps/odoo-mysql/README.md new file mode 100644 index 000000000..620c7bd7f --- /dev/null +++ b/sample-apps/odoo-mysql/README.md @@ -0,0 +1,136 @@ +# Odoo MySQL Sample App + +A containerized Odoo application demonstrating Aikido security protection with MySQL database integration. + +## Overview + +This sample app uses: +- **Odoo 16.0** - Popular open-source ERP/web framework +- **PostgreSQL** - Required by Odoo core +- **MySQL** - Used by the custom dog management module +- **Docker** - Containerized deployment following official Odoo on-premise guidelines + +## Architecture + +The app consists of: +- Custom Odoo addon module (`dog_management`) with HTTP controllers +- Vulnerable endpoints for testing security features (SQL injection, command injection, path traversal, SSRF) +- Aikido firewall integration via Python middleware + +## Getting Started + +### Prerequisites +- Docker and Docker Compose installed +- Ports 8114 and 8115 available + +### Build and Run + +```bash +# Build the Docker image +make build + +# Run with Aikido protection (port 8114) +make run + +# Run without Aikido protection (port 8115) +make runZenDisabled + +# Run in background +make up + +# View logs +make logs + +# Stop services +make down +``` + +## Accessing the Application + +- **With Aikido**: http://localhost:8114 +- **Without Aikido**: http://localhost:8115 (use `make runZenDisabled`) + +On first access, you'll need to create an Odoo database: +1. Set master password: `admin` +2. Database name: `odoo_demo` +3. Email: any email +4. Password: any password +5. Demo data: optional + +The `dog_management` module will be automatically installed. + +## Testing Vulnerabilities + +### SQL Injection +- Create a dog with name: `Malicious dog", 1); -- ` +- Visit: http://localhost:8114/create/via_query?dog_name=test%22%20OR%201=1-- + +### Command Injection +- Visit: http://localhost:8114/shell +- Enter command: `ls -la` + +### Path Traversal +- Visit: http://localhost:8114/open_file +- Enter filepath: `/etc/passwd` + +### SSRF +- Visit: http://localhost:8114/request +- Enter URL: `http://localhost:5000` + +## Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/` | GET | Homepage - lists all dogs | +| `/dogpage/` | GET | View specific dog details | +| `/create` | GET/POST | Create new dog form/handler | +| `/create/via_query?dog_name=X` | GET | Create dog via query parameter | +| `/multiple_queries` | POST | Execute 20 queries (performance test) | +| `/shell` | GET/POST | Execute shell commands | +| `/open_file` | GET/POST | Read files from filesystem | +| `/request` | GET/POST | Make HTTP requests (SSRF test) | +| `/test_ratelimiting_1` | GET | Rate limiting test endpoint | + +## Development + +### Project Structure +``` +odoo-mysql/ +├── addons/ +│ └── dog_management/ # Custom Odoo module +│ ├── __init__.py +│ ├── __manifest__.py # Module metadata +│ └── controllers/ +│ ├── __init__.py +│ └── main.py # HTTP controllers +├── docker-compose.yml # Multi-container setup +├── Dockerfile # Custom Odoo image +├── odoo.conf # Odoo configuration +├── Makefile # Build/run commands +└── README.md +``` + +### Useful Commands + +```bash +# Access Odoo container shell +make shell + +# Clean up everything (including volumes) +make clean + +# Restart services +make restart + +# Check application health +make health-check +``` + +## Notes + +- Odoo requires PostgreSQL for its core functionality +- The dog management module uses MySQL to demonstrate multi-database scenarios +- Aikido protection is configured via environment variables +- The app auto-installs the `dog_management` module on first run +- Port 8114 runs **with** Aikido protection +- Port 8115 runs **without** Aikido protection (for comparison) diff --git a/sample-apps/odoo-mysql/app.py b/sample-apps/odoo-mysql/app.py new file mode 100644 index 000000000..50c2985d4 --- /dev/null +++ b/sample-apps/odoo-mysql/app.py @@ -0,0 +1,301 @@ +import os +dont_add_middleware = os.getenv("DONT_ADD_MIDDLEWARE") +import aikido_zen # Aikido package import +aikido_zen.protect() + +# Sentry : +import sentry_sdk +sentry_sdk.init( + traces_sample_rate=1.0, + profiles_sample_rate=1.0, +) + +import subprocess +import requests +import MySQLdb +from werkzeug.wrappers import Request, Response +from werkzeug.routing import Map, Rule +from werkzeug.exceptions import HTTPException, NotFound + + +# MySQL connection helper +def get_db_connection(): + return MySQLdb.connect( + host='localhost', + user='user', + password='password', + database='db' + ) + + +class OdooStyleWSGIApp: + """ + A WSGI application inspired by Odoo's routing pattern. + Uses Werkzeug for routing and request/response handling. + """ + + def __init__(self): + self.url_map = Map([ + Rule('/', endpoint='homepage', methods=['GET']), + Rule('/dogpage/', endpoint='get_dogpage', methods=['GET']), + Rule('/create', endpoint='show_create_dog_form', methods=['GET']), + Rule('/create', endpoint='create_dog', methods=['POST']), + Rule('/create/via_query', endpoint='create_dog_via_query_param', methods=['GET']), + Rule('/multiple_queries', endpoint='multiple_queries', methods=['POST']), + Rule('/shell', endpoint='show_shell_form', methods=['GET']), + Rule('/shell', endpoint='execute_command', methods=['POST']), + Rule('/shell/', endpoint='execute_command_get', methods=['GET']), + Rule('/open_file', endpoint='show_open_file_form', methods=['GET']), + Rule('/open_file', endpoint='open_file', methods=['POST']), + Rule('/request', endpoint='show_request_page', methods=['GET']), + Rule('/request', endpoint='make_request', methods=['POST']), + Rule('/test_ratelimiting_1', endpoint='test_ratelimiting_1', methods=['GET']), + ]) + + def dispatch_request(self, request): + adapter = self.url_map.bind_to_environ(request.environ) + try: + endpoint, values = adapter.match() + return getattr(self, endpoint)(request, **values) + except NotFound: + return Response('Not Found', status=404) + except HTTPException as e: + return e + + def wsgi_app(self, environ, start_response): + request = Request(environ) + response = self.dispatch_request(request) + return response(environ, start_response) + + def __call__(self, environ, start_response): + return self.wsgi_app(environ, start_response) + + # Route handlers (mimicking Odoo's controller methods) + + def homepage(self, request): + try: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("SELECT * FROM db.dogs") + dogs = cursor.fetchall() + cursor.close() + conn.close() + + html = ''' + + + Homepage + +

Dogs List

+
    + ''' + for dog in dogs: + html += f'
  • {dog[1]}
  • ' + html += ''' +
+ Create a new dog + + + ''' + return Response(html, content_type='text/html') + except Exception as e: + return Response(f'Error: {str(e)}', content_type='text/html', status=500) + + def get_dogpage(self, request, dog_id): + try: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("SELECT * FROM db.dogs WHERE id = " + str(dog_id)) + dog = cursor.fetchone() + cursor.close() + conn.close() + + if dog: + html = f''' + + + Dog Page + +

{dog[1]}

+

ID: {dog[0]}

+

Is Admin: {"Yes" if dog[2] else "No"}

+ Back to homepage + + + ''' + return Response(html, content_type='text/html') + else: + return Response('Dog not found', content_type='text/html', status=404) + except Exception as e: + return Response(f'Error: {str(e)}', content_type='text/html', status=500) + + def show_create_dog_form(self, request): + html = ''' + + + Create Dog + +

Create a new dog

+
+ + + +
+ Back to homepage + + + ''' + return Response(html, content_type='text/html') + + def create_dog(self, request): + dog_name = request.form.get('dog_name', '') + try: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute(f'INSERT INTO dogs (dog_name, isAdmin) VALUES ("{dog_name}", 0)') + conn.commit() + cursor.close() + conn.close() + return Response(f'Dog {dog_name} created successfully', content_type='text/html') + except Exception as e: + return Response(f'Error: {str(e)}', content_type='text/html', status=500) + + def create_dog_via_query_param(self, request): + dog_name = request.args.get('dog_name', '') + if not dog_name: + return Response('Missing dog_name parameter', content_type='text/html', status=400) + try: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute(f'INSERT INTO dogs (dog_name, isAdmin) VALUES ("{dog_name}", 0)') + conn.commit() + cursor.close() + conn.close() + return Response(f'Dog {dog_name} created successfully', content_type='text/html') + except Exception as e: + return Response(f'Error: {str(e)}', content_type='text/html', status=500) + + def multiple_queries(self, request): + dog_name = request.form.get('dog_name', '') + try: + conn = get_db_connection() + cursor = conn.cursor() + for i in range(20): + cursor.execute(f'SELECT * FROM db.dogs WHERE dog_name = "{dog_name}"') + cursor.fetchmany(1) + cursor.close() + conn.close() + return Response('OK', content_type='text/html') + except Exception as e: + return Response(f'Error: {str(e)}', content_type='text/html', status=500) + + def show_shell_form(self, request): + html = ''' + + + Shell Command + +

Execute Shell Command

+
+ + + +
+ Back to homepage + + + ''' + return Response(html, content_type='text/html') + + def execute_command(self, request): + command = request.form.get('command', '') + result = subprocess.run(command, capture_output=True, text=True, shell=True) + return Response(str(result.stdout), content_type='text/html') + + def execute_command_get(self, request, command): + result = subprocess.run(command, capture_output=True, text=True, shell=True) + return Response(str(result.stdout), content_type='text/html') + + def show_open_file_form(self, request): + html = ''' + + + Open File + +

Open File

+
+ + + +
+ Back to homepage + + + ''' + return Response(html, content_type='text/html') + + def open_file(self, request): + filepath = request.form.get('filepath', '') + try: + with open(filepath, 'r', encoding='utf-8') as file: + content = file.read() + return Response(content, content_type='text/html') + except Exception as e: + return Response(f'Error: {str(e)}', content_type='text/html', status=500) + + def show_request_page(self, request): + html = ''' + + + Make Request + +

Make HTTP Request

+
+ + + +
+ Back to homepage + + + ''' + return Response(html, content_type='text/html') + + def make_request(self, request): + url = request.form.get('url', '') + try: + res = requests.get(url) + return Response(str(res), content_type='text/html') + except Exception as e: + return Response(f'Error: {str(e)}', content_type='text/html', status=500) + + def test_ratelimiting_1(self, request): + return Response('OK', content_type='text/html') + + +# Create the WSGI application +app = OdooStyleWSGIApp() + + +# WSGI application wrapper with Aikido middleware +def application(environ, start_response): + # Apply Aikido middleware if enabled + if dont_add_middleware is None or dont_add_middleware.lower() != "1": + import aikido_zen + from aikido_zen.middleware import AikidoWSGIMiddleware + + # Set user for Aikido + aikido_zen.set_user({"id": "123", "name": "John Doe"}) + + # Wrap WSGI application with Aikido middleware + wrapped_app = AikidoWSGIMiddleware(app) + return wrapped_app(environ, start_response) + + return app(environ, start_response) + + +if __name__ == '__main__': + # For development/testing + from werkzeug.serving import run_simple + port = int(os.getenv('PORT', 8114)) + run_simple('0.0.0.0', port, application, use_debugger=False, use_reloader=False) diff --git a/sample-apps/odoo-mysql/docker-compose.yml b/sample-apps/odoo-mysql/docker-compose.yml new file mode 100644 index 000000000..c81d3b747 --- /dev/null +++ b/sample-apps/odoo-mysql/docker-compose.yml @@ -0,0 +1,102 @@ +version: '3.8' + +services: + # PostgreSQL database for Odoo core + postgres: + image: postgres:15 + container_name: odoo_postgres_db + environment: + - POSTGRES_DB=postgres + - POSTGRES_USER=odoo + - POSTGRES_PASSWORD=odoo + volumes: + - odoo_postgres_data:/var/lib/postgresql/data + networks: + - odoo_network + + # MySQL database for dog management app + mysql: + image: mysql:8.0 + container_name: odoo_mysql_db + environment: + - MYSQL_DATABASE=db + - MYSQL_USER=user + - MYSQL_PASSWORD=password + - MYSQL_ROOT_PASSWORD=password + volumes: + - odoo_mysql_data:/var/lib/mysql + - ../databases/mysql_init.sql:/docker-entrypoint-initdb.d/init.sql + ports: + - "3306:3306" + networks: + - odoo_network + command: --default-authentication-plugin=mysql_native_password + + # Odoo application + odoo: + build: + context: ../.. + dockerfile: sample-apps/odoo-mysql/Dockerfile + container_name: odoo_app + depends_on: + - postgres + - mysql + ports: + - "8114:8069" + volumes: + - odoo_data:/var/lib/odoo + - ./addons:/mnt/extra-addons + - ./odoo.conf:/etc/odoo/odoo.conf + - ../../:/opt/aikido_zen:ro + environment: + - PYTHONPATH=/opt/aikido_zen:$PYTHONPATH + - HOST=postgres + - USER=odoo + - PASSWORD=odoo + - AIKIDO_DEBUG=true + - AIKIDO_BLOCK=true + - AIKIDO_TOKEN=AIK_secret_token + - AIKIDO_REALTIME_ENDPOINT=http://localhost:5000/ + - AIKIDO_ENDPOINT=http://localhost:5000/ + - AIKIDO_DISABLE=0 + networks: + - odoo_network + command: odoo -c /etc/odoo/odoo.conf --dev=all + + # Odoo without Aikido (for comparison/testing) + odoo_disabled: + build: + context: ../.. + dockerfile: sample-apps/odoo-mysql/Dockerfile + container_name: odoo_app_disabled + depends_on: + - postgres + - mysql + ports: + - "8115:8069" + volumes: + - odoo_disabled_data:/var/lib/odoo + - ./addons:/mnt/extra-addons + - ./odoo.conf:/etc/odoo/odoo.conf + - ../../:/opt/aikido_zen:ro + environment: + - PYTHONPATH=/opt/aikido_zen:$PYTHONPATH + - HOST=postgres + - USER=odoo + - PASSWORD=odoo + - AIKIDO_DISABLE=1 + networks: + - odoo_network + command: odoo -c /etc/odoo/odoo.conf --dev=all + profiles: + - disabled + +volumes: + odoo_postgres_data: + odoo_mysql_data: + odoo_data: + odoo_disabled_data: + +networks: + odoo_network: + driver: bridge diff --git a/sample-apps/odoo-mysql/entrypoint.sh b/sample-apps/odoo-mysql/entrypoint.sh new file mode 100755 index 000000000..5536dc4fa --- /dev/null +++ b/sample-apps/odoo-mysql/entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +# Initialize Aikido protection if enabled +if [ "${AIKIDO_DISABLE}" != "1" ]; then + echo "Initializing Aikido Zen protection..." + cat > /tmp/aikido_init.py << 'EOF' +import os +os.environ.setdefault('AIKIDO_DEBUG', 'true') +os.environ.setdefault('AIKIDO_BLOCK', 'true') + +import aikido_zen +aikido_zen.protect() + +# Set user context for Aikido +aikido_zen.set_user({"id": "odoo-system", "name": "Odoo System"}) + +print("✓ Aikido Zen protection initialized") +EOF + export PYTHONSTARTUP=/tmp/aikido_init.py +fi + +# Execute the main Odoo command +exec "$@" diff --git a/sample-apps/odoo-mysql/odoo.conf b/sample-apps/odoo-mysql/odoo.conf new file mode 100644 index 000000000..60c9c26de --- /dev/null +++ b/sample-apps/odoo-mysql/odoo.conf @@ -0,0 +1,14 @@ +[options] +addons_path = /mnt/extra-addons +data_dir = /var/lib/odoo +admin_passwd = admin +db_host = db +db_port = 5432 +db_user = odoo +db_password = odoo +http_port = 8069 +logfile = /var/log/odoo/odoo.log +log_level = info + +# Auto-install our custom module +# Note: This needs to be done via command line or web interface diff --git a/sample-apps/odoo-mysql/pyproject.toml b/sample-apps/odoo-mysql/pyproject.toml new file mode 100644 index 000000000..5c2a2701d --- /dev/null +++ b/sample-apps/odoo-mysql/pyproject.toml @@ -0,0 +1,24 @@ +[project] +name = "odoo-mysql-app" +version = "0.1.0" +description = "Sample app Odoo MySQL" +requires-python = ">3.9.1,<4.0" +dependencies = [ + "odoo (>=16.0,<17.0)", + "mysqlclient (>=2.2.0,<3.0.0)", + "requests (>=2.32.3,<3.0.0)", + "cryptography (>=44.0.0,<45.0.0)", + "werkzeug (>=3.0.0,<4.0.0)", + "aikido_zen", + "sentry-sdk (>=2.20.0,<3.0.0)" +] + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +package-mode = false + +[tool.poetry.dependencies] +aikido_zen = { path = "../../", develop = true } From 549b7e3ae52d7587843244f580da95003b2c3400 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Wed, 14 Jan 2026 16:03:31 +0100 Subject: [PATCH 2/5] add html files --- .../odoo-mysql/templates/create_dog.html | 1 + sample-apps/odoo-mysql/templates/dogpage.html | 1 + sample-apps/odoo-mysql/templates/execute.html | 17 +++++++++++++++++ sample-apps/odoo-mysql/templates/index.html | 1 + sample-apps/odoo-mysql/templates/open_file.html | 17 +++++++++++++++++ sample-apps/odoo-mysql/templates/request.html | 17 +++++++++++++++++ sample-apps/odoo-mysql/templates/shell.html | 17 +++++++++++++++++ 7 files changed, 71 insertions(+) create mode 120000 sample-apps/odoo-mysql/templates/create_dog.html create mode 120000 sample-apps/odoo-mysql/templates/dogpage.html create mode 100644 sample-apps/odoo-mysql/templates/execute.html create mode 120000 sample-apps/odoo-mysql/templates/index.html create mode 100644 sample-apps/odoo-mysql/templates/open_file.html create mode 100644 sample-apps/odoo-mysql/templates/request.html create mode 100644 sample-apps/odoo-mysql/templates/shell.html diff --git a/sample-apps/odoo-mysql/templates/create_dog.html b/sample-apps/odoo-mysql/templates/create_dog.html new file mode 120000 index 000000000..b0a077e0d --- /dev/null +++ b/sample-apps/odoo-mysql/templates/create_dog.html @@ -0,0 +1 @@ +../../shared-templates/common/create_dog.html \ No newline at end of file diff --git a/sample-apps/odoo-mysql/templates/dogpage.html b/sample-apps/odoo-mysql/templates/dogpage.html new file mode 120000 index 000000000..0cc02fe03 --- /dev/null +++ b/sample-apps/odoo-mysql/templates/dogpage.html @@ -0,0 +1 @@ +../../shared-templates/common/dogpage.html \ No newline at end of file diff --git a/sample-apps/odoo-mysql/templates/execute.html b/sample-apps/odoo-mysql/templates/execute.html new file mode 100644 index 000000000..bdecf14fa --- /dev/null +++ b/sample-apps/odoo-mysql/templates/execute.html @@ -0,0 +1,17 @@ + + + + + + + Execute command + + +

Execute a command :

+
+ + + +
+ + diff --git a/sample-apps/odoo-mysql/templates/index.html b/sample-apps/odoo-mysql/templates/index.html new file mode 120000 index 000000000..6fd9490c4 --- /dev/null +++ b/sample-apps/odoo-mysql/templates/index.html @@ -0,0 +1 @@ +../../shared-templates/common/index.html \ No newline at end of file diff --git a/sample-apps/odoo-mysql/templates/open_file.html b/sample-apps/odoo-mysql/templates/open_file.html new file mode 100644 index 000000000..cd2eee8dd --- /dev/null +++ b/sample-apps/odoo-mysql/templates/open_file.html @@ -0,0 +1,17 @@ + + + + + + + Open file + + +

Open file

+
+ + + +
+ + diff --git a/sample-apps/odoo-mysql/templates/request.html b/sample-apps/odoo-mysql/templates/request.html new file mode 100644 index 000000000..cffcd9af1 --- /dev/null +++ b/sample-apps/odoo-mysql/templates/request.html @@ -0,0 +1,17 @@ + + + + + + + Fetch from URL + + +

Fetch from URL

+
+ + + +
+ + diff --git a/sample-apps/odoo-mysql/templates/shell.html b/sample-apps/odoo-mysql/templates/shell.html new file mode 100644 index 000000000..8c2e0b3c7 --- /dev/null +++ b/sample-apps/odoo-mysql/templates/shell.html @@ -0,0 +1,17 @@ + + + + + + + Execute shell + + +

Execute a shell command

+
+ + + +
+ + From ee6084d1ccaa34a27f7018f751f70cacfa69176d Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Wed, 14 Jan 2026 16:03:44 +0100 Subject: [PATCH 3/5] add main odoo controller --- .../addons/dog_management/__init__.py | 1 + .../dog_management/controllers/__init__.py | 1 + .../addons/dog_management/controllers/main.py | 231 ++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 sample-apps/odoo-mysql/addons/dog_management/__init__.py create mode 100644 sample-apps/odoo-mysql/addons/dog_management/controllers/__init__.py create mode 100644 sample-apps/odoo-mysql/addons/dog_management/controllers/main.py diff --git a/sample-apps/odoo-mysql/addons/dog_management/__init__.py b/sample-apps/odoo-mysql/addons/dog_management/__init__.py new file mode 100644 index 000000000..e046e49fb --- /dev/null +++ b/sample-apps/odoo-mysql/addons/dog_management/__init__.py @@ -0,0 +1 @@ +from . import controllers diff --git a/sample-apps/odoo-mysql/addons/dog_management/controllers/__init__.py b/sample-apps/odoo-mysql/addons/dog_management/controllers/__init__.py new file mode 100644 index 000000000..12a7e529b --- /dev/null +++ b/sample-apps/odoo-mysql/addons/dog_management/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/sample-apps/odoo-mysql/addons/dog_management/controllers/main.py b/sample-apps/odoo-mysql/addons/dog_management/controllers/main.py new file mode 100644 index 000000000..b96912d77 --- /dev/null +++ b/sample-apps/odoo-mysql/addons/dog_management/controllers/main.py @@ -0,0 +1,231 @@ +import subprocess +import requests +import MySQLdb +from odoo import http +from odoo.http import request + + +# MySQL connection helper +def get_db_connection(): + return MySQLdb.connect( + host='mysql', + user='user', + password='password', + database='db' + ) + + +class DogController(http.Controller): + + @http.route('/', type='http', auth='none', methods=['GET'], csrf=False) + def homepage(self, **kwargs): + try: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("SELECT * FROM dogs") + dogs = cursor.fetchall() + cursor.close() + conn.close() + + html = ''' + + + Homepage + +

Dogs List

+
    + ''' + for dog in dogs: + html += f'
  • {dog[1]}
  • ' + html += ''' +
+ Create a new dog + + + ''' + return request.make_response(html, headers=[('Content-Type', 'text/html')]) + except Exception as e: + return request.make_response(f'Error: {str(e)}', headers=[('Content-Type', 'text/html')], status=500) + + @http.route('/dogpage/', type='http', auth='none', methods=['GET'], csrf=False) + def get_dogpage(self, dog_id, **kwargs): + try: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("SELECT * FROM dogs WHERE id = " + str(dog_id)) + dog = cursor.fetchone() + cursor.close() + conn.close() + + if dog: + html = f''' + + + Dog Page + +

{dog[1]}

+

ID: {dog[0]}

+

Is Admin: {"Yes" if dog[2] else "No"}

+ Back to homepage + + + ''' + return request.make_response(html, headers=[('Content-Type', 'text/html')]) + else: + return request.make_response('Dog not found', headers=[('Content-Type', 'text/html')], status=404) + except Exception as e: + return request.make_response(f'Error: {str(e)}', headers=[('Content-Type', 'text/html')], status=500) + + @http.route('/create', type='http', auth='none', methods=['GET'], csrf=False) + def show_create_dog_form(self, **kwargs): + html = ''' + + + Create Dog + +

Create a new dog

+
+ + + +
+ Back to homepage + + + ''' + return request.make_response(html, headers=[('Content-Type', 'text/html')]) + + @http.route('/create', type='http', auth='none', methods=['POST'], csrf=False) + def create_dog(self, **kwargs): + dog_name = request.params.get('dog_name', '') + try: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute(f'INSERT INTO dogs (dog_name, isAdmin) VALUES ("{dog_name}", 0)') + conn.commit() + cursor.close() + conn.close() + return request.make_response(f'Dog {dog_name} created successfully', headers=[('Content-Type', 'text/html')]) + except Exception as e: + return request.make_response(f'Error: {str(e)}', headers=[('Content-Type', 'text/html')], status=500) + + @http.route('/create/via_query', type='http', auth='none', methods=['GET'], csrf=False) + def create_dog_via_query_param(self, dog_name=None, **kwargs): + if not dog_name: + return request.make_response('Missing dog_name parameter', headers=[('Content-Type', 'text/html')], status=400) + try: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute(f'INSERT INTO dogs (dog_name, isAdmin) VALUES ("{dog_name}", 0)') + conn.commit() + cursor.close() + conn.close() + return request.make_response(f'Dog {dog_name} created successfully', headers=[('Content-Type', 'text/html')]) + except Exception as e: + return request.make_response(f'Error: {str(e)}', headers=[('Content-Type', 'text/html')], status=500) + + @http.route('/multiple_queries', type='http', auth='none', methods=['POST'], csrf=False) + def multiple_queries(self, **kwargs): + dog_name = request.params.get('dog_name', '') + try: + conn = get_db_connection() + cursor = conn.cursor() + for i in range(20): + cursor.execute(f'SELECT * FROM dogs WHERE dog_name = "{dog_name}"') + cursor.fetchmany(1) + cursor.close() + conn.close() + return request.make_response('OK', headers=[('Content-Type', 'text/html')]) + except Exception as e: + return request.make_response(f'Error: {str(e)}', headers=[('Content-Type', 'text/html')], status=500) + + @http.route('/shell', type='http', auth='none', methods=['GET'], csrf=False) + def show_shell_form(self, **kwargs): + html = ''' + + + Shell Command + +

Execute Shell Command

+
+ + + +
+ Back to homepage + + + ''' + return request.make_response(html, headers=[('Content-Type', 'text/html')]) + + @http.route('/shell', type='http', auth='none', methods=['POST'], csrf=False) + def execute_command(self, **kwargs): + command = request.params.get('command', '') + result = subprocess.run(command, capture_output=True, text=True, shell=True) + return request.make_response(str(result.stdout), headers=[('Content-Type', 'text/html')]) + + @http.route('/shell/', type='http', auth='none', methods=['GET'], csrf=False) + def execute_command_get(self, command, **kwargs): + result = subprocess.run(command, capture_output=True, text=True, shell=True) + return request.make_response(str(result.stdout), headers=[('Content-Type', 'text/html')]) + + @http.route('/open_file', type='http', auth='none', methods=['GET'], csrf=False) + def show_open_file_form(self, **kwargs): + html = ''' + + + Open File + +

Open File

+
+ + + +
+ Back to homepage + + + ''' + return request.make_response(html, headers=[('Content-Type', 'text/html')]) + + @http.route('/open_file', type='http', auth='none', methods=['POST'], csrf=False) + def open_file(self, **kwargs): + filepath = request.params.get('filepath', '') + try: + with open(filepath, 'r', encoding='utf-8') as file: + content = file.read() + return request.make_response(content, headers=[('Content-Type', 'text/html')]) + except Exception as e: + return request.make_response(f'Error: {str(e)}', headers=[('Content-Type', 'text/html')], status=500) + + @http.route('/request', type='http', auth='none', methods=['GET'], csrf=False) + def show_request_page(self, **kwargs): + html = ''' + + + Make Request + +

Make HTTP Request

+
+ + + +
+ Back to homepage + + + ''' + return request.make_response(html, headers=[('Content-Type', 'text/html')]) + + @http.route('/request', type='http', auth='none', methods=['POST'], csrf=False) + def make_request(self, **kwargs): + url = request.params.get('url', '') + try: + res = requests.get(url) + return request.make_response(str(res), headers=[('Content-Type', 'text/html')]) + except Exception as e: + return request.make_response(f'Error: {str(e)}', headers=[('Content-Type', 'text/html')], status=500) + + @http.route('/test_ratelimiting_1', type='http', auth='none', methods=['GET'], csrf=False) + def test_ratelimiting_1(self, **kwargs): + return request.make_response('OK', headers=[('Content-Type', 'text/html')]) From 7142d0d14b9664bfab35ec977a8da9beb549da37 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Wed, 14 Jan 2026 16:05:38 +0100 Subject: [PATCH 4/5] add a manifest file --- .../addons/dog_management/__manifest__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 sample-apps/odoo-mysql/addons/dog_management/__manifest__.py diff --git a/sample-apps/odoo-mysql/addons/dog_management/__manifest__.py b/sample-apps/odoo-mysql/addons/dog_management/__manifest__.py new file mode 100644 index 000000000..456bc907c --- /dev/null +++ b/sample-apps/odoo-mysql/addons/dog_management/__manifest__.py @@ -0,0 +1,18 @@ +{ + 'name': 'sample app aikido-zen', + 'version': '0.0.1', + 'category': 'Tools', + 'summary': 'Sample application for testing Aikido security', + 'description': 'This is used for testing Aikido Zen', + 'author': 'Aikido Security', + 'website': 'https://aikido.dev', + 'license': 'AGPL', + 'depends': ['base', 'web'], + 'external_dependencies': { + 'python': ['MySQLdb', 'aikido_zen', 'sentry_sdk', 'requests'], + }, + 'data': [], + 'installable': True, + 'application': True, + 'auto_install': False, +} From f0bbf66aea500cd2b9f7607460b07c5d0c4677cc Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Wed, 14 Jan 2026 16:16:55 +0100 Subject: [PATCH 5/5] fix db host for odoo-mysql --- sample-apps/odoo-mysql/odoo.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-apps/odoo-mysql/odoo.conf b/sample-apps/odoo-mysql/odoo.conf index 60c9c26de..b94e3ee88 100644 --- a/sample-apps/odoo-mysql/odoo.conf +++ b/sample-apps/odoo-mysql/odoo.conf @@ -2,7 +2,7 @@ addons_path = /mnt/extra-addons data_dir = /var/lib/odoo admin_passwd = admin -db_host = db +db_host = postgres db_port = 5432 db_user = odoo db_password = odoo