diff --git a/java/indexing/unchecked-split-index.yaml b/java/indexing/unchecked-split-index.yaml new file mode 100644 index 0000000..e2262d1 --- /dev/null +++ b/java/indexing/unchecked-split-index.yaml @@ -0,0 +1,77 @@ +rules: + - id: codevigilant.java.indexing.unchecked-split-index + mode: search + severity: MEDIUM + message: >- + A String.split(...) result is indexed at a positive literal offset + ($N) without first verifying the split-array length - either directly on + the split call, or through a local variable that holds the split result. + If the input does not contain enough separator-delimited segments (e.g. a + single-line payload split by a line-break regex, a structured record with + fewer fields than the code assumes, or a value with fewer + comma-separated fields than expected), the access throws an uncaught + ArrayIndexOutOfBoundsException. When the split input is even partially + attacker-influenced (record/field values parsed out of a structured + message, file contents, log/console lines, request data), this is an + uncaught-exception denial-of-service (CWE-248 / CWE-754) that aborts the + enclosing build, request, or - when the code runs in a polling, listener + or callback thread - the whole worker loop. Check the array length before + indexing (and validate each field's format before parsing it), so + malformed input degrades gracefully instead of throwing. + languages: + - java + patterns: + - pattern-either: + - pattern: $S.split($SEP)[$N] + - pattern: $S.split($SEP, $LIMIT)[$N] + - pattern: $S.split($SEP)[$N].$METHOD($ARGS) + - pattern: $S.split($SEP, $LIMIT)[$N].$METHOD($ARGS) + - patterns: + - pattern-inside: | + $ARR = $S.split($SEP, $LIMIT); + ... + - pattern: $ARR[$N] + - pattern-not-inside: | + if ($ARR.length < $M) { + ... + } + ... + - pattern-not-inside: | + if ($ARR.length <= $M) { + ... + } + ... + - patterns: + - pattern-inside: | + $ARR = $S.split($SEP); + ... + - pattern: $ARR[$N] + - pattern-not-inside: | + if ($ARR.length < $M) { + ... + } + ... + - pattern-not-inside: | + if ($ARR.length <= $M) { + ... + } + ... + - metavariable-regex: + metavariable: $N + regex: '[1-9][0-9]*' + metadata: + cwe: + - "CWE-248: Uncaught Exception" + - "CWE-754: Improper Check for Unusual or Exceptional Conditions" + owasp: + - "A05:2021 - Security Misconfiguration" + technology: + - java + confidence: MEDIUM + category: security + references: + - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ArrayIndexOutOfBoundsException.html + - https://owasp.org/www-community/vulnerabilities/Denial_of_Service + source: + - semgrep-rule-gap + license: MIT diff --git a/testcases/java/indexing/unchecked-split-index-neg.java b/testcases/java/indexing/unchecked-split-index-neg.java new file mode 100644 index 0000000..803a647 --- /dev/null +++ b/testcases/java/indexing/unchecked-split-index-neg.java @@ -0,0 +1,14 @@ +public class UncheckedSplitIndexNeg { + // guarded: length checked before indexing, index 0 only + public String guarded(String csvResults) { + String[] lines = csvResults.split("[\\r\\n]+"); + if (lines.length < 2) { + return ""; + } + String[] fields = lines[1].split(","); + if (fields.length >= 4) { + return fields[0].trim(); + } + return ""; + } +} \ No newline at end of file diff --git a/testcases/java/indexing/unchecked-split-index-pos.java b/testcases/java/indexing/unchecked-split-index-pos.java new file mode 100644 index 0000000..ae7bf76 --- /dev/null +++ b/testcases/java/indexing/unchecked-split-index-pos.java @@ -0,0 +1,20 @@ +public class UncheckedSplitIndexPos { + // a single-line file: no second line -> split("[\\r\\n]+")[1] throws AIOOBE + public String parseResults(String csvResults) { + return csvResults.split("[\\r\\n]+")[1]; + } + + public int field(String row) { + return Integer.parseInt(row.split(",")[2].trim()); + } + + public String fieldChained(String row) { + return row.split(",")[3].toUpperCase(); + } + + // split result stored in a local array, then indexed without a length check + public String twoStep(String arn, String host, String path) { + String[] tokens = arn.split(":", 6); + return String.format(host, tokens[3]) + String.format(path, tokens[5]); + } +} \ No newline at end of file