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
Published September 7, 2026Reviewed 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.


References

  • CWE-89: SQL Injection — https://cwe.mitre.org/data/definitions/89.html
  • OWASP SQL Injection — https://owasp.org/www-community/attacks/SQL_Injection
  • OWASP Input Validation Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
  • Bash Parameter Expansion and Security — https://www.gnu.org/software/bash/manual/html_node/Parameter-Expansion.html
  • Semgrep Rule: SQL Injection in Shell — https://semgrep.dev/r?q=sql-injection
  • GitHub PRfix: the check_kuota in check_kuota.sh

Frequently Asked Questions

What is SQL injection in shell scripts?

SQL injection occurs when unsanitized user input is concatenated directly into SQL query strings, allowing attackers to inject SQL syntax that breaks out of the intended query and executes malicious commands against the database.

How do you prevent SQL injection in shell scripts?

Validate all input against a strict whitelist of allowed characters before using it in SQL queries, use prepared statements if your SQL interface supports them, or better yet, use language-specific database libraries that handle escaping automatically instead of shell scripts.

What CWE is SQL injection?

SQL injection is CWE-89, classified as "SQL Injection" in the Common Weakness Enumeration. It's a fundamental injection flaw affecting databases across all programming languages.

Is output encoding enough to prevent SQL injection?

No. Output encoding helps prevent XSS but does not prevent SQL injection. SQL injection requires input validation or prepared statements at the database layer, not output-time encoding.

Can static analysis detect SQL injection in shell scripts?

Yes. Static analysis tools like Semgrep and commercial security scanners can detect patterns where variables are directly interpolated into SQL strings without validation. The Orbis AppSec multi_agent_ai scanner detected this exact issue.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #7

Related Articles

critical

How SQL Injection happens in PHP bulk email systems and how to fix it

A critical SQL injection vulnerability in `admin/utilities/bulkEmailSystem.php` allowed attackers to inject arbitrary SQL through unvalidated database names passed from user input. The fix implements strict input validation using regex pattern matching to ensure only safe database identifiers are processed, preventing exploitation of the bulk email functionality.

critical

How SQL Injection happens in JavaScript template literals and how to fix it

A critical SQL injection vulnerability in `index.js` allowed attackers to execute arbitrary database commands by manipulating block IDs passed through the UI. The fix implements strict input validation using a regex whitelist before any SQL construction, eliminating the injection vector while preserving functionality.

high

How SQL Injection via Template Literals happens in TypeScript and how to fix it

A high-severity SQL injection vulnerability was discovered in the admin panel's database tools where schema names were directly interpolated into SQL queries using JavaScript template literals. The fix replaced unsafe string concatenation with a proper `quoteSchemaLiteral()` function to sanitize inputs before query construction, eliminating the injection vector in two critical database inspection functions.

high

How utils.custom.sql-injection-template-literal happens in JavaScript and how to fix it

A high-severity SQL injection vulnerability was discovered in `CrewRouter-Desktop/src/server-manager.js` at line 266, where a SQL query was constructed using JavaScript template literals with dynamic input. This pattern allows remote attackers to inject arbitrary SQL commands through the web service's request handlers. The fix replaces the unsafe template literal interpolation with parameterized queries, eliminating the injection vector entirely.

high

How SQL Injection via Template Literals happens in Node.js and how to fix it

A high-severity SQL injection vulnerability was discovered in server-agents/common/src/search/schema.ts where the `insertRowsBatch` function constructed SQL queries using JavaScript template literals with dynamic input. The fix replaced the vulnerable `db.exec()` call with parameterized queries using `db.query().run()`, eliminating the injection risk in the full-text search merge operation.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.