A template renderer combines application-controlled source with values such as a visitor's name. A template is text containing placeholders and sometimes expressions. An expression is an operation the template language evaluates. In this model, {{ name }} inserts a variable and {{ 2 + 3 }} evaluates trusted arithmetic.
Server-side template injection, or SSTI, occurs when submitted text becomes template source instead of staying a variable value. The source role matters: a caller who can supply expressions may gain capabilities the application intended to reserve for its own templates.
Before you begin
- Recommended first
- Notes: scope, command, result, time, HTTP methods, headers, status codes, cookies, How a web app is put together, Same-origin policy, Insecure deserialization
- Estimated time
- 18 min reading, 12 min activity. Setup and optional practice may take longer.
- Where you work
- Read the original fictional source, object tables, token claims and policy models in any current desktop or mobile browser. No loader, template engine, provider registration, service or network request is required.
- Without a lab
- Follow the printed data through each stated rule on paper. Record the decision, its supporting fact and a limit, then compare with the guided rubric. Model reasoning does not establish a live tool result. Reading works without JavaScript. Checking requires JavaScript and a secure context; saving also requires usable storage.
After this lesson you can:
- Separate a template's source from a variable.
- Read the trusted sum as the model's arithmetic.
- Classify Card D and Card Q by who supplies the source.
01Example
Worked trusted source computes 2 + 3. The activity cards are D and Q.
Trusted source: Sum: {{ 2 + 3 }}
Model result: Sum: 5
User text kept as data stays the characters 2 + 3.
Not an observed engine.02Source and data
Card D fixes the source as Hello, {{ name }} and supplies Lane as the name value. The caller controls the value, not the source. Card Q instead compiles the caller's whole text as source. That is the trust boundary: data receives authority to define template behavior.
Keep application templates fixed and pass submitted values through the supported data interface. Output escaping remains necessary for the output context, but escaping alone cannot fix a design that compiles untrusted text as template source. Template features and syntax differ between engines; the simple arithmetic results below are a model, not a recorded engine run.
Keep a caller's text in a data variable, and mark the step that compiles that text as the template.Model. A teaching model drawn by hand. It shows the idea, not real tool output.
Read the full explanation below.
| Field | Expression in trusted source | Caller text as data |
|---|---|---|
| Trusted template source | Sum: {{ 2 + 3 }} | Trusted source remains separate |
| Caller-data role | Not caller data | 2 + 3 |
| Predicted result | Sum: 5 | The literal characters 2 + 3 |
The expression belongs to trusted template source only in the first card.
The expression belongs to trusted template source only in the first card.
Follow the explanation
- The expression belongs to trusted template source only in the first card.
- The model predicts 5 for trusted source and literal caller data. No engine was observed.
Read every state and value
1. The expression belongs to trusted template source only in the first card.
- Selected Comparison
- roles
- Highlighted Field
- Trusted template source · Caller-data role
2. The model predicts 5 for trusted source and literal caller data. No engine was observed.
- Selected Comparison
- results
- Highlighted Field
- Predicted result
03Classify the cards
Read the evidence before comparing with the activity rubric. Do not install an engine.
Template model. No template engine was installed. Results are the model's arithmetic.
Trusted source, no user text in the source:
source: Sum: {{ 2 + 3 }}
model result: Sum: 5
User text kept as data:
source: Hello, {{ name }}
name: 2 + 3
model result: Hello, 2 + 3
User text compiled as the source:
source: the caller's text
The caller's text becomes the template.
That is the boundary this lesson marks.
Activity cards.
Card D
The fixed source is Hello, {{ name }}
The caller supplies the name Lane
The caller text is variable data
Card Q
The caller supplies the whole template text
The program compiles that text as the source
Card D keeps the caller in the data role.
Card Q gives the caller the source role.
- Write who supplies the source on Card D and on Card Q.
- Write the data role Card D gives the caller.
- Check: Card D keeps the caller in the data role. Card Q gives the caller the source role. The worked sum is the trusted source.
- Stop. Do not compile caller text as a template.
04Optional challenge
Explain escaping versus keeping the caller in the data role. The rubric is: escaping changes how a value is written at one insertion point, and the data role stops the caller from becoming the source. About ten minutes.
05Fresh cases for the checks
Use these fictional cases for the checks below. They have different inputs from the worked examples. Read them here or make notes; no installation or external service is needed.
Template case A: fresh source/data cardsOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.
P: trusted fixed source "Total: {{ 4 + 6 }}".
Q: trusted fixed source "Hello, {{ name }}"; caller variable name is the literal text "4 + 6".
R: the caller supplies the entire string which the application compiles as template source.
Model rule: arithmetic in source is evaluated; variable text is inserted as text, not recompiled.
No template engine ran.
06Explain your evidence
Optional written self-check: explain one decision using the supplied case. No tool run is required.
Check your explanation: identify the compiled source and variable independently; trace source arithmetic versus literal data; state the missing engine observation; explain why output escaping alone does not repair caller-controlled source.
07Common mistakes
If you mark Card D as the source problem, you switched the cards. Card Q compiles the caller.
If you treat Sum: 5 as observed output, it is the model's arithmetic.
If you add a server command to the caller's text, you left the card.
References
- PortSwigger: Server-side template injection
Further explanation of this mechanism and its defenses. The original printed model supplies the required evidence.