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,59 @@
{
"topic": "secure-coding",
"type": "code_reading",
"schema_version": "1.0.0",
"generated": "2026-09-17T00:00:00+08:00",
"questions": [
{
"id": "cr-001",
"type": "code_reading",
"difficulty": 2,
"tags": [
"安全编程",
"整数溢出",
"内存安全"
],
"question": "阅读以下C代码,回答问题。",
"code": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nvoid process_input(const char *user_input) {\n unsigned int len = strlen(user_input);\n // 检查长度是否合理\n if (len > 1024) {\n printf(\"Input too long!\\n\");\n return;\n }\n // 分配缓冲区:len + 1 用于存放结尾的 '\\0'\n char *buffer = (char *)malloc(len + 1);\n if (buffer == NULL) {\n printf(\"Memory allocation failed!\\n\");\n return;\n }\n strcpy(buffer, user_input);\n printf(\"Processed: %s\\n\", buffer);\n free(buffer);\n}\n\nint main(int argc, char *argv[]) {\n if (argc < 2) {\n printf(\"Usage: %s <input>\\n\", argv[0]);\n return 1;\n }\n process_input(argv[1]);\n return 0;\n}",
"language": "c",
"sub_questions": [
{
"index": 1,
"type": "single_choice",
"question": "当 user_input 的 strlen 返回值恰好为 SIZE_MAX(即 (size_t)-1)时,len + 1 的结果是什么?",
"options": {
"A": "0,因为无符号整数溢出后回绕到 0",
"B": "SIZE_MAX + 1 = 一个更大的数",
"C": "程序会崩溃",
"D": "编译器会报错"
},
"answer": "A",
"explanation": "unsigned int 在 C 语言中发生溢出时会进行模 2^N 回绕(wrap around)。当 len 为 SIZE_MAX 时,len + 1 回绕为 0。此时 malloc(0) 的行为是实现定义的——可能返回 NULL 或一个可 free 的指针,但分配的大小为 0 字节。后续 strcpy 会写入远超分配空间的数据,导致堆缓冲区溢出。"
},
{
"index": 2,
"type": "single_choice",
"question": "这段代码在上述溢出场景下,strcpy(buffer, user_input) 会导致什么后果?",
"options": {
"A": "正常复制,程序正常运行",
"B": "堆缓冲区溢出(heap buffer overflow),可能执行任意代码",
"C": "栈溢出",
"D": "只会影响 buffer 变量,不会影响其他数据"
},
"answer": "B",
"explanation": "当 len + 1 回绕为 0 时,malloc(0) 可能分配一个极小的内存块(或返回一个有效指针但大小为 0),而 user_input 的实际长度远超分配空间。strcpy 会将整个 user_input 复制到这个过小的缓冲区中,造成堆缓冲区溢出,可能覆盖堆上的其他数据结构,攻击者可利用此漏洞执行任意代码。"
},
{
"index": 3,
"type": "short_answer",
"question": "请修改 process_input 函数,修复其中存在的所有安全问题(至少指出并修复 2 个问题)。",
"answer": "修复方案应包括:\n1. 将 strlen 返回值类型改为 size_t(而非 unsigned int),避免类型截截断;\n2. 在 len + 1 运算前检查 len 是否等于 SIZE_MAX(或使用安全的加法函数如 __builtin_add_overflow / SafeInt),防止整数溢出回绕;\n3. 使用 strncpy 或 memcpy 替代 strcpy,限制拷贝长度;\n4. 检查 malloc 返回值后,对 buffer 大小为 0 的情况进行防御;\n5. 考虑使用 calloc 或更安全的内存分配函数。\n\n示例修复代码:\nvoid process_input(const char *user_input) {\n size_t len = strlen(user_input);\n if (len > 1024) { printf(\"Input too long!\\n\"); return; }\n if (len == SIZE_MAX) { printf(\"Invalid input length!\\n\"); return; }\n char *buffer = (char *)malloc(len + 1);\n if (buffer == NULL || len + 1 == 0) { printf(\"Allocation failed!\\n\"); return; }\n memcpy(buffer, user_input, len);\n buffer[len] = '\\0';\n printf(\"Processed: %s\\n\", buffer);\n free(buffer);\n}",
"explanation": "修复涉及多个层面:类型安全(size_t)、整数溢出检查(SIZE_MAX 边界)、安全的内存复制(memcpy 替代 strcpy)。这些都是安全编码的基本实践。"
}
],
"explanation": "此代码阅读题考察学生对整数溢出导致缓冲区溢出的安全漏洞理解,以及修复安全漏洞的能力。",
"source": null,
"related": []
}
]
}
@@ -0,0 +1,28 @@
{
"slug": "secure-coding",
"name": "安全编程实践",
"description": "输入校验、整数溢出、格式化字符串、内存安全",
"tags": [
"安全编程",
"输入校验",
"整数溢出",
"格式化字符串",
"内存安全"
],
"created": "2026-09-17",
"question_files": [
"code_reading.json",
"short_answer.json",
"single_choice.json",
"true_false.json"
],
"stats": {
"total": 34,
"by_type": {
"code_reading": 1,
"short_answer": 3,
"single_choice": 20,
"true_false": 10
}
}
}
@@ -0,0 +1,83 @@
{
"topic": "secure-coding",
"type": "short_answer",
"schema_version": "1.0.0",
"generated": "2026-09-17T00:00:00+08:00",
"questions": [
{
"id": "sa-001",
"type": "short_answer",
"difficulty": 1,
"tags": [
"安全编程",
"输入校验"
],
"question": "简述Web应用中输入校验的「双重校验」原则(客户端+服务端),以及各自的作用和局限性。",
"answer": "双重校验原则要求在客户端和服务端都对用户输入进行校验,两者缺一不可。\n\n1. 客户端校验:在浏览器/前端通过JavaScript等脚本对用户输入进行即时检查(如格式、长度、范围)。其作用是提升用户体验,减少无效请求,提供即时反馈。局限性在于客户端代码完全受用户控制,攻击者可以绕过(禁用JS、篡改请求、使用代理工具直接发送恶意请求),因此不能作为安全保障。\n\n2. 服务端校验:在后端对所有接收到的输入进行严格校验,是安全的最后一道防线。其作用是防止恶意数据进入系统,防御SQL注入、XSS、命令注入等攻击。局限性在于反馈延迟、增加服务端负担,且校验规则需要与业务逻辑紧密配合。\n\n核心原则:永远不要信任来自客户端的任何数据。客户端校验是便利性措施,服务端校验是安全性措施。",
"keywords": [
"客户端校验",
"服务端校验",
"输入验证",
"不可信输入",
"XSS",
"SQL注入",
"白名单"
],
"scoring_rubric": "满分标准:(1) 说明双重校验的概念,即客户端和服务端都需要校验(2分);(2) 正确描述客户端校验的作用:提升用户体验、即时反馈(2分);(3) 正确描述客户端校验的局限性:可被绕过、不可信赖(2分);(4) 正确描述服务端校验的作用:安全最后防线、防御注入攻击(2分);(5) 提出核心原则——不信任客户端数据(2分)。",
"explanation": "输入校验是Web安全的基础。OWASP Top 10中多次将注入类漏洞列为第一大安全风险。双重校验体现了纵深防御思想:客户端校验解决易用性问题,服务端校验解决安全性问题。很多初级开发者误以为客户端校验就够了,导致直接通过API发送未经校验的恶意数据,引发SQL注入、存储型XSS等严重漏洞。理解这一原则是安全编程的入门基础。",
"source": null,
"related": []
},
{
"id": "sa-002",
"type": "short_answer",
"difficulty": 2,
"tags": [
"安全编程",
"整数溢出"
],
"question": "解释整数溢出的原理,并给出至少两种防御方法。",
"answer": "整数溢出原理:计算机中整数以固定位数的二进制存储(如32位有符号整数范围为 -2^31 ~ 2^31-1)。当算术运算的结果超出该数据类型能表示的范围时,高位被截断,导致结果「回绕」(wrap around)。例如,32位无符号整数 0xFFFFFFFF + 1 = 0x00000000(回绕为0)。有符号整数溢出还可能导致正数变负数,这在C/C++中属于未定义行为(UB)。\n\n整数溢出在安全上的危害包括:(1) 缓冲区溢出——分配内存时计算出的大小因溢出变小,导致后续写入越界;(2) 绕过边界检查——索引或长度校验因溢出被绕过;(3) 逻辑错误——资金计算、权限判断等场景产生错误结果。\n\n防御方法(至少两种):\n1. 使用安全的整数运算库/内置函数:如 GCC 的 __builtin_add_overflow()、Clang 的 __builtin_*_overflow 系列函数,或 C23 标准的 <stdckdint.h> 中的 ckd_add/ckd_mul 等检查溢出宏。这些工具在发生溢出时返回溢出标志,程序可据此进行错误处理。\n\n2. 运算前进行边界检查(前置校验):在执行加减乘除之前,检查操作数是否会导致溢出。例如,乘法前检查 a > SIZE_MAX / b;加法前检查 a > MAX - b。若会溢出则拒绝操作。\n\n3. 使用更大位宽的数据类型:将计算临时提升到更大的类型(如用64位计算后再检查是否超出32位范围),但这只是权宜之计,不能根本解决问题。\n\n4. 使用支持安全算术的语言或编译选项:如 Rust 默认在 debug 模式下检测溢出,Java 的 Math.addExact() 会在溢出时抛出 ArithmeticException,或使用 -ftrapv 编译选项让C程序在有符号溢出时中止。",
"keywords": [
"整数溢出",
"回绕",
"缓冲区溢出",
"未定义行为",
"边界检查",
"安全算术",
"__builtin_add_overflow",
"前置校验"
],
"scoring_rubric": "满分标准:(1) 正确解释整数溢出原理——固定位数存储、结果超出范围导致回绕或截断(3分);(2) 说明溢出的安全危害,如缓冲区溢出、边界检查绕过等(2分);(3) 给出至少两种有效的防御方法并说明原理(各2分,共4分);(4) 表述准确,术语使用正确(1分)。若仅给出一种防御方法扣2分;若原理描述不完整扣1-2分。",
"explanation": "整数溢出是一种常见但容易被忽视的安全漏洞,尤其在C/C++等系统编程语言中。2014年OpenSSL的Heartbleed漏洞的根因之一就涉及整数溢出。在安全编程中,任何涉及内存分配大小、数组索引、循环计数的整数运算都必须考虑溢出可能。掌握溢出原理和防御方法是编写安全系统软件的基本功。",
"source": null,
"related": []
},
{
"id": "sa-003",
"type": "short_answer",
"difficulty": 2,
"tags": [
"安全编程",
"格式化字符串"
],
"question": "对比 sprintf/snprintf 和 printf/fprintf 在安全性上的差异,说明为什么格式化字符串漏洞是危险的。",
"answer": "函数分类与安全性差异:\n\n1. printf/fprintf 是输出到标准输出/文件流的格式化输出函数。它们的安全风险在于:如果格式字符串(format string)来自用户输入,攻击者可以在格式串中插入 %x(读取栈内容)、%n(向任意地址写入)、%s(读取指针指向的内容)等格式说明符,导致信息泄露或任意内存写入。\n\n2. sprintf/snprintf 是输出到缓冲区的格式化写入函数。除了与 printf 相同的格式化字符串漏洞外,sprintf 还有一个额外风险:没有长度限制,如果格式化后的字符串长度超过目标缓冲区大小,就会发生缓冲区溢出。snprintf 通过限制最大写入字节数(包括 NUL 终止符)来缓解这个问题,是 sprintf 的安全替代品。\n\n安全性对比:\n- sprintf vs snprintf:snprintf 通过 size 参数防止缓冲区溢出,应始终优先使用 snprintf 而非 sprintf。\n- printf 类 vs sprintf 类:printf 类将结果输出到标准流,不会直接造成缓冲区溢出;sprintf 类可能同时面临缓冲区溢出和格式化字符串漏洞的双重风险。\n\n格式化字符串漏洞为什么危险:\n1. 信息泄露:%x、%p 等可以读取栈上的敏感数据(密钥、地址、canary 值)。\n2. 任意地址写入:%n 可以将已输出的字符数写入任意内存地址,攻击者可以精确控制写入的值和地址,从而覆写函数返回地址、GOT 表项等,实现代码执行。\n3. 进程崩溃:%s 对栈上随机值的解引用大概率导致段错误,可造成拒绝服务。\n4. 组合利用:信息泄露 + 任意写入的组合使得这类漏洞几乎总能转化为完整的代码执行利用。\n\n防御方法:始终将格式字符串硬编码为字面量(如 printf(\"%s\", user_input) 而非 printf(user_input)),对 sprintf 使用 snprintf 并正确计算缓冲区大小。",
"keywords": [
"格式化字符串",
"sprintf",
"snprintf",
"printf",
"缓冲区溢出",
"%n",
"任意写入",
"信息泄露",
"GOT覆写"
],
"scoring_rubric": "满分标准:(1) 正确区分 printf/fprintf 和 sprintf/snprintf 的功能差异(2分);(2) 说明 snprintf 相比 sprintf 的安全优势——防止缓冲区溢出(2分);(3) 解释格式化字符串漏洞的原理——用户可控的格式串中的 %x、%n 等(2分);(4) 说明危害性:信息泄露、任意地址写入、可能实现代码执行(3分);(5) 提出正确的防御方法——硬编码格式字符串、使用 snprintf(1分)。若未说明 %n 的写入能力扣2分;若未区分 sprintf/snprintf 扣1分。",
"explanation": "格式化字符串漏洞是经典的C/C++安全问题,常见于将用户输入直接作为 printf 系列函数的第一个参数。虽然现代编译器(如 GCC 的 -Wformat-security)会对此发出警告,但该漏洞在遗留代码中仍然广泛存在。snprintf 是 sprintf 的安全替代,但 snprintf 本身不能防御格式化字符串攻击——它只防止缓冲区溢出。理解这些函数族的安全特性差异对于编写安全的C程序至关重要。",
"source": null,
"related": []
}
]
}
@@ -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": []
}
]
}
@@ -0,0 +1,148 @@
{
"topic": "secure-coding",
"type": "true_false",
"schema_version": "1.0.0",
"generated": "2026-09-17T00:00:00+08:00",
"questions": [
{
"id": "tf-001",
"type": "true_false",
"difficulty": 1,
"tags": [
"安全编程",
"输入校验"
],
"question": "在输入校验中,黑名单校验比白名单校验更安全。",
"answer": false,
"explanation": "白名单校验比黑名单校验更安全。黑名单(拒绝已知的恶意输入)容易遗漏未知攻击向量,攻击者只需找到一个绕过方式即可成功。白名单(只允许已知合法的输入格式)默认拒绝一切未明确允许的输入,安全边界更紧。例如,白名单只允许字母数字字符,就自然阻止了所有注入类攻击,而黑名单很难穷举所有可能的恶意字符组合。",
"source": null,
"related": []
},
{
"id": "tf-002",
"type": "true_false",
"difficulty": 2,
"tags": [
"安全编程",
"格式化字符串"
],
"question": "在C语言中,使用 printf(str) 而非 printf(\"%s\", str) 存在格式化字符串漏洞风险。",
"answer": true,
"explanation": "当 str 由用户控制时,printf(str) 存在严重的格式化字符串漏洞。攻击者可以在 str 中嵌入 %x、%n 等格式说明符:%x 可以泄露栈上的敏感数据(栈信息泄露),%n 可以向任意地址写入数据(任意写)。正确做法是使用 printf(\"%s\", str) 将用户输入作为纯数据输出,而非格式化字符串的一部分。类似的危险函数还包括 fprintf、sprintf、snprintf 等——只要格式化参数来自不可信输入,都存在同类风险。",
"source": null,
"related": []
},
{
"id": "tf-003",
"type": "true_false",
"difficulty": 2,
"tags": [
"安全编程",
"整数溢出"
],
"question": "在C语言中,有符号整数溢出属于未定义行为(Undefined Behavior)。",
"answer": true,
"explanation": "根据C语言标准(C99/C11 §6.5/5),有符号整数溢出是未定义行为(UB),编译器可以做任何假设,包括直接优化掉溢出检查。这与无符号整数不同——无符号整数溢出是定义良好的(模 2^n 回绕)。因此,在安全编程中,对有符号整数做溢出检查时,不能依赖溢出后的值来判断,而应在运算前检测是否会溢出(例如检查 a > INT_MAX - b 再做 a + b)。",
"source": null,
"related": []
},
{
"id": "tf-004",
"type": "true_false",
"difficulty": 2,
"tags": [
"安全编程",
"内存安全"
],
"question": "strncpy 一定比 strcpy 更安全。",
"answer": false,
"explanation": "strncpy 并不总是更安全。strncpy 存在以下陷阱:(1) 如果源字符串长度 >= n,目标缓冲区不会以 null 结尾,后续字符串操作可能越界读取;(2) 如果源字符串远短于 n,strncpy 会用零字节填充剩余空间,造成不必要的性能开销;(3) 开发者仍需手动确保 n 不超过目标缓冲区大小。更安全的替代方案是使用 strlcpy(BSD 扩展)或 snprintf(buf, sizeof(buf), \"%s\", src),它们保证目标始终 null 结尾。",
"source": null,
"related": []
},
{
"id": "tf-005",
"type": "true_false",
"difficulty": 1,
"tags": [
"安全编程",
"输入校验"
],
"question": "使用参数化查询(Prepared Statements)可以有效防止SQL注入攻击。",
"answer": true,
"explanation": "参数化查询通过将SQL语句结构与数据严格分离来防止注入。数据库驱动会将参数值作为纯数据传输,不会被解释为SQL代码。例如,SELECT * FROM users WHERE id = ? 中,即使参数值包含 ' OR 1=1 -- 等注入载荷,数据库也只会将其视为普通字符串进行匹配,而非SQL逻辑。这是防御SQL注入的首选方案,比手动转义或存储过程更可靠。注意:如果动态拼接表名、列名或ORDER BY等SQL结构部分,参数化查询无法覆盖,仍需白名单校验。",
"source": null,
"related": []
},
{
"id": "tf-006",
"type": "true_false",
"difficulty": 1,
"tags": [
"安全编程",
"内存安全"
],
"question": "C11标准已将 gets() 函数标记为废弃(deprecated)并推荐使用更安全的替代方案。",
"answer": true,
"explanation": "gets() 在 C11 标准(ISO/IEC 9899:2011 Annex K)中已被正式移除。gets() 不接受长度参数,无法限制读取的字符数,是经典的缓冲区溢出漏洞来源(例如1988年Morris蠕虫就利用了 gets() 的缺陷)。C11 推荐使用 fgets(buf, size, stdin) 替代,fgets 允许指定最大读取长度,但需注意它会保留换行符。微软的 gets_s() 是另一个安全替代,但它是平台特定扩展,不具有跨平台可移植性。",
"source": null,
"related": []
},
{
"id": "tf-007",
"type": "true_false",
"difficulty": 2,
"tags": [
"安全编程",
"整数溢出"
],
"question": "无符号整数相减永远不会出现安全问题,因为无符号数没有负数。",
"answer": false,
"explanation": "无符号整数相减确实不会产生负数——但当被减数小于减数时,会发生无符号下溢(underflow),结果回绕到一个极大的正数。例如,(unsigned)3 - 5 在32位系统下等于 4294967294(即 2^32 - 2),而非 -2。这在安全场景中非常危险:如果用作内存分配大小、数组索引或循环边界,会导致分配过大的内存、越界访问或无限循环。正确做法是在减法前检查操作数关系,确保被减数 >= 减数。",
"source": null,
"related": []
},
{
"id": "tf-008",
"type": "true_false",
"difficulty": 3,
"tags": [
"安全编程",
"输入校验"
],
"question": "部署内容安全策略(CSP)可以完全阻止所有XSS攻击。",
"answer": false,
"explanation": "CSP是防御XSS的重要辅助手段,但无法完全阻止所有XSS。CSP的局限性包括:(1) 如果页面本身存在内联脚本且无法迁移到外部文件,需要使用 unsafe-inline,这削弱了CSP防护;(2) 已知的DOM型XSS(如通过innerHTML注入事件处理器)在某些CSP配置下仍可利用;(3) 如果CDN或第三方库被攻破,允许的脚本源可能成为攻击载体;(4) CSP无法阻止基于CSS或HTML属性的某些信息泄露攻击。XSS防御应采用纵深策略:输出编码 + CSP + 输入校验 + HttpOnly Cookie + 子资源完整性(SRI)。",
"source": null,
"related": []
},
{
"id": "tf-009",
"type": "true_false",
"difficulty": 2,
"tags": [
"安全编程",
"内存安全"
],
"question": "地址空间布局随机化(ASLR)是一种有效的内存安全防御技术,可以增加攻击者利用内存漏洞的难度。",
"answer": true,
"explanation": "ASLR通过在每次程序运行时随机化关键内存区域(栈、堆、共享库、可执行文件基址)的位置,使攻击者无法预测目标地址,从而大幅增加利用难度。例如,ROP攻击需要知道 gadget 的精确地址,ASLR使这些地址在每次运行时都不同。但ASLR并非万能:(1) 信息泄露漏洞可以泄露内存地址,绕过ASLR;(2) 32位系统的熵不足,暴力破解可能成功;(3) 部分实现(如旧版Windows ASLR)不覆盖所有区域。ASLR应与DEP/NX、Stack Canary、CFI等技术配合使用,构成完整的纵深防御体系。",
"source": null,
"related": []
},
{
"id": "tf-010",
"type": "true_false",
"difficulty": 1,
"tags": [
"安全编程",
"输入校验"
],
"question": "在Web应用中,只要服务端进行了充分的输入校验,客户端就不需要做任何输入校验。",
"answer": false,
"explanation": "服务端校验是安全的最后防线,必须执行,但客户端校验同样有价值:(1) 用户体验——即时反馈比提交后再报错体验更好,减少不必要的网络请求;(2) 服务端负载——客户端预过滤可以减少无效请求对服务端的压力;(3) 辅助防御——增加攻击者篡改请求的成本。但客户端校验绝不能替代服务端校验,因为客户端代码(JavaScript、HTML属性)可被用户完全绕过(禁用JS、修改DOM、直接发送HTTP请求)。正确的做法是:客户端校验用于提升体验,服务端校验用于保障安全,两者并行。",
"source": null,
"related": []
}
]
}