Skip to main content
  1. Documentation/
  2. Admin Guide/

Security Hardening

Table of Contents
Actionable hardening steps for production TantoC2 deployments.

TLS Configuration
#

Why TLS Matters
#

The teamserver API carries sensitive data: operator credentials, agent commands, collected credentials, and file contents. Without TLS, any observer on the path between an operator’s workstation and the server can intercept all of this.

Enabling TLS
#

Set in ~/.tantoc2/config.yaml:

1
2
3
tls_enabled: true
tls_cert_file: /opt/tantoc2/certs/server.crt
tls_key_file: /opt/tantoc2/certs/server.key

Or via environment variables:

1
2
3
export TANTOC2_TLS_ENABLED=true
export TANTOC2_TLS_CERT_FILE=/opt/tantoc2/certs/server.crt
export TANTOC2_TLS_KEY_FILE=/opt/tantoc2/certs/server.key

Generating a Certificate
#

For internal use (operator access only), a self-signed certificate is acceptable. Operators must configure the CLI and browser to trust it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Create certs directory
mkdir -p /opt/tantoc2/certs
chmod 700 /opt/tantoc2/certs

# Generate a 4096-bit RSA key and self-signed certificate
openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout /opt/tantoc2/certs/server.key \
  -out /opt/tantoc2/certs/server.crt \
  -days 365 \
  -subj "/CN=tantoc2.ops.internal" \
  -addext "subjectAltName=DNS:tantoc2.ops.internal,IP:10.10.0.20"

# Restrict key file access
chmod 600 /opt/tantoc2/certs/server.key
chown tantoc2:tantoc2 /opt/tantoc2/certs/

For external-facing deployments, use a certificate from a trusted CA (Let’s Encrypt or internal PKI).

TLS for Transport Listeners
#

Transport listeners (the ports agents beacon to) support TLS independently of the operator API. Configure per-listener when creating HTTPS listeners:

1
2
3
tantoc2> listeners create http --name ops-https --port 443 --tls \
  --cert /opt/tantoc2/certs/listener.crt \
  --key /opt/tantoc2/certs/listener.key
C2 beacon traffic is end-to-end encrypted at the application layer (AES-256-GCM) regardless of whether the transport uses TLS. TLS on the listener provides an additional layer and hides beacon protocol metadata from network observers.

JWT Token Configuration
#

TantoC2 uses opaque bearer tokens (not JWTs in the strict RFC 7519 sense, but token-based auth with similar properties). The defaults are:

TokenDefault TTLNotes
Access token3600s (1 hour)Required for all API calls
Refresh token86400s (24 hours)Single-use; exchanged for a new token pair

These values are compiled into the AuthService class. To change them, the server code must be modified and rebuilt. For most engagements, the defaults are appropriate.

Token Security Properties
#

  • Tokens are generated with secrets.token_urlsafe(32) — 256 bits of entropy
  • Tokens are stored in-memory only. A server restart invalidates all active sessions.
  • Refresh tokens are single-use: consuming a refresh token immediately revokes it and issues a new pair
  • Revoked JTI values are tracked in-memory to prevent replay attacks within a server session
  • Rate limiting: after 5 failed login attempts in a 300-second window, further attempts for that username are rejected

Forcing Logout
#

Revoke all tokens for a specific operator immediately (for compromised accounts or departing team members):

1
tantoc2> operators force-logout <operator-id>

API:

1
2
3
4
curl -X POST http://localhost:8443/api/v1/auth/force-logout \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"operator_id": "<id>"}'

Session Key Rotation
#

For long-running engagements, enable automatic session key rotation:

1
2
3
key_rotation_enabled: true
key_rotation_session_ttl: 3600    # rotate keys older than 1 hour
bg_key_rotation_interval: 300     # check every 5 minutes

When rotation is triggered, the server marks the session for re-key. On the agent’s next check-in, a new ECDH key exchange is performed transparently. The old session key is discarded.


Master Key Management
#

Every engagement is encrypted with a per-engagement master key derived from the engagement passphrase using PBKDF2-SHA256 (600,000 iterations, 16-byte random salt).

Cryptographic key hierarchy

Passphrase Requirements
#

  • The passphrase is never stored — it exists only in memory while the engagement is active
  • It must be re-entered each time the server restarts and the engagement is re-activated
  • If the passphrase is lost, the engagement data is unrecoverable
  • Use a high-entropy passphrase (random words via a password manager or openssl rand -base64 32)

Passphrase Storage Best Practices
#

  1. Store engagement passphrases in a team password manager (Bitwarden, 1Password, KeePass) separate from the server
  2. Do not store passphrases in environment variables or config files — they would be readable by anyone with server access
  3. Document which operator holds the passphrase for each engagement — if that person is unavailable, the engagement cannot be activated
  4. Consider using a shared vault entry with multiple admins having access

Salt Storage
#

The PBKDF2 salt is stored in the central database (data/tantoc2.db) alongside the engagement record. This is correct — the salt is not secret, it prevents precomputation attacks. The salt alone is useless without the passphrase.

Credential Encryption
#

Credential secrets collected during an engagement (passwords, NTLM hashes, SSH keys, API keys, etc.) are encrypted at rest using AES-256-GCM with the engagement master key. The plaintext secret is never stored — only the encrypted blob:

1
nonce (12 bytes) || ciphertext || GCM authentication tag

This means a compromised database file is not useful to an attacker without the engagement passphrase.


Network Segmentation
#

Recommended Topology#

Separate network access by function:

NetworkAccessible FromHosts
C2 network (operator)Operator workstations onlyTeamserver (API port :8443), CLI, Web UI
WAN / Internet-facingInternet (filtered by redirectors)Redirectors, listener ports
Target networksNo inbound from InternetAgents, target systems

See Deployment and IaC for a Docker Compose reference implementation of this topology.

Firewall Rules
#

Minimum firewall configuration for the teamserver host:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Allow operator API access only from known IP ranges
iptables -A INPUT -p tcp --dport 8443 -s 10.10.0.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 8443 -j DROP

# Allow listener ports only from the Internet (or specific CIDRs if known)
iptables -A INPUT -p tcp --dport 443 -j ACCEPT   # HTTPS listener
iptables -A INPUT -p tcp --dport 4444 -j ACCEPT  # TCP listener

# Block all other inbound
iptables -A INPUT -j DROP

For redirector setups, the teamserver listener ports only need to be reachable from the redirector IPs — not the open Internet.

Teamserver Isolation
#

The teamserver should not be directly reachable from the target network. Route all agent traffic through redirectors. This:

  • Hides the teamserver’s real address from target-side defenders
  • Allows redirectors to be sacrificed and replaced without exposing the server
  • Provides an additional layer for traffic filtering and logging

Operator Account Management
#

Role Assignment
#

Apply least-privilege:

ScenarioRecommended Role
Server admin, engagement setupadmin
Active red team operatoroperator
Client observer, QA reviewerspectator
Automated collection agentcollector

Create operators before the engagement starts. Document who has what role and which engagements they can access.

1
2
3
4
5
6
7
8
9
# Create an operator
curl -X POST http://localhost:8443/api/v1/operators/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"<strong-password>","role":"operator"}'

# Grant engagement access
curl -X POST http://localhost:8443/api/v1/operators/<operator-id>/engagements/<engagement-id> \
  -H "Authorization: Bearer $TOKEN"

Or via CLI:

1
2
tantoc2> operators create alice --role operator
tantoc2> operators grant <operator-id> <engagement-id>

Collector Role
#

The collector role starts with read-only access (same as spectator). Elevated permissions are granted dynamically with optional expiry and agent scoping:

1
2
3
4
5
6
7
POST /api/v1/collectors/grants
{
  "collector_id": "<collector-uuid>",
  "permission": "manage_agents",
  "agent_ids": ["<specific-agent-uuid>"],
  "expires_at": "2026-04-01T00:00:00Z"
}

Use this for automated tools that need temporary, scoped access. Revoke grants when no longer needed.

Password Policy
#

Enforce strong passwords:

  • Minimum 20 characters
  • No reuse across engagements
  • Changed after engagement completion (or immediately if suspected compromise)
  • Never communicated over unencrypted channels

Disabling Accounts
#

When an operator’s engagement access should be suspended without deleting their account:

1
2
3
4
curl -X PATCH http://localhost:8443/api/v1/operators/<id> \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'

Disabled accounts cannot log in. Force logout any existing sessions first.


Audit Log Monitoring
#

Every security-relevant action within an engagement is written to the per-engagement audit log. Security events (failed auth, force logout, privilege changes) also emit a security_alert WebSocket event for real-time notification.

What Is Logged
#

  • Operator login and logout
  • Failed authentication attempts
  • Engagement activation and deactivation
  • Operator creation, modification, deletion
  • Engagement access grants and revocations
  • Module executions
  • Credential access and export
  • Agent registration and status transitions
  • File transfers
  • Collector grant creation and revocation

Querying the Audit Log
#

CLI:

1
2
3
tantoc2> audit list
tantoc2> audit list --security-events
tantoc2> audit list --operator-id <id>

API:

1
2
3
4
5
6
7
# Security events only
curl "http://localhost:8443/api/v1/engagements/<id>/audit?is_security_event=true" \
  -H "Authorization: Bearer $TOKEN"

# Filter by action
curl "http://localhost:8443/api/v1/engagements/<id>/audit?action=login" \
  -H "Authorization: Bearer $TOKEN"

Retention
#

Audit log entries are stored in the per-engagement database. They are not moved to the archive table (unlike tasks). Back up the engagement database to retain the audit trail.

For long-term retention beyond the engagement lifecycle, export the audit log before archiving or deleting the engagement.

Monitoring Recommendations
#

Watch for these patterns during an engagement:

  • Multiple failed logins from the same source → brute force
  • Logins from unexpected IP addresses → possible credential theft
  • Bulk credential exports → insider threat
  • Unusual task volumes outside working hours → unplanned agent activity

Plugin Security
#

Plugins are Python wheel packages that run in the teamserver process with full Python access. A malicious plugin can do anything the server process can do.

Only Install Trusted Wheels
#

  • Use wheels built from your own source or from a distribution you control
  • Verify SHA-256 checksums against your distribution’s manifest before installing
  • Never install wheels from untrusted third parties into a production server

Plugin Inbox Permissions
#

The plugin_inbox_dir (default: <data_dir>/plugin_inbox) is polled by the server and auto-installs any .whl file dropped there. Restrict write access:

1
2
3
# Only the server admin (not the tantoc2 service user) should write here
chown root:tantoc2 /opt/tantoc2/plugin_inbox
chmod 730 /opt/tantoc2/plugin_inbox   # owner: rwx, group: -wx, others: ---

With this configuration, the tantoc2 service user can only execute (scan) the directory, not write to it. The admin account places wheels there explicitly.

Reviewing Plugin Code
#

Before deploying a third-party plugin:

  1. Unzip the wheel (it’s a zip file): unzip plugin.whl -d /tmp/plugin-review/
  2. Review all Python files — look for network calls, subprocess execution, file operations
  3. Check the entry points in *.dist-info/entry_points.txt
  4. Test in an isolated non-production environment first

Log Redaction
#

Enabled by default (log_redaction_enabled: true). The server filters sensitive values from all log output:

  • Credentials and secrets
  • Callback addresses (listener URLs)
  • Cryptographic keys and tokens
  • Session material

Disable only for debugging, and only on a non-production server:

1
export TANTOC2_LOG_REDACTION_ENABLED=false

Re-enable before any operational use. Log files with redaction disabled may contain cleartext credentials and key material.


Security Checklist Summary
#

ItemConfig / Command
TLS on operator APItls_enabled: true + cert/key files
TLS on agent-facing listenersper-listener --tls flag
Strong admin passwordChange immediately after first boot
Log redaction onlog_redaction_enabled: true (default)
Key rotation (long engagements)key_rotation_enabled: true
Firewall: API port restrictediptables / security group rules
Firewall: listeners not directly reachableRoute via redirectors
Plugin inbox write-restrictedchmod 730 plugin_inbox/
Engagement passphrase in secure vaultExternal password manager
Operator accounts with correct rolesLeast-privilege assignment
Force-logout for departing operatorsoperators force-logout
Audit log backed upInclude engagement DB in backups