使用 sscanf 安全解析 URL 查询字符串的正确方法

8次阅读

使用 sscanf 安全解析 URL 查询字符串的正确方法

本文详解如何用 sscanf 正确解析形如 Name=ss&Port=8081&ID=0&Config=testconfig 的 CGI 查询字符串,解决 %s 贪婪匹配导致字段越界的问题,并提供带缓冲区保护的安全格式化方案。

本文详解如何用 `sscanf` 正确解析形如 `name=ss&port=8081&id=0&config=testconfig` 的 cgi 查询字符串,解决 `%s` 贪婪匹配导致字段越界的问题,并提供带缓冲区保护的安全格式化方案。

在 C/C++ 编写的 CGI 程序中,常通过 getenv(“QUERY_STRING”) 获取 URL 查询参数(如 Name=ss&Port=8081&ID=0&Config=testconfig),再借助 sscanf 提取各字段。但若直接使用 %s 格式符(如 “Name=%s&Port=%d…”),会导致首个字符串字段贪婪捕获直到字符串末尾——因为 %s 默认以空白符(空格、制表符、换行)或 为终止边界,而查询字符串中不含这些字符,& 对它完全“不可见”。

例如原代码:

sscanf(data, "Name=%s&Port=%d&ID=%d&Config=%s", &name, &port, &id, &config);

当输入为 Name=ss&Port=8081&ID=0&Config=testconfig 时,%s 会将 ss&Port=8081&ID=0&Config=testconfig 全部读入 name,后续字段解析失败。

✅ 正确做法是:显式指定扫描终止符。使用 [^&] 字符集格式符——%[^&] 表示“匹配任意非 & 字符”,一旦遇到 & 即停止读取,完美契合查询字符串的分隔逻辑。

同时,必须限定缓冲区长度以防溢出(name[50] 最多存 49 字符 + 1 个 )。最终安全写法如下:

#include <stdio.h> #include <stdlib.h>  int main(void) {     printf("Content-Type: text/plain;charset=us-asciinn");     printf("Hello worldnn");      char* data = getenv("QUERY_STRING");     if (!data || *data == '') {         printf("Error: No query string received.n");         return 1;     }      char name[50];     int port, id;     char config[50];      // 关键改进:用 %49[^&] 替代 %s,明确以 '&' 为截断点;%49s 同样加宽度限制     int ret = sscanf(data, "Name=%49[^&]&Port=%d&ID=%d&Config=%49s",                       name, &port, &id, config);      if (ret != 4) {         printf("Parse error: expected 4 fields, got %dn", ret);         return 1;     }      printf("Name: %sn", name);   // 输出 "ss"     printf("Port: %dn", port);   // 输出 "8081"     printf("ID: %dn", id);       // 输出 "0"     printf("Config: %sn", config); // 输出 "testconfig"     return 0; }

? 关键要点总结

  • ❌ 避免裸用 %s 解析无空白分隔的 URL 参数;
  • ✅ 优先使用 %[^&](或 %[^&=] 等)配合明确分隔符;
  • 始终指定最大宽度(如 %49[^&]),杜绝缓冲区溢出风险;
  • ✅ 检查 sscanf 返回值,确保所有预期字段成功解析(返回值应等于格式项数量);
  • ⚠️ 注意:sscanf 不解码 URL 编码(如 + → 空格、%20 → 空格),生产环境需额外实现 url_decode() 函数。

此方案兼顾安全性、可读性与 CGI 场景的实用性,是解析简单查询字符串的经典实践。

text=ZqhQzanResources