Skip to content

Commit 8b291d9

Browse files
authored
applied catalog fixes and test (#68)
1 parent da2ffc7 commit 8b291d9

2 files changed

Lines changed: 41 additions & 10 deletions

File tree

sift-core/src/main/java/com/mirkoddd/sift/core/SiftCatalog.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,26 @@ public static SiftPattern<Fragment> uuid() {
6262
/**
6363
* Matches a valid IPv4 address.
6464
* <p>
65-
* Validates that each octet is strictly between 0 and 255.
66-
* Prevents matching invalid IPs like {@code 256.100.50.25}.
65+
* Validates that each octet is strictly between 0 and 255 and rejects octets with
66+
* leading zeros (e.g., {@code 01}, {@code 001}). This is intentional: several network
67+
* libraries interpret zero-padded octets as octal numbers, which is a known source of
68+
* IP-confusion / SSRF issues (see e.g. CVE-2021-29921).
6769
*
6870
* @return A SiftPattern representing an IPv4 address.
6971
*/
7072
public static SiftPattern<Fragment> ipv4() {
71-
// Octet logic: 25[0-5] OR 2[0-4][0-9] OR [01]?[0-9][0-9]?
73+
// Octet logic (no leading zeros): 25[0-5] | 2[0-4][0-9] | 1[0-9]{2} | [1-9][0-9] | [0-9]
7274
SiftPattern<Fragment> octet = anyOf(
7375
// 250-255
7476
Sift.fromAnywhere().character('2').followedBy('5').then().exactly(1).range('0', '5'),
7577
// 200-249
7678
Sift.fromAnywhere().character('2').then().exactly(1).range('0', '4').then().exactly(1).digits(),
77-
// 0-199
78-
Sift.fromAnywhere().optional().range('0', '1').then().exactly(1).digits().then().optional().digits()
79+
// 100-199
80+
Sift.fromAnywhere().character('1').then().exactly(2).digits(),
81+
// 10-99
82+
Sift.fromAnywhere().range('1', '9').then().exactly(1).digits(),
83+
// 0-9
84+
Sift.fromAnywhere().digits()
7985
);
8086

8187
return Sift.fromAnywhere()
@@ -220,12 +226,13 @@ public static SiftPattern<Fragment> isoDate() {
220226
* Matches a structural International Bank Account Number (IBAN).
221227
* <p>
222228
* <b>Important:</b> This pattern performs <i>syntactic</i> validation of the IBAN format.
223-
* It ensures the string matches the international standard shape:
229+
* It ensures the string matches the international standard shape (ISO 13616):
224230
* <ul>
225-
* <li>2 letters for the Country Code</li>
231+
* <li>2 <b>uppercase</b> letters for the Country Code</li>
226232
* <li>2 digits for the Check Digits</li>
227-
* <li>Between 11 and 30 alphanumeric characters for the Basic Bank Account Number (BBAN)</li>
233+
* <li>Between 11 and 30 <b>uppercase</b> alphanumeric characters for the Basic Bank Account Number (BBAN)</li>
228234
* </ul>
235+
* Lowercase input is rejected because ISO 13616 mandates uppercase representation.
229236
* <p>
230237
* It does <b>not</b> perform <i>semantic</i> validation, such as:
231238
* <ul>
@@ -240,9 +247,10 @@ public static SiftPattern<Fragment> isoDate() {
240247
*/
241248
public static SiftPattern<Fragment> iban() {
242249
return Sift.fromAnywhere()
243-
.exactly(2).letters() // Country Code
250+
.exactly(2).upperCaseLetters() // Country Code (ISO 13616: uppercase)
244251
.then().exactly(2).digits() // Check Digits
245-
.then().between(11, 30).alphanumeric() // Basic Bank Account Number
252+
.then().between(11, 30).range('A', 'Z')
253+
.including('0', '1', '2', '3', '4', '5', '6', '7', '8', '9') // BBAN
246254
.preventBacktracking();
247255
}
248256

sift-core/src/test/java/com/mirkoddd/sift/core/SiftCatalogTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,19 @@ void shouldValidateIpv4() {
122122
}
123123
}
124124

125+
@Test
126+
void shouldRejectIpv4WithLeadingZeros() {
127+
// Leading-zero octets are ambiguous: several libraries parse them as octal,
128+
// which is a known IP-confusion / SSRF vector (cfr. CVE-2021-29921).
129+
try (SiftCompiledPattern ipv4Pattern = SiftCatalog.ipv4().sieve()) {
130+
assertFalse(ipv4Pattern.matchesEntire("01.2.3.4"), "Should reject 01 (leading zero, two-digit)");
131+
assertFalse(ipv4Pattern.matchesEntire("192.168.001.1"), "Should reject 001 (leading zeros, three-digit)");
132+
assertFalse(ipv4Pattern.matchesEntire("010.10.10.10"), "Should reject 010 (octal-like)");
133+
assertFalse(ipv4Pattern.matchesEntire("00.0.0.0"), "Should reject 00 (double zero)");
134+
assertFalse(ipv4Pattern.matchesEntire("127.0.0.01"), "Should reject leading zero in last octet");
135+
}
136+
}
137+
125138
// -------------------------------------------------------------------------
126139
// MAC Address
127140
// -------------------------------------------------------------------------
@@ -374,6 +387,16 @@ void shouldValidateIban() {
374387
}
375388
}
376389

390+
@Test
391+
void shouldRejectLowercaseIban() {
392+
// ISO 13616 mandates uppercase representation for both Country Code and BBAN.
393+
try (SiftCompiledPattern ibanPattern = SiftCatalog.iban().sieve()) {
394+
assertFalse(ibanPattern.matchesEntire("it60X0542811101000000123456"), "Should reject lowercase Country Code");
395+
assertFalse(ibanPattern.matchesEntire("de89370400440532013000"), "Should reject fully lowercase IBAN");
396+
assertFalse(ibanPattern.matchesEntire("IT60x0542811101000000123456"), "Should reject lowercase letter in BBAN");
397+
}
398+
}
399+
377400
// -------------------------------------------------------------------------
378401
// JWT
379402
// -------------------------------------------------------------------------

0 commit comments

Comments
 (0)