当前位置:网站首页>反射修改jsessionid实现Session共享
反射修改jsessionid实现Session共享
2022-08-05 10:26:00 【ZJH'blog】
其实,原生的API是不行的。(不过文章末尾我在通读源码之后用反射写了一个Session共享的方式,需要的可以直接使用)
- 虽然说前端可以通过伪造Cookie中的JESESSIONID来模拟劫持
- 而后端并不能直接通过原生API成功修改JESSIONID获取指定session。
- 本案例一共12000字,源码分析部分10000字左右
0.Session域的大体流程
先大概了解一下为了创建Session会话域,原生API做了什么
- 存在一个接口:HttpServletRequest,有实现类Request、RequestFacade(外观模式,也是实际请求对象)
- Session对象维护在Request中
- Request对象维护在RequestFacade中
0.1第一次:创建Session
- JSESSIONID是在请求打过来的时候初始化的时候,用
ServerCookie
对Request进行了赋值(Request类是HttpServletRequest的实现类) - 然后创建一个供用户使用的Cookie(实际上是ServerCookie的拷贝值、ServerCookie不允许用户使用)
- 请求打过来的初始化过程就对Request对象的
Session和jsessionid
属性进行了绑定 - Request对象的
Session和jsessionid
属性(主要是这两个)在很多地方都起着互相校验的功能(修改时需要两个一起修改,否则会导致request.getSession()时重新生成Session)
0.2第二次:获取Session
- ServerCookie中携带的jseesionid赋值给Request对象
- 请求打过来时被预先处理,调用
request -》context -》manager
的findSession
方法直接获取Session后传给Request对象当形参 - 之后使用request.getSession()调用则直接调用Request的属性地址
下面进行论述
1.前端设置Cookie再发送
那么正常情况下我们切换浏览器(切换Session)是不能够获取这个k-v对的,但是我们可以通过在浏览器、PostMan、js代码
将JSESSIONID设置为刚才的
这样就能在服务器中劫持Session了但是实际开发环境中,这种方案可行度接近于0,那么尝试下后端原生API修改Cookie的值??
2.后端设置Cookie值
2.1输出JSESSIONID
Cookie[] cookies = request.getCookies();
if(cookies!=null)
for(Cookie c: cookies){
//此案例中Cookie仅包含JSESIONID,等价于(request.getSession().getId())
System.out.println(c.getName()+":"+c.getValue());
}
2.2修改JSESSIONID
request.getCookies()[0].setValue("newJSessionId");
2.3getSession()不变
//还是之前的JSESSIONID
System.out.println(request.getSession().getId());
3.后端为什么不能修改Session
3.1虽然后端是可以修改Cookie的
比如在使用 request.getCookies()[0].setValue("newJSessionId");
修改了Cookie之后,重新获取的Cookie确实是最新修改后的
Cookie[] cookies2 = request.getCookies();
if(cookies!=null)
for(Cookie c: cookies2){
System.out.println(c.getName()+":"+c.getValue());
}
但是出了另外一个问题,Cookie能修改,Cookie中的JESSIONID修改后却没有用?
3.2修改JSESSIONID但无效
3.2.1debug:getSession()
一直跟进request.getSession()
方法,发现修改Cookie中JSESSIONID之前和之后都是在这个地方返回的session对象,证明在getSession()之前,session一定被初始化了,修改Cookie不影响直接获取session
整个过程没有看到任何跟Cookie中JSESSIONID有关的代码
因此可以大胆推测:session对象早在请求打过来的时候就获取了其对应的地址值,后期修改Cookie不会导致session对象指向的地址改变
4.session初始化过程(多次请求的角度)
既然session不能被中途修改,那么一定是在初始化的时候,从Cookie中取出JSESSIONID,然后赋值给的session
4.1利用debug技巧定位
一个小技巧,可以在session对象上面打一个断点,每次(按F9)执行到修改session值的时候都会进入
4.2模拟创建Session
- 现在代码里写一个
request.getSession()
;然后新开一个浏览器请求两次,模拟创建Session和寻找Session的过程 - Session的使用是
懒汉式
的,如果你不调getSession方法,他就不会去创建,更不会在响应Cookie里面携带JSESSIONID。 - Session的创建是懒汉,但并不意味着有关Session的配置信息的初始化是懒汉的
4.2.1创建Session
4.2.2寻找Session
4.2.3释放
释放环节,为下一次请求做准备
4.3模拟第二次请求Session
浏览器刷新一下请求
4.3.1直接寻找Session
这次不需要创建,而是直接根据requestedSesionId来寻找,然后将其维护在request的session属性中
4.3.2释放
再次释放资源
4.4小结
Session的创建,寻找都是在doGetSession(boolean create)
方法中完成的。
5.getSession的实现(多次调用的角度)
由上述可知,getSession()最终调用的逻辑一定是doGetSession(),验证一下
- 应该找与Request同包的,里面只有两个
5.1基本结构
5.1.1外观模式Facade
- Facade是外观模式,最终的调用者肯定都是
request
5.1.2Request中的getSession(create)
这里明显有个create,决定这个方法是获取还是创建
- 明显空参的是使用的时候调
- 带参的一定带false参数,在初始化的时候调
5.2一次请求,多次getSession()
在接口中写下代码模拟调用两次getSession()。现在仅模拟第二次请求(不需要再去创建Session,只需要去寻找Session)
HttpSession session1 = request.getSession();
HttpSession sessio2n = request.getSession();
通过debug可以知道
- session1 需要先findSession把session交给request去维护,然后从request中取session对象
- 而sessio2n 只需要直接从request中取session对象
5.3小结
通过上面的案例可以侧面反映出大标题4中为什么最后需要进行资源释放
6.Session流程总结
6.1创建和获取都是懒汉式的
6.1.1创建懒汉式
- 如果
每次请求
都不需要getSession(),那么Session永不创建,Cookie中的JSESSIONID永不创建
6.1.2获取懒汉式
- 如果
一次请求
中多次使用getSession(),那么只有第一次会涉及createSession、findSession、request.session获取 - 而
接下来
每一次都只直接request.session获取
6.2路径变量历史遗留问题
早期应该是可以通过url上指定jsessionid来设置的,现在会覆盖掉导致设置失效
6.3创建、数据预置的关系
- 其实在
CoyoteAdapter
类的后置初始化过程中,会将jsessionid写入request,至于你用不用session完全取决于自己。 - 这个过程是数据预置,预置了也不一定会创建or获取
那么问题现在转成了这个requestSessionId是如何被初始化的
7.CoyoteAdapter(初始化过程)
适配器模式的 Adapter接口的一个实现类
观察里面的方法,并在service
postParseRequest
的开头分别打上断点
其中service(核心业务)
先将Requset对象进行创建,
然后postParseRequest(后置处理)
将req写入request
7.1postParseRequest()
这个方法名结合注释可知:解析完request之后必须要做的一些事。在开头打个断点然后发请求
7.1.1request对象赋值
在执行postParseRequest()之前,request对象中还没有值,所有的请求的值都存在于org.apache.coyote.Request req
中 执行完成之后就将请求参数赋值给了request
- 注意这三个的顺序!
- 后面经分析我认为这个parsePathParameters解析路径变量是一个历史遗留问题,以前应该是支持的,现在无论如何都会被下面的逻辑覆盖
7.1.2先parsePathParameters解析路径变量
按这种url格式发送http://localhost:8088/pic/people/pic1;jsessionid=F0D3200C7D61C8D41E129924585B6234
这里通过解析request.getParam 用这个枚举类,即解析路径变量中的jessesionid 然后赋值给request
7.1.3然后parseSessionCookiesId
- 这里并没有将
Cookie
赋值给request,而是直接从ServerCookie
中取值JSESSIONID塞给request
- 并且这个方法是在7.1.1之后执行的,也就是即便是路径变量中写了这个参数也会被覆盖
7.1.3被覆盖的路径变量jsessionid(历史遗留问题)
http://localhost:8088/pic/people/pic1;jsessionid=F0D3200C7D61C8D41E129924585B623这样的写法,会导致:
- 先修改成功request中的sessionid
- 然后再次被ServerCookie中的sessionid所覆盖
- 然后再用这个sessionid去获取Session对象
7.2小结
7.2.1后端修改为何没用
正是因为JSESSIONID是在这个CoyoteAdapter
类中的postParseRequest()
方法中的parseSessionCookiesId()
方法中就对JSESSIONID进行了赋值,导致如下情况:
- 前端请求可以通过修改Cookie中的JSESSIONID来实现获取指定session
- 但后端不能通过request在代码中修改Cookie中的JSESSIONID来实现获取指定session,也不能指定路径变量jsessionid(可能是历史遗留问题,会被覆盖)
7.2.2Cookie和ServerCookie
很明显这个JSESSIONID是从ServerCookie中获取的,那二者有什么区别呢?
ServerCookie来自于coyoteRequest,即tomcat的底层实现,因此不能针对其Cookie进行修改从而达到模拟session的效果
8.反射
因为request.getSession()
调用的是Reuqest类下的doGetSession()
,requestedSessionId
又是Request下的属性,那么能不能通过反射修改requestedSessionId的值来修改findSession()
的结果呢?答案是可以
8.0撸组件的前提知识
- 通常作为形参进行传递的
HttpServletRequest
是一个接口,实际上传递是是其实现类RequestFacade
,在涉及到反射操作的时候需要再开辟一个RequestFacade
的栈空间指向HttpServletRequest
堆空间 - 外观模式:
RequestFacade
实际上大部分调用的是Request
- Request下的
doGetSession(Boolean craete)
是最终获取Session对象的方法,包括初始化时session的生成、httpServletRequest.getSeesion()获取。区别是前者形参create=true、后者形参create=false - 在Request类下的doGetSession()方法中,当存在session时直接走
manager.findSession(sessionId)
;当初始化时,先getRequestedSessionId()
根据JVM随机数获取sessionid,然后作为形参调用session = manager.createSession(sessionId);
因此可以有如下两条思路:
- 通过findSession()直接从ConcurrentHashMap中获取Session对象
- 通过对doGetSession()的所需的参数进行一个覆盖操作
8.1根据JSESSIONID直接返回Session对象
/**
* @param httpServletRequest 多态性,本质是requestFacade
* @param jsessionid 指定的jsessionid
* @return org.apache.catalina.Session 指定的Session对象
**/
public static Session getSession(HttpServletRequest httpServletRequest,String jsessionid) throws Exception{
RequestFacade requestFacade = (RequestFacade)httpServletRequest;
Class<? extends RequestFacade> facadeClazz = requestFacade.getClass();
Field requestField = facadeClazz.getDeclaredField("request");
requestField.setAccessible(true);
Request request = (Request)requestField.get(requestFacade);
Context context = request.getContext();
Manager manager = context.getManager();
Session session = manager.findSession(jsessionid);
return session;
}
8.2将指定的Session和JSESSIONID赋值给request
/**
* @Description 调用此方法后,httpServletRequest.getSession()获取的是jsessionid对应的Session
* @Author zjh
* @Date 17:59 2022/8/3
* @param httpServletRequest 请求头,用于获取session
* @param jsessionid 指定的sessionid
* @return void
**/
public static void setSession2Request(HttpServletRequest httpServletRequest,String jsessionid) throws Exception{
//RequestFacade
RequestFacade requestFacade = (RequestFacade) httpServletRequest;
Class<? extends RequestFacade> facadeClazz = requestFacade.getClass();
//RequestFacade获取Request
Field requestField = facadeClazz.getDeclaredField("request");
requestField.setAccessible(true);
//Request
Request request = (Request)requestField.get(requestFacade);
Class<? extends Request> requestClazz = request.getClass();
//Request设置Session=null
Field sessionField = requestClazz.getDeclaredField("session");
sessionField.setAccessible(true);
sessionField.set(request,null);
//Request设置requestedSessionId = 指定值
Field requestedSessionIdField = requestClazz.getDeclaredField("requestedSessionId");
requestedSessionIdField.setAccessible(true);
requestedSessionIdField.set(request,jsessionid);
}
8.3最终工具类
经过异常处理之后的呈现结果
import java.io.IOException;
import java.lang.reflect.Field;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.Context;
import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Description Session共享的工具类,用于Session共享,可实现传入JSESSIONID返回对应Session对象,也可实现传入JSESSIONID永久修改request
* @Author zjh
* @Date 16:10 2022/8/3
**/
public class SessionShareUtils {
private static Logger logger = LoggerFactory.getLogger("SessionShareUtils");
/**
* @param httpServletRequest 多态性,本质是requestFacade
* @param jsessionid 指定的jsessionid
* @return org.apache.catalina.Session 指定的Session对象
**/
public static Session getSession(HttpServletRequest httpServletRequest,String jsessionid) {
Session session = null;
try {
RequestFacade requestFacade = (RequestFacade)httpServletRequest;
Class<? extends RequestFacade> facadeClazz = requestFacade.getClass();
//获取Request 最后findSession
Field requestField = facadeClazz.getDeclaredField("request");
requestField.setAccessible(true);
Request request = (Request)requestField.get(requestFacade);
Context context = request.getContext();
Manager manager = context.getManager();
session = manager.findSession(jsessionid);
}
catch (NoSuchFieldException e) {
logger.error("反射找不到属性{}",e);
}
catch (IllegalAccessException e) {
logger.error("反射未设置允许访问{}",e);
}
catch (IOException e) {
logger.error("HttpServletRequest的IO异常{}",e);
}
return session;
}
/**
* @Description 调用此方法后,httpServletRequest.getSession()获取的是jsessionid对应的Session
* @Author zjh
* @Date 17:59 2022/8/3
* @param httpServletRequest 请求头,用于获取session
* @param jsessionid 指定的sessionid
* @return void
**/
public static void setSession2Request(HttpServletRequest httpServletRequest,String jsessionid) {
try {
//RequestFacade
RequestFacade requestFacade = (RequestFacade) httpServletRequest;
Class<? extends RequestFacade> facadeClazz = requestFacade.getClass();
//RequestFacade获取Request
Field requestField = facadeClazz.getDeclaredField("request");
requestField.setAccessible(true);
//Request
Request request = (Request)requestField.get(requestFacade);
Class<? extends Request> requestClazz = request.getClass();
//Request设置Session=null
Field sessionField = requestClazz.getDeclaredField("session");
sessionField.setAccessible(true);
sessionField.set(request,null);
//Request设置requestedSessionId = 指定值
Field requestedSessionIdField = requestClazz.getDeclaredField("requestedSessionId");
requestedSessionIdField.setAccessible(true);
requestedSessionIdField.set(request,jsessionid);
}
catch (NoSuchFieldException e) {
logger.error("反射找不到属性{}",e);
}
catch (IllegalAccessException e) {
logger.error("反射未设置允许访问{}",e);
}
}
}
边栏推荐
- 用KUSTO查询语句(KQL)在Azure Data Explorer Database上查询LOG实战
- The fuse: OAuth 2.0 four authorized login methods must read
- 数分面试(一)----与业务相关
- 【Office】Microsoft Office下载地址合集(微软官方原版离线安装下载)
- 多线程(进阶) - 2.5w字总结
- This notebook of concurrent programming knowledge points strongly recommended by Ali will be a breakthrough for you to get an offer from a big factory
- The founder of the DFINITY Foundation talks about the ups and downs of the bear market, and where should DeFi projects go?
- 2022华数杯数学建模A题环形振荡器的优化设计思路思路代码分享
- What is SPL?
- 你最隐秘的性格在哪?
猜你喜欢
This notebook of concurrent programming knowledge points strongly recommended by Ali will be a breakthrough for you to get an offer from a big factory
JS逆向入门学习之回收商网,手机号码简易加密解析
linux下oracle常见操作以及日常积累知识点(函数、定时任务)
How to choose coins and determine the corresponding strategy research
Common operations of oracle under linux and daily accumulation of knowledge points (functions, timed tasks)
RT-Thread记录(一、RT-Thread 版本、RT-Thread Studio开发环境 及 配合CubeMX开发快速上手)
Huawei's lightweight neural network architecture GhostNet has been upgraded again, and G-GhostNet (IJCV22) has shown its talents on the GPU
PCB布局必知必会:教你正确地布设运算放大器的电路板
还在找网盘资源吗?快点收藏如下几个值得收藏的网盘资源搜索神器吧!
【MindSpore Easy-Diantong Robot-01】You may have seen many knowledge quiz robots, but this one is a bit different
随机推荐
[Android]如何使用RecycleView in Kotlin project
Jenkins manual (2) - software configuration
js hijacks the array push method
FPGA: Basic Getting Started Button Controlling LED Lights
Development common manual link sharing
还在找网盘资源吗?快点收藏如下几个值得收藏的网盘资源搜索神器吧!
第五章:redis持久化,包括rdb和aof两种方式[通俗易懂]
Ali's new launch: Microservices Assault Manual, all operations are written out in PDF
Common operations of oracle under linux and daily accumulation of knowledge points (functions, timed tasks)
60行从零开始自己动手写FutureTask是什么体验?
数据可视化(一)
【MindSpore Easy-Diantong Robot-01】You may have seen many knowledge quiz robots, but this one is a bit different
SQL外连接之交集、并集、差集查询
Our Web3 Entrepreneurship Project, Yellow
Brief Analysis of WSGI Protocol
IDEA performs the Test operation, resulting in duplicate data when data is inserted
What is SPL?
linux下oracle常见操作以及日常积累知识点(函数、定时任务)
Complete image segmentation efficiently based on MindSpore and realize Dice!
First Decentralized Heist?Loss of nearly 200 million US dollars: analysis of the attack on the cross-chain bridge Nomad