赞
踩
MVC模式是一种软件框架模式,被广泛应用在JavaEE项目的开发中。
MVC即模型(Model)、视图(View)、控制器(Controller)。
模型是用于处理数据逻辑的部分。
所谓数据逻辑,也就是数据的映射以及对数据的增删改查,Bean、DAO(dataaccess object,数据访问对象)等都属于模型部分。
视图负责数据与其它信息的显示,也就是给用户看到的页面。
HTML、JSP等页面都可以作为视图。
控制器是模型与视图之间的桥梁,控制着数据与用户的交互。
控制器通常负责从视图读取数据,处理用户输入,并向模型发送数据,也可以从模型中读取数据,再发送给视图,由视图显示。
首先要了解项目整体结构。大致了解作者编写逻辑,搞清请求流程。
src/main下面有两个目录,分别是java和resources,java目录中主要存放的是java代码,resources目录中主要存放的是资源文件,比如:html、js、css等。
annotation:放置项目自定义注解;
controller/:存放控制器,接收从前端传来的参数,对访问控制进行转发、各类基本参数校验或者不复用的业务简单处理等;
dao/:数据访问层,与数据库进行交互,负责数据库操作,在Mybaits框架中存放自定义的Mapper接口;
entity/:存放实体类;
interceptor/:拦截器;
service/:存放服务类,负责业务模块逻辑处理。Service层中有两种类,一是Service,用来声明接口;二是ServiceImpl,作为实现类实现接口中的方法;
utils/:存放工具类;
dto/:存放数据传输对象(DataTransfer Object),如请求参数和返回结果;
vo/:视图对象(ViewObject)用于封装客户端请求的数据,防止部分数据泄漏,保证数据安全
constant/:存放常量;
filter/:存放过滤器。
mapper/:存放Mybaits的mapper.xml文件;
static/:存放静态资源文件目录(Javascript、CSS、图片等),在这个目录中的所有文件可以被直接访问;
templates/:存放模版文件;
application.properties或application.yml:Spring Boot默认配置文件。
用户请求URL发送到服务器,服务器解析请求后发送到后端代码处理请求。
在后端代码处,首先经过Filter(过滤器)和Interceptor(拦截器),然后根据请求的URL映射到绑定的Controller,之后调用Service接口类,然后再调用serviceImpl接口实现类,最后调用DAO。
controller:负责简单的逻辑处理和参数校验功能,之后调用Service;
service:接口类,主要负责业务模块逻辑处理;
serviceImpl:接口实现类,实现类实现service接口中的方法;
DAO:如果service涉及数据库操作就会调用DAO。DAO主要处理数据库操作。DAO只做中间传递角色
@Controller 注解:标注该类为controller类,可以处理http请求。
@Controller一般要配合模版来使用。现在项目大多是前后端分离,后端处理请求,然后返回JSON格式数
据即可,这样也就不需要模板了。
@ResponseBody 注解:将该注解写在类的外面,表示这个类所有方法的返回的数据直接给浏览器。
@RestController 相当于 @ResponseBody 加上 @Controller
@RequestMapping 注解:配置URL映射 ,可以作用于某个Controller类上,也可以作用于某Controller类下的具体方法中,说白了就是URL中请求路径会直接映射到具体方法中执行代码逻辑。
@RequestParam 注解:将请求参数绑定到你控制器的方法参数上(是springmvc中接
收普通参数的注解),常用于POST请求处理表单。
@PathVariable 注解:接受请求URL路径中占位符的值
如下代码
@Controller
@ResponseBody
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/whoami/{name}/{sex}")
public String hello(@PathVariable("name") String name,@PathVariable("sex") String sex){
return "Hello" + name + sex;
}
}
方法 | 说明 |
---|---|
getParameter | request类获取参数方法 |
getParameterNames | 获取参数名 |
getParameterValues | 获取参数值 |
getParameterMap | 获取参数map类型 |
getQueryString | 获取URL的value值 |
getHeader | 获取http请求头 |
getHeaderNames | 获取请求头名 |
getRequestURI | 获取请求URL |
getCookies | 获取cookie |
getRequestedSessionId | 获取sessionid |
getInputStream | 获取输入数据 |
getReader | 获取请求内容 |
getMethod | 获取请求方法 |
getProtocol | 获取请求协议 |
getServerName | 获取服务名 |
getRemoteUser | 获取当前缓存的用户 |
getUserPrincipal | 获取用户指纹 |
方法 | 说明 |
---|---|
java.io.FileInputStream | 文件输入 |
java.io.FileOutputStream | 文件输出 |
java.io.FileReader | 文件读取 |
java.io.FileWriter | 文件写入 |
maven看pom.xml,整理出来第三方组件以及版本号,并说明存在漏洞组件
框架相关:S2、shiro、Spring
中间件相关:JBoss、Weblogic、Jenkins
Java库相关:Fastjson、Jackson
第三方编辑器:UEditor、KindEditor、FCKeditor
用脚本PomEye-main
python3 main.py
这里需要清楚一个概念,过滤器在任何框架都可以使用,而拦截器是Spring MVC独有的。
而过滤器需要配置在web.xml 里面,而拦截器会配置在springmvc.xml文件里面
搜索代码:doFilter
如xss过滤器
(1)web.xml文件 :
<filter>
<filter-name>xssFilter</filter-name>
<filter-class>com.test.filter.xssFiler</filter-class>
</filter>
<!-- 解决xss漏洞 -->
<filter-mapping>
<filter-name>xssFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
(2)Filter代码:
package com.test.filter; import com.test.utils.XssFilterWrapper; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * 作用:Xss过滤器 * 作者:Tiddler * 时间:2018/11/11 10:21 * 类名: XssFilter **/ public class xssFiler implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //使用包装器 System.out.println("过滤器执行了"); XssFilterWrapper xssFilterWrapper=new XssFilterWrapper((HttpServletRequest) servletRequest); filterChain.doFilter(xssFilterWrapper,servletResponse); } @Override public void destroy() { } }
(3)XssFilterWrapper代码:
package com.test.utils; import org.springframework.web.util.HtmlUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * 作用:防Xss过滤器[包装器] * 作者:Tiddler * 时间:2018/11/11 10:20 * 类名: XssFilterWrapper **/ public class XssFilterWrapper extends HttpServletRequestWrapper { public XssFilterWrapper(HttpServletRequest request) { super(request); } /** * 对数组参数进行特殊字符过滤 */ @Override public String[] getParameterValues(String name) { if("content".equals(name)){//不想过滤的参数,此处content参数是 富文本内容 return super.getParameterValues(name); } String[] values = super.getParameterValues(name); String[] newValues = new String[values.length]; for (int i = 0; i < values.length; i++) { newValues[i] = HtmlUtils.htmlEscape(values[i]);//spring的HtmlUtils进行转义 } return newValues; } }
(1)SpringBoot Actuator 未授权访问
(2)配置文件中可能会存在数据库或其他组件的连接信息
Druid 登陆暴力破解
搜索代码:WebMvcConfigurer
搜索代码:@(.*?)Mapping\(
从controller接口入手,找到外部可控的参数,并跟踪参数是否传入到危险方法中
找危险函数,跟踪参数是否从外部传入,判断是否有做严格的参数校验和过滤
白盒测试
配置文件都在src/main/resources下面,名字通常为application.yml或者
application.properties
pom依赖:spring-boot-starter-actuator
配置文件:management.endpoints
/actuator/heapdump
可用jvisualvm.exe获取数据库密码
select s.value.toString() from java.util.Hashtable$Entry s where /password/.test(s.key.toString())
select s.value.toString() from java.lang.String s where /pass/.test(s.value.toString())
select s from java.lang.String s where /pass/.test(s.value.toString())
获取Shirokey
org.apache.shiro.web.mgt.CookieRememberMeManager
整改方案
①.引入 security 依赖,打开安全限制,或禁用不需要接口
endpoints.env.enabled=false
②.去除可访问文件
白盒测试
pom依赖:springfox-swagger-ui
整改方案
①.加上enable(false)或者将SwaggerConfig给注释掉,弊端就是你自己也访问不了接口文档
.enable(false)
②.添加认证授权机制: 在Swagger配置中添加认证授权机制,确保只有经过授权的用户才能访问Swagger页面。您可以使用基本身份验证、OAuth、API密钥等方式来实现认证。
③.限制访问权限: 通过配置服务器,限制只有特定IP范围或者需要登录后才能访问Swagger页面。这可以减少未经授权的访问。
④.移除生产环境不必要的Swagger文档: 如果您的应用程序是在生产环境部署的,可以考虑在生产环境中禁用Swagger文档或隐藏它以防止未经授权的访问。
白盒测试
配置文件:druid
整改方案
https://www.python100.com/html/ZTO76WX5F640.html
在过滤器或者拦截器里获取请求,然后request.getSession(),通过请求携带的sessionID获取对应服务器上的Session,如果没有携带,则Session为null,重新创建一个Session,此时通过session里有无信息就可以判断此用户是否拥有权限
鉴权过后可以重定向到其他页面完成对应的访问或者登陆操作
例子:存在漏洞如下,只要包含路径/admin/login就可以未授权访问
例子:漏洞主要发生于第 23 行和第 24 行,下面我们分析下漏洞成因。
首先,关键点是第 23 行,使用了 request.getRequestURI() 方法获取路径。如果使用该方法获取的路径 进行权限判断是极易出现权限绕过漏洞的。
简单来说, getRequestURI 方法返回的路径是未经过服务器端处理的原始路径,可能包含特殊字符或 路径跳转,从而绕过服务器端的安全控制。 可改成getRequestURL方法:getRequestURL()函数可以帮助获取更准确且经过处理的请求 URL,从而减少特殊字符或路径跳转等问题
其次,第 24 行使用了 uri.startsWith(“/admin”) 判断 Uri 路径中是否以 /admin 开头,以及获取并判断 Session 中的 loginUser 属性是否为 null,两个条件 && 在一起结果为 True 的话进入条件代码,提示需要登录并跳转到后台登录页面中。
既然这样,我们知道 a && b 需要两者都为 True 整体则为 True 才会进入条件判断代码中,如果另其中 一个条件为 False 则整体就为 False,就不会进入条件判断中去了。
这两个条件中,Session 部分我们是没办法操纵的。但 uri.startsWith(“/admin”) 这个条件我们可以搞点 小破坏,前面提到了 uri 是使用的 getRequestURI 方法获取的原始路径,那么我们可以找一些特殊字符 绕过路径判断,并且不影响整体接口,比如:分号 ; ,正斜杠 / 等等。
最终,构造结构路径为 /;/admin/test 或 ///admin/test ,这样路径就不是以 /admin 开头了,并且 该路径不会影响结构访问,实现了权限绕过。
Spring Security 可以通过 http.authorizeRequests() 开启对 web 请求进行授权保护。
url匹配
antMatchers()
使用 antMatchers 方法需要注意配置规则的顺序,配置顺序会影响授权的效果,越是具体的应该放在前面,越是笼统的应该放到后面。
antMatchers(“/cxyxj/**”).hasRole(“admin”):表示访问/cxyxj/路径的必须要有 admin 角色。
antMatchers(“/security/**”).hasRole(“user”):表示访问/security/路径的必须要有 user 角色。
.antMatchers(“/permitAll”).permitAll():表示访问/permitAll 路径不需要认证
.anyRequest().authenticated():表示除了前面定义的url,其余url访问都得认证后才能访问(登录)
and:表示结束当前标签,回到上下文 HttpSecurity,开启新一轮的配置
formLogin:开启表单登陆
@Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("cxyxj") .password("123").roles("admin", "user") .and() .withUser("security") .password("security").roles("user"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //开启配置 .antMatchers("/cxyxj/**").hasRole("admin") //访问/cxyxj/**下的路径,必须具备admin身份 .antMatchers("/security/**").hasRole("user") //访问/security/**下的路径,必须具备user身份 .antMatchers("/permitAll").permitAll() // 访问/permitAll路径,不需要登录 .anyRequest() //其他请求 .authenticated()//验证 表示其他请求只需要登录就能访问 .and() .formLogin(); // 开启表单登陆 }
Java里面常见的数据库连接方式也就那么几个,分别是JDBC,Mybatis,和Hibernate
JDBC的最原始的连接代码,如下demo
@WebServlet("/demo") public class domain extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("get访问"); String id = req.getParameter("id"); Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/demo", "root", "root"); String sql = "select * from users where id = '"+id+"' "; Statement statement = conn.createStatement(); ResultSet resultSet = statement.executeQuery(sql); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doGet(req,resp); } }
白盒测试
statement
executeQuery
整改方案
JDBC 预编译:prepareStatement
Connection conn = JDBCUtils.getConnection();
String sql = "select * from users where username = ? and password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql); //使用预编译传入sql语句
pstmt.setString(1,username); //设置第一个参数为username
pstmt.setString(2,password); //设置第二个参数为password
pstmt.executeQuery();
Mybatis获取值的方式有两种,分别是${} 和 #{}
#{}:解析的是占位符问号,可以防止SQL注入,使用了预编译。
${}:直接获取值
映射文件的命名规则:表所对应的实体类的类名+Mapper.xml
白盒测试
在*Mapper.xml文件搜索${
跟踪如下
从 UserMapper.xml 跳到 UserDao.java
点ctrl健+右键,看谁引用了
查看传参是否存在nickName、userName
接口/api/user,存在sql注入
整改方案
三个特殊情况
错误代码
<select id="findlike" resultType="com.test.domain.User" parameterType="string">
select * from user where name like '%${name}%',
</select>
正确代码
安全的写法应当使用 CONCAT 函数连接通配符
<select id="findlike" resultType="com.test.domain.User" parameterType="string">
select * from user where name like concat('%',#{name},'%')
</select>
错误代码
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username in (${usernames})
</select>
正确代码
安全的做法应当使用 foreach 标签
<select id="getUserFromList" resultType="user.NewUserDO">
select * from user_table where username in
<foreach collection="list" item="username" open="(" separator="," close=")">
#{username}
</foreach>
</select>
错误代码
Select * from news where title ='#{titlename}' order by ${time} asc
正确代码
安全的做法应当在 Java 代码层面来进行解决
可以设置一个字段值的白名单,仅允许用户传入白名单内的字段
白盒测试
一般先找是否有XSS过滤器,如果存在看是否能绕过,以及它部署的范围
request.getParameter(param)或${param}获取用户的输入信息
getParameter
param.
<%=
JSP表达式
"<%=变量%>“是”<%out.println 变量;%>“的简写方式,”<%=%>"用于将已声明的变量或表达式输出到外网页中
通过"request.getParameter"获取msg传入的值,然后通过"<%=msg%>"将其输出到网页中
<% String msg = request.getParameter("msg");%>
<%= msg %>
例子1、全局搜索数据库的插入语句(关键词:insert,save,update),然后找到该插入语句所属的方
法名如(insertUser()),然后全局搜索该方法在哪里被调用,一层层的跟踪。直到
getParamter()方法获取请求参数的地方停止,如果没有全局 XSS 过滤器,跟踪的整个流
程都没有对获取的参数过滤,则存在存储型XSS。
例子2. 从getParamter 关键词开始 ,跟踪请求参数,直到插入数据库的语句,如果中间没有过
滤参数,则存在存储型XSS。
代码中45行和46行获取usertype和name的值,然后在56行存进数据库由于没有过
滤传进来的参数,所以会在显示时出来触发XSS
在前端源码搜索
document.location
document.referer
document.url
整改方案
①、修复XSS攻击,一个有效的方案是将特殊字符做转义,对所有字符采用HTML实体编码
private static String XssFilter(String content) {
content = StringUtils.replace(content, "&", "&");
content = StringUtils.replace(content, "<", "<");
content = StringUtils.replace(content, ">", ">");
content = StringUtils.replace(content, "\", """);
content = StringUtils.replace(content, "'", "'");
content = StringUtils.replace(content, "/", "/");
return content;
}
②、采用Spring自带的方法会对特殊字符全转义,使用htmlEscape方法
@GetMapping("/safe1")
public static String safe1(String content) {
return HtmlUtils.htmlEscape(content);
}
③、对于富文本编辑器的XSS防御,一般采用白名单标签的方法,因为针对富文本的处理方式,需保留部分标签可以被解析使用
public static String safe3(String content) {
Safelist whitelist = (new Safelist())
.addTags("p", "hr", "div", "img", "span", "textarea") // 设置允许的标签
.addAttributes("a", "href", "title") // 设置标签允许的属性, 避免如nmouseover属性
.addProtocols("img", "src", "http", "https") // img的src属性只允许http和https开头
.addProtocols("a", "href", "http", "https");
return Jsoup.clean(content, whitelist);
}
④、编写全局过滤器实现拦截,并在web.xml进行配置
表单中的enctype
application/x-www-form-urlencoded:默认编码方式,只处理表单中的value属性值,这种编码方式会将表单中的值处理成URL编码方式
multipart/form-data:multipart/form-data这种编码方式的表单会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数里。通常会见到配合method=post去搭配使用,而后端采取inputstream等方式读取客户端传入的二进制流来处理文件
text/plain:把空格转换为+ ,当表单action属性为mailto:URL形式时比较方便,适用于直接通过表单发送邮件方式
SpringBoot对JSP是有做限制的
SpringBoot项目审计时如果想要查看是否对JSP完全解析,可以从熟悉的 pom.xml 文件下手, 查看是否引入了相关依赖
<!--用于编译jsp-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
JSP木马
<%
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a;
byte[] b = new byte[1024];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.println(new String(b,0,a));
}
%>
00截断问题
PHP中:PHP<5.3.29,且GPC关闭
Java中:同时考虑到00截断绕过的问题,在JDK1.7.0_40(7u40)开始对\00进行了检查
final boolean isInvalid(){
if(status == null){
status=(this.path.indexOf('\u0000')<0)?PathStatus.CHECKED:PathStatus.INVALID;
}
return status == PathStatus.INVALID;
}
白盒测试
DiskFileItemFactory
@MultipartConfig
MultipartFile
File
upload
InputStream
OutputStream
write
fileName
filePath
getOriginalFilename() :方法获取上传时的文件名
@RequestMapping("/upload1") public String fileUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException { String path = request.getServletContext().getRealPath("upload"); String filename = file.getOriginalFilename(); if (file.isEmpty()) { return "请上传文件"; } try { OutputStream fos = new FileOutputStream(path + "/" + filename); InputStream fis = file.getInputStream(); int len; while ((len = fis.read()) != -1) { fos.write(len); } fos.flush(); fos.close(); fis.close(); return "Success!"; } catch (FileNotFoundException e) { e.printStackTrace(); } return ""; }
String getOriginalFilename():获取上传文件的原名
InputStream getInputStream():获取文件流
void transferTo(File dest):将上传文件保存到一个目录文件中
String getContentType():获取上传文件的MIME类型
@RequestMapping("/file2") public String MultiFileUpload(@RequestParam("file") MultipartFile file ,HttpServletRequest request) { if (file.isEmpty()) { return "请上传文件"; } String filePath = request.getServletContext().getRealPath("upload"); String fileName = file.getOriginalFilename(); File dest = new File(filePath + File.separator + fileName); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } try { file.transferTo(dest); return "Success!"; } catch (IOException e) { e.printStackTrace(); } return ""; }
若要对上传内容进行限制则可设置:
springboot
spring:
servlet:
multipart:
enabled: true
# 单文件大小
max-file-size: 100MB
# 文件达到多少磁盘写入
file-size-threshold: 4MB
springmvc
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 需要与jsp中的pageEncoding配置一致,默认为iso-8859-1-->
<property name="defaultEncoding" value="utf-8"/>
<!-- 单文件大小,单位为字节10485700=100M-->
<property name="maxUploadSize" value="10485700"/>
<!-- 文件达到多少磁盘写入-->
<property name="maxInMemorySize" value="409600"/>
</bean>
基于Commons-FileUpload组件
pom依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
Springboot环境需关闭multipart
spring:
servlet:
multipart:
enabled: false
创建步骤
创建磁盘工厂:DiskFileItemFactory factory = new DiskFileItemFactory();
创建处理工具:ServletFileUpload upload = new ServletFileUpload(factory);
设置上传文件大小:upload.setFileSizeMax(3145728);
接收全部内容:List items = upload.parseRequest(request);
@RequestMapping("/upload3") protected void ServletFileUpload(HttpServletRequest request, HttpServletResponse response) throws IOException { { //设置文件上传路径 String filePath = request.getServletContext().getRealPath("upload"); File uploadFile = new File(filePath); //若不存在该路径则创建之 if (!uploadFile.exists() && !uploadFile.isDirectory()) { uploadFile.mkdir(); } try { //创建一个磁盘工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //创建文件上传解析器 ServletFileUpload fileupload = new ServletFileUpload(factory); //三个照顾要上传的文件大小 fileupload.setFileSizeMax(3145728); //判断是否为multipart/form-data类型,为false则直接跳出该方法 if (!fileupload.isMultipartContent(request)) { return; } //使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项 List<FileItem> items = fileupload.parseRequest(request); for (FileItem item : items) { //isFormField方法用于判断FileItem类对象封装的数据是否属于一个普通表单字段,还是属于一个文件表单字段,如果是普通表单字段则返回true,否则返回false。 if (item.isFormField()) { String name = item.getFieldName(); //解决普通输入项的数据的中文乱码问题 String value = item.getString("UTF-8"); String value1 = new String(name.getBytes("iso8859-1"), "UTF-8"); System.out.println(name + " : " + value); System.out.println(name + " : " + value1); } else { //获得上传文件名称 String fileName = item.getName(); System.out.println(fileName); if (fileName == null || fileName.trim().equals("")) { continue; } //注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt //处理获取到的上传文件的文件名的路径部分,只保留文件名部分 fileName = fileName.substring(fileName.lastIndexOf(File.separator) + 1); //获取item中的上传文件的输入流 InputStream is = item.getInputStream(); FileOutputStream fos = new FileOutputStream(filePath + File.separator + fileName); byte buffer[] = new byte[1024]; int length = 0; while ((length = is.read(buffer)) > 0) { fos.write(buffer, 0, length); } is.close(); fos.close(); item.delete(); } } response.getWriter().write("Success!"); } catch (FileUploadException e) { e.printStackTrace(); } } }
④、Servlet Part上传
Servlet3之后,有提出了request.getParts()获取上传文件的方式
String getName():获取这部分的名称,例如相关表单域的名称
String getContentType():如果Part是一个文件,那么将返回Part的内容类型,否则返回null(可以利用这一方法来识别是否为文件域)
Collection getHeaderNames():返回这个Part中所有标头的名称
String getHeader(String headerName):返回指定标头名称的值
void write(String path):将上传的文件写入服务器中项目的指定地址下,如果path是一个绝对路径,那么将写入指定的路径,如果path是一个相对路径,那么将被写入相对于location属性值的指定路径。
InputStream getInputStream():以inputstream的形式返回上传文件的内容
@RequestMapping("/upload4") public void ServletPartUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String filePath = request.getServletContext().getRealPath("upload"); File uploadFile = new File(filePath); //若不存在该路径则创建之 if (!uploadFile.exists() && !uploadFile.isDirectory()) { uploadFile.mkdir(); } //通过表单中name属性值,获取filename Part part = request.getPart("file"); if(part == null) { return ; } String filename = filePath + File.separator + part.getSubmittedFileName(); part.write(filename); part.delete(); }
整改方案
①、content-type白名单
//1、MIME检测
String contentType = file.getContentType();
String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"};
Boolean ctFlag = false;
for (String suffix:white_type){
if (contentType.equalsIgnoreCase(suffix)){
ctFlag = true;
break;
}
}
if (!ctFlag){
return "content-type not allow";
}
②、重命名文件
可以用uuid、md5、时间戳等方式
//2、重命名文件
String uuid = UUID.randomUUID().toString();
fileName = uuid+fileName.substring(fileName.lastIndexOf("."));;
③、后缀白名单
//3、后缀白名单
String fileSuffix = fileName.substring(fileName.lastIndexOf("."));
String[] white_suffix = {"gif","jpg","jpeg","png"};
Boolean fsFlag = false;
for (String suffix:white_suffix){
if (contentType.equalsIgnoreCase(fileSuffix)){
fsFlag = true;
break;
}
}
if (!fsFlag){
return "suffix not allow";
}
转载:https://blog.csdn.net/weixin_54902210/article/details/127700409
C:\boot.ini //查看系统版本
C:\Windows\System32\inetsrv\MetaBase.xml //IIS配置文件
C:\Windows\repair\sam //存储系统初次安装的密码
C:\Program Files\mysql\my.ini //Mysql配置
C:\Windows\php.ini //php配置信息
C:\Windows\win.ini //Windows系统的一个基本系统配置文件
C:\Windows\my.ini //Mysql配置信息
Linux文件
/etc/httpd/conf/httpd.conf /etc/rc.local:有时可以读出来apache的路径 /usr/local/apache/conf/httpd.conf /var/www/html/apache/conf/httpd.conf /home/httpd/conf/httpd.conf /usr/local/apache2/conf/httpd.conf /usr/local/httpd/conf/httpd.conf /etc/apache/httpd.conf /usr/local/lib/php.ini /etc/hosts.deny:定义禁止访问本机的主机 /etc/bashrc:bash shell 的系统全局配置 /etc/group:系统用户组的定义文件 /etc/httpd/httpd.conf /etc/issue:显示Linux核心的发行版本信息(用于本地登陆用户) /etc/issue/net:显示Linux核心和发行版本信息(用于远程登陆用户)----没成功 /etc/ssh/ssh_config:ssh配置文件 /etc/termcap:终端定义和配置文件 /etc/xinetd.d /etc/mtab:包含当前安装的文件系统列表 有时可以读取到当前网站的路径 /etc/vsftpd/vsftpd.conf /etc/xinetd.conf xinetd:配置文件 /etc/protocols:列举当前可用的协议 /etc/logrotate.conf:维护 /var/log 目录中的日志文件 /etc/ld.so.conf:“动态链接程序”(Dynamic Linker)的配置。 /etc/wgetrc:Linux操作系统用户配置文件 /etc/passwd /etc/shadow /etc/inputrc:DNS客户机配置文件,设置DNS服务器的IP地址及DNS域名 /etc/resolv.conf:内容为Default Router的ip地址Redhat 5.x /etc/sysconfig/network /etc/sendmail.cf:(Linux) Sendmail(EMAIL服务器)配置文件 /etc/sendmail.cw:本地主机名 /etc/my.cnf:mysql配置文件 /root/.ssh/id_rsa:ssh私钥,ssh公钥是id_rsa.pub /root/.ssh/id_ras.keystore:记录每个访问计算机用户的公钥 /root/.ssh/known_hosts:记录每个访问计算机用户的公钥 /root/.bash_history:用户历史命令记录文件 /root/.mysql_history:mysql历史命令记录文件 /porc/config.gz:内核配置文件 /var/lib/mlocate/mlocate.db:全文件路径 /porc/self/cmdline:当前进程的cmdline参数
白盒测试
fileName
filePath
getFile
getWriter
例子1:
@WebServlet("/readServlet") public class readServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String filename = request.getParameter("filename"); File file = new File(filename); OutputStream outputStream = null; InputStream inputStream = new FileInputStream(file); int len; byte[] bytes = new byte[1024]; while(-1 != (len = inputStream.read())) { outputStream.write(bytes,0,len); } } }
例子2
@RequestMapping("/ReadBufferedReader") public void readBufferedReader(String fileName, HttpServletResponse response) throws IOException{ File file = new File(fileName); FileInputStream fis = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(isr); String line; //将注释去掉,重新运行启动项目,在浏览器键入要读取的文件地址,观察下效果有什么不一样。 //response.reset(); //response.setContentType("application/octet-stream"); //response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8")); PrintWriter out = response.getWriter(); System.out.println("使用BufferedReader读取文本文件......"); while((line = br.readLine()) != null){ System.out.println(line); out.print(line); } br.close(); }
在这段代码中,readBufferedReader方法接收一个fileName参数并尝试使用BufferedReader读取文件内容然后输出到响应中。原始代码存在任意文件读取漏洞,因为用户可以通过fileName参数传入任意文件路径,从而读取敏感文件
整改方案
@RequestMapping("/SecureReadBufferedReader") public void secureReadBufferedReader(String fileName, HttpServletResponse response) throws IOException { String baseDirectory = "/path/to/secure/directory/"; String filePath = baseDirectory + fileName; // 规范化并检查文件路径是否安全 Path fullPath = Paths.get(filePath).normalize(); if (!Paths.get(baseDirectory).toAbsolutePath().relativize(fullPath).equals(fullPath) || hasUnsafePathElements(fullPath)) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); return; } File file = new File(filePath); if (!file.exists()) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } FileInputStream fis = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(isr); response.setContentType("text/plain; charset=utf-8"); PrintWriter out = response.getWriter(); System.out.println("使用BufferedReader读取文本文件......"); String line; while ((line = br.readLine()) != null) { System.out.println(line); out.println(line); } br.close(); } // 检查路径是否包含不安全的元素 private boolean hasUnsafePathElements(Path path) { for (Path element : path) { if (element.toString().contains("..")) { return true; } } return false; }
白盒测试
HttpURLConnection. getInputStream
URLConnection. getInputStream
Request.Get. execute
Request.Post. execute
URL.openStream
ImageIO.read
OkHttpClient.newCall.execute
HttpClients. execute
HttpClient.execute
1、HttpURLConnection
@RequestMapping("/fetchURL") public String fetchURL(String url) { URL urlObj = new URL(url); HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection(); connection.setRequestMethod("GET"); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuilder content = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { content.append(line); } reader.close(); return content.toString(); }
2、URLConnection
//urlConnection ssrf vul
String url = request.getParameter("url");
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //发起请求,触发漏洞
String inputLine;
StringBuffer html = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
html.append(inputLine);
}
System.out.println("html:" + html.toString());
in.close();
3、ImageIO
// ImageIO ssrf vul
String url = request.getParameter("url");
URL u = new URL(url);
BufferedImage img = ImageIO.read(u); // 发起请求,触发漏
4、其他
//1、 Request漏洞示例 String url = request.getParameter("url"); return Request.Get(url).execute().returnContent().toString();//发起请求 //2、 URL类中的openStream漏洞示例 String url = request.getParameter("url"); URL u = new URL(url); inputStream = u.openStream(); //发起请求 // 3、OkHttpClient漏洞示例 String url = request.getParameter("url"); OkHttpClient client = new OkHttpClient(); com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build(); client.newCall(ok_http).execute(); //发起请求 // 4、HttpClients漏洞示例 String url = request.getParameter("url"); CloseableHttpClient client = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); HttpResponse httpResponse = client.execute(httpGet); //发起请求
转载:https://blog.csdn.net/Jiajiazml/article/details/127012285
整改方案
@RequestMapping("/fetchURL") public String fetchURL(String url) { if (!isValidURL(url)) { return "Invalid URL"; } // 进行URL有效性检查和白名单验证后再发送请求 try { URL urlObj = new URL(url); if (!isSafeProtocol(urlObj.getProtocol())) { return "Unsupported protocol"; } // 设置连接超时时间 HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection(); connection.setConnectTimeout(5000); // 5 seconds connection.setRequestMethod("GET"); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuilder content = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { content.append(line); } reader.close(); return content.toString(); } catch (MalformedURLException e) { return "Malformed URL"; } catch (IOException e) { return "Error fetching URL"; } } // 验证URL是否合法 private boolean isValidURL(String url) { // 可根据实际需求添加更严格的URL验证逻辑 return url != null && !url.isEmpty(); } // 检查是否使用支持的协议 private boolean isSafeProtocol(String protocol) { // 只允许http和https协议 return "http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol); }
白盒测试
exec
execute
ProcessBuilder
扩展
ProcessBuilder相比于直接调用Runtime.exec()方法更安全的原因包括:
参数处理:ProcessBuilder允许您将命令和参数作为独立的字符串传递,而不是将整个命令作为一个字符串传递给操作系统 Shell。这减少了命令注入攻击的风险,因为参数不会被解释为命令。
环境控制:ProcessBuilder允许您明确设置进程的工作目录、环境变量等信息,从而降低了意外执行的风险。
1、Runtime类:提供调用系统命令的功能
①Runtime.getRuntime():获得JVM运行时的环境
②Runtime.getRuntime().exec(cmd)执行用户输入的cmd命令
protected void doGet (HttpServletRequest req, HttpServletRequest resp) throws ServletException, IOException{
String cmd = req.getParameter("cmd");
Process process = Runtime.getRuntime().exec(cmd);
InputStream in = process.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] b = new byte[1024];//获取1M的一个缓冲区
int i = -1;
while((i=in.read(b)) != -1)//判断是否读完
{
byteArrayOutputStream.write(b,0,i);
}
PrintWriter Out = resp.getWriter();
out.print(new String(byteArrayOutputStream.toByteArray()));
}
2、ProcessBuilder:可以创建操作系统进程
//利用指定的操作系统程序和参数构造一个进程生成器。
ProcessBuilder(String… command)
//设置此进程生成器的操作系统程序和参数。
command(List<String> command)
command(String… command)
//设置此进程生成器的工作目录。
directory(File directory)
//返回此进程生成器环境的字符串映射视图。 environment方法获得运行进程的环境变量,得到一个Map,可以修改环境变量
environment()
//使用此进程生成器的属性启动一个新进程。
start()
3、Groovy
①execute():可执行shell命令,eg:
def command = "git log"
def proc = command.execute()//执行git log的命令
proc.waitFor()
def status = proc.exitValue()
②result = sh(script: “shell command”, returnStdout: true).trim()
③ GroovyShell()
//直接执行Groovy代码
GroovyShell shell = new GroovyShell();shell.evaluate("\'calc\'.execute()");
//通过加载本地脚本
//1.
GroovyShell shell = new GroovyShell();
Script script = shell.parse(new File("src/main/java/ysoserial/vulndemo/GroovyTest.groovy"));
script.run();
//2.
GroovyShell shell = new GroovyShell();
shell.evaluate(new File("src/main/java/ysoserial/vulndemo/GroovyTest.groovy"));
//通过加载远程脚本
GroovyShell shell = new GroovyShell();shell.evaluate(new URI("http://127.0.0.1:8888/GroovyTest.groovy"));
整改方案
如下不存在命令注入
protected ByteArrayOutputStream ping(String url) throws IOException{
Process process = Runtime.getRuntime().exec("ping " + url);
InputStream in = process.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int i = -1;
while((i = in.read(b)) != -1){
byteArrayOutputStream.write(b,0,i);
}
return byteArrayOutputStream;
}
1、写过滤器
2、白名单限制
原文链接:https://blog.csdn.net/m0_52923241/article/details/129267012
Java 语言中,常见的 XML 解析器有:
1、DOM (Document Object Model) 解析:这是一种基于树的解析器,它将整个 XML 文档加载到内
存中,并将文档组织成一个树形结构。
2、 SAX (Simple API for XML) 解析:这是一种基于事件的解析器,它逐行读取 XML 文档并触发特定的事件。
3、JDOM 解析:这是一个用于 Java 的开源库,它提供了一个简单易用的 API 来解析和操作 XML 文档。
4、DOM4J 解析:DOM4J 是一个 Java 的 XML API,是 JDOM 的升级品,用来读写 XML 文件的。
5、Digester 解析:Digester 是 Apache 下一款开源项目。Digester 是对 SAX 的包装,底层是采用的是 SAX 解析方式。 其中,DOM 和 SAX 为原生自带的。JDOM、DOM4J 和 Digester 需要引入第三方依赖库
白盒测试
javax.xml.parsers.DocumentBuilder javax.xml.parsers.SAXParser javax.xml.transform.TransformerFactory javax.xml.validation.Validator javax.xml.validation.SchemaFactory javax.xml.transform.sax.SAXTransformerFactory javax.xml.transform.sax.SAXSource org.xml.sax.XMLReader org.xml.sax.helpers.XMLReaderFactory org.dom4j.io.SAXReader org.jdom.input.SAXBuilder org.jdom2.input.SAXBuilder javax.xml.bind.Unmarshaller javax.xml.xpath.XpathExpression javax.xml.stream.XMLStreamReader org.apache.commons.digester3.Digester
1、DocumentBuilder ( DOM Read XML )
在这个示例中,maliciousXml 字符串包含了恶意构造的 XML 数据,其中 role 的值被设置为admin。如果恶意用户能够控制 XML 数据,并成功注入恶意内容,例如修改 role 的值为恶意的内容,则可能导致安全风险
DocumentBuilder类是JDK自带的类,在该类解析产生的XXE漏洞是有回显的。 import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; public class XmlInjectionExample { public static void main(String[] args) { try { String maliciousXml = "<user><name>John</name><role>admin</role></user>"; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new InputSource(new StringReader(maliciousXml))); System.out.println("Successfully parsed the XML document:"); System.out.println("Name: " + doc.getElementsByTagName("name").item(0).getTextContent()); System.out.println("Role: " + doc.getElementsByTagName("role").item(0).getTextContent()); } catch (Exception e) { System.out.println("Error while parsing XML document: " + e.getMessage()); } } }
2、saxReader ( DOM4J Read XML )
在这个示例中,userInput 字符串包含了从用户输入中获取的 XML 数据。通过使用 SAX 解析器处理 XML 数据时,可能会导致 XML 注入漏洞,特别是在处理未经验证的用户输入时。恶意用户可以尝试注入恶意内容,例如修改 role 的值为恶意内容,从而造成安全风险
saxReader是第三方的库,该类是无回显的 import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.StringReader; public class XmlInjectionSaxExample { public static void main(String[] args) { try { String userInput = "<user><name>John</name><role>admin</role></user>"; SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); DefaultHandler handler = new DefaultHandler() { boolean name = false; boolean role = false; public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equalsIgnoreCase("name")) { name = true; } if (qName.equalsIgnoreCase("role")) { role = true; } } public void characters(char ch[], int start, int length) throws SAXException { if (name) { System.out.println("Name: " + new String(ch, start, length)); name = false; } if (role) { System.out.println("Role: " + new String(ch, start, length)); role = false; } } }; saxParser.parse(new InputSource(new StringReader(userInput)), handler); } catch (Exception e) { System.out.println("Error while parsing XML document: " + e.getMessage()); } } }
3、SAXBuilder ( JDOM2 Read XML)
4、SAXParserFactory
5、XMLReaderFactory
6、Digester
7、XMLReader
整改方案
使用XML解析器时需要设置其属性,禁用DTDs或者禁止使用外部实体
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //禁用DTDs (doctypes),几乎可以防御所有xml实体攻击
//如果不能禁用DTDs,可以使用下两项,必须两项同时存在
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); //防止外部普通实体POC 攻击
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); //防止外部参数实体POC攻击
白盒测试
整改方案
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。