Mining/AUDIT-INPUT-VALIDATION.md

205 lines
10 KiB
Markdown
Raw Permalink Normal View History

# Security Audit: Input Validation
This document outlines the findings of a security audit focused on input validation and sanitization within the mining application.
## Input Entry Points Inventory
### API Endpoints
The primary entry points for untrusted input are the API handlers defined in `pkg/mining/service.go`. The following handlers process user-controllable data from URL path parameters, query strings, and request bodies:
- **System & Miner Management:**
- `POST /miners/:miner_name/install`: `miner_name` from path.
- `DELETE /miners/:miner_name/uninstall`: `miner_name` from path.
- `DELETE /miners/:miner_name`: `miner_name` from path.
- `POST /miners/:miner_name/stdin`: `miner_name` from path and JSON body (`input`).
- **Statistics & History:**
- `GET /miners/:miner_name/stats`: `miner_name` from path.
- `GET /miners/:miner_name/hashrate-history`: `miner_name` from path.
- `GET /miners/:miner_name/logs`: `miner_name` from path.
- `GET /history/miners/:miner_name`: `miner_name` from path.
- `GET /history/miners/:miner_name/hashrate`: `miner_name` from path, `since` and `until` from query.
- **Profiles:**
- `POST /profiles`: JSON body (`MiningProfile`).
- `GET /profiles/:id`: `id` from path.
- `PUT /profiles/:id`: `id` from path and JSON body (`MiningProfile`).
- `DELETE /profiles/:id`: `id` from path.
- `POST /profiles/:id/start`: `id` from path.
### WebSocket Events
The WebSocket endpoint provides another significant entry point for untrusted input:
- **`GET /ws/events`**: Establishes a WebSocket connection. While the primary flow is server-to-client, the initial handshake and any client-to-server messages must be considered untrusted input. The `wsUpgrader` in `pkg/mining/service.go` has an origin check, which is a good security measure.
## Validation Gaps Found
The `Config.Validate()` method in `pkg/mining/mining.go` provides a solid baseline for input validation but has several gaps:
### Strengths
- **Core Fields Validated**: The most critical fields for command-line construction (`Pool`, `Wallet`, `Algo`, `CLIArgs`) have validation checks.
- **Denylist for Shell Characters**: The `containsShellChars` function attempts to block a wide range of characters that could be used for shell injection.
- **Range Checks**: Numeric fields like `Threads`, `Intensity`, and `DonateLevel` are correctly checked to ensure they fall within a sane range.
- **Allowlist for Algorithm**: The `isValidAlgo` function uses a strict allowlist for the `Algo` field, which is a security best practice.
### Weaknesses and Gaps
- **Incomplete Field Coverage**: A significant number of fields in the `Config` struct are not validated at all. An attacker could potentially abuse these fields if they are used in command-line arguments or other sensitive operations in the future. Unvalidated fields include:
- `Coin`
- `Password`
- `UserPass`
- `Proxy`
- `RigID`
- `LogFile` (potential for path traversal)
- `CPUAffinity`
- `Devices`
- Many others.
- **Denylist Approach**: The primary validation mechanism, `containsShellChars`, relies on a denylist of dangerous characters. This approach is inherently brittle because it is impossible to foresee all possible malicious inputs. A determined attacker might find ways to bypass the filter using alternative encodings or unlisted characters. An allowlist approach, accepting only known-good characters, is much safer.
- **No Path Traversal Protection**: The `LogFile` field is not validated. An attacker could provide a value like `../../../../etc/passwd` to attempt to write files in arbitrary locations on the filesystem.
- **Inconsistent Numeric Validation**: While some numeric fields are validated, others like `Retries`, `RetryPause`, `CPUPriority`, etc., are not checked for negative values or reasonable upper bounds.
## Injection Vectors Discovered
The primary injection vector discovered is through the `Config.CLIArgs` field, which is used to pass additional command-line arguments to the miner executables.
### XMRig Miner (`pkg/mining/xmrig_start.go`)
- **Unused in `xmrig_start.go`**: The `addCliArgs` function in `xmrig_start.go` does not actually use the `CLIArgs` field. It constructs arguments from other validated fields. This is good, but the presence of the field in the `Config` struct is misleading and could be used in the future, creating a vulnerability if not handled carefully.
### TT-Miner (`pkg/mining/ttminer_start.go`)
- **Direct Command Injection via `CLIArgs`**: The `addTTMinerCliArgs` function directly appends the contents of `Config.CLIArgs` to the command-line arguments. Although it uses a denylist-based `isValidCLIArg` function to filter out some dangerous characters, this approach is not foolproof.
- **Vulnerability**: An attacker can bypass the filter by crafting a malicious string that is not on the denylist but is still interpreted by the shell. For example, if a new shell feature or a different shell is used on the system, the denylist may become ineffective.
- **Example**: While the current filter blocks most common injection techniques, an attacker could still pass arguments that might cause unexpected behavior in the miner, such as `--algo some-exploitable-algo`, if the miner itself has vulnerabilities in how it parses certain arguments.
### Path Traversal in Config File Creation
- **Vulnerability**: The `getXMRigConfigPath` function in `xmrig.go` uses the `instanceName` to construct a config file path. The `instanceName` is derived from the user-provided `config.Algo`. While the `instanceNameRegex` in `manager.go` sanitizes the algorithm name, it still allows forward slashes (`/`).
- **Example**: If an attacker provides a crafted `algo` like `../../../../tmp/myconfig`, the `instanceNameRegex` will not sanitize it, and the application could write a config file to an arbitrary location. This could be used to overwrite critical files or place malicious configuration files in sensitive locations.
## Remediation Recommendations
To address the identified vulnerabilities, the following remediation actions are recommended:
### 1. Strengthen `Config.Validate()` with an Allowlist Approach
Instead of relying on a denylist of dangerous characters, the validation should be updated to use a strict allowlist of known-good characters for each field.
**Code Example (`pkg/mining/mining.go`):**
\`\`\`go
// isValidInput checks if a string contains only allowed characters.
// This should be used for fields like Wallet, Password, Pool, etc.
func isValidInput(s string, allowedChars string) bool {
for _, r := range s {
if !strings.ContainsRune(allowedChars, r) {
return false
}
}
return true
}
// In Config.Validate():
func (c *Config) Validate() error {
// Example for Wallet field
if c.Wallet != "" {
// Allow alphanumeric, plus common address characters like '-' and '_'
allowedChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
if !isValidInput(c.Wallet, allowedChars) {
return fmt.Errorf("wallet address contains invalid characters")
}
}
// Apply similar allowlist validation to all other string fields.
// ...
return nil
}
\`\`\`
### 2. Sanitize File Paths to Prevent Path Traversal
Sanitize any user-controllable input that is used to construct file paths. The `filepath.Clean` function and checks to ensure the path stays within an expected directory are essential.
**Code Example (`pkg/mining/manager.go`):**
\`\`\`go
import "path/filepath"
// In Manager.StartMiner():
// ...
instanceName := miner.GetName()
if config.Algo != "" {
// Sanitize algo to prevent directory traversal
sanitizedAlgo := instanceNameRegex.ReplaceAllString(config.Algo, "_")
// Also, explicitly remove any path-related characters that the regex might miss
sanitizedAlgo = strings.ReplaceAll(sanitizedAlgo, "/", "")
sanitizedAlgo = strings.ReplaceAll(sanitizedAlgo, "..", "")
instanceName = fmt.Sprintf("%s-%s", instanceName, sanitizedAlgo)
}
// ...
\`\`\`
### 3. Avoid Passing Raw CLI Arguments to `exec.Command`
The `CLIArgs` field is inherently dangerous. If it must be supported, it should be parsed and validated argument by argument, rather than being passed directly to the shell.
**Code Example (`pkg/mining/ttminer_start.go`):**
\`\`\`go
// In addTTMinerCliArgs():
func addTTMinerCliArgs(config *Config, args *[]string) {
if config.CLIArgs != "" {
// A safer approach is to define a list of allowed arguments
allowedArgs := map[string]bool{
"--list-devices": true,
"--no-watchdog": true,
// Add other safe, non-sensitive arguments here
}
extraArgs := strings.Fields(config.CLIArgs)
for _, arg := range extraArgs {
if allowedArgs[arg] {
*args = append(*args, arg)
} else {
logging.Warn("skipping potentially unsafe CLI argument", logging.Fields{"arg": arg})
}
}
}
}
\`\`\`
### 4. Expand Validation Coverage in `Config.Validate()`
All fields in the `Config` struct should have some form of validation. For string fields, this should be allowlist-based character validation. For numeric fields, this should be range checking.
**Code Example (`pkg/mining/mining.go`):**
\`\`\`go
// In Config.Validate():
// ...
// Example for LogFile
if c.LogFile != "" {
// Basic validation: ensure it's just a filename, not a path
if strings.Contains(c.LogFile, "/") || strings.Contains(c.LogFile, "\\") {
return fmt.Errorf("LogFile cannot be a path")
}
// Use an allowlist for the filename itself
allowedChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_."
if !isValidInput(c.LogFile, allowedChars) {
return fmt.Errorf("LogFile contains invalid characters")
}
}
// Example for CPUPriority
if c.CPUPriority < 0 || c.CPUPriority > 5 {
return fmt.Errorf("CPUPriority must be between 0 and 5")
}
// ...
\`\`\`