feat: add cybersecurity topic with 6 subtopics (204 questions)
Deploy Examination / deploy (push) Successful in 7s

New topic group: 网络安全专题 (cybersecurity)
Subtopics:
- web-security: Web 安全基础 (34 questions)
- crypto-basics: 密码学基础 (34 questions)
- buffer-overflow: 缓冲区溢出与漏洞利用 (34 questions)
- os-security: 操作系统安全 (34 questions)
- network-attack: 网络攻防 (34 questions)
- secure-coding: 安全编程实践 (34 questions)

Question types per subtopic:
- single_choice × 20
- true_false × 10
- short_answer × 3
- code_reading × 1

Difficulty range: 1-3 (基础)
This commit is contained in:
2026-09-17 13:33:04 +08:00
parent da9667c265
commit 70e368e235
31 changed files with 4516 additions and 1 deletions
@@ -0,0 +1,407 @@
{
"topic": "secure-coding",
"type": "single_choice",
"schema_version": "1.0.0",
"generated": "2026-09-17T00:00:00+08:00",
"questions": [
{
"id": "sc-001",
"type": "single_choice",
"difficulty": 1,
"tags": [
"安全编程",
"输入校验"
],
"question": "在输入校验策略中,白名单(白名单验证)相对于黑名单(黑名单过滤)的主要优势是什么?",
"options": {
"A": "白名单只允许已知合法的输入格式,能有效防御未知攻击向量",
"B": "白名单实现更简单,不需要维护规则列表",
"C": "白名单处理速度更快,因为它匹配的规则更少",
"D": "白名单不需要考虑编码绕过问题"
},
"answer": "A",
"explanation": "白名单验证的核心思想是「默认拒绝,仅放行已知合法的输入」。这意味着只有明确符合预定义规则的输入才能通过,因此即使攻击者使用全新的、前所未见的攻击载荷,只要它不符合白名单规则就会被拒绝。相比之下,黑名单试图列举所有已知的恶意模式,但攻击者总能通过编码变换、大小写混淆、零宽字符等手段绕过过滤。B 错误:白名单通常需要更精确地定义合法输入的规则,实现并不比黑名单简单。C 错误:速度差异不是核心优势,两者在规则数量上没有必然的大小关系。D 错误:白名单同样需要考虑输入编码问题,否则仍可能被绕过。",
"source": null,
"related": []
},
{
"id": "sc-002",
"type": "single_choice",
"difficulty": 1,
"tags": [
"安全编程",
"输入校验"
],
"question": "关于输入校验的执行位置,以下哪种做法是最安全的?",
"options": {
"A": "仅在客户端(前端)使用 JavaScript 进行输入校验",
"B": "仅在服务端(后端)进行输入校验",
"C": "客户端和服务端都进行输入校验,但以服务端校验为最终安全保障",
"D": "在数据库层面使用存储过程进行所有输入校验"
},
"answer": "C",
"explanation": "安全的最佳实践是采用「纵深防御」策略:客户端校验用于提供即时反馈、改善用户体验(如提示格式错误),但客户端校验可以被完全绕过(攻击者可以直接发送 HTTP 请求,不经过前端),因此不能作为安全屏障。服务端校验是真正的安全防线,因为所有请求最终都要经过服务端处理。两者结合既保证了用户体验,又确保了安全性。A 错误:客户端校验可以被绕过,不能单独依赖。B 错误:虽然服务端校验是必须的,但缺少客户端校验会降低用户体验。D 错误:将所有校验放在数据库层会导致架构耦合,且不适用于所有输入类型。",
"source": null,
"related": []
},
{
"id": "sc-003",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"输入校验"
],
"question": "以下代码存在 SQL 注入风险,最有效的修复方式是什么?\n\n```python\nquery = \"SELECT * FROM users WHERE name = '\" + user_input + \"'\"\n```",
"options": {
"A": "对 user_input 中的单引号进行转义,将 `'` 替换为 `''`",
"B": "使用参数化查询(Prepared Statement)",
"C": "将 user_input 限制为最多 50 个字符",
"D": "在 user_input 外层再加一层单引号包裹"
},
"answer": "B",
"explanation": "参数化查询(Prepared Statement)是防御 SQL 注入的行业标准做法。它通过将 SQL 语句结构与数据完全分离,确保用户输入始终被当作数据而非代码执行,从根本上消除了 SQL 注入的可能性。A 错误:手动转义引号是一种不完整且容易出错的防御方式,在某些字符集(如 GBK)下可能被绕过(宽字节注入),且无法覆盖所有注入场景。C 错误:限制输入长度不能防止 SQL 注入,即使很短的输入也可以构造恶意 SQL。D 错误:这不仅不能解决问题,反而可能导致 SQL 语法错误。",
"source": null,
"related": []
},
{
"id": "sc-004",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"输入校验"
],
"question": "以下哪段代码存在命令注入(Command Injection)漏洞?",
"options": {
"A": "subprocess.run(['ping', '-c', '4', user_input], shell=False)",
"B": "os.system('ping -c 4 ' + user_input)",
"C": "subprocess.run(['ping', '-c', '4', sanitize(user_input)], shell=False)",
"D": "result = subprocess.check_output(['ping', '-c', '4', user_input], shell=False, stderr=subprocess.DEVNULL)"
},
"answer": "B",
"explanation": "`os.system()` 通过 shell 执行命令,当 user_input 中包含 shell 元字符(如 `; rm -rf /` 或 `$(malicious_command)`)时,这些字符会被 shell 解释执行,从而导致命令注入。A、D 使用 `subprocess.run`/`check_output` 并设置 `shell=False`,此时命令参数以列表形式传递,不经过 shell 解析,用户输入被视为单个参数而非 shell 表达式,因此是安全的。C 在 A 的基础上额外使用了 sanitize 函数进行输入净化,更加安全。防御命令注入的核心原则是:永远不要将用户输入直接拼接到 shell 命令中;如必须调用系统命令,应使用参数列表形式(list)且关闭 shell 解释。",
"source": null,
"related": []
},
{
"id": "sc-005",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"整数溢出"
],
"question": "在 C 语言中,需要对用户输入的两个 `int` 值进行相加并分配内存。以下哪种做法最安全?",
"options": {
"A": "int *buf = (int*)malloc((a + b) * sizeof(int));",
"B": "if (a > 0 && b > 0 && a <= INT_MAX - b) { int *buf = (int*)malloc((a + b) * sizeof(int)); }",
"C": "int *buf = (int*)malloc((long)(a + b) * sizeof(int));",
"D": "unsigned int total = (unsigned int)a + (unsigned int)b; int *buf = (int*)malloc(total * sizeof(int));"
},
"answer": "B",
"explanation": "B 是唯一正确的做法——在执行加法之前先检查是否会溢出。`a <= INT_MAX - b` 确保 `a + b` 的结果不会超过 `INT_MAX`,从而避免整数溢出。如果溢出发生,加法结果会回绕(wrap around)为一个很小的数,导致 `malloc` 分配远小于预期的内存,后续写入会造成堆缓冲区溢出。A 错误:直接相加不检查溢出,当 a + b 超过 INT_MAX 时结果为负数或很小的正数。C 错误:虽然将结果转为 long,但 `(a + b)` 在转换之前已经溢出了,溢出发生在 int 层面,cast 无法修复。D 错误:转为 unsigned int 不能防止溢出——unsigned 溢出虽然不会触发未定义行为(会回绕),但仍会导致 malloc 分配过小的内存,造成相同的安全问题。",
"source": null,
"related": []
},
{
"id": "sc-006",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"整数溢出"
],
"question": "以下 C 代码检查数组索引是否越界,但存在漏洞。根本原因是什么?\n\n```c\nvoid access_array(int index, int offset) {\n if (index + offset < ARRAY_SIZE) {\n array[index + offset] = value;\n }\n}\n```",
"options": {
"A": "数组 array 没有被正确初始化",
"B": "index + offset 可能发生整数溢出,导致边界检查被绕过",
"C": "缺少对 index 和 offset 的负数检查",
"D": "ARRAY_SIZE 应该使用 sizeof 运算符计算"
},
"answer": "B",
"explanation": "当 `index` 和 `offset` 都是较大的正整数时,`index + offset` 可能发生整数溢出(例如 index = 0x7FFFFFF0, offset = 0x20,结果为 0x80000010 即一个负数),这个负数在与 `ARRAY_SIZE`(通常被解读为 unsigned 比较)比较时可能变为一个非常大的正数,从而绕过 `< ARRAY_SIZE` 的检查,导致数组越界访问。修复方式应改为:`if (offset < ARRAY_SIZE && index < ARRAY_SIZE - offset)`,先检查减法不会下溢,再检查加法结果。C 也是相关问题(负数可以绕过单边检查),但根本原因是算术溢出导致检查条件本身被绕过。A 和 D 与漏洞无关。",
"source": null,
"related": []
},
{
"id": "sc-007",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"格式化字符串"
],
"question": "以下哪段代码存在格式化字符串漏洞?",
"options": {
"A": "printf(\"%s\", user_input);",
"B": "printf(user_input);",
"C": "fprintf(stderr, \"%.*s\", MAX_LEN, user_input);",
"D": "snprintf(buf, sizeof(buf), \"%s\", user_input);"
},
"answer": "B",
"explanation": "`printf(user_input)` 将用户输入直接作为格式化字符串使用。如果攻击者输入 `%x%x%x%x%n`,`printf` 会将栈上的数据作为参数读取(`%x` 泄露栈数据),`%n` 会将已输出的字符数写入指定地址,从而实现任意内存读取和写入。A 安全:`user_input` 作为 `%s` 的参数,仅被当作普通字符串输出,其中的 `%` 字符不会被解释为格式化说明符。C 安全:同样将 user_input 作为 `%s` 的参数,并限制了输出长度。D 安全:`snprintf` 中 `%s` 是格式字符串,user_input 是参数,缓冲区大小受 `sizeof(buf)` 限制。防御原则:永远不要将用户输入直接作为 `printf` 系列函数的第一个参数。",
"source": null,
"related": []
},
{
"id": "sc-008",
"type": "single_choice",
"difficulty": 3,
"tags": [
"安全编程",
"格式化字符串"
],
"question": "在一次渗透测试中,测试人员发现某个 C 程序使用 `printf(user_input)` 输出日志。攻击者输入 `%7$n` 成功向某个内存地址写入了值。这个攻击能够成功的根本原因是什么?",
"options": {
"A": "printf 的内部缓冲区发生了溢出",
"B": "格式化说明符 %n 会将已输出的字符数写入对应参数指向的地址,而 %7$ 通过栈偏移定位到了攻击者可控的地址",
"C": "printf 在处理 %n 时自动调用了 system()",
"D": "编译器没有开启栈保护(Stack Canary)导致 %n 可以覆盖返回地址"
},
"answer": "B",
"explanation": "格式化字符串漏洞的核心机制是:`%n` 说明符将「到目前为止已输出的字符数」写入一个由参数指定的地址。当用户输入作为格式化字符串时,攻击者可以通过构造特定格式串来控制 printf 从栈上读取的「参数」。`%7$n` 表示取第 7 个参数(从格式化字符串参数之后开始计数)的值作为写入目标地址,同时通过控制 `%n` 之前的输出字符数来控制写入的值。这实现了「任意地址写任意值」。A 错误:这与缓冲区溢出无关,是格式化字符串的参数解释机制被滥用。C 错误:`%n` 的行为是内存写入,不是调用 system()。D 错误:栈保护(Stack Canary)保护的是返回地址被覆盖的场景,格式化字符串漏洞利用的是 printf 的参数解释机制,不直接触发 canary 违例。当然,现代编译器和 libc 提供了格式化字符串编译期警告和运行时保护,但问题问的是漏洞根因。",
"source": null,
"related": []
},
{
"id": "sc-009",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"内存安全"
],
"question": "以下代码存在缓冲区溢出风险。最有效的修复方式是什么?\n\n```c\nvoid greet(char *name) {\n char buf[64];\n strcpy(buf, name);\n printf(\"Hello, %s!\\n\", buf);\n}\n```",
"options": {
"A": "将 strcpy 替换为 strncpy(buf, name, sizeof(buf) - 1); 并确保 buf[sizeof(buf)-1] = '\\0';",
"B": "将 buf 的大小改为 256 字节以容纳更多输入",
"C": "使用 sprintf(buf, \"%s\", name) 替代 strcpy",
"D": "在函数开头添加 if (strlen(name) < 64) 检查后再 strcpy"
},
"answer": "A",
"explanation": "`strncpy` 限制了最多复制 `sizeof(buf) - 1` 个字符,防止溢出,之后手动添加 null 终止符确保字符串正确终止(`strncpy` 在源字符串长度超过 n 时不会自动添加 `\\0`)。这是防御缓冲区溢出的标准做法。B 错误:增大缓冲区只是推迟了问题——只要输入足够长,仍会溢出,这是一种「鸵鸟策略」。C 错误:`sprintf` 同样不检查目标缓冲区大小,存在相同的溢出风险,只是将 `strcpy` 换了一个等价的函数。D 错误:`strlen(name)` 本身需要遍历整个字符串,如果 name 不是 null 终止的(已经被破坏的情况),`strlen` 可能读取越界内存导致未定义行为;此外,`strlen` 检查与 `strcpy` 之间存在 TOCTOU(检查时间与使用时间)竞态问题。更优的做法是使用 `strlcpy`(如果平台支持)或 `snprintf(buf, sizeof(buf), \"%s\", name)`。",
"source": null,
"related": []
},
{
"id": "sc-010",
"type": "single_choice",
"difficulty": 3,
"tags": [
"安全编程",
"内存安全"
],
"question": "现代编译器的栈保护机制(Stack Canary)能够有效防御以下哪种攻击?",
"options": {
"A": "格式化字符串漏洞导致的任意内存写入",
"B": "栈缓冲区溢出覆盖返回地址(Return Address)",
"C": "堆(Heap)上的 Use-After-Free 漏洞",
"D": "整数溢出导致的内存分配不足"
},
"answer": "B",
"explanation": "Stack Canary(栈金丝雀)是一种编译器插入的运行时保护机制。编译器在函数栈帧中、局部变量与返回地址之间插入一个随机值(canary),函数返回前检查该值是否被修改。如果发生栈缓冲区溢出并试图覆盖返回地址,必然会先破坏 canary 值,程序在函数返回前检测到 canary 被篡改就会终止进程并报告栈溢出。A 错误:格式化字符串漏洞通过 printf 的 `%n` 直接写入指定地址,不经过栈变量覆盖,不会触发 canary 检查。C 错误:Use-After-Free 是堆内存管理问题,与栈保护无关。D 错误:整数溢出导致的内存分配不足是一个逻辑层面的安全问题,栈保护机制对此无能为力。需要注意的是,Stack Canary 不能防御所有栈溢出——如果攻击者能泄露 canary 值(如通过格式化字符串或信息泄露漏洞),仍然可以绕过此保护。",
"source": null,
"related": []
},
{
"id": "sc-011",
"type": "single_choice",
"difficulty": 1,
"tags": [
"安全编程",
"输入校验"
],
"question": "在 Web 应用中防御 XSS(跨站脚本攻击)时,以下哪种做法是最基本且有效的输出编码策略?",
"options": {
"A": "对所有用户输入进行 Base64 编码后再存入数据库",
"B": "在输出到 HTML 页面时,对特殊字符进行 HTML 实体编码(如 < 转义为 &lt;)",
"C": "在前端使用 JavaScript 的 eval() 函数解析用户输入",
"D": "限制用户输入长度不超过 100 个字符即可防止 XSS"
},
"answer": "B",
"explanation": "HTML 实体编码是防御 XSS 最基本的输出编码策略:将 <、>、&、双引号、单引号等特殊字符转义为对应的 HTML 实体,使浏览器将其视为纯文本而非可执行代码。A 选项的 Base64 编码仅是编码格式转换,解码后仍然是恶意脚本,无法防御 XSS。C 选项使用 eval() 反而会直接执行任意代码,极大增加风险。D 选项限制输入长度既不能可靠阻止攻击(短 payload 也可构成 XSS),也不是正确的防御思路——安全应依赖输出编码而非输入限制。",
"source": null,
"related": []
},
{
"id": "sc-012",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"输入校验"
],
"question": "关于内容安全策略(Content Security Policy, CSP),以下说法正确的是?",
"options": {
"A": "CSP 通过 HTTP 响应头 Content-Security-Policy 配置,可以限制页面能加载和执行的资源来源",
"B": "只要设置了 CSP,就完全不需要对用户输入做输出编码了",
"C": "CSP 的 default-src 指令只能设置为 'self',不能配置外部域名",
"D": "CSP 是浏览器端的技术,只在客户端生效,服务端无法控制"
},
"answer": "A",
"explanation": "CSP 是一种通过 HTTP 响应头声明的安全策略,它告诉浏览器只允许从指定来源加载和执行脚本、样式、图片等资源,从而有效限制 XSS 攻击面。B 选项错误:CSP 是纵深防御的一层,不能替代输出编码。CSP 可能被绕过(如 CSP 配置不当、存在 JSONP endpoint 等),输出编码仍是基础防线。C 选项错误:default-src 可以配置任意来源,包括 'self'、特定域名、https: 协议等。D 选项错误:CSP 虽由浏览器执行,但策略本身由服务端通过 HTTP 响应头下发,服务端完全控制策略内容。",
"source": null,
"related": []
},
{
"id": "sc-013",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"整数溢出"
],
"question": "以下 C 语言代码片段存在整数安全问题,其根本原因是什么?\n\nint buf_size = user_input * 4; // user_input 为 unsigned int\nchar *buf = malloc(buf_size);\nmemcpy(buf, src, user_input * 4);",
"options": {
"A": "malloc 可能返回 NULL,代码未检查分配是否成功",
"B": "有符号整数 buf_size 与无符号整数相乘可能导致有符号整数溢出,产生未定义行为",
"C": "memcpy 的第三个参数类型不匹配",
"D": "应该使用 calloc 代替 malloc 来避免安全问题"
},
"answer": "B",
"explanation": "这段代码的核心问题是:user_input 是 unsigned int,乘以 4 后赋值给有符号的 int 类型 buf_size。当 user_input 足够大(如 0x40000001),user_input * 4 在无符号算术下会回绕到一个较小的值,赋值给 int 时可能产生负数或一个远小于预期的正数。malloc 分配了过小的缓冲区,但 memcpy 仍按原始乘积拷贝数据,导致堆缓冲区溢出。虽然 A 选项也是合理的编程习惯问题,但题目问的是整数安全问题的根本原因。C 选项错误:size_t 和 int 在此场景不影响类型安全。D 选项错误:calloc 并不能解决整数溢出问题。",
"source": null,
"related": []
},
{
"id": "sc-014",
"type": "single_choice",
"difficulty": 3,
"tags": [
"安全编程",
"整数溢出"
],
"question": "在安全编码中,使用安全的整数运算库(如 GCC 的 __builtin_add_overflow 或 Microsoft 的 SafeInt)的主要目的是什么?",
"options": {
"A": "提升整数运算的执行速度,减少 CPU 开销",
"B": "将所有整数运算自动转换为浮点运算以避免溢出",
"C": "在运算发生溢出时立即检测并返回错误状态,而非依赖未定义行为或静默回绕",
"D": "将有符号整数统一转换为无符号整数进行运算"
},
"answer": "C",
"explanation": "安全整数运算库的核心价值是在算术运算发生溢出时提供可靠的检测机制。例如 __builtin_add_overflow(a, b, &result) 在加法溢出时返回 true 并可通过返回值判断是否溢出,而非让程序继续使用一个错误的回绕值。C 语言中,有符号整数溢出是未定义行为(UB),无符号整数溢出会静默回绕——两者都可能导致安全漏洞(如缓冲区溢出)。A 选项错误:安全库通常有少量额外开销,不是为了性能。B 选项错误:浮点运算有精度损失,不是解决整数溢出的正确方案。D 选项错误:简单转为无符号不解决溢出问题,无符号整数同样会回绕。",
"source": null,
"related": []
},
{
"id": "sc-015",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"内存安全"
],
"question": "关于 Use-After-Free(UAF)漏洞,以下描述正确的是?",
"options": {
"A": "UAF 是指程序试图释放一个从未被分配的内存地址",
"B": "UAF 发生在程序释放某块内存后,仍然通过旧指针(悬挂指针)访问该内存,此时内存可能已被重新分配给其他对象",
"C": "使用 malloc 分配内存就不会产生 UAF 问题,只有手动管理内存的语言才会出现",
"D": "UAF 只会导致程序崩溃,不会被利用来执行任意代码"
},
"answer": "B",
"explanation": "Use-After-Free 的本质是:程序调用 free() 释放了某块堆内存后,没有将指针置为 NULL,后续代码继续通过该悬挂指针(dangling pointer)访问已释放的内存。此时该内存区域可能已被分配给其他对象,读取会获得不可预期的数据,写入则可能篡改其他对象的内容——攻击者可以精心构造堆布局,通过 UAF 实现类型混淆或控制流劫持,最终执行任意代码。A 选项描述的是 double free 或 free of unallocated memory,不是 UAF。C 选项错误:任何有手动内存管理的语言(C/C++)都可能出现 UAF。D 选项错误:UAF 是浏览器、内核等高价值目标中被频繁利用的漏洞类型,可以实现远程代码执行。",
"source": null,
"related": []
},
{
"id": "sc-016",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"内存安全"
],
"question": "以下 C 代码存在 Double Free 漏洞,其最可能造成的后果是什么?\n\nchar *buf = malloc(100);\n// ... 使用 buf ...\nfree(buf);\n// 某些错误处理路径中再次调用\nfree(buf);",
"options": {
"A": "程序会忽略第二次 free 调用,不会产生任何影响",
"B": "Double Free 会破坏堆管理器的内部数据结构,可能导致程序崩溃或被利用来实现任意代码执行",
"C": "Double Free 只会造成内存泄漏,不会产生安全风险",
"D": "编译器会自动检测 Double Free 并在编译时报错"
},
"answer": "B",
"explanation": "Double Free 是指对同一块已释放的内存再次调用 free()。堆管理器(如 glibc 的 ptmalloc)使用元数据链表管理空闲块,double free 会破坏这些元数据结构(如在 fastbin 中形成环形链表),导致后续 malloc/free 操作出现不可预期行为。攻击者可以利用被破坏的堆结构,通过精心构造的分配序列实现堆内存的任意写入,最终劫持控制流。A 选项错误:标准 malloc 实现不会忽略 double free,glibc 会检测到并 abort,但这已经是防御性行为。C 选项错误:double free 不是内存泄漏,而是堆损坏。D 选项错误:编译器通常无法静态检测 double free,需要运行时工具(如 AddressSanitizer)来检测。",
"source": null,
"related": []
},
{
"id": "sc-017",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程",
"输入校验"
],
"question": "在 Web 应用中实现文件下载功能时,以下哪种做法最容易导致路径遍历(Path Traversal)漏洞?",
"options": {
"A": "使用用户提供的文件名直接拼接到文件系统路径,如 path = /uploads/ + user_filename",
"B": "将文件映射到数据库中的 ID,通过 ID 查找对应的真实文件路径",
"C": "对用户输入进行白名单校验,只允许字母和数字字符",
"D": "使用 chroot 或容器化技术隔离文件存储目录"
},
"answer": "A",
"explanation": "直接将用户输入拼接到文件路径是路径遍历漏洞的典型成因。攻击者可以提供 ../../../etc/passwd 作为文件名,读取服务器上的任意文件。B 选项是安全的做法:通过间接映射(ID 到文件路径)将用户输入与真实文件路径完全隔离,用户无法控制实际路径。C 选项通过白名单过滤也有效防止了路径遍历(../ 等特殊字符被拒绝),属于输入校验的正确做法。D 选项通过隔离限制了漏洞影响范围,即使存在路径遍历也无法逃出沙箱。只有 A 选项完全没有防护,是路径遍历的标准攻击场景。",
"source": null,
"related": []
},
{
"id": "sc-018",
"type": "single_choice",
"difficulty": 3,
"tags": [
"安全编程",
"输入校验"
],
"question": "在防御路径遍历攻击时,以下哪组措施组合最为全面和可靠?",
"options": {
"A": "仅对用户输入中的 ../ 进行字符串替换删除",
"B": "先对输入进行 URL 解码,再用 realpath() 规范化路径,最后验证规范化后的路径是否以允许的基准目录开头",
"C": "限制文件名长度不超过 255 个字符",
"D": "在 Web 服务器配置中禁止目录列表功能即可"
},
"answer": "B",
"explanation": "B 选项是防御路径遍历的最全面方案:(1) URL 解码防止 %2e%2e%2f 等编码绕过;(2) realpath() 将路径解析为绝对路径并消除所有符号链接、./ 和 ../ 等,得到规范化路径;(3) 验证规范化路径是否位于允许的基准目录下(startsWith 检查),确保最终访问的文件在预期范围内。这三个步骤缺一不可。A 选项的简单字符串替换可以被多种方式绕过,如 ....//(删除一个 ../ 后仍剩一个 ../)、%2e%2e/ 编码、反斜杠路径分隔符等。C 选项与路径遍历无关。D 选项仅影响目录浏览,不阻止直接的路径遍历访问。",
"source": null,
"related": []
},
{
"id": "sc-019",
"type": "single_choice",
"difficulty": 2,
"tags": [
"安全编程"
],
"question": "在系统安全设计中,最小权限原则(Principle of Least Privilege)的正确实践是?",
"options": {
"A": "给所有开发人员相同的生产环境访问权限,便于协作",
"B": "进程和服务应仅拥有完成其功能所必需的最小权限集,需要时临时提升权限,用完后立即撤销",
"C": "使用 root 或管理员账户运行所有服务,避免权限不足导致的功能异常",
"D": "最小权限原则只适用于操作系统层面,应用程序层面不需要考虑"
},
"answer": "B",
"explanation": "最小权限原则要求任何主体(用户、进程、服务)仅被授予完成其任务所必需的最小权限。这意味着:(1) 日常运行使用低权限账户;(2) 需要特权操作时通过 sudo 等机制临时提权;(3) 操作完成后立即降回低权限。即使进程被攻破,攻击者也只能获得有限的权限,限制了横向移动和影响范围。A 选项违反了权限分离原则。C 选项以 root 运行服务是严重的安全隐患——一旦被利用,攻击者直接获得最高权限。D 选项错误:最小权限原则适用于所有层面,包括操作系统、应用程序、数据库、API 访问控制等。",
"source": null,
"related": []
},
{
"id": "sc-020",
"type": "single_choice",
"difficulty": 3,
"tags": [
"安全编程",
"内存安全"
],
"question": "GCC 编译选项 -fstack-protector-strong 的主要作用是什么?",
"options": {
"A": "启用地址空间布局随机化(ASLR),使栈地址不可预测",
"B": "在函数返回前检查栈上的 canary 值是否被篡改,检测栈缓冲区溢出并终止程序",
"C": "将所有局部变量从栈上移到堆上分配,避免栈溢出",
"D": "禁止函数使用递归调用,防止栈空间耗尽"
},
"answer": "B",
"explanation": "-fstack-protector-strong 在编译时在函数的栈帧中插入一个随机的 canary 值(哨兵值),位于局部变量和返回地址之间。函数返回前检查该 canary 值是否与原始值一致——如果栈缓冲区溢出覆盖了返回地址,必然会先破坏 canary 值,程序检测到后调用 __stack_chk_fail 终止执行并记录日志。-fstack-protector-strong 相比 -fstack-protector 更积极:它保护所有包含数组、地址取用操作或局部变量大小超过一定阈值的函数。A 选项描述的是 ASLR,是操作系统级特性,非编译器选项。C 选项不正确:编译器不会自动将局部变量移到堆上。D 选项不正确:该选项不限制递归,递归栈溢出是运行时问题。",
"source": null,
"related": []
}
]
}