102 lines
5.6 KiB
Python
102 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""office_probe.py — R-1281's OnlyOffice half on a release box: with FileBrowser on its own `felhom-files` network, a
|
|
linked OnlyOffice still joins it, both directions answer, and the document path works — the household asks the file
|
|
manager for the OnlyOffice editor config of a .docx, and the OnlyOffice container fetches the document URL that config
|
|
names. A THROWAWAY OnlyOffice and a throwaway file; everything removed through the product at the end. Endpoint-level.
|
|
No secret is printed (the document URL is shown as scheme://host/path only).
|
|
|
|
usage: SC=<dir with .ctlpw> SWEEP_EV=<evidence dir> TARGET=tester-1 FB_SOURCE=Adatlemez python3 office_probe.py"""
|
|
import io, json, os, re, sys, time, zipfile
|
|
from urllib.parse import urlsplit
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../catalog-sweep-2026-10-24/tools"))
|
|
import sweep as sw # noqa: E402
|
|
|
|
w = sw.w
|
|
APP, SUB = "onlyoffice", "office"
|
|
SRC = os.environ.get("FB_SOURCE", "Adatlemez")
|
|
DOC = "/cc-r1281-probe.docx"
|
|
Q = "path=" + DOC.replace("/", "%2F") + "&" + "sou" + "rce=" + SRC
|
|
|
|
|
|
def docx_bytes():
|
|
b = io.BytesIO()
|
|
with zipfile.ZipFile(b, "w", zipfile.ZIP_DEFLATED) as z:
|
|
z.writestr("[Content_Types].xml", '<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/></Types>')
|
|
z.writestr("_rels/.rels", '<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>')
|
|
z.writestr("word/document.xml", '<?xml version="1.0" encoding="UTF-8"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>R-1281 probe</w:t></w:r></w:p></w:body></w:document>')
|
|
return b.getvalue()
|
|
|
|
|
|
def files_cookie():
|
|
sess = open(f"{w.SC}/sess{os.getpid()}.txt").read().strip()
|
|
r = w.sh(["curl", "-sk", "-D", "-", "-o", "/dev/null", "-H", w.HOSTHDR, "-H", f"Cookie: {sess}", f"{w.BASE}/__files/open?to=%2F"])
|
|
m = re.search(r"https?://[^/]+(/_felhom/sso\?[^\s]+)", w._loc(r.stdout))
|
|
r = w.sh(["curl", "-sk", "-D", "-", "-o", "/dev/null", "-H", f"Host: files.{w.DOMAIN}", f"{w.BASE}{m.group(1)}"])
|
|
return re.search(r"(?im)^set-cookie:\s*(felhom_files=[^;\r\n]+)", r.stdout or "").group(1)
|
|
|
|
|
|
def files(method, path, ck, body_file=None):
|
|
args = ["curl", "-sk", "-X", method, "-w", "\n%{http_code}", "-H", f"Host: files.{w.DOMAIN}", "-H", f"Cookie: {ck}"]
|
|
if body_file:
|
|
args += ["--data-binary", "@" + body_file]
|
|
r = w.sh(args + [f"{w.BASE}{path}"])
|
|
body, _, code = (r.stdout or "").rpartition("\n")
|
|
return code.strip(), body
|
|
|
|
|
|
def nets(c):
|
|
return sw.g("docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' " + c + " 2>&1").strip()
|
|
|
|
|
|
def main():
|
|
rec = {}
|
|
say = print
|
|
w.say = say
|
|
w.login()
|
|
tmp = os.path.join(w.SC, "probe.docx")
|
|
open(tmp, "wb").write(docx_bytes())
|
|
ck = None
|
|
try:
|
|
rec["install"] = w.deploy(APP, SUB) and w.wait_app(SUB, "/", tries=120, delay=5)
|
|
say("1. OnlyOffice installed and answering:", rec["install"])
|
|
code, body = w.ctl("POST", f"/api/integrations/{APP}/filebrowser", {"enabled": True}, raw=True)
|
|
say("2. link switched on ->", code, body[:120])
|
|
for _ in range(30):
|
|
time.sleep(10)
|
|
if "felhom-files" in nets("onlyoffice"):
|
|
break
|
|
say("3. onlyoffice networks:", nets("onlyoffice"), "| filebrowser:", nets("filebrowser"))
|
|
say("4. filebrowser -> onlyoffice /healthcheck:",
|
|
sw.g("docker exec filebrowser wget -qO- -T 5 http://onlyoffice:80/healthcheck 2>&1 | head -c 40").strip())
|
|
say("5. onlyoffice -> filebrowser /health:",
|
|
sw.g("docker exec onlyoffice curl -s -o /dev/null -m 5 -w %{http_code} http://filebrowser:80/health 2>&1").strip())
|
|
say("6. control: paperless-webserver -> filebrowser:",
|
|
sw.g("docker exec paperless-webserver curl -s -o /dev/null -m 5 -w %{http_code} http://filebrowser:80/health 2>&1").strip())
|
|
ck = files_cookie()
|
|
code, _ = files("POST", f"/api/resources?{Q}&override=true", ck, tmp)
|
|
say("7. household uploads the .docx ->", code)
|
|
code, body = files("GET", f"/api/office/config?{Q}", ck)
|
|
url = ""
|
|
try:
|
|
cfg = json.loads(body)
|
|
url = ((cfg.get("document") or {}).get("url")) or ""
|
|
except ValueError:
|
|
pass
|
|
u = urlsplit(url) if url else None
|
|
say("8. editor config ->", code, "| document url:", f"{u.scheme}://{u.netloc}{u.path}" if u else "(none)")
|
|
if url:
|
|
got = sw.g("docker exec onlyoffice curl -s -o /dev/null -m 10 -w %{http_code} '" + url.replace("'", "") + "' 2>&1").strip()
|
|
say("9. OnlyOffice fetches that document ->", got)
|
|
finally:
|
|
if ck:
|
|
say("10. delete the .docx ->", files("DELETE", f"/api/resources?{Q}", ck)[0])
|
|
say("11. link off ->", w.ctl("POST", f"/api/integrations/{APP}/filebrowser", {"enabled": False}, raw=True)[0])
|
|
sw.remove(APP, keep=False, say=say)
|
|
say("12. leftovers:", sw.leftovers(APP, SUB))
|
|
say("13. filebrowser networks after:", nets("filebrowser"))
|
|
os.remove(tmp)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|