⚠ Authorized testing only. Disclosed public bug-bounty data for defensive/educational research. Use payloads only against systems you are permitted to test.
LogoThe Hacktivity Field GuideReal-world web hacking, catalogued
🔎
Field Guide/Vulnerabilities/Logging & Monitoring Failures
Vulnerabilities

Logging & Monitoring Failures

Specimens 14No direct PortSwigger lab
⚠ Thin coverage — only 14 disclosed reports for this class; illustrative, not exhaustive.

§Basic information

Logging & monitoring failures are the two-way break of the audit pipeline. Either the log records too much — plaintext passwords, API keys, bearer tokens, and PII pulled into a file that a lower-privileged reader can reach — or it records too little / wrong — entries an attacker can forge, drop, or strip of attribution so detection and incident response never fire. A log is a durable, replayable credential store that outlives the request and sits outside the app's own access-control model: whoever can read the log effectively reads every secret that flowed through it.

The core mechanism is that sanitization is applied on the happy path and forgotten on the failure path. Auth code scrubs the password when login succeeds, then an exception handler serializes the call arguments — password included — the instant login fails. On the audit side, defenders check "is the action logged?" but not what is logged, so an event can be present yet forensically useless (blanked source IP) or absent entirely (an alternate endpoint wired to a different logging path). Treat logging bugs as a credential-harvest and anti-forensics primitive, not a compliance nit.

§Methodology

  1. Get a log-read primitive first — decide how you can read logs, then decide what leaked. Log-read can be: local admin / shared container, a pods/log RBAC grant, a log-viewer feature in the app, an LFI/path-traversal, or an exposed/guessable log file (error.log, debug.log, *_ERROR_LOG).
  2. Grep the reachable logs for the classic secret sinks (authorization, bearer, access_token, api_key, password) and for argument-dump signatures (ldap_bind, validateUserPass, toCurl).
  3. Force the error/failure path — this is where scrubbing is weakest. Make the auth backend unreachable, fail a 2FA/WebDAV login, break SMTP mid-signup, or raise log verbosity so debug dumpers fire.
  4. Read the secret out of the log and reuse it — a harvested password/token is replayed against the same or an adjacent service (LDAP, cluster API, the linked OAuth system).
  5. For attacker-controlled fields (username, method, path, X-Forwarded-For), test whether they land in logs unescaped → log forging, entry-drop, ANSI injection, or audit-IP spoofing.
  6. For audit evasion, don't ask "is it logged?" — baseline the production call, then diff an alternate endpoint (non-prod / FIPS / mirror) and check whether the event appears at all, and whether WHO/WHERE fields survive.
# Grep any reachable log for the classic secret sinks grep -RniE 'authorization|bearer|access_token|refresh_token|api[_-]?key|password|ldap_bind' /path/to/logs # Argument-dump loggers: serialized call args that hold secrets grep -RniE 'validateUserPass|areCredentialsValid|ldap_bind|addUser|toCurl' /path/to/logs # PII / raw statements baked into error logs grep -RniE 'INSERT INTO|UPDATE .* SET|VALUES \(' /path/to/logs

§Leak & lie sinks

Find which of these the target exhibits, then apply the matching probe.

Argument-dump / exception loggers

The single most common sink. A generic handler serializes the arguments of the failing call — and the secret was passed as one of those arguments. Trigger the failure path to make it fire.

# Fail auth against a 2FA-required account -> the auth exception logs the raw password arg curl -u 'VICTIM:REALPASSWORD' https://TARGET/remote.php/dav/ # Resulting log line: # OCA\DAV\Connector\Sabre\Auth->validateUserPass('VICTIM', 'REALPASSWORD') # Make the LDAP/auth backend unreachable so the failing bind() logs its DN + password, # or drop log level to debug so a blanket arg-dumper serializes ldap_bind: grep -i ldap_bind /path/to/nextcloud.log # Calling LDAP function ldap_bind with parameters [{},"uid=VICTIM","REALPASSWORD"]
▸ TIP
Attack the failure path, not the login flow. Password captured on a failed login is the tell that an exception serializer — not the auth code — is doing the logging, and those serializers scrub by method allowlist, not by parameter name, so any un-enumerated method leaks.

Verbose / debug request dumpers

toCurl(), request/response dumpers, and "log the full HTTP call" helpers serialize headers — including Authorization and Cookie — often masking in one branch but not the sibling curl-dump branch. Raise verbosity to trigger them.

# Raise component verbosity so the curl-command dumper serializes the bearer token # (klog.V(9) round_trippers.go toCurl(); debug-level app request dumpers): kubectl -n kube-system logs <controller-manager-pod> | grep -iE 'Authorization|curl -k' # curl -k -v -XGET -H "Authorization: Bearer <TOKEN>" 'https://TARGET' <- replay this token # Integration/bot exhaust leaks live OAuth tokens after any auth flow: grep -RniE 'access_token|refresh_token|Authorization' /var/log/hubot* /path/to/integration-logs

Raw SQL / query error logs

On DB failure, an error handler writes the entire failed statement — with every field value — into a log file. Find a reachable log, then grep for statements, not just stack traces.

# Locate an exposed/guessable error log, then pull the embedded PII out of raw INSERTs grep -RniE 'INSERT INTO|UPDATE .* SET' /path/to/logs # INSERT INTO dli.dli_customer_data VALUES (seq.NEXTVAL, SYSDATE, <name>, <email>, <phone>, <address>, ...)

Log injection & forging (attacker-controlled fields)

Any request-controlled value written into a space/quote-delimited log without escaping lets you forge lines, drop entries, or inject terminal escapes. The fields defenders forget: $remote_user (Basic-auth username), REQUEST_METHOD, path, and User-Agent.

# Delimiter smuggling: a space in the decoded Basic-auth username adds a column, # breaking the space-delimited log parser so the whole entry is silently dropped. curl -X POST -u '- A:B' 'https://TARGET/graphql?secret=1' # Authorization: Basic LSBBOkI= -> $remote_user = '- A' -> extra field -> ingestion drops the line

The same class, delivered as raw control bytes. Send 0x08 (backspace) / 0x1b (ESC) inside a field the logger prints verbatim, and the sequence executes when an operator tails the log in a terminal.

GET\b\b\bPOST /sign_in?x=1 HTTP/1.0 Host: TARGET # \b = 0x08 backspace, ESC = 0x1b -> hide/forge lines, move cursor, set the terminal title # (the logger escaped the query string but printed REQUEST_METHOD raw)

Audit-trail spoofing & evasion

Two distinct failures. Spoof the recorded identity by poisoning a trusted-but-forgeable field; or evade by routing the call through an endpoint wired to a different (or no) logging path.

POST /accounts/login/ HTTP/1.1 Host: TARGET X-Forwarded-For: VICTIM-IP Content-Type: application/x-www-form-urlencoded username=victim&password=x # App trusts raw XFF (no trusted-proxy allowlist) -> forged IP lands in the profile audit + admin console
# Silent enumeration: baseline hits CloudTrail, the alternate endpoint does not. aws s3tables list-table-buckets --region us-west-2 # baseline -> event appears aws s3tables list-table-buckets --region us-west-2 \ --endpoint-url https://s3tables.us-west-2.<internal>.people.aws.dev \ --no-verify-ssl # NO CloudTrail event = blind spot # permitted principal -> {"tableBuckets": []} # denied principal -> AccessDeniedException: not authorized to perform: s3tables:ListTableBuckets
● NOTE
"Is it logged?" is the wrong question. An event can be present yet useless: FIPS endpoints record the call but set sourceIPAddress and userAgent to the literal AWS Internal, gutting the network/host indicators an investigator would pivot on. Always check whether the WHO/WHERE fields survive, not just whether a row exists.

§Bypasses

Filter / controlBypassSeen in
Masking on one debug branchtoCurl() dumps Authorization unmasked while the sibling debugRequestHeaders masks it#952771
Sanitize allowlist by methodscrub list enumerates auth methods but not LDAP bind/areCredentialsValid#264426
Happy-path scrubbing onlyexception serializer logs call args even when normal-path logging is sanitized#1662194, #244092
Blanket arg-dump wrapperjson_encode of every LDAP arg at debug leaks the ldap_bind password#2101165
Log-before-validateraw token logged before it is parsed; stays valid on the error path#972561
Verbosity gate--v=4+ prints full k8s Secret objects (tokens/keys) to component logs#966383
Space-delimited log parserspace in Basic-auth $remote_user adds a column → ingestion drops the line#447488
Field the logger forgets to escapequery string escaped but REQUEST_METHOD printed raw → ANSI/control-char injection#1411867
Trusted-proxy assumptionraw X-Forwarded-For written to the audit log without a proxy allowlist#296632
Alternate endpoint, no lognon-prod *.people.aws.dev speaks the API but isn't wired to CloudTrail#3780277
Alternate endpoint, blanked fieldsFIPS endpoint logs the event but sets source IP / user-agent to AWS Internal#2979238
▲ WARNING
A secret in a log is only a finding if you can reach the log with a principal that shouldn't hold that secret. "Passwords are logged" with no log-read primitive, or where only root on the same box can read them, is a hardening nag. Pair the sink with a concrete read path — pods/log grant, log-viewer app, exposed file, LFI — or it closes as informative.

§Escalation & impact

Logging bugs are almost never the endpoint — they feed the next stage.

§Prevention

Where secrets hide in the "exhaust" — a hunting checklist

Secrets leak most often not in the app UI but in its exhaust. After exercising any auth/OAuth/integration flow, grep for authorization|bearer|access_token|refresh_token|api_key|password across: application & exception logs, pods/log in kube-system, node journald, log-aggregation UIs (Kibana/Grafana/Splunk), integration & bot logs (Hubot, webhook handlers), CI/CD job output, debug/--verbose dumps, and any *_ERROR_LOG / debug.log / .log.1 a path guess reaches. Then force the error path (unreachable backend, failed 2FA, broken SMTP, raised verbosity) and grep again — the failure handler usually dumps more than the happy path.

§Tools

Specimens — real-world examples

The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 14 in this class.

Real-world example

Plaintext password of failed login written to application log

◆ High
Specimen #244092 · nextcloud · none · 9 votes · resolved
Program nextcloudSurface webChain log credential leak -> credential reuse against LDAP/othe

Root cause

When a WebDAV/2FA login failed, the Sabre Auth code path threw and logged the exception with the method arguments, writing the user's (correct) plaintext password into the application log where admins/debuggers could read it.

Method

  1. Trigger a failing auth path that still receives the real password (e.g. WebDAV basic auth against a 2FA-required account)
  2. Inspect the application/exception log for the logged call arguments
  3. Read the plaintext credential from the stack trace: Auth->validateUserPass('user','PASSWORD')
# log line pattern OCA\DAV\Connector\Sabre\Auth->validateUserPass('USER', 'THE_PASSWORD')

Insight — Exception handlers that log function arguments will spill secrets passed as parameters (passwords, tokens, card numbers). Look for verbose debug/exception logs and endpoints that fail after receiving credentials; the log becomes a credential store reusable against LDAP/other services.

Real-world example

Error log records full SQL INSERT statements containing PII

◆ High
Specimen #3242830 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface web

Root cause

On DB failure the app's error handler writes the entire failed SQL INSERT (including all customer field values) into a log file that is itself reachable/retrievable, persisting cleartext PII well beyond intent.

Method

  1. Locate exposed/retrievable log files (ORDER_ERROR_LOG, error.log, debug logs) via path guessing or listing
  2. Search the log for full INSERT/UPDATE statements
  3. Extract the embedded PII (names, emails, phones, addresses, customer/transaction data)
# ORDER_ERROR_LOG contains: INSERT INTO dli.dli_customer_data VALUES ( dli_customer_data_sequence.NEXTVAL, SYSDATE, <full_name>, <email>, <phone>, <address>, ... ) # hunt: grep -RniE 'INSERT INTO|UPDATE .* SET' /path/to/logs

Insight — Two-part lesson: (1) as an app antipattern, logging raw failed SQL statements bakes PII into logs; (2) as a hunter, when you find any reachable log/debug file, grep it for INSERT/UPDATE statements and connection strings, not just stack traces.

Real-world example

Silent AWS IAM permission enumeration via non-production endpoints that skip CloudTrail

◆ Medium
Specimen #3780277 · aws_vdp · none · 64 votes · resolved
Program aws_vdpSurface cloudChain Stolen creds -> silent permission enumeration here -> Tag cloud-aws

Root cause

AWS spins up non-production/personal service endpoints (test NLBs, *.people.aws.dev, employee-aliased hosts) that accept real signed SigV4 requests with standard IAM credentials and perform normal IAM authorization, but do NOT emit a matching CloudTrail event. An attacker who redirects the AWS CLI/SDK to one of these hosts can test whether stolen credentials hold a given permission without generating the failed/successful API log that detection relies on.

Method

  1. Obtain/target a set of AWS credentials (the attacker case: stolen access keys or an assumed role).
  2. Discover candidate non-production endpoints: monitor Certificate Transparency logs (crt.sh / cert-stream) for hostnames like <service>*.people.aws.dev, s3table-nlb-personal-*.elb.<region>.amazonaws.com, <service>-fips.*, and other employee/test-named service hosts.
  3. Baseline: run a normal read call against the production endpoint (e.g. `aws s3tables list-table-buckets`), wait 10-15 min, confirm a CloudTrail event appears.
  4. Redirect the same call to a candidate endpoint with `--endpoint-url` (add `--no-verify-ssl` / short timeouts as needed) and repeat with both a privileged and an unprivileged principal.
  5. Diff the two responses: a permitted principal returns normal data / a non-IAM error, a denied principal returns an explicit AccessDeniedException naming the action. That differential leaks whether the credential holds the permission.
  6. Re-check CloudTrail (all regions) and confirm NO matching event was produced for either principal -> the enumeration was invisible.
# Baseline (production, DOES log to CloudTrail): aws s3tables list-table-buckets --region us-west-2 # Silent test against a non-production endpoint (NO CloudTrail event): aws s3tables list-table-buckets --region us-west-2 \ --cli-connect-timeout 10 --cli-read-timeout 10 \ --endpoint-url https://s3tables.us-west-2.<internal>.people.aws.dev \ --no-verify-ssl # Response differential reveals the permission bit: # permitted principal -> {"tableBuckets": []} # denied principal -> AccessDeniedException: ... not authorized to perform: s3tables:ListTableBuckets

Insight — Detection of credential abuse in AWS assumes every API call hits CloudTrail. Any alternate host that speaks the same API but is not wired into CloudTrail is a logging blind spot. When assessing a cloud provider or a large SaaS, hunt for non-production/mirror endpoints (Certificate Transparency is the cheap discovery channel) and test whether they are (a) reachable with prod credentials and (b) absent from the audit log. The `--endpoint-url` override is the general trick: same signed request, different host, different (or zero) logging.

Real-world example

Secrets logged in cleartext (API keys and passwords in logs)

◆ Medium
Specimen #1662194 · Omise · awarded · 31 votes · resolved
Program OmiseSurface other

Root cause

Sensitive values were written to logs in cleartext: the Omise client debug-logs the Authorization/secret API key, and (merged 469668) Nextcloud's exception serializer logs full method parameters including user passwords when an exception is thrown.

Method

  1. Enable/inspect debug logging or trigger an exception in a code path handling secrets
  2. For Omise: run any request with logging level DEBUG; secret key appears via logger.debug('Authorization: %s', api_key)
  3. For Nextcloud (469668): create a user while SMTP is down; the addUser exception stack trace logs the password parameter
  4. Read the secrets from the log output
# Omise omise/request.py L88/L111 logger.debug('Authorization: %s', self.api_key) # Nextcloud exception log (SMTP failure during addUser) addUser("[user]", "[PASSWORD]", ...) at Dispatcher.php:166

Insight — Grep the codebase/logs for logger.debug/print of api_key, Authorization, password, token. Exception serializers that dump call arguments are a stealthy sink: filter by parameter name ($password), not by an allowlist of methods, or passwords leak on every unexpected error.

Real-world example

CloudTrail attribution obfuscation: FIPS endpoints log attacker IP/user-agent as 'AWS Internal'

◆ Medium
Specimen #2979238 · aws_vdp · none · 29 votes · resolved
Program aws_vdpSurface cloudTag cloud-aws

Root cause

For certain AWS service endpoints (observed on FIPS endpoints across several services), CloudTrail still records the event but populates the sourceIPAddress and userAgent fields with the literal string 'AWS Internal' instead of the caller's real IP and client. An attacker routing calls through these endpoints keeps the action logged but strips the network/host indicators an investigator would pivot on.

Method

  1. Baseline: run a call against the normal production endpoint (e.g. `aws comprehendmedical list-phi-detection-jobs`), wait 5-10 min, inspect the CloudTrail record and confirm sourceIPAddress and userAgent are populated with the real values.
  2. Repeat the identical call against a FIPS endpoint via `--endpoint-url` (e.g. comprehendmedical-fips.us-east-1.api.aws).
  3. Inspect the resulting CloudTrail event: sourceIPAddress and userAgent now read 'AWS Internal', hiding the attacker's IP and client fingerprint.
  4. Prefer these endpoints for logged-but-attributed operations to degrade incident-response pivoting on network indicators.
# Baseline (real IP + user-agent land in CloudTrail): aws comprehendmedical list-phi-detection-jobs # Via FIPS endpoint -> CloudTrail records sourceIPAddress/userAgent = "AWS Internal": aws comprehendmedical list-phi-detection-jobs \ --endpoint-url https://comprehendmedical-fips.us-east-1.api.aws

Insight — Logging is not binary. An event can be present in the audit log yet still be useless for attribution if the identifying fields are blanked or spoofed. When reviewing any audit pipeline, don't just check whether an action is logged -- check whether the WHO/WHERE (source IP, user-agent, principal) survive. Alternate endpoint families (FIPS, dualstack, regional mirrors) can be logged by a different code path that fills these fields differently.

Real-world example

Credentials written to application debug logs

◆ Medium
Specimen #2101165 · nextcloud · awarded · 28 votes · resolved
Program nextcloudSurface web

Root cause

A generic pre-call logger json_encodes all arguments of every LDAP function at debug level, so ldap_bind's password argument lands in cleartext in the log file (CVE-2023-48305).

Method

  1. Set loglevel to 0 (debug) on an LDAP-backed instance
  2. Log in as an LDAP user
  3. Grep the log for 'ldap_bind' and read the cleartext password in the message args
grep ldap_bind nextcloud.log # message: Calling LDAP function ldap_bind with parameters [{},"uid=<USER>","<PASSWORD>"]

Insight — Blanket 'log all function args' wrappers are a recurring credential-leak sink. When you have log read access (local admin, log-viewer app, exposed log endpoint), grep for bind/login/auth function names; secrets often sit in serialized argument arrays.

Real-world example

Log injection / entry-drop via Basic-auth username in nginx $remote_user

◆ Medium
Specimen #447488 · security · none · 19 votes · resolved
Program securitySurface web

Root cause

nginx logs $remote_user (decoded from Authorization: Basic) unquoted and unescaped between space-delimited fields. An attacker embeds a space/delimiter in the username, adding an extra column that breaks downstream log ingestion so the entry is silently discarded.

Method

  1. Send Basic auth whose decoded username contains a whitespace/delimiter
  2. Request is proxied normally but the access-log line gains an extra field
  3. Log pipeline fails to parse and drops the entry -> request is unlogged (anti-forensics)
curl -X POST -u '- A:B' 'https://TARGET/graphql?secret=1' # Authorization: Basic LSBBOkI= -> $remote_user = '- A' -> extra column in space-delimited access log

Insight — Attacker-controlled fields written into space/quote-delimited logs without escaping enable log forging and, worse, log-drop that hides malicious requests from the SIEM. Audit every request-controlled value ($remote_user, cookies, UA) in log_format for unquoted placeholders.

Real-world example

High log verbosity prints Kubernetes Secret contents in cloud controller logs

◆ Medium
Specimen #966383 · kubernetes · awarded · 3 votes · resolved
Program kubernetesSurface cloudChain pods/log read in kube-system -> Secret contents in controTag cloud

Root cause

The vSphere legacy cloud provider registers a Secret informer and logs the full secret object; with kube-controller-manager log verbosity set to 4 or higher, secret data (passwords, private keys, service-account tokens) is written to the controller-manager log on every secret create/update (CVE-2020-8563).

Method

  1. Confirm the cluster runs vSphere as cloud provider with controller-manager verbosity >= 4.
  2. As a principal that can read kube-system pod logs (GET pods/log), tail the cloud-controller-manager / kube-controller-manager pod log.
  3. Trigger or wait for a Secret create/update; read the secret payload printed by the SetInformers handler.
  4. If a leaked value is a service-account token, use it to escalate privileges.
kubectl -n kube-system logs <kube-controller-manager-pod> | grep -i secret # with --v=4+, secret objects (data incl. tokens/keys) are printed on create/update

Insight — Verbose logging is a first-class secret sink in cloud/orchestration platforms. Whoever can read component logs (pods/log in kube-system, node journald, log aggregators) effectively reads secrets. Audit log verbosity flags and which identities can read the logs; a leaked SA token is a direct privesc.

Real-world example

ANSI/terminal escape-sequence injection into request logs

◆ Medium
Specimen #1411867 · rails · none · 2 votes · resolved
Program railsSurface web

Root cause

Rack CommonLogger writes the raw REQUEST_METHOD (and other request metadata) to the log without stripping control characters, so control bytes/escape sequences land verbatim in log files.

Method

  1. Send a request whose method or path contains raw control bytes (backspace 0x08, ESC 0x1b).
  2. The logger escapes the query string but prints the request method as-is.
  3. When an operator views the log in a terminal, the injected escape sequences execute (cursor moves, color, title-set, potential command hiding).
GET\b\b\bPOST /sign_in?test1=1\b2 HTTP/1.0 Host: 127.0.0.1:4567 # \b = 0x08 backspace; ESC (0x1b) sequences enable ANSI injection / log forging

Insight — Anywhere raw request fields (method, UA, path, headers) are echoed to logs, test control-char/ANSI injection: hide entries with backspaces, forge lines with CRLF, or trigger terminal escape sequences on whoever tails the log. Same class curl fixed in WEBrick years earlier.

Real-world example

Bearer tokens logged via klog verbose toCurl() (CVE-2019-11250)

◆ Medium
Specimen #952771 · kubernetes · awarded · 1 votes · resolved
Program kubernetesSurface otherChain Token in logs -> log-reader (Eve) replays bearer token -&Tag jwt

Root cause

client-go's debuggingRoundTripper masks header values only in the debugRequestHeaders branch; the preceding debugCurlCommand branch calls reqInfo.toCurl(), which serializes all request headers (including Authorization) unmasked into logs at high klog verbosity.

Method

  1. Run kube components at high verbosity (klog.V(9)/debugCurlCommand)
  2. Observe round_trippers.go logging a full 'curl -k -v -H "Authorization: <token>" URL' line
  3. Anyone with log access replays the bearer token to impersonate the user
// round_trippers.go toCurl(): builds "curl -k -v -X%s %s '%s'" with raw -H "Authorization: <bearer>" // maskValue() applied only in debugRequestHeaders branch, not in toCurl()

Insight — Credential masking must cover every logging code path, not just one. When auditing verbose/debug logging, grep for curl-command builders, request dumpers, and toString()/toCurl() helpers that serialize headers without reusing the masking routine - Authorization/Cookie/token headers leak to log readers who lack cluster access.

Real-world example

OAuth tokens written to integration logs in cleartext

◆ Medium
Specimen #1394399 · rocket_chat · none · 1 votes · resolved
Program rocket_chatSurface webTag oauth

Root cause

An integration/bot subsystem (Hubot) logs full request/response data including OAuth access tokens in plaintext, so anyone with log access (ops, log aggregation, shared containers) can lift live credentials.

Method

  1. After any OAuth/token flow, inspect application, integration, and bot logs (Hubot, webhook handlers, debug logs) for Authorization headers, access_token, refresh_token, or bearer values.
  2. Confirm the tokens are live (unredacted) and grant access to the linked system; report as cleartext storage of secrets.

Insight — Secrets leak most often not in the app UI but in its exhaust: integration/bot logs, debug output, and log shippers. Always grep captured logs and any log-viewing UI for token/bearer/authorization/password after exercising auth flows. Fix is to redact/placeholder tokens before logging.

Real-world example

Secret logged before validation/deletion (kubeadm bootstrap token)

◆ Low
Specimen #972561 · kubernetes · awarded · 7 votes · resolved
Program kubernetesSurface other

Root cause

CLI logs the raw input (which may be a full bootstrap token, not just an ID) at verbosity before parsing/deleting it; if deletion fails the token stays valid and is now persisted in logs.

Method

  1. Locate log calls that print user/CLI input prior to type/validity checks
  2. Note where input may be a full credential vs an identifier
  3. Confirm the credential remains valid on the error path -> log-readable secret
// kubeadm cmd/token.go RunDeleteTokens klog.V(1).Infof("[token] parsing token %q", tokenIDOrToken) // logs full token before it is parsed

Insight — Grep source/logs for the pattern 'log the raw argument, then decide if it's a secret'. Sensitive material logged before validation (or on failure paths) is a durable credential leak to anyone with log access.

Real-world example

Credentials written to logs on error path (missing from sensitive-param sanitize list)

◆ Low
Specimen #264426 · nextcloud · none · 2 votes · resolved
Program nextcloudSurface webChain backend error -> creds in logs -> (with log-read/LFI)

Root cause

When an LDAP backend is unavailable, the failing method call (bind/areCredentialsValid/etc.) is logged with its arguments; those methods were not enumerated in the log's sensitive-parameter exclusion list, so plaintext usernames and passwords land in log files.

Method

  1. Trigger the auth backend error path (e.g. make the LDAP server unreachable)
  2. Observe that the failing method call is logged with full arguments
  3. Read plaintext credentials from the application log
# lib/private/Log.php $methodsWithSensitiveParameters missing: # bind, areCredentialsValid, invokeLDAPMethod, checkPasswordNoLogging # -> LDAP bind() DN + password logged in cleartext on connection failure

Insight — Audit error/exception logging paths: happy-path logging is usually sanitized, but failure handlers frequently dump full method args including passwords/tokens. When reviewing source, grep the log-scrubbing allowlist and diff it against every method that receives a secret. As an attacker, force auth/backend errors and then find a log-read primitive to harvest creds.

Real-world example

Audit-log source-IP spoofing via X-Forwarded-For at login

◆ Info
Specimen #296632 · weblate · none · 4 votes · resolved
Program weblateSurface webTag logging

Root cause

The application (Dockerized Weblate behind a reverse proxy) trusts the client-supplied X-Forwarded-For header as the real client IP and writes it directly into audit-log entries without validating it against the actual trusted-proxy chain.

Method

  1. Locate a feature that records the client IP into an audit/security log (login events, profile activity, admin console).
  2. Send the login (or logged action) with a spoofed X-Forwarded-For: ATTACKER-CHOSEN-IP header.
  3. Observe the forged IP appear in the user profile audit log and admin console.
  4. Optionally probe adjacent sinks: here User-Agent value '"<b>test was stored unencoded (no XSS, but a sign the field flows into other sinks).
POST /accounts/login/ HTTP/1.1 Host: TARGET X-Forwarded-For: 1.2.3.4 Content-Type: application/x-www-form-urlencoded username=victim&password=...

Insight — When an app sits behind a proxy, check whether logged/displayed client IPs come from X-Forwarded-For (or X-Real-IP) without a trusted-proxy allowlist. If so you can poison the audit trail, frame another IP, or hide the real source of a compromise. Also test whether the same forgeable IP feeds rate-limiting or IP-based access control for higher impact.

§References & practice

  1. No dedicated PortSwigger lab for this class; use the methodology above and the cited reports.
  2. All 14 disclosed reports for this class are catalogued as specimens above.
  3. See also: exploit chains · payload libraries · methodology.