T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/qlik-search.sh:21
- Finding
- Arbitrary Python Code Execution Through Unsafe Argument Interpolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qlik-search.sh:21-32` **Vulnerability Type**: Python code injection **Risk Level**: High ### Vulnerable Code ```bash TENANT="${QLIK_TENANT%/}" [[ "$TENANT" != http* ]] && TENANT="https://$TENANT" ENCODED_QUERY=$(python3 -c "import urllib.parse; print(urllib.parse.quote('''$QUERY'''))") curl -sL \ -H "Authorization: Bearer ${QLIK_API_KEY}" \ -H "Content-Type: application/json" \ "${TENANT}/api/v1/items?query=${ENCODED_QUERY}&limit=50" | python3 -c " import json import sys query = '''$QUERY''' timestamp = '$TIMESTAMP' ``` The same unsafe construction is also present in other scripts, including: - `scripts/qlik-answers-ask.sh:57-64` - `scripts/qlik-lineage.sh:110,122-132` - `scripts/qlik-users-search.sh:26-28,37-42` - Multiple scripts that insert identifiers directly into double-quoted `python3 -c` programs ### Technical Analysis The script constructs Python source code by directly substituting the user-controlled `QUERY` shell variable into triple-quoted Python literals. Shell quoting does not provide Python-language escaping. An input containing a terminating triple quote can escape the intended string literal and add arbitrary Python statements. The value is interpolated twice: once while URL-encoding the query and again while formatting the response. Exploitation can therefore occur before the HTTP request is sent. For example, a query shaped like the following breaks out of the literal: ```text x'''); print("INJECTED"); # ``` The first Python command becomes structurally equivalent to: ```python import urllib.parse print(urllib.parse.quote('''x''')) print("INJECTED") #''')) ``` An attacker can replace the harmless `print` operation with calls such as `__import__("os").system(...)`, file operations, or network operations. The resulting code runs with the same operating-system identity and environment as the Skill. In `qlik-lineage.sh`, the vulnerable `QRI` value may also origi ...[truncated 1450 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Never construct Python source from shell variables. Pass data through environment variables, standard input, or positional arguments. For example: ```bash ENCODED_QUERY=$( QUERY="$QUERY" python3 -c ' import os import urllib.parse print(urllib.parse.quote(os.environ["QUERY"])) ' ) ``` Use the same approach for response formatting: ```bash curl ... | QUERY="$QUERY" TIMESTAMP="$TIMESTAMP" python3 -c ' import json import os import sys query = os.environ["QUERY"] timestamp = os.environ["TIMESTAMP"] data = json.load(sys.stdin) # Process data without generating Python source. ' ``` Apply this correction to every interpolated value in every `python3 -c` block, including identifiers, QRI values, directions, levels, response bodies, and questions. Additional hardening should include: 1. Validate UUID arguments against a strict UUID expression before use. 2. Accept only `upstream`, `downstream`, or `both` for lineage direction. 3. Parse limits and levels as bounded integers in shell before invoking Python or `curl`. 4. Use `urllib.parse.urlencode` or `curl --get --data-urlencode` for query parameters. 5. Add regression tests containing quotes, triple quotes, newlines, backslashes, and Python syntax. 6. Avoid embedding API responses in Python source; provide them through standard input. ]]>
