Cookie、Session、token和JWT伪造
Cookie、Session、token和JWT伪造
一、Cookie
(1)工作原理
Cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器。Cookie中有很多字段。不同网站Cookie中字段是不一样的,是由服务器端设置的。Cookie中常放入session id或者token用来验证会话的登录状态。
(2)Cookie分类
session cookie
当我们打开一个浏览器访问某个网站的时候,该网站服务器会返回一个session cookie,当我们继续访问该网站下其他页面时,用该Cookie验证我们的身份。所以我们不需要每个页面都登录,但是我们关闭浏览器重新访问该网站时,需要重新登录获取浏览器返回的Cookie。session cookie在访问一个网站的过程中一般是不变化的,有时也会变化,比如切换不同的权限时Cookie值会变化。
浏览器生成session cookie的过程如下:
浏览器第一次发送请求(用户名和密码)给Web服务器,Web服务器把用户的登陆信息存在Cookie中发送给浏览器,浏览器再次访问该网站携带Cookie,直接访问。
permennent cookie
是保存在客户端浏览器上存储用户登录信息的数据,是由服务端生成发送给浏览器的。浏览器会将Cookie保存在某个目录下的文本文件中,下次请求同一网站时就发送该Cookie给服务器。前提是浏览器设置开启了Cookie。
(3)安全性问题
HTTPOnly:通过设置HttpOnly属性,可以防止客户端脚本访问Cookie,从而减少跨站脚本攻击(XSS)的风险。当一个 cookie 被标记为 HTTP-only 时,它只能通过 HTTP 协议进行访问和修改,无法通过客户端脚本(如 JavaScript)进行访问。
Secure:表示该 Cookie 仅允许通过 HTTPS 加密连接传输。这意味着,如果用户通过 HTTP(非加密)访问网站,浏览器不会发送带有 Secure 标记的 Cookie。这样能有效防止 Cookie 在网络传输过程中被窃听或篡改,避免中间人攻击(MITM)带来的安全风险。
SameSite:
SameSite 属性用来限制 Cookie 在跨站请求时的发送规则,防止第三方网站借助用户身份发起伪造请求。该属性有三个主要取值:
- Strict:严格模式,只允许同站请求携带 Cookie。任何跨站请求都不会携带 Cookie,安全性最高,但可能影响用户体验,例如从其他网站点击链接跳转时无法保持登录状态。
- Lax(默认):在大多数跨站请求中不发送 Cookie,但允许部分“安全”跨站请求(如从外部网站点击链接跳转)携带 Cookie,兼顾安全与用户体验。
- None:允许所有跨站请求携带 Cookie,但必须搭配 Secure 属性使用,否则浏览器会忽略该设置。
通过合理配置 SameSite,可以大幅度减少 CSRF 攻击的风险。
(4)伪造
- 可以通过XSS攻击获取用户的Cookie。
- 可以通过CSRF攻击利用用户的Cookie进行未授权操作。
二、Session
(1)工作原理
session是保存在服务器端的经过加密的存储特定用户会话所需的属性及配置信息的数据。当我们打开浏览器访问某网站时,session建立,只要浏览器不关闭(也有时间限制,可以自己设置超时时间),这个网站就可以记录用户的状态,当浏览器关闭时,session结束。
session是一个容器,可以存放会话过程中的任何对象。
SessionID除了可以保存在Cookie中外,还可以保存在URL中,作为请求的一个参数(sid)。其中,保存在Cookie中的SessionID与session cookie类似
(2)Session的弊端
上文提到Session的信息是保存在服务器端的,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,这时候Session的问题就会暴露出来:
每个用户经过应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言Session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源。
比如说用两个机器组成一个集群,小明通过机器A登录系统,那么SessionID就会保存在机器A上,假设下一次请求被转发到机器B怎么办?机器B没有小明的SessionID。有时候会采用session sticky的小技巧,就是让小明的请求一直粘连在机器A上。但是这也不管用,要是机器A挂掉了,还得转到机器B上去。这样的话就只好做SessionID的复制了,即把A机器上的SessionID复制到B机器上。后来memcached提出把SessionID集中存储在一个地方,所有机器都通过这个地方来访问。但是这样一来,一旦负责Session的机器挂掉,所有人就得重新登录一遍,这种问题也是不愿意发生的。
(3)安全问题
- Session固定攻击:攻击者可以通过劫持用户的Session ID来进行会话固定攻击。
- Session劫持:一旦攻击者获取了用户的Session ID,他们就可以冒用用户的身份。
- Session伪造(针对 Flask Session):如果知道Session的加密方法的话,可以对session进行伪造,冒充用户身份
(4)Flask Session伪造
Flask是一个基于Jinja2模板搭建而成的应用框架,具体如下所示
Flask是一个Web应用程序框架,使用Python编写。该软件由ArminRonacher开发,他领导着Pocco国际Python爱好者小组。该软件基于WerkzeugWSGI工具箱和Jinja2模板引擎.
Flask中的Session,它是存在于客户端的,也就是说我们在进行登录过后可以看到自己的Session值,而当我们对这个Session值进行base64解码后,就可以读取它的具体内容。
对应Flask,它在生成session时会使用app.config['SECRET_KEY']中的值作为salt对session进行一个简单处理,那么这里的话,只要key不泄露,我们就只能得到具体内容,但是无法修改具体内容,因此这个时候就引发了一个问题,当key泄露的时候,就出现了内容伪造的情况,比如具体内容为{'name':'123'},而当我们掌握key时,可修改内容为{'name':'admin'},从而达到一个越权的效果,因此我们接下来就要说说CTF中怎么获取Key
Key的获取
有两种情况
第一种情况,当源码泄露时,Key也可能会泄露,它的泄露位置是config.py,在[HCTF2018]admin中有所体现。
第二种情况,就是当存在任意文件读取漏洞时,我们可以通过读取/proc/self/maps来获取堆栈分布,而后读取/proc/self/mem,通过真正则匹配筛选出我们需要的key,这个在**[2022蓝帽杯]file_session**中有所体现。
看下列例子
1 | |
可以看到它的这个key是随机生成的uuid,在download路由中存在key,我们这里注意到他有三个参数,分别是file、offset以及length,接下来按我们刚刚所说,第一步通过/proc/self/maps读取堆栈分布,然后在读取/proc/self/mem的内存数据。这里的话需要说明一下,内存中存在一个动态库/usr/local/lib/faketime/libfaketime.so.1,这个动态链接库是可以劫持程序获取时间时的返回值。

因此我们这里可以使用这个来进行一个简单筛选,读取出堆栈分布,接下来进行读取内存,此时用一个uuid格式的正则匹配,就可以得到key(由于没有找到复现环境,这里使用的截图参考自其他师傅的Wp)
1 | |

此时就可以进行Session伪造了
CTFshow内部赛[蓝瘦]
题目环境https://ctf.show/challenges 打开题目是一个环境框

看源代码是否有注释

两个注释
param:参数,这里的话就可能是提示有名为ctfshow的参数 key:这里的话联想到FLask的Secret_key
随便输入一下,成功进入

界面回显admin,看一下cookie
1 | |
用flask_session_cookie_manager3.py进行解密
项目地址:https://github.com/noraj/flask-session-cookie-manager/
1 | |

得到数据为{'username':'1'},猜测这里应该是想让我们修改为admin,因此修改1为admin,而后进行加密
1 | |

将得到的Session去替换网站上的

提示缺少参数,这里想到之前的ctfshow,拿上去看看

有回显,想到这里可能是SSTI,检验一下

用语句直接打
1 | |

三、token
(一)token建立
由于上述的Session的弊端
有人提出了摆脱Session的想法。具体就是不在服务端保存SessionID了,让客户端去保存一个服务端生成的token,每次请求的时候附加上这个token,服务端只需要对这个token进行相应的校验就可以完成身份的验证了。
由于服务端不再存放SessionID了,也不会有token,那么可能就会有攻击者伪造token来攻击。因此服务端需要对token做一些防伪造的的处理,具体就是对数据做一个签名。譬如用HMAC-SHA256算法加上一个只有服务器知道的密钥,对数据做一个签名,然后把签名和数据一起作为token,由于密钥别人不知道,就无法伪造token了。服务器并不会保存这个token,当再次将token发到服务器的时候,服务端会用同样的HMAC-SHA256算法和同样的密钥去对数据再计算一次签名,并和token中的签名做一个比较,如果相同的话,就可以判断出已经登陆过,并且可以直接取到userId;如果不相同,则数据部分肯定被人篡改过,这时就能够做一些身份校验失败的相应处理。
这样一来,服务器就不需要保存SessionID了,只需要生成token,然后校验token,相当于用CPU计算时间换回了存储空间。
(二)工作原理
最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名)
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程上是这样的:
1.用户使用用户名密码来请求服务器
2.服务器进行验证用户的信息
3.服务器通过验证发送给用户一个token
4.客户端存储token,并在每次请求时附送上这个token值
5.服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了。
(三)安全问题
- 攻击者可以尝试篡改Token的签名或内容来伪造身份。
- 需要破解Token的签名或猜测其内容结构。
四、JWT
(1)工作原理:
JWT是一个紧凑的、URL安全的令牌格式,它被定义为三个部分:头部(Header)、载荷(Payload)、签名(Signature)。
头部(header)
JWT的头部承载两部分信息:
1.声明类型:这里是JWT
2.声明加密算法:通常使用HMAC SHA256
譬如上文第一部分的头部即为:
1 | |
的base64加密后的字符串。
JWT支持将算法设定为“None”。如果“alg”字段设为“ None”,那么JWT的第三部分会被置空,这样任何token都是有效的。这样就可以伪造token进行随意访问。
载荷(payload)
存放有效信息,包含标准中注册的声明、公共的声明、私有的声明
标准中注册的声明 (不强制使用)
1 | |
公共的声明
可添加任何信息,一般添加用户的相关信息或必要的信息,不建议添加敏感信息。
私有的声明
私有声明是提供者和消费者共同定义的声明,不建议存放敏感信息。
譬如上文第二部分的载荷即为:
1 | |
的base64加密后的字符串。
签证(signature)
JWT第三部分是一个签证信息,由三部分组成:header(base64加密后的)、payload (base64加密后的)、secret。
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成JWT的第三部分。
譬如上文第三部分的签证即为:
1 | |
加盐secret组合加密后的字符串。
注意:secret是保存在服务器端的,JWT的签发生成也是在服务器端的,secret就是用来进行JWT的签发和JWT的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret,那就意味着客户端是可以自我签发JWT了。
(2)JWT认证
客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。
服务器每次收到信息都会对它的前两部分进行加密,然后比对加密后的结果是否跟客户端传送过来的第三部分相同,如果相同则验证通过,否则失败。

JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行进行身份验证。为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。
(3)安全问题
- 修改算法为none
- 修改算法从RS256到HS256
- 信息泄漏 密钥泄漏
- 爆破密钥
(4)JWT伪造
第一步:将web服务器返回的JWT数据进行翻译,我这里使用的是这个https://jwt.io/ 在线工具(也可以用base64去解码),找到要伪造的内容。
第二步:使用工具爆破秘钥https://github.com/brendan-rius/c-jwt-cracker 该程序需要在Linux环境下运行。
工具的安装及使用
终端进入到虚拟机下该文件目录中,输入docker build . -t jwtcrack即可进行build,如果报错的话,证明你的权限不够,需要将用户添加到docker用户组,输入:
1 | |
后重启即可。
build结束后就可以运行了
1 | |
等待输出secret即可。
第三步:在https://jwt.io/ 在线工具中输入爆破出来的秘钥进行验证,如果第三部分的内容与之前一致,证明秘钥正确,就可以进行修改了,修改后再粘贴生成的JWT利用burpsuite抓包进行修改后发回到服务器中即可实现JWT伪造。