本文是根据关键词“漏洞|安全|封堵”从开源项目若伊提交记录的文字描述中筛选出来的。旨在介绍日常项目开发中需要注意的一些安全问题以及如何解决。
❝
项目安全是每个开发者需要关注的关键问题。如果项目漏洞太多,就容易出现黑客攻击和用户信息泄露的风险。本文将结合三个典型案例,讲解常见的安全漏洞及修复方案,帮助大家在项目开发中进一步提高安全意识。
❞
RuoYi项目地址:https://gitee.com/y_project/RuoYi博主github地址:https://github.com/wayn111,欢迎大家关注一、重置用户密码
若伊项目中有重置用户密码的接口。提交记录dd37524b之前的代码如下:
@ Log(title = & # 34;重设密码& # 34;,业务类型=业务类型。更新)@ post mapping(& # 34;/reset pwd & # 34;)@ ResponseBodypublic Ajax result reset pwd(SysUser用户){ user . set salt(shiroutils . random salt());user . set password(passwordservice . encrypt password(user . get loginname()、user.getPassword()、user . get salt());int rows = userservice . resetuserpwd(user);if(行数& gt0){ set sysuser(userservice . selectuserbyid(user . get userid()));返回成功();}返回错误();}可以看到界面会读取进来的用户信息,重置用户密码后,会根据进来的userId更新数据库和缓存。
这里有一个非常严重的安全问题,就是盲目信任传入的用户信息。如果攻击者通过一个接口构造一个请求,并在传入的用户参数中将userId设置为其他用户的userId,那么这个接口会导致一些用户的密码被重置,从而被攻击者掌握。
1.1 攻击流程
如果攻击者掌握了其他用户的userId和登录帐户名。
构造重置密码请求将 userId 设置未其他用户的 userId服务端根据传入的 userId 修改用户密码使用新的用户账号以及重置后的密码进行登录攻击成功1.2 如何解决
记录dd37524b的提交后,代码更新如下:
@ Log(title = & # 34;重设密码& # 34;,业务类型=业务类型。更新)@ post mapping(& # 34;/reset pwd & # 34;)@ ResponseBodypublic Ajax result reset pwd(String old password,String new password){ sys user user = getsys user();if(string utils . isnotempty(new password)& & passwordservice . matches(user,old password)){ user . setsalt(shiroutils . random salt());user . set password(passwordservice . encrypt password(user . get loginname()、newPassword、user . get salt());if(userservice . resetuserpwd(user)& gt;0){ set sysuser(userservice . selectuserbyid(user . get userid()));返回成功();}返回错误();} else {返回错误(& # 34;修改密码失败,旧密码错误& # 34;);}}解决方法其实很简单。不要盲目相信用户传入的参数,通过登录状态获取当前登录用户的userId。上面的代码通过getSysUser()方法获取当前登录用户的userId,然后根据userId重置密码。
二、文件下载
文件下载作为web开发中每个项目都会遇到的功能,相信大家都不陌生。若伊在提交记录18f6366f之前下载文件的逻辑如下:
@ get mapping(& # 34;常用/下载& # 34;)public void fileDownload(字符串文件名,布尔删除,HttpServletResponse响应,HttpServletRequest请求){ try { if(!file utils . is valid fileName(fileName)){ throw new Exception(string utils . format(& # 34;文件名({})非法,不允许下载。",文件名));} String real filename = system . current time millis()+filename . substring(filename . index of(& # 34;_") + 1);string file path = global . getdownloadpath()+fileName;response . set content type(media type。应用_八位组_流_值);fileutils . setattachmentresponseheader(response,real filename);FileUtils.writeBytes(filePath,response . get output stream());if(delete){ fileutils . delete file(file path);} } catch(异常e){ log . error(& # 34;无法下载文件& # 34;,e);} }公共类FileUtils{公共静态字符串FILENAME _ PATTERN = & # 34[a-zA-Z0-9_\-\|\。\ \ u4e 00-\ \ u9fa 5]+& # 34;;public static boolean is valid FILENAME(字符串文件名){ return filename.matches(文件名_模式);}}你可以看到,在代码中下载一个文件时,它会判断文件名是否合法。如果不合法,会提示“文件名({})不合法,不允许下载。的话”。乍一看,似乎没有问题。博主公司项目里下载的文件也有这种类似的代码。传入下载文件的名称,然后在指定的目录下找到要下载的文件,通过streaming写回客户端。
既然如此,我们来看看投稿记录18f6366f的描述信息。
不知道是不是没看,第一眼就惊呆了。原来,在本次提交之前,项目存在任意文件下载漏洞。在这里,博主给大家解释一下为什么会有任意文件下载漏洞。
2.1 攻击流程
如果下载目录是/data/upload/
构造下载文件请求设置下载文件名称为:../../home/重要文件.txt服务端将文件名与下载目录进行拼接,获取实际下载文件的完整路径为 /data/upload/../../home/重要文件.txt由于下载文件包含 「..」 字符,会执行上跳目录的逻辑上跳目录逻辑执行完毕,实际下载文件为 /home/重要文件.txt攻击成功2.2 如何解决
我们来看看投稿记录18f6366f主要是做什么的。代码如下:
@ get mapping(& # 34;常用/下载& # 34;)public void fileDownload(字符串文件名,布尔删除,HttpServletResponse响应,HttpServletRequest请求){ try { if(!fileutils . checkallowdownload(fileName)){ throw new Exception(string utils . format(& # 34;文件名({})非法,不允许下载。",文件名));} String real filename = system . current time millis()+filename . substring(filename . index of(& # 34;_") + 1);string file path = global . getdownloadpath()+fileName;response . set content type(media type。应用_八位组_流_值);fileutils . setattachmentresponseheader(response,real filename);FileUtils.writeBytes(filePath,response . get output stream());if(delete){ fileutils . delete file(file path);} } catch(异常e){ log . error(& # 34;无法下载文件& # 34;,e);} }}public class FileUtils{ /** *检查文件是否可以下载* * @param需要下载的资源文件* @ return true normal false launch */Public static Boolean checkallow download(String resource){//禁止目录跳转级别if (String utils。包含(资源,& # 34;..")){返回false}//检查文件规则是否(array utils . contains(mime类型utils。default _ allowed _ extension,filetypeutils。get filetype (resource))允许下载{ return true}//不允许下载文件规则返回false}} …public static final string[]default _ allowed _ extension = {//picture & # 34;bmp & # 34, "gif & # 34, "jpg & # 34, "jpeg & # 34, "巴新& # 34;,//word excel PowerPoint & # 34;doc & # 34, "docx & # 34, "xls & # 34, "xlsx & # 34, "ppt & # 34, "pptx & # 34, "html & # 34, "htm & # 34, "txt & # 34,//压缩文件& # 34;rar & # 34, "zip & # 34, "gz & # 34, "bz2 & # 34,//视频格式& # 34;mp4 & # 34, "i & # 34, "rmvb & # 34,//pdf & # 34;pdf & # 34};…公共类FileTypeUtils{ /** * <文件类型* < p & gt*例如:ruoyi.txt,return: txt * * @param fileName文件名* @return后缀(不含& # 34;。")*/public static String getFileType(String fileName){ int separator index = fileName . lastindexof(& # 34;。");if(separator index & lt;0){ return & # 34;";}返回filename . substring(separator index+1)。toLowerCase();}}可以看出,在提交记录18f6366f中,fileutils。isvalid filename(文件名)方法在下载文件时被fileutils替换。checkallowdownload(文件名)方法。此方法检查文档名参数是否包含“…”以防止目录跳转,然后检查文档名称是否在白名单中。这样,任何文件下载漏洞都可以避免。
❝
路径遍历允许攻击者通过操纵路径的可变部分来访问目录和文件的内容。在处理文件上传、下载等操作时,需要严格检查路径参数,防止目录遍历漏洞。
❞
三、分页查询排序参数
若伊项目作为后台管理项目,几乎每个菜单都使用分页查询,所以封装了分页查询类PageDomain,其他的会读取客户端传入的orderByColumn参数。在提交记录807b7231之前,分页查询代码如下:
公共类PageDomain{…public void setOrderByColumn(String orderByColumn){ this . orderByColumn = orderByColumn;} …}/* * Set request分页数据*/public静态void start page(){ pagedomainpagedomain = tablesupport . build page request();integer pageNum = page domain . get pageNum();integer pageSize = page domain . get pageSize();string order by = page domain . getorderby();布尔合理= page domain . get reasonable();PageHelper.startPage(pageNum,pageSize,orderBy)。setReasonable(合理);}/* * *分页查询*/@所需权限(& # 34;system:post:list & # 34;)@ post mapping(& # 34;/list & # 34;)@ ResponseBodypublic table datainfo list(sys post post){ start page();列表& ltSysPost & gtlist = postservice . selectpostlist(post);返回get datatable(list);}大家可以看到,分页查询通常直接使用封装的startPage()方法,PageDomain的orderByColumn属性会直接放入PageHelper,最后会拼接成实际的SQL查询语句。
3.1 攻击流程
如果攻击者知道用户表名是users,
构造分页查询请求传入 orderByColumn 参数为 1; DROP TABLE users;实际执行的 SQL 可能为:SELECT * FROM users WHERE username = 'admin' ORDER BY 1; DROP TABLE users;执行 SQL,DROP TABLE users; 完毕,users 表被删除攻击成功3.2 如何解决
再次提交记录807b7231后,排序参数被转义。最新的代码如下。
公共类PageDomain{…public void setOrderByColumn(String orderByColumn){ this . orderByColumn = sqlutil . escape SQL(orderByColumn);} }}/** * sql操作工具类* * @ authorruoyi */public class SQL util {/* *仅支持字母、数字、下划线、空网格、逗号、小数点(可排序多个字段)*/public静态字符串SQL _ pattern = & # 34[a-zA-Z0-9_\\,\ \。]+";/* * *检查字符以防止注入绕过*/public static String escape order by SQL(String value){ if(String utils。不为空(值)&!isValidOrderBySql(value)){ throw new UtilException(& # 34;参数不符合规范,无法查询& # 34;);}返回值;}/* * *验证order by语法是否符合规范*/public static Boolean is valid order by SQL(字符串值){返回值。matches(SQL _ pattern);} …}可以看到order by语句后可以拼接的字符串是有规律匹配的,只支持字母、数字、下划线、空网格、逗号、小数点(支持多字段排序)。这样就可以避免在order by后面拼接其他非法字符,比如drop|if()|union等等,从而避免order by注入问题。
❝
SQL注入是Web应用程序中最常见、最严重的漏洞之一。它允许攻击者通过在Web表单提交中插入SQL命令,并在数据库中执行非法SQL命令来实现这一目的。千万不要相信用户输入,尤其是拼接SQL语句的时候。我们应该过滤掉用户传入的不可控参数。
❞
四、总结
通过这三个若夷项目中的代码案例,我们可以总结出一些项目开发中应该注意的点:
不要盲目相信用户传入的参数。无论是修改密码还是文件下载,都不应该直接使用用户传入的参数构造 SQL 语句或拼接路径,这会导致 SQL 注入及路径遍历等安全漏洞。我们应该根据实际业务获取真实的用户 ID 或其他参数,然后再进行操作。SQL 参数要进行转义。在拼接 SQL 语句时,对用户传入的不可控参数一定要进行转义,防止 SQL 注入。路径要进行校验。在处理文件上传下载等操作时,对路径参数要进行校验,防止目录遍历漏洞。例如判断路径中是否包含 「..」 字符。接口要设置权限。对一些敏感接口,例如重置密码,我们需要设置对应的权限,避免用户越权访问。记录提交信息。在记录提交信息时,最好详细描述本次提交的内容,例如修复的漏洞或新增的功能。这在后续代码审计或回顾项目提交历史时会很有帮助。定期代码审计。作为项目维护人员,我们需要定期进行代码审计,找出项目中可能存在的漏洞,并及时修复。这可以最大限度地保证项目代码的安全性与健壮性。
总而言之,写代码不仅仅是完成需求。我们还需要更加注意各种细节,警惕用户传入的参数,仔细拼接SQL语句,仔细检查路径。定期的代码审计可以尽早发现并修复项目漏洞,给用户更安全可靠的产品。希望通过这些案例,提醒大家在代码编写过程中进一步加强安全意识。
本文完毕,感谢您的阅读。感兴趣的朋友可以喜欢一下,关注一下。你们的支持将是我更新的动力。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。