Initial commit
This commit is contained in:
363
README.md
Normal file
363
README.md
Normal file
@@ -0,0 +1,363 @@
|
||||
# ApplePY
|
||||
|
||||
A macOS security review and attack-surface scanner. It can be run on a Mac to produce a Markdown report** and a multi-worksheet XLSX spreadsheet covering CIS benchmark controls, privilege escalation paths, MDM posture, NIST mSCP compliance, living-off-the-land catalogues, and much more. Optionally exports a BloodHound CE–compatible graph JSON file for visual attack-path analysis (courtesy of JAMF Hound).
|
||||
|
||||
### Disclaimer
|
||||
|
||||
For **authorised** security assessments only. Results are indicative; confirm material findings in your environment before acting on them. Third-party sources and licences are listed in [CREDITS.md](CREDITS.md). All rights to original authors.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# Unprivileged scan (no root required), outputs ./applepy-out/report.md and findings.xlsx
|
||||
./dist/applepy/applepy
|
||||
|
||||
# Full scan (unprivileged + privileged checks)
|
||||
sudo ./dist/applepy/applepy
|
||||
|
||||
# Choose a custom output folder
|
||||
sudo ./dist/applepy/applepy --output-dir ~/Desktop/assessment
|
||||
```
|
||||
|
||||
See [Getting the binary](#getting-the-binary) below if you do not yet have `dist/applepy/applepy`.
|
||||
|
||||
---
|
||||
|
||||
## What it produces
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `report.md` | Narrative summary grouped into themes (e.g. Hardening, MDM, Compliance). Each finding shows severity, risk, impact, and remediation steps. |
|
||||
| `findings.xlsx` | Multi-worksheet spreadsheet. The **Contents** tab links to every category sheet. Each sheet is colour-coded by severity (critical → informational) and includes a MITRE ATT&CK mapping worksheet. |
|
||||
| `graph_findings.json` | *(Optional — `--export-graph-json`)* JamfHound-compatible BloodHound CE OpenGraph JSON for visual attack-path analysis. |
|
||||
|
||||
---
|
||||
|
||||
## Privilege model
|
||||
|
||||
ApplePY splits its work into two phases so that you do not need root for a partial scan:
|
||||
|
||||
| How you run | What runs |
|
||||
|-------------|-----------|
|
||||
| `applepy` (no `sudo`) | **Unprivileged phase only.** Checks that do not require root. A warning is printed reminding you that the privileged phase was skipped. |
|
||||
| `sudo applepy` | **Both phases.** Unprivileged checks first, then privileged checks (sudoers, SUID/SGID binaries, TCC permissions, world-writable LaunchDaemon binaries, mSCP compliance generation, and so on). |
|
||||
|
||||
Optional overrides:
|
||||
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `--unprivileged-only` | Run the unprivileged phase only, even when invoked with `sudo`. |
|
||||
| `--privileged-only` | Run the privileged phase only (combine with `sudo`; without root, no checks run). |
|
||||
|
||||
---
|
||||
|
||||
## Getting the binary
|
||||
|
||||
### Option A — Build the PyInstaller bundle (recommended)
|
||||
|
||||
The **primary deployment method** is a self-contained one-folder bundle built with PyInstaller. It can then transferred to the target host as a single self extracting archive. The bundle embeds the Python runtime, dependencies and optionally the NIST mSCP compliance corpus and Lynis.
|
||||
|
||||
**Requirements (build machine only):** a macOS workstation, Python ≥ 3.11 and git.
|
||||
|
||||
```bash
|
||||
cd /path/to/security-review
|
||||
chmod +x scripts/build_bundle.sh scripts/vendor_compliance_assets.sh
|
||||
./scripts/build_bundle.sh
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Shallow-clone **NIST mSCP** and **Lynis** into `applepy/data/`.
|
||||
2. Install ApplePY with the `bundle` extra (PyInstaller, PyYAML, xlwt).
|
||||
3. Run PyInstaller using `applepy.spec`.
|
||||
|
||||
Output:
|
||||
|
||||
- **`dist/applepy/applepy`** — the binary you run on the target.
|
||||
- **`dist/applepy/_internal/`** — bundled libraries, data, and Python runtime (keep alongside the binary).
|
||||
|
||||
> **Do not run `build/applepy/applepy`.** The `build/` tree is PyInstaller's working directory — it is missing the `_internal/` folder and will fail. Always use `dist/applepy/`.
|
||||
|
||||
**Rebuilding without re-cloning** (e.g. offline):
|
||||
|
||||
```bash
|
||||
SKIP_VENDOR_COMPLIANCE=1 ./scripts/build_bundle.sh
|
||||
```
|
||||
|
||||
**If the build fails with a `PermissionError` removing `dist/applepy`:** a previous `sudo applepy` run left root-owned files in `_internal/.../macos_security/build/`. Clear them, then rebuild:
|
||||
|
||||
```bash
|
||||
sudo rm -rf dist/applepy
|
||||
./scripts/build_bundle.sh
|
||||
```
|
||||
|
||||
### Option B — Install from source (development / library)
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv && source .venv/bin/activate
|
||||
pip install -e ".[dev]"
|
||||
applepy --help
|
||||
```
|
||||
|
||||
To include compliance reporting in this workflow, run `applepy --bootstrap-compliance` (see [Compliance](#compliance-mscp-and-lynis)).
|
||||
|
||||
---
|
||||
|
||||
## Running on the target Mac
|
||||
|
||||
### Clear the quarantine flag
|
||||
|
||||
When you transfer `dist/applepy/` by browser download, email, or certain archive tools, macOS applies a quarantine attribute. Clear it before running:
|
||||
|
||||
```bash
|
||||
xattr -dr com.apple.quarantine /path/to/dist/applepy
|
||||
```
|
||||
|
||||
Verify nothing remains:
|
||||
|
||||
```bash
|
||||
xattr -lr /path/to/dist/applepy
|
||||
```
|
||||
|
||||
### Run the scan
|
||||
|
||||
```bash
|
||||
# Show all available options
|
||||
/path/to/dist/applepy/applepy --help
|
||||
|
||||
# Unprivileged scan (safe to run without sudo)
|
||||
/path/to/dist/applepy/applepy --output-dir ./out-user
|
||||
|
||||
# Full scan (both phases)
|
||||
sudo /path/to/dist/applepy/applepy --output-dir ./out-full
|
||||
|
||||
# Full scan + graph JSON for BloodHound CE
|
||||
sudo /path/to/dist/applepy/applepy --output-dir ./out-full --export-graph-json
|
||||
```
|
||||
|
||||
### Unsigned binary on a managed Mac
|
||||
|
||||
If macOS shows a security warning on first launch, **Control-click → Open** in Finder, or go to **System Settings → Privacy & Security** and click **Open Anyway**. This is expected for programs that have not been signed with an Apple Developer ID.
|
||||
|
||||
---
|
||||
|
||||
## What it checks
|
||||
|
||||
ApplePY runs registered checks in the unprivileged and privileged phases. Below is a capability overview grouped by area.
|
||||
|
||||
### Core platform
|
||||
Host summary, **SIP** status (`csrutil`), **Gatekeeper**, **Application Firewall**, system extensions listing, LaunchAgent/LaunchDaemon inventories, common **interpreters** on `PATH`.
|
||||
|
||||
### CIS benchmark controls
|
||||
Over 80 CIS macOS Benchmark Level 1 and Level 2 checks covering: FileVault, screen saver lock, password policy, guest account, remote login, Bluetooth, mDNS, IPv6, Safari settings, Handoff, iCloud services, Siri, AirDrop, Time Machine, printer sharing, wake settings, and many more. Each check emits PASS, FAIL, or WARN with evidence.
|
||||
|
||||
### Privilege escalation
|
||||
Read-only detection of common privilege escalation paths:
|
||||
|
||||
- **Sudoers NOPASSWD** — `/etc/sudoers` and `/etc/sudoers.d/` entries that allow password-free `sudo`.
|
||||
- **Unexpected SUID/SGID binaries** — files with setuid or setgid bits outside the expected macOS baseline.
|
||||
- **Writable LaunchDaemon binaries** — LaunchDaemon plists that reference binaries writable by non-root users.
|
||||
- **Writable PATH directories** — directories on `PATH` (from `/etc/paths`) that are writable by non-root users.
|
||||
- **High-risk TCC permissions** — applications granted full-disk access, camera, microphone, and similar sensitive TCC services.
|
||||
|
||||
### Dylib hijacking
|
||||
RPATH ordering and writable-dylib-directory checks for installed applications.
|
||||
|
||||
### Filesystem posture
|
||||
World-writable files under LaunchAgent and LaunchDaemon directories (caps configurable via environment variables).
|
||||
|
||||
### Living-off-the-land catalogues
|
||||
**LOOBins** (live with cache; fallback data), **GTFOBins** (live API with cache), **lolapps**, **lottunnels**, cloud CLI presence on `PATH`, credential path hints (presence only — no secret reads).
|
||||
|
||||
### Compliance
|
||||
**CIS worksheet** pointer, FileVault, pwpolicy, boot/uptime, Time Machine destinations, admin group, default umask, **NIST mSCP** status and (privileged) baseline generation + compliance script execution with per-rule worksheet output, **Lynis** quick audit when available.
|
||||
|
||||
### Common paths
|
||||
Mounts, guest login, remote login / ARD plists, MDM enrolment hints, browser extension directories, local users (`dscl`), SSH public key filenames, cron/periodic/at surface, Automator workflow directories, user Keychains filenames.
|
||||
|
||||
### Extended surface
|
||||
Routing table, `/etc/hosts`, DNS settings, per-service HTTP proxies, AirDrop/sharing defaults, shell startup file metadata, kernel extension list, USB/Bluetooth `system_profiler` snapshots, `last` logins, Wi-Fi preferences, NFS exports, `pmset`, CUPS/printing surface, configuration profiles, Spotlight, `launchctl print-disabled`, `sysctl kern` snapshot.
|
||||
|
||||
### Network listeners
|
||||
TCP LISTEN and UDP sockets via `lsof`, excluding loopback-only and OS ephemeral port ranges; correlated with process names.
|
||||
|
||||
### Application surface
|
||||
Entitlements sample (`codesign`), SSH directory, TCC / login items / background task store paths, Electron app ASAR and writable parent-directory checks.
|
||||
|
||||
### MDM posture (read-only)
|
||||
- **Jamf** — local plists, receipts, LaunchAgents, helpers, extension attribute cache, listener hints, configuration profile stores, filesystem caps.
|
||||
- **Kandji** — Application Support tree, LaunchAgents, receipts, preferences, helpers, listeners, configuration profile store.
|
||||
|
||||
### AI tools and IDEs
|
||||
AI CLI tools on `PATH` (e.g. Claude Code, Cursor, GitHub Copilot CLI), AI desktop apps, local model storage, and IDE surface.
|
||||
|
||||
### Interpreters and package managers
|
||||
Python, Node.js, Ruby, Go, Rust, and other runtimes found on `PATH` or in standard locations, plus Homebrew, pip, npm, and conda distributions.
|
||||
|
||||
---
|
||||
|
||||
## CLI reference
|
||||
|
||||
```text
|
||||
applepy [options] # from pip install / editable install
|
||||
python -m applepy [options] # from source tree (without install)
|
||||
./dist/applepy/applepy [options] # PyInstaller bundle
|
||||
```
|
||||
|
||||
| Short | Long | Description |
|
||||
|-------|------|-------------|
|
||||
| | `--version` | Print version and exit. |
|
||||
| `-h` | `--help` | Show help and exit. |
|
||||
| `-o PATH` | `--output-dir PATH` | Directory for `report.md`, `findings.xlsx`, and optional JSON (default: `./applepy-out`). |
|
||||
| `-v` | `--verbose` | Increase log verbosity. Use `-vv` for debug output. |
|
||||
| `-q` | `--quiet` | Suppress all output except errors on stderr (disables progress reporting). |
|
||||
| | `--unprivileged-only` | Run the unprivileged phase only, even with `sudo`. |
|
||||
| | `--privileged-only` | Run the privileged phase only (use with `sudo`). |
|
||||
| | `--dry-run` | Run all checks but do not write any output files. |
|
||||
| | `--export-graph-json` | Write `graph_findings.json` in JamfHound-compatible BloodHound CE OpenGraph format. Requires the SpecterOps Jamf extension in BloodHound CE. |
|
||||
| | `--sequential` | Disable parallel check execution within each phase (useful for debugging). |
|
||||
| | `--bootstrap-compliance` | Clone NIST mSCP and Lynis into `./vendor`, print hints, then exit (requires `git` and internet access). |
|
||||
| `HEX` | `--heading-color HEX` | XLSX heading background colour as a 6-digit hex code (default: `871727`). |
|
||||
|
||||
### Progress output
|
||||
|
||||
By default, ApplePY prints a running progress display to stderr:
|
||||
|
||||
- **Each check** shows a progress bar, percentage, check name, and either `running` or the elapsed time and finding count once complete.
|
||||
- **At the end of each phase**, a severity breakdown is shown (critical / high / medium / low / informational counts).
|
||||
- **`-q` / `--quiet`** suppresses all of this.
|
||||
- **`NO_COLOR=1`** disables ANSI colour codes. **`FORCE_COLOR=1`** forces colour even when stderr is not a terminal.
|
||||
|
||||
---
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `APPLEPY_MACOS_SECURITY_ROOT` | Override the path to the `macos_security` clone (takes precedence over the bundled copy). |
|
||||
| `APPLEPY_MSCP_BASELINE` | Baseline YAML stem or filename under `baselines/` (e.g. `cis_level1`). |
|
||||
| `APPLEPY_MSCP_SKIP_GENERATE=1` | Skip `generate_guidance.py`; only run an existing `*_compliance.sh --check`. |
|
||||
| `APPLEPY_MSCP_COMPLIANCE_LOG_MAX` | Maximum characters retained per stream in compliance evidence (default: 524288). |
|
||||
| `APPLEPY_MSCP_FORCE_SUBPROCESS=1` | *(Frozen bundle only)* Use a system `python3` instead of re-invoking the bundle for `generate_guidance.py`. |
|
||||
| `APPLEPY_PYTHON` | Python 3 interpreter to use for `generate_guidance.py` (when not frozen, or with `APPLEPY_MSCP_FORCE_SUBPROCESS=1`). |
|
||||
| `APPLEPY_DECK_EXPORT_TXT` | Path to an optional SpecterOps-style deck reference text file. |
|
||||
| `APPLEPY_KANDJI_MAX_FILES` | Cap for Kandji Application Support file listing. |
|
||||
| `APPLEPY_FS_MAX_SCAN` / `APPLEPY_FS_MAX_HITS` | Caps for world-writable filesystem walks. |
|
||||
| `APPLEPY_CATALOG_INCLUDE_NOISE=1` | Emit per-binary LOOBins rows for common UI binaries (Dock, Finder, etc.) that are normally summarised only. |
|
||||
| `APPLEPY_GTFO_DETAILED=1` | One finding per on-host GTFOBins match (default: a single summary row). |
|
||||
| `APPLEPY_GTFO_LIST_ABSENT=1` | Add a finding listing every GTFOBins name not found on the host (large; off by default). |
|
||||
| `APPLEPY_GTFO_ROLLUP_MAX_LINES` | Maximum lines in the default GTFOBins summary row (default: 120). |
|
||||
| `SKIP_VENDOR_COMPLIANCE=1` | Skip compliance cloning in `build_bundle.sh` (use when `applepy/data/` is already populated). |
|
||||
| `NO_COLOR` | Disable ANSI colours in progress output (see [no-color.org](https://no-color.org/)). |
|
||||
| `FORCE_COLOR` | Force ANSI colours even when stderr is not a TTY. |
|
||||
|
||||
---
|
||||
|
||||
## Compliance — NIST mSCP and Lynis
|
||||
|
||||
### NIST mSCP (macOS Security Compliance Project)
|
||||
|
||||
The NIST mSCP corpus lives at [usnistgov/macos_security](https://github.com/usnistgov/macos_security). ApplePY does **not** commit it; instead it resolves the corpus in this order:
|
||||
|
||||
1. `APPLEPY_MACOS_SECURITY_ROOT` (environment variable).
|
||||
2. Bundled `applepy/data/macos_security` (populated by `scripts/vendor_compliance_assets.sh` at build time).
|
||||
3. `./vendor/macos_security`, `./macos_security`, then common locations under the user's home directory.
|
||||
|
||||
When found, ApplePY runs `generate_guidance.py` against the selected baseline YAML (auto-selected, or set via `APPLEPY_MSCP_BASELINE`), then executes the generated `*_compliance.sh --check` script and parses the resulting audit plist into findings. Temporary artefacts (`build/` and the audit plist) are cleaned up after each scan.
|
||||
|
||||
### Lynis
|
||||
|
||||
Lynis is used from `PATH` if available, then from the bundled `applepy/data/lynis/lynis`, then from `./vendor/lynis/lynis`. Lynis is GPL-licensed — see [CREDITS.md](CREDITS.md).
|
||||
|
||||
### Bootstrap workflow (source install without bundled data)
|
||||
|
||||
```bash
|
||||
# Clone mSCP and Lynis into ./vendor, print export hints, then exit
|
||||
applepy --bootstrap-compliance
|
||||
|
||||
# Set paths for subsequent scans
|
||||
export APPLEPY_MACOS_SECURITY_ROOT="$PWD/vendor/macos_security"
|
||||
export PATH="$PWD/vendor/lynis:$PATH"
|
||||
|
||||
# Install mSCP Python dependencies
|
||||
pip install pyyaml xlwt
|
||||
|
||||
# Run the full scan
|
||||
sudo applepy --output-dir ./out
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Graph JSON (BloodHound CE)
|
||||
|
||||
With `--export-graph-json`, ApplePY writes `graph_findings.json` to the output directory. This file is in the **JamfHound-compatible BloodHound CE OpenGraph format** (v1.1.2), suitable for import into BloodHound CE with the SpecterOps Jamf extension installed.
|
||||
|
||||
To ingest: open BloodHound CE → **File Ingest** → upload `graph_findings.json`.
|
||||
|
||||
See [JamfHound on GitHub](https://github.com/SpecterOps/JamfHound) for extension installation instructions.
|
||||
|
||||
---
|
||||
|
||||
## Output files in detail
|
||||
|
||||
### `report.md`
|
||||
|
||||
A narrative summary organised into **overarching themes** (Hardening, Compliance, MDM, Catalogues, etc.). Each theme section lists findings from critical down to informational. Every finding includes:
|
||||
|
||||
- **Title** and **severity** (CRITICAL / HIGH / MEDIUM / LOW / INFORMATIONAL / WARN)
|
||||
- **Description** of what was found
|
||||
- **Risk** — what an attacker could do with this
|
||||
- **Impact** — what remediation affects
|
||||
- **Remediation** steps
|
||||
- **MITRE ATT&CK technique IDs** where applicable
|
||||
- **Evidence** — raw command output or parsed data supporting the finding
|
||||
|
||||
### `findings.xlsx`
|
||||
|
||||
A multi-worksheet spreadsheet:
|
||||
|
||||
- **Contents** — index tab with hyperlinks to each category sheet and a finding count.
|
||||
- **Findings list** — all non-informational findings sorted by severity, then ID, with a link to the relevant category sheet.
|
||||
- **Category sheets** — one sheet per check category, all findings including informational.
|
||||
- **MITRE** — all findings with MITRE technique IDs, for import into threat-modelling tools.
|
||||
- **CIS index** — CIS benchmark section cross-reference.
|
||||
|
||||
Cells are colour-coded by severity (red for critical through grey for informational). Empty optional fields show an em dash (—) rather than a blank cell.
|
||||
|
||||
---
|
||||
|
||||
## Developer reference
|
||||
|
||||
### Running the test suite
|
||||
|
||||
```bash
|
||||
pip install -e ".[dev]"
|
||||
./scripts/verify.sh # Ruff, pytest, ty (if installed), Semgrep (if installed)
|
||||
```
|
||||
|
||||
Or individually:
|
||||
|
||||
```bash
|
||||
ruff check applepy/
|
||||
pytest
|
||||
```
|
||||
|
||||
### Portable `PYTHONPATH` layout (no venv, no PyInstaller)
|
||||
|
||||
```bash
|
||||
mkdir -p vendor && pip install -t vendor 'openpyxl>=3.1'
|
||||
PYTHONPATH=vendor:. python -m applepy --output-dir ./out
|
||||
```
|
||||
|
||||
Note: PyObjC is required on macOS for the full check set and is not available on Linux or Windows.
|
||||
|
||||
### PyObjC dependency
|
||||
|
||||
ApplePY depends on `pyobjc-core` and `pyobjc-framework-Cocoa` on macOS for native platform introspection. These packages are macOS-only; `pip install` on Linux or Windows simply skips them. Run the full scan on a Mac.
|
||||
|
||||
---
|
||||
|
||||
## Licence
|
||||
|
||||
See [CREDITS.md](CREDITS.md) for third-party references and licences.
|
||||
Reference in New Issue
Block a user