【Linux篇章】互联网身份密码:解密 Session 与 Cookie 的隐藏玩法和致命漏洞!
本篇摘要
- 本篇将承接上篇
HTTP
讲解(戳我查看
)遗留的关于Cookie与Session的介绍,在本篇,将会介绍Cookie
的由来,作用,以及缺点等,进而引出Session
,最后介绍一下它们的性质等,以接我们上文模拟实现的http服务器加上这两个功能
做测试,来验证上述的成立性!
欢迎拜访:
点击进入博主主页
本篇主题:解密Cookie与Session的不解之缘!
制作日期: 2025.08.04
隶属专栏:点击进入所属Linux专栏
一· Cookie
介绍
什么是 Cookie
?
- HTTP Cookie(也称为 Web Cookie、 浏览器 Cookie 或简称
Cookie
) 是服务器发送到用户浏览器并保存在浏览器上的一小块数据, 它会在浏览器之后向同一服务器再次发起请求时被携带并发送到服务器上
。 通常, 它用于告知服务端两个请求是否来自同一浏览器, 如保持用户的登录状态、 记录用户偏好
等! - 简单来说,就是记录用户的一个
标记
,便于服务器识别用户!
Cookie
的原理
- 当用户第一次访问网站时, 服务器会在响应的 HTTP 头中设置 Set-Cookie字段, 用于发送 Cookie 到用户的浏览器。
- 浏览器在
接收到 Cookie
后, 会将其保存在本地
(通常是按照域名进行存储
) 。 - 在之后的请求中, 浏览器会自动在 HTTP 请求头中携带 Cookie 字段, 将之前保存的 Cookie 信息
发送给服务器
。
Cookie
的用途
- 用户认证和会话管理(常用)。
- 跟踪用户行为。
- 缓存用户偏好等。
单Cookie行为举例
下面我们就拿单Cookie的用户认证
举个例子(因为会话管理等是结合Session的后续讲):
比如我们登录的时候,
由于http无状态
,就需要它来记录用户登录时候的账号密码等,当用户第一次成功登录,就把它记录下来
,发给浏览器
,然后我们关断这个网页,下次再去访问甚至很久或者关了浏览器在一段时间内都是可以进行直接登录的
(这就利用了Cookie机制
,之后每次我们登录,浏览器都会自动把对应域名下的Cookie携带上,但是这样是有风险的
,我们后面谈到,因此需要结合Session一起用
)。
Cookie
存储分类
会话
Cookie(Session Cookie
): 在浏览器关闭
时失效。持久
Cookie(Persistent Cookie
): 带有明确的过期日期或持续时间
,可以跨多个浏览器会话存在。
因此可以认为储存的Cookie分为内存级别与文件级别,就是对应上面的两种分类,内存中是见不到的,但是文件方式以二进制或 sqlite 格式
存储,一般我们查看, 直接在浏览器对应的选项
中直接查看即可。
比如这种查看文件格式:
Cookie
应用的格式
首先,我们要知道Cookie是在浏览器和服务器之间交互的一个标记。
- HTTP 存在一个
报头选项
:Set-Cookie
, 可以用来进行给浏览器设置 Cookie值。 - 在 HTTP
响应头中添加
, 客户端(如浏览器) 获取并自行设置并保存Cookie。
具体用法
set-cookie: username=111; expires=sat,03 May 2025 10:34:45 UTC; path=/;
看一下例子:
服务端先Set-Cookie
:
客户端再发送回来
:
下面我们就以这个例子,说一下下面Cookie的填充部分,我们就以Cookie对应的名称
,expires
,还有path
讲解:
-
对于服务端,需要建立Cookie也就是
Set-Cookie
,那么它后面跟的第一个名称
就是将来浏览器要发送给服务器的(假设我们要进行登录验证,那么第一个值就可以设置成对应的关键词
比如登录,密码等)。 -
由于下面我们测试的是
文件级别的Cookie
,因此给它设置上过期时间(expires),这个时间格式就是我们上面例子那样,这里我们采取的是UTC
也就是协调世界时(UTC 是现在用的时间标准, 多数全球性的网络和软件系统将其作为标准时间),当然也可以是GMT(格林尼治标准时间),感兴趣的可以去了解下,这里不是重点,这里如果设置了expires那么就文件级别
等到到时间就过期自动清除,否则就是内存级别
,浏览器关闭自动清除! -
对于path,其实就是对应的路径,也就是
指定的path
后,只有我们访问对应的浏览器下Cookie保存的路径时,浏览器才会发送这个Cookie,比如保存的是/ 那么始终都会发送,但是如果是/a/b,那么只有访问这个路径才会发送
,后面我们验证下! -
这里我们服务端只需要
按照指定格式
给浏览器发生即可,然后浏览器就会按照一定规则储存起来,需要的时候在按照一定格式发回来
,这样完成交互(这里他俩的解析方式我们不用管)!
如果有需要了解其他的拓展部分,可参考这张表:
注意事项:
-
每个 Cookie 属性都以分号
(;)
和空格( )
分隔。 -
名称和值
之间使用等号(=)
分隔。 -
如果
Cookie 的名称或值包含特殊字符
(如空格、 分号、 逗号等) , 则要进行URL 编码
(比如名称如果包含了就需要自主进行编码,否则发给浏览器识别不了就不会储存这个Cookie,这也就要求双方自主编码处理了,这里一般还是服务器要注意,因为是它构建Cookie
)。 -
这里我们比如拿
登录
为例子,此时我们不能一个Set-Cookie后面跟账号密码就完了,因为浏览器会拿Cookie的第一个名称作为自己本地保存的名称
,因此我们需要给它发两个Cookie:一个username,一个password
,这样它就会呈现两个Cookie保存,发过来的时候合并成一个Cookie(如下图所示)
就是上面我们看到的那样(就把它当做规则记住)!
Cookie
安全性
-
使用
secure 标志
可以确保 Cookie 仅在 HTTPS 连接上发送, 从而提高安全性。 -
使用
HttpOnly 标志
可以防止客户端脚本(如 JavaScript) 访问 Cookie,从而防止 XSS 攻击。 -
通过合理设置
Set-Cookie 的格式和属性
, 可以确保 Cookie 的安全性、 有效性和可访问性, 从而满足 Web 应用程序的需求。
以上了解即可!
单独使用Cookie
的风险
有利就有弊:
比如我们登录账号的时候,返回来的Cookie就是账号与密码然后进行保存
,那么此时如果浏览器被黑客监控,此时账号就相当于被盗了,信息就都泄露了
!!!
- 由于
Cookie 是存储在客户端的
, 因此存在被篡改或窃取
的风险。
那么如何避免呢,也就是后续我们引入的Session
了,它虽然避免了盗号
的风险,但是也是会泄露个人数据信息等
!
基于模拟实现HTTP服务器
测试 Cookie
下面我们还是基于我们之前写的http服务器的代码进行改造,给它加上请求报头返回Cookie
的功能即可,先说下思路:
首先,我们密码设置的是
666
,也就是只要用户输入的密码是666,无论用户名是什么,此时第一次输入正确密码
,就建立Cookie然后给客户端发送作为Cookie
保存,当后面无论输入什么或者不输入都能登录进来
,然后我们给它加了个截止日期
,也就是到时间Cookie自动清除,还有指定路径
等!
大致分成以下几步:
- 构建
Cookie
: 此时我们按照一定的格式把对应的名称,expires
,path
给它添加进去。
// 获得时间: string get_mon(int month) { vector<string> months = { \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\" }; return months[month]; } string get_week(int day) { vector<string> weekdays = { \"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\" }; return weekdays[day]; } string expires_time(int te) { time_t curtime = time(nullptr) + te; struct tm* pm = gmtime(&curtime); // 可以认为传递指针为了防止拷贝,返回指针可以认为是便于指针访问 char buff[1024]; snprintf(buff, sizeof(buff), \"%s, %02d %s %d %02d:%02d:%02d UTC\", get_week(pm->tm_wday).c_str(), pm->tm_mday, get_mon(pm->tm_mon).c_str(), pm->tm_year + 1900, pm->tm_hour, pm->tm_min, pm->tm_sec); return buff; } void set_cookie_username(string username) { string cookie; cookie = \"Set-Cookie: username=\" + username + \"; expires=\" + expires_time(300) + \"; path=/;\"; _cookies.emplace_back(cookie); } void set_cookie_password(string password) { string cookie; cookie = \"Set-Cookie: password=\" + password + \"; expires=\" + expires_time(300) + \"; path=/;\"; _cookies.emplace_back(cookie); }
login界面
的时候进行提取对应的请求报头中Cookie的password信息进行保存
,后面无论是get还是post方法拿到的都是一样的,下面我们就判断拿到的Cookie
信息是否为666
即可,如果是的话就直接给它返回对应登录成功界面
,否则:那么看password
是否是666
,如果是就构建Cookie
返回,否则进行报错处理!
1·验证cookie是否存在: if (re.get_password() == \"666\") { // 存在直接让它访问(无需验证密码) res.set_route(\"./wwwroot/video.html\"); goto again; } if (pw == \"666\") { // 密码正确,cookie不存在,第一次添加cookie: res.set_cookie_username(username); res.set_cookie_password(pw); res.set_route(\"./wwwroot/video.html\"); } else { res.set_route(\"./wwwroot/404.html\"); }
整体逻辑就这样,下面我们测试下:
验证Cookie
可以成功交互
- 首先第一次请求登录输入正确密码:
- 服务器识别到密码正确给浏览器发对应的
Cookie:
- 查看浏览器收到的对应的
Cookie:
- 下面我们尝试错误密码输入:
- 也是可以成功登录:
- 这里无密码也是可以成功登录的:
- 下面我们已经保存了正确的
Cookie
,上面是get
请求,下面我们验证下post
请求:
也是成立的!
验证Cookie
过期
- 因为我们设置的
5min
后过期,现在已经过期了:
- 找不到,被返回了
404
界面:
Cookie
被清空:
验证 path
路径
- 我们给对应的构建
Cookie
的时候修改路径:
- 接着还是老样子访问发现进不去了:
验证Cookie结束!
- 对于代码 :我们从请求中提取
Cookie
的更改在deserialize
函数里面;然后对Cookie是如何构建的,这两部分都在http.hpp
文件
里;其次就是处理时候是利用回调函数完成的在main.cc
文件
里;其他部分未做修改
,后续源码处自提!
二· Session
介绍
上面讲到了,单纯的Cookie是不安全的
,下面就需要Cookie与Session达成的会话一起来维护
,而session只不过是一种形式,还是以Cookie为载体的,因此用法还走的Cookie那套,下面我们就介绍下Session
!
什么是 Session
?
-
HTTP Session
是服务器用来跟踪用户与服务器交互
期间用户状态的机制。 -
由于
HTTP协议是无状态的
(每个请求都是独立的),因此服务器需要通过Session
来记住用户的信息。
Session
的原理
-
当用户首次访问网站时, 服务器会为用户创建一个唯一的
Session ID
, 并通过Cookie
将其发送到客户端
。 -
客户端在之后的请求中会
携带这个 Session ID
, 服务器通过Session ID
来识别用户, 从而获取用户的会话信息
。 -
对服务器而言,服务器通常会将
Session 信息
存储在内存、 数据库或缓存中
。
总之Session也是走的Cookie那一套,然后比Cookie更加加密一下
,对应的Cookie信息
不会直接暴露,但是可以客户端可以拿着它访问一定的曾经记录的资源
,并不会像之前那样因为账号密码等直接暴露导致的重大问题
发生!
Session
特性及用途
特性
Session 可以设置超时时间
, 当超过这个时间后, Session 会自动失效(类似Cookie的expires设置
,或者服务端自己记录超时时间
),服务器也可以主动使 Session 失效, 例如当用户退出时(服务端识别后去更改对应的Session信息
,当被标记后,再次请求,服务端就去检查Session发现状态不对就让它过期
)!
用途
用户认证和会话管理(重点)
- 存储用户的
临时数据
(如购物车内容) - 实现
分布式系统的会话共享
(通过将会话数据存储在共享数据库或缓存中)
Cookie+Session机制举例
下面我们就拿购物车内容作为session
工作的例子:
-
比如用户访问一个服务器,然后登录上,此时服务器机会创建对应
唯一的session-id来标识对应资源
,然后作为Cookie返回给浏览器;接下来用户在进行购物车添加的时候,对应的维护在服务端的session-id对应资源也在变化
(由服务器维护) -
如果此时用户退出来,然后再登陆,就会给对应服务器发送那个
session-id
,然后服务器拿到后识别到已经登录
,确定好身份
,然后用户去访问不同页面,此时服务器就先去看session-id
资源里面保存的情况然后呈现出来。 -
如果黑客拿到
对应session-id去访问
,此时服务器也会把它当成真正用户处理
,和上面过程一样,访问畅通无阻
,只不多session都会设置过期
,这就某种程度限制了黑客行为,但是也是不安全的
。
Session
安全性
-
与
Cookie
相似, 由于Session ID
是在客户端和服务器
之间传递的, 因此也存在被窃取的风险,但是一般虽然 Cookie 被盗取了, 但是用户只泄漏了一个Session ID(以及和它相关的记录)
, 私密信息暂时没有全部被泄露的风险(比如密码等,而且session一般会过期,如果是单Cookie的话就是永久泄露,但是session就是暂时了)。 -
Session ID
便于服务端进行客户端有效性的管理
, 比如异地登录。
通俗易懂解释:
也就是如果用的是
session
,被泄露后,比如:黑客拿到对应的session,它就能够直接访问到用户的资源,也就是被黑客盗取session后
,服务端就把黑客当成原用户对待
了(只不过黑客拿不到密码
,而且session只是暂时有效),这样避免了上面的问题,但是还是有漏洞
,因此还是需要解决方案来防范的
!
安全解决方法:
- 可以通过
HTTPS
和设置合适的Cookie
属性(如HttpOnly
和Secure
) 来增强安全性。
更详细版:
-
使用HTTPS防止中间人攻击来窃听
session ID
。 -
设置
HttpOnly
和Secure
标志来保护cookie
,防止通过JavaScript访问和确保只通过HTTPS
传输。 -
实施session过期机制,减少
session ID
被盗用的风险。 -
采用更复杂的
token机制
(如OAuth2.0
)代替简单的session ID。 -
对敏感操作实施额外验证步骤,如二次确认、短信验证码等。
基于模拟实现HTTP
服务器测试 Session
下面我们验证一下关于Session的正确使用以及过期是如何处理的:
先说下流程:
首先第一次发送正确密码
666
.然后被服务端识别到是第一次
,然后构建对应的唯一的session并且和资源绑定
,然后作为Cookie
发给浏览器,浏览器拿到后进行保存,当第二次
,只要访问同一个服务端就会发送这个session
,此时就随意登录
了。
代码实现大致可以分成以下几步:
- 先确定好对应
Session-id
和对应资源
的映射
关系,这里我们把对应属性,状态,资源
封装成一个类,利用哈希完成映射
即可,其次就是因为我们用的处理函数是回调,跑到main里面执行,因此为了避免回调传参
,我们把管理资源的类也就是这个哈希表
初始化在全局
。
#pragma once#include #include #include #include #include #include using namespace std;class session{public: session(const string& username, const string& status) : _username(username), _status(status) { _create_time = time(nullptr); _terminatal_time = _create_time + 300; // 默认300秒后过期 } ~session() { } string _username; string _status; uint64_t _create_time; uint64_t _terminatal_time; // 用户其他状态可以后续添加};using session_ptr = shared_ptr<session>;class session_manager{public: session_manager() { srand(time(nullptr) ^ getpid()); } string add_session(session_ptr s) { // 保证sessionid为正数 uint32_t randomid = rand() + time(nullptr); // 随机数+时间戳,实际有形成sessionid的库,比如boost uuid库,或者其他第三方库等 string sessionid = std::to_string(randomid); _sessions.insert(std::make_pair(sessionid, s)); return sessionid; } session_ptr get_session(string sessionid) { if (_sessions.find(sessionid) == _sessions.end()){ return nullptr; } return _sessions[sessionid]; } ~session_manager() {}private: unordered_map<string, session_ptr> _sessions;};
- 解析请求的时候利用和上面
Cookie一样的机制
,其次就是建立Cookie变一下
,这里虽然我们不写截止日期,可以在服务端内部session管理的资源内设置
。
void set_cookie_session(string session_id) { string cookie; cookie = \"Set-Cookie: sessionid=\" + session_id + \";\"; _cookies.emplace_back(cookie); }
- 下面就是如何响应这个session,第一次先检查有没有
session
被检测出来,如果有那么就找到它的资源看是否为空
(有可能浏览器保存了,但是服务端重启了,导致服务端数据没了)其次就是检查下对应的session资源里有没有超时标记
,都没有的话就直接把资源给它即让它登录
;否则再看密码是否666
,是的话就建立session
作为Cookie
写回去,反之就报错!
// 2·验证session是否存在(也就是cookie——session共同使用): if (!re.get_password().empty()) { // 非第一次套用session: session_ptr sn = ursm->get_session(re.get_password()); // cookie没有过期 if (sn && sn->_terminatal_time > time(nullptr)) { use_log(loglevel::DEBUG) << sn->_username << \" --- \" << sn->_status; res.set_route(\"./wwwroot/video.html\"); goto again; } else { // 可能服务器程序关闭了,此时浏览器还保存了对应的之前的cookie-sessionid,这样下次如果服务器访问就会找不到: use_log(loglevel::DEBUG) << \"用户的cookie已经过期, 需要清理!\"; res.set_route(\"./wwwroot/404.html\"); } } else { // 第一次建立session: if (pw == \"666\") { string user = \"user-\" + std::to_string(number++); session_ptr sn = make_shared<session>(user, \"login\"); string sessionid = ursm->add_session(sn); res.set_cookie_session(sessionid); use_log(loglevel::DEBUG) << sn->_username << \" 的cookie-session成功被添加,id是:\" << sessionid; res.set_route(\"./wwwroot/video.html\"); } else { res.set_route(\"./wwwroot/404.html\"); } }again: 1;
验证Session环节:
- 老样子还是以正确密码登录:
- 然后我们会发现服务器给浏览器写回一个Cookie:
- 这个Cookie就绑定了一些状态,资源等,下面我们随机密码访问:
- 也是成功返回理想界面:
- 因为这里我们Session设置额是有截止日期,五分钟后过期:
- 因此如果过期后我们在进行访问:
- 就会放回404页面:
- 服务器这边日志也显示过期了:
验证Session结束!
- 对于代码:
Session-id
对于资源等放在session.hpp
文件中,然后就是在tcpserver.hpp
处理任务改成单进程
就可以验证对应session功能,剩下的改动就是对应的main.cc
中对session的处理逻辑,以及http.hpp
中构建session-cookie逻辑
了!
关于测试Session
时出现的bug
总结
-
BUG-1:
-
就是因为我们自己
服务器就是个程序
,测试的时候肯定会关闭重启
之类的,因此之前储存的session-id对应资源也会丢失
,如果之前被浏览器保存了对应的session
但是服务器自己重启了,然后浏览器即便发回来正确session-id
也无法被服务器识别
(这里我们就认为是过期了,需要清理),因此服务器处理session-id
的时候判断一下
即可! -
BUG-2:
-
也是一直
困扰了博主半天的bug
,当时由于没看底层tcp是如何实现的
,导致了一直就是一开始成功发送session,而且浏览器也发给服务器了,但是服务器却查找资源没找到
;找了几个小时,把那些头文件都看了个遍,最后仔细看tcp实现
,发现是多进程
实现的,而且每次都是派出孙子进程去执行执行的,那么这样就写实拷贝,因此每次看到的session
由于哈希映射的资源都会被清空
(即使服务器不重启)。
解决方案1:
- 只为了
追求出测试效果
,可以考虑单进程,但是这样就会出现服务器压力过大,反应慢,甚至加载不出来等
(比如加载一个动图需要服务器那边的进程一直执行,而当用户在浏览器继续点击的时候又会发送请求,此时服务器就无法应答,一直卡着)当然实际这种方法是绝对不可行的,这里我们使用的单进程这种!
解决方案2:
- 可以考虑
多线程
或者线程池
去实现http
这块请求处理与答复
!
三·基于Cookie
与Session
的总结
-
这里我们只需要记住单纯
Cookie机制
不结合Session
来完成对http
无状态的记录是十分不安全的,因此需要session
与cookie
共同完成会话的记录,但是这样也不是绝对安全
的,因此在此基础上又会加上一些安全措施来保证用户安全上网
! -
因此,记住,
Cookie
通过记录某些信息来使得服务端由状态性而Session
是在Cookie
基础上一种相对保密的措施,以id
映射对应用户曾经访问资源,状态的一种手段
,但都是不安全的
,还需要另加保护防范!
四· 源码汇总
点击这里跳转my-gitee
查看测试源代码
五· 本篇小结
-
在本篇中深入学习了
Session原理
,用法,不足等等,Cookie和此外还进行了基于自己实现的HTTP服务器
模拟测试这些功能,在书写代码的过程中同样遇到了好多bug
,也浪费了很多时间
,上面也总结了,因此之后在学习的时候一定要把整个代码运行过程的轮廓(包括上层和底层都考虑清楚
),拒绝类似bug
再次发生,向下一次的网络学习进击! -
太年轻的人,他总是不满足!