├── .gitignore ├── LICENSE ├── README.md ├── pyproject.toml └── src └── sqlmap_websocket_proxy ├── __init__.py ├── console.py ├── handler.py └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .vscode/ 3 | build/ 4 | .ruff_cache/ 5 | *.egg-info/ 6 | dist/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Brandon Kreisel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sqlmap Websocket Proxy 2 | 💉Tool to enable blind sql injection attacks against websockets using sqlmap 3 | 4 | Heavily based on an excellent writeup from Rayhan Ahmed: [Automating Blind SQL injection over WebSocket](https://rayhan0x01.github.io/ctf/2021/04/02/blind-sqli-over-websocket-automation.html) 5 | 6 | ## Fork Changes 7 | - Proper JSON Encoding 8 | - before: " was replaced with ', leading to problems with payloads 9 | - now: using json.dumps to escape " 10 | 11 | ## Example 12 | ``` 13 | sqlmap-websocket-proxy -u ws://sketcy.lol:1337 -p '{"id": "%param%"}' 14 | python3 sqlmap.py -u http://localhost:8080/?param1=1 15 | ``` 16 | ## Usage 17 | ```bash 18 | usage: sqlmap-websocket-proxy [-h] -u URL -d DATA [-p PORT] 19 | 20 | options: 21 | -h, --help show this help message and exit 22 | -u URL, --url URL URL to the websocket (example: ws://vuln_server:1337/ws) 23 | -d DATA, --data DATA Paylod with injectable fields encoded as '%param%' (example: {"id": "%param%"}) 24 | -p PORT, --port PORT Proxy Port (default: 8080) 25 | ``` 26 | 27 | ## Installation 28 | 29 | ### PyPI 30 | ```bash 31 | python3 -m pip install sqlmap-websocket-proxy 32 | ``` 33 | 34 | ### Manual 35 | ```bash 36 | python3 -m pip install sqlmap_websocket_proxy-1.1.0-py3-none-any.whl 37 | ``` 38 | 39 | ### Git 40 | ```bash 41 | python3 -m pip install . 42 | ``` 43 | 44 | [Download Latest Release](https://github.com/BKreisel/sqlmap-websocket-proxy/releases/download/1.1.0/sqlmap_websocket_proxy-1.1.0-py3-none-any.whl) 45 | 46 | ## Demo 47 | [![demo](https://asciinema.org/a/550190.svg)](https://asciinema.org/a/550190?autoplay=1) 48 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "sqlmap-websocket-proxy" 7 | version = "1.1.0" 8 | authors = [ 9 | { name="Brandon Kreisel", email="BKreisel@users.noreply.github.com" }, 10 | ] 11 | description = "HTTP Proxy for using sqlmap against websockets" 12 | readme = "README.md" 13 | requires-python = ">=3.11" 14 | classifiers = [ 15 | "Programming Language :: Python :: 3", 16 | "License :: OSI Approved :: MIT License", 17 | "Operating System :: OS Independent", 18 | ] 19 | 20 | dependencies = [ 21 | "websocket-client", 22 | "rich", 23 | ] 24 | 25 | [project.optional-dependencies] 26 | dev = [ 27 | "ruff" 28 | ] 29 | 30 | [project.scripts] 31 | sqlmap-websocket-proxy = "sqlmap_websocket_proxy.main:main" 32 | 33 | [project.urls] 34 | "Homepage" = "https://github.com/BKreisel/sqlmap-websocket-proxy" 35 | "Bug Tracker" = "https://github.com/BKreisel/sqlmap-websocket-proxy/issues" 36 | 37 | [tool.ruff.lint] 38 | select = ["ALL"] 39 | extend-ignore=["T201", "DTZ005"] 40 | -------------------------------------------------------------------------------- /src/sqlmap_websocket_proxy/__init__.py: -------------------------------------------------------------------------------- 1 | """sqlmap websocket proxy.""" 2 | -------------------------------------------------------------------------------- /src/sqlmap_websocket_proxy/console.py: -------------------------------------------------------------------------------- 1 | """Console output functions.""" 2 | import sys 3 | from datetime import datetime 4 | 5 | import rich 6 | 7 | 8 | def error(msg: str, depth: int = 0) -> None: 9 | """Print an error message.""" 10 | print("") 11 | _rich_print("red", "⛔", msg, depth=depth) 12 | sys.exit(1) 13 | 14 | def status(msg: str, depth: int = 0, symbol: str = "[*]") -> None: 15 | """Print a status message.""" 16 | _rich_print("blue", symbol, msg, depth=depth) 17 | 18 | def success(msg: str, depth: int = 0) -> None: 19 | """Print a success message.""" 20 | _rich_print("green", "[+]", msg, depth=depth) 21 | 22 | def warning(msg: str, depth: int = 0) -> None: 23 | """Print a success message.""" 24 | _rich_print("yellow", "[!]", msg, depth=depth) 25 | 26 | def _rich_print(color: str, symbol: str, msg: str, depth: int = 0) -> None: 27 | """Format and print messages with rich.""" 28 | rich.print(f"{depth * ' '}[{color}]{symbol}[/{color}] {msg}") 29 | 30 | def timestamp() -> str: 31 | """Get the current timestamp.""" 32 | return datetime.now().strftime("%H:%M:%S") 33 | -------------------------------------------------------------------------------- /src/sqlmap_websocket_proxy/handler.py: -------------------------------------------------------------------------------- 1 | """HTTP Payload Handler.""" 2 | import contextlib 3 | import json 4 | import sys 5 | from functools import partial 6 | from http.server import SimpleHTTPRequestHandler 7 | from json import JSONDecodeError 8 | from socketserver import TCPServer 9 | from typing import Any, Self 10 | from urllib.parse import parse_qsl, unquote 11 | 12 | import rich 13 | import websocket 14 | 15 | from sqlmap_websocket_proxy.console import error, status, timestamp 16 | 17 | # Don't proxy these headers 18 | SKIPPED_HEADERS = [ 19 | "connection", 20 | "accept-encoding", 21 | "accept", 22 | ] 23 | 24 | class HTTPHandler(SimpleHTTPRequestHandler): 25 | """HTTP Endpoint that Proxies to the weboscket.""" 26 | 27 | def __init__( 28 | self: Self, 29 | url: str, 30 | data: str, 31 | *args: Any, # noqa: ANN401 32 | **kwargs: dict[Any], 33 | ) -> None: 34 | """Init.""" 35 | self.url = url 36 | self.data = data 37 | 38 | self.json_encode = False 39 | with contextlib.suppress(JSONDecodeError): 40 | json.loads(data) 41 | self.json_encode = True 42 | status("Detected JSON input. Auto-escaping.") 43 | 44 | # Suppress default logging 45 | self.log_message = lambda *_args: None 46 | 47 | super().__init__(*args, **kwargs) 48 | 49 | def do_GET(self: Self) -> None: # noqa: N802 50 | """Handle GET requests.""" 51 | self.send_response(200) 52 | self.end_headers() 53 | resp = send_inject( 54 | self.url, 55 | self.path, 56 | self.data, 57 | self.headers, 58 | self.json_encode, 59 | ) 60 | self.wfile.write(resp) 61 | 62 | def send_inject( 63 | url: str, 64 | path: str, 65 | data: str, 66 | headers: dict, 67 | json_encode: bool, # noqa: FBT001 68 | ) -> bytes: 69 | """Send sqlmap inject acrossthe websocket.""" 70 | params = [x for _, x in parse_qsl(path)] 71 | 72 | if json_encode: 73 | params = [json.dumps(unquote(x))[1:-1] for x in params] 74 | 75 | for x in params: 76 | data = data.replace("%param%", x, 1) 77 | 78 | try: 79 | ws = websocket.create_connection( 80 | url, 81 | header=[ 82 | f"{k}: {v}" 83 | for k, v in headers.items() 84 | if k.lower() not in SKIPPED_HEADERS 85 | ], 86 | ) 87 | except Exception as e: # noqa: BLE001 88 | error(f"Websocket Connection Failed: {e}") 89 | 90 | try: 91 | ws.send(data) 92 | rich.print(f"[{timestamp()}] Proxied: {data}") 93 | data = ws.recv() 94 | return data.encode("utf-8") if data else b"" 95 | except Exception as err: # noqa: BLE001 96 | rich.print(f"[yellow]Request Failed: {err!s}[/yellow]") 97 | finally: 98 | ws.close() 99 | 100 | 101 | def run_server(port: int, url: str, data: str) -> None: 102 | """Run the Proxy Server.""" 103 | try: 104 | handler = partial(HTTPHandler, url, data) 105 | with TCPServer(("", port), handler) as httpd: 106 | status("Server Started (Ctrl+c to stop)\n") 107 | httpd.serve_forever() 108 | except KeyboardInterrupt: 109 | status("Quitting...") 110 | sys.exit(0) 111 | except Exception as err: # noqa: BLE001 112 | error(f"Exception: {err}") 113 | -------------------------------------------------------------------------------- /src/sqlmap_websocket_proxy/main.py: -------------------------------------------------------------------------------- 1 | """sqlmap websocket proxy main.""" 2 | import re 3 | from argparse import ArgumentParser, Namespace 4 | from typing import cast 5 | 6 | from sqlmap_websocket_proxy.console import error, status, success, warning 7 | from sqlmap_websocket_proxy.handler import run_server 8 | 9 | 10 | class Args(Namespace): 11 | """Add type hints for CLI args.""" 12 | 13 | url: str 14 | port: int 15 | data: str 16 | 17 | 18 | def parse_args() -> Args: 19 | """Read and parse CLI args.""" 20 | parser = ArgumentParser() 21 | parser.add_argument( 22 | "-u", 23 | "--url", 24 | required=True, 25 | help="URL to the websocket (example: ws://vuln_server:1337/ws)", 26 | ) 27 | parser.add_argument( 28 | "-d", 29 | "--data", 30 | required=True, 31 | help= 32 | "Paylod with injectable fields encoded as '%%param%%'" 33 | ' (example: {"id": "%%param%%"})', 34 | ) 35 | parser.add_argument( 36 | "-p", 37 | "--port", 38 | type=int, 39 | default=8080, 40 | help="Proxy Port (default: 8080)", 41 | ) 42 | args = parser.parse_args() 43 | 44 | if not args.url.startswith("ws"): 45 | warning("Input URL doesn't appear to be a websocket.") 46 | 47 | return cast(Args, args) 48 | 49 | def main() -> None: 50 | """CLI main.""" 51 | args = parse_args() 52 | status("Sqlmap Websocket Proxy", symbol="💉") 53 | 54 | status(f"{'Proxy Port':10} : {args.port}", depth=1, symbol="-") 55 | status(f"{'URL':10} : {args.url}", depth=1, symbol="-") 56 | status(f"{'Payload':10} : {args.data}", depth=1, symbol="-") 57 | 58 | params = [match.start() for match in re.finditer(r"%param%", args.data)] 59 | if not len(params): 60 | error("No Injectable Parameters Found :(") 61 | 62 | status(f"Targeting {len(params)} injectable parameter(s)") 63 | param_str = "&".join([f"param{x}=1" for x in range(1,len(params)+1)]) 64 | success(f"sqlmap url flag: -u http://localhost:{args.port}/?{param_str}") 65 | 66 | run_server(args.port, args.url, args.data) 67 | 68 | if __name__ == "__main__": 69 | main() 70 | --------------------------------------------------------------------------------