🐛 初步解决 SSRF 问题 https://github.com/rymcu/forest/issues/149
🐛 初步解决 SSRF 问题 https://github.com/rymcu/forest/issues/149
This commit is contained in:
commit
11d840e555
29
pom.xml
29
pom.xml
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.13</version>
|
||||
<version>2.7.17</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.rymcu</groupId>
|
||||
@ -39,8 +39,26 @@
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.4.11</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>1.4.11</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec</artifactId>
|
||||
@ -83,7 +101,7 @@
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.30</version>
|
||||
<version>8.0.33</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -105,7 +123,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-core</artifactId>
|
||||
<version>9.0.83</version>
|
||||
<version>9.0.84</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@ -371,6 +389,11 @@
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>33.0.0-jre</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
157
src/main/java/com/rymcu/forest/util/SSRFUtil.java
Normal file
157
src/main/java/com/rymcu/forest/util/SSRFUtil.java
Normal file
@ -0,0 +1,157 @@
|
||||
package com.rymcu.forest.util;
|
||||
|
||||
import com.google.common.net.InternetDomainName;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Created on 2023/12/29 11:52.
|
||||
*
|
||||
* @author ronger
|
||||
* @email ronger-x@outlook.com
|
||||
* @desc : com.rymcu.forest.util
|
||||
*/
|
||||
public class SSRFUtil {
|
||||
public static boolean checkUrl(URL url, boolean checkWhiteList) {
|
||||
// 协议限制
|
||||
if (!url.getProtocol().startsWith("http") && !url.getProtocol().startsWith("https")) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// 获取域名,并转为小写
|
||||
String host = url.getHost().toLowerCase();
|
||||
// 禁止内网 IP
|
||||
if (!internalIp(host)) {
|
||||
return false;
|
||||
}
|
||||
if (checkWhiteList) {
|
||||
// 获取一级域名
|
||||
String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString();
|
||||
// TODO 白名单
|
||||
}
|
||||
} catch (IllegalArgumentException exception) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws MalformedURLException {
|
||||
URL url = new URL("http://192.168.0.1");
|
||||
boolean b = checkUrl(url, false);
|
||||
System.out.println(b);
|
||||
}
|
||||
|
||||
public static boolean internalIp(String ip) {
|
||||
byte[] addr = textToNumericFormatV4(ip);
|
||||
return internalIp(addr) || "127.0.0.1".equals(ip);
|
||||
}
|
||||
|
||||
private static boolean internalIp(byte[] addr) {
|
||||
if (Objects.isNull(addr) || addr.length < 2) {
|
||||
return true;
|
||||
}
|
||||
final byte b0 = addr[0];
|
||||
final byte b1 = addr[1];
|
||||
// 10.x.x.x/8
|
||||
final byte SECTION_1 = 0x0A;
|
||||
// 172.16.x.x/12
|
||||
final byte SECTION_2 = (byte) 0xAC;
|
||||
final byte SECTION_3 = (byte) 0x10;
|
||||
final byte SECTION_4 = (byte) 0x1F;
|
||||
// 192.168.x.x/16
|
||||
final byte SECTION_5 = (byte) 0xC0;
|
||||
final byte SECTION_6 = (byte) 0xA8;
|
||||
switch (b0) {
|
||||
case SECTION_1:
|
||||
return true;
|
||||
case SECTION_2:
|
||||
if (b1 >= SECTION_3 && b1 <= SECTION_4) {
|
||||
return true;
|
||||
}
|
||||
case SECTION_5:
|
||||
switch (b1) {
|
||||
case SECTION_6:
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将IPv4地址转换成字节
|
||||
*
|
||||
* @param text IPv4地址
|
||||
* @return byte 字节
|
||||
*/
|
||||
public static byte[] textToNumericFormatV4(String text) {
|
||||
if (text.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[4];
|
||||
String[] elements = text.split("\\.", -1);
|
||||
try {
|
||||
long l;
|
||||
int i;
|
||||
switch (elements.length) {
|
||||
case 1:
|
||||
l = Long.parseLong(elements[0]);
|
||||
if ((l < 0L) || (l > 4294967295L)) {
|
||||
return null;
|
||||
}
|
||||
bytes[0] = (byte) (int) (l >> 24 & 0xFF);
|
||||
bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
|
||||
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
|
||||
bytes[3] = (byte) (int) (l & 0xFF);
|
||||
break;
|
||||
case 2:
|
||||
l = Integer.parseInt(elements[0]);
|
||||
if ((l < 0L) || (l > 255L)) {
|
||||
return null;
|
||||
}
|
||||
bytes[0] = (byte) (int) (l & 0xFF);
|
||||
l = Integer.parseInt(elements[1]);
|
||||
if ((l < 0L) || (l > 16777215L)) {
|
||||
return null;
|
||||
}
|
||||
bytes[1] = (byte) (int) (l >> 16 & 0xFF);
|
||||
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
|
||||
bytes[3] = (byte) (int) (l & 0xFF);
|
||||
break;
|
||||
case 3:
|
||||
for (i = 0; i < 2; ++i) {
|
||||
l = Integer.parseInt(elements[i]);
|
||||
if ((l < 0L) || (l > 255L)) {
|
||||
return null;
|
||||
}
|
||||
bytes[i] = (byte) (int) (l & 0xFF);
|
||||
}
|
||||
l = Integer.parseInt(elements[2]);
|
||||
if ((l < 0L) || (l > 65535L)) {
|
||||
return null;
|
||||
}
|
||||
bytes[2] = (byte) (int) (l >> 8 & 0xFF);
|
||||
bytes[3] = (byte) (int) (l & 0xFF);
|
||||
break;
|
||||
case 4:
|
||||
for (i = 0; i < 4; ++i) {
|
||||
l = Integer.parseInt(elements[i]);
|
||||
if ((l < 0L) || (l > 255L)) {
|
||||
return null;
|
||||
}
|
||||
bytes[i] = (byte) (int) (l & 0xFF);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
@ -25,13 +25,11 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import com.rymcu.forest.util.SSRFUtil;
|
||||
|
||||
/**
|
||||
* 文件上传控制器
|
||||
@ -265,6 +263,10 @@ public class UploadController {
|
||||
return GlobalResultGenerator.genSuccessResult(data);
|
||||
}
|
||||
URL link = new URL(url);
|
||||
// SSRF 校验
|
||||
if (!SSRFUtil.checkUrl(link, false)) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
HttpURLConnection conn = (HttpURLConnection) link.openConnection();
|
||||
//设置超时间为3秒
|
||||
conn.setConnectTimeout(3 * 1000);
|
||||
|
Loading…
Reference in New Issue
Block a user