一个从不与私有网络通信的公开表单,仍然会产生私有网络必须判断的数据。隧道只改变了谁呼叫谁。下载之后的一切都是导入决策,属于私有侧。本演练跟随一条提交,从浏览器一直到已提交的内部记录。

从提交落地之处开始

浏览器向一个公开端点提交,该端点把一行写入受控的接收存储,并且只返回一个确认。该存储就是交接点,它应当是公开层唯一能写入的地方。把原始载荷原样留在那里,连同到达时间和一个稳定的记录标识符。

随后,私有工作进程通过隧道拉取待处理的行,并决定如何处理每一行。它以自己的身份运行,这是下一节的主题。

curl --silent --fail \
  --cert /etc/importer/client.crt --key /etc/importer/client.key \
  "https://intake.corp.internal/api/pending?limit=20" \
  -o /var/lib/importer/pending.json

只给导入程序所需的权限

公开 Web 进程与写入内部应用的身份应当是两条不同的主体。公开服务器仅仅因为要投递表单数据,并不需要内部管理权限;而为两侧共用一个账户,会让任何 Web 缺陷变成一次内部写入。给工作进程签发它自己的凭据,把凭据限定在一次导入操作上,并记录每次导入由哪个主体执行。正是这种分离,才让隧道边界具有意义,而不是装饰。

还要限制工作进程可以读取的来源。如果实现使用队列或传输服务,该服务会保留自己的认证与访问策略,因为 VPN 不会对载荷内部的任何内容做认证。

短时函数同样不能免于这项审查。在它运行期间,其网络位置和凭据仍然授予权限,更短的存活时间只是缩小了窗口。存活时间不能替代对身份可以执行哪些操作的决定。

在任何内部状态改变之前完成校验

公开表单字段在被证明相反之前都是攻击者控制的文本,而 OWASP 的输入校验指南正是因此把后端数据流列入不可信来源。加密传输只说明字节完整到达,并不说明它们描述的是一个被允许的请求。在任何特权步骤之前解析并校验,使坏行永远不会到达能够行动的代码路径。

定义允许的字段、它们的格式、大小以及必须成立的业务规则。日期可以格式正确,却对所请求的操作仍然是错的;而一个写明内部账户或审批状态的载荷,不能给自己授予其中任何一项。

把被拒绝的行送到复核结果,而不是猜测修正,或把它们转发给 shell 或原始数据库接口。记录记录标识符和失败的规则,而不是申请人输入的每个字段。

在一次操作上设置重试点

如果记录 submission-1042 因为第一次确认丢失而被拉取两次,工作进程需要一种持久的方式来识别来源记录及其已提交结果。只运行一次的调度并不能证明每一行都被应用了一次。重复投递是正常的,所以设计必须吸收它。

使用内部应用自身的事务与重复处理机制,并把来源标识符、结果与确认保持在一条连贯的序列中。只有在预期操作已提交或被持久分配之后才确认,并且绝不因为字节已被下载就删除接收行。

然后用三份夹具证明这一点:一行有效数据、一行格式错误的数据,以及同一个受控来源标识符被投递两次。每一份都经过工作进程使用的同一个端点。

for f in fixtures/valid-submission.json fixtures/bad-date.json; do
  curl --silent --fail --cert /etc/importer/client.crt \
    -X POST --data @"$f" https://intake.corp.internal/api/records
done
curl --silent --fail --cert /etc/importer/client.crt \
  -X POST --data @fixtures/valid-submission.json https://intake.corp.internal/api/records

前两行必须终止于不同的内部状态,第三行必须返回该来源记录已经提交的标识符,而不是创建第二个。其他任何结果都意味着导入尚不可重复。

相关文章