Skip to content

Commit f16a44d

Browse files
docs: add Hardening-Cacti-Installation guide
1 parent c707c76 commit f16a44d

1 file changed

Lines changed: 293 additions & 0 deletions

File tree

Hardening-Cacti-Installation.md

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
# Hardening a Cacti Installation
2+
3+
This page covers OS-level hardening controls for a production Cacti host.
4+
The goal is defense in depth: even if a vulnerability in Cacti's PHP code
5+
is exploited, the attacker's reach should be limited.
6+
7+
## Why These Controls Matter
8+
9+
A recurring class of vulnerability in Cacti involves writing an arbitrary
10+
file into the `resource/` or `scripts/` tree and then reaching that file
11+
over the web. The package-import advisory (GHSA-jj7m-3x5c-9xvr) is a
12+
concrete example: a crafted package archive could write `resource/test.php`
13+
and execute it as a web request. The controls below close or limit that
14+
path at the OS and web-server layers, independent of the PHP fix.
15+
16+
---
17+
18+
## 1. File Permissions
19+
20+
Cacti writes to three paths at runtime. Everything else should be owned
21+
by root (or a non-web user) and not writable by the web process.
22+
23+
| Path | Purpose | Recommended mode |
24+
|------|---------|-----------------|
25+
| `rra/` | RRDtool data files | `www-data:www-data 750` |
26+
| `log/` | Cacti log files | `www-data:www-data 750` |
27+
| `cache/` | Boost / poller cache | `www-data:www-data 750` |
28+
| `resource/` | XML templates | `root:root 755` (read-only for web) |
29+
| `scripts/` | Data-source scripts | `root:root 755` (read-only for web) |
30+
| `lib/`, `include/` | PHP libraries | `root:root 755` |
31+
32+
Apply once after installation:
33+
34+
```bash
35+
# Writable paths (web user only)
36+
chown -R www-data:www-data /var/www/html/cacti/rra \
37+
/var/www/html/cacti/log \
38+
/var/www/html/cacti/cache
39+
chmod -R 750 /var/www/html/cacti/rra \
40+
/var/www/html/cacti/log \
41+
/var/www/html/cacti/cache
42+
43+
# Everything else read-only for the web user
44+
chown -R root:root /var/www/html/cacti
45+
chmod -R 755 /var/www/html/cacti
46+
# Re-apply writable exceptions after the recursive chmod
47+
chown -R www-data:www-data /var/www/html/cacti/rra \
48+
/var/www/html/cacti/log \
49+
/var/www/html/cacti/cache
50+
```
51+
52+
> **Note:** The `scripts/` and `resource/` paths may be written during
53+
> template import or package import by a privileged CLI process running
54+
> as root, not by the web process. If your workflow requires the web
55+
> process to write there, use a strict code-review policy for any import
56+
> and consider the compat profile instead.
57+
58+
---
59+
60+
## 2. Web Server — Block Direct Access to Scripts and Resources
61+
62+
PHP files inside `scripts/` and `resource/` must not be reachable via HTTP.
63+
These directories hold data-source scripts and XML templates; a web request
64+
to a `.php` file written there is the direct exploitation path.
65+
66+
### Apache
67+
68+
Add inside your Cacti `<VirtualHost>` or `.htaccess`:
69+
70+
```apache
71+
# Deny direct HTTP access to the script and resource trees.
72+
# A PHP file written here via import cannot be reached over the web.
73+
<DirectoryMatch "^/var/www/html/cacti/(scripts|resource)/">
74+
<FilesMatch "\.php$">
75+
Require all denied
76+
</FilesMatch>
77+
</DirectoryMatch>
78+
```
79+
80+
Or, more broadly, deny all non-XML/non-script files from direct access:
81+
82+
```apache
83+
<Directory "/var/www/html/cacti/scripts">
84+
Require all denied
85+
</Directory>
86+
87+
<Directory "/var/www/html/cacti/resource">
88+
<FilesMatch "\.php$">
89+
Require all denied
90+
</FilesMatch>
91+
</Directory>
92+
```
93+
94+
### Nginx
95+
96+
Add inside your `server {}` block:
97+
98+
```nginx
99+
# Block PHP execution in scripts/ and resource/
100+
location ~ ^/cacti/(scripts|resource)/.*\.php$ {
101+
return 403;
102+
}
103+
```
104+
105+
---
106+
107+
## 3. PHP-FPM Pool Hardening
108+
109+
PHP itself warns that php-fpm pools are not a true security boundary, but
110+
they provide useful knobs. Create a dedicated pool for Cacti
111+
(`/etc/php/8.x/fpm/pool.d/cacti.conf`):
112+
113+
```ini
114+
[cacti]
115+
user = www-data
116+
group = www-data
117+
118+
listen = /run/php/php-cacti.sock
119+
listen.owner = www-data
120+
listen.group = www-data
121+
122+
; Drop all inherited environment variables — pass only what Cacti needs.
123+
clear_env = yes
124+
125+
; Only execute .php files — blocks .phtml, .php3, .phar, etc.
126+
security.limit_extensions = .php
127+
128+
; Disable dangerous functions at the pool level (not overridable by ini_set).
129+
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,popen,proc_open,pcntl_exec
130+
131+
; Restrict file operations to the Cacti tree.
132+
; NOTE: open_basedir disables the PHP realpath cache. Measure the
133+
; performance impact before enabling in production. It is an extra
134+
; safety net, not a full security boundary (PHP documentation).
135+
; php_admin_value[open_basedir] = /var/www/html/cacti:/tmp
136+
137+
; Suppress PHP version disclosure.
138+
php_admin_flag[expose_php] = Off
139+
```
140+
141+
Reload php-fpm after changes:
142+
143+
```bash
144+
systemctl reload php8.x-fpm # Debian/Ubuntu
145+
systemctl reload php-fpm # RHEL/Rocky/Alma
146+
```
147+
148+
---
149+
150+
## 4. SELinux (RHEL / AlmaLinux / Rocky Linux)
151+
152+
SELinux ships enforcing on RHEL-family systems. These commands apply
153+
the correct file contexts so Apache can serve Cacti while limiting what
154+
the web process can write.
155+
156+
### File Contexts
157+
158+
```bash
159+
CACTI=/var/www/html/cacti
160+
161+
# Read-only web content (Apache can read, not write)
162+
semanage fcontext -a -t httpd_sys_content_t "${CACTI}(/.*)?"
163+
semanage fcontext -a -t httpd_sys_content_t "${CACTI}/scripts(/.*)?"
164+
semanage fcontext -a -t httpd_sys_content_t "${CACTI}/resource(/.*)?"
165+
166+
# Read-write content for runtime paths only
167+
semanage fcontext -a -t httpd_sys_rw_content_t "${CACTI}/rra(/.*)?"
168+
semanage fcontext -a -t httpd_sys_rw_content_t "${CACTI}/log(/.*)?"
169+
semanage fcontext -a -t httpd_sys_rw_content_t "${CACTI}/cache(/.*)?"
170+
171+
# Apply the contexts
172+
restorecon -Rv "${CACTI}"
173+
```
174+
175+
### Database Network Access
176+
177+
If your database server is reached over TCP (not a local Unix socket),
178+
enable the SELinux boolean that permits it:
179+
180+
```bash
181+
setsebool -P httpd_can_network_connect_db on
182+
```
183+
184+
### Poller Systemd Drop-in
185+
186+
The Cacti spine poller runs as a separate process and may need to connect
187+
outward for SNMP. Add a drop-in under
188+
`/etc/systemd/system/cactid.service.d/selinux.conf`:
189+
190+
```ini
191+
[Service]
192+
# Allow spine to connect to arbitrary network hosts for SNMP polling.
193+
SELinuxContext=system_u:system_r:httpd_t:s0
194+
```
195+
196+
Run `systemctl daemon-reload && systemctl restart cactid` after changes.
197+
198+
> **Verify:** After applying contexts, confirm Apache can still serve pages
199+
> and the poller can poll. Use `audit2why` on any AVC denials in
200+
> `/var/log/audit/audit.log`.
201+
202+
---
203+
204+
## 5. AppArmor (Debian / Ubuntu)
205+
206+
Ubuntu Server ships AppArmor enforcing by default. Create a profile for
207+
the Cacti php-fpm pool at `/etc/apparmor.d/cacti-fpm`:
208+
209+
```
210+
#include <tunables/global>
211+
212+
profile cacti-fpm /usr/sbin/php-fpm* {
213+
#include <abstractions/base>
214+
#include <abstractions/php>
215+
#include <abstractions/mysql>
216+
217+
# Cacti tree — read-only by default
218+
/var/www/html/cacti/ r,
219+
/var/www/html/cacti/** r,
220+
221+
# Allow execution of Cacti PHP files
222+
/var/www/html/cacti/*.php r,
223+
/var/www/html/cacti/**/*.php r,
224+
225+
# Runtime write paths
226+
/var/www/html/cacti/rra/** rw,
227+
/var/www/html/cacti/log/** rw,
228+
/var/www/html/cacti/cache/** rw,
229+
230+
# Temp files
231+
/tmp/** rw,
232+
/var/tmp/** rw,
233+
234+
# Deny write to scripts/ and resource/
235+
deny /var/www/html/cacti/scripts/** w,
236+
deny /var/www/html/cacti/resource/** w,
237+
}
238+
```
239+
240+
Load and enforce the profile:
241+
242+
```bash
243+
apparmor_parser -r /etc/apparmor.d/cacti-fpm
244+
aa-enforce /etc/apparmor.d/cacti-fpm
245+
```
246+
247+
### Attach the Profile to the php-fpm Service
248+
249+
Add a drop-in at `/etc/systemd/system/php8.x-fpm.service.d/apparmor.conf`:
250+
251+
```ini
252+
[Service]
253+
AppArmorProfile=cacti-fpm
254+
```
255+
256+
Reload and restart:
257+
258+
```bash
259+
systemctl daemon-reload
260+
systemctl restart php8.x-fpm
261+
```
262+
263+
Use complain mode (`aa-complain`) first to test without blocking, then
264+
switch to enforce once logs show no legitimate denials:
265+
266+
```bash
267+
# Test mode
268+
aa-complain /etc/apparmor.d/cacti-fpm
269+
270+
# Enforce when ready
271+
aa-enforce /etc/apparmor.d/cacti-fpm
272+
```
273+
274+
---
275+
276+
## 6. Verification Checklist
277+
278+
After applying all controls, confirm:
279+
280+
- [ ] `curl -s http://localhost/cacti/scripts/ss_webseer.php` returns 403
281+
- [ ] `curl -s http://localhost/cacti/resource/` returns 403 for any `.php` inside
282+
- [ ] The Cacti web UI loads and graphs render (rra/ is writable)
283+
- [ ] The poller runs without errors (`tail -f /var/www/html/cacti/log/cacti.log`)
284+
- [ ] On RHEL: no AVC denials in `ausearch -m avc -ts recent`
285+
- [ ] On Debian/Ubuntu: no AppArmor denials in `journalctl -k | grep apparmor`
286+
287+
---
288+
289+
## See Also
290+
291+
- [Installing Under Ubuntu/Debian](Installing-Under-Ubuntu-Debian.md)
292+
- [Installing Under CentOS (LAMP)](Install-Under-CentOS_LAMP.md)
293+
- [Standards Security](Standards-Security.md)

0 commit comments

Comments
 (0)