Snyk has a proof-of-concept or detailed explanation of how to exploit this vulnerability.
In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.
Test your applicationsA fix was pushed into the master branch but not yet published.
dfir-unfurl is an Unfurl takes a URL and expands ("unfurls") it into a directed graph
Affected versions of this package are vulnerable to Active Debug Code due to improper parsing of the debug configuration value, which is always interpreted as truthy and enables the Werkzeug debugger regardless of intended settings. An attacker can gain access to sensitive information and potentially execute arbitrary code by accessing the exposed debugger interface if the service is accessible from outside the local environment.
#!/usr/bin/env python3
"""
Unfurl Debug Mode PoC (Corrected)
================================
This PoC demonstrates that Unfurl's Flask debug mode is effectively
**always enabled by default** due to string parsing of the `debug`
config value. Even `debug = False` in `unfurl.ini` evaluates truthy
when passed to `app.run(debug=...)`.
Two modes:
1) --spawn (default): launch a local Unfurl server with debug=False
in a temp config and inspect logs for "Debug mode: on".
2) --target: attempt a remote indicator check (best-effort; may be silent
if no exception is triggered).
"""
import argparse
import os
import subprocess
import sys
import tempfile
import textwrap
import time
def run_spawn_check() -> None:
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
ini_contents = textwrap.dedent("""
[UNFURL_APP]
host = 127.0.0.1
port = 5055
debug = False
remote_lookups = false
[API_KEYS]
bitly =
macaddress_io =
""").strip() + "\n"
with tempfile.TemporaryDirectory() as tmp:
ini_path = os.path.join(tmp, 'unfurl.ini')
with open(ini_path, 'w') as f:
f.write(ini_contents)
env = os.environ.copy()
env['PYTHONPATH'] = repo_root
cmd = [sys.executable, '-c', 'from unfurl.app import web_app; web_app()']
proc = subprocess.Popen(
cmd,
cwd=tmp,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Allow server to start and emit logs
time.sleep(2)
proc.terminate()
try:
out, err = proc.communicate(timeout=2)
except subprocess.TimeoutExpired:
proc.kill()
out, err = proc.communicate()
output = (out or "") + (err or "")
print("\n[+] Debug mode spawn check")
print(" Config: debug = False")
if "Debug mode: on" in output or "Debugger is active" in output:
print(" ✅ Debug mode is ON despite debug=False (vulnerable)")
else:
print(" ⚠️ Debug mode not detected in logs (check output below)")
if output.strip():
print("\n--- server output (truncated) ---")
print("\n".join(output.splitlines()[:15]))
print("--- end ---")
def run_remote_probe(target: str) -> None:
import requests
print("\n[+] Remote debug indicator probe (best-effort)")
print(f" Target: {target}")
# This app does not easily throw exceptions from user input, so
# absence of indicators does NOT prove debug is off.
probe_urls = [
f"{target.rstrip('/')}/__nonexistent__",
]
detected = False
for url in probe_urls:
try:
resp = requests.get(url, timeout=10)
if "Werkzeug Debugger" in resp.text or "Traceback" in resp.text:
detected = True
print(" ✅ Debug indicators found")
break
except Exception as e:
print(f" ⚠️ Probe failed: {e}")
if not detected:
print(" ⚠️ No debug indicators found (this is not definitive)")
def main():
parser = argparse.ArgumentParser(description='Unfurl debug mode PoC (corrected)')
parser.add_argument('--spawn', action='store_true', help='Run local spawn check (default)')
parser.add_argument('--target', help='Target Unfurl URL for remote probe')
args = parser.parse_args()
if args.target:
run_remote_probe(args.target)
else:
run_spawn_check()
if __name__ == '__main__':
main()