读完这篇,你能做什么
能为一个查询和一个导入接口增加可测试的权限、输入与出站边界。
学习理由、准备要求与知识点
为什么学它
参数校验与登录成功并不等于接口安全;对象越权、注入和任意外连需要不同控制点。
先读这些内容
知识点清单
- 对象级授权
- 属性级授权
- 参数化 SQL
- 标识符白名单
- SSRF 出站限制
- 资源消耗限制
- 敏感响应裁剪
- 安全反例测试
运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。
01先弄懂它是什么
接口安全要按具体问题分别检查:用户能看哪条订单,是对象权限;能改哪些字段,是字段权限;数据不能变成 SQL 指令,要用参数化查询;服务器能访问哪些外部地址,要有出站限制。它们解决的问题不同,不能只加一个输入校验注解就认为都已处理。
把请求字段直接批量复制到数据库实体,可能让普通用户改到审核状态等不该开放的字段。应使用专门的请求 DTO,只映射允许输入的字段,并在业务层检查状态是否允许改变。即使格式完全合法,过大的分页、批量、解压文件、正则处理和外部请求也可能耗尽资源,因此还要规定数量、大小与时间上限。
如果接口让用户提供一个地址,再由服务器去访问,就要防止 SSRF,也就是借服务器代发不该发送的请求。服务器可能能访问用户原本进不去的内部服务,还可能带有自己的凭据。能只访问固定业务服务时,就先把目标限制在这些服务内。必须访问更广的地址时,要同时检查协议、域名、解析出的地址和跳转后的目标,并限制服务器实际能连到哪里。只检查一次地址文字,不能保证最后连接的目标符合要求。
文中这几个词,先认一认
- 属性级授权
- 判断某个用户允许读取或修改哪些字段,例如能改昵称但不能改审核状态。
- 出站允许列表
- 服务器只连接业务预先允许的目标,其他目标默认不允许。
- 检查使用差异
- 检查时看到的地址或资源,到了真正使用时已经变化,使原先检查不再覆盖实际目标。
02看看它怎么工作
本题只计算一个 IPv4 地址是否落在允许的网段里。CIDR 的写法是在地址后加前缀长度,例如 /24 表示前 24 个二进制位用来标识网络。程序把地址的四个十进制部分合成一个 32 位数,放进 long 的低 32 位;再用掩码只保留前 p 位,与配置的网络地址比较。本题要求配置里剩下的主机位全为零,不会悄悄把一个含糊地址扩成更大的允许范围。
例如示例文档网段192.0.2.0/24包含192.0.2.7,不包含192.0.3.7。这里只判断显式配置的集合成员,不把任何网段天然称为安全,也不连接这些地址。允许列表应来自服务资产范围,不能让请求者自行提供。
解析只接受规范四段十进制,不接受前导零、缩写、十六进制或IPv6。严格格式减少解析器之间的歧义,但并不解决DNS重绑定、代理转发或重定向。真实HTTP客户端需要把校验结果与实际连接目标绑定,并辅以网络层限制。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart TD
I["请求输入"] --> A["对象与属性授权"]
A --> D["参数绑定与结构白名单"]
I --> U["URL语法和目标政策"]
U --> N["解析连接与网络出口限制"]
D --> B["资源预算与安全错误"]
N --> B按顺序看,每一步做了什么
- 画出输入到数据库、资源对象与出站请求的路径。
- 用可信身份和允许字段控制对象及属性权限。
- 使用参数化数据值与固定结构白名单。
- 定义出站目标、解析和重定向政策以及资源上限。
- 在隔离测试中覆盖拒绝路径,并记录不泄密的错误。
03对照实现代码
OpenJDK 17 GA,jdk-17+35 · URI.create / getScheme / getHost
下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,不代表数据库或容器内核实现。完整程序见算法模块。
URI uri = URI.create(input);
if (!"https".equalsIgnoreCase(uri.getScheme())) throw new IllegalArgumentException("scheme"); // 限制业务协议。
if (uri.getHost() == null || uri.getUserInfo() != null) throw new IllegalArgumentException("authority"); // 拒绝含糊主机和内嵌凭据。
String host = uri.getHost().toLowerCase(Locale.ROOT); // 规范化后匹配服务端允许列表。
requireAllowedHost(host); // 之后仍需验证解析结果、实际连接与重定向。URI解析语法组成部分,不会自动判断目标是否可信。getHost为空时不能退回手工截取字符串猜主机,否则可能与HTTP客户端解析产生差异。
scheme、userinfo、host、port和重定向策略需要分别处理。片段只展示最小语法层,requireAllowedHost是应用策略函数,不是JDK内置防SSRF能力。
URI.create 只解析地址文字,不会查 DNS 或建立连接。域名通过允许列表之后,解析结果和实际连接仍可能变化;HTTP 客户端与网络出口也要执行一致限制,不能只检查输入字符串。
完整算法只处理规范IPv4字符串与CIDR位运算,不能替代成熟URL解析器或生产代理策略。它用于理解地址集合,不声称覆盖IPv6和所有出站攻击面。
代码先调用谁,再调用谁
- URI拆分语法
- 协议与主机策略检查
- 受控解析并检查地址集合
- 限制实际连接和重定向
- 限制响应大小与总时间
04动手写一个例子
规范IPv4的CIDR允许列表判定
输入规范IPv4地址和服务端CIDR列表,返回是否属于任一网段。拒绝歧义格式及非网络对齐CIDR。纯计算不进行DNS或HTTP。Java17自测。
- 输入 1
192.0.2.7与192.0.2.0/24- 输出 1
true
- 输入 2
192.0.3.7与192.0.2.0/24- 输出 2
false
每段必须是一到三位ASCII数字,除单独零外不能以零开头,累加值不超过255。左移八位拼接,使四段进入无符号三十二位空间。
前缀零特殊处理为零掩码,避免Java移位距离按位截断造成三十二位移位错误。前缀三十二掩码保留全部位,表示单一地址。
先解析并校验全部规则,再判定成员关系,防止前面一条命中后把后面无效配置静默忽略。空列表自然返回拒绝。
为什么这样写是对的地址和网络均位于long低三十二位,掩码仅有连续高p位为一;所有已接受CIDR的主机位为零。
L为地址与规则总字符数,r为规则数;解析每个字符常数次,保存规范化网络与掩码,查询扫描r个规则。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
import java.util.*;
public class Main {
record Network(long address,long mask){}
static long ip(String s){
String[] parts=s.split("[.]",-1);if(parts.length!=4)throw new IllegalArgumentException("ipv4");long value=0;
for(String part:parts){if(part.isEmpty()||part.length()>3||part.length()>1&&part.charAt(0)=='0')throw new IllegalArgumentException("part");int n=0;
for(char c:part.toCharArray()){if(c<'0'||c>'9')throw new IllegalArgumentException("digit");n=n*10+c-'0';}
if(n>255)throw new IllegalArgumentException("range");value=(value<<8)|n; // 用long保存无符号IPv4值。
}return value;
}
static boolean allowed(String address,List<String> rules){
long candidate=ip(address);List<Network> parsed=new ArrayList<>();
for(String rule:rules){String[] p=rule.split("/",-1);if(p.length!=2||!p[1].matches("0|[1-9][0-9]?"))throw new IllegalArgumentException("cidr");int prefix=Integer.parseInt(p[1]);if(prefix>32)throw new IllegalArgumentException("prefix");
long mask=prefix==0?0:(0xffffffffL<<(32-prefix))&0xffffffffL;long network=ip(p[0]); // 零前缀单独处理,避免移位歧义。
if((network&mask)!=network)throw new IllegalArgumentException("host bits");parsed.add(new Network(network,mask));
}
for(Network n:parsed)if((candidate&n.mask())==n.address())return true;return false; // 只执行集合判断,不发网络请求。
}
static void eq(boolean e,boolean a){if(e!=a)throw new AssertionError(a);}
public static void main(String[] args){
eq(true,allowed("192.0.2.7",List.of("192.0.2.0/24")));
eq(false,allowed("192.0.3.7",List.of("192.0.2.0/24")));
eq(true,allowed("255.255.255.255",List.of("255.255.255.255/32")));
eq(true,allowed("203.0.113.1",List.of("0.0.0.0/0")));
eq(false,allowed("192.0.2.1",List.of()));
try{allowed("192.000.2.1",List.of());throw new AssertionError();}catch(IllegalArgumentException expected){}
try{allowed("192.0.2.1",List.of("192.0.2.1/24"));throw new AssertionError();}catch(IllegalArgumentException expected){}
System.out.println("PASS 7 tests");
}
}这些特殊情况也要试一试
- 零前缀意味着全部IPv4,是否允许由业务策略决定。
- 仅支持规范IPv4,不静默转换其他表示。
- CIDR成员判定不是完整SSRF防护。
进阶选读 · 后续再读:多协议出站控制
场景大量域名、IPv6、代理和动态地址共同存在。
做法通过受控出站代理统一策略,使用可靠解析结果与连接绑定,限制重定向,每一跳验证目标,并对网络实际出口施加规则。
复杂度前缀树可让固定地址宽度下查询按位数完成;完整网络路径仍包含DNS、代理和连接状态,不能简化为一个字符串算法。
好处与代价允许列表维护成本高但边界清楚;广泛抓取更灵活,却需要更复杂的隔离和资源控制。
05项目里怎么用
SQL参数绑定保护值,不保护动态拼接的表名、列名和排序方向。将这些结构映射到服务端固定枚举,同时用最小数据库权限限制事故范围。对象查询加入可信所有者条件,返回DTO仅包含允许字段,更新时拒绝未开放属性。
远程导入要限制连接等待、全部处理时间、响应大小、内容类型和解压规模,必要时放在独立受限环境。重定向可以禁止,或每跳重新检查,不能只验证最初地址。应用检查和网络出口限制需要一起维护,防止代理或不同解析方式绕过原规则。
安全回归包含匿名、跨用户、字段越权、超大输入和不可达依赖等边界。拒绝日志记录规则类型与安全关联ID,避免记录令牌、完整敏感URL参数或内部堆栈。错误响应保持一致,既便于客户端处理,也减少不必要的资源存在性泄漏。
容易踩的坑
- 有效认证被当作对象授权。
- 参数化一个值后继续拼接任意SQL结构。
- 初始URL合法就信任所有重定向。
- 只限制请求体却忽略解压和响应大小。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 窄DTO和字段白名单 | 减少属性越权与耦合 | 需要显式映射维护 | 业务写入接口 |
| 固定出站目标 | 边界清楚便于审计 | 不适合任意网页抓取 | 集成已知服务 |
| 隔离抓取服务 | 限制网络与资源影响 | 增加部署和通信成本 | 必须处理广泛外部内容 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01SQL注入和越权有何不同?
展开参考答案与推导
前者改变语句解释,后者使用合法语句访问不该访问的数据;参数绑定不能自动决定资源权限。
02URI解析为何不是网络安全判断?
展开参考答案与推导
解析只拆分语法,未验证DNS、实际连接、代理与重定向。
03为什么限制字段?
展开参考答案与推导
即使资源属于用户,也不代表用户能修改审核状态、角色等所有属性。
换个情况,还会处理吗
01排序参数进入SQL,如何修?
展开参考答案与推导
把请求值映射到固定列和方向枚举,值仍参数化,不允许任意SQL片段。
02域名允许但连接到非预期地址,检查什么?
展开参考答案与推导
检查DNS变化、实际连接目标、代理和重定向,使用网络层限制及解析连接一致性设计。
03小压缩文件导致内存暴涨,缺什么?
展开参考答案与推导
缺解压后大小、比例和处理时间预算,不能仅限制上传压缩字节数。
写代码试一试
0124位前缀如何匹配?
- 输入 / 条件
192.0.2.7与192.0.2.0/24- 输出 / 预期
匹配
提示 保留前三段。
展开参考答案与推导
掩码去掉最后八位,候选结果等于规范网络地址。
02前缀零为何单独处理?
- 输入 / 条件
0.0.0.0/0- 输出 / 预期
掩码0
提示 Java移位有距离规则。
展开参考答案与推导
不能对int简单左移三十二位得到预期结果,应显式零掩码并以long处理无符号值。
03首规则匹配能忽略后续错误吗?
- 输入 / 条件
合法匹配后还有非法CIDR- 输出 / 预期
拒绝配置
提示 先全部验证。
展开参考答案与推导
规则配置必须整体有效,不能让匹配顺序隐藏潜在危险或运维错误。
读完之后,独立完成
课后作业:自己做一遍
本篇共 12 道作业,先完成 9 道基础与综合任务,再按前置知识挑战进阶题。其中包含 2 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能为一个查询和一个导入接口增加可测试的权限、输入与出站边界。
本篇重点加练
出站限制与资源预算必须覆盖完整执行过程,单次 URL 校验和入口大小限制都可能遗漏后续阶段。
- SSRF 出站限制
- 资源消耗限制
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01代码推演
本地资料修改接口允许客户端提交 id、nickname、role、
tenantId。按字段分析谁可修改什么,并构造同一角色跨对象访问的测试请求。- 提交内容
- 字段权限与对象权限矩阵
- 怎样算完成
- 区分对象与属性授权
- 使用虚构用户和租户
练到的知识点对象级授权 · 属性级授权
- 作业 02原理问答
搜索接口既接收关键字又接收排序字段与方向。分别定义参数化 SQL 与标识符选择的契约,说明两类输入为何不能按相同方式处理。
- 提交内容
- 输入分类及查询接口草案
- 怎样算完成
- 值参数和结构参数分别审查
- 覆盖非法字段名
练到的知识点参数化 SQL · 标识符白名单
- 作业 03面试场景
订单响应复用了数据库实体,包含内部备注和凭据字段。审查序列化输出,并为成功与失败响应各设计负面测试。
- 提交内容
- 响应字段清单与测试矩阵
- 怎样算完成
- 检查实际响应而非仅看 DTO 名称
- 错误日志同样检查敏感信息
练到的知识点敏感响应裁剪 · 安全反例测试
- 作业 04代码推演
本地 URL 获取器先验证域名,再跟随一次重定向,实际连接地址另由解析结果决定。标明每个阶段需要记录的目标身份。
- 提交内容
- 出站解析及跳转图
- 怎样算完成
- 区分文本域名和最终连接对象
- 不访问任何非本地目标
练到的知识点SSRF 出站限制
- 作业 05原理问答
上传请求体很小,但解码后产生大量条目,每条又触发一次查询。建立传输、展开、计算与外部调用四种资源预算。
- 提交内容
- 多阶段预算表
- 怎样算完成
- 每个上限有计量单位
- 不能只限制单个文件大小
练到的知识点资源消耗限制
综合应用
- 作业 06设计评审
头像导入接口可拉取 URL,另可上传压缩文件。制定网络目的地、跳转次数、响应大小和解压预算的验收项。
- 提交内容
- 资源与出站限制表
- 怎样算完成
- 只针对本地可控桩设计测试
- 覆盖重定向和超限响应
练到的知识点SSRF 出站限制 · 资源消耗限制
- 作业 07编程实践
创建只绑定回环的练习 API 和 HTTP 桩,编写授权、非法排序字段、受限出站与大响应测试;所有请求限于自建环境。
- 提交内容
- 本地工程与安全回归记录
- 怎样算完成
- 无第三方目标或真实凭据
- 每项测试记录被验证的控制点
练到的知识点对象级授权 · 属性级授权 · 参数化 SQL · 标识符白名单 · SSRF 出站限制 · 资源消耗限制 · 敏感响应裁剪 · 安全反例测试
- 作业 08设计评审
批量网页摘要接口允许提交十个 URL,重定向和重试都可再次发请求。评审一次业务请求的总出站次数与响应字节预算。
- 提交内容
- 调用树与预算契约
- 怎样算完成
- 跨 URL 共享预算
- 包含错误页和无终止响应
练到的知识点SSRF 出站限制 · 资源消耗限制
- 作业 09编程实践
建立仅回环可访问的 HTTP 桩,提供受控重定向、缓慢小块响应和超限内容,验证本地获取器的目标判定与总资源上限。
- 提交内容
- 本地工程和限制记录
- 怎样算完成
- 不模拟访问真实内网或第三方
- 限制实验时长与生成字节数
练到的知识点SSRF 出站限制 · 资源消耗限制
进阶挑战 · 掌握前置后选做
- 作业 10设计评审
网关有速率限制,业务服务仍出现高内存和跨租户数据访问。提出独立调查假设,并设计能区分入口限制与业务控制的验收。
- 提交内容
- 威胁路径图与调查计划
- 怎样算完成
- 覆盖绕过网关的受控本地路径
- 不以单一防护推断整体安全
练到的知识点对象级授权 · 属性级授权 · SSRF 出站限制 · 资源消耗限制 · 敏感响应裁剪 · 安全反例测试
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 11思维拓展 · 反例构造
构造每个单次响应都在限制内,但一个业务请求仍消耗过多资源的场景,说明需要增加哪种聚合口径。
- 提交内容
- 反例请求树
- 怎样算完成
- 单次限制确实满足
- 明确放大发生在哪个阶段
练到的知识点SSRF 出站限制 · 资源消耗限制
- 作业 12思维拓展 · 跨域迁移
把 URL 获取能力交给 Agent 自动规划,调用数量预先未知。重新定义工具预算、目标许可与停止条件,研究如何核验执行记录。
- 提交内容
- Agent 工具契约与审计方案
- 怎样算完成
- 模型输出视为不可信建议
- 预算跨多次工具调用累计
练到的知识点SSRF 出站限制 · 资源消耗限制
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读实践指南英文
OWASP API Security Top 10 2023 (新标签页)↗
阅读对象级授权、资源消耗与 SSRF 风险,把风险转换为接口测试条件。
- 实践实践指南英文
OWASP:SQL Injection Prevention (新标签页)↗
阅读参数化查询和标识符白名单,区分数据绑定与动态 SQL 结构。
- 深入实践指南英文
OWASP:Authorization Cheat Sheet (新标签页)↗
阅读默认拒绝、每次请求校验和对象权限,建立权限矩阵及反例测试。
- 深入实践指南英文
OWASP:SSRF Prevention (新标签页)↗
阅读允许列表、重定向与实际连接目标校验,为远程导入设置可验证的出站边界。
- 实践实践指南中文
网站安全入门:先知道常见问题怎样出现 (新标签页)↗
先读 SQL 注入与跨站脚本部分。小练习:给自己的留言功能画出“输入—保存—页面显示”三步,逐处写清参数绑定、输出转义和权限检查分别应放在哪里。 文章公开,适合认识常见风险。文中的手工 SQL 转义示例不能作为通用防护直接照抄;实际查询应使用参数绑定,具体防护再对照 OWASP 的专门指南。
- 实践实践指南中文
OWASP Top 10:用常见风险检查自己的后端 (新标签页)↗
先选访问控制、配置错误和异常处理三类。小练习:检查自己的一个接口,写出谁能调用、输入错误怎样返回、日志会不会带出敏感内容;每条都指向一处代码或配置。 官方站点的中文入口公开;语言路径为 zh-Hant,页面含中文译文。它是风险意识资料,不是能保证安全的完整检查表或认证。
- 深入参考文档英文
OpenJDK 17 URI 源码 (新标签页)↗
按指定版本定位
URI.create/getScheme/getHost;对照源码分析理解实际调用边界。