$ cat writeup.md…
$ cat writeup.md…
avitoctf
Task: Analyze an Android APK whose runtime secret is placed in a Python environment variable and whose documentation opens in a WebView. Solution: Chain a reviewer deep link with an open redirect, retain the JavaScript bridge, execute Python, and exfiltrate its output.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
Перед вами Pytome — защищенная от пчёл-вайбкодеров мобильная платформа по передаче вымирающего знания программирования на питоне, разработанная медоедами для медоедов. Медоеды очень боятся слить свои переменные окружения в приложении, особенно FLAG=..., пчёлам. Помоги им понять, возможно ли это сделать.
The supplied artifact was an Android APK for a Python learning application. The goal was to determine whether the secret environment variable in the organizer's Android instance could be disclosed.
Artifact: https://avitoctf.ru/files/pytome.apk
Pytome embeds CPython 3.11 through Chaquopy. Its exported documentation activity accepts a deep link, checks only the first URL's scheme and host, and loads that URL in a JavaScript-enabled WebView. Because an allowlisted endpoint provides an unrestricted redirect, an external page can inherit the exposed AndroidBridge, invoke unrestricted Python execution, read the runtime environment, and send the result to a callback server.
The complete chain was:
pytome://docs deep link.DocsActivity accepted an allowlisted HTTPS URL./go?to= endpoint redirected the WebView to an external payload.AndroidBridge.eval(...), which reached Python exec.Static APK inspection found no real flag in resources, Chaquopy archives, or application-specific native code. This was expected after reviewing MainActivity: the organizer supplies the value as an Intent string extra when launching the challenge instance.
Decompiled reference: jadx/sources/ru/avitoctf/pytome/MainActivity.java:38-41.
String stringExtra = getIntent().getStringExtra("FLAG"); if (stringExtra != null) { PythonEngine.INSTANCE.setEnv(stringExtra); }
PythonEngine.setEnv stores it in CPython's process environment.
Decompiled reference: jadx/sources/ru/avitoctf/pytome/PythonEngine.java:22-38.
PyObject module = python.getModule("os"); PyObject environ = (PyObject) module.get((Object) "environ"); environ.callAttr("__setitem__", "FLAG", env);
...
$ grep --similar