【Linux篇章】互联网身份密码:解密 Session 与 Cookie 的隐藏玩法和致命漏洞!
一· 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=/;
AI生成项目
cpp
运行
1
看一下例子:
服务端先Set-Cookie:
客户端再发送回来:
下面我们就以这个例子,说一下下面Cookie的填充部分,我们就以Cookie对应的名称,expires,还有path讲解:
对于服务端,需要建立Cookie也就是Set-Cookie,那么它后面跟的第一个名称就是将来浏览器要发送给服务器的(假设我们要进行登录验证,那么第一个值就可以设置成对应的关键词比如登录,密码等)。
由于下面我们测试的是文件级别的Cookie,因此给它设置上过期时间(expires),这个时间格式就是我们上面例子那样,这里我们采取的是UTC也就是协调世界时(UTC 是现在用的时间标准, 多数全球性的网络和软件系统将其作为标准时间),当然也可以是GMT(格林尼治标准时间),感兴趣的可以去了解下,这里不是重点,这里如果设置了expires那么就文件级别等到到时间就过期自动清除,否则就是内存级别,浏览器关闭自动清除!
对于path,其实就是对应的路径,也就是指定的path后,只有我们访问对应的浏览器下Cookie保存的路径时,浏览器才会发送这个Cookie,比如保存的是/ 那么始终都会发送,但是如果是/a/b,那么只有访问这个路径才会发送,后面我们验证下!
这里我们服务端只需要按照指定格式给浏览器发生即可,然后浏览器就会按照一定规则储存起来,需要的时候在按照一定格式发回来,这样完成交互(这里他俩的解析方式我们不用管)!
如果有需要了解其他的拓展部分,可参考这张表:
属性 值 描述
username peter 这是 Cookie 的名称和值,标识用户名为 “peter”。
expires Thu, 18 Dec 2024 12:00:00 UTC 指定 Cookie 的过期时间。在这个例子中,Cookie 将在 2024 年 12 月 18 日 12:00:00 UTC 后过期。
path / 定义 Cookie 的作用范围。这里设置为根路径 /,意味着 Cookie 对.example.com 域名下的所有路径都可用。
domain .example.com 指定哪些域名可以接收这个 Cookie。点前缀(.)表示包括所有子域名。
secure - 指示 Cookie 只能通过 HTTPS 协议发送,不能通过 HTTP 协议发送,增加安全性。
HttpOnly - 阻止客户端脚本(如 JavaScript)访问此 Cookie,有助于防止跨站脚本攻击(XSS)。
注意事项:
每个 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 <iostream>
#include <string>
#include <memory>
#include <ctime>
#include <unistd.h>
#include <unordered_map>
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再次发生,向下一次的网络学习进击!
————————————————
版权声明:本文为CSDN博主「羑悻的小杀马特.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2401_82648291/article/details/147703324