Files
org_roam/20260527152602-microlise_xss_rom_pentest.org
Zaine a7285933c3
All checks were successful
Build Roam Site / build (push) Successful in 31s
29-05
2026-05-29 08:41:15 +01:00

439 lines
18 KiB
Org Mode
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
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.
:PROPERTIES:
:ID: b2ac09aa-e888-48d3-8357-2292f9b2526c
:END:
#+title: XSS Pentesting Report Fix
#+filetags: :microlise:security:notes:
* 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 [[file:d:/_dev/_misc/Pentest-04-26/Microlise TMC PO WA April 2026 v1.0.pdf][Microlise TMC PO WA April 2026 v1.0.pdf]] lists *14 findings* total. Pages 3334 ([[file:d:/_dev/_misc/Pentest-04-26/33-34.pdf][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 | [[https://cwe.mitre.org/data/definitions/79.html][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.
#+begin_src mermaid :exports none
sequenceDiagram
participant Attacker
participant App as WebApplication
participant Victim as VictimBrowser
Attacker->>App: Submit malicious input
App->>App: Store or reflect input
App->>Victim: HTML/JS page containing payload
Victim->>Victim: Browser runs attacker's script
Note over Victim: Script runs with the site's origin<br/>can access cookies, DOM, APIs
#+end_src
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 | [[file:d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx][ScheduleExecutionBoard.aspx]] |
| WebMethod + page properties | [[file:d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx.cs][ScheduleExecutionBoard.aspx.cs]] |
| Session storage | [[file:d:/_dev/WebPortal/src/code/AmberWebUI/SEB/SEBSessionState.cs][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)
#+begin_src 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
#+end_src
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*
#+begin_src 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;
// ...
}
#+end_src
*2. Persist — per-user ASP.NET session*
[[file:d:/_dev/WebPortal/src/code/AmberWebUI/SEB/SEBSessionState.cs][SEBSessionState.cs]] stores values under keys =dateID=, =timeID=, =orderByID=.
*3. Load — on next full page GET*
#+begin_src csharp
// ScheduleExecutionBoard.aspx.cs lines 267-277
private void SetupControls()
{
var sebState = new SEBSessionState();
SessionOrderID = sebState.OrderBy;
SessionDate = sebState.SearchDate;
SessionTime = sebState.SearchTime;
// ...
}
#+end_src
*4. Render — vulnerable inline JavaScript (root cause)*
#+begin_src 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');
}
#+end_src
Example payload in session: ="); alert(document.domain); //=
#+begin_src javascript
$('#txtStart').val(""); alert(document.domain); //");
#+end_src
*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)
#+begin_src 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
#+end_src
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):
#+begin_src 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));
#+end_src
*curl* (replace host, path, cookies):
#+begin_src 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\":\"\"}"
#+end_src
*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: [[file:d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx][ScheduleExecutionBoard.aspx]], =SetupControls()= (~18471884).
| Line (approx) | Field | Encode |
|---------------+-----------------+--------|
| 18491850 | SessionSearchID | Yes |
| 18541855 | SessionDate | Yes |
| 18601861 | SessionTime | Yes |
| 18721874 | SessionOrderID | Yes |
| 18781879 | QuickSearch | Yes |
*Before:*
#+begin_src javascript
$('#txtStart').val("<%= SessionDate %>");
#+end_src
*After:*
#+begin_src javascript
$('#txtStart').val("<%= HttpUtility.JavaScriptStringEncode(SessionDate ?? string.Empty) %>");
#+end_src
Encode =if= guards too, or use code-behind booleans (=HasSessionDate=).
*** Fix 2 — Server-side input validation
File: [[file:d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx.cs][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 |
#+begin_src csharp
[WebMethod]
public static void SaveSearchCriteriaToSession(...)
{
if (!IsValidSebDate(date) || !IsValidSebTime(time)
|| !IsValidQueryComponentId(orderID) || !IsValidQueryComponentId(searchID))
{
return;
}
var sebState = new SEBSessionState();
// ...
}
#+end_src
*Date regex:*
#+begin_src
^[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)))$
#+end_src
*** 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:*
#+begin_src json
{
"searchID": "0.X",
"orderID": "0.X",
"date": "\"); alert(1);//",
"time": "00:00",
"hours": 24,
"displayPriorityJourneys": false,
"quickSearch": ""
}
#+end_src
*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 |
|--------------------------------+-----------------------------------------------|
| [[file:d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx][ScheduleExecutionBoard.aspx]] | =JavaScriptStringEncode= in =SetupControls()= |
| [[file:d:/_dev/WebPortal/src/code/AmberWebUI/SEB/ScheduleExecutionBoard.aspx.cs][ScheduleExecutionBoard.aspx.cs]] | Validation in =SaveSearchCriteriaToSession= |
*References:* [[https://owasp.org/www-community/attacks/xss/][OWASP XSS]], [[https://portswigger.net/web-security/cross-site-scripting/stored][PortSwigger Stored XSS]], [[https://learn.microsoft.com/en-us/dotnet/api/system.web.httputility.javascriptstringencode][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.