JAVA代码审计

MVC模型

MVC模式是一种软件框架模式,被广泛应用在JavaEE项目的开发中。
MVC即模型(Model)、视图(View)、控制器(Controller)。

模型(Model)

模型是用于处理数据逻辑的部分。
所谓数据逻辑,也就是数据的映射以及对数据的增删改查,Bean、DAO(dataaccess object,数据访问对象)等都属于模型部分。

视图(View)

视图负责数据与其它信息的显示,也就是给用户看到的页面。
HTML、JSP等页面都可以作为视图。

控制器(controller)

控制器是模型与视图之间的桥梁,控制着数据与用户的交互。
控制器通常负责从视图读取数据,处理用户输入,并向模型发送数据,也可以从模型中读取数据,再发送给视图,由视图显示。

整体结构

首先要了解项目整体结构。大致了解作者编写逻辑,搞清请求流程。
src/main下面有两个目录,分别是java和resources,java目录中主要存放的是java代码,resources目录中主要存放的是资源文件,比如:html、js、css等。

java目录

annotation:放置项目自定义注解;

controller/:存放控制器,接收从前端传来的参数,对访问控制进行转发、各类基本参数校验或者不复用的业务简单处理等;

dao/:数据访问层,与数据库进行交互,负责数据库操作,在Mybaits框架中存放自定义的Mapper接口;

entity/:存放实体类;

interceptor/:拦截器;

service/:存放服务类,负责业务模块逻辑处理。Service层中有两种类,一是Service,用来声明接口;二是ServiceImpl,作为实现类实现接口中的方法;

utils/:存放工具类;

dto/:存放数据传输对象(DataTransfer Object),如请求参数和返回结果;

vo/:视图对象(ViewObject)用于封装客户端请求的数据,防止部分数据泄漏,保证数据安全

constant/:存放常量;

filter/:存放过滤器。

resources目录

mapper/:存放Mybaits的mapper.xml文件;

static/:存放静态资源文件目录(Javascript、CSS、图片等),在这个目录中的所有文件可以被直接访问;

templates/:存放模版文件;

application.properties或application.yml:Spring Boot默认配置文件。

URL请求流程

用户请求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;
	}
}

函数

1、不可信数据入口方法

方法说明
getParameterrequest类获取参数方法
getParameterNames获取参数名
getParameterValues获取参数值
getParameterMap获取参数map类型
getQueryString获取URL的value值
getHeader获取http请求头
getHeaderNames获取请求头名
getRequestURI获取请求URL
getCookies获取cookie
getRequestedSessionId获取sessionid
getInputStream获取输入数据
getReader获取请求内容
getMethod获取请求方法
getProtocol获取请求协议
getServerName获取服务名
getRemoteUser获取当前缓存的用户
getUserPrincipal获取用户指纹

2、不可信文件访问方法

方法说明
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

在这里插入图片描述

二、看配置文件

1、看过滤器 filter

这里需要清楚一个概念,过滤器在任何框架都可以使用,而拦截器是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;
    }

}

2、看配置文件 application.properties、application.yml

(1)SpringBoot Actuator 未授权访问
在这里插入图片描述(2)配置文件中可能会存在数据库或其他组件的连接信息
Druid 登陆暴力破解
在这里插入图片描述

3、看config

在这里插入图片描述

搜索代码:WebMvcConfigurer

4、看拦截器,一般目录为interceptor

在这里插入图片描述

三、正向排查

搜索代码:@(.*?)Mapping\(

从controller接口入手,找到外部可控的参数,并跟踪参数是否传入到危险方法中

四、逆向排查

找危险函数,跟踪参数是否从外部传入,判断是否有做严格的参数校验和过滤

五、功能点审计

六、“工具”+“人工”

七、补丁对比

常见漏洞审计

一、认证和授权

1、三方组件未授权访问

①、Actuator未授权访问

白盒测试
配置文件都在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

②.去除可访问文件
在这里插入图片描述

②、Swagger未授权访问

白盒测试

pom依赖:springfox-swagger-ui

整改方案
①.加上enable(false)或者将SwaggerConfig给注释掉,弊端就是你自己也访问不了接口文档

.enable(false)

在这里插入图片描述②.添加认证授权机制: 在Swagger配置中添加认证授权机制,确保只有经过授权的用户才能访问Swagger页面。您可以使用基本身份验证、OAuth、API密钥等方式来实现认证。

③.限制访问权限: 通过配置服务器,限制只有特定IP范围或者需要登录后才能访问Swagger页面。这可以减少未经授权的访问。

④.移除生产环境不必要的Swagger文档: 如果您的应用程序是在生产环境部署的,可以考虑在生产环境中禁用Swagger文档或隐藏它以防止未经授权的访问。

③、Druid未授权访问

白盒测试

配置文件:druid

整改方案

https://www.python100.com/html/ZTO76WX5F640.html

2、登录权限绕过

①、过滤器编写不当

在这里插入图片描述在过滤器或者拦截器里获取请求,然后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 开头了,并且 该路径不会影响结构访问,实现了权限绕过。
在这里插入图片描述

③、shiro鉴权

在这里插入图片描述

④、Spring Security鉴权

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(); // 开启表单登陆
}

二、SQL注入

Java里面常见的数据库连接方式也就那么几个,分别是JDBC,Mybatis,和Hibernate

1、JDBC

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();

2、Mybatis

Mybatis获取值的方式有两种,分别是${} 和 #{}

#{}:解析的是占位符问号,可以防止SQL注入,使用了预编译。
${}:直接获取值

映射文件的命名规则:表所对应的实体类的类名+Mapper.xml
白盒测试

*Mapper.xml文件搜索${

跟踪如下
从 UserMapper.xml 跳到 UserDao.java

点ctrl健+右键,看谁引用了
在这里插入图片描述在这里插入图片描述
查看传参是否存在nickName、userName
在这里插入图片描述接口/api/user,存在sql注入
在这里插入图片描述

整改方案
三个特殊情况

①、like注入

错误代码

<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>
②、in后注入

错误代码

<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>
③、order by 注入

错误代码

Select * from news where title ='#{titlename}' order by ${time} asc

正确代码
安全的做法应当在 Java 代码层面来进行解决
可以设置一个字段值的白名单,仅允许用户传入白名单内的字段

三、XSS

白盒测试
一般先找是否有XSS过滤器,如果存在看是否能绕过,以及它部署的范围

1、反射型XSS

request.getParameter(param)或${param}获取用户的输入信息

getParameter
param.
<%=

JSP表达式
"<%=变量%>“是”<%out.println 变量;%>“的简写方式,”<%=%>"用于将已声明的变量或表达式输出到外网页中

通过"request.getParameter"获取msg传入的值,然后通过"<%=msg%>"将其输出到网页中

<% String msg = request.getParameter("msg");%>
<%= msg %>

2、存储型XSS

例子1、全局搜索数据库的插入语句(关键词:insert,save,update),然后找到该插入语句所属的方
法名如(insertUser()),然后全局搜索该方法在哪里被调用,一层层的跟踪。直到
getParamter()方法获取请求参数的地方停止,如果没有全局 XSS 过滤器,跟踪的整个流
程都没有对获取的参数过滤,则存在存储型XSS。
在这里插入图片描述

在这里插入图片描述

例子2. 从getParamter 关键词开始 ,跟踪请求参数,直到插入数据库的语句,如果中间没有过
滤参数,则存在存储型XSS。
在这里插入图片描述

代码中45行和46行获取usertype和name的值,然后在56行存进数据库由于没有过
滤传进来的参数,所以会在显示时出来触发XSS

3、Dom型XSS

在前端源码搜索

document.location
document.referer
document.url

整改方案
①、修复XSS攻击,一个有效的方案是将特殊字符做转义,对所有字符采用HTML实体编码

private static String XssFilter(String content) {
    content = StringUtils.replace(content, "&", "&amp;");
    content = StringUtils.replace(content, "<", "&lt;");
    content = StringUtils.replace(content, ">", "&gt;");
    content = StringUtils.replace(content, "\", "&quot;");
    content = StringUtils.replace(content, "'", "&#x27;");
    content = StringUtils.replace(content, "/", "&#x2F;");
    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形式时比较方便,适用于直接通过表单发送邮件方式

上传JSP木马

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 "";
}
②、MultipartFile方式上传

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>
③、ServletFileUpload上传

基于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;
}

六、SSRF

白盒测试

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

八、XML注入

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攻击

九、反序列化

白盒测试
整改方案

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值