当前位置:网站首页>反射修改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接口的一个实现类
观察里面的方法,并在servicepostParseRequest的开头分别打上断点
其中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);
}
}
}
边栏推荐
- MySQL data view
- Meteorological data processing example - matlab string cutting matching and R language date matching (data splicing)
- JS逆向入门学习之回收商网,手机号码简易加密解析
- 入门 Polkadot 平行链开发,看这一篇就够了
- 我们的Web3创业项目,黄了
- uniapp 连接ibeacon
- How can project cost control help project success?
- STM32+ULN2003 drives 28BYJ4 stepper motor (forward and reverse according to the number of turns)
- 第九章:activit内置用户组设计与组任务分配和IdentityService接口的使用
- 如何测试一下现场的备机失败,转发主机的场景?
猜你喜欢

百年北欧奢华家电品牌ASKO智能三温区酒柜臻献七夕,共品珍馐爱意

电竞、便捷、高效、安全,盘点OriginOS功能的关键词

【 temperature warning program DE development 】 event driven model instance

MySQL transactions

阿里全新推出:微服务突击手册,把所有操作都写出来了PDF

单片机:温度控制DS18B20

高质量 DeFi 应用构建指南,助力开发者玩转 DeFi Summer

In-depth understanding of timeout settings for Istio traffic management

Opencv算术操作

012_SSS_ Improving Diffusion Model Efficiency Through Patching
随机推荐
FPGA:基础入门LED灯闪烁
技术干货 | 基于 MindSpore 实现图像分割之豪斯多夫距离
产品太多了,如何实现一次登录多产品互通?
Chapter 5: Activiti process shunting judgment, judging to go to different task nodes
poj2935 Basic Wall Maze (2016xynu暑期集训检测 -----D题)
浅析WSGI协议
[Translation] Chaos Net + SkyWalking: Better observability for chaos engineering
Microcontroller: temperature control DS18B20
Opencv算术操作
R语言使用yardstick包的pr_curve函数评估多分类(Multiclass)模型的性能、查看模型在多分类每个分类上的ROC曲线(precision(精准率),R代表的是recall(召回率)
第四章:redis 数组结构的set和一些通用命令「建议收藏」
Common operations of oracle under linux and daily accumulation of knowledge points (functions, timed tasks)
还在找网盘资源吗?快点收藏如下几个值得收藏的网盘资源搜索神器吧!
创建一个 Dapp,为什么要选择波卡?
Chapter 5: Multithreaded Communication—wait and notify
MySQL事务
我们的Web3创业项目,黄了
多线程(进阶) - 2.5w字总结
Opencv图像缩放和平移
The JVM collection that Alibaba's top architects have summarized for many years, where can't I check it!