mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-08-27 12:02:01 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a5240fcbc | |||
| ecd365767a | |||
| f358140974 | |||
| 541c21f963 | |||
| bdd253383d | |||
| 0fb53d5159 | |||
| b9ff408bdd | |||
| aeb4394cf8 | |||
| 8d419d3265 | |||
| 363c603bbb |
@@ -531,7 +531,13 @@ Core模块集成Vert.x实现类似spring的注解式路由API
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#qaiu/netdisk-fast-download&Date)
|
||||
<a href="https://www.star-history.com/?repos=qaiu%2Fnetdisk-fast-download&type=date&legend=bottom-right">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=qaiu/netdisk-fast-download&type=date&theme=dark&legend=bottom-right&sealed_token=dfQO_dJcTqcPkEnM7SfxRyHoFbV5Ah4LxoEhdlheMn4T2YLEV_WETxFZexeAbWN5OmNyYuycWan2d42PAFbw0CuU4oCTKgehfErFJ9eVl2CyVpP_4xrdQw" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=qaiu/netdisk-fast-download&type=date&legend=bottom-right&sealed_token=dfQO_dJcTqcPkEnM7SfxRyHoFbV5Ah4LxoEhdlheMn4T2YLEV_WETxFZexeAbWN5OmNyYuycWan2d42PAFbw0CuU4oCTKgehfErFJ9eVl2CyVpP_4xrdQw" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=qaiu/netdisk-fast-download&type=date&legend=bottom-right&sealed_token=dfQO_dJcTqcPkEnM7SfxRyHoFbV5Ah4LxoEhdlheMn4T2YLEV_WETxFZexeAbWN5OmNyYuycWan2d42PAFbw0CuU4oCTKgehfErFJ9eVl2CyVpP_4xrdQw" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## **免责声明**
|
||||
- 用户在使用本项目时,应自行承担风险,并确保其行为符合当地法律法规。开发者不对用户因使用本项目而导致的任何后果负责。
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.7.11</version>
|
||||
<version>42.7.12</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -20,6 +20,9 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -201,6 +204,76 @@ public abstract class PanBase implements IPanTool, Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SSRF 防护: 校验通用自定义域名解析器 (CE/Ce4/Kd/Other 等) 即将请求的目标主机,
|
||||
* 拒绝解析到回环/内网/链路本地/组播等非公网地址的域名, 阻止攻击者通过可控 DNS
|
||||
* 记录 (或直接填写内网域名) 让服务端向内网/云元数据接口发起请求。
|
||||
* <p>
|
||||
* 必须在子类 parse() 中构造出 baseUrl/发起任何 clientSession 请求之前调用。
|
||||
*
|
||||
* @param url 从 shareLinkInfo.getShareUrl() 解析出的 URL
|
||||
* @throws IOException 当主机无法解析或解析结果落入禁止的地址段时抛出
|
||||
*/
|
||||
protected static void assertPublicHost(URL url) throws IOException {
|
||||
String host = url.getHost();
|
||||
InetAddress[] addresses;
|
||||
try {
|
||||
addresses = InetAddress.getAllByName(host);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new IOException("无法解析目标主机: " + host, e);
|
||||
}
|
||||
if (addresses.length == 0) {
|
||||
throw new IOException("无法解析目标主机: " + host);
|
||||
}
|
||||
for (InetAddress addr : addresses) {
|
||||
if (isDisallowedAddress(addr)) {
|
||||
throw new IOException("目标地址不允许访问(内网/回环/链路本地/组播): "
|
||||
+ host + " -> " + addr.getHostAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断地址是否落入禁止访问的范围: 0.0.0.0/8, 10/8, 127/8, 169.254/16(含云元数据
|
||||
* 169.254.169.254), 172.16/12, 192.168/16, 100.64/10(CGNAT), 224/4~255/4(组播/保留),
|
||||
* 以及对应的 IPv6 回环/链路本地/唯一本地地址(fc00::/7)/组播地址; IPv4-映射的 IPv6
|
||||
* 地址(::ffff:a.b.c.d) 会先还原为 IPv4 再判断,避免绕过。
|
||||
*/
|
||||
private static boolean isDisallowedAddress(InetAddress addr) {
|
||||
byte[] bytes = addr.getAddress();
|
||||
if (bytes.length == 16 && isIPv4Mapped(bytes)) {
|
||||
byte[] v4 = new byte[4];
|
||||
System.arraycopy(bytes, 12, v4, 0, 4);
|
||||
bytes = v4;
|
||||
}
|
||||
if (bytes.length == 4) {
|
||||
int b0 = bytes[0] & 0xFF;
|
||||
int b1 = bytes[1] & 0xFF;
|
||||
if (b0 == 0) return true; // 0.0.0.0/8
|
||||
if (b0 == 10) return true; // 10.0.0.0/8
|
||||
if (b0 == 127) return true; // 127.0.0.0/8 loopback
|
||||
if (b0 == 169 && b1 == 254) return true; // 169.254.0.0/16 (含云元数据 169.254.169.254)
|
||||
if (b0 == 172 && b1 >= 16 && b1 <= 31) return true; // 172.16.0.0/12
|
||||
if (b0 == 192 && b1 == 168) return true; // 192.168.0.0/16
|
||||
if (b0 == 100 && b1 >= 64 && b1 <= 127) return true;// 100.64.0.0/10 CGNAT
|
||||
return b0 >= 224; // 224.0.0.0/4 组播 + 240.0.0.0/4 保留
|
||||
}
|
||||
// IPv6
|
||||
if (addr.isAnyLocalAddress() || addr.isLoopbackAddress()
|
||||
|| addr.isLinkLocalAddress() || addr.isSiteLocalAddress()
|
||||
|| addr.isMulticastAddress()) {
|
||||
return true;
|
||||
}
|
||||
return bytes.length == 16 && (bytes[0] & 0xFE) == 0xFC; // fc00::/7 unique local
|
||||
}
|
||||
|
||||
private static boolean isIPv4Mapped(byte[] b) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (b[i] != 0) return false;
|
||||
}
|
||||
return (b[10] & 0xFF) == 0xFF && (b[11] & 0xFF) == 0xFF;
|
||||
}
|
||||
|
||||
protected String baseMsg() {
|
||||
if (shareLinkInfo.getShareUrl() != null) {
|
||||
return shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": url=" + shareLinkInfo.getShareUrl();
|
||||
|
||||
@@ -37,6 +37,7 @@ public class Ce4Tool extends PanBase {
|
||||
|
||||
try {
|
||||
URL url = new URL(shareLinkInfo.getShareUrl());
|
||||
assertPublicHost(url);
|
||||
String baseUrl = url.getProtocol() + "://" + url.getHost();
|
||||
// 如果有端口,拼接上端口
|
||||
if (url.getPort() != -1) {
|
||||
|
||||
@@ -43,6 +43,7 @@ public class CeTool extends PanBase {
|
||||
String pwd = shareLinkInfo.getSharePassword();
|
||||
try {
|
||||
URL url = new URL(shareLinkInfo.getShareUrl());
|
||||
assertPublicHost(url);
|
||||
String baseUrl = url.getProtocol() + "://" + url.getHost();
|
||||
// 如果有端口,拼接上端口
|
||||
if (url.getPort() != -1) {
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<parserVersion>10.2.5</parserVersion>
|
||||
<jackson.version>2.18.6</jackson.version>
|
||||
<!-- Logback 最新稳定版 -->
|
||||
<logback.version>1.5.32</logback.version>
|
||||
<logback.version>1.5.33</logback.version>
|
||||
<junit.version>4.13.2</junit.version>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@monaco-editor/loader": "^1.4.0",
|
||||
"@vueuse/core": "^11.2.0",
|
||||
"axios": "1.16.1",
|
||||
"axios": "1.18.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"core-js": "^3.8.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
|
||||
@@ -386,6 +386,16 @@
|
||||
"type": "string",
|
||||
"example": "uuid123"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stoken",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "分享 token,用于子目录解析时复用认证状态",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "OASBe5qM0pg2VvvyLM..."
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
@@ -68,9 +68,9 @@ public class URLParamUtil {
|
||||
boolean firstParam = !decodedUrl.contains("?");
|
||||
|
||||
for (String paramName : params.names()) {
|
||||
// 忽略 "url", "pwd", "dirId", "uuid", "auth" 参数(这些参数单独处理,不应拼接到分享URL中)
|
||||
// 忽略 "url", "pwd", "dirId", "uuid", "auth", "stoken" 参数(这些参数单独处理,不应拼接到分享URL中)
|
||||
if (!paramName.equals("url") && !paramName.equals("pwd") && !paramName.equals("dirId")
|
||||
&& !paramName.equals("uuid") && !paramName.equals("auth")) {
|
||||
&& !paramName.equals("uuid") && !paramName.equals("auth") && !paramName.equals("stoken")) {
|
||||
if (firstParam) {
|
||||
urlBuilder.append("?");
|
||||
firstParam = false;
|
||||
|
||||
@@ -161,7 +161,7 @@ public class ParserApi {
|
||||
|
||||
@RouteMapping("/getFileList")
|
||||
public Future<List<FileInfo>> getFileList(HttpServerRequest request, String pwd, String dirId, String uuid,
|
||||
String auth) {
|
||||
String stoken, String auth) {
|
||||
String url = URLParamUtil.parserParams(request);
|
||||
ParserCreate parserCreate;
|
||||
try {
|
||||
@@ -176,6 +176,9 @@ public class ParserApi {
|
||||
if (StringUtils.isNotBlank(dirId)) {
|
||||
parserCreate.getShareLinkInfo().getOtherParam().put("dirId", dirId);
|
||||
}
|
||||
if (StringUtils.isNotBlank(stoken)) {
|
||||
parserCreate.getShareLinkInfo().getOtherParam().put("stoken", stoken);
|
||||
}
|
||||
if (StringUtils.isNotBlank(uuid)) {
|
||||
parserCreate.getShareLinkInfo().getOtherParam().put("uuid", uuid);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user