daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

selinux.md (15469B)


      1 ---
      2 title: "SELinux"
      3 section: "Linux"
      4 sectionSlug: "linux-hardening"
      5 sourcePath: "src/linux-hardening/interesting-files-permissions/selinux.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/linux-hardening/interesting-files-permissions/selinux.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # SELinux
     14 
     15 SELinux is a **label-based Mandatory Access Control (MAC)** system. In practice, this means that even if DAC permissions, groups, or Linux capabilities look enough for an action, the kernel can still deny it because the **source context** is not allowed to access the **target context** with the requested class/permission.<sup>[[1]](#references)</sup>
     16 
     17 A context usually looks like:<sup>[[1]](#references)</sup>
     18 
     19 ```text
     20 user:role:type:level
     21 system_u:system_r:httpd_t:s0
     22 unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
     23 ```
     24 
     25 From a privesc perspective, the `type` (domain for processes, type for objects) is usually the most important field:<sup>[[1]](#references)</sup>
     26 
     27 - A process runs in a **domain** such as `unconfined_t`, `staff_t`, `httpd_t`, `container_t`, `sysadm_t`
     28 - Files and sockets have a **type** such as `admin_home_t`, `shadow_t`, `httpd_sys_rw_content_t`, `container_file_t`
     29 - Policy decides whether one domain can read/write/execute/transition to the other
     30 
     31 ## Fast Enumeration
     32 
     33 If SELinux is enabled, enumerate it early because it can explain why common Linux privesc paths fail or why a privileged wrapper around a "harmless" SELinux tool is actually critical:<sup>[[1]](#references)</sup>
     34 
     35 ```bash
     36 getenforce
     37 sestatus
     38 id -Z
     39 ps -eZ | head
     40 cat /proc/self/attr/current
     41 ls -Zd / /root /home /tmp /etc /var/www 2>/dev/null
     42 ```
     43 
     44 Useful follow-up checks:<sup>[[1]](#references)[[3]](#references)[[4]](#references)[[7]](#references)[[12]](#references)</sup>
     45 
     46 ```bash
     47 # Installed policy modules and local customizations
     48 semodule -lfull 2>/dev/null
     49 semanage fcontext -C -l 2>/dev/null
     50 semanage permissive -l 2>/dev/null
     51 semanage login -l 2>/dev/null
     52 semanage user -l 2>/dev/null
     53 
     54 # Labels that frequently reveal mistakes or unusual paths
     55 find / -context '*:default_t:*' -o -context '*:file_t:*' 2>/dev/null
     56 
     57 # Compare current label vs policy default for a path
     58 matchpathcon -V /path/of/interest 2>/dev/null
     59 restorecon -n -v /path/of/interest 2>/dev/null
     60 ```
     61 
     62 Interesting findings:<sup>[[1]](#references)[[3]](#references)[[7]](#references)[[19]](#references)</sup>
     63 
     64 - `Disabled` or `Permissive` mode removes most of the value of SELinux as a boundary.
     65 - `unconfined_t` usually means SELinux is present but not meaningfully constraining that process.
     66 - `default_t`, `file_t`, or obviously wrong labels on custom paths often indicate mislabeling or incomplete deployment.
     67 - Local overrides in `file_contexts.local` take precedence over policy defaults, so review them carefully.
     68 
     69 ## Policy Analysis
     70 
     71 SELinux is much easier to attack or bypass when you can answer two questions:
     72 
     73 1. **What can my current domain access?**
     74 2. **What domains can I transition into?**
     75 
     76 The most useful tools for this are `sepolicy` and **SETools** (`seinfo`, `sesearch`, `sedta`):<sup>[[2]](#references)[[9]](#references)</sup>
     77 
     78 ```bash
     79 # Transition graph from the current domain
     80 sepolicy transition -s "$(id -Z | awk -F: '{print $3}')" 2>/dev/null
     81 
     82 # Search allow and type_transition rules
     83 sesearch -A -s staff_t 2>/dev/null | head
     84 sesearch --type_transition -s staff_t 2>/dev/null | head
     85 
     86 # Inspect policy components
     87 seinfo -t 2>/dev/null | head
     88 seinfo -r 2>/dev/null | head
     89 ```
     90 
     91 This is especially useful when a host uses **confined users** rather than mapping everyone to `unconfined_u`. In that case, look for:<sup>[[3]](#references)</sup>
     92 
     93 - user mappings via `semanage login -l`
     94 - allowed roles via `semanage user -l`
     95 - reachable admin domains such as `sysadm_t`, `secadm_t`, `webadm_t`
     96 - `sudoers` entries using `ROLE=` or `TYPE=`
     97 
     98 If `sudo -l` contains entries like this, SELinux is part of the privilege boundary:<sup>[[3]](#references)</sup>
     99 
    100 ```text
    101 linux_user ALL=(ALL) ROLE=webadm_r TYPE=webadm_t /bin/bash
    102 ```
    103 
    104 Also check whether `newrole` is available:<sup>[[3]](#references)[[10]](#references)[[11]](#references)</sup>
    105 
    106 ```bash
    107 sudo -l
    108 which newrole runcon
    109 newrole -l 2>/dev/null
    110 ```
    111 
    112 `runcon` and `newrole` are not automatically exploitable, but if a privileged wrapper or a `sudoers` rule lets you select a better role/type, they become high-value escalation primitives.<sup>[[3]](#references)[[10]](#references)[[11]](#references)</sup>
    113 
    114 ## Files, Relabeling, and High-Value Misconfigurations
    115 
    116 The most important operational difference between common SELinux tools is:<sup>[[1]](#references)[[6]](#references)[[7]](#references)[[8]](#references)</sup>
    117 
    118 - `chcon`: temporary label change on a specific path
    119 - `semanage fcontext`: persistent path-to-label rule
    120 - `restorecon` / `setfiles`: apply the policy/default label again
    121 
    122 This matters a lot during privesc because **relabeling is not just cosmetic**. It can turn a file from "blocked by policy" into "readable/executable by a privileged confined service".<sup>[[1]](#references)[[7]](#references)[[8]](#references)</sup>
    123 
    124 Check for local relabel rules and relabel drift:<sup>[[1]](#references)[[7]](#references)[[8]](#references)</sup>
    125 
    126 ```bash
    127 grep -R . /etc/selinux/*/contexts/files/file_contexts.local 2>/dev/null
    128 restorecon -nvr / 2>/dev/null | head -n 50
    129 matchpathcon -V /etc/passwd /etc/shadow /usr/local/bin/* 2>/dev/null
    130 ```
    131 
    132 One subtle but useful detail: plain `restorecon` does **not always fully revert a suspicious label**. If the target type is in `customizable_types`, you may need `-F` to force a full reset. From an offensive perspective, this explains why an unusual `chcon` can sometimes survive a casual "we already ran restorecon" cleanup.<sup>[[8]](#references)</sup>
    133 
    134 ```bash
    135 grep -R . /etc/selinux/*/contexts/customizable_types 2>/dev/null | head
    136 restorecon -n -v /path/of/interest 2>/dev/null
    137 restorecon -F -v /path/of/interest 2>/dev/null
    138 ```
    139 
    140 High-value commands to hunt in `sudo -l`, root wrappers, automation scripts, or file capabilities:<sup>[[1]](#references)[[4]](#references)[[5]](#references)[[12]](#references)[[13]](#references)[[14]](#references)</sup>
    141 
    142 ```bash
    143 which semanage restorecon chcon setfiles semodule audit2allow runcon newrole setsebool load_policy 2>/dev/null
    144 getcap -r / 2>/dev/null | grep -E 'cap_mac_admin|cap_mac_override'
    145 ```
    146 
    147 If either MAC capability shows up, cross-check the [Linux capabilities page](/hacktricks/linux-hardening/interesting-files-permissions/linux-capabilities) as well; the Linux capabilities documentation describes `cap_mac_admin` and `cap_mac_override` as Smack-specific, so do not assume that their names alone bypass SELinux.<sup>[[5]](#references)</sup>
    148 
    149 Especially interesting:<sup>[[1]](#references)[[4]](#references)[[7]](#references)[[8]](#references)[[12]](#references)[[13]](#references)</sup>
    150 
    151 - `semanage fcontext`: persistently changes what label a path should receive
    152 - `restorecon` / `setfiles`: reapplies those changes at scale
    153 - `semodule -i`: loads a custom policy module
    154 - `semanage permissive -a <domain_t>`: makes one domain permissive without flipping the whole host
    155 - `setsebool -P`: permanently changes policy booleans
    156 - `load_policy`: reloads the active policy
    157 
    158 These are often **helper primitives**, not standalone root exploits. Their value is that they let you:<sup>[[1]](#references)[[4]](#references)[[7]](#references)[[8]](#references)[[12]](#references)[[13]](#references)[[14]](#references)</sup>
    159 
    160 - make a target domain permissive
    161 - broaden access between your domain and a protected type
    162 - relabel attacker-controlled files so a privileged service can read or execute them
    163 - weaken a confined service enough that an existing local bug becomes exploitable
    164 
    165 Example checks:<sup>[[1]](#references)[[7]](#references)[[8]](#references)</sup>
    166 
    167 ```bash
    168 # If sudo exposes semanage/restorecon, think in terms of policy abuse
    169 sudo -l | grep -E 'semanage|restorecon|setfiles|semodule|runcon|newrole|setsebool|load_policy'
    170 
    171 # Look for places where local file-context overrides may matter
    172 semanage fcontext -C -l 2>/dev/null
    173 restorecon -n -v /usr/local/bin /opt /srv /var/www 2>/dev/null
    174 ```
    175 
    176 If you can load a policy module as root, you usually control the SELinux boundary:<sup>[[1]](#references)[[4]](#references)[[14]](#references)</sup>
    177 
    178 ```bash
    179 ausearch -m AVC,USER_AVC -ts recent 2>/dev/null | audit2allow -M localfix
    180 sudo semodule -i localfix.pp
    181 ```
    182 
    183 That is why `audit2allow`, `semodule`, and `semanage permissive` should be treated as sensitive admin surfaces during post-exploitation. They can silently convert a blocked chain into a working one without changing classic UNIX permissions.<sup>[[1]](#references)[[4]](#references)[[12]](#references)[[14]](#references)</sup>
    184 
    185 ## Hidden Denials and Module Extraction
    186 
    187 A very common offensive frustration is a chain that fails with a bland `EACCES` while the expected AVC denial never appears. `dontaudit` rules may be hiding the exact permission you need. If you can run `semodule` through `sudo` or another privileged wrapper, temporarily disabling `dontaudit` can turn a silent failure into a precise policy clue:<sup>[[4]](#references)[[15]](#references)</sup>
    188 
    189 ```bash
    190 # Rebuild policy without dontaudit rules, trigger the action again, then inspect AVCs
    191 sudo semodule -DB
    192 ausearch -m AVC,USER_AVC,SELINUX_ERR -ts recent 2>/dev/null | tail -n 50
    193 sudo semodule -B
    194 
    195 # Extract installed modules for offline review / diffing
    196 semodule -lfull 2>/dev/null
    197 semodule -E --cil <module_name> 2>/dev/null
    198 ```
    199 
    200 This is also useful for reviewing what local admins already changed. A small custom module or one-domain permissive rule is often the reason a target service behaves much more loosely than the base policy would suggest.<sup>[[1]](#references)[[4]](#references)[[12]](#references)</sup>
    201 
    202 ## Audit Clues
    203 
    204 AVC denials are often offensive signal, not just defensive noise. They tell you:<sup>[[1]](#references)[[15]](#references)</sup>
    205 
    206 - which target object/type you hit
    207 - which permission was denied
    208 - which domain you currently control
    209 - whether a small policy change would make the chain work
    210 
    211 ```bash
    212 ausearch -m AVC,USER_AVC,SELINUX_ERR -ts recent 2>/dev/null
    213 journalctl -t setroubleshoot --no-pager 2>/dev/null | tail -n 50
    214 ```
    215 
    216 If a local exploit or persistence attempt keeps failing with `EACCES` or strange "permission denied" errors despite root-looking DAC permissions, SELinux is usually worth checking before discarding the vector.<sup>[[1]](#references)</sup>
    217 
    218 ## SELinux Users
    219 
    220 There are SELinux users in addition to regular Linux users. Each Linux user is mapped to an SELinux user as part of the policy, which lets the system impose different allowed roles and domains on different accounts.<sup>[[3]](#references)</sup>
    221 
    222 Quick checks:<sup>[[3]](#references)</sup>
    223 
    224 ```bash
    225 id -Z
    226 semanage login -l 2>/dev/null
    227 semanage user -l 2>/dev/null
    228 sudo -l 2>/dev/null
    229 grep -R "ROLE=\|TYPE=" /etc/sudoers /etc/sudoers.d 2>/dev/null
    230 ```
    231 
    232 On many mainstream systems, users are mapped to `unconfined_u`, which reduces the practical impact of user confinement. On hardened deployments, however, confined users can make `sudo`, `su`, `newrole`, and `runcon` much more interesting because **the escalation path may depend on entering a better SELinux role/type, not only on becoming UID 0**. Also remember that some confined users cannot invoke `sudo`/`su` at all unless policy explicitly allows the underlying setuid transition, so a host using `staff_u` + `sysadm_r` can turn a seemingly minor `sudo ROLE=` / `TYPE=` rule into the real privilege boundary.<sup>[[3]](#references)</sup>
    233 
    234 ## SELinux in Containers
    235 
    236 Container runtimes commonly launch workloads in a confined domain such as `container_t` and label container content as `container_file_t`. If a container process escapes but still runs with the container label, host writes may still fail because the label boundary stayed intact.<sup>[[1]](#references)[[17]](#references)</sup>
    237 
    238 Quick example:<sup>[[16]](#references)[[18]](#references)</sup>
    239 
    240 ```bash
    241 $ podman run -d fedora sleep 100
    242 d4194babf6b877c7100e79de92cd6717166f7302113018686cea650ea40bd7cb
    243 $ podman top -l label
    244 LABEL
    245 system_u:system_r:container_t:s0:c647,c780
    246 ```
    247 
    248 The `c647,c780` part is not decoration. In many container deployments, runtimes dynamically assign MCS categories so that two processes running as `container_t` are still separated from each other. If an escape lands you in a host namespace but keeps the original category set, category mismatches can still explain why some host paths remain unreadable or unwritable.<sup>[[17]](#references)</sup>
    249 
    250 Modern container operations worth noting:<sup>[[16]](#references)[[17]](#references)</sup>
    251 
    252 - `--security-opt label=disable` turns off SELinux label separation for the container
    253 - bind mounts with `:z` / `:Z` trigger relabeling of the host path for shared/private container use
    254 - broad relabeling of host content can become a security issue on its own
    255 
    256 This page keeps the container content short to avoid duplication. For the container-specific abuse cases and runtime examples, check:
    257 
    258 [Selinux](/hacktricks/linux-hardening/containers-namespaces/container-security/protections/selinux)
    259 
    260 ## References
    261 
    262 - [1] [Red Hat docs: Using SELinux](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html-single/using_selinux/index)
    263 - [2] [SETools: Policy analysis tools for SELinux](https://github.com/SELinuxProject/setools)
    264 - [3] [Managing confined and unconfined users - RHEL 9 docs](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/using_selinux/managing-confined-and-unconfined-users_using-selinux)
    265 - [4] [semodule(8) - Linux manual page](https://man7.org/linux/man-pages/man8/semodule.8.html)
    266 - [5] [capabilities(7) - Linux manual page](https://man7.org/linux/man-pages/man7/capabilities.7.html)
    267 - [6] [chcon(1) - Linux manual page](https://man7.org/linux/man-pages/man1/chcon.1.html)
    268 - [7] [semanage-fcontext(8) - Linux manual page](https://man7.org/linux/man-pages/man8/semanage-fcontext.8.html)
    269 - [8] [restorecon(8) - Linux manual page](https://man7.org/linux/man-pages/man8/restorecon.8.html)
    270 - [9] [sepolicy-transition(8) - Linux manual page](https://man7.org/linux/man-pages/man8/sepolicy-transition.8.html)
    271 - [10] [runcon(1) - Linux manual page](https://man7.org/linux/man-pages/man1/runcon.1.html)
    272 - [11] [newrole(1) - Linux manual page](https://man7.org/linux/man-pages/man1/newrole.1.html)
    273 - [12] [semanage-permissive(8) - Linux manual page](https://man7.org/linux/man-pages/man8/semanage-permissive.8.html)
    274 - [13] [setsebool(8) - Linux manual page](https://man7.org/linux/man-pages/man8/setsebool.8.html)
    275 - [14] [audit2allow(1) - Linux manual page](https://man7.org/linux/man-pages/man1/audit2allow.1.html)
    276 - [15] [ausearch(8) - Linux manual page](https://man7.org/linux/man-pages/man8/ausearch.8.html)
    277 - [16] [Podman run documentation](https://docs.podman.io/en/latest/markdown/podman-run.1.html)
    278 - [17] [Why you should be using Multi-Category Security for your Linux containers](https://www.redhat.com/en/blog/why-you-should-be-using-multi-category-security-your-linux-containers)
    279 - [18] [Podman top documentation](https://docs.podman.io/en/latest/markdown/podman-top.1.html)
    280 - [19] [selinux(8) - Linux manual page](https://man7.org/linux/man-pages/man8/selinux.8.html)