Pentestas / help

Azure / M365 CIS benchmark

Pentestas ships a modular CIS-aligned inspector framework that checks your Azure Entra, Exchange Online, SharePoint/OneDrive, Teams, and general M365 posture against the CIS Microsoft 365 Foundations Benchmark.

What it checks

Current inspectors (25, expanding):

Entra ID (6) — MFA enforced for admins, legacy auth blocked via Conditional Access, Security Defaults OR equivalent CA policies, user consent to apps restricted to low-risk scopes, guest-invite restricted to admins, two break-glass Global Admins.

Exchange Online (9) — SPF hard-fail, DKIM signing per domain, DMARC enforced, Safe Links, external forwarding blocked, modern auth enforced, mailbox auditing, external sender tag, transport-rule audit.

SharePoint (5) — external sharing level, default sharing link type + permission, anonymous-link lifetime, guest re-auth, legacy auth blocked.

Teams (4) — external access (federation) allowlist, guest policy, anonymous meeting join requires lobby, third-party apps restricted.

M365 general (1) — unified audit log enabled.

Run it

From the UI

Settings → Azure → Run CIS benchmark. Requires Azure credentials to be saved (service principal or paste-a-token).

From the API

POST /api/azure/cis-report?families=exchange,sharepoint,teams,entra,m365

Returns:

json
{
  "report": {
    "summary": {
      "total": 25,
      "counts": {"PASS": 14, "FAIL": 6, "SKIPPED": 5, "NOT_RUN": 0},
      "pass_rate_percent": 56.0,
      "by_family": {"exchange": {...}, "sharepoint": {...}, ...},
      "by_profile": {"L1": {...}, "L2": {...}}
    },
    "rows": [
      {
        "check_id": "entra.mfa-admins",
        "title": "Conditional Access enforces MFA for all admin roles",
        "family": "entra",
        "cis_ids": ["1.1.1"],
        "cis_profiles": ["L1"],
        "cis_license": ["E3"],
        "severity": "CRITICAL",
        "status": "FAIL",
        "evidence": "Admin MFA not enforced for: Helpdesk Administrator, User Administrator",
        "remediation": "Entra portal → Protection → Conditional Access → ...",
        "references": [
          "https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/howto-conditional-access-policy-admin-mfa"
        ]
      },
      ...
    ]
  }
}

Credentials

The benchmark uses the same credentials as the Azure pentest:

  • Service principal with Reader + Security Reader + AuditLog.Read.All.
  • OR paste-a-token — run az account get-access-token and paste the output. Works for one-hour scans.

See Azure subscription scan for setup details.

What "SKIPPED" means

Some CIS controls live only in Exchange PowerShell / Teams PowerShell (cmdlets like Get-SafeLinksPolicy, Get-TransportRule, Get-CsTeamsMeetingPolicy). Those aren't exposed via Graph, so Pentestas emits an honest SKIPPED row explaining which cmdlet verifies it. Future releases will add an Exchange PowerShell bridge via the Linux agent.

Adding custom checks

The inspector framework makes it trivial to add a check:

  1. Drop a file at backend/tools/azure/cis/<family>/ci_<id>_<name>.py.
  2. Subclass CisInspector, set class attributes (CHECK_ID, CIS_IDS, CIS_PROFILES, DEFAULT_SEVERITY, REMEDIATION, REFERENCES).
  3. Implement audit(ctx) returning findings via self.fail(...) / self.pass_record(...) / self.skipped(...).

The runner auto-discovers the file — no registration, no wiring.

See also