Skip to main content
QtRecon provides comprehensive credential management on a per-host basis, allowing you to store and utilize credentials during penetration tests.

Per-host credentials

Credentials are stored in the hosts_creds table, associated with specific hosts. Each credential entry contains type, domain, username, and password fields.

Database structure

The credentials table schema:
database.py
CREATE TABLE hosts_creds(
  id INTEGER primary key autoincrement not null, 
  host_id integer, 
  type TEXT DEFAULT 'password', 
  domain TEXT DEFAULT '', 
  username TEXT DEFAULT '', 
  password TEXT DEFAULT ''
)
Each host can have multiple credential sets, enabling you to store different user accounts, hash types, or SSH keys for the same target.

Credential types

QtRecon supports multiple credential types to accommodate different authentication scenarios:

Password

Plaintext passwords for authentication

Hash

NTLM hashes or other hash formats for pass-the-hash attacks

SSH key

SSH private keys for key-based authentication

Credential fields

Each credential entry contains:
FieldDescriptionExample
typeCredential type (password, hash, ssh_key)password
domainWindows domain or workgroupCORP
usernameAccount usernameadministrator
passwordPassword, hash, or key contentP@ssw0rd123

Managing credentials

The CredsModel class provides methods for credential operations:
core/models/credsmodel.py
class CredsModel(QAbstractTableModel):
    def get_creds_for_host(self, host_id: int) -> list:
        return Database.request(
            'select * from hosts_creds where host_id = ?', 
            (host_id, )
        ).fetchall()
    
    def create_creds(self, host_id: int) -> int:
        new_id = Database.request(
            "INSERT INTO hosts_creds(host_id) VALUES (?) RETURNING id", 
            (host_id, )
        ).fetchone()['id']
        self.update_data()
        return new_id
    
    def update_credentials(self, cred_id: str, column: str, new_value: str):
        Database.request(
            f"UPDATE hosts_creds SET {column} = ? WHERE id = ?", 
            (new_value, cred_id)
        )
        self.update_data()

Variable replacement

When launching tools, QtRecon automatically replaces credential variables in command arguments:

Available variables

%%%USERNAME%%%
string
Replaced with the username field from selected credentials
%%%PASSWORD%%%
string
Replaced with the password field (for password-type credentials)
%%%HASH%%%
string
Replaced with the password field (for hash-type credentials)
%%%SSH_KEY%%%
string
Replaced with the password field (for ssh_key-type credentials)
%%%DOMAIN%%%
string
Replaced with the domain field

Usage in tools

Configure tools to use credential variables:
conf.json
"xfreerdp": {
  "name": "xfreerdp",
  "text": "Launch xfreerdp",
  "detached": true,
  "in_terminal": false,
  "binary": "/usr/bin/xfreerdp",
  "args": [
    "/v:%%%IP%%%", 
    "/d:%%%DOMAIN%%%", 
    "/u:%%%USERNAME%%%", 
    "/p:%%%PASSWORD%%%", 
    "/cert:ignore", 
    "/drive:tmp,/tmp", 
    "/dynamic-resolution"
  ]
}
conf.json
"smb_script_authenticated": {
  "name": "SMB authenticated enum",
  "text": "Launch authenticated custom smb enumeration script",
  "detached": false,
  "binary": "/bin/bash",
  "args": [
    "scripts/smb.sh", 
    "%%%IP%%%", 
    "%%%DOMAIN%%%", 
    "%%%USERNAME%%%", 
    "%%%PASSWORD%%%"
  ]
}

Variable replacement logic

core/controller.py
def replace_variables(self, input_string, credtype=None, domain=None, 
                     username=None, password=None, host_id: str = "", 
                     port: str = "") -> str:
    output_string = input_string
    
    if credtype:
        output_string = output_string.replace(f"%%%DOMAIN%%%", domain)
        output_string = output_string.replace(f"%%%USERNAME%%%", username)
        if f"%%%{credtype.upper()}%%%" in output_string:
            output_string = output_string.replace(
                f"%%%{credtype.upper()}%%%", 
                password
            )
    
    return output_string
The variable replacement matches the credential type. For example, %%%HASH%%% only gets replaced when using hash-type credentials.

Credential detection

When launching a tool, QtRecon automatically detects if credentials are needed and available:
core/controller.py
# Check if creds are needed and available for this host and command
creds_from_database = []
creds_types = [row['type'] for row in 
               Database.request("SELECT DISTINCT type FROM hosts_creds").fetchall()]

for creds_type in creds_types:
    if f"%%%{creds_type.upper()}%%%" in ' '.join(program['args']):
        creds_from_database += Database.request(
            "SELECT DISTINCT hosts_creds.type, hosts_creds.domain, "
            "hosts_creds.username, hosts_creds.password "
            "FROM hosts_creds, hosts "
            "WHERE (host_id = ? AND type = ?) OR "
            "(hosts.id = hosts_creds.host_id AND "
            "(lower(domain) != 'localhost' and lower(domain) != lower(hosts.hostname)) "
            "AND type = ?)", 
            (host_dst['id'], creds_type, creds_type)
        ).fetchall()

if creds_from_database:
    reply = QMessageBox.question(
        self.ui, 
        'Valid credentials are available', 
        f"Valid credentials are available to use against this target. "
        f"Do you want to use them ?"
    )
QtRecon prompts you to select which credentials to use when multiple valid options are available for the target.

Import from secretsdump

QtRecon can parse and import credentials from Impacket’s secretsdump output:
core/models/credsmodel.py
def parse_creds_from_secretsdump(self, output: str) -> list:
    """
    :param output:
    :return: [{'type': 'hash', 'username':'admin', 'value':'59d3b937f3cb34ce0da3f7a7c4b58694}, ...]
    """
    creds = []
    for line in output:
        # Dumping local SAM hashes (uid:rid:lmhash:nthash)
        if re.match('^[^:]+:[0-9]+:[a-fA-F0-9]{32}:[a-fA-F0-9]{32}:::$', line):
            creds.append({
                'type': 'hash', 
                'username': line.split(':')[0], 
                'value': line.split(':')[3]
            })
        # Dumping LSA Secrets
        elif re.match('^[^:]+:[a-fA-F0-9]{32}:[a-fA-F0-9]{32}:::$', line):
            creds.append({
                'type': 'hash', 
                'username': line.split(':')[0], 
                'value': line.split(':')[2]
            })
    
    return creds
1

Run secretsdump

Execute Impacket’s secretsdump against a target to extract credentials.
2

Copy output

Copy the secretsdump output containing NTLM hashes.
3

Import to QtRecon

Use the import function to parse and store hashes in QtRecon’s database.

Import formats

QtRecon supports multiple import formats:
Administrator:500:aad3b435b51404eeaad3b435b51404ee:59d3b937f3cb34ce0da3f7a7c4b58694:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Automatically extracts username and NT hash.
def parse_creds_from_user_password(self, output: str) -> list:
    return [{
        'type': 'password', 
        'username': line.split(':')[0], 
        'value': line.removeprefix(line.split(':')[0] + ':').strip()
    } for line in output]
Simple username:password pairs.
def parse_creds_from_user_hash(self, output: str) -> list:
    return [{
        'type': 'hash', 
        'username': line.split(':')[0], 
        'value': line.removeprefix(line.split(':')[0] + ':').strip()
    } for line in output]
Username:hash pairs for manual import.