ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Hurl 安全测试实战:服务端校验、CSRF 防护与信息泄露断言

Hurl 安全测试实战:服务端校验、CSRF 防护与信息泄露断言 Hurl 安全测试实战服务端校验、CSRF 防护与信息泄露断言【免费下载链接】hurlHurl, run and test HTTP requests with plain text.项目地址: https://gitcode.com/GitHub_Trending/hu/hurl在 Web 应用安全测试中客户端校验、CSRF 令牌、HTML 注释泄露都是容易被无头浏览器掩盖的薄弱点。本文基于 Hurl 官方教程 Security 一节以注册signup工作流为例完整演示如何用 Hurl 这个纯 HTTP 层工具绕过浏览器保护直接测试服务端校验逻辑、验证 CSRF 防护、断言页面不泄露 HTML 注释并结合 Hurl 源码说明[Options]重定向控制与url/xpath断言的底层实现帮助读者建立一套可复用、可放入 CI 的安全回归测试方法。为什么用 Hurl 做服务端安全测试浏览器端的客户端校验minlength、maxlength、pattern、required等 HTML 属性或 JavaScript 校验逻辑本意是帮用户避免输入错误、减少无效请求但它不是安全边界。只要攻击者不经过浏览器直接构造 HTTP 请求这些校验全部失效。因此服务端必须自行完成校验和清洗并且这些服务端逻辑必须被测试覆盖。问题在于如果你的测试依赖无头浏览器浏览器的客户端校验会主动拦截非法输入使你很难把脏数据真正发到服务端使用 JavaScript 做校验时构造非法请求更是麻烦。Hurl 的定位是构建在 curl 之上的 HTTP 执行器HTTP runner它不经过浏览器、没有任何浏览器保护发送和断言非法数据因此变得轻而易举。这正是它作为安全测试工具的价值所在。以 Hurl 教程配套的示例站点运行在http://localhost:3000为例注册页http://localhost:3000/signup的 HTML 表单大致如下form classsignup-form methodpost action/signup ... input typetext nameusername idusername autocompleteoff minlength3 maxlength32 pattern[a-zA-Z0-9_-]{3,32} titleUsername must use a-z, A-Z, 0-9 or _ - required ... input typetext namename idname autocompleteoff minlength3 maxlength32 pattern[a-zA-Z\d\s-]{3,32} required ... input typeemail nameemail idemail autocompleteoff minlength4 maxlength32 required ... input typepassword namepassword idpassword autocompleteoff minlength6 maxlength32 required ... input typepassword namepassword-confirm idpassword-confirm autocompleteoff minlength6 maxlength32 required /form其中username输入框带有 validation HTML attributesminlength3、maxlength32、一个正则pattern和required。在浏览器里这些属性会阻止用户提交缺失值或超长名字——但服务端是否真的拒绝这类数据只有绕过浏览器才能验证。先测合法路径正常创建用户安全测试的第一步是先保证名义nominal用例通过。整个signup.hurl文件的思路分四步获取可用用户名、进入注册页抓取 CSRF 令牌并 POST 表单、跟随重定向、再叠加非法数据测试。第 1 步从 REST API 获取一个可用用户名。这样后续注册不会因重名而干扰断言# First we obtain an available username: GET http://localhost:3000/api/usernames/available HTTP 200 [Captures] username: jsonpath $.username这里用[Captures]段把 JSON 响应中的$.username捕获为变量username供后面的请求模板引用JSONPath capture 的完整说明见 Capturing Response。第 2 步访问注册页用 XPath 抓取 CSRF 令牌然后 POST 表单创建用户。CSRF跨站请求伪造防护要求服务端在表单中嵌入一个动态生成的令牌POST 时必须携带有效令牌否则请求被拒。令牌每次请求都会变化不能硬编码必须动态捕获这一流程在教程上一节 Captures 中已铺垫# First we obtain an available username: # ... # Create a new valid user: get the CSRF token the signup: GET http://localhost:3000/signup HTTP 200 [Captures] csrf_token: xpath string(//input[name_csrf]/value) POST http://localhost:3000/signup [Form] _csrf: {{csrf_token}} username: {{username}} name: Bob email: {{username}}example.net password: 12345678 HTTP 302 [Asserts] header Location /my-movies # Go to my movies GET http://localhost:3000/my-movies HTTP 200要点说明[Form]段发送表单值Hurl 会自动推断Content-Type: application/x-www-form-urlencoded无需手写该头{{csrf_token}}是 Hurl 的模板变量语法引用前面[Captures]捕获的值与示例站一致服务采用 Post/Redirect/Get 模式POST 成功后返回302 Found[Asserts]段用header Location /my-movies断言重定向目标再用一个显式GET /my-movies验证落地页。用[Options]段自动跟随重定向手工写出重定向的每一步略显繁琐可以让 Hurl 在单个请求上自动跟随重定向。Hurl 默认不跟随重定向与其 HTTP 引擎 curl 的行为一致这对逐步验证每个跳转是有益的而[Options]段可以把某个选项只应用到某一个请求上。第 3 步在 POST 请求中加[Options]段# First we obtain an available username: # ... # Create a new valid user: get the CSRF token the signup: GET http://localhost:3000/signup HTTP 200 [Captures] csrf_token: xpath string(//input[name_csrf]/value) POST http://localhost:3000/signup [Options] location: true [Form] _csrf: {{csrf_token}} username: {{username}} name: Bob email: {{username}}example.net password: 12345678 HTTP 200 [Asserts] url endsWith /my-movies两个关键行为变化值得注意断言作用于最终响应。跟随重定向后Hurl 的断言和捕获都是针对最后一次跳转后的 HTTP 响应执行的所以状态码断言要写成HTTP 200而不是302。用url断言检查最终落点。url endsWith /my-movies直接断言重定向链条的终点 URL见 URL assert。从源码结构看[Options]段的解析在 option.rs 中完成location选项被解析为OptionKind::FollowLocation(value)option.rs#L163与命令行全局选项-L, --location对应见 manual.md 选项表。Hurl 支持的完整请求级选项cacert、connect-timeout、max-redirs、proxy、variables-file等及默认语义在 Hurl File 文档的 Options 小节中有完整列表。此外文档还指出一个细节[Options]段中的variable和variables-file定义的变量对后续 entry 也生效这是唯一例外的选项其余选项只对当前请求生效。第 4 步运行并验证。$ hurl --test signup.hurl signup.hurl: Success (4 request(s) in 26 ms) -------------------------------------------------------------------------------- Executed files: 1 Executed requests: 4 (148.1/s) Succeeded files: 1 (100.0%) Failed files: 0 (0.0%) Duration: 27 ms4 个请求获取用户名、取 CSRF 令牌、POST 注册、GET 我的电影页全部通过合法注册路径确认无误。测试非法输入过短的用户名合法路径通了现在故意提交服务端应当拒绝的数据。比如用一个只有两个字母的用户名bo——表单的minlength是 3服务端应把它打回注册页并显示错误信息。第 5 步追加一个用户名过短的 POST# First we obtain an available username: # ... # Create a new valid user: get the CSRF token the signup: # ... # Try an invalid username: too short. We should stay on signup GET http://localhost:3000/signup HTTP 200 [Captures] csrf_token: xpath string(//input[name_csrf]/value) POST http://localhost:3000/signup [Options] location: true [Form] _csrf: {{csrf_token}} username: bo name: Bob email: bob78example.net password: 12345678 HTTP 200 [Asserts] url endsWith /signup xpath string(//div[classform-errors]) contains Username must be 3 to 32 chars long断言设计有两层url endsWith /signup由于加了location: true请求最终停在注册页而非跳转成功页断言 URL 后缀即可证明用户没被创建xpath string(//div[classform-errors]) contains Username must be 3 to 32 chars long用 XPath 断言 精确核对服务端渲染出的错误文案。如果无头浏览器跑这段流程username字段的minlength3属性会直接阻止提交服务端校验逻辑根本不会被执行——这就是客户端校验掩盖服务端缺陷的典型场景。测试 CSRF 防护不带令牌的请求必须被拒绝第 6 步追加一个不带_csrf字段的 POST 请求# First we obtain an available username: # ... # Create a new valid user: get the CSRF token the signup: # ... # Try an invalid username: too short. We should stay on signup # ... # Test CSRF token is mandatory: POST http://localhost:3000/signup [Form] username: bob name: Bob email: bob78example.net password: 12345678 HTTP 403这里表单里故意不包含_csrf键直接断言HTTP 403。这个测试的价值与上一条相同如果你用无头浏览器测试页面CSRF 令牌总会被浏览器自动创建并随表单提交你永远测不到后端是否真的校验令牌这一环。Hurl 以裸 HTTP 客户端身份发请求让 CSRF 防护成为显式可测对象。第 7 步运行完整文件。$ hurl --test signup.hurl signup.hurl: Success (8 request(s) in 38 ms) -------------------------------------------------------------------------------- Executed files: 1 Executed requests: 8 (205.1/s) Succeeded files: 1 (100.0%) Failed files: 0 (0.0%) Duration: 39 ms8 个请求合法路径 4 个 非法用户名 2 个 CSRF 校验 1 个 中间取令牌的 GET 等全部通过。附加安全点断言 HTML 不泄露注释Hurl 贴近 HTTP 层的另一个安全用途检查服务器实际下发的 HTML 是否泄露注释。注释可能包含敏感信息TODO、临时逻辑、内部配置安全测试指南普遍建议在生产的 HTML 文件中裁剪注释。这里有一个常见的认知误区像 React、Vue 这类客户端渲染框架会管理 DOM你用浏览器开发者工具检查 DOM 时看不到任何注释——因为框架在构建动态 DOM 时把它们丢掉了。但如果你查看网络上实际传输的 HTML即服务器发送的原始文档而非框架动态生成的文档注释仍然在。Hurl 恰好断言的就是这份真实的网络数据。在signup.hurl的第二个 entryGET 注册页中追加一条 XPath 断言# First we obtain an available username: # ... # Create a new valid user: get the CSRF token the signup: GET http://localhost:3000/signup HTTP 200 [Captures] csrf_token: xpath string(//input[name_csrf]/value) [Asserts] xpath //comment count 0 # Check that we dont leak comments # ...xpath //comment count 0要求整个响应文档中注释节点数量为 0与前面演示的contains/谓词配合构成对页面信息面的持续守护。运行后依然全部通过$ hurl --test signup.hurl signup.hurl: Success (8 request(s) in 47 ms) -------------------------------------------------------------------------------- Executed files: 1 Executed requests: 8 (166.7/s) Succeeded files: 1 (100.0%) Failed files: 0 (0.0%) Duration: 48 ms完整复盘最终的 signup.hurl把各步骤合并后完整的安全测试文件如下——它覆盖了合法注册、CSRF 令牌捕获与使用、非法输入拒绝、CSRF 防护验证、注释泄露检查五个安全要点# First we obtain an available username: GET http://localhost:3000/api/usernames/available HTTP 200 [Captures] username: jsonpath $.username # Create a new valid user: get the CSRF token the signup: GET http://localhost:3000/signup HTTP 200 [Captures] csrf_token: xpath string(//input[name_csrf]/value) [Asserts] xpath //comment count 0 # Check that we dont leak comments POST http://localhost:3000/signup [Options] location: true [Form] _csrf: {{csrf_token}} username: {{username}} name: Bob email: {{username}}example.net password: 12345678 HTTP 200 [Asserts] url endsWith /my-movies # Play some checks on signup form: username too short # email already taken, invalid pattern for username GET http://localhost:3000/signup HTTP 200 [Captures] csrf_token: xpath string(//input[name_csrf]/value) # Create a new user, username too short POST http://localhost:3000/signup [Options] location: true [Form] _csrf: {{csrf_token}} username: bo name: Bob email: bob78example.net password: 12345678 HTTP 200 [Asserts] url endsWith /signup xpath string(//div[classform-errors]) contains Username must be 3 to 32 chars long # Test CSRF is mandatory: POST http://localhost:3000/signup [Form] username: bob name: Bob email: bob78example.net password: 12345678 HTTP 403可以注意到文件注释中还预留了扩展方向邮箱已被占用email already taken、用户名模式非法invalid pattern for username等服务端校验场景都能按同样的非法输入 断言落点/错误文案模式继续添加。小结与延伸本节的结论是Hurl 可以作为安全测试工具直接检查服务端校验逻辑——因为它是 HTTP 层执行器而非浏览器客户端校验既不会替你拦截请求也不会替你补上 CSRF 令牌服务端的行为因此暴露无遗。围绕 Security 一节你应当能掌握三个可复用的安全断言模式非法输入回归故意构造违反minlength/maxlength/pattern的表单值用url断言 错误文案xpath断言双重验证服务端拒绝行为CSRF 防护验证发送不带_csrf字段的 POST断言HTTP 403信息泄露检查xpath //comment count 0断言原始网络响应不携带 HTML 注释。所有测试至此都在本地完成。下一节 CI/CD Integration 将演示如何把这类 Hurl 安全测试文件接入 GitHub Actions 或 GitLab CI/CD 流水线让安全回归在每次提交时自动执行。相关概念可继续在 Request 文档[Form]、[Options]段、Asserting Response 文档url/xpath断言与谓词和 Capturing Response 文档JSONPath/XPath 捕获中查阅。【免费下载链接】hurlHurl, run and test HTTP requests with plain text.项目地址: https://gitcode.com/GitHub_Trending/hu/hurl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进