CVE-2025-63414: REMOTE CODE EXECUTION IN ALLSKY WEBUI DUE TO IMPROPER COMMAND VALIDATION

Published By: BLUE TEAM

Published On: 31/12/2025

Published in:

Last year, I discovered a Path Traversal vulnerability in the open-source Allsky WebUI project, which was later assigned CVE-2024-44373. For someone who was still new in the field like me, that was a big milestone. It was genuinely exciting — almost like I finally contributed something meaningful to the open-source community.

While working on that issue, I also realized the project’s WebUI wasn’t getting much security focus, even though it’s the main part users use to manage and configure their Allsky cameras. Because I had a lot going on at the time, I couldn’t finish reviewing the whole project. Everything had to be put on hold.

Recently, while going through the notes I left unfinished, I found another vulnerability. At first glance, it appeared to be another Path Traversal vulnerability that could lead to remote code execution, similar to CVE-2024-44373. However, a deeper analysis revealed that the root cause was different and more subtle, ultimately resulting in arbitrary command execution through improper command handling. This issue has since been assigned CVE-2025-63414.

You can find more information about CVE-2024-44373 here.

Figure 1. The Allsky camera is a specialized, fisheye-lens imaging system used in astronomy to observe the entire sky and monitor celestial phenomena.

I.Whitebox Analysis

The first step in my analysis was reviewing the project’s source code. Whitebox Testing gives you the advantage of seeing how the entire system fits together. Instead of testing only through the front-end, you can understand what happens behind the scenes by directly examining the application’s code. This is also the part I enjoy most when doing Whitebox Testing.

Focusing on the scope where I discovered CVE-2025-63414, the vulnerability is located in the execute.php file inside the project’s html directory. Below is the detailed breakdown of the vulnerable section of the source code:

function execute($cmd, $args="", $outputToConsole=false)
{
       global $use_TEXT, $sep;

       // Do NOT quote $args since there may be multiple arguments.
       $cmd = escapeshellcmd("sudo --user=" . ALLSKY_OWNER . " $cmd $args");
       $result = null;
       exec("$cmd 2>&1", $result, $return_val);

       if ($result !== null) {
              $result = implode($sep, $result);
       }
       if (! $use_TEXT && $outputToConsole) {
              // Writing to the console aids in debugging.
              $cmd = str_replace("'""'", $cmd);
              echo "<script>console.log(";
              echo "'[$cmd] returned $return_val, result=$result'";
              echo ");</script>\n";
       }

       if (checkRet($cmd, $return_val, $result)) {
              return "";
       } else {
              return($result);
       }
}

The execute() function acts as the “sink” for CVE-2025-63414 because it relies on the dangerous exec() function to run system commands directly. The line $cmd = escapeshellcmd("sudo --user=" . ALLSKY_OWNER . " $cmd $args");  shows that the final command is constructed by concatenating several components: the sudo prefix, the target user, the $cmd value (taken from the first part of the id parameter), and $args (the remaining portion of the same parameter). The problem is that both $cmd and $args are fully controlled by user input. Although the intended design assumes $cmd will match a valid script name inside the scripts directory, the function actually accepts arbitrary strings.

The developer attempted to mitigate command-injection risks by using escapeshellcmd(). However, this is the wrong tool for the job. escapeshellcmd() only escapes certain shell metacharacters to prevent injection of additional commands; it does not prevent path traversal or the use of crafted paths. As a result, malicious input that resembles a path outside the expected directory can still pass through unchanged.

Another issue is highlighted by the comment // Do NOT quote $args. Leaving $args unquoted—and not passing it through escapeshellarg()—means the function allows arbitrary parameters to be appended to whatever binary ends up being executed. Once an attacker manages to redirect execution to an unintended binary, they can supply arguments that cause the binary to run commands of their choosing.

$ID = getVariableOrDefault($_REQUEST, 'id'null);
if ($ID === null) {
       $ID = getVariableOrDefault($_REQUEST, 'ID'null);
       if ($ID !== null) {
              $use_TEXT = true;
       }
}

Continuing from the code above, it defines the entry point of the untrusted data. The $ID variable is populated directly from the $_REQUEST superglobal — which aggregates user input from GET, POST, and COOKIE — via the helper function getVariableOrDefault. The application accepts this input without performing any validation, pattern checks, or sanitization at the boundary. The helper function simply checks whether the key exists; it does not enforce any restrictions on the content itself. As a result, any string provided by the client — including path traversal sequences or other unexpected characters — is passed through unchanged and becomes part of $ID.

From this behavior, we can outline the taint flow for the vulnerability:

The untrusted id parameter from $_REQUEST is assigned directly to $ID with no filtering. The code later splits this value based on whitespace, treating the first segment as $ID and any remaining segments as $ARGS. Because this logic assumes well-behaved input but applies no actual validation, an attacker can influence both variables. Ultimately, these tainted values propagate into the command construction logic discussed earlier, leading to unintended behavior in the execution path.

case "allsky-config":
       if ($ARGS === "") {
              echo "${eS}ERROR: Argument not given to command ID: '${ID}'.${eE}";
              exit(1);
       }
       $CMD = ALLSKY_SCRIPTS . "/$ID";
       execute($CMD, $ARGS);
       break;

The execution flow includes a case that handles the "allsky-config" command. Inside this case, the $CMD variable is constructed by concatenating ALLSKY_SCRIPTS with the user-supplied $ID. For example, a value that contains a path traversal sequence results in something like:

/home/pi/allsky/scripts/allsky-config/../../../../bin/

After the system normalizes this path, it resolves to /bin/, effectively pointing outside the intended scripts directory. Both $CMD and $ARGS are then passed into the execute() function.

Within execute(), the final command is assembled and executed through exec(). Even though escapeshellcmd() is applied, it only escapes specific shell metacharacters and does not prevent path traversal. This allows an attacker to influence which binary is executed and to supply additional arguments through $ARGS. As a result, the function may end up invoking a system binary with user-controlled parameters, leading to unintended command execution under the privileges of the target user.

All of the above scenarios are merely theoretical assumptions about how this issue might be triggered. However, whether it actually behaves that way in practice is a completely different story. To verify whether this is truly a serious security vulnerability, we need to build a PoC on a real Allsky WebUI instance, capture the request using BurpSuite, and analyze what is actually happening.

II.Exploit

Assuming a scenario where everything lines up perfectly — where the vulnerability triggers exactly the way we expect — is the ideal case. But with CVE-2025-63414, things are nowhere near that simple.

With all the payloads I’ve thrown at it, the outcome has been consistently disappointing. The system just keeps spitting back “Unknown command ID.”

Why do we get the "Unknown command ID" error here in the first place? That question basically forces us to go back and carefully re-check the code’s execution flow.

The logic that parses the spaces only splits $ID into two parts based on the first space. That means if our payload is:

allsky-config/../../../../bin/bash -c whoami

then after parsing, we’ll have:

$ID = allsky-config/../../../../bin/bash
$ARGS = -c whoami

To hit the "allsky-config" case in the switch statement, $ID has to match the string "allsky-config" exactly. But with this payload, $ID is allsky-config/../../../../bin/bash, which is a completely different string. Obviously, the switch can’t find a matching case, so execution falls through to the default branch and returns the "Unknown command ID" error. If $ID has to be strictly "allsky-config" to get into that case, then the path traversal cannot live inside $ID. That means we either need a different way to inject the path traversal, or we need to find another case in the switch where $ID isn’t hardcoded-checked but is still used to build a path.

Notice that in the "allsky-config" case, the line:

$CMD = ALLSKY_SCRIPTS . "/$ID";

It still concatenates $ID into the path — but the condition to even enter this case requires $ID to match exactly. So the real bug probably isn’t about injecting the path into $ID at all. It’s more likely hiding somewhere else that we haven’t looked at yet — maybe via $ARGS, or in another case in the switch where $ID is more flexible.

This is where I stumbled onto another critical piece of the CVE: the allsky-config.sh file. The vulnerability doesn’t come from changing the $CMD path (because that’s fixed); it comes from the fact that the allsky-config.sh script (called from PHP) itself contains a serious security flaw.

Digging into allsky-config.sh:

if [[ -z ${CMD} ]];  then
    # ... (Interactive mode logic)
else
    # Command-line mode logic
    # shellcheck disable=SC2086
    run_command "${CMD}" ${CMD_ARGS}
    exit $?
fi

The run_command function in this script only checks whether the command ($CMD) exists — using type — and never validates it against any kind of whitelist. Because of that, if you pass a system command (like whoami, ls, cat) as the first argument to allsky-config, the script will happily execute it. So instead of trying to force $ID to point to /bin/bash, the smarter move is to keep $ID as allsky-config to get past the switch-case, and then push the command you actually want to run into the argument section after the space.

http://[TARGET-IP]/html/execute.php?id=allsky-config%20whoami
http://[TARGET-IP]/html/execute.php?id=allsky-config%20cat%20/etc/passwd

Even though the behavior of the application differed from our initial assumptions — largely due to the way the project components are interconnected — the analysis and proof of concept clearly demonstrate that CVE-2025-63414 results in arbitrary command execution due to improper command handling in the Allsky WebUI.

From the project’s development side, you can tell they did try to block it, but those efforts were nowhere near enough. The “protection” provided by escapeshellcmd is basically a flimsy wooden fence trying to stop a horse that’s already sprinting full speed. If we can’t inject special characters straight into the command line, then fine — we just pull in an external script, give it execution permission, and run it. Whatever’s inside that script sits completely outside the control of escapeshellcmd.

Looking at the project’s version history, execute.php only appeared starting from v2024.12.06_01, and the vulnerability affects the range from v2024.12.06_02 through v2024.12.06_06.

This is an extremely serious RCE flaw, and it needs a proper fix sooner rather than later. And honestly, there’s no way a single patch — like swapping out one escape function — can fully neutralize something this deep. A real solution has to follow a Defense-in-Depth approach with multiple safety layers:

  • Layer 1: A strict whitelist of allowed commands.
  • Layer 2: Use escapeshellarg() instead of escapeshellcmd().
  • Layer 3: Restrict Sudo privileges.

I’m pretty sure this won’t be the last report from my team for this project. The system clearly has more corners worth inspecting. And since this write-up is already long enough, all the extra pieces — the exact remediation steps, the other security gaps you found for gaining access to the admin page, and the way you spun up a fake Allsky Camera to create an ideal test environment — they can wait for the next blog entries. See you in the next one 🙂

91 lượt xem