Responds to the independent security review, which correctly flagged that the previous
commit published a live credential.
The Terminal plugin's in-app password gate is a hardcoded constant. The previous commit
quoted the value verbatim in docs/known-issues.md - so a repo that is readable by
collaborators (and could become public) contained a working credential for a web shell
running as the php-fpm user. That was my error.
Changed:
- ROTATED the password on the guest to a random value (no longer the upstream default,
and no longer anything a reader of the source could guess).
- REMOVED the value from docs/known-issues.md. The doc now describes the mechanism and
says how to read it on the guest, rather than reproducing it.
- REDACTED it from patches/Terminal-terminal.php.patched (shows a placeholder; the real
value lives only on the guest).
- Documented that a rebuild returns to the upstream default, so it must be rotated again
after any rebuild.
Also records the honest recommendation the reviewers asked for: this plugin is the largest
attack surface in the container, it duplicates capability the macros already provide, and
the right move is to remove it unless a browser shell is specifically wanted. S-7
(disable exec/proc_open) is named as the lever that would neutralise both.
Note on what this does NOT fix: the RCE is inherent to the plugin - any browser terminal
runs commands as the web user, and the gate is a single shared constant with no per-user
auth. Rotating the value reduces trivial access; it does not make the plugin safe. That is
stated plainly rather than papered over.
173 lines
7.6 KiB
Markdown
173 lines
7.6 KiB
Markdown
# Known Issues & Patches
|
|
|
|
Upstream Atheos is an actively-moving project with **no releases**, and its plugin market ships
|
|
plugins that are **ports from Codiad** (Atheos's predecessor). Two of those plugins use APIs that
|
|
Atheos has since removed or changed. This file records what we hit, what we changed, and why the
|
|
fix is done the way it is.
|
|
|
|
Pinned Atheos commit: `f90cd1806dbe0e25673a77e27390e8b1a240c3f6` (build 611).
|
|
|
|
---
|
|
|
|
## 1. 🔴 Plugin market: the **Terminal** plugin does not work as shipped
|
|
|
|
**Symptom:** installed the Terminal plugin from the market; it failed / did not function.
|
|
|
|
**Root cause — a removed API.** `plugins/Terminal/terminal.php` calls `Common::execute(...)`, which
|
|
**no longer exists** in Atheos (it was renamed):
|
|
|
|
```
|
|
plugin calls : Common::execute(...) -> MISSING
|
|
available : Common::raw_execute(...) -> returns ["code" => int, "output" => array]
|
|
Common::safe_execute(...)
|
|
```
|
|
|
|
The plugin's header says it is a port of `Fluidbyte/Codiad-Terminal`, so it targets the old API. Its
|
|
return-shape assumption is also stale (the old `execute()` returned `text`; `raw_execute()` returns
|
|
`output` as an array).
|
|
|
|
**Fix applied** (`patches/Terminal-terminal.php.patched`):
|
|
|
|
```diff
|
|
- $output = Common::execute("pwd");
|
|
+ $output = Common::raw_execute("pwd");
|
|
- $_SESSION["activeDir"] = array([$_SESSION["project"]] => $output["text"]);
|
|
+ $_SESSION["activeDir"] = array([$_SESSION["project"]] => implode("\n", (array)$output["output"]));
|
|
|
|
- $output = Common::execute($cmd);
|
|
- return $output["text"];
|
|
+ $output = Common::raw_execute($cmd);
|
|
+ return implode("\n", (array)$output["output"]);
|
|
```
|
|
|
|
**Verified** against the real method (`php83 -l` clean; both call patterns exercised):
|
|
|
|
| Call | Result |
|
|
|---|---|
|
|
| `raw_execute("pwd")` | `code=0`, `output=["/root",""]` |
|
|
| `raw_execute("echo ...")` | `code=0`, returns the text |
|
|
| after `chdir` to the repo | returns `/var/www/atheos/workspace/blog` — so the terminal opens in the blog |
|
|
|
|
**Re-apply after an Atheos update** — `scripts/update-atheos.sh` replaces the application files, so
|
|
this patch is lost on update. Re-apply it, or drop the plugin.
|
|
|
|
### Two further defects in the same plugin (NOT fixed — upstream, and mostly harmless)
|
|
|
|
1. **`terminal.php:95`** — `$blocked = explode(",", BLOCKED === true);` passes a **boolean** to
|
|
`explode()`. This evaluates to `explode(",", false)`, so the intended block-list
|
|
(`define("BLOCKED","ssh,telnet")`) **never actually blocks anything**. The plugin's own guard is
|
|
dead code. Cosmetic in effect (the command would just fail on its own), but worth knowing rather
|
|
than trusting.
|
|
2. **`terminal.php:124`** — `if (strtolower($command === "exit"))` passes a boolean to
|
|
`strtolower()`. Should be `strtolower($command) === "exit"`. Result: typing `exit` does not clear
|
|
`term_auth`; the session stays authenticated until the browser session ends.
|
|
|
|
Left as-is: they are upstream bugs, and fixing them changes the plugin's behaviour beyond the
|
|
minimum needed to make it run. Recorded here so nobody "fixes" them by accident or trusts the
|
|
block-list.
|
|
|
|
### ⚠️ Security note
|
|
|
|
This plugin gives a **browser terminal that executes shell commands as the php-fpm user**
|
|
(`atheos`). Two things matter:
|
|
|
|
1. **The plugin ships with a well-known, hardcoded gate constant.** Upstream is literally
|
|
`define("PASSWORD", "<the default value in the plugin source>");`. Anyone who can reach the page
|
|
can read the source on GitHub and unlock it. **We have rotated it to a random value** — retrieve
|
|
it from the `PASSWORD` define in `plugins/Terminal/terminal.php` on the guest. It is deliberately
|
|
**not recorded in this repo** (see below).
|
|
2. **It is a shell, so treat it as a privilege decision, not a convenience one.** Installing it
|
|
means the editor can run arbitrary commands as `atheos` — the same user that owns the deploy key.
|
|
|
|
Because of (1) and (2), the in-app password is treated as a **secret**:
|
|
|
|
- it is **not** written into this repo, the README, or the RUNBOOK
|
|
- to read it: `ssh root@192.168.178.57 'grep PASSWORD /var/www/atheos/plugins/Terminal/terminal.php'`
|
|
- it changes per install; `scripts/provision.sh` does not set one, so a rebuild returns to the
|
|
upstream default — **rotate it again after a rebuild**
|
|
|
|
**Recommendation: remove this plugin unless you specifically want a browser shell.** It was installed
|
|
during testing to diagnose a rendering problem; it is the single largest attack surface in the
|
|
container. Note that Atheos macros already provide command execution, so the plugin adds little you
|
|
do not already have — and macros are at least under your own configuration.
|
|
|
|
`docs/security-notes.md` §2 S-7 covers disabling `exec`/`proc_open`, which would neutralise **both**
|
|
this plugin and the macros. That is the correct lever if you ever expose this container.
|
|
|
|
---
|
|
|
|
## 2. 🟠 The Macro dialog crashed when folder-scoped macros existed
|
|
|
|
**Symptom:** the Settings → Macro dialog returned HTTP 500.
|
|
|
|
**Root cause — invalid data by Atheos's own design.** `components/macro/dialog.php:35` does:
|
|
|
|
```php
|
|
implode(", ", $macro["fTypes"]) // requires an ARRAY
|
|
```
|
|
|
|
but `components/macro/class.macro.php` `save()` writes the **string** `"N/A"` for folder macros:
|
|
|
|
```php
|
|
"fTypes" => $type === "file" ? explode(",", $fTypes) : "N/A",
|
|
```
|
|
|
|
So **any folder-type macro** makes that dialog fatal:
|
|
|
|
```
|
|
PHP Fatal error: Uncaught TypeError: implode(): Argument #2 ($array) must be of type ?array,
|
|
string given in /var/www/atheos/components/macro/dialog.php:35
|
|
```
|
|
|
|
**Fix applied — correct the DATA, not Atheos's code.** The stored `fTypes` for folder macros is now
|
|
`[]` (an empty array) instead of `"N/A"`:
|
|
|
|
```sh
|
|
sed -i 's/"fTypes": "N\/A"/"fTypes": []/g' /var/www/atheos/data/macros.db.php
|
|
```
|
|
|
|
Verified: `implode()` now succeeds for all 8 macros.
|
|
|
|
**Why data rather than code:** patching `dialog.php` would be overwritten by `update-atheos.sh`
|
|
(and by any Atheos update), whereas valid data keeps working. The trade-off is that the UI's own
|
|
"save" path could re-introduce `"N/A"` if a macro is edited in the UI — see the follow-up below.
|
|
|
|
**Follow-up (not done):** the real upstream fix is
|
|
`$type === "file" ? explode(",", $fTypes) : []` in `class.macro.php::save()`. Applying it would stop
|
|
the UI re-introducing the bad value, at the cost of a patch that must be re-applied after updates.
|
|
Worth doing if the dialog starts 500-ing again after editing a macro in the UI.
|
|
|
|
---
|
|
|
|
## 3. Checking for plugin problems in future
|
|
|
|
The plugin's JS is bundled into `public/plugins.min.js` (built by `classes/sourcemanager.php`), and
|
|
its rightbar button is rendered by `components/sidebars/sb-right.php` from `plugin.json`. Both were
|
|
verified working:
|
|
|
|
```
|
|
plugins.min.js contains 'plugins/Terminal/init.js' -> YES (bundle is fine)
|
|
rightbar HTML -> <a onclick="atheos.Terminal.open();">
|
|
<i class="fas fa-terminal"></i>Terminal</a>
|
|
```
|
|
|
|
So when a plugin misbehaves, check in this order:
|
|
|
|
```sh
|
|
# 1. did it install?
|
|
tail /var/www/atheos/data/log/market.log.php
|
|
|
|
# 2. is its JS in the served bundle?
|
|
grep -c "plugins/<Name>/init.js" /var/www/atheos/public/plugins.min.js
|
|
|
|
# 3. is it a stale-API port? (Codiad-era plugins are the risk)
|
|
grep -rhoE 'Common::[a-zA-Z_]+' /var/www/atheos/plugins/<Name>/ | sort -u
|
|
# then check each exists: grep -rn "function <name>" /var/www/atheos/common.php /var/www/atheos/traits/
|
|
|
|
# 4. is there a PHP fatal?
|
|
grep -E 'PHP Fatal' /var/log/nginx/atheos.error.log | tail
|
|
```
|
|
|
|
**Rule of thumb:** if a plugin was ported from Codiad, assume its `Common::` calls are stale and
|
|
verify them before trusting it.
|