- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
本文是从开源项目 RuoYi 的提交记录文字描述中根据关键字 漏洞|安全|阻止 筛选而来。旨在为大家介绍日常项目开发中需要注意的一些安全问题以及如何解决.
项目安全是每个开发人员都需要重点关注的问题。如果项目漏洞太多,很容易遭受黑客攻击与用户信息泄露的风险。本文将结合3个典型案例,解释常见的安全漏洞及修复方案,帮助大家在项目开发中进一步提高安全意识.
RuoYi 项目中有一个重置用户密码的接口,在提交记录 dd37524b 之前的代码如下:
@Log(title = "重置密码", businessType = BusinessType.UPDATE)
@PostMapping("/resetPwd")
@ResponseBody
public AjaxResult resetPwd(SysUser user)
{
user.setSalt(ShiroUtils.randomSalt());
user.setPassword(passwordService.encryptPassword(user.getLoginName(),
user.getPassword(), user.getSalt()));
int rows = userService.resetUserPwd(user);
if (rows > 0)
{
setSysUser(userService.selectUserById(user.getUserId()));
return success();
}
return error();
}
可以看出该接口会读取传入的用户信息,重置完用户密码后,会根据传入的 userId 更新数据库以及缓存.
这里有一个非常严重的安全问题就是盲目相信传入的用户信息,如果攻击人员通过接口构造请求,并且在传入的 user 参数中设置 userId 为其他用户的 userId,那么这个接口就会导致某些用户的密码被重置因而被攻击人员掌握.
假如攻击人员掌握了其他用户的 userId 以及登录账号名 。
在记录 dd37524b 提交之后,代码更新如下:
@Log(title = "重置密码", businessType = BusinessType.UPDATE)
@PostMapping("/resetPwd")
@ResponseBody
public AjaxResult resetPwd(String oldPassword, String newPassword)
{
SysUser user = getSysUser();
if (StringUtils.isNotEmpty(newPassword)
&& passwordService.matches(user, oldPassword))
{
user.setSalt(ShiroUtils.randomSalt());
user.setPassword(passwordService.encryptPassword(
user.getLoginName(), newPassword, user.getSalt()));
if (userService.resetUserPwd(user) > 0)
{
setSysUser(userService.selectUserById(user.getUserId()));
return success();
}
return error();
}
else
{
return error("修改密码失败,旧密码错误");
}
}
解决方法其实很简单,不要盲目相信用户传入的参数,通过登录状态获取当前登录用户的userId。如上代码通过 getSysUser() 方法获取当前登录用户的 userId 后,再根据 userId 重置密码.
文件下载作为 web 开发中,每个项目都会遇到的功能,相信对大家而言都不陌生。RuoYi 在提交记录 18f6366f 之前的下载文件逻辑如下:
@GetMapping("common/download")
public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request)
{
try
{
if (!FileUtils.isValidFilename(fileName))
{
throw new Exception(StringUtils.format(
"文件名称({})非法,不允许下载。 ", fileName));
}
String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
String filePath = Global.getDownloadPath() + fileName;
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
FileUtils.setAttachmentResponseHeader(response, realFileName);
FileUtils.writeBytes(filePath, response.getOutputStream());
if (delete)
{
FileUtils.deleteFile(filePath);
}
}
catch (Exception e)
{
log.error("下载文件失败", e);
}
}
public class FileUtils
{
public static String FILENAME_PATTERN =
"[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
public static boolean isValidFilename(String filename)
{
return filename.matches(FILENAME_PATTERN);
}
}
可以看到代码中在下载文件时,会判断文件名称是否合法,如果不合法会提示 文件名称({})非法,不允许下载。 的字样。咋一看,好像没什么问题,博主公司项目中下载文件也有这种类似代码。传入下载文件名称,然后再指定目录中找到要下载的文件后,通过流回写给客户端.
既然如此,那我们再看一下提交记录 18f6366f 的描述信息, 不看不知道,一看吓一跳,原来再这个提交之前,项目中存在任意文件下载漏洞,这里博主给大家讲解一下为什么会存在任意文件下载漏洞.
假如下载目录为 /data/upload/ 。
../../home/重要文件.txt
/data/upload/../../home/重要文件.txt
/home/重要文件.txt
我们看一下提交记录 18f6366f 主要干了什么,代码如下:
@GetMapping("common/download")
public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request)
{
try
{
if (!FileUtils.checkAllowDownload(fileName))
{
throw new Exception(StringUtils.format(
"文件名称({})非法,不允许下载。 ", fileName));
}
String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
String filePath = Global.getDownloadPath() + fileName;
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
FileUtils.setAttachmentResponseHeader(response, realFileName);
FileUtils.writeBytes(filePath, response.getOutputStream());
if (delete)
{
FileUtils.deleteFile(filePath);
}
}
catch (Exception e)
{
log.error("下载文件失败", e);
}
}
public class FileUtils
{
/**
* 检查文件是否可下载
*
* @param resource 需要下载的文件
* @return true 正常 false 非法
*/
public static boolean checkAllowDownload(String resource)
{
// 禁止目录上跳级别
if (StringUtils.contains(resource, ".."))
{
return false;
}
// 检查允许下载的文件规则
if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION,
FileTypeUtils.getFileType(resource)))
{
return true;
}
// 不在允许下载的文件规则
return false;
}
}
...
public static final String[] DEFAULT_ALLOWED_EXTENSION = {
// 图片
"bmp", "gif", "jpg", "jpeg", "png",
// word excel powerpoint
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
// 压缩文件
"rar", "zip", "gz", "bz2",
// 视频格式
"mp4", "avi", "rmvb",
// pdf
"pdf" };
...
public class FileTypeUtils
{
/**
* 获取文件类型
* <p>
* 例如: ruoyi.txt, 返回: txt
*
* @param fileName 文件名
* @return 后缀(不含".")
*/
public static String getFileType(String fileName)
{
int separatorIndex = fileName.lastIndexOf(".");
if (separatorIndex < 0)
{
return "";
}
return fileName.substring(separatorIndex + 1).toLowerCase();
}
}
可以看到,提交记录 18f6366f 中,将下载文件时的 FileUtils.isValidFilename(fileName) 方法换成了 FileUtils.checkAllowDownload(fileName) 方法。这个方法会检查文件名称参数中是否包含 .. ,以防止目录上跳,然后再检查文件名称是否再白名单中。这样就可以避免任意文件下载漏洞.
路径遍历允许攻击者通过操纵路径的可变部分访问目录和文件的内容。在处理文件上传、下载等操作时,我们需要对路径参数进行严格校验,防止目录遍历漏洞.
RuoYi 项目作为一个后台管理项目,几乎每个菜单都会用到分页查询,因此项目中封装了分页查询类 PageDomain ,其他会读取客户端传入的 orderByColumn 参数。再提交记录 807b7231 之前,分页查询代码如下:
public class PageDomain
{
...
public void setOrderByColumn(String orderByColumn)
{
this.orderByColumn = orderByColumn;
}
...
}
/**
* 设置请求分页数据
*/
public static void startPage()
{
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
String orderBy = pageDomain.getOrderBy();
Boolean reasonable = pageDomain.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
}
/**
* 分页查询
*/
@RequiresPermissions("system:post:list")
@PostMapping("/list")
@ResponseBody
public TableDataInfo list(SysPost post)
{
startPage();
List<SysPost> list = postService.selectPostList(post);
return getDataTable(list);
}
可以看到,分页查询一般会直接条用封装好的 startPage() 方法,会将 PageDomain 的 orderByColumn 属性直接放进 PageHelper 中,最后也就会拼接在实际的 SQL 查询语句中.
假如攻击人员知道用户表名称为 users, 。
orderByColumn
参数为 1; DROP TABLE users;
SELECT * FROM users WHERE username = 'admin' ORDER BY 1; DROP TABLE users;
DROP TABLE users;
完毕,users 表被删除 再提交记录 807b7231 之后,针对排序参数做了转义处理,最新代码如下, 。
public class PageDomain
{
...
public void setOrderByColumn(String orderByColumn)
{
this.orderByColumn = SqlUtil.escapeSql(orderByColumn);
}
}
/**
* sql操作工具类
*
* @author ruoyi
*/
public class SqlUtil
{
/**
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
*/
public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
/**
* 检查字符,防止注入绕过
*/
public static String escapeOrderBySql(String value)
{
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
{
throw new UtilException("参数不符合规范,不能进行查询");
}
return value;
}
/**
* 验证 order by 语法是否符合规范
*/
public static boolean isValidOrderBySql(String value)
{
return value.matches(SQL_PATTERN);
}
...
}
可以看到对于 order by 语句后可以拼接的字符串做了正则匹配,仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)。以此可以避免 order by 后面拼接其他非法字符,例如 drop|if()|union 等等,因而可以避免 order by 注入问题.
SQL 注入是 Web 应用中最常见也是最严重的漏洞之一。它允许攻击者通过将SQL命令插入到 Web 表单提交中实现,数据库中执行非法 SQL 命令。 永远不要信任用户的输入,特别是在拼接SQL语句时。我们应该对用户传入的不可控参数进行过滤.
通过这三个 RuoYi 项目中的代码案例,我们可以总结出项目开发中需要注意的几点
综上,写代码不仅仅是完成需求这么简单。我们还需要在各个细节上多加注意,对用户传入的参数要保持警惕,对 SQL 语句要谨慎拼接,对路径要严谨校验。定期代码审计可以尽早发现并修复项目漏洞,给用户更安全可靠的产品。希望通过这几个案例,可以提醒大家在代码编写过程中进一步加强安全意识.
到此本文讲解完毕,感谢大家阅读,感兴趣的朋友可以点赞加关注,你的支持将是我的更新动力😘.
公众号【waynblog】每周更新博主最新技术文章,欢迎大家关注 。
最后此篇关于项目讲解之常见安全漏洞的文章就讲到这里了,如果你想了解更多关于项目讲解之常见安全漏洞的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
所以我实现了ciphersaber-1 .它几乎可以工作,我可以解密 cstest1.cs1。但我无法让 cstest2.cs1 正常工作。 输出是: The Fourth Amendment to
更改 unsat 查询中断言的顺序后,它变为 sat。 查询结构为: definitions1 assertions1 definitions2 bad_assertions check-sat 我使
就目前情况而言,这个问题不太适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、民意调查或扩展讨论。如果您觉得这个问题可以改进并可能重新开放,visit
我的应用程序用于使用 wifi 进行实时视频流和录制(音频和视频)。使用以下依赖项: repositories { maven { url 'https://raw.github.com/iParse
我正在使用 Delphi,并且我想在我的应用程序中使用 ActiveX 组件(用于压缩)。这会让我的程序更容易受到病毒攻击吗?我的程序是一个备份应用程序,它使用 FTP 和套接字来传输文件和消息。我的
我用这个函数来防止SQL注入(inject) function cleanQuery($string) { if(get_magic_quotes_gpc()) // prevents du
最近我在向我的 friend 解释参数化及其优势,他问我在安全性方面它比 mysqli_escape_string 有什么好处。具体来说,您能想到尽管输入字符串被转义(使用 mysqli_escape
我想我在最新版本的 Highstock 中发现了一个错误: 定义了以下 RangeSelector: rangeSelector: { buttons: [{ typ
我在阅读有关 C 语言字符串中的漏洞的文章后,发现了这段代码。谁能给我解释为什么会这样?提前致谢。 int main (int argc, char* argv[]) { char a[16];
我最近浏览了 php 等的 emacs 模式,并决定选择 nXhtml。但是,我不断收到以下错误:每当我打开一个 html 文件时,整个文件都以蓝色突出显示。不用说,这很烦人。我认为这可能是因为我的
我被分配到我公司的一个遗留 Web 应用程序,在研究源代码一两天后,我发现了一个类似于以下内容的 SQL 注入(inject)向量: mysql_query("SELECT * FROM foo WH
在坚持代码分析错误的过程中,我正在将我的属性更改为具有私有(private) setter 。然后我开始尝试更多地了解为什么。根据一些研究,MS 说 this : A writable collect
我最近开始使用 AngularJS,我想我遇到了一个奇怪的错误。 首先,这是一些工作代码: 查看: Delete Num
阅读时djangobook chapter ,我遇到了提到 csrf 漏洞的部分,其中注销链接被放置在隐藏的恶意站点中。 在我使用 django 创建的 Web 应用程序中,我使用了类似的注销链接 基
以前,在我的应用程序中,我得到了一个 NPE,并且通过在 NullPointerException 处设置断点,可以获得中断、堆栈跟踪(在“调试”窗口中)以及“变量”窗口中的当前变量。 但是,现在其他
我下载了 sponza_obj.rar (sponza.obj + sponza.mtl) 和 sponza_textures.rar Crytek 网站。看起来缺少gi_flag.tga。我在哪里可
在我的应用程序中,我们有许多用户,他们都与不同的调用中心相关。在 URL 中,有一个向后的 hack,他们可以在 ? 后面输入 call_center=number。它将引导他们进入不同的调用中心数据
我正在建立一个带有 SQL 注入(inject)漏洞的网站用于测试目的。但是,我只想配置 SQL 盲注。我有这个 PHP 代码: NEW
我们有一个 ASP.NET/C# 网站。我们的开发人员在亚洲离岸,我刚刚发现他们一直在网站前端放置原始 SQL。 我担心我们现在容易受到 SQL 注入(inject)攻击。有谁知道我如何检测网站上的漏
我正在尝试从代码运行 Microsoft Rdp 应用程序。 我有以下伪代码,SonarQube 提示命令注入(inject)漏洞 String rdpFilePath = myObject.getR
我是一名优秀的程序员,十分优秀!