-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add some rule for check java audit prompt
- Loading branch information
v1ll4n
committed
Aug 21, 2024
1 parent
6cadbc7
commit 0d16773
Showing
3 changed files
with
393 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
desc( | ||
title: "Find Springframework MultipartFile.transferTo use", | ||
type: audit, | ||
level: info, | ||
) | ||
|
||
|
||
.transferTo?{<getObject><typeName>?{have: MultipartFile}} as $sinkCall; | ||
check $sinkCall; | ||
$sinkCall(*<slice(start=1)> as $params); | ||
$params#{ | ||
hook: `* as $relative`, | ||
}-> | ||
alert $relative for "Constant or External for transferTo local filesystem"; | ||
|
||
desc( | ||
lang: java, | ||
'file://a.java': <<<FILE | ||
package com.ruoyi.file.utils; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.nio.file.Paths; | ||
import java.util.Objects; | ||
import org.apache.commons.io.FilenameUtils; | ||
import org.springframework.web.multipart.MultipartFile; | ||
import com.ruoyi.common.core.exception.file.FileException; | ||
import com.ruoyi.common.core.exception.file.FileNameLengthLimitExceededException; | ||
import com.ruoyi.common.core.exception.file.FileSizeLimitExceededException; | ||
import com.ruoyi.common.core.exception.file.InvalidExtensionException; | ||
import com.ruoyi.common.core.utils.DateUtils; | ||
import com.ruoyi.common.core.utils.StringUtils; | ||
import com.ruoyi.common.core.utils.file.FileTypeUtils; | ||
import com.ruoyi.common.core.utils.file.MimeTypeUtils; | ||
import com.ruoyi.common.core.utils.uuid.Seq; | ||
|
||
/** | ||
* 文件上传工具类 | ||
* | ||
* @author ruoyi | ||
*/ | ||
public class FileUploadUtils | ||
{ | ||
|
||
|
||
/** | ||
* 文件上传 | ||
* | ||
* @param baseDir 相对应用的基目录 | ||
* @param file 上传的文件 | ||
* @param allowedExtension 上传文件类型 | ||
* @return 返回上传成功的文件名 | ||
* @throws FileSizeLimitExceededException 如果超出最大大小 | ||
* @throws FileNameLengthLimitExceededException 文件名太长 | ||
* @throws IOException 比如读写文件出错时 | ||
* @throws InvalidExtensionException 文件校验异常 | ||
*/ | ||
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) | ||
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, | ||
InvalidExtensionException | ||
{ | ||
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); | ||
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) | ||
{ | ||
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); | ||
} | ||
|
||
assertAllowed(file, allowedExtension); | ||
|
||
String fileName = extractFilename(file); | ||
|
||
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); | ||
file.transferTo(Paths.get(absPath)); | ||
return getPathFileName(fileName); | ||
} | ||
|
||
/** | ||
* 编码文件名 | ||
*/ | ||
public static final String extractFilename(MultipartFile file) | ||
{ | ||
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), | ||
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), FileTypeUtils.getExtension(file)); | ||
} | ||
|
||
private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException | ||
{ | ||
File desc = new File(uploadDir + File.separator + fileName); | ||
|
||
if (!desc.exists()) | ||
{ | ||
if (!desc.getParentFile().exists()) | ||
{ | ||
desc.getParentFile().mkdirs(); | ||
} | ||
} | ||
return desc.isAbsolute() ? desc : desc.getAbsoluteFile(); | ||
} | ||
} | ||
FILE | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
desc( | ||
title: "Find File Download Position Config via Attachment Filename", | ||
title_zh: "寻找文件下载位置配置点(使用 setHeader(Content-disposition...))配置", | ||
type: audit, | ||
level: low, | ||
) | ||
|
||
.setHeader(*<slice(start=1)> as $params); | ||
check $params; | ||
$params?{opcode: const && <string>?{any: disposition,Disposition} } as $flag; | ||
check $flag; | ||
$flag<getCall><getCaller> as $sink; | ||
check $sink; | ||
alert $sink for "Config Download filename Position"; | ||
|
||
desc( | ||
language: java, | ||
'file://download.java': <<<TEXT | ||
package com.ruoyi.common.core.utils.file; | ||
|
||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.FileNotFoundException; | ||
import java.io.IOException; | ||
import java.io.OutputStream; | ||
import java.io.UnsupportedEncodingException; | ||
import java.net.URLEncoder; | ||
import java.nio.charset.StandardCharsets; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import org.apache.commons.lang3.ArrayUtils; | ||
import com.ruoyi.common.core.utils.StringUtils; | ||
|
||
/** | ||
* 文件处理工具类 | ||
* | ||
* @author ruoyi | ||
*/ | ||
public class FileUtils | ||
{ | ||
public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException | ||
{ | ||
final String agent = request.getHeader("USER-AGENT"); | ||
String filename = fileName; | ||
if (agent.contains("MSIE")) | ||
{ | ||
// IE浏览器 | ||
filename = URLEncoder.encode(filename, "utf-8"); | ||
filename = filename.replace("+", " "); | ||
} | ||
else if (agent.contains("Firefox")) | ||
{ | ||
// 火狐浏览器 | ||
filename = new String(fileName.getBytes(), "ISO8859-1"); | ||
} | ||
else if (agent.contains("Chrome")) | ||
{ | ||
// google浏览器 | ||
filename = URLEncoder.encode(filename, "utf-8"); | ||
} | ||
else | ||
{ | ||
// 其它浏览器 | ||
filename = URLEncoder.encode(filename, "utf-8"); | ||
} | ||
return filename; | ||
} | ||
|
||
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException | ||
{ | ||
String percentEncodedFileName = percentEncode(realFileName); | ||
|
||
StringBuilder contentDispositionValue = new StringBuilder(); | ||
contentDispositionValue.append("attachment; filename=") | ||
.append(percentEncodedFileName) | ||
.append(";") | ||
.append("filename*=") | ||
.append("utf-8''") | ||
.append(percentEncodedFileName); | ||
|
||
response.setHeader("Content-disposition", contentDispositionValue.toString()); | ||
response.setHeader("download-filename", percentEncodedFileName); | ||
} | ||
} | ||
|
||
TEXT | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
desc( | ||
title: "Find sqlString.append() Query, Prompt a SQL Injection Checking", | ||
type: audit, | ||
level: low, | ||
desc: <<<TEXT | ||
本提示并不代表真实漏洞,而是代码中不安全的用法,可能会有潜在威胁。 | ||
|
||
在Java中,拼接SQL查询语句可能会导致SQL注入漏洞。建议使用预编译的SQL语句或ORM框架来避免SQL注入。 | ||
TEXT | ||
) | ||
|
||
|
||
*sql*.append(*<slice(start=1)> as $params); | ||
check $params; | ||
|
||
$params?{!opcode: const}#{ | ||
hook: `*?{opcode: const && have: 'WHERE'}<show> as $flag`, | ||
}-> | ||
alert $flag; | ||
|
||
|
||
desc( | ||
lang: java, | ||
'file://risk.java': <<<TEXT | ||
package com.ruoyi.common.datascope.aspect; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import org.aspectj.lang.JoinPoint; | ||
import org.aspectj.lang.annotation.Aspect; | ||
import org.aspectj.lang.annotation.Before; | ||
import org.springframework.stereotype.Component; | ||
import com.ruoyi.common.core.context.SecurityContextHolder; | ||
import com.ruoyi.common.core.text.Convert; | ||
import com.ruoyi.common.core.utils.StringUtils; | ||
import com.ruoyi.common.core.web.domain.BaseEntity; | ||
import com.ruoyi.common.datascope.annotation.DataScope; | ||
import com.ruoyi.common.security.utils.SecurityUtils; | ||
import com.ruoyi.system.api.domain.SysRole; | ||
import com.ruoyi.system.api.domain.SysUser; | ||
import com.ruoyi.system.api.model.LoginUser; | ||
|
||
@Aspect | ||
@Component | ||
public class DataScopeAspect | ||
{ | ||
/** | ||
* 全部数据权限 | ||
*/ | ||
public static final String DATA_SCOPE_ALL = "1"; | ||
|
||
/** | ||
* 自定数据权限 | ||
*/ | ||
public static final String DATA_SCOPE_CUSTOM = "2"; | ||
|
||
/** | ||
* 部门数据权限 | ||
*/ | ||
public static final String DATA_SCOPE_DEPT = "3"; | ||
|
||
/** | ||
* 部门及以下数据权限 | ||
*/ | ||
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; | ||
|
||
/** | ||
* 仅本人数据权限 | ||
*/ | ||
public static final String DATA_SCOPE_SELF = "5"; | ||
|
||
/** | ||
* 数据权限过滤关键字 | ||
*/ | ||
public static final String DATA_SCOPE = "dataScope"; | ||
|
||
@Before("@annotation(controllerDataScope)") | ||
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable | ||
{ | ||
clearDataScope(point); | ||
handleDataScope(point, controllerDataScope); | ||
} | ||
|
||
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) | ||
{ | ||
// 获取当前的用户 | ||
LoginUser loginUser = SecurityUtils.getLoginUser(); | ||
if (StringUtils.isNotNull(loginUser)) | ||
{ | ||
SysUser currentUser = loginUser.getSysUser(); | ||
// 如果是超级管理员,则不过滤数据 | ||
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) | ||
{ | ||
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), SecurityContextHolder.getPermission()); | ||
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), | ||
controllerDataScope.userAlias(), permission); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* 数据范围过滤 | ||
* | ||
* @param joinPoint 切点 | ||
* @param user 用户 | ||
* @param deptAlias 部门别名 | ||
* @param userAlias 用户别名 | ||
* @param permission 权限字符 | ||
*/ | ||
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission) | ||
{ | ||
StringBuilder sqlString = new StringBuilder(); | ||
List<String> conditions = new ArrayList<String>(); | ||
List<String> scopeCustomIds = new ArrayList<String>(); | ||
user.getRoles().forEach(role -> { | ||
if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) | ||
{ | ||
scopeCustomIds.add(Convert.toStr(role.getRoleId())); | ||
} | ||
}); | ||
|
||
for (SysRole role : user.getRoles()) | ||
{ | ||
String dataScope = role.getDataScope(); | ||
if (conditions.contains(dataScope)) | ||
{ | ||
continue; | ||
} | ||
if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) | ||
{ | ||
continue; | ||
} | ||
if (DATA_SCOPE_ALL.equals(dataScope)) | ||
{ | ||
sqlString = new StringBuilder(); | ||
conditions.add(dataScope); | ||
break; | ||
} | ||
else if (DATA_SCOPE_CUSTOM.equals(dataScope)) | ||
{ | ||
if (scopeCustomIds.size() > 1) | ||
{ | ||
// 多个自定数据权限使用in查询,避免多次拼接。 | ||
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds))); | ||
} | ||
else | ||
{ | ||
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId())); | ||
} | ||
} | ||
else if (DATA_SCOPE_DEPT.equals(dataScope)) | ||
{ | ||
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); | ||
} | ||
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) | ||
{ | ||
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId())); | ||
} | ||
else if (DATA_SCOPE_SELF.equals(dataScope)) | ||
{ | ||
if (StringUtils.isNotBlank(userAlias)) | ||
{ | ||
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); | ||
} | ||
else | ||
{ | ||
// 数据权限为仅本人且没有userAlias别名不查询任何数据 | ||
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); | ||
} | ||
} | ||
conditions.add(dataScope); | ||
} | ||
|
||
// 角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据 | ||
if (StringUtils.isEmpty(conditions)) | ||
{ | ||
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); | ||
} | ||
|
||
if (StringUtils.isNotBlank(sqlString.toString())) | ||
{ | ||
Object params = joinPoint.getArgs()[0]; | ||
if (StringUtils.isNotNull(params) && params instanceof BaseEntity) | ||
{ | ||
BaseEntity baseEntity = (BaseEntity) params; | ||
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* 拼接权限sql前先清空params.dataScope参数防止注入 | ||
*/ | ||
private void clearDataScope(final JoinPoint joinPoint) | ||
{ | ||
Object params = joinPoint.getArgs()[0]; | ||
if (StringUtils.isNotNull(params) && params instanceof BaseEntity) | ||
{ | ||
BaseEntity baseEntity = (BaseEntity) params; | ||
baseEntity.getParams().put(DATA_SCOPE, ""); | ||
} | ||
} | ||
} | ||
TEXT | ||
) |