diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java index 3f0137e2cd8f..9e0ca422dd68 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.InetAddress; import java.net.URL; import java.nio.file.Files; import java.util.ArrayList; @@ -18,12 +19,15 @@ import java.util.Optional; import javax.servlet.http.HttpServletRequest; - +import org.xbill.DNS.Address; +import org.xbill.DNS.ExtendedResolver; +import org.xbill.DNS.Resolver; import com.dotcms.http.CircuitBreakerUrl; import com.dotcms.http.CircuitBreakerUrl.Method; import com.dotcms.util.CloseUtils; import com.dotcms.util.ConversionUtils; import com.dotcms.util.SecurityUtils; +import com.dotcms.util.network.IPUtils; import com.dotmarketing.business.APILocator; import com.dotmarketing.business.DotStateException; import com.dotmarketing.business.UserAPI; @@ -31,6 +35,7 @@ import com.dotmarketing.exception.DotRuntimeException; import com.dotmarketing.exception.DotSecurityException; import com.dotmarketing.util.Config; +import com.dotmarketing.util.DNSUtil; import com.dotmarketing.util.FileUtil; import com.dotmarketing.util.Logger; import com.dotmarketing.util.SecurityLogger; @@ -60,6 +65,9 @@ public class TempFileAPI { public static final String TEMP_RESOURCE_PREFIX = "temp_"; private static final String WHO_CAN_USE_TEMP_FILE = "whoCanUse.tmp"; + private static final String TEMP_RESOURCE_BY_URL_ADMIN_ONLY="TEMP_RESOURCE_BY_URL_ADMIN_ONLY"; + + /** * Returns an empty TempFile of a unique id and file handle that can be used to write and access a @@ -200,14 +208,45 @@ public DotTempFile createTempFileFromUrl(final String incomingFileName, final DotTempFile dotTempFile = createEmptyTempFile(fileName, request); final File tempFile = dotTempFile.file; - final OutputStream out = new BoundedOutputStream(maxFileSize(request), - Files.newOutputStream(tempFile.toPath())); - - final CircuitBreakerUrl urlGetter = - CircuitBreakerUrl.builder().setMethod(Method.GET).setUrl(url.toString()) - .setTimeout(timeoutSeconds * 1000).build(); + + final boolean tempFilesByUrlAdminOnly = Config + .getBooleanProperty(TEMP_RESOURCE_BY_URL_ADMIN_ONLY, false); + + + /** + * If url requested is on a private subnet, block by default + */ + if(IPUtils.isIpPrivateSubnet(url.getHost())) { + throw new DotRuntimeException("Unable to load file by url:" + url); + } + + + /** + * by adding the source IP give visibility to the + * remote server of who initiatied the reuqest + */ + final String sourceIpAddress = request.getRemoteAddr(); + final String finalUrl = url.toString().contains("?") ? url.toString() + "&sourceIp=" + sourceIpAddress : url.toString() + "?sourceIp=" + sourceIpAddress ; + + + + /** + * Only allow admins to use the URL functionality + */ + User user = PortalUtil.getUser(request); + if(user == null || tempFilesByUrlAdminOnly && !user.isAdmin()) { + throw new DotRuntimeException("Only Admins can import a file by URL"); + } + + try(final OutputStream out = new BoundedOutputStream(maxFileSize(request), + Files.newOutputStream(tempFile.toPath()))){ - urlGetter.doOut(out); + final CircuitBreakerUrl urlGetter = + CircuitBreakerUrl.builder().setMethod(Method.GET).setUrl(finalUrl) + .setTimeout(timeoutSeconds * 1000).build(); + + urlGetter.doOut(out); + } return dotTempFile; diff --git a/dotCMS/src/main/java/com/dotcms/util/network/IPUtils.java b/dotCMS/src/main/java/com/dotcms/util/network/IPUtils.java index cfff53dce0ad..e85b6ebb0614 100644 --- a/dotCMS/src/main/java/com/dotcms/util/network/IPUtils.java +++ b/dotCMS/src/main/java/com/dotcms/util/network/IPUtils.java @@ -1,7 +1,11 @@ package com.dotcms.util.network; +import java.net.InetAddress; import java.util.Objects; +import org.xbill.DNS.Address; import com.dotcms.repackage.org.apache.commons.net.util.SubnetUtils; +import com.dotmarketing.exception.DotRuntimeException; +import com.dotmarketing.util.Logger; import com.dotmarketing.util.UtilMethods; import io.vavr.control.Try; @@ -38,4 +42,53 @@ public static boolean isIpInCIDR(final String ip, final String CIDR) { } + + final static private String[] privateSubnets = {"10.0.0.0/8","172.16.0.0/12", "192.168.0.0/16"}; + + + + /** + * It is important when we allow calling to remote endpoints that we verify + * that the remote endpoint is not in our corprate or private network. + * This method checks if the ip or hostname passed in is on the private network + * which can be blocked if needed. + * @param ipOrHostName + * @return + */ + public static boolean isIpPrivateSubnet(final String ipOrHostName) { + + if (ipOrHostName == null) { + return true; + } + + try { + InetAddress addr = Address.getByName(ipOrHostName); + + final String ip = addr.getHostAddress(); + + if ("127.0.0.1".equals(ip)) { + return true; + } + + if ("localhost".equals(ip)) { + return true; + } + + for (String subnet : privateSubnets) { + if (isIpInCIDR(ip, subnet)) { + return true; + } + } + } catch (Exception e) { + Logger.warn(IPUtils.class, "unable to resolve hostname, assuming the worst:" + ipOrHostName + " "+ e.getMessage()); + return true; + } + return false; + + + + } + + + } diff --git a/dotCMS/src/test/java/com/dotcms/util/network/IPUtilsTest.java b/dotCMS/src/test/java/com/dotcms/util/network/IPUtilsTest.java index 453e7146f19a..5fa47e3ac888 100644 --- a/dotCMS/src/test/java/com/dotcms/util/network/IPUtilsTest.java +++ b/dotCMS/src/test/java/com/dotcms/util/network/IPUtilsTest.java @@ -56,4 +56,39 @@ public void test_false_cases() { } } + + + final static String[] ipsOnPrivateSubnets= { + "192.168.1.255", + "10.0.0.4", + "127.0.0.1", + "172.16.3.5", + "172.16.3.0", + "localhost" + }; + + final static String[] ipsOnPublicSubnets= { + "2.2.2.2", + "3.22.136.122", + "142.251.32.110", + "74.6.231.21", + "dotcms.com", + "193.252.133.20" + }; + + @Test + public void test_ip_private_subnets() { + for(String testCase : ipsOnPrivateSubnets) { + assertTrue( IPUtils.isIpPrivateSubnet(testCase)); + } + } + @Test + + public void test_ip_public_subnets() { + for(String testCase : ipsOnPublicSubnets) { + assertFalse( IPUtils.isIpPrivateSubnet(testCase)); + } + } + + }