Compare commits

...
Author SHA1 Message Date
Ahmed Allam 685bbc8138 chore: release v1.7.1 2026-11-10 23:12:11 +02:00
theo-at-strix cc28951c7f feat(skills): detect query-language and JSON parser differentials (#1720)
* feat(skills): detect query and JSON parser differentials

* fix(skills): preserve NoSQL error probing guidance

* refactor(skills): focus parser differential guidance
2026-11-10 13:54:53 -05:00
Alex Schapiro 9798ecc404 fix(image): pin katana source to v1.8.0, install gitleaks once 2026-11-10 19:38:02 +02:00
Alex Schapiro 48d93cd3d9 fix(sandbox): rebuild Go tools with patched dependencies 2026-11-10 19:38:02 +02:00
9 changed files with 166 additions and 21 deletions

No files matched your search

+10 -13
View File
@@ -11,14 +11,22 @@ RUN apt-get update && \
apt-get install -y --no-install-recommends golang-go git ca-certificates
ENV GOBIN=/out/bin
ENV CGO_ENABLED=0
RUN mkdir -p /out/bin && \
go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest && \
go install -v github.com/projectdiscovery/katana/cmd/katana@latest && \
go install -v github.com/projectdiscovery/cvemap/cmd/vulnx@latest && \
go install -v github.com/jaeles-project/gospider@latest && \
go install -v github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest && \
go install -v golang.org/x/vuln/cmd/govulncheck@latest
RUN git clone --depth 1 --branch v1.8.0 https://github.com/projectdiscovery/katana.git /tmp/katana && \
cd /tmp/katana && \
go get github.com/jackc/pgx/[email protected] && \
go mod tidy && \
go build -o /out/bin/katana ./cmd/katana
RUN GOBIN=/out/gitleaks go install -v -ldflags "-X github.com/zricethezav/gitleaks/v8/version.Version=8.30.1" github.com/zricethezav/gitleaks/[email protected]
# ---------------------------------------------------------------------------
# Runtime stage
# ---------------------------------------------------------------------------
@@ -179,18 +187,7 @@ USER root
ARG TRUFFLEHOG_VERSION=3.99.2
RUN curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /home/pentester/.local/bin "v${TRUFFLEHOG_VERSION}" && \
chown -R pentester:pentester /home/pentester/.local
RUN set -eux; \
ARCH="$(uname -m)"; \
case "$ARCH" in \
x86_64) GITLEAKS_ARCH="x64" ;; \
aarch64|arm64) GITLEAKS_ARCH="arm64" ;; \
*) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;; \
esac; \
TAG="$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/gitleaks/gitleaks/releases/latest | sed 's#.*/##')"; \
curl -fsSL "https://github.com/gitleaks/gitleaks/releases/download/${TAG}/gitleaks_${TAG#v}_linux_${GITLEAKS_ARCH}.tar.gz" -o /tmp/gitleaks.tgz; \
tar -xzf /tmp/gitleaks.tgz -C /tmp; \
install -m 0755 /tmp/gitleaks /usr/local/bin/gitleaks; \
rm -f /tmp/gitleaks /tmp/gitleaks.tgz
COPY --from=gobuilder /out/gitleaks/gitleaks /usr/local/bin/gitleaks
ARG TRIVY_VERSION=0.76.1
ARG TRIVY_SHA256_X86=c7cbe3137fb8a48a17574bede3dd6beb16742d1e9772929940fc90082c39d9c0
+1 -1
View File
@@ -186,7 +186,7 @@ When remote vars are set, Strix dual-writes telemetry to both local JSONL and th
## Docker Configuration
<ParamField path="STRIX_IMAGE" default="ghcr.io/usestrix/strix-sandbox:1.4.0" type="string">
<ParamField path="STRIX_IMAGE" default="ghcr.io/usestrix/strix-sandbox:1.4.1" type="string">
Docker image to use for the sandbox container.
</ParamField>
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "strix-agent"
version = "1.7.0"
version = "1.7.1"
description = "Open-source AI Hackers for your apps"
readme = "README.md"
license = "Apache-2.0"
+1 -1
View File
@@ -4,7 +4,7 @@ set -euo pipefail
APP=strix
REPO="usestrix/strix"
STRIX_IMAGE="ghcr.io/usestrix/strix-sandbox:1.4.0"
STRIX_IMAGE="ghcr.io/usestrix/strix-sandbox:1.4.1"
MUTED='\033[0;2m'
RED='\033[0;31m'
+1 -1
View File
@@ -164,7 +164,7 @@ class RuntimeSettings(BaseSettings):
model_config = _BASE_CONFIG
image: str = Field(
default="ghcr.io/usestrix/strix-sandbox:1.4.0",
default="ghcr.io/usestrix/strix-sandbox:1.4.1",
alias="STRIX_IMAGE",
)
backend: str = Field(default="docker", alias="STRIX_RUNTIME_BACKEND")
+4 -2
View File
@@ -53,11 +53,13 @@ Object-level authorization failures (BOLA/IDOR) lead to cross-account data expos
**Enumeration Techniques**
- Alternate types: `{"id":123}` vs `{"id":"123"}`, arrays vs scalars, objects vs scalars
- Edge values: null/empty/0/-1/MAX_INT, scientific notation, overflows
- Duplicate keys/parameter pollution: `id=1&id=2`, JSON duplicate keys `{"id":1,"id":2}` (parser precedence)
- Edge and numeric boundaries: null, empty, 0, -1, MAX_INT, and exact and adjacent values around each runtime's safe-integer and overflow limits, including decimal and exponent forms
- Duplicate keys/parameter pollution: send raw query strings such as `id=1&id=2` and raw JSON such as `{"id":1,"id":2}` so serializers cannot collapse duplicates. Use conflicting values in both orders to determine parser precedence
- Case/aliasing: userId vs userid vs USER_ID; alt names like resourceId, targetId, account
- Path traversal-like in virtual file systems: `/files/user_123/../../user_456/report.csv`
For gateways, workers, and multi-service APIs, treat authorization as an identity invariant: the object authorized must equal the object read, changed, or exported. Run the representation and duplicate-member matrix even after one variant succeeds. Compare response identity/content, not status alone.
**UUID/Opaque ID Sources**
- Logs, exports, JS bundles, analytics endpoints, emails, public activity
- Time-based IDs (UUIDv1, ULID) may be guessable within a window
@@ -14,6 +14,7 @@ NoSQL injection exploits the mismatch between how applications pass user input t
- Form fields with bracket notation (`field[$ne]=`) coerced into operator objects by Express, PHP, and similar middleware
- URL-encoded JSON in query strings, headers, and cookies
- GraphQL variables passed directly into resolver-level NoSQL filters
- Scalar search, filter, selector, and authorization strings may be compiled into another expression language instead of a NoSQL operator object. Before testing or closing any such field, load `query_language_injection` and run its Required First Pass on every scalar sink
**Code patterns that enable injection**
- Raw filter dicts/objects from user input handed to `find`/`findOne`/`aggregate`
@@ -241,7 +242,7 @@ DoS surface (use only with explicit authorization scope):
1. **Identify query-receiving endpoints** — login, search, filter, lookup
2. **Determine input format** — JSON body vs form fields vs URL params
3. **Send error-probing payloads** — malformed operator objects; watch for MongoDB/driver errors
3. **Send error-probing payloads** — test malformed operator objects and watch for MongoDB/driver errors. For scalar expression construction, load `query_language_injection` and complete its Required First Pass before closing the field
4. **Attempt operator injection** — `$ne`, `$gt`, `$regex` against login endpoint
5. **Confirm boolean oracle** — response, status, redirect differs between true/false predicates
6. **Extract data blindly** — character-by-character `$regex` on sensitive fields (token, reset code)
@@ -0,0 +1,145 @@
---
name: query-language-injection
description: Query-language injection testing for scalar search, filter, selector, and authorization inputs compiled into JSONPath, JMESPath, OData, Lucene, LDAP, Cypher, XPath, or similar expressions. Requires a per-sink four-cell balanced true/false matrix before generic spraying or no-issue closure
---
# Query-Language Injection
An HTTP scalar becomes an injection sink when an application inserts it into a query, selector, policy, or expression string. The input may look like an exact-match value at the API boundary while becoming executable syntax in a downstream parser. Treat each accepted search, filter, selector, and authorization field as an independent sink.
## Non-Skippable First Pass
Make this the first test after baselines. Do not begin generic SQL, NoSQL, wildcard, metacharacter, or product-specific spraying until every candidate scalar has a completed row in the execution ledger.
For each scalar search, filter, selector, and authorization input:
1. Record its transport path, a full known-match value `V`, a known-no-match value, and the corresponding record property.
2. Map transport structure to record structure. For a nested request path such as `filters.status`, start with the leaf property `@.status`, not `@.filters.status`, unless responses or documentation show that the record itself is nested. For a free-text input, test each record property it searches.
3. Keep authentication, sibling fields, method, pagination, and ordering fixed.
4. Put each of these four values in the selected input itself:
- `V' || @.<property> == @.<property> || 'V`
- `V' && @.<property> != @.<property> && 'V`
- `V" || @.<property> == @.<property> || "V`
- `V" && @.<property> != @.<property> && "V`
5. Compare returned object identities. A structural injection normally makes the `OR/true` cells broaden and the `AND/false` cells narrow.
6. Record the transport path, record property, known value, all four exact transmitted values, status, result count, and returned identities.
Substitute every placeholder before transmission. For the neutral example `filters.status=active`, the record property is `status`, so send exactly:
- `active' || @.status == @.status || 'active`
- `active' && @.status != @.status && 'active`
- `active" || @.status == @.status || "active`
- `active" && @.status != @.status && "active`
Use the selected input's own accepted value at both ends. Do not send literal `V`, `@.<property>`, a bare closure fragment, or a payload in a free-text sibling. Repeat the matrix independently for every scalar, even when another field is literal or inert.
A ledger row missing any cell is incomplete. Do not substitute standalone quotes, SQL tautologies, object operators, wildcards, or generic search tokens. Do not close the input as exact-match, safe, or `no issue` unless the row is complete or a specific control proves its value cannot reach expression construction. Otherwise retain an open proof gap.
## Attack Surface
Prioritize inputs that influence:
- search, filtering, sorting, faceting, lookup, and autocomplete
- tenant, owner, role, classification, visibility, or policy selectors
- exports, reports, saved searches, dashboards, and background jobs
- document, graph, directory, or inventory queries
- path-like selectors, projections, field expressions, and rule conditions
Test query parameters, JSON and form fields, headers, cookies, GraphQL arguments, WebSocket messages, and values copied into asynchronous work. A client-side dropdown or fixed option list is not a server-side control.
Common sinks include JSONPath, JMESPath, XPath, LDAP filters, OData, Lucene-style search, Cypher, expression engines, and application-specific filter DSLs. Do not assume the grammar from the endpoint name alone. Infer it from errors, response behavior, client code, documentation, or source when available.
## Core Model
Map the complete path before selecting payloads:
```text
attacker-controlled scalar
-> decoding or normalization
-> string interpolation or expression construction
-> query-language parser
-> evaluator
-> authorization or result handling
```
The key question is whether the value stays data or changes the structure of the final expression. A protected query is vulnerable when attacker input can close its original value context, introduce an operator or expression, and rebalance the remaining suffix.
## Reconnaissance
For every candidate field:
1. Record a known-match value and a known-no-match value.
2. Keep method, authentication, sibling fields, pagination, and ordering fixed.
3. Compare returned object identities and authorization boundaries, not only status or response length.
4. Repeat requests when data, timing, or ordering can vary.
5. Preserve the raw request. High-level serializers may quote, normalize, or collapse the representation being tested.
Test the field itself. A payload in a sibling field does not clear the original sink. Standalone quotes or metacharacters are discovery signals, not sufficient negative tests.
## Grammar Adaptation
After completing the first pass, use observed behavior to translate the same broadening and narrowing pair rather than spraying unrelated payloads. Balance the expected suffix instead of relying only on comments. Constant-only expressions may be rejected while field comparisons remain valid.
| Language family | Useful structural clues | Paired strategy |
|---|---|---|
| JSONPath | `$`, `@`, filters, brackets, quote-delimited comparisons | close the comparison, add true and false predicates, then rebalance |
| JMESPath | filters, projections, backtick literals, pipe expressions | alter a filter or projection and compare broadened with empty results |
| XPath | path steps, predicates, `and`, `or`, quote-delimited values | close the literal and compare true with false node-set predicates |
| LDAP | parenthesized filters, `&`, `|`, `!`, wildcards | inject balanced filter groups and compare match-all with match-none controls |
| OData | `$filter`, `eq`, `ne`, `and`, `or`, single-quoted strings | close the literal and compare true with false clauses using valid escaping |
| Lucene-like DSLs | field clauses, quotes, grouping, boolean operators | escape the term context and compare broad with contradictory clauses |
| Cypher-like expressions | quoted values, `WHERE`, boolean operators, comments | close the value and compare tautological with contradictory predicates |
Also test plausible value contexts such as strings, numbers, lists, paths, function arguments, and nested predicates. Change one structural assumption at a time. Use encoded forms only after a raw structural probe establishes a likely grammar or intermediary filter.
## Differential Oracles
High-signal evidence includes:
- the broadening probe returns objects absent from the known-match baseline
- the narrowing control returns no objects or a strict subset
- protected, cross-tenant, privileged, or hidden records appear
- error shape changes reveal the downstream parser or expression context
- the same predicate controls an existence, count, timing, or authorization oracle
An empty result can mean either a false predicate or a swallowed parser exception. Distinguish them with a syntactically valid false control, repeated requests, and a second predicate pair. A parser error alone proves input reached a grammar, not that the query can be controlled.
## Authorization Filters
Treat query injection as an authorization issue when the vulnerable predicate enforces tenant, owner, role, classification, or visibility boundaries. Prove that the injected request returns an object the same principal cannot obtain through the normal selector. Record the object identity and protected property so intended broad search behavior is not mistaken for a bypass.
Do not stop after showing that the result count changed. Determine whether the injected expression can read protected data, influence an export, modify a selected object, or reach a more privileged downstream operation.
## Validation
A confirmed finding should include:
1. A normal known-match baseline.
2. A normal known-no-match baseline.
3. A broadening expression with a stable unauthorized result.
4. A paired narrowing or contradictory expression.
5. Repetition showing the differential is deterministic.
6. Evidence of the affected objects, boundary, and business impact.
Keep sibling fields and credentials constant across the evidence pair. Prefer object identifiers or normalized response digests over raw body length. If only an error or response-shape anomaly is available, retain it as a proof gap rather than reporting data access that was not demonstrated.
## False Positives
- intended wildcard, fuzzy search, or documented advanced-query syntax
- changing datasets, pagination, ranking, caching, or unstable ordering
- generic validation errors that occur before the expression parser
- a quote accepted as literal data without control over query structure
- a broad result caused by an omitted filter rather than injected syntax
- an empty response caused by a swallowed exception without a valid predicate oracle
## Remediation
- Avoid building expressions through string concatenation.
- Use typed parameters, variables, or a safe AST/query-builder API when supported.
- Map user-facing options to server-owned predicates instead of accepting expression fragments.
- Apply strict allowlists for field names, operators, functions, and value types when dynamic queries are required.
- Enforce authorization outside attacker-controlled search expressions and re-check every resolved object before returning or acting on it.
- Preserve one canonical parse and representation across gateways, workers, and backend services.
- Add regression tests using paired true and false predicates for every previously vulnerable field.
Do not conclude `exact match`, `safe`, or `ruled out` until the paired matrix ran on every candidate scalar sink, or a specific non-concatenating control was identified.
Generated
+1 -1
View File
@@ -2432,7 +2432,7 @@ wheels = [
[[package]]
name = "strix-agent"
version = "1.7.0"
version = "1.7.1"
source = { editable = "." }
dependencies = [
{ name = "boto3" },