Monitoring Power Pages traffic + detecting attacks + data-exfiltration coverage
Praxisis D365 Wiki · Public reference · Client-specific details redacted
Source: Claude.ai chat (original: monitoring-website-traffic-and-detecting-attacks-on-power-pa__d726b3b6.md)
Added: 2026-10-02
Summary
- WAF/traffic logs only see HTTP shape. Data exfiltration through APIs looks like perfectly well-formed authenticated traffic — need a different set of signals.
- Four-step monitoring design: (1) enumerate the channels before monitoring anything, (2) baseline what's actually exposed, (3) stand up three monitoring feeds (WAF + Dataverse activity + Entra sign-ins), (4) control not just observe.
Key takeaways
Step 1 — Channels (more egress paths than people expect):
- Portals Web API
/_api/<entityset>→ full CRUD. Follows table + column permissions via web role. Needs CSRF token. - OData feeds
/_odata→ read-only, list-based. Deprecated — Microsoft directs you to the Web API. - Lists / forms / Liquid → page-rendered, no separate endpoint to watch.
- Web files + annotations
/file.axd, notes → often overlooked. - Direct Dataverse Web API
*.crm3.dynamics.com/api/data/v9.2→ bypasses the portal entirely (app registrations, S2S). No amount of WAF tuning sees this. - Flows / connectors / Synapse Link → Dataverse-side egress, also outside Power Pages observability.
Step 2 — Baseline (two drift-detection checks):
- Anonymous OData exposure: browse to
{PortalUrl}/_odatain InPrivate without auth. If a feed renders instead of returning 403, you have an anonymous data leak (the classic UpGuard/Power Apps Portals class). Design studio now blocks saving OData-enabled lists with permissions disabled — but the guard isn't API-enforced; a direct PATCH toadx_entitylists({list-id}) via the Dataverse Web API can override it. Checkadx_entitylist` records directly, don't trust the UI. - Web API surface:
GET adx_sitesettings?$filter=contains(adx_name,'Webapi'). Flag anything wherefieldsis*rather than an explicit list. Cross-reference against table permissions on the Anonymous Users web role. Enhanced data model sites usemspp_sitesettinginstead ofadx_sitesettings.
Step 3 — Three monitoring feeds:
- A. WAF logs for portal API traffic — the only per-request view of
/_api/+/_odatacalls with client IP. Useful alert shapes: high-volume anonymous/_api/GETs from one IP (enumeration); sustained 403s in sequence (probing table permissions); any/_odatatraffic (if deprecated); large$topwith no$filter(bulk pull vs. app usage). - B. Dataverse activity logging — WAF tells you a request happened; Dataverse logs tell you which records left. Enable auditing at env level + Single-record (Retrieve) + Multi-record (RetrieveMultiple) auditing on PII tables. Gotchas: option appears only once minimum Office licensing is met · Purview 3-KB limit per audit record means one Dataverse record can split into multiple Office records — use
CorrelationIdto reassemble. - C. Entra sign-in logs for the non-portal path — filter
SigninLogs(and the service-principal sign-in category) for the Dataverse resource. Catches app-registration / leftover S2S exfiltration. Alert on any new service principal authenticating to Dataverse, and on non-interactive sign-in volume outside normal integration windows. Pairs with the offboarding audit — a departed consultant's app registration is the classic finding.
Step 4 — Controls (not just observation):
- Rate-limit the API paths: WAF custom rule matching request URI containing
/_api/with a rate-limit action. Cheapest single control. - Column permissions: narrow the
fieldssite setting to columns the front end actually reads. Reduces blast radius and makes anomalous$selectvalues visible. - CORS: audit environment-level + site-level allowed origins. Over-broad allowed-origins = third-party page can invoke your Web API inside an authenticated user's browser session.
- Retire OData feeds entirely — deprecated anyway; disabling removes a whole surface.
- Site Checker in release pipeline — run after every prod import and diff output. Catches unsecured-list regression before an attacker does.
Learnings / how to apply
- OData feeds are read-only; the Web API allows full CRUD. The alerting priority is asymmetric: anonymous write attempts against
/_api/deserve a page; anonymous reads deserve a daily digest. - None of this is a SIEM. If a client needs real correlation across WAF + identity + Dataverse read events with >90-day retention, the answer is shipping all three feeds into Sentinel or an existing SIEM. Scope that as a separate conversation before building bespoke Power Automate glue.
- Package this as a reusable Praxisis "Power Pages Security Monitoring" document template (navy/blue, Arial, banded tables,
<<placeholder>>tokens on cover) — reusable across clients. Sections: 5 monitoring layers + alerting matrix + hardening + implementation checklist with Owner/Status columns. - Thresholds in any alerting matrix are starting points that need tuning against a week or two of baseline traffic before notifications turn on.
Need this applied to your own tenant?
Praxisis Sustain Self-Serve builds a private AI Wiki trained on your documents and tickets, with free Tier-2 support when the AI cannot answer. Book a 30-min overview.