T09 · Insecure Skill Coding Practices
- Location
scripts/auth.py:43- Finding
OAuth Callback Does Not Validate a CSRF State Parameter
- Content
View full analysis
Vulnerability Details
File Location:
scripts/auth.py, lines 43-76
Vulnerability Type: OAuth login CSRF and account substitution
Risk Level: MediumVulnerable Code
python class CallbackHandler(BaseHTTPRequestHandler): def do_GET(self): if '/callback' in self.path: # Parse authorization code query = urllib.parse.urlparse(self.path).query params = urllib.parse.parse_qs(query) if 'code' in params: self.server.auth_code = params['code'][0] self.send_response(200) self.end_headers() self.wfile.write(b'Authorization successful! You can close this window.') elif 'error' in params: self.server.error = params['error'][0] self.send_response(400) self.end_headers() self.wfile.write(f'Error: {params["error"][0]}'.encode()) else: self.send_response(404) self.end_headers()python def get_authorization_url(): """Build OAuth authorization URL""" scopes = 'read,activity:read' # Read your activities params = { 'client_id': CLIENT_ID, 'redirect_uri': REDIRECT_URI, 'response_type': 'code', 'approval_prompt': 'force', 'scope': scopes } return f"https://www.strava.com/oauth/authorize?{urllib.parse.urlencode(params)}"Technical Analysis
The OAuth authorization request does not include a cryptographically random
stateparameter. The local callback handler consequently accepts anycodeparameter without verifying that the callback corresponds to the authorization flow initiated by the current process.The
stateparameter binds an OAuth response to its initiating browser session and prevents login CSRF. Without this binding, a compatible authorization resp ...[truncated 1591 chars]- Remediation
View remediation
Remediation Suggestions
-
Generate a fresh state value for every authorization attempt:
python import secrets oauth_state = secrets.token_urlsafe(32) -
Add the value to the authorization request:
python params = { 'client_id': CLIENT_ID, 'redirect_uri': REDIRECT_URI, 'response_type': 'code', 'approval_prompt': 'force', 'scope': scopes, 'state': oauth_state, } -
Retain the expected state only for the active process and reject callbacks that omit it or supply a different value. Use
secrets.compare_digestfor comparison. -
Exchange the authorization code only after successful state validation.
-
Match the callback path exactly, such as
parsed.path == '/callback'. -
Return an error and terminate the flow when state validation fails.
-
Add
timeout=30to the token-exchangeurlopencall so it matches the documented network timeout policy.
-
