Out Of Bounds Read with Basic Auth
How Out Of Bounds Read Manifests in Basic Auth
Basic Authentication relies on the Authorization: Basic header, where username:password. When a server decodes this header it often extracts the username and password by locating the colon separator. If the code that performs this extraction does not validate the length of the decoded string or the position of the colon, an attacker can supply a credential string that causes an out‑of‑bounds read.
Consider a C‑language snippet that copies the username into a fixed‑size buffer without bounds checking:
char user[32];
char *cred = base64_decode(auth_header); // user‑controlled length
char *colon = strchr(cred, ':');
if (colon) {
size_t len = colon - cred; // no check that len < sizeof(user)
strcpy(user, cred); // copies len+1 bytes, may overflow user
}
If the attacker provides a username longer than 31 bytes (plus the colon and password), strcpy reads past the end of the cred buffer, leading to an out‑of‑bounds read. The overread can expose adjacent memory contents, which may include other authentication tokens, internal pointers, or application data that gets echoed back in error messages or logs.
In higher‑level languages the same mistake appears when developers use unsafe string functions or assume the colon is present. Example in Node.js:
const cred = Buffer.from(authHeader.split(' ')[1], 'base64').toString();
const username = cred.substring(0, cred.indexOf(':')); // indexOf returns -1 if not found
// substring(0, -1) yields the whole string, but later code may assume a split
const [user, pass] = cred.split(':'); // if no colon, pass is undefined
if (pass.length > 0) { /* … */ } // accessing length on undefined throws
While the JavaScript example throws an exception, the underlying V8 engine may still read beyond the allocated string buffer when computing indexOf on a maliciously long string, potentially leaking memory through side‑channels such as timing differences or detailed error stacks.
Thus, out‑of‑bounds reads in Basic Auth typically arise from:
- Missing length validation of the decoded credential before copying or slicing.
- Assuming a colon separator exists without checking
indexOforstrchrresult. - Using unsafe C string functions (
strcpy,strncpywithout proper bounds) on attacker‑controlled data.
Basic Auth‑Specific Detection
Detecting this flaw requires sending credential strings that stress the parsing logic. A scanner must:
- Capture the
Authorizationheader expected by the endpoint. - Generate a series of Basic Auth values where the Base64 payload is:
- Extremely long (e.g., 1 KB, 10 KB) with no colon.
- Contains a colon near the start or end to test boundary conditions.
- Contains invalid Base64 padding to see if decoding routines overrun.
- Observe the server’s response for signs of an overread:
- HTTP 500 errors with stack traces that reveal memory addresses.
- Delayed responses indicating a page fault or signal handler.
- Leaked data in the response body (e.g., fragments of other headers, environment variables).
- Changes in response length that correlate with the size of the injected payload.
middleBrick performs these checks automatically as part of its Input Validation and Property Authorization scans. When you submit a URL via the dashboard, CLI, or GitHub Action, the platform:
- Builds a matrix of Basic Auth headers with credential lengths ranging from 64 bytes to 64 KB.
- Sends each header in parallel, measuring response time, status code, and body entropy.
- Flags any anomaly where the response deviates from the baseline (e.g., non‑2xx status, increased latency, or presence of non‑UTF‑8 bytes) as a potential out‑of‑bounds read.
- Provides a finding with severity, the exact payload that triggered the issue, and remediation guidance.
Example CLI command that triggers the scan and returns JSON output:
middlebrick scan https://api.example.com/resource --format json
The resulting JSON includes a findings array; an out‑of‑bounds read appears under the Input Validation category with a description similar to:
{
"id": "OB-001",
"name": "Out‑of‑bounds read via Basic Auth credential length",
"severity": "high",
"description": "The server does not validate the length of the decoded Basic Auth credential before copying it into a fixed buffer, allowing an attacker to cause an overread and potentially leak memory contents.",
"remediation": "Validate credential length and ensure a colon separator exists before extracting username/password. Use safe copy functions with explicit bounds."
}
Basic Auth‑Specific Remediation
Fixing the issue requires defensive handling of the Basic Auth header at the point where the credential is decoded and split. The remediation steps are language‑agnostic but must be applied to the specific code path that processes the header.
- Validate the header format before decoding:
- Ensure the header starts with
Basic(case‑insensitive). - Extract the Base64 token and reject if its length exceeds a reasonable limit (e.g., 512 bytes).
- Decode the token using a safe Base64 decoder that returns an error on malformed input.
- After decoding, verify that the credential string contains exactly one colon separating username and password.
- Split the string into two parts and enforce individual length limits (e.g., username ≤ 100 chars, password ≤ 256 chars).
- Use bounded copy functions when moving data into fixed buffers:
- In C:
strncpy(dest, src, sizeof(dest)-1); dest[sizeof(dest)-1] = '\0'; - In Node.js:
[user, pass] = cred.split(':', 2); if (!user || !pass) throw new Error('Invalid credentials'); - In Python:
if ':' not in cred: raise ValueError; user, _, pass = cred.partition(':'); if len(user) > MAX_USER or len(pass) > MAX_PASS: raise ValueError - Reject any request that fails validation with HTTP 401 (Unauthorized) and a generic WWW‑Authenticate header—do not expose details about why the credential was malformed.
Code example: a secure middleware for an Express.js endpoint.
function basicAuthValidator(req, res, next) {
const auth = req.headers.authorization;
if (!auth || !/^Basic\s+/i.test(auth)) {
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').send();
}
const token = auth.split(' ')[1];
if (token.length > 1024) { // limit encoded length
return res.status(401).send();
}
let cred;
try {
cred = Buffer.from(token, 'base64').toString('utf8');
} catch (_) {
return res.status(401).send();
}
if (cred.indexOf(':') === -1) {
return res.status(401).send();
}
const [username, password] = cred.split(':', 2);
if (!username || !password) {
return res.status(401).send();
}
if (username.length > 100 || password.length > 256) {
return res.status(401).send();
}
// attach to request for downstream use
req.auth = { username, password };
next();
}
app.get('/data', basicAuthValidator, (req, res) => {
res.json({ message: 'ok', user: req.auth.username });
});
In C, the equivalent safe handling looks like:
int validate_basic_auth(const char *header, char *user_out, size_t user_len,
char *pass_out, size_t pass_len) {
if (strncasecmp(header, "Basic ", 6) != 0) return 0;
const char *b64 = header + 6;
if (strlen(b64) > 1024) return 0;
size_t dec_len;
unsigned char *dec = base64_decode(b64, &dec_len);
if (!dec) return 0;
if (dec_len == 0 || memchr(dec, ':', dec_len) == NULL) {
free(dec);
return 0;
}
// find colon
const char *colon = memchr(dec, ':', dec_len);
size_t user_len_raw = colon - dec;
size_t pass_len_raw = dec - (colon + 1);
if (user_len_raw >= user_len || pass_len_raw >= pass_len) {
free(dec);
return 0;
}
memcpy(user_out, dec, user_len_raw);
user_out[user_len_raw] = '\0';
memcpy(pass_out, colon + 1, pass_len_raw);
pass_out[pass_len_raw] = '\0';
free(dec);
return 1;
}
By enforcing length limits, confirming the presence of a colon, and using bounded copy operations, the out‑of‑bounds read vector is eliminated while preserving the legitimate Basic Auth workflow.