Files
org_roam/Career Concepts/20260527152602-microlise_xss_rom_pentest.org
Zaine a758ae1793
Some checks failed
Build Roam Site / build (push) Failing after 40s
29-05 moving
2026-05-29 08:42:36 +01:00

18 KiB
Executable File
Raw Blame History

XSS Pentesting Report Fix

Metadata

Name
Understanding Session XSS
Overview
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.

Todos

review-finding
Read pages 3334 and map finding to SaveSearchCriteriaToSession + ScheduleExecutionBoard.aspx flow
locate-source
Open TMC Web Portal repo and find session save + inline script render for date/orderID/time
remediate-encode
Apply HttpUtility.JavaScriptStringEncode to all session values in SetupControls() (~1849-1879)
remediate-validate
Add server-side validation in SaveSearchCriteriaToSession before writing SEBSessionState
remediate-retest
Retest with direct POST payload + normal UI search flow on ScheduleExecutionBoard

Understanding the Session Stored XSS Finding (Pages 3334)

Where this sits in the report

The Microlise TMC PO WA April 2026 v1.0.pdf lists 14 findings total. Pages 3334 (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.


What XSS is (general)

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).


What happened in this finding (TMC context)

Affected surface (source located)

Role Path
Page + inline JS ScheduleExecutionBoard.aspx
WebMethod + page properties ScheduleExecutionBoard.aspx.cs
Session storage SEBSessionState.cs
  • Endpoint: ASP.NET [WebMethod] SaveSearchCriteriaToSession on ScheduleExecutionBoard.aspx
  • Parameters: JSON fields date, orderID, time (also searchID, hours, quickSearch in the same flow)
  • Host (pentest): cert.microlise.com, path /PENTEST/TMCWebPortal/SEB/...

Attack flow (as tested)

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
  1. Save: User (or tester) POSTs JSON to SaveSearchCriteriaToSession. The app saves search criteria into the server-side session.
  2. Render: On the next load of ScheduleExecutionBoard.aspx, those values are written into the HTML inside a <script> block, as JavaScript string literals.
  3. Bug: Values are inserted without JavaScript string encoding. A crafted date can break out of the string and run arbitrary JS.
  4. Proof: Pentesters confirmed execution in the browser; screenshots in the PDF show the POST and page source.

Code path (matches report exactly)

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.

Why “stored”, “session”, and “self-XSS”

  • Stored: Payload survives page navigation in session state.
  • Self-XSS: Only the submitters session is affected; no normal cross-user path.
  • Severity Informational because threat model is weak vs shared stored XSS.

Client vs server validation gap

Pentesters bypassed browser validation via direct POST. No server-side validation blocked arbitrary strings.


Replicating the vulnerability (hands-on)

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.

Prerequisites

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).

What you should observe (before fix)

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
  1. WebMethod returns HTTP 200.
  2. Payload never went through DateValidation().
  3. Full page reload → JS runs (e.g. alert).
  4. View Source: payload inside double-quoted JS string, unescaped.

Self-XSS: only your session is poisoned.

Step-by-step reproduction

Step 1 — Baseline (optional)

  1. Open SEB, perform a search.
  2. DevTools → Network → SaveSearchCriteriaToSession.
  3. Note POST, 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)

  1. Full navigation reload of ScheduleExecutionBoard.aspx (F5).
  2. SetupControls() embeds session date (~lines 18541855).

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.

Comparison matrix (before vs after fixes)

Run the same Steps 24 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.

Why UI-only testing misses the bug

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.

Evidence to capture (for fix sign-off)

  1. Request (POST body with payload).
  2. Screenshot of alert (before) or no alert (after).
  3. View Source snippet around $('#txtStart').val(.
  4. Regression: legitimate date, reload, criteria restored.

Safety and scope

  • No production without approval.
  • Prefer alert(document.domain) over exfiltration demos.
  • Self-XSS: coding defect demo, not mass compromise.

How to fix it

Use two layers: output encoding + server-side validation.

Fix 1 — Output encoding (required)

File: ScheduleExecutionBoard.aspx, SetupControls() (~18471884).

Line (approx) Field Encode
18491850 SessionSearchID Yes
18541855 SessionDate Yes
18601861 SessionTime Yes
18721874 SessionOrderID Yes
18781879 QuickSearch Yes

Before:

$('#txtStart').val("<%= SessionDate %>");

After:

$('#txtStart').val("<%= HttpUtility.JavaScriptStringEncode(SessionDate ?? string.Empty) %>");

Encode if guards too, or use code-behind booleans (HasSessionDate).

Fix 2 — Server-side input validation

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 1999
[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)))$

Fix 3 — What not to do

  • Do not rely on client DateValidation() alone.
  • Do not use HtmlEncode in JS string literals.
  • Do not use innerHTML; keep .val().

Fix 4 — Verification / test plan

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.

Files to change (summary)

File Change
ScheduleExecutionBoard.aspx JavaScriptStringEncode in SetupControls()
ScheduleExecutionBoard.aspx.cs Validation in SaveSearchCriteriaToSession

References: OWASP XSS, PortSwigger Stored XSS, JavaScriptStringEncode


Mental model: severity vs correctness

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)

Next step

  1. Reproduce on cert/UAT (replicate-vuln todo).
  2. Implement fixes in D:\_dev\WebPortal (remediate-encode, remediate-validate).
  3. Replay steps and fill comparison matrix (remediate-retest todo).

Confirm execution when ready to change WebPortal code.