学习一下java中可能出现的一些安全问题具体如何实现的。先学一下部分无安全防护的代码实现,为后面深入学习漏洞原理打个基础
Java文件上传 主要学习Multipartfile和ServletFileUpload两种方式的文件上传,还有什么文件流方式
smartupload组件上传之后再了解。先摸索一下原理
Multipartfile文件上传 Multipartfile是SpringMVC提供简化上传操作的工具类
这里我们就直接拿之前的springboot项目来整就好了
在此之前需要完善一下项目的目录结构,即在main目录下创建个webapp
目录,并在webapp目录下创建WEB-INF
目录
这里的WEB-INF目录是java web的安全目录,该目录只能由服务端访问,客户端无法访问,目录中应有web.xml文件
确保WEB-INF有web.xml
1 2 3 4 5 6 <?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns ="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version ="4.0" > </web-app >
确保pom.xml
有如下依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot</artifactId > <version > 3.3.1</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <scope > provided</scope > </dependency > <dependency > <groupId > org.apache.tomcat.embed</groupId > <artifactId > tomcat-embed-jasper</artifactId > <scope > provided</scope > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 3.1.0</version > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > jstl</artifactId > <version > 1.2</version > </dependency > </dependencies >
然后在application.properties
添加如下信息
1 2 3 4 5 server.port =8080 spring.mvc.view.prefix =/jsp1/spring.mvc.view.suffix =.jsp
确保你的项目结构如下
在webapp/jsp1目录下创建index.jsp
1 2 3 4 5 6 7 8 9 10 11 12 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" > <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" > </meta> <title>Hello</title> </head> <body> <h1>Hello!!! jsp</h1> </body> </html>
此时启动服务并访问有如下响应
接下来写个multipartfileUpload.jsp
用来实现一个文件上传的前端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>文件上传页面</title> </head> <body> <h1>文件上传页面</h1> <form action="/upload" method="post" enctype="multipart/form-data" > 选择要上传的文件:<input type="file" name="file" /> <input type="submit" value="上传" /> </form> </body> </html>
然后要在controller层写一个处理文件上传的代码multipartfileController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.example.springboottest.controller;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestPart;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.multipart.MultipartFile;import java.io.File;import java.nio.file.Files;import java.nio.file.Paths;@Controller public class multipartfileController { @Value("${file.upload.path}") private String uploadPath; @PostMapping("/upload") @ResponseBody public String create (@RequestPart MultipartFile file) throws Exception{ String fileName = file.getOriginalFilename(); String filePath = uploadPath + fileName; filePath = Paths.get(filePath).toAbsolutePath().toString(); File dest = new File (filePath); Files.copy(file.getInputStream(),dest.toPath()); return "Upload success: " + dest.getAbsolutePath(); } }
接着在根目录下创建upload
文件夹
启动程序,访问/jsp1/multipartfileUpload.jsp
上传文件
查看对应的文件夹就能发现已经成功上传了
ServletFileUpload文件上传 ServletFileUpload
文件上传需要依赖commons-fileupload
组件
常用的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 FileItemFactory 表单项工厂接口 ServletFileUpload 文件上传类,用于解析上传的数据 FileItem 表单项类,表示每一个表单项 boolean ServletFileUpload . isMultipartContent(HttpServletRequest request ) 判断当前上传的数据格式是否是多段的格式,只有是多段数据,才能使用该方式 public List<FileItem> parseRequest(HttpServletRequest request ) 解析上传的数据,返回包含 表单项的 List 集合 boolean FileItem . isFormField() 判断当前这个表单项,是否是普通的表单项,还是上传的文件类型,true 表示普通类型的表单项;false 表示上传的文件类型 String FileItem . getFieldName() 获取表单项的 name 属性值 String FileItem . getString() 获取当前表单项的值; String FileItem . getName() 获取上传的文件名 void FileItem . write( file ) 将上传的文件写到 参数 file 所指向存 取的硬盘位置
关于其相关应用可以参考JAVA文件上传 ServletFileUpLoad 实例
Java文件读取与下载 文件读取和下载区别不大,都是可以用来获取到文件的内容和数据的一种方式,不同的是,文件读取是之前讲文件的内容回显输出到响应中,而文件下载则是通过浏览器下载到本地,自己再打开该文件来获取文件内容
关于下载还是读取,可以通过响应头Content-Disposition
来控制,它指示了响应是以何种方式来呈现的,通过直接输出还是通过浏览器的附件下载
读取/下载方式 使用java.nio.file.Files读取文件 Files 可以用于读取文件到List,可以将较小文件全部读取到内存中,常见的方法是使用 Files 类将文件的所有内容读入字节数组。 Files 类还有一个方法可以读取所有行到字符串列表。
Files 类是在Java 7 中引入的,如果想加载所有文件内容,使用这个类是比较适合的。只有在处理小文件并且需要加载所有文件内容到内存中时才应使用此方法。
写个NioFiles
的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import java.io.IOException;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.List;public class NioFiles { public static void main (String[] args) throws IOException { String filePath = "C:\\Users\\HONOR\\Desktop\\1.txt" ; readfile(filePath); } private static void readfile (String filePath) throws IOException { Path path = Paths.get(filePath); byte [] bytes = Files.readAllBytes(path); List<String> allLines = Files.readAllLines(path, StandardCharsets.UTF_8); System.out.println("使用Files.readAllBytes :" ); System.out.println(new String (bytes)); System.out.println("使用Files.readAllLines :" ); for (String line : allLines) { System.out.println(line); } } }
再单独运行这个java代码,两种Files的函数读取文件结果如下
使用 java.io.FileReader 类读取文件 相较于上一种方式,java.io.FileReader
不支持编码方式的指定,并使用默认编码,所有有时候它的文本读取效果不是很好。
也可以使用FileReader来获取BufferedReader,然后用它来逐行读取文件
创建如下内容的IoFileReader
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import org.springframework.boot.autoconfigure.ssl.SslProperties;import java.io.BufferedReader;import java.io.File;import java.io.FileReader;import java.io.IOException;public class IoFileReader { public static void main (String[] args) throws IOException { String filePath = "C:\\Users\\HONOR\\Desktop\\1.txt" ; readfile(filePath); } private static void readfile (String filePath) throws IOException { File file = new File (filePath); FileReader fileReader = new FileReader (file); int chart; System.out.println("使用FileReader :" ); while ((chart = fileReader.read()) != -1 ) { System.out.print((char ) chart); } fileReader.close(); fileReader = new FileReader (file); BufferedReader bufferedReader = new BufferedReader (fileReader); String line; System.out.println("使用BufferedReader :" ); while ((line = bufferedReader.readLine()) != null ) { System.out.println(line); } bufferedReader.close(); fileReader.close(); } }
可以通过FileReader
直接读取文件(int),或者也可以通过BufferedReader
直接读取
使用 java.io.BufferedReader 读取文件 java.io.BufferedReader
的简单使用刚才已经运用过了
BufferedReader
主要用于逐行读取文件,并且可以读取大文件BufferedReader
是同步的,也就是说多线程可操作
默认缓冲区大小为: 8KB ,因此可以安全地从多个线程完成对BufferedReader 的读取操作。
创建如下内容的BufferedReaderTest
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java.io.*;import java.nio.charset.Charset;import java.nio.charset.StandardCharsets;public class BufferedReaderTest { public static void main (String[] args) throws IOException { String filePath = "C:\\Users\\HONOR\\Desktop\\1.txt" ; readfile(filePath, StandardCharsets.UTF_8); } private static void readfile (String filePath, Charset charset) throws IOException { File file = new File (filePath); FileInputStream fileInputStream = new FileInputStream (file); InputStreamReader inputStreamReader = new InputStreamReader (fileInputStream, charset); BufferedReader bufferedReader = new BufferedReader (inputStreamReader); String line; System.out.println("使用BufferedReader :" ); while ((line = bufferedReader.readLine()) != null ) { System.out.println(line); } bufferedReader.close(); } }
这里先以字节流方式读取文件,在将字节流转化为字符来输出文件内容
InputStreamReader
继承自Reader
类
将输入字节流InputStream
(例如:FileInputStream、Socket.getInputStream)转化为字符输入流Reader
,从而可以按字符读取输入字节
支持设置字符集编码。在构造InputStreamReader
,可以指定编码方式,来将字节转换为相应的字符
1 InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream , charset ) ;
使用 Scanner 读取文件 Scanner
是基于正则 来读取文件,该类不同步,不能多线程使用。
但可以用来逐行的读取文件,或者配合java正则表达式来读取文件
Scanner 类使用正则表达式作为分隔标记解析字符串,分隔符模式默认匹配空格 。然后可以使用各种下一种方法将得到的标记转换成不同类型的值。Scanner 类不同步,因此不是线程安全的。
创建如下内容的ScannerTest
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.io.IOException;import java.nio.file.Path;import java.nio.file.Paths;import java.util.Scanner;public class ScannerTest { public static void main (String[] args) throws IOException{ String filePath = "C:\\Users\\HONOR\\Desktop\\1.txt" ; readfile(filePath); } private static void readfile (String filePath) throws IOException { Path path = Paths.get(filePath); Scanner scanner = new Scanner (path); System.out.println("使用Scanner :" ); while (scanner.hasNextLine()) { String line = scanner.nextLine(); System.out.println(line); } scanner.close(); } }
使用 RandomAccessFile 断点续传 随机流(RandomAccessFile)不属于IO流。
首先把随机访问的文件对象看作存储在文件系统中的一个大型byte数组,然后通过指向该byte数组的光标或索引(即文件指针 FilePointer)在该数组任意位置读取或写入任意数据。
断点续传原理:
下载断开的时候,记录文件断点的位置 position
继续下载的时候,通过RandomAccessFile 找到之前的position 位置开始下载
创建如下内容的RandomAccessFileTest
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.io.IOException;import java.io.RandomAccessFile;public class RandomAccessFileTest { public static void main (String[] args) throws IOException { String filePath = "C:\\Users\\HONOR\\Desktop\\1.txt" ; readfile(filePath); } private static void readfile (String filePath) throws IOException { RandomAccessFile randomAccessFile = new RandomAccessFile (filePath, "r" ); String str; while ((str = randomAccessFile.readLine()) != null ) { System.out.println("使用RandAccessfile断点续传 : " ); System.out.println(str); } randomAccessFile.close(); } }
中文乱码是因为RandomAccessFile
读取文件的 readLine
方法默认使用 ISO-8859-1 编码
使用外部库 org.apache.commons.io.FileUtils.readFileToString() 使用commons-io
库可以非常简单地实现文件读取
但需要在pom.xml添加依赖:
1 2 3 4 5 <dependency > <groupId > commons-io</groupId > <artifactId > commons-io</artifactId > <version > 2.11.0</version > </dependency >
创建如下内容的ApachereadFile
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import org.apache.commons.io.FileUtils;import java.io.File;import java.io.IOException;import java.nio.charset.StandardCharsets;public class ApachereadFile { public static void main (String[] args) throws IOException { String filePath = "C:\\Users\\HONOR\\Desktop\\1.txt" ; readfile(filePath); } private static void readfile (String filePath) throws IOException { File file = new File (filePath); System.out.println("使用common0-io读取数据 :" ); System.out.println(FileUtils.readFileToString(file, StandardCharsets.UTF_8)); } }
使用 Files.readString 读取文本 在Java 11
后出现了Files.readString
,也可以来读取文件
创建如下内容的ReadStringTest
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.io.IOException;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;public class ReadStringTest { public static void main (String[] args) throws IOException { String filePath = "C:\\Users\\HONOR\\Desktop\\1.txt" ; readfile(filePath); } private static void readfile (String filePath) throws IOException { Path path = Paths.get(filePath); String string = Files.readString(path, StandardCharsets.UTF_8); System.out.println("readString读取文件 : " ); System.out.println(string); } }
文件读取的JavaWeb工程 开一个springboot项目,添加如下依赖
1 2 3 4 5 <dependency > <groupId > commons-io</groupId > <artifactId > commons-io</artifactId > <version > 2.11.0</version > </dependency >
然后编写如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 package com.example.springboottest.controller;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import org.apache.commons.io.FileUtils;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.io.*;import java.net.URLEncoder;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.Scanner;@Controller @ResponseBody public class webreadfile { @RequestMapping("/NioFiles") public String NioFiles (String filename, HttpServletResponse response) throws Exception { Path path = Paths.get(filename); byte [] bytes = Files.readAllBytes(path); System.out.println("使用Files.readAllBytes读取文件中......" ); boolean resset= true ; if (resset) { response.reset(); response.setContentType("application/octet-stream" ); response.setHeader("Content-Disposition" , "attachment;filename=" + URLEncoder.encode(filename, "UTF-8" )); } System.out.println(new String (bytes)); return new String (bytes); } @RequestMapping("/IoFileReader") public void IoFileReader (String filename, HttpServletResponse response) throws Exception { File file = new File (filename); FileReader fileReader = new FileReader (file); int chart; System.out.println("使用IoFileReader读取文件 :" ); boolean resset= true ; if (resset) { response.reset(); response.setContentType("application/octet-stream" ); response.setHeader("Content-Disposition" , "attachment;filename=" + URLEncoder.encode(filename, "UTF-8" )); } PrintWriter out = response.getWriter(); while ((chart = fileReader.read()) != -1 ) { out.print((char ) chart); } fileReader.close(); } @RequestMapping("/IoBufferedReader") public void IoBufferedReader (String filename, HttpServletResponse response) throws Exception { File file = new File (filename); FileInputStream fileInputStream = new FileInputStream (file); InputStreamReader inputStreamReader = new InputStreamReader (fileInputStream, StandardCharsets.UTF_8); BufferedReader bufferedReader = new BufferedReader (inputStreamReader); System.out.println("使用BufferedReader :" ); boolean resset= true ; if (resset) { response.reset(); response.setContentType("application/octet-stream" ); response.setHeader("Content-Disposition" , "attachment;filename=" + URLEncoder.encode(filename, "UTF-8" )); } String line; PrintWriter writer = response.getWriter(); while ((line = bufferedReader.readLine()) != null ) { writer.println(line); } bufferedReader.close(); inputStreamReader.close(); } @RequestMapping("/ScannerTest") public void ScannerTest (String filename, HttpServletResponse response) throws Exception { Path path = Paths.get(filename); Scanner scanner = new Scanner (path); System.out.println("使用Scanner :" ); boolean resset= true ; if (resset) { response.reset(); response.setContentType("application/octet-stream" ); response.setHeader("Content-Disposition" , "attachment;filename=" + URLEncoder.encode(filename, "UTF-8" )); } PrintWriter writer = response.getWriter(); while (scanner.hasNextLine()) { String line = scanner.nextLine(); writer.println(line); } scanner.close(); } @RequestMapping("/ApachereadFile") public void ApachereadFile (String filename, HttpServletResponse response) throws Exception { File file = new File (filename); boolean resset= true ; if (resset) { response.reset(); response.setContentType("application/octet-stream" ); response.setHeader("Content-Disposition" , "attachment;filename=" + URLEncoder.encode(filename, "UTF-8" )); } System.out.println("使用common0-io读取数据 :" ); System.out.println(FileUtils.readFileToString(file, StandardCharsets.UTF_8)); } }
如图可以通过调用接口来成功以附件的方式下载文件
rest()
方法的主要作用是清空缓冲区中的任何数据,重置响应头,并且为响应设置一些默认的状态。具体来说,它做了以下几件事情:
清空缓冲区: 如果之前已经通过 getWriter() 或getOutputStream() 获取了输出流并写入了一些数据, reset() 会清空这些数据,以确保响应是一个干净的、空白的状态。
重置响应头: reset() 方法会清除之前设置的响应头,将响应头恢复为默认状态。
设置默认状态: reset() 会将响应状态码重置为默认值,通常是 200 OK
Nio或io的区别 Java 中的 I/O(Input/Output)包含传统的 I/O(使用 java.io 包)和 NIO(New I/O,使用 java.nio 包)。以下是它们之间的主要区别:
1.阻塞 vs 非阻塞:
2.通道与缓冲区:
I/O: 传统的 I/O 使用流(InputStream 和 OutputStream)。它们是单向的,而且通常是字节流或字符流。
NIO: NIO 引入了通道(Channel)和缓冲区(Buffer)的概念。通道是双向的,而缓冲区可以读取和写入数据。
3.选择器(Selectors): NIO: NIO 提供了选择器(Selectors),允许单个线程同时管理多个通道。通过 选择器,可以实现单线程处理多个连接的高并发性能。
4.内存映射文件: NIO: NIO 提供了对文件进行内存映射的功能。这意味着可以直接在内存中操作文 件,而不必通过传统的读取和写入方法。
5.性能和扩展性: NIO: 由于非阻塞和选择器的机制,NIO 在处理大量连接时通常比传统 I/O 更具有性能和扩展性。
6.适用场景:
I/O: 适用于较简单的同步 I/O 操作,适合于连接数较少的场景。
NIO: 适用于需要处理大量连接并实现高并发的场景,例如网络编程、服务器编程等。
总的来说,NIO 提供了更为灵活和高效的 I/O 操作,特别适用于需要处理大量并发连接的应用场景。但对于简单的 I/O 操作,传统的 I/O 也是足够的。选择使用哪种取决于具体的应用需求。
Java命令执行 同PHP中的system
、eval
等函数差不多,Java中也有JDK原生提供的命令执行方法,它们分别是:
java.lang.Runtime
java.lang.ProcessBuilder
java.lang.UNIXProcess/ProcessImpl (需要反射调用)
java.lang :https://docs.oracle.com/javase/7/docs/api/java/lang/package-summary.html
java.lang.Runtime Runtime 是java.lang 中的一个类,主要是与操作系统交互执行操作命令。 而在java.lang.Runtime 中的exec()
方法,可以用来执行具体的命令,而执行 exec()方法有以下六种形式(重载),如下图所示:
这里主要关注exec(String command)和exec(String[] cmdarray)这两种执行方式
exec(String command) 看代码就懂了,无需多言
1 2 String command = "calc" Runtime.getRuntime().exec(command)
exec(String[] cmdarray) 1 2 String[] command = {"cmd" ,"/c" ,"whoami" }; Runtime.getRuntime().exec (command );
构造回显 同php中的命令执行函数不同的是,这些java函数都没有回显,实战中经常需要通过dnslog等外带来判断该漏洞是否可以执行命令,得到命令的输出
为了直观地看到命令的结果,这里将结果通过getInputStream 和getErrorStream 用 BufferedReader 生成输出流,然后输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;public class RuntimeTest { public static void main (String[] args) throws IOException { String cmdString = "cmd /c dir" ; String cmdArgs[] = {"cmd" , "/c" , "ping" , "www.baidu.com" }; cmdStringExec(cmdString); cmdArgsExec(cmdArgs); } private static void cmdStringExec (String cmdString) throws IOException { String line; Runtime runtime = Runtime.getRuntime(); Process exec = runtime.exec(cmdString); BufferedReader bufferedReader = new BufferedReader (new InputStreamReader (exec.getInputStream(), "GBK" )); BufferedReader bufferedErrorReader = new BufferedReader (new InputStreamReader (exec.getErrorStream(), "GBK" )); while ((line = bufferedReader.readLine()) != null ) { System.out.println(line); } while ((line = bufferedErrorReader.readLine()) != null ) { System.out.println(line); } } private static void cmdArgsExec (String[] cmdArgs) throws IOException { String line; Runtime runtime = Runtime.getRuntime(); Process exec = runtime.exec(cmdArgs); BufferedReader bufferedReader = new BufferedReader (new InputStreamReader (exec.getInputStream(), "GBK" )); BufferedReader bufferedErrorReader = new BufferedReader (new InputStreamReader (exec.getErrorStream(), "GBK" )); while ((line = bufferedReader.readLine()) != null ) { System.out.println(line); } while ((line = bufferedErrorReader.readLine()) != null ) { System.out.println(line); } } }
使用 BufferedReader 的时候,命令并不是等待执行完之后得到的,而是一边执行,一边输出的。
以执行ping为例,它是逐渐输出结果的,而不是等代码执行完成才全部输出
java.lang.ProcessBuilder java.lang.ProcessBuilder
也可以来执行命令
如图,它有两种传参方式一种是传入字符串,一种是字符串列表
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.List;public class BuilderTest { public static void main (String[] args) throws IOException{ List<String> cmdlists = new ArrayList <String>(); cmdlists.add("cmd" ); cmdlists.add("/c" ); cmdlists.add("ping www.baidu.com" ); ProcessBuilder builder = new ProcessBuilder (cmdlists); Process process = builder.start(); BufferedReader bufferedReader = new BufferedReader (new InputStreamReader (process.getInputStream(), "GBK" )); BufferedReader bufferErrorReader = new BufferedReader (new InputStreamReader (process.getErrorStream(), "GBK" )); String line; while ((line = bufferedReader.readLine()) != null ) { System.out.println(line); } while ((line = bufferErrorReader.readLine()) != null ) { System.out.println(line); } } }
java.lang.UNIXProcess/ProcessImpl 再JDK9的时候UNIXProcess
被合并到了 ProcessImpl
当中
同时,相比上面两种方法,这种方法太底层了,无法直接调用,需要使用反射来调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.lang.reflect.Method;import java.util.Map;public class ProcessImplExecTest { public static void main (String[] args) throws Exception { String[] cmds = {"cmd.exe" ,"/c" ,"ping www.baidu.com" }; Class c = Class.forName("java.lang.ProcessImpl" ); Method m = c.getDeclaredMethod("start" , String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean .class); m.setAccessible(true ); Process invoke = (Process) m.invoke(null , cmds, null , "." , null , true ); BufferedReader bufferedReader = new BufferedReader (new InputStreamReader (invoke.getInputStream(), "GBK" )); BufferedReader buffErrorReader = new BufferedReader (new InputStreamReader (invoke.getErrorStream(), "GBK" )); String line; while ((line = bufferedReader.readLine()) != null ) { System.out.println(line); } while ((line = buffErrorReader.readLine()) != null ) { System.out.println(line); } } }
但在Java 9之后,Oracle引入了一个新的系统,称为Java Platform Module System(JPMS),它使得Java核心库内部的类和接口不再对默认情况下公开可见,除非明确声明为”open”。这是为了提供更好的封装和安全性。
也意味着通过反射来访问 java.lang.ProcessImpl.start 方法,这在Java 9及以上版本中是不允许的,因为 java.lang 包没有明确声明为”open”。
需要添加一个命令行选项到您的JVM启动参数,来开放这个模块:
1 --add -opens java.base /java.lang=ALL-UNNAMED
否则就会报错:
命令执行的JavaWeb工程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.example.springboottest.controller;import jakarta.servlet.http.HttpServletResponse;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;@Controller @ResponseBody public class webruntime { @RequestMapping("/execRuntimeString") public void execRuntimeString (String cmd, HttpServletResponse response) throws IOException { String line; Process exec = Runtime.getRuntime().exec(cmd); response.setContentType("text/html;charset=GBK" ); BufferedReader bufferedReader = new BufferedReader (new InputStreamReader (exec.getInputStream(), "GBK" )); BufferedReader bufferedErrorReader = new BufferedReader (new InputStreamReader (exec.getErrorStream(), "GBK" )); PrintWriter writer = response.getWriter(); while ((line = bufferedReader.readLine()) != null ) { writer.println(line); System.out.println(line); } while ((line = bufferedErrorReader.readLine()) != null ) { writer.println(line); System.out.println(line); } } }
Java数据库操作 Java中常见的数据库操作方式有:
JDBC :比较原生繁琐
Mybatis:比较便捷主流
JDBC使用 Java 数据库连接,(Java Database Connectivity,简称JDBC)是Java 语言中用来规范客户端程序如何来访问数据库的应用程序接口(位于jdk 的java.sql 中)。
通常说的JDBC 是面向关系型数据库的,提供了诸如查询、更新、删除、增加数据库中数据的方法。在使用时候需要导入具体的jar 包,不同数据库需要导入的jar 包不同。
JDBC 与MySQL 进行连接交互,通常为以下6 个流程:
注册驱动 (仅仅做一次)
建立连接(Connection)
创建运行 SQL 的语句(Statement)
运行语句
处理运行结果(ResultSet)
释放资源
先创建个数据库:
1 2 3 4 5 6 7 8 9 10 11 CREATE DATABASE jdbcdemo; USE jdbcdemo;CREATE TABLE `user ` ( `id` int (10 ) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一ID' , `username` varchar (25 ) NOT NULL COMMENT '用户名' , `password ` varchar (25 ) NOT NULL COMMENT '密码' ,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;INSERT INTO user (id,username,password ) VALUES (1 , "root1", "roo1");INSERT INTO user (id,username,password ) VALUES (2 , "root", "root");INSERT INTO user (id,username,password ) VALUES (3 , "root2", "root2");
新建个spring项目,勾上依赖SQL->JDBC API,MYsql Driver
注册驱动:
在注册数据库驱动时,虽然
1 DriverManager . registerDriver(newcom .mysql .jdbc .Driver() )
方法可以完成,但会使数据库驱动被注册两次。这是因为Driver 类的源码中,已经在静态代码块中完成了数据库驱动的注册。所以,为了避免数据库驱动被重复注册,我们只需要在程序中加载驱动类即可,具体加载方式如下所示:
1 Class .for Name("com.mysql.jdbc.Driver" ) ;
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.example.jdbcdemo;import java.io.File;import java.io.IOException;import java.nio.charset.StandardCharsets;import java.sql .*;public class JdbcDemo { public static void main(String[] args) throws SQLException, ClassNotFoundException{ Class .forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/jdbcdemo?useUnicode=true&characterEncoding=utf8"; String username = "root"; String password = "1234qwer"; Connection conn = DriverManager.getConnection(url, username, password ); Statement statement = conn.createStatement(); String sql = "SELECT * FROM user"; ResultSet resultSet = statement .executeQuery(sql ); while (resultSet.next()){ System .out .println("============================="); System .out .println(resultSet.getInt("id")); System .out .println(resultSet.getString("username")); System .out .println(resultSet.getString("password")); } resultSet.close (); statement .close (); conn.close (); } }
Mybatis使用 依赖项:
Web → Spring boot
SQL → JDBC API
SQL → Mybatis Framework
SQL→ Mysql Driver
数据库就沿用之前的就好了
在项目文件夹(Application 的路径)下创建如下文件夹:
controller :Controller 层
service :业务层
impl : service 层的实现
entity : 实体类,用来和数据做映射
mapper : 数据操作层 DAO
如图:
同时在resources 下也创建一个mapper 文件夹
1.在entity 下建立一个 User 的 java 对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.example.jdbcdemo.entity;import java.security.PrivateKey;public class User { private int id; private String username; private String password; public int getId () { return id; } public String getUsername () { return username; } public String getPassword () { return password; } public void setId (int id) { this .id = id; } public void setUsername (String username) { this .username = username; } public void setPassword (String password) { this .password= password; } @Override public String toString () { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + '}' ; } }
2.在mapper 中建立 UserMapper的接口 使用 @Mapper 注解使得 MyBatis 在运行时自动创建该接口的实现。
调用该接口时,就会使用与xml 对应的语句,然后返回结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.example.jdbcdemo.mapper;import com.example.jdbcdemo.entity.User;import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper public interface UserMapper { public boolean addUser (User user) ; public boolean delUser (String username) ; public boolean updateUser (User user) ; public User getUser (int id) ; public List<User> getAllUser () ; }
3.在src.main.resources.mapper 中创建UserMapper.xml 文件 创建UserMapper.xml 文件,用于和 dao 层映射绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?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.example.jdbcdemo.mapper.UserMapper" > <insert id ="addUser" parameterType ="com.example.jdbcdemo.entity.User" > INSERT INTO user(id,name,password) VALUES (#{id},#{userName},#{password}) </insert > <delete id ="delUser" parameterType ="String" > DELETE FROM user WHERE name=#{name} </delete > <update id ="updateUser" parameterType ="com.example.jdbcdemo.entity.User" > UPDATE user SET name=#{userName},password=#{password} WHERE id=#{id} </update > <select id ="getUser" parameterType ="int" resultType ="com.example.jdbcdemo.entity.User" > SELECT * FROM user WHERE id=#{id} </select > <select id ="getAllUser" resultType ="com.example.jdbcdemo.entity.User" > SELECT * FROM user </select > </mapper >
4.在Service 中创建一个 UserService 的接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.example.jdbcdemo.service.impl;import com.example.jdbcdemo.entity.User;import org.springframework.stereotype.Service;import java.util.List;@Service public interface UserService { public boolean addUser (User user) ; public boolean delUser (String username) ; public boolean updateUser (User user) ; public User getUser (int id) ; public List<User> getAllUser () ; }
然后在impl 中新建一个UserServiceImpl 的对象,实现UserService 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package com.example.jdbcdemo.service.impl;import com.example.jdbcdemo.entity.User;import com.example.jdbcdemo.mapper.UserMapper;import com.example.jdbcdemo.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;@Service public class UserServiceImpl implements UserService { private final UserMapper userMapper; @Autowired public UserServiceImpl (UserMapper userMapper) { this .userMapper = userMapper; } @Override public boolean addUser (User user) { boolean flag = false ; flag = userMapper.addUser(user); return flag; } @Override public boolean delUser (String username) { boolean flag = false ; flag = userMapper.delUser(username); return flag; } @Override public boolean updateUser (User user) { boolean flag = false ; flag = userMapper.updateUser(user); return flag; } @Override public User getUser (int id) { User user = null ; user = userMapper.getUser(id); return user; } @Override public List<User> getAllUser () { List<User> list = null ; list = userMapper.getAllUser(); return list; } }
5.在Controller 层中创建UserController 的类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.example.jdbcdemo.controller;import com.example.jdbcdemo.entity.User;import com.example.jdbcdemo.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;import java.util.List;@Controller @RestController public class UserController { private final UserService service; @Autowired public UserController (UserService service) { this .service = service; } @RequestMapping(value = "/addUser", method = RequestMethod.POST) @ResponseBody public String addUser (User user) { boolean flag = service.addUser(user); if (flag) { return "Success" ; } else { return "False" ; } } @RequestMapping(value = "/delUser", method = RequestMethod.POST) @ResponseBody public String delUser (String username) { boolean flag = service.delUser(username); if (flag) { return "Success" ; } else { return "False" ; } } @RequestMapping(value = "/updateUser", method = RequestMethod.POST) @ResponseBody public String updateUser (User user) { boolean flag = service.updateUser(user); if (flag) { return "Success" ; } else { return "False" ; } } @RequestMapping(value = "/getUser/{id}", method = RequestMethod.GET) @ResponseBody public User getUser (@PathVariable("id") int id) { return service.getUser(id); } @RequestMapping(value = "/getAllUser", method = RequestMethod.GET) @ResponseBody public List<User> getAllUser () { return service.getAllUser(); } }
6.最后在application.properties 中添加配置: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 spring.application.name =JDBCdemoserver.port =8080 mybatis.mapper-locations =classpath:mapper/*.xmlmybatis.type-aliases-package =com.example.jdbcdemo.entityspring.datasource.url =jdbc:mysql://localhost:3306 /jdbcdemo?useUnicode=true &characterEncoding=utf8spring.datasource.username =rootspring.datasource.password =1234 qwerspring.datasource.driver-class-name =com.mysql.cj.jdbc.Drivermybatis.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImplmybatis.configuration.log-prefix =[Mybatis]
Java反射基础 反射-1-forName 不同于php等其他语言,java拥有反射 这一特性,Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。
p牛对反射的解释:
对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语⾔言附加上动态特性。
通过代码来简单理解理解~
1 2 3 4 public void execute (String className, String methodName) throws Exception { Class clazz = Class.forName(className); clazz.getMethod(methodName).invoke(clazz.newInstance()); }
获取类的方法: forName
实例例化类对象的方法: newInstance
获取函数的方法:getMethod
执行函数的方法: invoke
如何获取到类
obj.getClass()
如果上下文中存在某个类的实例obj ,那么我们可以直接通过obj.getClass() 来获取它的类。准确的说是返回调用该方法的对象的运行时类对象(Runtime Class Object)。也就是说,它返回的是调用这个方法的对象所属的类的 Class 对象。
Test.class
如果你已经加载了某个类,只是想获取到它的java.lang.Class 对象,那么就直接
拿它的class 属性即可。这个方法其实不不属于反射。
Class.forName
如果你知道某个类的名字,想获取到这个类,就可以使用forName 来获取
同时,ClassLoader.getSystemClassLoader().loadClass(“java.lang.Runtime”) 类似的利用类加载机制,也可以获取 Class 对象
获取Runtime类Class对象代码片段:
1 2 3 4 String className = "java.lang.Runtime" ;Class runtimeClass1 = Class.forName(className);Class runtimeClass2 = java.lang.Runtime.class;Class runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
通过以上任意一种方式就可以获取java.lang.Runtime
类的Class对象了
后面会提到:反射调用内部类的时候需要使用$
来代替.
,如com.anbai.Test
类有一个叫做Hello
的内部类,那么调用的时候就应该将类名写成:com.anbai.Test$Hello
。
forName forName有两个函数重载:
1 2 Class<?> for Name(String name ) Class<?> for Name(String name , ** boolean ** initialize , ClassLoader loader )
第一个就是我们最常见的获取class的方式,其实可以理理解为第二种方式的一个封装:
1 2 3 Class .for Name(className ) Class .for Name(className , true , currentLoader )
默认情况下, forName 的第⼀个参数是类名;第二个参数表示是否初始化;第三个参数就 是ClassLoader 。 ClassLoader 是什么呢?它就是⼀个“加载器器”,告诉Java虚拟机如何加载这个类。
Java默认的ClassLoader 就是根据类名来加载类,这个类名是类完整路路径,如java.lang.Runtime
获取数组类型的Class对象需要特殊注意,需要使用Java类型的描述符方式,如下:
1 2 Class<?> doubleArray = Class.forName("[D" ); Class<?> cStringArray = Class.forName("[[Ljava.lang.String;" );
类的初始化顺序 不妨先运行一下p牛给的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class TrainPrint { { System.out.printf("Empty block initial %s\n" , this .getClass()); } static { System.out.printf("Static initial %s\n" , TrainPrint.class); } public TrainPrint () { System.out.printf("Initial %s\n" , this .getClass()); } public static void main (String[] args) { TrainPrint train = new TrainPrint (); } }
可以看到,首先调用的是static {}
,其次是{}
,最后是构造函数
。 其中, static {}
就是在“类初始化”的时候调用的,而{}
中的代码会放在构造函数的super() 后面,但在当前构造函数内容的前面。 所以说, forName 中的initialize=true
其实就是告诉Java虚拟机是否执行”类初始化“。
Person p = new Person(“zhangsan”,20); 该句话都做了什么事情?
1,因为new用到了Person.class.所以会先找到Person.class文件并加载到内存中。
2,执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
3,在堆内存中开辟空间,分配内存地址。
4,在堆内存中建立对象的特有属性。并进行默认初始化。
5,对属性进行显示初始化。
6,对对象进行构造代码块初始化。
7,对对象进行对应的构造函数初始化。
8,将内存地址付给栈内存中的p变量。
简单利用 那么,假设我们有如下函数,其中函数的参数name可控:
1 2 3 public void ref (String name) throws Exception { Class.forName(name); }
我们就可以编写⼀个恶意类,将恶意代码放置在static {}
中,从而执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.lang.Runtime;import java.lang.Process;public class TouchFile {static {try {Runtime rt = Runtime.getRuntime(); String[] commands = {"touch" , "/tmp/success" };Process pc = rt.exec(commands); pc.waitFor(); } catch (Exception e) { } } }
通过反射无需实例调用
1 2 3 4 5 6 public class hey { public String name="Hey" ; public void get_name () { System.out.print("good job! " +name+", You've successfully used reflections\n" ); } }
1 2 3 4 5 6 7 8 9 10 11 public class test { public static void main (String[] args) throws Exception{ execute("hey" , "get_name" ); } public static void execute (String a, String b) throws Exception{ Class clazz = null ; clazz = Class.forName(a); clazz.getMethod(b).invoke(clazz.newInstance()); } }
反射-2-单例模式中静态方法利用 我们可以使用forName加载任意类,而不需要import
,这样对于我们的攻击者来说就十分有利。
class.newInstance()
可以调用类中的无参构造函数 但是有些情况是无法使用的
因为可能使用的类没有无参构造函数
构造函数是私有的(单例情况
加载内部类 有时候会看到类名的部分包含$
符号,它的作用是查找内部类
Java的普通类C1
中支持编写内部类C2
,而在编译的时候,会生成两个文件: C1.class
和C1$C2.class
,我们可以把他们看作两个无关的类,通过Class.forName("C1$C2")
即可加载这个内部类。
newInstance() class.newInstance()
可以调用类中的无参构造函数 ,但是有些情况是无法使用的:
因为可能使用的类没有无参构造函数
构造函数是私有的(单例情况)
当类的构造方法是私有时,但它是单例模式(以Runtime为例),我们无法成功利用下面的代码来执行命令
比如,对于Web应用来说,数据库连接只需要建立一次,而不是每次用到数据库的时候再新建立一个连 接,此时作为开发者你就可以将数据库连接使用的类的构造函数设置为私有,然后编写一个静态方法来 获取:
1 2 Class clazz = Class.forName("java.lang.Runtime" ); clazz.getMethod("exec" , String.class).invoke(clazz.newInstance(), "calc.exe" );
那就没有办法去执行私有方法了吗?还可以利用 getMethod
和invoke
方通过Runtime.getRuntime()
(静态方法 )来获取到Runtime 对象
1 2 Class clazz = Class.forName("java.lang.Runtime" ); clazz.getMethod("exec" , String.class).invoke(clazz.getMethod("getRuntime" ).invoke(clazz), "calc.exe" );
拆分来看就是:
1 2 3 4 5 Class clazz = Class.forName("java.lang.Runtime" );Method execMethod = clazz.getMethod("exec" , String.class);Method getRuntimeMethod = clazz.getMethod("getRuntime" );Object runtime = getRuntimeMethod.invoke(clazz); execMethod.invoke(runtime, "calc.exe" );
invoke 的作用是执行方法,它的第一个参数是:
如果这个方法是一个普通方法,那么第一个参数是类对象
如果这个方法是一个静态方法,那么第一个参数是类
这也比较好理解了,我们正常执行方法是[1].method([2], [3], [4]…) ,其实在反射里就是method.invoke([1], [2], [3], [4]…)
如果这个方法是一个普通方法,那么第一个参数是类对象
如果这个方法是一个静态方法,那么第一个参数是类
这也比较好理解了,我们正常执行方法是[1].method([2], [3], [4]...)
,其实在反射里就是method.invoke([1], [2], [3], [4]...)
反射-3-参数的构造 getConstructor getConstructor
可以帮助我们解决:如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类 呢? 该问题
1 2 3 Class clazz = Class.forName("java.lang.ProcessBuilder" ); ((ProcessBuilder) clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe" ))).start();
相比之前的
1 2 Class clazz = Class.forName("java.lang.Runtime" ); clazz.getMethod("exec" , String.class).invoke(clazz.newInstance(), "calc.exe" );
这里用来执行命令的方式ProcessBuilder
,它需要调用start()
来执行命令
而这里getConstructor
作用和getMethod
类似,它接收的参数是构造函数列表类型,因为构造函数也支持重载,所以必须用参数列表类型才能唯一确定一个构造函数。
1 2 public ProcessBuilder(List <String > command)public ProcessBuilder(String ... command)
前面这个Payload用到了Java里的强制类型转换,有时候我们利用漏洞的时候(在表达式上下文中)是没有这种语法的。所以,我们仍需利用反射来完成这一步。
1 2 Class clazz = Class.forName("java.lang.ProcessBuilder" ); clazz.getMethod("start" ).invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe" )));
通过getMethod("start")
获取到start方法,然后invoke 执行, invoke 的第一个参数就是ProcessBuilder Object
了
public ProcessBuilder(String… command)
也可以用这种方式来构造
...
这样的语法来表示“这个函数的参数个数是可变的,String...
也就是可变长参数(varargs)
对于可变长参数,Java其实在编译的时候会编译成一个数组,也就是说,如下这两种写法在底层是等价的(也就不能重载):
1 2 public void hello (String [] names ) {}public void hello (String ...names ) {}
那么对于反射来说,如果要获取的目标函数里包含可变长参数,其实我们认为它是数组就行了。所以,我们将字符串数组的类String[].class 传给getConstructor ,获取ProcessBuilder 的第二种构造函数:
1 2 Class clazz = Class.forName("java.lang.ProcessBuilder" ); clazz.getConstructor(String[].class)
在调用newInstance 的时候,因为这个函数本身接收的是一个可变长参数,我们传给ProcessBuilder 的也是一个可变长参数,二者叠加为一个二维数组,所以整个Payload如下:
1 2 3 Class clazz = Class.forName("java.lang.ProcessBuilder" ); ((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String [][]{{"calc.exe" }})).start();
getDeclared 这个可以用来解决:如果一个方法或构造方法是私有方法,我们是否能执行它呢?
它与普通的getMethod 、getConstructor 区别是:
1 2 getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法 getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
getDeclaredMethod 的具体用法和getMethod 类似, getDeclaredConstructor 的具体用法和getConstructor 类似
前文我们说过Runtime这个类的构造函数是私有的,我们需要用Runtime.getRuntime() 来获取对象。其实现在我们也可以直接用getDeclaredConstructor 来获取这个私有的构造方法来实例化对象,进而执行命令:
1 2 3 4 Class clazz = Class.forName("java.lang.Runtime" );Constructor m = clazz.getDeclaredConstructor(); m.setAccessible(true ); clazz.getMethod("exec" , String.class).invoke(m.newInstance(), "calc.exe" );
这里使用了一个方法setAccessible
,这个是必须的。我们在获取到一个私有方法后,必须用setAccessible
修改它的作用域,否则仍然不能调用。
反射Runtime执行本地命令代码片段: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Class runtimeClass1 = Class.forName("java.lang.Runtime" );Constructor constructor = runtimeClass1.getDeclaredConstructor(); constructor.setAccessible(true );Object runtimeInstance = constructor.newInstance();Method runtimeMethod = runtimeClass1.getMethod("exec" , String.class);Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);InputStream in = process.getInputStream(); System.out.println(org.apache.commons.io.IOUtils.toString(in, "UTF-8" ));
反射调用Runtime
实现本地命令执行的流程如下:
反射获取Runtime
类对象(Class.forName("java.lang.Runtime")
)。
使用Runtime
类的Class对象获取Runtime
类的无参数构造方法(getDeclaredConstructor()
),因为Runtime
的构造方法是private
的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true)
)。
获取Runtime
类的exec(String)
方法(runtimeClass1.getMethod("exec", String.class);
)。
调用exec(String)
方法(runtimeMethod.invoke(runtimeInstance, cmd)
)。
Java序列化与反序列化
序列化 序列化就是将对象变成字符序列 的过程,这样有利于保存和传输。
ObjectOutputStream
类的writeObject()
方法可以用来实现序列化
writeObject():将指定对象写入ObjectOutputStream
ObjectOutputStream (Java Platform SE 8 )
一个对象序列化的必要条件:
该类必须实现java.io.Serializable
接口(起声明作用的空接口)
该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
反序列化 反序列化就是将字符序列变成对象的过程
使用的是ObjectInputStream
类的readObject()
方法
ObjectInputStream (Java Platform SE 8 )
I/O流
常见输入输出流:
1.文件字节流:
1 2 FileInputStream fis = new FileInputStream ("D:\\test.txt" );FileOutputStream fos = new FileOutputStream ("D:\\test2.txt" );
2.缓冲字节流:
1 2 BufferedInputStream bis = new BufferedInputStream (new FileInputStream ("D:\\test.txt" ));BufferedOutputStream bos = new BufferedOutputStream (new FileOutputStream ("D:\\test2.txt" ));
3.缓存字符流:
1 2 BufferedReader br = new BufferedReader (new FileReader ("D:\\test.txt" )); BufferedWriter bw = new BufferedWriter (new FileWriter ("D:\\test2.txt" ));
4.字节流
1 2 3 byte[] byteArray = new byte[1024]; ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); ByteArrayOutputStream baos = new ByteArrayOutputStream();
5.序列化和反序列化流
1 2 ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("D:\\test.txt" )); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("D:\\test.txt" ));
6.CharArrayReader和CharArrayWriter:用于在内存中读写字符数组
1 2 3 char [] charArray = new char [1024 ] ; CharArrayReader car = new CharArrayReader(charArray ) ; CharArrayWriter caw = new CharArrayWriter() ;
7.StringReader和StringWriter:用于在内存中读写字符串
1 2 StringReader sr = new StringReader("hello world" ) StringWriter sw = new StringWriter()
Demo 编写一个redrock 的类,然后将其序列化/反序列化到/从文件/base64中
四个函数:
将对象序列化到文件
从文件内容反序列化到对象
将对象序列化后base64 输出
将base64 解码然后反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.example.jdbcdemo;import java.io.*;import java.util.Base64;public class serdemo { public static void main (String[] args) throws IOException, ClassNotFoundException { Redrock redrock = new Redrock (); redrock.Org = "SRE" ; redrock.Num = 114514 ; System.out.println("打印原版Redrock:" ); System.out.println(redrock); String fileName = "./RedrockSRE.bin" ; SerialToFile(redrock, fileName); Redrock redrockFromFile = (Redrock) UnserialFromFile(fileName); System.out.println("反序列化后Redrock:" ); System.out.println(redrockFromFile); System.out.println("反序列化后Redrock并base64编码:" ); String RedrockSer = SerialToBase64(redrock); System.out.println("将base64编码的文件反序列化后:" ); System.out.println(UnserialFromBase64(RedrockSer)); } private static Object UnserialFromBase64 (String base64Msg) throws IOException, ClassNotFoundException{ Base64.Decoder decoder = Base64.getDecoder(); byte [] msg = decoder.decode(base64Msg); ByteArrayInputStream bis = new ByteArrayInputStream (msg); ObjectInputStream ois = new ObjectInputStream (bis); return ois.readObject(); } private static String SerialToBase64 (Object o) throws IOException{ ByteArrayOutputStream bos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (bos); oos.writeObject(o); Base64.Encoder encoder = Base64.getEncoder(); byte [] msg = encoder.encode(bos.toByteArray()); System.out.println(new String (msg)); return new String (msg); } private static Object UnserialFromFile (String fileName) throws IOException, ClassNotFoundException{ FileInputStream fis = new FileInputStream (fileName); ObjectInputStream ois = new ObjectInputStream (fis); return ois.readObject(); } private static void SerialToFile (Object o, String fileName) throws IOException{ FileOutputStream fos = new FileOutputStream (fileName); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(o); } }
在创建一个Redrock类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.example.jdbcdemo;import java.io.Serializable;public class Redrock implements Serializable { public String Org; public int Num; @Override public String toString () { return "Redrock{" + "Org='" + Org + '\'' + ", Num=" + Num + '}' ; } }
base64 编码后反序列化特征为rO0AB
,二进制头部为:ACED
Java RMI基础 RMI(Remote Method Invocation,远程方法调用)是Java 的一组拥护开发分布式应用程序的API。RMI 使用 Java 语言接口定义了远程对象,它集合了Java 序列化和Java 远程方法协议(Java Remote MethodProtocol)
简单地说,原先的程序仅能在同一操作系统的方法调用,通过RMI 可以变成在不同操作系统之间对程序中方法的调用。 RMI 依赖的通信协议是 JRMP 。RMI 对象是通过序列化方式进行传输的。
JRMP: Java 远程方法协议(Java Remote Method Protocol,JRMP),是特定于Java 技术的、用于查找和引用远程对象的协议。
RMI 说明 既然RMI需要进行远程通信,那就不可避免的会涉及到客户端与服务端这两类对象,通常情况下,服务端通过监听一个端口,然后客户端去通过RMI的通信协议去访问该端口,服务端接受客户端的请求并作出响应,以此来达成远程方法调用的目的
实际在使用的时候,服务端可能需要有多个服务分别监听对应的多个端口,这时候就需要有个可以管理每个服务(方法)对应哪一端口的服务,也就是注册中心 。每个服务(方法)都在注册中心注册与端口的对应关系,之后客户端便能通过注册中心,使用方法名称就可以得到这个服务监听的端口
通常服务端与注册中心是在同一个主机上的,所以注册中心只需要指定端口的对应服务即可
RMI 中的三个角色 RMI 中的三个角色:
服务端 Server :负责将远程对象绑定至注册中心
客户端 Client :服务端会将远程对象绑定至此。客户端会向注册中心查询绑定的远程对象
注册中心 Registry :与注册中心和服务端交互
具体关系如图:
存根/桩(Stub):客户端侧的代理,每个远程对象都包含一个代理对象stub,当运行在本地Java 虚拟机上的程序调用运行在远程Java 虚拟机上的对象方法时,它首先在本地创建该对象的代理对象stub, 然后调用代理对象上匹配的方法。
骨架(Skeleton):服务端侧的代理,用于读取stub 传递的方法参数,调用服务器方的 实际对象方法, 并接收方法执行后的返回值。
Demo 先实现一个Server ,也就是先写一个对外开放的接口,供客户端调用:IRemoteObj.java
1 2 3 4 5 6 7 8 9 package com.example.rmidemo;import java.rmi.Remote;import java.rmi.RemoteException;public interface IRemoteObj extends Remote { public String sayHello (String name) throws RemoteException; }
然后根据RMI的要求实现这个接口,具体就是写个类,需要继承UnicastRemoteObject
IremoteObjImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.example.rmidemo;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;public class IRemoteObjImpl extends UnicastRemoteObject implements IRemoteObj { protected IRemoteObjImpl () throws RemoteException{ } @Override public String sayHello (String name) throws RuntimeException { String upward = name.toUpperCase(); System.out.println(upward); return upward; } }
然后就写个注册中心,并将对象绑定上去
在下面第一步写好实现类时,就已经申请端口并监听端口了
在RMI中,远程对象通常会使用RMI系统自动分配的端口进行通信
RmiRegistry.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.example.rmidemo;import java.rmi.AlreadyBoundException;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class RmiRegistry { public static void main (String[] args) throws RemoteException, AlreadyBoundException { IRemoteObjImpl iRemoteObj = new IRemoteObjImpl (); Registry registry = LocateRegistry.createRegistry(1099 ); registry.bind("IRemoteObj" , iRemoteObj); } }
最后在编写一下客户端,调用一下服务端
RmiClient.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.example.jdbcdemo;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class RmiClient { public static void main (String[] args) throws Exception{ Registry registry = LocateRegistry.getRegistry("127.0.0.1" , 1099 ); IRemoteObj iRemoteObj = (IRemoteObj) registry.lookup("IRemoteObj" ); String result = iRemoteObj.sayHello("World" ); System.out.printf(result); } }
先起服务端,在起客户端调用
Java JNDI基础 什么是JNDI JNDI(Java Naming and Directory Interface,Java 命名和目录接口)是一种标准的Java 命名系统接口。
JNDI 提供统一的客户端API,通过不同的访问提供者接口 JNDI 服务供应接口(SPI)的实现,由管理者将JNDI API 映射为特定的命名服务和目录系统,使得Java 应用程序可以和这些命名服务和目录服务之间进行交互。
目录服务是命名服务的一种自然扩展。JNDI 可以访问的目录及服务,比如:DNS、LDAP、CORBA 对象服务、RMI 等等。
例如 对外提供了服务的RMI,JNDI 可以通过相关API 可以链接处理这些服务。
JNDI 的五个包 javax.naming 它包含了命名服务的类和接口。比如其中定义了Context 接口,可以用于查找、绑定/解除绑定、重命名对象以及创建和销毁子上下文等操作。
查找
最常用的操作是lookup()
,向lookup()提供想要查找的对象的名称,它会返回与该名称绑定的对象
绑定
listBindings()
:返回一个名字到对象的绑定的枚举。绑定是一个元组,包含绑定对象的名称、对象的类的名称和对象本身
列表
list()
与listBindings()
类似,只是它返回一个包含对象名称和对象类名称的名称枚举
list()对于诸如浏览器等想要发现上下文中绑定的对象的信息但又不需要所有实际对象的应用程序来说非常有用
引用
在一个实际的名称服务中,有些对象可能无法直接存储在系统内,这时它们便以引用的形式进行存储
InitialContext 类 构造方法:
1 2 3 4 5 6 InitialContext() InitialContext(boolean lazy) InitialContext(Hashtable<?,?> environment)
常用方法:
1 2 3 4 5 bind(Name name, Object obj) list(String name) lookup(String name) rebind(String name, Object obj) unbind(String name)
Reference 类 构造方法:
1 2 3 4 5 6 7 8 Reference(String className) Reference(String className, RefAddr addr) Reference(String className, RefAddr addr, String factory, String factoryLocation) Reference(String className, String factory, String factoryLocation)
常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void add (int posn, RefAddr addr) void add (RefAddr addr) void clear () RedAddr get (int posn) RefAddr get (String addrType) Enumeration<RefAddr> getAll () String getClassName () String getFactoryClassLocation () String getFactoryClassName () Object remove (int posn) int size () String toString ()
官方文档介绍:Naming Package (The Java™ Tutorials > Java Naming and Directory Interface > Overview of JNDI)
javax.naming.directory 继承了javax.naming,提供了除命名服务外访问目录服务的功能。
可以参考:Directory and LDAP Packages (The Java™ Tutorials > Java Naming and Directory Interface > Overview of JNDI)
javax.naming.ldap 继承了javax.naming,提供了访问LDAP 的能力。
可以参考:Directory and LDAP Packages (The Java™ Tutorials > Java Naming and Directory Interface > Overview of JNDI)
javax.naming.event 包含了用于支持命名和目录服务中的事件通知的类和接口。
Event and Service Provider Packages (The Java™ Tutorials > Java Naming and Directory Interface > Overview of JNDI)
javax.naming.spi 允许动态插入不同实现,为不同命名目录服务供应商的开发人员提供开发和实现的途径,以便应用程序通过JNDI 可以访问相关服务。
Event and Service Provider Packages (The Java™ Tutorials > Java Naming and Directory Interface > Overview of JNDI)
JNDI 操作RMI 直接在之前RMI基础的代码中进行修改就好了
注意要将IremoteObjImpl.java的protect IRemoteObjImpl()改成public
创建一个jndidemo
的包,并创建jndiserver
和jndiclient
的类
jndiserver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.example.rmidemo.jndidemo;import com.example.rmidemo.IRemoteObjImpl;import javax.naming.InitialContext;import javax.naming.NamingException;import java.rmi.RemoteException;public class jndiserver { public static void main (String[] args) throws NamingException, RemoteException { InitialContext initialContext = new InitialContext (); System.setProperty("java.rmi.server.hostname" , "127.0.0.1" ); initialContext.rebind("rmi://127.0.0.1:1099/IRemoteObj" , new IRemoteObjImpl ()); System.out.println("JNDI Server ready" ); } }
jndiclient.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.example.rmidemo.jndidemo;import com.example.rmidemo.IRemoteObj;import javax.naming.InitialContext;public class jndiclient { public static void main (String[] args) throws Exception { InitialContext initialContext = new InitialContext (); IRemoteObj remoteObj = (IRemoteObj) initialContext.lookup("rmi://127.0.0.1:1099/IRemoteObj" ); System.out.println(remoteObj.sayHello("JNDI Client Hello" )); } }
RMIserve -> jndiServer -> JndiClient 依次运行