Skip to content

Commit f2f15e9

Browse files
committed
Support Headers, Arbitrary URLS, #1 #2 #3
1 parent 21033d1 commit f2f15e9

File tree

7 files changed

+226
-229
lines changed

7 files changed

+226
-229
lines changed

.gitignore

Lines changed: 3 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,6 @@
1-
# Byte-compiled / optimized / DLL files
21
__pycache__/
3-
*.py[cod]
4-
*$py.class
5-
6-
# C extensions
7-
*.so
8-
9-
# Distribution / packaging
10-
.Python
2+
.vscode/
113
build/
12-
develop-eggs/
13-
dist/
14-
downloads/
15-
eggs/
16-
.eggs/
17-
lib/
18-
lib64/
19-
parts/
20-
sdist/
21-
var/
22-
wheels/
23-
pip-wheel-metadata/
24-
share/python-wheels/
4+
.ruff_cache/
255
*.egg-info/
26-
.installed.cfg
27-
*.egg
28-
MANIFEST
29-
30-
# PyInstaller
31-
# Usually these files are written by a python script from a template
32-
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33-
*.manifest
34-
*.spec
35-
36-
# Installer logs
37-
pip-log.txt
38-
pip-delete-this-directory.txt
39-
40-
# Unit test / coverage reports
41-
htmlcov/
42-
.tox/
43-
.nox/
44-
.coverage
45-
.coverage.*
46-
.cache
47-
nosetests.xml
48-
coverage.xml
49-
*.cover
50-
*.py,cover
51-
.hypothesis/
52-
.pytest_cache/
53-
54-
# Translations
55-
*.mo
56-
*.pot
57-
58-
# Django stuff:
59-
*.log
60-
local_settings.py
61-
db.sqlite3
62-
db.sqlite3-journal
63-
64-
# Flask stuff:
65-
instance/
66-
.webassets-cache
67-
68-
# Scrapy stuff:
69-
.scrapy
70-
71-
# Sphinx documentation
72-
docs/_build/
73-
74-
# PyBuilder
75-
target/
76-
77-
# Jupyter Notebook
78-
.ipynb_checkpoints
79-
80-
# IPython
81-
profile_default/
82-
ipython_config.py
83-
84-
# pyenv
85-
.python-version
86-
87-
# pipenv
88-
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89-
# However, in case of collaboration, if having platform-specific dependencies or dependencies
90-
# having no cross-platform support, pipenv may install dependencies that don't work, or not
91-
# install all needed dependencies.
92-
#Pipfile.lock
93-
94-
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
95-
__pypackages__/
96-
97-
# Celery stuff
98-
celerybeat-schedule
99-
celerybeat.pid
100-
101-
# SageMath parsed files
102-
*.sage.py
103-
104-
# Environments
105-
.env
106-
.venv
107-
env/
108-
venv/
109-
ENV/
110-
env.bak/
111-
venv.bak/
112-
113-
# Spyder project settings
114-
.spyderproject
115-
.spyproject
116-
117-
# Rope project settings
118-
.ropeproject
119-
120-
# mkdocs documentation
121-
/site
122-
123-
# mypy
124-
.mypy_cache/
125-
.dmypy.json
126-
dmypy.json
127-
128-
# Pyre type checker
129-
.pyre/
6+
dist/

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,18 @@ Heavily based on an excellent writeup from Rayhan Ahmed: [Automating Blind SQL i
55

66
## Example
77
```
8-
sqlmap-websocket-proxy -u ws://sketchyurl.htb:8081 -p '{"uid_of_some_sort": "%param%"}' --json
8+
sqlmap-websocket-proxy -u ws://sketcy.lol:1337 -p '{"id": "%param%"}'
99
python3 sqlmap.py -u http://localhost:8080/?param1=1
1010
```
1111
## Usage
1212
```bash
13-
usage: sqlmap-websocket-proxy [-h] -u URL -p PAYLOAD [-o PORT] [--json]
13+
usage: sqlmap-websocket-proxy [-h] -u URL -d DATA [-p PORT]
1414

1515
options:
1616
-h, --help show this help message and exit
17-
-u URL, --url URL URL to the websocket
18-
-p PAYLOAD, --payload PAYLOAD
19-
String with params for the playload encoded as %param% (example: {"uid_of_some_sort": "%param%"})
20-
-o PORT, --port PORT Proxy Port (default: 8080)
21-
--json Escape text for JSON payloads
17+
-u URL, --url URL URL to the websocket (example: ws://vuln_server:1337/ws)
18+
-d DATA, --data DATA Paylod with injectable fields encoded as '%param%' (example: {"id": "%param%"})
19+
-p PORT, --port PORT Proxy Port (default: 8080)
2220
```
2321

2422
## Installation
@@ -30,9 +28,15 @@ python3 -m pip install sqlmap-websocket-proxy
3028

3129
### Manual
3230
```bash
33-
python3 -m pip install sqlmap_websocket_proxy-1.0.0-py3-none-any.whl
31+
python3 -m pip install sqlmap_websocket_proxy-1.1.0-py3-none-any.whl
3432
```
35-
[Download Latest Release](https://github.com/BKreisel/sqlmap-websocket-proxy/releases/download/1.0.0/sqlmap_websocket_proxy-1.0.0-py3-none-any.whl)
33+
34+
### Git
35+
```bash
36+
python3 -m pip install .
37+
```
38+
39+
[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)
3640

3741
## Demo
3842
[![demo](https://asciinema.org/a/550190.svg)](https://asciinema.org/a/550190?autoplay=1)

pyproject.toml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "sqlmap-websocket-proxy"
7-
version = "1.0.0"
7+
version = "1.1.0"
88
authors = [
99
{ name="Brandon Kreisel", email="BKreisel@users.noreply.github.com" },
1010
]
1111
description = "HTTP Proxy for using sqlmap against websockets"
1212
readme = "README.md"
13-
requires-python = ">=3.8"
13+
requires-python = ">=3.11"
1414
classifiers = [
1515
"Programming Language :: Python :: 3",
1616
"License :: OSI Approved :: MIT License",
@@ -22,9 +22,18 @@ dependencies = [
2222
"rich",
2323
]
2424

25+
[project.optional-dependencies]
26+
dev = [
27+
"ruff"
28+
]
29+
2530
[project.scripts]
26-
sqlmap-websocket-proxy = "sqlmap_websocket_proxy.main:cli"
31+
sqlmap-websocket-proxy = "sqlmap_websocket_proxy.main:main"
2732

2833
[project.urls]
2934
"Homepage" = "https://github.com/BKreisel/sqlmap-websocket-proxy"
30-
"Bug Tracker" = "https://github.com/BKreisel/sqlmap-websocket-proxy/issues"
35+
"Bug Tracker" = "https://github.com/BKreisel/sqlmap-websocket-proxy/issues"
36+
37+
[tool.ruff.lint]
38+
select = ["ALL"]
39+
extend-ignore=["T201", "DTZ005"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""sqlmap websocket proxy."""
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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")
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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 = [unquote(x).replace('"',"'") 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}")

0 commit comments

Comments
 (0)