- PROTOCOL.md: base URL, login/session auth, request envelope, push, media - endpoints.md: 100 app endpoints extracted from the Capacitor JS bundle - FINDINGS.md: security findings incl. hardcoded dev account in build - tools: endpoint extractor + credential-safe login script - results: original analysis report (IDs redacted)
64 lines
2.0 KiB
Python
Executable File
64 lines
2.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Extract the Boti Education app API endpoints from decompiled Capacitor JS chunks.
|
|
|
|
Usage: python3 extract_endpoints.py <dir-with-chunks> [--markdown]
|
|
|
|
Scans *.js for the Angular ApiService call signature:
|
|
<http>.post(<payload>, "endpoint") / <http>.get(<payload>, "endpoint")
|
|
and emits a deduplicated endpoint -> methods table.
|
|
"""
|
|
import re
|
|
import sys
|
|
import glob
|
|
import os
|
|
import argparse
|
|
|
|
CALL = re.compile(
|
|
r'\.(post|get|getBlob)\(\s*'
|
|
r'(?:"[^"]*"|`[^`]*`|[A-Za-z_$][\w$]*(?:\s*\.\s*[\w$]+)*(?:\([^()]*\))?|\{[^{}]*\})'
|
|
r'\s*,\s*"([^"]*)"'
|
|
)
|
|
|
|
|
|
def scan(directory):
|
|
found = {} # endpoint -> {method: [chunks]}
|
|
for path in sorted(glob.glob(os.path.join(directory, '*.js'))):
|
|
chunk = os.path.basename(path)
|
|
src = open(path, encoding='utf8', errors='replace').read()
|
|
for m in CALL.finditer(src):
|
|
method, ep = m.group(1), m.group(2)
|
|
d = found.setdefault(ep, {})
|
|
d.setdefault(method, []).append(chunk)
|
|
return found
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('directory')
|
|
ap.add_argument('--markdown', action='store_true')
|
|
args = ap.parse_args()
|
|
|
|
found = scan(args.directory)
|
|
if not found:
|
|
sys.exit('no endpoints found, wrong directory?')
|
|
|
|
if args.markdown:
|
|
print(f'# API endpoints extracted from {os.path.basename(args.directory)}')
|
|
print()
|
|
print(f'{len(found)} unique endpoints.')
|
|
print()
|
|
print('| endpoint | methods | seen in chunk(s) |')
|
|
print('|---|---|---|')
|
|
for ep in sorted(found):
|
|
methods = ', '.join(sorted(found[ep]))
|
|
chunks = ', '.join(sorted({c for ms in found[ep].values() for c in ms})[:3])
|
|
print(f'| {ep} | {methods} | {chunks} |')
|
|
else:
|
|
for ep in sorted(found):
|
|
print(ep, sorted(found[ep]))
|
|
print(f'TOTAL endpoints: {len(found)}', file=sys.stderr)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|