引言

想象一下,你无需密码就能登录他人的社交账号、窃取他们的购物车,甚至进行支付操作。这听起来像好莱坞电影,但实际上,通过一种叫做 Cookie注入 的攻击,这完全可以实现

是网站用来识别用户“身份”的凭证,是维持会话(Session)的核心

本文将带你深入了解Cookie注入是什么、它是如何发生的、黑客如何利用它,以及最重要的——我们如何防御它



什么是HTTP Cookie

用一个比喻带你深入了解一下

想象一下你去一家奶茶店:

  1. 第一次光临:你点了一杯奶茶。店员发现你没有会员卡,于是给你办了一张新卡,并在卡上写下一串唯一的号码(例如 #114514)。同时,他们在店里的账本上记下:“卡号 #114514 的顾客喜欢芝士葡萄,积分 10”。然后他们把这张卡给你
  2. 保存卡片:你离开时,把这张会员卡(Cookie)放进了自己的钱包(浏览器)里
  3. 再次光临:一周后,你再次来到这家店。一进门,你就出示了这张会员卡(浏览器在请求中自动发送 Cookie)。店员看到卡号 #114514,立刻去查账本(服务器上的数据库)。然后马上说:“王先生您好!还是老规矩,一杯芝士葡萄吗?您目前有 10 积分。”
  4. 无卡光临:如果你下次没带这张卡,店员就会把你当作新顾客,流程又重新开始

技术定义

HTTP Cookie(通常直接称为 Cookie)是一小段由服务器发送到用户浏览器保存在本地的数据

  • 服务器生成:当你访问一个网站时,网站的服务器可以决定生成一个或多个 Cookie
  • 浏览器存储:你的浏览器会接收这些 Cookie 并将它们存储在你的电脑上
  • 自动发送:此后,每当浏览器再向同一服务器发起请求时,它会自动地把这些 Cookie 附加在 HTTP 请求头中一起发送回去

这样,服务器就能“记住”之前在你浏览器上发生过的信息(如登录状态、偏好设置等),从而实现有状态的会话,而 HTTP 协议本身是无状态的(服务器默认不记得之前的请求)


Cookie 的用途

Cookie 最初是为了解决“记住用户状态”的问题而发明的,现在主要有以下用途:

  1. 会话管理(最核心的用途)

    • 用户登录状态:保持用户的登录信息。服务器在你登录后发送一个包含“会话ID”的 Cookie,浏览器下次带着这个 ID 来,服务器就知道你是谁了,例如:每次进入CSDN就会有自己账号的信息
    • 购物车内容:在电商网站,即使你跳转到其他页面或刷新,购物车里的商品也不会消失,就是因为商品信息被存在 Cookie 里
  2. 个性化

    • 用户偏好:记住你的语言设置、主题(深色/浅色模式)、字体大小等
    • 内容推荐:根据你过去的浏览习惯,展示你可能感兴趣的内容
  3. 跟踪与分析

    • 广告追踪:广告商利用第三方 Cookie 来追踪你在不同网站上的行为,从而向你投放更相关(或者说更精准)的广告。这也是 Cookie 常常与隐私问题关联的原因
    • 网站分析:帮助网站所有者分析用户行为,了解流量来源、用户停留时间等,例如 Google Analytics 就使用 Cookie

Cookie 的关键属性

一个 Cookie 不只是一段数据,它还包含一些控制其行为的属性,由服务器在设置时指定:

  • 名称(Name)和值(Value):实际的数据内容,都是字符串格式(例如 username=john_doe

  • 过期时间(Expires/Max-Age)

    • 会话期 Cookie (Session Cookie):不设置 Expires 或 Max-Age。这种 Cookie 在浏览器关闭后就会被删除。就像咖啡店的会员卡只在你本次消费期间有效
    • 持久性 Cookie (Persistent Cookie):设置了具体的过期时间。即使关闭浏览器,它也会保存在电脑上,直到过期。就像一张一年内有效的实体会员卡
  • 域(Domain)和路径(Path)

    • 定义了 Cookie 应该被发送到哪个域名和路径下。例如,设置为 .hsqg.com 的 Cookie 会被发送给 www.hsqg.com、api.hsqg.com 等所有子域名
  • 安全标志(Secure)

    • 设置了 Secure 的 Cookie只能通过 HTTPS 加密连接传输,防止在传输过程中被窃听
  • HttpOnly 标志

    • 设置了 HttpOnly 的 Cookie 无法 通过 JavaScript 的 Document.cookie API 访问。这是重要的安全措施,可以有效防止跨站脚本(XSS)攻击窃取 Cookie 信息
  • SameSite 标志(现代浏览器重要安全属性):

    • Strict:Cookie 仅在与当前页面域名相同的第一方请求中发送,完全禁止第三方上下文发送
    • Lax:(默认值)在跨站请求中,只允许在安全且“顶级导航”(如点击链接)的情况下发送 Cookie。这防止了 CSRF 攻击的大部分情况,同时不影响用户体验
    • None:Cookie 在所有上下文中发送,包括跨站请求(如嵌入的 iframe、图片),必须与 Secure 属性一同设置(即必须使用 HTTPS)

隐私与安全考量

  • 隐私问题:主要是第三方 Cookie 引起的。第三方服务器可以设置和读取它们自己的 Cookie,从而构建你的跨站浏览画像。现代浏览器正逐步淘汰第三方 Cookie
  • 安全风险
    • XSS(跨站脚本):窃取未设置 HttpOnly 的 Cookie
    • CSRF(跨站请求伪造):利用用户已存在的登录 Cookie 发起非预期请求,SameSite 属性是解决此问题的主要手段

小结

HTTP Cookie 是一个由服务器创建、由浏览器存储、并在后续请求中自动发回给服务器的数据片段。它的核心作用是让无状态的 HTTP 协议能够“记住”信息,从而实现登录状态、偏好设置等关键功能,但同时它也带来了隐私追踪和安全方面的挑战


知识拓展

会话(Session)与Cookie的关系

会话(Session)和 Cookie 的关系是协同工作、相辅相成

咱来用一个非常经典的比喻来理解:

  1. Session 是商场(服务器)里的「储物柜」

    • 这个储物柜有一个唯一的编号(例如 #1314
    • 柜子里可以存放你的各种物品(用户数据,如用户ID、用户名、购物车信息、登录状态等)
    • 所有这些储物柜都放在商场后场的储物区(服务器的内存、数据库或缓存中)
  2. Cookie 是商场前台发给你的「钥匙卡」

    • 当你第一次来到商场(访问网站),商场前台(服务器)看你没有钥匙卡,就为你开了一个新的储物柜 #1314,并把里面暂时放上你的信息
    • 然后,前台给你一张只写了储物柜编号 #1314 的钥匙卡。这张卡本身不存放你的任何物品,只告诉商场你的柜子是哪一个
    • 你离开时,把这张钥匙卡(Cookie)放进口袋(浏览器)
  3. 你再次光临商场

    • 你一进门(发起新的请求),自动出示了你的钥匙卡(浏览器自动在请求头中携带Cookie)
    • 商场前台(服务器)看到钥匙卡上的编号 #1314,就去后场的储物区找到对应的 #1314 储物柜,拿出里面的物品,就知道你是谁、你的喜好和之前的行为了

技术性理解

特性CookieSession
存储位置客户端(用户的浏览器中)服务端(服务器的内存、数据库或专用缓存如Redis中)
存储内容一个唯一的标识符,即 Session ID所有的用户数据(如 {user_id: 123, username: ‘alice’, is_logged_in: true})
安全性相对较低,数据存储在客户端,可能被查看或篡改,不应存储敏感信息。相对较高,关键数据存储在服务端,客户端无法直接访问或修改
生命周期可以设置很长的过期时间(如“记住我”功能),即使浏览器关闭也可能存在通常有失效时间(如用户 inactivity 30分钟后,Session自动过期)服务器可以主动销毁Session
性能影响不占用服务器资源。但每次HTTP请求都会携带,增加带宽开销占用服务器资源,用户量巨大时,需要精心管理存储方案(如使用Redis)以避免内存耗尽

它们如何协同工作

在这里插入图片描述


一句话概括:
Session 是一种在服务端存储用户状态的机制,而 Cookie 是在客户端用于维持这种状态(通过传递Session ID)的主流实现方式



Cookie注入的前置条件

  1. 应用程序存在漏洞:信任并使用了Cookie中的数据

这是最核心的条件。应用程序的业务逻辑必须直接将Cookie中的某个或多个参数的值,未经充分安全处理拼接到SQL查询语句中

  • 常见场景
    • 用于身份认证的 user_idusername
    • 用于个性化内容的 preferencestheme
    • 用于跟踪的 session_id
  1. 输入处理缺失:未对Cookie数据进行过滤或转义

应用程序必须缺乏有效的输入验证、过滤和转义机制

  • 缺失的防护

    • 未使用参数化查询(Prepared Statements):这是最根本的防御措施。如果用了参数化查询,即使Cookie被修改,其内容也只会被当作数据而非代码执行
    • 未进行类型转换:例如,如果 user_id 本应是整数,程序却没有将其强制转换为整型 (int)$_COOKIE[‘user_id’]
    • 未转义特殊字符:没有对单引号 '、注释符 、/**/ 等SQL元字符进行转义或过滤
  1. 攻击面可用:Cookie参数可被预测和操控

攻击者必须能够修改目标Cookie的值,并将其发送到存在漏洞的服务器。

  • 修改方式

    • 浏览器开发者工具:现代浏览器都提供可随时修改Cookie的功能
    • 浏览器插件/扩展:可以更方便地管理和编辑Cookie
    • 拦截代理工具:如Burp Suite 在HTTP请求发送到服务器前拦截并修改其中的Cookie值,这是专业渗透测试最常用的方法
    • 利用XSS漏洞:如果网站同时存在XSS(跨站脚本)漏洞,攻击者可以编写恶意脚本,悄悄地修改用户的Cookie,从而发起更隐蔽的攻击
  1. 信息反馈:有可观测的输出或错误信息

攻击通常是一个“试错”过程,攻击者需要根据服务器的响应来调整攻击载荷(Payload)。反馈方式有两种:

  • 显式反馈(经典SQL注入):服务器将SQL查询的错误信息直接返回给页面。这使攻击者能快速了解SQL语句的结构,极大地便利了攻击
  • 隐式反馈(盲注 - Blind SQL Injection):页面不会返回数据库错误信息,但会根据注入的SQL语句的逻辑真假表现出不同的行为(如返回正常页面、404错误、不同的响应时间等)。攻击者通过观察这些细微差异来推断注入是否成功。虽然难度更大、更耗时,但仍然是可行的

核心思想:只要破坏了上述条件中的任意一个,尤其是条件1和2,Cookie注入攻击就无法成功。而使用参数化查询是同时破坏条件1和2的最有效、最根本的解决方案(防御亦是如此)

在这里插入图片描述



Cookie注入详解

核心概念
Cookie注入是SQL注入攻击的一种特殊形式,它与传统SQL注入的核心原理完全相同——都是利用应用程序未对用户输入进行充分过滤和转义的漏洞,将恶意构造的SQL代码注入到后台数据库中执行

它们唯一的区别在于 注入点(攻击入口) 不同:

  • 传统SQL注入:恶意代码通过用户可见的输入点传入,如:

    • GET 请求的URL参数(example.com?id=1)
    • POST 请求的表单字段(如登录框、搜索框)
  • Cookie注入:恶意代码通过HTTP请求中的Cookie字段传入


Cookie注入攻击的原理

Cookie注入的本质是:攻击者通过篡改浏览器中的Cookie数据,将恶意SQL代码传递到服务器。由于服务器应用程序盲目信任Cookie中的数据,并未经任何过滤就将其拼接到SQL语句中执行,从而导致数据库被恶意查询,还是跟其他注入原理一样

它是一种SQL注入攻击,只是攻击的输入向量(Input Vector) 从常见的表单(Form)或URL参数(GET)变成了HTTP请求头中的 Cookie

在这里插入图片描述

就以本次靶场less-20的源代码举例,来看看到底为什么存在注入

if(isset($_POST['uname']) &&
isset($_POST['passwd']))
{
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$result1 = mysql_query($sql);
$row1 = mysql_fetch_array($result1);
$cookee = $row1['username'];
if($row1)
{
echo '<font color= "#FFFF00" font size = 3 >';
  setcookie('uname', $cookee, time()+3600);
  header ('Location: index.php');
  echo "I LOVE YOU COOKIES";
echo "</font>";
echo '<font color= "#0000ff" font size = 3 >';
  //echo 'Your Cookie is: ' .$cookee;
echo "</font>";
echo "<br>";
  print_r(mysql_error());
  echo "<br><br>";
    echo '<img src="../images/flag.jpg" />';
    echo "<br>";
      }
      else
      {
      echo '<font color= "#0000ff" font size="3">';
        //echo "Try again looser";
        print_r(mysql_error());
      echo "</br>";
    echo "</br>";
    echo '<img src="../images/slap.jpg" />';
  echo "</font>";
  }
  }
echo "</font>";
echo '</font>';
echo '</div>';
}
else
{
if(!isset($_POST['submit']))
{
$cookee = $_COOKIE['uname'];
$format = 'D d M Y - H:i:s';
$timestamp = time() + 3600;
echo "<center>";
echo '<br><br><br>';
echo '<img src="../images/Less-20.jpg" />';
echo "<br><br><b>";
  echo '<br><font color= "red" font size="4">';
    echo "YOUR USER AGENT IS : ".$_SERVER['HTTP_USER_AGENT'];
  echo "</font><br>";
    echo '<font color= "cyan" font size="4">';
      echo "YOUR IP ADDRESS IS : ".$_SERVER['REMOTE_ADDR'];
    echo "</font><br>";
      echo '<font color= "#FFFF00" font size = 4 >';
        echo "DELETE YOUR COOKIE OR WAIT FOR IT TO EXPIRE <br>";
          echo '<font color= "orange" font size = 5 >';
            echo "YOUR COOKIE : uname = $cookee and expires: " . date($format, $timestamp);
          echo "<br></font>";
            $sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
            $result=mysql_query($sql);
            if (!$result)
            {
            die('Issue with your mysql: ' . mysql_error());
            }
            $row = mysql_fetch_array($result);
            if($row)
            {
            echo '<font color= "pink" font size="5">';
              echo 'Your Login name:'. $row['username'];
              echo "<br>";
                echo '<font color= "grey" font size="5">';
                  echo 'Your Password:' .$row['password'];
                echo "</font></b>";
                echo "<br>";
                  echo 'Your ID:' .$row['id'];
                  }
                  else
                  {
                  echo "<center>";
                    echo '<br><br><br>';
                      echo '<img src="../images/slap1.jpg" />';
                      echo "<br><br><b>";
                        //echo '<img src="../images/Less-20.jpg" />';
                        }
                        echo '<center>';
                          echo '<form action="" method="post">';
                            echo '<input type="submit" name="submit" value="Delete Your Cookie!" />';
                          echo '</form>';
                        echo '</center>';
                        }
                        else
                        {
                        echo '<center>';
                          echo "<br>";
                            echo "<br>";
                              echo "<br>";
                                echo "<br>";
                                  echo "<br>";
                                    echo "<br>";
                                      echo '<font color= "#FFFF00" font size = 6 >';
                                        echo " Your Cookie is deleted";
                                        setcookie('uname', $row1['username'], time()-3600);
                                        header ('Location: index.php');
                                      echo '</font></center></br>';
                                      }
                                      echo "<br>";
                                        echo "<br>";
                                          //header ('Location: main.php');
                                          echo "<br>";
                                            echo "<br>";
                                            //echo '<img src="../images/slap.jpg" /></center>';
                                            //logging the connection parameters to a file for analysis. 
                                            $fp=fopen('result.txt','a');
                                            fwrite($fp,'Cookie:'.$cookee."\n");
                                            fclose($fp);
                                            }

最重要的也就这几行代码:

将用户输入的用户名和密码与数据库进行对比查询,查看是否一致

$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";

将拼接好的SQL语句发送到MySQL数据库执行,并从查询结果中取出一行数据,将行中取出 username 字段的值,并将其存入变量 $cookee 中,为设置Cookie做准备
在这里插入图片描述


其中还有个非常重要的函数 setcookie()
这是本篇的重点之一,一个非常重要的函数,用于向客户端的浏览器发送一个 Cookie

setcookie(
string $name,
string $value = "",
int $expires = 0,
string $path = "",
string $domain = "",
bool $secure = false,
bool $httponly = false,
array $options = []
): bool

参数详解

其中有很多参数都用不到,本示例也就用到了前三个参数

参数说明
$nameCookie 的名称,这是唯一必须的参数
$valueCookie 的值
$expiresCookie 的过期时间
$pathCookie 有效的服务器路径
$domainCookie 有效的域名/子域名
$secure安全性开关
$httponlyHTTP Only 开关
$options(PHP 7.3+)一个关联数组

重要特性

  • 基于 HTTP 头
    • setcookie() 函数实际上是告诉 PHP 在 HTTP 响应中发送一个 Set-Cookie
    • 浏览器收到这个头后,会根据指令将 Cookie 存储起来
    • 之后,浏览器向匹配路径和域名的服务器发送请求时,会自动在 HTTP 请求头中包含一个 Cookie 头,将信息传回服务器

注入点就出现在这:
这段代码的目的是:使用从用户Cookie中获取的用户名来查询数据库,验证该用户是否存在,即执行用户输入的内容(恶意代码)
在这里插入图片描述



实战演练

环境设置: 本示例为 sqli-labs 20
工具准备: Burp Suite
在这里插入图片描述
less-20和前面两个一样有安全绕过所以必须登录正确的用户名和密码,登陆成功后他才能把 cookie 插入到数据库中

输入正确的用户名密码回车后,进行抓包
username:admin
password:admin
并直接右键发送到重发器
在这里插入图片描述

$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";

因为这里是单引号闭合并且是用union进行查询的,所以直接用单引号闭合的union查询即可

先使用 order by 判断列数
在这里插入图片描述

' order by 3 --+

构建注入语句,查询库名,不会的看这:sql整型注入
在这里插入图片描述

' union select 1,2,(select database()) --+

查询表名,后面的就不写了,跟sql整型注入一模一样
在这里插入图片描述

' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=database()) --+

防御之道:如何防御Cookie注入

在前面的Cookie注入的前置条件中就有提及,正所谓解铃还须系铃人,哪有漏洞补哪就行了

  1. 对Cookie进行安全编码(HttpOnly Flag)

    • 作用: 防止XSS窃取。设置了HttpOnly的Cookie无法通过JavaScript的document.cookie访问
  2. 强制使用HTTPS(Secure Flag)

    • 作用: 防止网络嗅探。设置了Secure的Cookie只会通过HTTPS加密连接传输
  3. 实施严格的输入验证和输出编码

    • 根源上解决XSS,从而杜绝最主要的Cookie窃取手段。对所有用户输入进行过滤和验证,对所有输出到页面的数据进行编码
  4. 服务器端不要信任客户端传来的Cookie

    • 核心原则: Cookie中的任何数据都应被视为不可信的输入
  5. 设置合适的SameSite属性

    • 作用: 可以有效防御CSRF攻击,而CSRF有时也会和Cookie利用相关联
  6. 定期更换SessionID

    • 作用: 即使Cookie被窃取,其有效期也很短,减小损失
    • 时机: 用户登录后、登出时、重要操作(如修改密码)后
posted on 2025-09-15 08:01  ycfenxi  阅读(25)  评论(0)    收藏  举报