
pfSense CE 2.9.0-DEVELOPMENT - High - Remote - RCE - limiter number dnctl
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · pfsense-limiter-number
Class: RCE Reach: Remote
pfSense CE 2.9.0-DEVELOPMENT (9363ac5) - pfsense.org
Traffic Shaper Limiters emit a hidden number field on edit. Create overwrites that number with dnpipe_find_nextnumber(). Edit does not. validate_input never checks number. GetNumber() is concatenated unquoted into dnctl pipe|sched|queue delete. PHP mwexec is /bin/sh -c. Privilege is page-firewall-trafficshaper-limiter, not Diagnostics: Command.
| ID | no CVE yet |
| Class | RCE |
| Reach | Remote (authenticated WebGUI) |
| CWE | CWE-78 |
| CVSS | High: 8.8 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Product | pfSense CE |
| Affected | 2.9.0-DEVELOPMENT (9363ac5b8651a1c7a333180425ce7719070f95f9) limiter number |
| Auth | WebGUI session with page-firewall-trafficshaper-limiter |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
I pinned public master 9363ac5. The lab is a PHP gadget of pinned ReadConfig / SetNumber / delete_queue. Loopback 127.0.0.1:18840.
| Method | Sink | Privilege |
|---|---|---|
Create a limiter, POST save with attacker number, then delete that limiter | mwexec("/sbin/dnctl pipe delete " . $this->GetNumber()) (shaper.inc:3820) | Integrity (lab-proven): root shell. Lab: edit-delete wrote the witness (edit-delete-exec=yes). Create overwrite and apply-file path wrote none. |
| Same root shell | host filesystem | Recon (inferred): config.xml, keys. |
Root shell or dnctl of attacker numbers | dummynet | Availability (inferred): limiter / dummynet disruption. |
Lab-proven vs inferred: create overwrite (no witness), apply-file path (dnctl of a file, no witness), and edit-delete exec (witness) are lab-proven. Filesystem recon is inferred from root PHP-FPM.
Requires: WebGUI session with page-firewall-trafficshaper-limiter. CSRF on POST save. Delete is GET action=delete.
| Surface | Lab |
|---|---|
| Create path | dnpipe_find_nextnumber() overwrites, create-overwrites=yes |
| Apply path | dnctl of /tmp/rules.limiter, filename not user-controlled, apply-file=yes |
| Limiter name charset | ^[a-zA-Z0-9_-]+$ |
| Unauth | closed |
| Diagnostics: Command | stays closed unless also granted |
AQM params into /tmp/rules.limiter are extra dummynet language, not this leftover.
cd lab
./run.sh
Loopback 127.0.0.1:18840. Compose project pfsense-limiter-number. Image tag pfsense-limiter-number:2.9.0-dev. Witness PFSENSE-LIMITER-NUMBER-WITNESS. Last line:
SUCCESS PFSENSE-LIMITER-NUMBER create-overwrites=yes apply-file=yes edit-delete-exec=yes PFSENSE-LIMITER-NUMBER-WITNESS
Int-cast GetNumber() before every dnctl. Ignore POST number on edit, or range-check it against the live dummynet id. Create already overwrites; make edit do the same.
9363ac5b8651a1c7a333180425ce7719070f95f9shaper.inc delete_queue / GetNumberfirewall_shaper_vinterface.php