T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/auth.py:55
- Finding
- OAuth Callback Is Exposed to Login CSRF and Network-Based Code Injection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/auth.py:55-114` **Vulnerability Type**: OAuth login CSRF caused by missing state validation and an unnecessarily exposed callback listener **Risk Level**: Medium ### Vulnerable Code ```python def get_auth_url(self) -> str: """ Generate authorization URL for Bigin Returns: Authorization URL string """ return ( f"https://accounts.zoho.{self.dc}/oauth/v2/auth?" f"scope={self.scope}&" f"client_id={self.client_id}&" f"response_type=code&" f"access_type=offline&" f"redirect_uri={self.redirect_uri}" ) def start_auth_flow(self) -> Dict[str, Any]: """ Start local server and authenticate via browser Returns: Token dictionary with access_token and refresh_token """ auth_code = None class CallbackHandler(http.server.BaseHTTPRequestHandler): def do_GET(handler_self): nonlocal auth_code query = urllib.parse.urlparse(handler_self.path).query params = urllib.parse.parse_qs(query) if 'code' in params: auth_code = params['code'][0] handler_self.send_response(200) handler_self.send_header('Content-type', 'text/html') handler_self.end_headers() handler_self.wfile.write(b""" <html> <body> <h1>Authentication Successful!</h1> <p>You can close this window and return to the terminal.</p> </body> </html> """) else: handler_self.send_response(400) handler_self.end_headers() handler_self.wfile.write(b"Authentication failed. No code received.") def log_message(self, format, *args): # Suppress default logging pass # Open browser for authent ...[truncated 2979 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Generate a cryptographically secure state value for each authorization attempt: ```python import secrets state = secrets.token_urlsafe(32) ``` 2. Include the URL-encoded `state` value in the authorization request and retain the expected value only for the lifetime of the current authentication flow. 3. Require the callback to provide the same state and compare it using `secrets.compare_digest`. 4. Bind the callback listener explicitly to loopback: ```python with socketserver.TCPServer(("127.0.0.1", 8888), CallbackHandler) as httpd: ``` 5. Validate that the parsed callback path is exactly `/callback`. 6. Reject callbacks containing an OAuth `error`, missing or duplicate `code` parameters, missing state, or invalid state. 7. Stop accepting requests immediately after the first valid callback. 8. Add PKCE with an S256 code challenge where supported. 9. Construct authorization parameters with `urllib.parse.urlencode` rather than manual string concatenation. 10. Add automated tests covering invalid state, missing state, an incorrect callback path, duplicate callbacks, and listener binding. ]]>
