rzo1 opened a new issue, #2080:
URL: https://github.com/apache/stormcrawler/issues/2080

   
   ## What happens
   The connection-time IP filter is the only check that looks at the address 
the fetcher actually connects to, and both of its keys ship commented out in 
`crawler-default.yaml`. `HttpProtocol` installs the interceptor only when 
`IPFilterRules.isEmpty()` is false, so at defaults it is never installed. What 
remains is the regex exclusion list in the archetypes, which matches literal 
`localhost`, dotted-quad 127/8, 10/8, 192.168/16, 172.16/12 and `[::1]` and 
nothing else. Link-local, CGNAT, IPv6 unique-local and the abbreviated and 
integer IPv4 host forms that the JVM resolver still maps to 127.0.0.1 all pass 
it.
   
   ## Where
   `core/src/main/resources/crawler-default.yaml:161-162`, under the comment 
block at `:150-160`, keys `http.filter.ipaddress.include` and 
`http.filter.ipaddress.exclude`:
   
   ```yaml
     # http.filter.ipaddress.include:
     # http.filter.ipaddress.exclude: "localhost,sitelocal"
   ```
   
   
`core/src/main/java/org/apache/stormcrawler/protocol/okhttp/HttpProtocol.java:234-237`:
   
   ```java
   final IPFilterRules ipFilterRules = new IPFilterRules(conf);
   if (!ipFilterRules.isEmpty()) {
       builder.addNetworkInterceptor(new 
HTTPFilterIPAddressInterceptor(ipFilterRules));
   }
   ```
   
   The regex rules are at 
`archetype/src/main/resources/archetype-resources/src/main/resources/default-regex-filters.txt:21-29`,
 byte-identical in the opensearch and solr archetypes. The comment above them 
states the intent: to stop faked links leaking information from services 
running on the crawling machine.
   
   ## Why it matters
   A crawled page decides which hosts the fetcher connects to, and the fetched 
body goes on to be parsed and indexed. A string-level rule cannot enforce this 
at all, since it never sees where a host name resolves to, and the list of 
literal forms it does cover has gaps that need no DNS control. On a cloud 
worker the link-local range is reachable and unauthenticated. The regex list is 
also the only defence in the archetypes, and `urlfilters.config.file` itself 
ships commented out in `crawler-default.yaml:243`, so a topology built from the 
library rather than an archetype has no URL filtering at all.
   
   ## Reproduction
   
   Save as 
`core/src/test/java/org/apache/stormcrawler/filtering/DefaultRegexFiltersPrivateRangeTest.java`.
   
   ```java
   /*
    * Licensed to the Apache Software Foundation (ASF) under one or more
    * contributor license agreements.  See the NOTICE file distributed with
    * this work for additional information regarding copyright ownership.
    * The ASF licenses this file to you under the Apache License, Version 2.0
    * (the "License"); you may not use this file except in compliance with
    * the License.  You may obtain a copy of the License at
    *
    *      http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
   
   package org.apache.stormcrawler.filtering;
   
   import com.fasterxml.jackson.databind.node.ArrayNode;
   import com.fasterxml.jackson.databind.node.JsonNodeFactory;
   import com.fasterxml.jackson.databind.node.ObjectNode;
   import java.net.MalformedURLException;
   import java.net.URL;
   import java.util.HashMap;
   import java.util.Map;
   import org.apache.stormcrawler.Metadata;
   import org.apache.stormcrawler.filtering.regex.RegexURLFilter;
   import org.apache.stormcrawler.util.URLUtil;
   import org.junit.jupiter.api.Assertions;
   import org.junit.jupiter.api.Test;
   
   /**
    * The private-range rules shipped in the archetype 
default-regex-filters.txt, applied to hosts the
    * JVM resolver maps into loopback, link-local and other non-routable space.
    */
   class DefaultRegexFiltersPrivateRangeTest {
   
       /** The exclusion rules of archetype default-regex-filters.txt, 
verbatim. */
       private static final String[] ARCHETYPE_RULES = {
           "-^(file|ftp|mailto):",
           
"-^https?://(?:localhost|127(?:\\.(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))){3}|\\[::1\\])(?::\\d+)?(?:/|$)",
           
"-^https?://(?:10(?:\\.(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))){3})(?::\\d+)?(?:/|$)",
           
"-^https?://(?:192\\.168(?:\\.(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))){2})(?::\\d+)?(?:/|$)",
           
"-^https?://(?:172\\.(?:1[6789]|2[0-9]|3[01])(?:\\.(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))){2})(?::\\d+)?(?:/|$)",
           "+."
       };
   
       private URLFilter createFilter() {
           ObjectNode filterParams = new ObjectNode(JsonNodeFactory.instance);
           ArrayNode rules = filterParams.putArray("urlFilters");
           for (String rule : ARCHETYPE_RULES) {
               rules.add(rule);
           }
           RegexURLFilter filter = new RegexURLFilter();
           Map<String, Object> conf = new HashMap<>();
           filter.configure(conf, filterParams);
           return filter;
       }
   
       private void assertRejected(URLFilter filter, String url) throws 
MalformedURLException {
           URL source = URLUtil.toURL("http://www.example.com/index.html";);
           Assertions.assertNull(filter.filter(source, new Metadata(), url), 
url);
       }
   
       @Test
       void dottedQuadFormsAreRejected() throws MalformedURLException {
           URLFilter filter = createFilter();
           assertRejected(filter, "http://127.0.0.1/";);
           assertRejected(filter, "http://10.0.0.5/";);
           assertRejected(filter, "http://192.168.1.1/";);
       }
   
       @Test
       void otherNonRoutableRangesAreRejected() throws MalformedURLException {
           URLFilter filter = createFilter();
           assertRejected(filter, "http://169.254.169.254/";);
           assertRejected(filter, "http://100.64.0.1/";);
           assertRejected(filter, "http://[fd00::1]/";);
       }
   
       /** Both host forms are resolved to 127.0.0.1 by InetAddress.getByName. 
*/
       @Test
       void abbreviatedAndIntegerLoopbackFormsAreRejected() throws 
MalformedURLException {
           URLFilter filter = createFilter();
           assertRejected(filter, "http://127.1/";);
           assertRejected(filter, "http://2130706433/";);
       }
   }
   ```
   
   Run it:
   
   ```
   mvn -pl core test -Dtest=DefaultRegexFiltersPrivateRangeTest
   ```
   
   The archetype exclusion rules are used verbatim and two of the three tests 
fail on main. `InetAddress.getByName` maps both `127.1` and `2130706433` to 
127.0.0.1 on the JDK used here (Temurin 25).
   
   ```
   [ERROR] Tests run: 3, Failures: 2, Errors: 0, Skipped: 0
   [ERROR]   
DefaultRegexFiltersPrivateRangeTest.otherNonRoutableRangesAreRejected:77
       http://169.254.169.254/ ==> expected: <null> but was: 
<http://169.254.169.254/>
   [ERROR]   
DefaultRegexFiltersPrivateRangeTest.abbreviatedAndIntegerLoopbackFormsAreRejected:86
       http://127.1/ ==> expected: <null> but was: <http://127.1/>
   ```
   The first test, covering the dotted-quad forms the rules were written for, 
passes.
   
   ## Suggested fix
   Ship `http.filter.ipaddress.exclude` enabled in `crawler-default.yaml` and 
in the three archetype `crawler-conf.yaml` files, covering loopback, RFC1918, 
169.254.0.0/16, 100.64.0.0/10, 0.0.0.0/8 and the IPv6 equivalents. Add the 
missing ranges and the abbreviated and integer IPv4 forms to the archetype 
`default-regex-filters.txt`, and say in the comment there that a regex list 
cannot stop a host name that resolves into private space. Since the two layers 
judge different bytes, also make `BasicURLNormalizer` canonicalise the 
authority: `BasicURLNormalizer.java:135-150` lower-cases the host and converts 
an IDN host to ASCII, but does nothing else, so a percent-encoded or 
abbreviated host reaches the filters in a different form from the one the 
resolver sees. Enabling the exclude list by default changes behaviour for 
anyone crawling an intranet or a loopback service, so it needs a release note 
and a documented way to opt out.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to