322 lines
9.7 KiB
Go
322 lines
9.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/chromedp/cdproto/network"
|
|
"github.com/chromedp/cdproto/page"
|
|
"github.com/chromedp/chromedp"
|
|
)
|
|
|
|
func main() {
|
|
roomURL := flag.String("room", "https://app.kosmi.io/room/@hyperspaceout", "Kosmi room URL")
|
|
output := flag.String("output", "auth-data.json", "Output file for captured data")
|
|
flag.Parse()
|
|
|
|
fmt.Printf("Capturing authentication data from: %s\n", *roomURL)
|
|
fmt.Printf("Output will be saved to: %s\n\n", *output)
|
|
|
|
// Storage for captured data
|
|
authData := &AuthData{
|
|
RoomURL: *roomURL,
|
|
CaptureTime: time.Now(),
|
|
Cookies: []Cookie{},
|
|
RequestHeaders: map[string]interface{}{},
|
|
ResponseHeaders: map[string]interface{}{},
|
|
WebSocketFrames: []WebSocketFrame{},
|
|
NetworkRequests: []NetworkRequest{},
|
|
}
|
|
|
|
// Create Chrome context with network logging
|
|
opts := append(chromedp.DefaultExecAllocatorOptions[:],
|
|
chromedp.Flag("headless", true),
|
|
chromedp.Flag("disable-gpu", false),
|
|
chromedp.Flag("no-sandbox", true),
|
|
chromedp.Flag("disable-dev-shm-usage", true),
|
|
chromedp.Flag("disable-blink-features", "AutomationControlled"),
|
|
chromedp.Flag("disable-infobars", true),
|
|
chromedp.Flag("window-size", "1920,1080"),
|
|
chromedp.UserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"),
|
|
)
|
|
|
|
allocCtx, allocCancel := chromedp.NewExecAllocator(context.Background(), opts...)
|
|
defer allocCancel()
|
|
|
|
ctx, cancel := chromedp.NewContext(allocCtx)
|
|
defer cancel()
|
|
|
|
// Enable network tracking
|
|
chromedp.ListenTarget(ctx, func(ev interface{}) {
|
|
switch ev := ev.(type) {
|
|
case *network.EventRequestWillBeSent:
|
|
if containsKosmiDomain(ev.Request.URL) {
|
|
fmt.Printf("→ REQUEST: %s %s\n", ev.Request.Method, ev.Request.URL)
|
|
authData.NetworkRequests = append(authData.NetworkRequests, NetworkRequest{
|
|
URL: ev.Request.URL,
|
|
Method: ev.Request.Method,
|
|
Headers: ev.Request.Headers,
|
|
Time: time.Now(),
|
|
})
|
|
}
|
|
|
|
case *network.EventResponseReceived:
|
|
if containsKosmiDomain(ev.Response.URL) {
|
|
fmt.Printf("← RESPONSE: %d %s\n", ev.Response.Status, ev.Response.URL)
|
|
if ev.Response.Status >= 200 && ev.Response.Status < 300 {
|
|
authData.ResponseHeaders[ev.Response.URL] = ev.Response.Headers
|
|
}
|
|
}
|
|
|
|
case *network.EventWebSocketCreated:
|
|
fmt.Printf("🔌 WebSocket Created: %s\n", ev.URL)
|
|
authData.WebSocketURL = ev.URL
|
|
authData.WebSocketRequestID = ev.RequestID.String()
|
|
|
|
case *network.EventWebSocketFrameSent:
|
|
data := string(ev.Response.PayloadData)
|
|
fmt.Printf("📤 WS SEND: %s\n", truncate(data, 100))
|
|
authData.WebSocketFrames = append(authData.WebSocketFrames, WebSocketFrame{
|
|
Direction: "sent",
|
|
Data: data,
|
|
Time: time.Now(),
|
|
})
|
|
|
|
case *network.EventWebSocketFrameReceived:
|
|
data := string(ev.Response.PayloadData)
|
|
fmt.Printf("📥 WS RECV: %s\n", truncate(data, 100))
|
|
authData.WebSocketFrames = append(authData.WebSocketFrames, WebSocketFrame{
|
|
Direction: "received",
|
|
Data: data,
|
|
Time: time.Now(),
|
|
})
|
|
}
|
|
})
|
|
|
|
// Inject WebSocket hook script
|
|
hookScript := getWebSocketHookScript()
|
|
|
|
// Run the capture
|
|
err := chromedp.Run(ctx,
|
|
network.Enable(),
|
|
chromedp.ActionFunc(func(ctx context.Context) error {
|
|
_, err := page.AddScriptToEvaluateOnNewDocument(hookScript).Do(ctx)
|
|
return err
|
|
}),
|
|
chromedp.Navigate(*roomURL),
|
|
chromedp.WaitReady("body"),
|
|
)
|
|
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error navigating: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("\nWaiting for page to fully load and WebSocket to connect...")
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// Capture all cookies
|
|
fmt.Println("\n📋 Capturing cookies...")
|
|
var cookiesData []map[string]interface{}
|
|
script := `
|
|
(function() {
|
|
return document.cookie.split(';').map(c => {
|
|
const parts = c.trim().split('=');
|
|
return {
|
|
name: parts[0],
|
|
value: parts.slice(1).join('='),
|
|
domain: window.location.hostname,
|
|
path: '/'
|
|
};
|
|
}).filter(c => c.name && c.value);
|
|
})();
|
|
`
|
|
if err := chromedp.Run(ctx, chromedp.Evaluate(script, &cookiesData)); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error capturing cookies: %v\n", err)
|
|
} else {
|
|
for _, c := range cookiesData {
|
|
if name, ok := c["name"].(string); ok {
|
|
if value, ok := c["value"].(string); ok {
|
|
authData.Cookies = append(authData.Cookies, Cookie{
|
|
Name: name,
|
|
Value: value,
|
|
Domain: c["domain"].(string),
|
|
Path: c["path"].(string),
|
|
})
|
|
fmt.Printf(" 🍪 %s=%s\n", name, truncate(value, 40))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get CDP cookies (includes HTTPOnly)
|
|
cookies, err := network.GetCookies().Do(ctx)
|
|
if err == nil {
|
|
fmt.Println("\n📋 CDP Cookies (including HTTPOnly):")
|
|
for _, c := range cookies {
|
|
if containsKosmiDomain(c.Domain) {
|
|
authData.HTTPOnlyCookies = append(authData.HTTPOnlyCookies, Cookie{
|
|
Name: c.Name,
|
|
Value: c.Value,
|
|
Domain: c.Domain,
|
|
Path: c.Path,
|
|
Secure: c.Secure,
|
|
HTTPOnly: c.HTTPOnly,
|
|
SameSite: string(c.SameSite),
|
|
})
|
|
fmt.Printf(" 🔒 %s=%s (HTTPOnly=%v, Secure=%v)\n",
|
|
c.Name, truncate(c.Value, 40), c.HTTPOnly, c.Secure)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check WebSocket status
|
|
var wsStatus map[string]interface{}
|
|
checkScript := `
|
|
(function() {
|
|
return {
|
|
hookInstalled: !!window.__KOSMI_WS_HOOK_INSTALLED__,
|
|
wsFound: !!window.__KOSMI_WS__,
|
|
wsConnected: window.__KOSMI_WS__ ? window.__KOSMI_WS__.readyState === WebSocket.OPEN : false,
|
|
wsURL: window.__KOSMI_WS__ ? window.__KOSMI_WS__.url : null,
|
|
messageQueueSize: window.__KOSMI_MESSAGE_QUEUE__ ? window.__KOSMI_MESSAGE_QUEUE__.length : 0
|
|
};
|
|
})();
|
|
`
|
|
if err := chromedp.Run(ctx, chromedp.Evaluate(checkScript, &wsStatus)); err == nil {
|
|
authData.WebSocketStatus = wsStatus
|
|
fmt.Printf("\n🔌 WebSocket Status:\n")
|
|
for k, v := range wsStatus {
|
|
fmt.Printf(" %s: %v\n", k, v)
|
|
}
|
|
}
|
|
|
|
// Wait a bit more to capture some messages
|
|
fmt.Println("\nWaiting 5 more seconds to capture WebSocket traffic...")
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// Save to file
|
|
fmt.Printf("\n💾 Saving captured data to %s...\n", *output)
|
|
data, err := json.MarshalIndent(authData, "", " ")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error marshaling data: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := os.WriteFile(*output, data, 0644); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error writing file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("\n✅ Authentication data captured successfully!")
|
|
fmt.Println("\nSummary:")
|
|
fmt.Printf(" - Cookies: %d\n", len(authData.Cookies))
|
|
fmt.Printf(" - HTTPOnly Cookies: %d\n", len(authData.HTTPOnlyCookies))
|
|
fmt.Printf(" - Network Requests: %d\n", len(authData.NetworkRequests))
|
|
fmt.Printf(" - WebSocket Frames: %d\n", len(authData.WebSocketFrames))
|
|
fmt.Printf(" - WebSocket URL: %s\n", authData.WebSocketURL)
|
|
}
|
|
|
|
// Data structures
|
|
type AuthData struct {
|
|
RoomURL string `json:"room_url"`
|
|
CaptureTime time.Time `json:"capture_time"`
|
|
Cookies []Cookie `json:"cookies"`
|
|
HTTPOnlyCookies []Cookie `json:"httponly_cookies"`
|
|
RequestHeaders map[string]interface{} `json:"request_headers"`
|
|
ResponseHeaders map[string]interface{} `json:"response_headers"`
|
|
WebSocketURL string `json:"websocket_url"`
|
|
WebSocketRequestID string `json:"websocket_request_id"`
|
|
WebSocketStatus map[string]interface{} `json:"websocket_status"`
|
|
WebSocketFrames []WebSocketFrame `json:"websocket_frames"`
|
|
NetworkRequests []NetworkRequest `json:"network_requests"`
|
|
}
|
|
|
|
type Cookie struct {
|
|
Name string `json:"name"`
|
|
Value string `json:"value"`
|
|
Domain string `json:"domain"`
|
|
Path string `json:"path"`
|
|
Secure bool `json:"secure,omitempty"`
|
|
HTTPOnly bool `json:"httponly,omitempty"`
|
|
SameSite string `json:"same_site,omitempty"`
|
|
}
|
|
|
|
type WebSocketFrame struct {
|
|
Direction string `json:"direction"`
|
|
Data string `json:"data"`
|
|
Time time.Time `json:"time"`
|
|
}
|
|
|
|
type NetworkRequest struct {
|
|
URL string `json:"url"`
|
|
Method string `json:"method"`
|
|
Headers map[string]interface{} `json:"headers"`
|
|
Time time.Time `json:"time"`
|
|
}
|
|
|
|
// Helper functions
|
|
func containsKosmiDomain(url string) bool {
|
|
return contains(url, "kosmi.io") || contains(url, "engine.kosmi.io") || contains(url, "app.kosmi.io")
|
|
}
|
|
|
|
func contains(s, substr string) bool {
|
|
return len(s) >= len(substr) && (s == substr ||
|
|
(len(s) > len(substr) && (
|
|
s[:len(substr)] == substr ||
|
|
findSubstring(s, substr))))
|
|
}
|
|
|
|
func findSubstring(s, substr string) bool {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func truncate(s string, maxLen int) string {
|
|
if len(s) <= maxLen {
|
|
return s
|
|
}
|
|
return s[:maxLen] + "..."
|
|
}
|
|
|
|
func getWebSocketHookScript() string {
|
|
return `
|
|
(function() {
|
|
if (window.__KOSMI_WS_HOOK_INSTALLED__) return;
|
|
|
|
const OriginalWebSocket = window.WebSocket;
|
|
window.__KOSMI_MESSAGE_QUEUE__ = [];
|
|
window.__KOSMI_WS__ = null;
|
|
|
|
window.WebSocket = function(url, protocols) {
|
|
const socket = new OriginalWebSocket(url, protocols);
|
|
|
|
if (url.includes('engine.kosmi.io') || url.includes('gql-ws')) {
|
|
console.log('[Auth Capture] WebSocket created:', url);
|
|
window.__KOSMI_WS__ = socket;
|
|
window.__KOSMI_WS_CONNECTED__ = true;
|
|
}
|
|
|
|
return socket;
|
|
};
|
|
|
|
window.WebSocket.prototype = OriginalWebSocket.prototype;
|
|
window.WebSocket.CONNECTING = OriginalWebSocket.CONNECTING;
|
|
window.WebSocket.OPEN = OriginalWebSocket.OPEN;
|
|
window.WebSocket.CLOSING = OriginalWebSocket.CLOSING;
|
|
window.WebSocket.CLOSED = OriginalWebSocket.CLOSED;
|
|
|
|
window.__KOSMI_WS_HOOK_INSTALLED__ = true;
|
|
})();
|
|
`
|
|
}
|
|
|