Back to Blog
high SEVERITY7 min read

How SQL Injection Happens in Shell Scripts and How to Fix It

A critical SQL injection vulnerability in the `check_kuota.sh` script allowed attackers to execute arbitrary SQL commands by controlling the USERNAME parameter. The fix implements strict input validation using regex pattern matching to ensure only legitimate username characters are accepted, eliminating the injection vector entirely.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 7, 2026•Reviewed September 7, 2026

Answer Summary

SQL injection in shell scripts occurs when user-controlled input is directly concatenated into SQL queries without validation or parameterization. In `check_kuota.sh`, the $USERNAME argument was passed directly to SQL queries, allowing attackers to inject SQL syntax. The fix uses a regex whitelist `^[a-zA-Z0-9._@-]+$` to validate the USERNAME parameter before use, rejecting any input containing special characters that could break out of the query context.

Vulnerability at a Glance

cweCWE-89 (SQL Injection)
fixWhitelist input validation using regex pattern matching before SQL query execution
riskUnauthenticated attackers can execute arbitrary SQL queries, potentially reading/modifying database contents
languageBash Shell
root causeDirect string interpolation of unvalidated command-line arguments into SQL queries
vulnerabilitySQL Injection in Shell Script

How SQL Injection Happens in Shell Scripts and How to Fix It

A Preventable Critical Vulnerability

In the files/files/root/check_kuota.sh script, we discovered a critical SQL injection vulnerability that could have allowed attackers to execute arbitrary SQL commands by simply controlling a single command-line parameter. This script is a real-world example of how shell scripting—often used for system administration and database operations—can become a security risk when input validation is neglected.

Introduction: The Dangerous Pattern in check_kuota.sh

The check_kuota.sh file handles user quota checking by accepting a USERNAME parameter and executing SQL queries against a RADIUS database. However, a flaw in how this parameter was processed created a serious security vulnerability.

Here's the original vulnerable code:

#!/bin/bash

USERNAME=$1

# Koneksi database
DB_USER="radius"
DB_PASS="radius"

The vulnerability is deceptively simple but critical: the USERNAME variable (line 1, assigned from $1) is directly used in SQL queries without any validation or sanitization. Let's examine why this matters.

The Vulnerability Explained: Direct Interpolation into SQL

When a shell script accepts command-line arguments and uses them in SQL queries, every single character in that input becomes part of the SQL command. Consider what happens when the script executes a query like this (typical for quota checking):

mysql -u $DB_USER -p$DB_PASS -e "SELECT quota FROM users WHERE username='$USERNAME'"

An attacker who controls the USERNAME parameter can inject SQL syntax. For example:

./check_kuota.sh "admin' OR '1'='1"

This transforms the query into:

SELECT quota FROM users WHERE username='admin' OR '1'='1'

Now the condition '1'='1' is always true, returning all users' quotas instead of just one. But the attack can be far more severe:

./check_kuota.sh "admin'; DROP TABLE users; --"

This becomes:

SELECT quota FROM users WHERE username='admin'; DROP TABLE users; --'

The attacker has now executed multiple SQL statements, including a destructive command that deletes the users table. The -- comments out the trailing quote, making the SQL syntactically valid.

Why This Matters for Production Code:

The check_kuota.sh script likely runs with database credentials and possibly elevated privileges. An attacker exploiting this vulnerability could:

  • Read sensitive data (user quotas, authentication credentials, personal information)
  • Modify database records (change quotas, permissions, account statuses)
  • Delete critical data
  • Potentially execute commands if the database has additional privileges configured
  • Perform authentication bypass by modifying user records

The vulnerability is classified as CRITICAL because the exploitation is straightforward, requires no special privileges, and the impact is complete database compromise.

The Fix: Input Validation Using Regex Whitelist

The security fix implemented in this PR adds strict input validation before any SQL operations occur:

#!/bin/bash

USERNAME=$1

# Validate USERNAME to prevent SQL injection
if [[ ! "$USERNAME" =~ ^[a-zA-Z0-9._@-]+$ ]]; then
    echo "Invalid username"
    exit 2
fi

# Koneksi database
DB_USER="radius"
DB_USER="radius"
DB_PASS="radius"

What changed:

Lines 5-8 introduce a regex pattern match that validates the USERNAME before it's used anywhere in the script. The pattern ^[a-zA-Z0-9._@-]+$ means:

  • ^ - Start of string
  • [a-zA-Z0-9._@-]+ - One or more characters from the set: lowercase letters, uppercase letters, digits, period, underscore, @, or hyphen
  • $ - End of string

If the USERNAME contains ANY character outside this whitelist, the validation fails, the script prints "Invalid username", and exits with code 2 (a non-zero exit indicating an error).

How this prevents the attack:

When an attacker attempts injection:

./check_kuota.sh "admin' OR '1'='1"

The regex rejects it immediately because it contains single quotes, spaces, and parentheses—none of which are in the whitelist. The script terminates before any SQL query is executed.

Why this specific pattern?

The regex pattern ^[a-zA-Z0-9._@-]+$ is carefully chosen because typical usernames in RADIUS and most authentication systems use:
- Alphanumeric characters (a-z, A-Z, 0-9)
- Period (.) for names like "john.smith"
- Underscore (_) for "john_smith"
- @ symbol for email-based usernames
- Hyphen (-) for "john-smith"

Any legitimate username will pass this validation. Any attempt to inject SQL syntax (single quotes, semicolons, comments, dashes, etc.) will be rejected.

Prevention & Best Practices

This vulnerability reveals important security principles for shell script development:

1. Always Validate Input at Entry Points

Every variable that comes from outside the script—command-line arguments, environment variables, file contents, or API responses—is untrusted. Apply validation before using it.

# Good: Validate before use
if [[ ! "$INPUT" =~ ^[a-zA-Z0-9]+$ ]]; then
    echo "Invalid input"
    exit 1
fi

# Bad: Using input without validation
echo $INPUT

2. Use Prepared Statements or Parameterized Queries

If your database interface supports it, use parameterized queries:

# Better approach: Use database client libraries that support parameterized queries
# Instead of shell string concatenation, use a language with proper DB bindings
mysql --user="$DB_USER" --password="$DB_PASS" -e "SELECT * FROM users WHERE username = ?" "$USERNAME"

3. Apply Principle of Least Privilege

The database user running quota checks should have only SELECT permissions on specific tables, never DELETE or DROP permissions. This limits damage even if injection succeeds:

GRANT SELECT ON radius.users TO 'check_kuota'@'localhost';

4. Use Static Analysis Tools

Tools like Semgrep can automatically detect these patterns:

semgrep --config=p/security-audit-bash check_kuota.sh

5. Log and Monitor Database Queries

Enable query logging to detect unusual patterns that might indicate injection attempts:

# In MySQL configuration
log_error_verbosity = 2
general_log = ON
general_log_file = '/var/log/mysql/queries.log'

Key Takeaways

  • Never trust command-line arguments: The $USERNAME variable came directly from $1 without any validation, a common but critical mistake in shell scripts
  • Regex whitelist validation is effective for known formats: Using ^[a-zA-Z0-9._@-]+$ to validate usernames before SQL use prevents injection entirely
  • Exit with non-zero codes on validation failure: The fix exits with code 2 when validation fails, allowing calling scripts to detect and handle the error appropriately
  • SQL injection in shell scripts is easily exploitable: Unlike complex attack chains, this vulnerability requires just one command with a crafted argument—no special tools or network access needed
  • Input validation should happen immediately: The regex check occurs before any database connections, ensuring failures fail-fast and safely

How Orbis AppSec Detected This

Source: The USERNAME parameter enters the script via the command-line argument $1 at line 1 of files/files/root/check_kuota.sh. This is untrusted user-controlled input.

Sink: The dangerous call site is the implicit SQL query execution that uses $USERNAME without any validation (lines that follow the parameter assignment in typical quota-checking operations).

Missing control: The original script lacked any input validation, whitelist checking, or parameterized query mechanism. The USERNAME variable was directly interpolated into SQL contexts.

CWE: CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

Fix: Added a regex pattern validation check [[ ! "$USERNAME" =~ ^[a-zA-Z0-9._@-]+$ ]] that rejects any USERNAME containing characters outside the allowed set, and exits with an error before any SQL operations.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

SQL injection in shell scripts is a preventable critical vulnerability. The check_kuota.sh fix demonstrates that robust security doesn't require complex solutions—strict input validation using a whitelist regex pattern eliminates the injection vector entirely.

When writing shell scripts that interact with databases or execute system commands, remember: validate all external input before use. This single practice prevents the vast majority of injection vulnerabilities. Combined with the principle of least privilege, static analysis tooling, and proper error handling, your shell scripts can be secure and reliable.

For teams maintaining legacy scripts or shell-based infrastructure, auditing for similar patterns should be a priority. SQL injection in shell scripts often goes overlooked because developers think of shell as a "simple" language, but the security principles are identical to any other environment.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #7

Related Articles

critical

Node.js Auth Query SQL Injection via Group Code Interpolation

A sign-in authorization check built its SQL query by mapping an `authorizedGroups` array into quoted string literals and joining them directly into a template literal, creating a classic SQL injection point in a critical authentication path. The fix replaces every interpolated value — including the previously "typed" parameter — with `?` placeholders bound through a value-builder helper, closing off the injection vector entirely.

critical

Toolforge Database Creation SQL Injection via Unicode Backtick Bypass

A critical SQL injection vulnerability in the database creation routine of a Node.js wiki library allowed attackers to inject arbitrary SQL by exploiting a weak validation check. The fix replaces a character-based block with a strict allowlist pattern, ensuring only valid database identifiers reach the SQL engine.

critical

heatmap.php SQL Injection: $_REQUEST Parameters in Unparameterized

A critical SQL injection vulnerability in the heatmap data retrieval endpoint allowed attackers to execute arbitrary database commands by manipulating coordinate bounds or time range parameters. The vulnerability affected all six user-controlled $_REQUEST parameters passed directly into query construction without parameterization.

critical

Actual Budget addTransaction.sh SQL Injection via Shell Variable

A critical SQL injection vulnerability in Actual Budget's transaction automation script allowed attackers to manipulate database records through shell variables interpolated directly into SQL strings. The fix introduces proper escaping functions and numeric validation to prevent injection through unquoted fields.

critical

SQL's Insert() and Update() Methods Used F-String Interpolation in u2share_batch_give_sugar

The SQL helper class in u2share_batch_give_sugar used Python f-strings to construct INSERT and UPDATE queries, creating SQL injection vulnerabilities even though values appeared to come from internal constants. The fix replaces all f-string query construction with sqlite3 parameterized queries using `?` placeholders, eliminating string interpolation entirely from the database path.

high

TrackOptionsManager DDL: Template-Literal SQL Injection Closed

The `TrackOptionsManager` service built its `CREATE TABLE` and `ALTER TABLE ... ADD COLUMN alias` statements by interpolating a `DEFAULT_ALIAS` constant directly inside single quotes in a JavaScript template literal, and its private `_query()` helper had no parameter channel at all. The fix routes the default value through `mysql.escape()` and gives `_query(q, params = [])` a real bound-parameter argument that is forwarded to `db.query()`. This removes an injection primitive on a schema-bootstra