[
  {
    "path": "C,C++安全指南.md",
    "content": "<!-- markdown=\"1\" is required for GitHub Pages to render the TOC properly. -->\n\n<details markdown=\"1\">\n  <summary>目录</summary>\n\n-   [1 通用安全指南](#1)\n    *   [I. C/C++使用错误](#1.1)\n\t\t+   [1.1 不得直接使用无长度限制的字符拷贝函数](#1.1.1)\n\t\t+   [1.2 创建进程类的函数的安全规范](#1.1.2)\n\t\t+   [1.3 尽量减少使用 _alloca 和可变长度数组](#1.1.3)\n\t\t+   [1.4 printf系列参数必须对应](#1.1.4)\n\t\t+   [1.5 防止泄露指针（包括%p）的值](#1.1.5)\n\t\t+   [1.6 不应当把用户可修改的字符串作为printf系列函数的“format”参数](#1.1.6)\n\t\t+   [1.7 对数组delete时需要使用delete[]](#1.1.7)\n\t\t+   [1.8 注意隐式符号转换](#1.1.8)\n\t\t+   [1.9 注意八进制问题](#1.1.9)\n    *   [II. 不推荐的编程习惯](#1.2)\n\t\t+   [2.1 switch中应有default](#1.2.1)\n\t\t+   [2.2 不应当在Debug或错误信息中提供过多内容](#1.2.2)\n\t\t+   [2.3 不应该在客户端代码中硬编码对称加密秘钥](#1.2.3)\n\t\t+   [2.4 返回栈上变量的地址](#1.2.4)\n\t\t+   [2.5 有逻辑联系的数组必须仔细检查](#1.2.5)\n\t\t+   [2.6 避免函数的声明和实现不同](#1.2.6)\n\t\t+   [2.7 检查复制粘贴的重复代码](#1.2.7)\n\t\t+   [2.8 左右一致的重复判断/永远为真或假的判断](#1.2.8)\n\t\t+   [2.9 函数每个分支都应有返回值](#1.2.9)\n\t\t+   [2.10 不得使用栈上未初始化的变量](#1.2.10)\n\t\t+   [2.11 不得直接使用刚分配的未初始化的内存（如realloc）](#1.2.11)\n\t\t+   [2.12 校验内存相关函数的返回值](#1.2.12)\n\t\t+   [2.13 不要在if里面赋值](#1.2.13)\n\t\t+   [2.14 确认if里面的按位操作](#1.2.14)\n    *   [III. 多线程](#1.3)\n\t\t+   [3.1 变量应确保线程安全性](#1.3.1) \n\t\t+   [3.2 注意signal handler导致的条件竞争](#1.3.2) \n\t\t+   [3.3 注意Time-of-check Time-of-use条件竞争](#1.3.3)\n    *   [IV. 加密解密](#1.4)\n\t\t+   [4.1 不得明文存储用户密码等敏感数据](#1.4.1) \n\t\t+   [4.2 内存中的用户密码等敏感数据应该安全抹除](#1.4.2) \n\t\t+   [4.3 rand() 类函数应正确初始化](#1.4.3)\n\t\t+   [4.4 在需要高强度安全加密时不应使用弱PRNG函数](#1.4.4)\n\t\t+   [4.5 自己实现的rand范围不应过小](#1.4.5)\n    *   [V. 文件操作](#1.5)\n\t\t+   [5.1 避免路径穿越问题](#1.5.1)\n\t\t+   [5.2 避免相对路径导致的安全问题](#1.5.2)\n\t\t+   [5.3 文件权限控制](#1.5.3)\n    *   [Ⅵ. 内存操作](#1.6)\n\t\t+   [6.1 防止各种越界写](#1.6.1)\n\t\t+   [6.2 防止任意地址写](#1.6.2)\n    *   [Ⅶ. 数字操作](#1.7)\n\t\t+   [7.1 防止整数溢出](#1.7.1)\n\t\t+   [7.2 防止Off-By-One](#1.7.2)\n\t\t+   [7.3 避免大小端错误](#1.7.3)\n\t\t+   [7.4 检查除以零异常](#1.7.4)\n\t\t+   [7.5 防止数字类型的错误强转](#1.7.5)\n\t\t+   [7.6 比较数据大小时加上最小/最大值的校验](#1.7.6)\n    *   [Ⅷ. 指针操作](#1.8)\n\t\t+   [8.1 检查在pointer上使用sizeof](#1.8.1) \n\t\t+   [8.2 检查直接将数组和0比较的代码](#1.8.2) \n\t\t+   [8.3 不应当向指针赋予写死的地址](#1.8.3)\n\t\t+   [8.4 检查空指针](#1.8.4)\n\t\t+   [8.5 释放完后置空指针](#1.8.5)\n\t\t+   [8.6 防止错误的类型转换](#1.8.6)\n\t\t+   [8.7 智能指针使用安全](#1.8.7)\n</details>\n\n<a id=\"1\"></a>\n## 通用安全指南\n\n<a id=\"1.1\"></a>\n### 1 C/C++使用错误\n\n<a id=\"1.1.1\"></a>\n#### 1.1  【必须】不得直接使用无长度限制的字符拷贝函数\n\n不应直接使用legacy的字符串拷贝、输入函数，如strcpy、strcat、sprintf、wcscpy、mbscpy等，这些函数的特征是：可以输出一长串字符串，而不限制长度。如果环境允许，应当使用其_s安全版本替代，或者使用n版本函数（如：snprintf，vsnprintf）。\n\n若使用形如sscanf之类的函数时，在处理字符串输入时应当通过%10s这样的方式来严格限制字符串长度，同时确保字符串末尾有\\0。如果环境允许，应当使用_s安全版本。\n\n但是注意，虽然MSVC 2015时默认引入结尾为0版本的`snprintf`（行为等同于C99定义的`snprintf`）。但更早期的版本中，MSVC的`snprintf`可能是`_snprintf`的宏。而`_snprintf`是不保证\\0结尾的（见本节后半部分）。\n\n```c++\n（MSVC）\nBeginning with the UCRT in Visual Studio 2015 and Windows 10, snprintf is no longer identical to _snprintf. The snprintf function behavior is now C99 standard compliant.\n\n从Visual Studio 2015和Windows 10中的UCRT开始，snprintf不再与_snprintf相同。snprintf函数行为现在符合C99标准。\n\n请参考：https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/snprintf-snprintf-snprintf-l-snwprintf-snwprintf-l?redirectedfrom=MSDN&view=vs-2019\n```\n\n因此，在使用n系列拷贝函数时，要确保正确计算缓冲区长度，同时，如果你不确定是否代码在各个编译器下都能确保末尾有0时，建议可以适当增加1字节输入缓冲区，并将其置为\\0，以保证输出的字符串结尾一定有\\0。\n\n```c++\n// Good\nchar buf[101] = {0};\nsnprintf(buf, sizeof(buf) - 1, \"foobar ...\", ...);\n```\n\n一些需要注意的函数，例如`strncpy`和`_snprintf`是不安全的。 `strncpy`不应当被视为`strcpy`的n系列函数，它只是恰巧与其他n系列函数名字很像而已。`strncpy`在复制时，如果复制的长度超过n，不会在结尾补\\0。\n\n同样，MSVC `_snprintf`系列函数在超过或等于n时也不会以0结尾。如果后续使用非0结尾的字符串，可能泄露相邻的内容或者导致程序崩溃。\n\n```c++\n// Bad\nchar a[4] = {0};\n_snprintf(a, 4, \"%s\", \"AAAA\");\nfoo = strlen(a);\n```\n\n上述代码在MSVC中执行后， a[4] == 'A'，因此字符串未以0结尾。a的内容是\"AAAA\"，调用`strlen(a)`则会越界访问。因此，正确的操作举例如下：\n\n```c++\n// Good\nchar a[4] = {0};\n_snprintf(a, sizeof(a), \"%s\", \"AAAA\");\na[sizeof(a) - 1] = '\\0';\nfoo = strlen(a);\n```\n\n在 C++ 中，强烈建议用 `string`、`vector` 等更高封装层次的基础组件代替原始指针和动态数组，对提高代码的可读性和安全性都有很大的帮助。\n\n关联漏洞:\n\n`中风险-信息泄露`\n\n`低风险-拒绝服务`\n\n`高风险-缓冲区溢出`\n\n<a id=\"1.1.2\"></a>\n#### 1.2  【必须】创建进程类的函数的安全规范\n\nsystem、WinExec、CreateProcess、ShellExecute等启动进程类的函数，需要严格检查其参数。\n\n启动进程需要加上双引号，错误例子：\n\n```c++\n// Bad\nWinExec(\"D:\\\\program files\\\\my folder\\\\foobar.exe\", SW_SHOW);\n```\n\n当存在`D:\\program files\\my.exe`的时候，my.exe会被启动。而foobar.exe不会启动。\n\n```c++\n// Good\nWinExec(\"\\\"D:\\\\program files\\\\my folder\\\\foobar.exe\\\"\", SW_SHOW);\n```\n\n另外，如果启动时从用户输入、环境变量读取组合命令行时，还需要注意是否可能存在命令注入。\n\n```c++\n// Bad\nstd::string cmdline = \"calc \";\ncmdline += user_input;\nsystem(cmdline.c_str());\n```\n\n比如，当用户输入`1+1 && ls`时，执行的实际上是calc 1+1和ls 两个命令，导致命令注入。\n\n需要检查用户输入是否含有非法数据。\n\n```c++\n// Good\nstd::string cmdline = \"ls \";\ncmdline += user_input;\n\nif(cmdline.find_first_not_of(\"1234567890.+-*/e \") == std::string::npos)\n  system(cmdline.c_str());\nelse\n  warning(...);\n```\n\n关联漏洞:\n\n`高风险-代码执行`\n\n`高风险-权限提升`\n\n<a id=\"1.1.3\"></a>\n#### 1.3  【必须】尽量减少使用 _alloca 和可变长度数组\n\n_alloca 和[可变长度数组](https://zh.wikipedia.org/wiki/%E5%8F%AF%E5%8F%98%E9%95%BF%E6%95%B0%E7%BB%84)使用的内存量在编译期间不可知。尤其是在循环中使用时，根据编译器的实现不同，可能会导致：（1）栈溢出，即拒绝服务； （2）缺少栈内存测试的编译器实现可能导致申请到非栈内存，并导致内存损坏。这在栈比较小的程序上，例如IoT设备固件上影响尤为大。对于 C++，可变长度数组也属于非标准扩展，在代码规范中禁止使用。\n\n错误示例：\n\n```c++\n// Bad\nfor (int i = 0; i < 100000; i++) {\n  char* foo = (char *)_alloca(0x10000);\n  ..do something with foo ..;\n}\n\nvoid Foo(int size) {\n  char msg[size]; // 不可控的栈溢出风险！\n}\n```\n\n正确示例：\n\n```c++\n// Good\n// 改用动态分配的堆内存\nfor (int i = 0; i < 100000; i++) {\n  char * foo = (char *)malloc(0x10000);\n  ..do something with foo ..;\n  if (foo_is_no_longer_needed) {\n    free(foo);\n    foo = NULL;\n  }\n}\n\nvoid Foo(int size) {\n  std::string msg(size, '\\0');  // C++\n  char* msg = malloc(size);  // C\n}\n```\n\n关联漏洞:\n\n`低风险-拒绝服务`\n\n`高风险-内存破坏`\n\n<a id=\"1.1.4\"></a>\n#### 1.4  【必须】printf系列参数必须对应\n\n所有printf系列函数，如sprintf，snprintf，vprintf等必须对应控制符号和参数。\n\n错误示例：\n\n```c++\n// Bad\nconst int buf_size = 1000;\nchar buffer_send_to_remote_client[buf_size] = {0};\n\nsnprintf(buffer_send_to_remote_client, buf_size, \"%d: %p\", id, some_string);  // %p 应为 %s\n\nbuffer_send_to_remote_client[buf_size - 1] = '\\0';\nsend_to_remote(buffer_send_to_remote_client);\n```\n\n正确示例：\n\n```c++\n// Good\nconst int buf_size = 1000;\nchar buffer_send_to_remote_client[buf_size] = {0};\n\nsnprintf(buffer_send_to_remote_client, buf_size, \"%d: %s\", id, some_string);\n\nbuffer_send_to_remote_client[buf_size - 1] = '\\0';\nsend_to_remote(buffer_send_to_remote_client);\n```\n\n前者可能会让client的攻击者获取部分服务器的原始指针地址，可以用于破坏ASLR保护。\n\n关联漏洞:\n\n`中风险-信息泄露`\n\n<a id=\"1.1.5\"></a>\n#### 1.5  【必须】防止泄露指针（包括%p）的值\n\n所有printf系列函数，要防止格式化完的字符串泄露程序布局信息。例如，如果将带有%p的字符串泄露给程序，则可能会破坏ASLR的防护效果。使得攻击者更容易攻破程序。\n\n%p的值只应当在程序内使用，而不应当输出到外部或被外部以某种方式获取。\n\n错误示例：\n\n```c++\n// Bad\n// 如果这是暴露给客户的一个API：\nuint64_t GetUniqueObjectId(const Foo* pobject) {\n  return (uint64_t)pobject;\n}\n```\n\n正确示例：\n\n```c++\n// Good\nuint64_t g_object_id = 0;\n\nvoid Foo::Foo() {\n  this->object_id_ = g_object_id++;\n}\n\n// 如果这是暴露给客户的一个API：\nuint64_t GetUniqueObjectId(const Foo* object) {\n  if (object)\n    return object->object_id_;\n  else\n    error(...);\n}\n```\n\n关联漏洞:\n\n`中风险-信息泄露`\n\n<a id=\"1.1.6\"></a>\n#### 1.6  【必须】不应当把用户可修改的字符串作为printf系列函数的“format”参数\n\n如果用户可以控制字符串，则通过 %n %p 等内容，最坏情况下可以直接执行任意恶意代码。\n\n在以下情况尤其需要注意： WIFI名，设备名……\n\n错误：\n\n```c++\nsnprintf(buf, sizeof(buf), wifi_name);\n```\n\n正确：\n\n```c++\nsnprinf(buf, sizeof(buf), \"%s\", wifi_name);\n```\n\n关联漏洞:\n\n`高风险-代码执行`\n\n`高风险-内存破坏`\n\n`中风险-信息泄露`\n\n`低风险-拒绝服务`\n\n<a id=\"1.1.7\"></a>\n#### 1.7 【必须】对数组delete时需要使用delete[]\n\ndelete []操作符用于删除数组。delete操作符用于删除非数组对象。它们分别调用operator delete[]和operator delete。\n\n```c++\n// Bad\nFoo* b = new Foo[5];\ndelete b;  // trigger assert in DEBUG mode\n```\n\n在new[]返回的指针上调用delete将是取决于编译器的未定义行为。代码中存在对未定义行为的依赖是错误的。\n\n```c++\n// Good\nFoo* b = new Foo[5];\ndelete[] b;\n```\n\n在 C++ 代码中，使用 `string`、`vector`、智能指针（比如[std::unique_ptr<T[]>](https://zh.cppreference.com/w/cpp/memory/unique_ptr)）等可以消除绝大多数 `delete[]` 的使用场景，并且代码更清晰。\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n`中风险-逻辑漏洞`\n\n`低风险-内存泄漏`\n\n`低风险-拒绝服务`\n\n<a id=\"1.1.8\"></a>\n#### 1.8【必须】注意隐式符号转换\n\n两个无符号数相减为负数时，结果应当为一个很大的无符号数，但是小于int的无符号数在运算时可能会有预期外的隐式符号转换。\n\n```c++\n// 1\nunsigned char a = 1;\nunsigned char b = 2;\n\nif (a - b < 0)  // a - b = -1 (signed int)\n  a = 6;\nelse\n  a = 8;\n\n// 2\nunsigned char a = 1;\nunsigned short b = 2;\n\nif (a - b < 0)  // a - b = -1 (signed int)\n  a = 6;\nelse\n  a = 8;\n```\n\n上述结果均为a=6\n\n```c++\n// 3\nunsigned int a = 1;\nunsigned short b = 2;\n\nif (a - b < 0)  // a - b = 0xffffffff (unsigned int)\n  a = 6;\nelse\n  a = 8;\n  \n// 4\nunsigned int a = 1;\nunsigned int b = 2;\n\nif (a - b < 0)  // a - b = 0xffffffff (unsigned int)\n  a = 6;\nelse\n  a = 8;\n```\n\n上述结果均为a=8\n\n如果预期为8，则错误代码：\n\n```c++\n// Bad\nunsigned short a = 1;\nunsigned short b = 2;\n\nif (a - b < 0)  // a - b = -1 (signed int)\n  a = 6;\nelse\n  a = 8;\n```\n\n正确代码：\n\n```c++\n// Good\nunsigned short a = 1;\nunsigned short b = 2;\n\nif ((unsigned int)a - (unsigned int)b < 0)  // a - b = 0xffff (unsigned short)\n  a = 6;\nelse\n  a = 8;\n```\n\n关联漏洞:\n\n`中风险-逻辑漏洞`\n\n<a id=\"1.1.9\"></a>\n#### 1.9【必须】注意八进制问题\n\n代码对齐时应当使用空格或者编辑器自带的对齐功能，谨慎在数字前使用0来对齐代码，以免不当将某些内容转换为八进制。\n\n例如，如果预期为20字节长度的缓冲区，则下列代码存在错误。buf2为020（OCT）长度，实际只有16（DEC）长度，在memcpy后越界：\n\n```c++\n// Bad\nchar buf1[1024] = {0};\nchar buf2[0020] = {0};\n\nmemcpy(buf2, somebuf, 19);\n```\n\n应当在使用8进制时明确注明这是八进制。\n\n```c++\n// Good\nint access_mask = 0777;  // oct, rwxrwxrwx\n```\n\n关联漏洞:\n\n`中风险-逻辑漏洞`\n\n<a id=\"1.2\"></a>\n### 2 不推荐的编程习惯\n\n<a id=\"1.2.1\"></a>\n#### 2.1 【必须】switch中应有default\n\nswitch中应该有default，以处理各种预期外的情况。这可以确保switch接受用户输入，或者后期在其他开发者修改函数后确保switch仍可以覆盖到所有情况，并确保逻辑正常运行。\n\n```c++\n// Bad\nint Foo(int bar) {\n  switch (bar & 7) {\n    case 0:\n      return Foobar(bar);\n      break;\n    case 1:\n      return Foobar(bar * 2);\n      break;\n  }\n}\n```\n\n例如上述代码switch的取值可能从0～7，所以应当有default：\n\n```c++\n// Good\nint Foo(int bar) {\n  switch (bar & 7) {\n    case 0:\n      return Foobar(bar);\n      break;\n    case 1:\n      return Foobar(bar * 2);\n      break;\n    default:\n      return -1;\n  }\n}\n```\n\n关联漏洞:\n\n`中风险-逻辑漏洞`\n\n`中风险-内存泄漏`\n\n<a id=\"1.2.2\"></a>\n#### 2.2 【必须】不应当在Debug或错误信息中提供过多内容\n\n包含过多信息的Debug消息不应当被用户获取到。Debug信息可能会泄露一些值，例如内存数据、内存地址等内容，这些内容可以帮助攻击者在初步控制程序后，更容易地攻击程序。\n\n```c++\n// Bad\nint Foo(int* bar) {\n  if (bar && *bar == 5) {\n    OutputDebugInfoToUser(\"Wrong value for bar %p = %d\\n\", bar, *bar);\n  }\n}\n```\n\n而应该：\n\n```c++\n// Good\nint foo(int* bar) {\n\n#ifdef DEBUG\n  if (bar && *bar == 5) {\n    OutputDebugInfo(\"Wrong value for bar.\\n\");\n  }\n#endif\n\n}\n```\n\n关联漏洞:\n\n`中风险-信息泄漏`\n\n<a id=\"1.2.3\"></a>\n#### 2.3 【必须】不应该在客户端代码中硬编码对称加密秘钥\n\n不应该在客户端代码中硬编码对称加密秘钥。例如：不应在客户端代码使用硬编码的 AES/ChaCha20-Poly1305/SM1 密钥，使用固定密钥的程序基本和没有加密一样。\n\n如果业务需求是认证加密数据传输，应优先考虑直接用 HTTPS 协议。\n\n如果是其它业务需求，可考虑由服务器端生成对称秘钥，客户端通过 HTTPS 等认证加密通信渠道从服务器拉取。\n\n或者根据用户特定的会话信息，比如登录认证过程可以根据用户名用户密码业务上下文等信息，使用 HKDF 等算法衍生出对称秘钥。\n\n又或者使用 RSA/ECDSA + ECDHE 等进行认证秘钥协商，生成对称秘钥。\n\n\n```c++\n// Bad\nchar g_aes_key[] = {...};\n\nvoid Foo() {\n  ....\n  AES_func(g_aes_key, input_data, output_data);\n}\n```\n\n可以考虑在线为每个用户获取不同的密钥：\n\n```c++\n// Good\nchar* g_aes_key;\n\nvoid Foo() {\n  ....\n  AES_encrypt(g_aes_key, input_data, output_data);\n}\n\nvoid Init() {\n  g_aes_key = get_key_from_https(user_id, ...);\n}\n```\n\n关联漏洞:\n\n`中风险-信息泄露`\n\n<a id=\"1.2.4\"></a>\n#### 2.4 【必须】返回栈上变量的地址\n\n函数不可以返回栈上的变量的地址，其内容在函数返回后就会失效。\n\n```c++\n// Bad\nchar* Foo(char* sz, int len){\n  char a[300] = {0};\n  if (len > 100) {\n    memcpy(a, sz, 100);\n  }\n  a[len] = '\\0';\n  return a;  // WRONG\n}\n```\n\n而应当使用堆来传递非简单类型变量。\n\n```c++\n// Good\nchar* Foo(char* sz, int len) {\n    char* a = new char[300];\n    if (len > 100) {\n        memcpy(a, sz, 100);\n    }\n    a[len] = '\\0';\n    return a;  // OK\n}\n```\n\n对于 C++ 程序来说，强烈建议返回 `string`、`vector` 等类型，会让代码更加简单和安全。\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n<a id=\"1.2.5\"></a>\n#### 2.5 【必须】有逻辑联系的数组必须仔细检查\n\n例如下列程序将字符串转换为week day，但是两个数组并不一样长，导致程序可能会越界读一个int。\n\n```c++\n// Bad\nint nWeekdays[] = {1, 2, 3, 4, 5, 6};\nconst char* sWeekdays[] = {\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"};\nfor (int x = 0; x < ARRAY_SIZE(sWeekdays); x++) {\n  if (strcmp(sWeekdays[x], input) == 0)\n    return nWeekdays[x];\n}\n```\n\n应当确保有关联的nWeekdays和sWeekdays数据统一。\n\n```c++\n// Good\nconst int nWeekdays[] = {1, 2, 3, 4, 5, 6, 7};\nconst char* sWeekdays[] = {\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"};\nassert(ARRAY_SIZE(nWeekdays) == ARRAY_SIZE(sWeekdays));\nfor (int x = 0; x < ARRAY_SIZE(sWeekdays); x++) {\n  if (strcmp(sWeekdays[x], input) == 0) {\n    return nWeekdays[x];\n  }\n}\n```\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n<a id=\"1.2.6\"></a>\n#### 2.6 【必须】避免函数的声明和实现不同\n\n在头文件、源代码、文档中列举的函数声明应当一致，不应当出现定义内容错位的情况。\n\n错误：\n\nfoo.h\n\n```c++\nint CalcArea(int width, int height);\n```\n\nfoo.cc\n\n```c++\nint CalcArea(int height, int width) {  // Different from foo.h\n  if (height > real_height) {\n    return 0;\n  }\n  return height * width;\n}\n```\n\n正确：\nfoo.h\n\n```c++\nint CalcArea(int height, int width);\n```\n\nfoo.cc\n\n```c++\nint CalcArea (int height, int width) {\n  if (height > real_height) {\n    return 0;\n  }\n  return height * width;\n}\n```\n\n关联漏洞:\n\n`中风险-逻辑问题`\n\n<a id=\"1.2.7\"></a>\n#### 2.7 【必须】检查复制粘贴的重复代码（相同代码通常代表错误）\n\n当开发中遇到较长的句子时，如果你选择了复制粘贴语句，请记得检查每一行代码，不要出现上下两句一模一样的情况，这通常代表代码哪里出现了错误：\n\n```c++\n// Bad\nvoid Foobar(SomeStruct& foobase, SomeStruct& foo1, SomeStruct& foo2) {\n  foo1.bar = (foo1.bar & 0xffff) | (foobase.base & 0xffff0000);\n  foo1.bar = (foo1.bar & 0xffff) | (foobase.base & 0xffff0000);\n}\n```\n\n如上例，通常可能是：\n\n```c++\n// Good\nvoid Foobar(SomeStruct& foobase, SomeStruct& foo1, SomeStruct& foo2) {\n  foo1.bar = (foo1.bar & 0xffff) | (foobase.base & 0xffff0000);\n  foo2.bar = (foo2.bar & 0xffff) | (foobase.base & 0xffff0000);\n}\n\n```\n\n最好是把重复的代码片段提取成函数，如果函数比较短，可以考虑定义为 `inline` 函数，在减少冗余的同时也能确保不会影响性能。\n\n关联漏洞:\n\n`中风险-逻辑问题`\n\n<a id=\"1.2.8\"></a>\n#### 2.8  【必须】左右一致的重复判断/永远为真或假的判断（通常代表错误）\n\n这通常是由于自动完成或例如Visual Assistant X之类的补全插件导致的问题。\n\n```c++\n// Bad\nif (foo1.bar == foo1.bar) {\n  …\n}\n```\n\n可能是：\n\n```c++\n// Good\nif (foo1.bar == foo2.bar) {\n  …\n}\n```\n\n关联漏洞:\n\n`中风险-逻辑问题`\n\n<a id=\"1.2.9\"></a>\n#### 2.9 【必须】函数每个分支都应有返回值\n\n函数的每个分支都应该有返回值，否则如果函数走到无返回值的分支，其结果是未知的。\n\n```c++\n// Bad\nint Foo(int bar) {\n  if (bar > 100) {\n    return 10;\n  } else if (bar > 10) {\n    return 1;\n  }\n}\n```\n\n上述例子当bar<10时，其结果是未知的值。\n\n```c++\n// Good\nint Foo(int bar) {\n  if (bar > 100) {\n    return 10;\n  } else if (bar > 10) {\n    return 1;\n  }\n  return 0;\n}\n```\n\n开启适当级别的警告（GCC 中为 `-Wreturn-type` 并已包含在 `-Wall` 中）并设置为错误，可以在编译阶段发现这类错误。\n\n关联漏洞:\n\n`中风险-逻辑问题`\n\n`中风险-信息泄漏`\n\n<a id=\"1.2.10\"></a>\n#### 2.10 【必须】不得使用栈上未初始化的变量\n\n在栈上声明的变量要注意是否在使用它之前已经初始化了\n\n```c++\n// Bad\nvoid Foo() {\n  int foo;\n  if (Bar()) {\n    foo = 1;\n  }\n  Foobar(foo); // foo可能没有初始化\n}\n```\n\n最好在声明的时候就立刻初始化变量，或者确保每个分支都初始化它。开启相应的编译器警告（GCC 中为 `-Wuninitialized`），并把设置为错误级别，可以在编译阶段发现这类错误。\n\n```c++\n// Good\nvoid Foo() {\n  int foo = 0;\n  if (Bar()) {\n    foo = 1;\n  }\n  Foobar(foo);\n}\n```\n\n关联漏洞:\n\n`中风险-逻辑问题`\n\n`中风险-信息泄漏`\n\n<a id=\"1.2.11\"></a>\n#### 2.11  【建议】不得直接使用刚分配的未初始化的内存（如realloc）\n\n一些刚申请的内存通常是直接从堆上分配的，可能包含有旧数据的，直接使用它们而不初始化，可能会导致安全问题。例如，CVE-2019-13751。应确保初始化变量，或者确保未初始化的值不会泄露给用户。\n\n```c++\n// Bad\nchar* Foo() {\n  char* a = new char[100];\n  a[99] = '\\0';\n  memcpy(a, \"char\", 4);\n  return a;\n}\n```\n\n```c++\n// Good\nchar* Foo() {\n  char* a = new char[100];\n  memcpy(a, \"char\", 4);\n  a[4] = '\\0';\n  return a;\n}\n```\n\n在 C++ 中，再次强烈推荐用 `string`、`vector` 代替手动内存分配。\n\n关联漏洞:\n\n`中风险-逻辑问题`\n\n`中风险-信息泄漏`\n\n<a id=\"1.2.12\"></a>\n#### 2.12 【必须】校验内存相关函数的返回值\n\n与内存分配相关的函数需要检查其返回值是否正确，以防导致程序崩溃或逻辑错误。\n\n```c++\n// Bad\nvoid Foo() {\n  char* bar = mmap(0, 0x800000, .....);\n  *(bar + 0x400000) = '\\x88'; // Wrong\n}\n\n```\n\n如上例mmap如果失败，bar的值将是0xffffffff (ffffffff)，第二行将会往0x3ffffff写入字符，导致越界写。\n\n```c++\n// Good\nvoid Foo() {\n  char* bar = mmap(0, 0x800000, .....);\n  if(bar == MAP_FAILED) {\n    return;\n  }\n\n  *(bar + 0x400000) = '\\x88';\n}\n```\n\n关联漏洞:\n\n`中风险-逻辑问题`\n\n`高风险-越界操作`\n\n<a id=\"1.2.13\"></a>\n#### 2.13 【必须】不要在if里面赋值\n\nif里赋值通常代表代码存在错误。\n\n```c++\n// Bad\nvoid Foo() {\n  if (bar = 0x99) ...\n}\n```\n\n通常应该是：\n\n```c++\n// Good\nvoid Foo() {\n  if (bar == 0x99) ...\n}\n```\n\n建议在构建系统中开启足够的编译器警告（GCC 中为 `-Wparentheses` 并已包含在 `-Wall` 中），并把该警告设置为错误。\n\n关联漏洞:\n\n`中风险-逻辑问题`\n\n<a id=\"1.2.14\"></a>\n#### 2.14 【建议】确认if里面的按位操作\n\nif里，非bool类型和非bool类型的按位操作可能代表代码存在错误。\n\n```c++\n// Bad\nvoid Foo() {\n  int bar = 0x1;     // binary 01\n  int foobar = 0x2;    // binary 10\n\n  if (foobar & bar)     // result = 00, false\n    ...\n}\n```\n\n上述代码可能应该是：\n\n```c++\n// Good\nvoid foo() {\n  int   bar = 0x1;\n  int foobar = 0x2;\n\n  if (foobar && bar)  // result : true\n    ...\n}\n```\n\n关联漏洞:\n\n`中风险-逻辑问题`\n\n<a id=\"1.3\"></a>\n### 3    多线程\n\n<a id=\"1.3.1\"></a>\n#### 3.1  【必须】变量应确保线程安全性\n\n当一个变量可能被多个线程使用时，应当使用原子操作或加锁操作。\n\n```c++\n// Bad\nchar  g_somechar;\nvoid foo_thread1() {\n  g_somechar += 3;\n}\n\nvoid foo_thread2() {\n  g_somechar += 1;\n}\n```\n\n对于可以使用原子操作的，应当使用一些可以确保内存安全的操作，如：\n\n```c++\n// Good\nvolatile char g_somechar;\nvoid foo_thread1() {\n  __sync_fetch_and_add(&g_somechar, 3);\n}\n\nvoid foo_thread2() {\n  __sync_fetch_and_add(&g_somechar, 1);\n}\n```\n\n对于 C 代码，`C11` 后推荐使用 [atomic](https://en.cppreference.com/w/c/atomic) 标准库。\n对于 C++代码，`C++11` 后，推荐使用 [`std::atomic`](https://zh.cppreference.com/w/cpp/atomic/atomic)。\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n`中风险-逻辑问题`\n\n<a id=\"1.3.2\"></a>\n#### 3.2 【必须】注意signal handler导致的条件竞争\n\n竞争条件经常出现在信号处理程序中，因为信号处理程序支持异步操作。攻击者能够利用信号处理程序争用条件导致软件状态损坏，从而可能导致拒绝服务甚至代码执行。\n\n1. 当信号处理程序中发生不可重入函数或状态敏感操作时，就会出现这些问题。因为信号处理程序中随时可以被调用。比如，当在信号处理程序中调用`free`时，通常会出现另一个信号争用条件，从而导致双重释放。即使给定指针在释放后设置为`NULL`，在释放内存和将指针设置为`NULL`之间仍然存在竞争的可能。\n2. 为多个信号设置了相同的信号处理程序，这尤其有问题——因为这意味着信号处理程序本身可能会重新进入。例如，malloc()和free()是不可重入的，因为它们可能使用全局或静态数据结构来管理内存，并且它们被syslog()等看似无害的函数间接使用；这些函数可能会导致内存损坏和代码执行。\n\n```c++\n// Bad\nchar *log_message;\n\nvoid Handler(int signum) {\n  syslog(LOG_NOTICE, \"%s\\n\", log_m_essage);\n  free(log_message);\n  sleep(10);\n  exit(0);\n}\n\nint main (int argc, char* argv[]) {\n  log_message = strdup(argv[1]);\n  signal(SIGHUP, Handler);\n  signal(SIGTERM, Handler);\n  sleep(10);\n}\n```\n\n可以借由下列操作规避问题：\n\n1. 避免在多个处理函数中共享某些变量。\n2. 在信号处理程序中使用同步操作。\n3. 屏蔽不相关的信号，从而提供原子性。\n4. 避免在信号处理函数中调用不满足[异步信号安全](https://www.man7.org/linux/man-pages/man7/signal-safety.7.html)的函数。\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n`中风险-逻辑问题`\n\n<a id=\"1.3.3\"></a>\n#### 3.3 【建议】注意Time-of-check Time-of-use (TOCTOU) 条件竞争\n\nTOCTOU： 软件在使用某个资源之前检查该资源的状态，但是该资源的状态可以在检查和使用之间更改，从而使检查结果无效。当资源处于这种意外状态时，这可能会导致软件执行错误操作。\n\n当攻击者可以影响检查和使用之间的资源状态时，此问题可能与安全相关。这可能发生在共享资源(如**文件、内存**，甚至多线程程序中的**变量**)上。在编程时需要注意避免出现TOCTOU问题。\n\n例如，下面的例子中，该文件可能已经在检查和lstat之间进行了更新，特别是因为printf有延迟。\n\n```c++\nstruct stat *st;\n\nlstat(\"...\", st);\n\nprintf(\"foo\");\n\nif (st->st_mtimespec == ...) {\n  printf(\"Now updating things\\n\");\n  UpdateThings();\n}\n```\n\nTOCTOU难以修复，但是有以下缓解方案：\n\n1. 限制对来自多个进程的文件的交叉操作。\n2. 如果必须在多个进程或线程之间共享对资源的访问，那么请尝试限制”检查“（CHECK）和”使用“（USE）资源之间的时间量，使他们相距尽量不要太远。这不会从根本上解决问题，但可能会使攻击更难成功。\n3. 在Use调用之后重新检查资源，以验证是否正确执行了操作。\n4. 确保一些环境锁定机制能够被用来有效保护资源。但要确保锁定是检查之前进行的，而不是在检查之后进行的，以便检查时的资源与使用时的资源相同。\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n`中风险-逻辑问题`\n\n<a id=\"1.4\"></a>\n### 4    加密解密\n\n<a id=\"1.4.1\"></a>\n#### 4.1  【必须】不得明文存储用户密码等敏感数据\n\n用户密码应该使用 Argon2, scrypt, bcrypt, pbkdf2 等算法做哈希之后再存入存储系统, https://password-hashing.net/\n\nhttps://libsodium.gitbook.io/doc/password_hashing/default_phf#example-2-password-storage\n\n\n用户敏感数据，应该做到传输过程中加密，存储状态下加密\n传输过程中加密，可以使用 HTTPS 等认证加密通信协议\n\n存储状态下加密，可以使用 SQLCipher 等类似方案。\n\n<a id=\"1.4.2\"></a>\n#### 4.2  【必须】内存中的用户密码等敏感数据应该安全抹除\n\n例如用户密码等，即使是临时使用，也应在使用完成后应当将内容彻底清空。\n\n错误：\n\n```c++\n#include <openssl/crypto.h>\n#include <unistd.h>\n\n    {\n        ...\n        string user_password(100, '\\0');\n        snprintf(&user_password, \"password: %s\", user_password.size(), password_from_input);\n        ...\n    }\n```\n\n正确：\n\n```c++\n    {\n        ...\n        string user_password(100, '\\0');\n        snprintf(&user_password, \"password: %s\", user_password.size(), password_from_input);\n        ...\n        OPENSSL_cleanse(&user_password[0], user_password.size());\n    }\n\n```\n\n关联漏洞:\n\n`高风险-敏感信息泄露`\n\n<a id=\"1.4.3\"></a>\n#### 4.3  【必须】rand() 类函数应正确初始化\n\nrand类函数的随机性并不高。而且在使用前需要使用srand()来初始化。未初始化的随机数可能导致某些内容可预测。\n\n```c++\n// Bad\nint main() {\n  int foo = rand();\n  return 0;\n}\n```\n\n上述代码执行完成后，foo的值是固定的。它等效于 `srand(1); rand();`。\n\n```c++\n// Good\n\nint main() {\n  srand(time(0));\n  int foo = rand();\n  return 0;\n}\n```\n\n关联漏洞:\n\n`高风险-逻辑漏洞`\n\n<a id=\"1.4.4\"></a>\n#### 4.4  【必须】在需要高强度安全加密时不应使用弱PRNG函数\n\n在需要生成 AES/SM1/HMAC 等算法的密钥/IV/Nonce， RSA/ECDSA/ECDH 等算法的私钥，这类需要高安全性的业务场景，必须使用密码学安全的随机数生成器 (Cryptographically Secure PseudoRandom Number Generator (CSPRNG) ), 不得使用 `rand()` 等无密码学安全性保证的普通随机数生成器。\n\n推荐使用的 CSPRNG 有：\n1. OpenSSL 中的 `RAND_bytes()` 函数, https://www.openssl.org/docs/man1.1.1/man3/RAND_bytes.html\n\n1. libsodium 中的 `randombytes_buf()` 函数\n\n1. Linux kernel 的 `getrandom()` 系统调用, https://man7.org/linux/man-pages/man2/getrandom.2.html .\n    或者读 /dev/urandom 文件, 或者 /dev/random 文件。\n\n1. Apple IOS 的 `SecRandomCopyBytes()`, https://developer.apple.com/documentation/security/1399291-secrandomcopybytes\n\n1. Windows 下的 `BCryptGenRandom()`, `CryptGenRandom()`, `RtlGenRandom()`\n\n\n```c++\n#include <openssl/aes.h>\n#include <openssl/crypto.h>\n#include <openssl/rand.h>\n#include <unistd.h>\n\n    {\n        unsigned char key[16];\n        if (1 != RAND_bytes(&key[0], sizeof(key))) {  //... 错误处理\n            return -1;\n        }\n\n        AES_KEY aes_key;\n        if (0 != AES_set_encrypt_key(&key[0], sizeof(key) * 8, &aes_key)) {\n            // ... 错误处理\n            return -1;\n        }\n\n        ...\n\n        OPENSSL_cleanse(&key[0], sizeof(key));\n    }\n\n```\n\n\n`rand()`类函数的随机性并不高。敏感操作时，如设计加密算法时，不得使用rand()或者类似的简单线性同余伪随机数生成器来作为随机数发生器。符合该定义的比特序列的特点是，序列中“1”的数量约等于“0”的数量；同理，“01”、“00”、“10”、“11”的数量大致相同，以此类推。\n\n例如 C 标准库中的 `rand()` 的实现只是简单的[线性同余算法](https://sourceware.org/git/?p=glibc.git;a=blob;f=stdlib/random_r.c;hb=glibc-2.28#l353)，生成的伪随机数具有较强的可预测性。\n\n当需要实现高强度加密，例如涉及通信安全时，不应当使用 `rand()` 作为随机数发生器。\n\n实际应用中，[ C++11 标准提供的`random_device`保证加密的安全性和随机性](https://docs.microsoft.com/en-us/cpp/standard-library/random-device-class?redirectedfrom=MSDN&view=vs-2019#remarks)\n但是 [C++ 标准并不保证这一点](https://stackoverflow.com/questions/44867500/is-stdrandom-device-cryptographic-secure)。跨平台的代码可以考虑用 [OpenSSL](https://wiki.openssl.org/index.php/Random_Numbers) 等保证密码学安全的库里的随机数发生器。\n\n\n\n关联漏洞:\n\n`高风险-敏感数据泄露`\n\n<a id=\"1.4.5\"></a>\n#### 4.5  【必须】自己实现的rand范围不应过小\n\n如果在弱安全场景相关的算法中自己实现了PRNG，请确保rand出来的随机数不会很小或可预测。\n\n```c++\n// Bad\nint32_t val = ((state[0] * 1103515245U) + 12345U) & 999999;\n```\n\n上述例子可能想生成0~999999共100万种可能的随机数，但是999999的二进制是11110100001000111111，与&运算后，0位一直是0，所以生成出的范围明显会小于100万种。\n\n```c++\n// Good\nint32_t val = ((state[0] * 1103515245U) + 12345U) % 1000000;\n\n// Good\nint32_t val = ((state[0] * 1103515245U) + 12345U) & 0x7fffffff;\n```\n\n关联漏洞:\n\n`高风险-逻辑漏洞`\n\n<a id=\"1.5\"></a>\n### 5    文件操作\n\n<a id=\"1.5.1\"></a>\n#### 5.1  【必须】避免路径穿越问题\n\n在进行文件操作时，需要判断外部传入的文件名是否合法，如果文件名中包含 `../` 等特殊字符，则会造成路径穿越，导致任意文件的读写。\n\n错误：\n\n```c++\nvoid Foo() {\n  char file_path[PATH_MAX] = \"/home/user/code/\";\n  // 如果传入的文件名包含../可导致路径穿越\n  // 例如\"../file.txt\"，则可以读取到上层目录的file.txt文件\n  char name[20] = \"../file.txt\";\n  memcpy(file_path + strlen(file_path), name, sizeof(name));\n  int fd = open(file_path, O_RDONLY);\n  if (fd != -1) {\n    char data[100] = {0};\n    int num = 0;\n    memset(data, 0, sizeof(data));\n    num = read(fd, data, sizeof(data));\n    if (num > 0) {\n      write(STDOUT_FILENO, data, num);\n    }\n    close(fd);\n  }\n}\n```\n\n正确：\n\n```c++\nvoid Foo() {\n  char file_path[PATH_MAX] = \"/home/user/code/\";\n  char name[20] = \"../file.txt\";\n  // 判断传入的文件名是否非法，例如\"../file.txt\"中包含非法字符../，直接返回\n  if (strstr(name, \"..\") != NULL){\n    // 包含非法字符\n    return;\n  }\n  memcpy(file_path + strlen(file_path), name, sizeof(name));\n  int fd = open(file_path, O_RDONLY);\n  if (fd != -1) {\n    char data[100] = {0};\n    int num = 0;\n    memset(data, 0, sizeof(data));\n    num = read(fd, data, sizeof(data));\n    if (num > 0) {\n      write(STDOUT_FILENO, data, num);\n    }\n    close(fd);\n   }\n}\n```\n\n关联漏洞:\n\n`高风险-逻辑漏洞`  \n\n<a id=\"1.5.2\"></a>\n#### 5.2  【必须】避免相对路径导致的安全问题（DLL、EXE劫持等问题）\n\n在程序中，使用相对路径可能导致一些安全风险，例如DLL、EXE劫持等问题。\n\n例如以下代码，可能存在劫持问题：\n\n```c++\nint Foo() {\n  // 传入的是dll文件名，如果当前目录下被写入了恶意的同名dll，则可能导致dll劫持\n  HINSTANCE hinst = ::LoadLibrary(\"dll_nolib.dll\");\n  if (hinst != NULL) {\n    cout<<\"dll loaded!\" << endl;\n  }\n  return 0;\n}\n```\n\n针对DLL劫持的安全编码的规范：\n\n   1）调用LoadLibrary，LoadLibraryEx，CreateProcess，ShellExecute等进行模块加载的函数时，指明模块的完整（全）路径，禁止使用相对路径，这样就可避免从其它目录加载DLL。\n   2）在应用程序的开头调用SetDllDirectory(TEXT(\"\")); 从而将当前目录从DLL的搜索列表中删除。结合SetDefaultDllDirectories，AddDllDirectory，RemoveDllDirectory这几个API配合使用，可以有效的规避DLL劫持问题。这些API只能在打了KB2533623补丁的Windows7，2008上使用。\n\n关联漏洞:\n\n`中风险-逻辑漏洞`\n\n<a id=\"1.5.3\"></a>\n#### 5.3  【必须】文件权限控制\n\n在创建文件时，需要根据文件的敏感级别设置不同的访问权限，以防止敏感数据被其他恶意程序读取或写入。\n\n错误：\n\n```c++\nint Foo() {\n  // 不要设置为777权限，以防止被其他恶意程序操作\n  if (creat(\"file.txt\", 0777) < 0) {\n    printf(\"文件创建失败！\\n\");\n  } else {\n    printf(\"文件创建成功！\\n\");\n  }\n  return 0;\n}\n```\n\n关联漏洞:\n\n `中风险-逻辑漏洞`  \n\n<a id=\"1.6\"></a>\n### 6 内存操作\n\n<a id=\"1.6.1\"></a>\n#### 6.1 【必须】防止各种越界写（向前/向后）\n\n错误1：\n\n```c++\nint a[5];\na[5] = 0;\n```\n\n错误2：\n\n```c++\nint a[5];\nint b = user_controlled_value;\na[b] = 3;\n```\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n<a id=\"1.6.2\"></a>\n#### 6.2 【必须】防止任意地址写\n\n任意地址写会导致严重的安全隐患，可能导致代码执行。因此，在编码时必须校验写入的地址。\n\n错误：\n\n```c++\nvoid Write(MyStruct dst_struct) {\n  char payload[10] = { 0 };\n  memcpy(dst_struct.buf, payload, sizeof(payload));\n}\n\nint main() {\n  MyStruct dst_stuct;\n  dst_stuct.buf = (char*)user_controlled_value;\n  Write(dst_stuct);\n  return 0;\n}\n```\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n<a id=\"1.7\"></a>\n### 7 数字操作\n\n<a id=\"1.7.1\"></a>\n#### 7.1 【必须】防止整数溢出\n\n在计算时需要考虑整数溢出的可能，尤其在进行内存操作时，需要对分配、拷贝等大小进行合法校验，防止整数溢出导致的漏洞。\n\n错误（该例子在计算时产生整数溢出）\n\n```c++\nconst int kMicLen = 4;\n// 整数溢出\nvoid Foo() {\n  int len = 1;\n  char payload[10] = { 0 };\n  char dst[10] = { 0 };\n  // Bad, 由于len小于4，导致计算拷贝长度时，整数溢出\n  // len - kMicLen == 0xfffffffd\n  memcpy(dst, payload, len - kMicLen);\n}\n```\n\n正确例子\n\n```c++\nvoid Foo() {\n  int len = 1;\n  char payload[10] = { 0 };\n  char dst[10] = { 0 };\n  int size = len - kMicLen;\n  // 拷贝前对长度进行判断\n  if (size > 0 && size < 10) {\n    memcpy(dst, payload, size);\n    printf(\"memcpy good\\n\");\n  }\n}\n```\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n<a id=\"1.7.2\"></a>\n#### 7.2 【必须】防止Off-By-One\n\n在进行计算或者操作时，如果使用的最大值或最小值不正确，使得该值比正确值多1或少1，可能导致安全风险。\n\n错误：\n\n```c++\nchar firstname[20];\nchar lastname[20];\nchar fullname[40];\n\nfullname[0] = '\\0';\n\nstrncat(fullname, firstname, 20);\n// 第二次调用strncat()可能会追加另外20个字符。如果这20个字符没有终止空字符，则存在安全问题\nstrncat(fullname, lastname, 20);\n```\n\n正确：\n\n```c++\nchar firstname[20];\nchar lastname[20];\nchar fullname[40];\n\nfullname[0] = '\\0';\n\n// 当使用像strncat()函数时，必须在缓冲区的末尾为终止空字符留下一个空字节，避免off-by-one\nstrncat(fullname, firstname, sizeof(fullname) - strlen(fullname) - 1);\nstrncat(fullname, lastname, sizeof(fullname) - strlen(fullname) - 1);\n```\n\n对于 C++ 代码，再次强烈建议使用 `string`、`vector` 等组件代替原始指针和数组操作。\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n<a id=\"1.7.3\"></a>\n#### 7.3 【必须】避免大小端错误\n\n在一些涉及大小端数据处理的场景，需要进行大小端判断，例如从大端设备取出的值，要以大端进行处理，避免端序错误使用。\n\n关联漏洞:\n\n`中风险-逻辑漏洞`\n\n<a id=\"1.7.4\"></a>\n#### 7.4 【必须】检查除以零异常\n\n在进行除法运算时，需要判断被除数是否为零，以防导致程序不符合预期或者崩溃。\n\n错误：\n\n```c++\nint divide(int x, int y) {\n  return x / y;\n}\n```\n\n正确：\n\n```c++\nint divide(int x, int y) {\n  if (y == 0) {\n    throw DivideByZero;\n  }\n  return x / y;\n}\n```\n\n关联漏洞:\n\n`低风险-拒绝服务`\n\n<a id=\"1.7.5\"></a>\n#### 7.5 【必须】防止数字类型的错误强转\n\n在有符号和无符号数字参与的运算中，需要注意类型强转可能导致的逻辑错误，建议指定参与计算时数字的类型或者统一类型参与计算。\n\n错误例子\n\n```c++\nint Foo() {\n  int len = 1;\n  unsigned int size = 9;\n  // 1 < 9 - 10 ? 由于运算中无符号和有符号混用，导致计算结果以无符号计算\n  if (len < size - 10) {\n    printf(\"Bad\\n\");\n  } else {\n    printf(\"Good\\n\");\n  }\n}\n```\n\n正确例子\n\n```c++\nvoid Foo() {\n  // 统一两者计算类型为有符号\n  int len = 1;\n  int size = 9;\n  if (len < size - 10) {\n    printf(\"Bad\\n\");\n  } else {\n    printf(\"Good\\n\");\n  }\n}\n```\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n`中风险-逻辑漏洞`\n\n<a id=\"1.7.6\"></a>\n#### 7.6 【必须】比较数据大小时加上最小/最大值的校验\n\n在进行数据大小比较时，要合理地校验数据的区间范围，建议根据数字类型，对其进行最大和最小值的判断，以防止非预期错误。\n\n错误：\n\n```c++\nvoid Foo(int index) {\n  int a[30] = {0};\n  // 此处index是int型，只考虑了index小于数组大小，但是并未判断是否大于等于0\n  if (index < 30) {\n    // 如果index为负数，则越界\n    a[index] = 1;\n  }\n}\n```\n\n正确：\n\n```c++\nvoid Foo(int index) {\n  int a[30] = {0};\n  // 判断index的最大最小值\n  if (index >= 0 && index < 30) {\n    a[index] = 1;\n  }\n}\n```\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n<a id=\"1.8\"></a>\n### 8    指针操作\n\n<a id=\"1.8.1\"></a>\n#### 8.1 【建议】检查在pointer上使用sizeof\n\n除了测试当前指针长度，否则一般不会在pointer上使用sizeof。\n\n正确：\n\n```c++\nsize_t pointer_length = sizeof(void*);\n```\n\n可能错误：\n\n```c++\nsize_t structure_length = sizeof(Foo*);\n```\n\n可能是：\n\n```c++\nsize_t structure_length = sizeof(Foo);\n```\n\n关联漏洞:\n\n`中风险-逻辑漏洞`\n\n<a id=\"1.8.2\"></a>\n#### 8.2 【必须】检查直接将数组和0比较的代码\n\n错误：\n\n```c++\nint a[3];\n...;\n\nif (a > 0)\n  ...;\n```\n\n该判断永远为真，等价于:\n\n```c++\nint a[3];\n...;\n\nif (&a[0])\n  ...;\n```\n\n可能是：\n\n```c++\nint a[3];\n...;\n\nif(a[0] > 0)\n  ...;\n```\n\n开启足够的编译器警告（GCC 中为 `-Waddress`，并已包含在 `-Wall` 中），并设置为错误，可以在编译期间发现该问题。\n\n关联漏洞:\n\n`中风险-逻辑漏洞`\n\n<a id=\"1.8.3\"></a>\n#### 8.3 【必须】不应当向指针赋予写死的地址\n\n特殊情况需要特殊对待（比如开发硬件固件时可能需要写死）\n\n但是如果是系统驱动开发之类的，写死可能会导致后续的问题。\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n<a id=\"1.8.4\"></a>\n#### 8.4 【必须】检查空指针\n\n错误：\n\n```c++\n*foo = 100;\n\nif (!foo) {\n  ERROR(\"foobar\");\n}\n```\n\n正确：\n\n```c++\nif (!foo) {\n  ERROR(\"foobar\");\n}\n\n*foo = 100;\n```\n\n错误：\n\n```c++\nvoid Foo(char* bar) {\n  *bar = '\\0';\n}\n```\n\n正确：\n\n```c++\nvoid Foo(char* bar) {\n  if(bar)\n    *bar = '\\0';\n  else\n    ...;\n}\n```\n\n关联漏洞:\n\n`低风险-拒绝服务`\n\n<a id=\"1.8.5\"></a>\n#### 8.5 【必须】释放完后置空指针\n\n在对指针进行释放后，需要将该指针设置为NULL，以防止后续free指针的误用，导致UAF等其他内存破坏问题。尤其是在结构体、类里面存储的原始指针。\n\n错误：\n\n```c++\nvoid foo() {\n  char* p = (char*)malloc(100);\n  memcpy(p, \"hello\", 6);\n  printf(\"%s\\n\", p);\n  free(p); // 此时p所指向的内存已被释放，但是p所指的地址仍然不变\n  // 未设置为NULL，可能导致UAF等内存错误\n\n  if (p != NULL) {  // 没有起到防错作用\n    printf(\"%s\\n\", p); // 错误使用已经释放的内存\n  }\n}\n```\n\n正确：\n\n```c++\nvoid foo() {\n  char* p = (char*)malloc(100);\n  memcpy(p, \"hello\", 6);\n  // 此时p所指向的内存已被释放，但是p所指的地址仍然不变\n  printf(\"%s\\n\", p);\n  free(p);\n  //释放后将指针赋值为空\n  p = NULL;\n  if (p != NULL)  { // 没有起到防错作用\n    printf(\"%s\\n\", p); // 错误使用已经释放的内存\n  }\n}\n```\n\n对于 C++ 代码，使用 string、vector、智能指针等代替原始内存管理机制，可以大量减少这类错误。\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n<a id=\"1.8.6\"></a>\n#### 8.6 【必须】防止错误的类型转换（type confusion）\n\n在对指针、对象或变量进行操作时，需要能够正确判断所操作对象的原始类型。如果使用了与原始类型不兼容的类型进行访问，则存在安全隐患。\n\n错误：\n\n```c++\nconst int NAME_TYPE = 1;\nconst int ID_TYPE = 2;\n\n// 该类型根据 msg_type 进行区分，如果在对MessageBuffer进行操作时没有判断目标对象，则存在类型混淆\nstruct MessageBuffer {\n  int msg_type;\n  union {\n    const char *name;\n    int name_id;\n  };\n};\n\nvoid Foo() {\n  struct MessageBuffer buf;\n  const char* default_message = \"Hello World\";\n  // 设置该消息类型为 NAME_TYPE，因此buf预期的类型为 msg_type + name\n  buf.msg_type = NAME_TYPE;\n  buf.name = default_message;\n  printf(\"Pointer of buf.name is %p\\n\", buf.name);\n\n  // 没有判断目标消息类型是否为ID_TYPE，直接修改nameID，导致类型混淆\n  buf.name_id = user_controlled_value;\n\n  if (buf.msg_type == NAME_TYPE) {\n    printf(\"Pointer of buf.name is now %p\\n\", buf.name);\n    // 以NAME_TYPE作为类型操作，可能导致非法内存读写\n    printf(\"Message: %s\\n\", buf.name);\n  } else {\n    printf(\"Message: Use ID %d\\n\", buf.name_id);\n  }\n}\n```\n\n正确（判断操作的目标是否是预期类型）：\n\n```c++\nvoid Foo() {\n  struct MessageBuffer buf;\n  const char* default_message = \"Hello World\";\n  // 设置该消息类型为 NAME_TYPE，因此buf预期的类型为 msg_type + name\n  buf.msg_type = NAME_TYPE;\n  buf.name = default_msessage;\n  printf(\"Pointer of buf.name is %p\\n\", buf.name);\n\n  // 判断目标消息类型是否为 ID_TYPE，不是预期类型则做对应操作\n  if (buf.msg_type == ID_TYPE)\n    buf.name_id = user_controlled_value;\n\n  if (buf.msg_type == NAME_TYPE) {\n    printf(\"Pointer of buf.name is now %p\\n\", buf.name);\n    printf(\"Message: %s\\n\", buf.name);\n  } else {\n    printf(\"Message: Use ID %d\\n\", buf.name_id);\n  }\n}\n```\n\n关联漏洞:\n\n`高风险-内存破坏`\n\n<a id=\"1.8.7\"></a>\n#### 8.7 【必须】智能指针使用安全\n\n在使用智能指针时，防止其和原始指针的混用，否则可能导致对象生命周期问题，例如 UAF 等安全风险。\n\n错误例子：\n\n```c++\nclass Foo {\n public:\n  explicit Foo(int num) { data_ = num; };\n  void Function() { printf(\"Obj is %p, data = %d\\n\", this, data_); };\n private:\n  int data_;\n};\n\nstd::unique_ptr<Foo> fool_u_ptr = nullptr;\nFoo* pfool_raw_ptr = nullptr;\n\nvoid Risk() {\n  fool_u_ptr = make_unique<Foo>(1);\n\n  // 从独占智能指针中获取原始指针,<Foo>(1)\n  pfool_raw_ptr = fool_u_ptr.get();\n  // 调用<Foo>(1)的函数\n  pfool_raw_ptr->Function();\n\n  // 独占智能指针重新赋值后会释放内存\n  fool_u_ptr = make_unique<Foo>(2);\n  // 通过原始指针操作会导致UAF，pfool_raw_ptr指向的对象已经释放\n  pfool_raw_ptr->Function();\n}\n\n\n// 输出：\n// Obj is 0000027943087B80, data = 1\n// Obj is 0000027943087B80, data = -572662307\n```\n\n正确，通过智能指针操作:\n\n```c++\nvoid Safe() {\n  fool_u_ptr = make_unique<Foo>(1);\n  // 调用<Foo>(1)的函数\n  fool_u_ptr->Function();\n\n  fool_u_ptr = make_unique<Foo>(2);\n  // 调用<Foo>(2)的函数\n  fool_u_ptr->Function();\n}\n\n// 输出：\n// Obj is 000002C7BB550830, data = 1\n// Obj is 000002C7BB557AF0, data = 2\n```\n\n关联漏洞:\n\n`高风险-内存破坏`\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# 为 代码安全指南 作出贡献\n\n欢迎 [提出问题或建议](issues) 或 [提交合并请求](pulls)，建议在为项目作出贡献时，阅读以下指南。\n\n### I. Commit Mesage 编写指引\n\n为便于索引，Commit Message应包括三个部分：Header（必需），Body（可选）和 Footer（可选）。\n\n```html\n<type>(<scope>): <subject>\n// 空一行\n<body>\n// 空一行\n<footer>\n```\n\n**Header 部分**只有一行，包括三个字段：type（必需）、scope（可选）和subject（必需）。\n\n> type 用于说明 commit 的类别，可使用下面3个标识：<br/>\n> - add: 添加新规范语言或条目<br/>\n> - fix: 修订内容<br/>\n> - chore: 非指南文档本身或相关辅助工具的变动<br/>\n> \n> scope 用于指定 commit 影响的范围，包括对应的语言及其条目编号；如：go/1.1.1。\n> \n> subject是 commit 目的的简短描述。\n\n**Body 部分**是对本次 commit 的详细描述。\n\n**Footer 部分**关闭 Issue。如果当前 commit 针对某个issue，可以在 Footer 部分关闭这个 issue 。\n\n一个完整的示例如下：\n\n```html\nfix(go/1.1.1): 修订条目内容\n \n- 修正代码示例缩进问题\n \nClose #1\n```\n\n\n\n### II. Issues 编写指引\n\n为便于理解与管理，提交问题或建议时，参考以下格式：\n\n```\n标题：#<对应的指南语言># 指南<对应的条目编号>修订建议\n\n内容：\n1、问题描述\n<指出存在的问题>\n\n2、解决建议\n<提供解决建议及参考材料>\n```\n\n一个完整的示例如下：\n\n```\n标题：#JavaScript# 指南1.3.1条修订建议\n\n内容：\n1、问题描述\nJavaScript代码安全指南的【1.3.1条】赋值或更新HTML属性部分，需补充\n\n2、解决建议\n应补充下列风险点：\narea.href、input.formaction、button.formaction\n```"
  },
  {
    "path": "Go安全指南.md",
    "content": "<!-- markdown=\"1\" is required for GitHub Pages to render the TOC properly. -->\n\n<details markdown=\"1\">\n  <summary>目录</summary>\n\n-   [1 通用类](#1)\n    *   [I. 代码实现](#1.1)\n\t\t+   [1.1 内存管理](#1.1.1)\n\t\t+   [1.2 文件操作](#1.1.2)\n\t\t+   [1.3 系统接口](#1.1.3)\n\t\t+   [1.4 通信安全](#1.1.4)\n\t\t+   [1.5 敏感数据保护](#1.1.5)\n\t\t+   [1.6 加密解密](#1.1.6)\n\t\t+   [1.7 正则表达式](#1.1.7)\n\n-   [2 后台类](#2)\n    *   [I. 代码实现](#2.1)\n\t\t+   [1.1 输入校验](#2.1.1)\n\t\t+   [1.2 SQL操作](#2.1.2)\n\t\t+   [1.3 网络请求](#2.1.3)\n\t\t+   [1.4 服务器端渲染](#2.1.4)\n\t\t+   [1.5 Web跨域](#2.1.5)\n\t\t+   [1.6 响应输出](#2.1.6)\n\t\t+   [1.7 会话管理](#2.1.7)\n\t\t+   [1.8 访问控制](#2.1.8)\n\t\t+   [1.9 并发保护](#2.1.9)\n</details>\n\n<a id=\"1\"></a>\n# 通用类\n\n<a id=\"1.1\"></a>\n## 1. 代码实现类\n\n<a id=\"1.1.1\"></a>\n### 1.1 内存管理\n\n#### 1.1.1【必须】切片长度校验\n\n- 在对slice进行操作时，必须判断长度是否合法，防止程序panic\n\n```go\n// bad: 未判断data的长度，可导致 index out of range\nfunc decode(data []byte) bool {\n\tif data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'E' && data[5] == 'R' {\n\t\tfmt.Println(\"Bad\")\n\t\treturn true\n\t}\n\treturn false\n}\n\n// bad: slice bounds out of range\nfunc foo() {\n\tvar slice = []int{0, 1, 2, 3, 4, 5, 6}\n\tfmt.Println(slice[:10])\n}\n\n// good: 使用data前应判断长度是否合法\nfunc decode(data []byte) bool {\n\tif len(data) == 6 {\n\t\tif data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'E' && data[5] == 'R' {\n\t\t\tfmt.Println(\"Good\")\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n```\n\n#### 1.1.2【必须】nil指针判断\n\n- 进行指针操作时，必须判断该指针是否为nil，防止程序panic，尤其在进行结构体Unmarshal时\n\n```go\ntype Packet struct {\n\tPackeyType    uint8\n\tPackeyVersion uint8\n\tData          *Data\n}\n\ntype Data struct {\n\tStat uint8\n\tLen  uint8\n\tBuf  [8]byte\n}\n\nfunc (p *Packet) UnmarshalBinary(b []byte) error {\n\tif len(b) < 2 {\n\t\treturn io.EOF\n\t}\n\n\tp.PackeyType = b[0]\n\tp.PackeyVersion = b[1]\n\n\t// 若长度等于2，那么不会new Data\n\tif len(b) > 2 {\n\t\tp.Data = new(Data)\n\t}\n\treturn nil\n}\n\n// bad: 未判断指针是否为nil\nfunc main() {\n\tpacket := new(Packet)\n\tdata := make([]byte, 2)\n\tif err := packet.UnmarshalBinary(data); err != nil {\n\t\tfmt.Println(\"Failed to unmarshal packet\")\n\t\treturn\n\t}\n\n\tfmt.Printf(\"Stat: %v\\n\", packet.Data.Stat)\n}\n\n// good: 判断Data指针是否为nil\nfunc main() {\n\tpacket := new(Packet)\n\tdata := make([]byte, 2)\n\n\tif err := packet.UnmarshalBinary(data); err != nil {\n\t\tfmt.Println(\"Failed to unmarshal packet\")\n\t\treturn\n\t}\n\n\tif packet.Data == nil {\n\t\treturn\n\t}\n\n\tfmt.Printf(\"Stat: %v\\n\", packet.Data.Stat)\n}\n```\n\n#### 1.1.3【必须】整数安全\n\n- 在进行数字运算操作时，需要做好长度限制，防止外部输入运算导致异常：\n  - 确保无符号整数运算时不会反转\n  - 确保有符号整数运算时不会出现溢出\n  - 确保整型转换时不会出现截断错误\n  - 确保整型转换时不会出现符号错误\n\n- 以下场景必须严格进行长度限制：\n  - 作为数组索引\n  - 作为对象的长度或者大小\n  - 作为数组的边界（如作为循环计数器）\n\n```go\n// bad: 未限制长度，导致整数溢出\nfunc overflow(numControlByUser int32) {\n\tvar numInt int32 = 0\n\tnumInt = numControlByUser + 1\n\t// 对长度限制不当，导致整数溢出\n\tfmt.Printf(\"%d\\n\", numInt)\n\t// 使用numInt，可能导致其他错误\n}\n\nfunc main() {\n\toverflow(2147483647)\n}\n\n// good\nfunc overflow(numControlByUser int32) {\n\tvar numInt int32 = 0\n\tnumInt = numControlByUser + 1\n\tif numInt < 0 {\n\t\tfmt.Println(\"integer overflow\")\n\t\treturn\n\t}\n\tfmt.Println(\"integer ok\")\n}\n\nfunc main() {\n\toverflow(2147483647)\n}\n```\n\n#### 1.1.4【必须】make分配长度验证\n\n- 在进行make分配内存时，需要对外部可控的长度进行校验，防止程序panic。\n\n```go\n// bad\nfunc parse(lenControlByUser int, data []byte) {\n\tsize := lenControlByUser\n\t// 对外部传入的size，进行长度判断以免导致panic\n\tbuffer := make([]byte, size)\n\tcopy(buffer, data)\n}\n\n// good\nfunc parse(lenControlByUser int, data []byte) ([]byte, error) {\n\tsize := lenControlByUser\n\t// 限制外部可控的长度大小范围\n\tif size > 64*1024*1024 {\n\t\treturn nil, errors.New(\"value too large\")\n\t}\n\tbuffer := make([]byte, size)\n\tcopy(buffer, data)\n\treturn buffer, nil\n}\n```\n\n#### 1.1.5【必须】禁止SetFinalizer和指针循环引用同时使用\n- 当一个对象从被GC选中到移除内存之前，runtime.SetFinalizer()都不会执行，即使程序正常结束或者发生错误。由指针构成的“循环引用”虽然能被GC正确处理，但由于无法确定Finalizer依赖顺序，从而无法调用runtime.SetFinalizer()，导致目标对象无法变成可达状态，从而造成内存无法被回收。\n\n```go\n// bad\nfunc foo() {\n\tvar a, b Data\n\ta.o = &b\n\tb.o = &a\n\n\t// 指针循环引用，SetFinalizer()无法正常调用\n\truntime.SetFinalizer(&a, func(d *Data) {\n\t\tfmt.Printf(\"a %p final.\\n\", d)\n\t})\n\truntime.SetFinalizer(&b, func(d *Data) {\n\t\tfmt.Printf(\"b %p final.\\n\", d)\n\t})\n}\n\nfunc main() {\n\tfor {\n\t\tfoo()\n\t\ttime.Sleep(time.Millisecond)\n\t}\n}\n\n```\n\n#### 1.1.6【必须】禁止重复释放channel\n- 重复释放一般存在于异常流程判断中，如果恶意攻击者构造出异常条件使程序重复释放channel，则会触发运行时panic，从而造成DoS攻击。\n\n```go\n// bad\nfunc foo(c chan int) {\n\tdefer close(c)\n\terr := processBusiness()\n\tif err != nil {\n\t\tc <- 0\n\t\tclose(c) // 重复释放channel\n\t\treturn\n\t}\n\tc <- 1\n}\n\n// good\nfunc foo(c chan int) {\n\tdefer close(c) // 使用defer延迟关闭channel\n\terr := processBusiness()\n\tif err != nil {\n\t\tc <- 0\n\t\treturn\n\t}\n\tc <- 1\n}\n```\n\n#### 1.1.7【必须】确保每个协程都能退出\n- 启动一个协程就会做一个入栈操作，在系统不退出的情况下，协程也没有设置退出条件，则相当于协程失去了控制，它占用的资源无法回收，可能会导致内存泄露。\n\n```go\n// bad: 协程没有设置退出条件\nfunc doWaiter(name string, second int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(second) * time.Second)\n\t\tfmt.Println(name, \" is ready!\")\n\t}\n}\n```\n\n#### 1.1.8【推荐】不使用unsafe包\n- 由于unsafe包绕过了 Golang 的内存安全原则，一般来说使用该库是不安全的，可导致内存破坏，尽量避免使用该包。若必须要使用unsafe操作指针，必须做好安全校验。\n\n```go\n// bad: 通过unsafe操作原始指针\nfunc unsafePointer() {\n\tb := make([]byte, 1)\n\tfoo := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(0xfffffffe)))\n\tfmt.Print(*foo + 1)\n}\n\n// [signal SIGSEGV: segmentation violation code=0x1 addr=0xc100068f55 pc=0x49142b]\n\n```\n\n#### 1.1.9【推荐】不使用slice作为函数入参\n\n- slice在作为函数入参时，函数内对slice的修改可能会影响原始数据\n\n```go\n  // bad\n  // slice作为函数入参时包含原始数组指针\n  func modify(array []int) {\n      array[0] = 10 // 对入参slice的元素修改会影响原始数据\n  }\n  \n  func main() {\n      array := []int{1, 2, 3, 4, 5}\n  \n      modify(array)\n      fmt.Println(array) // output：[10 2 3 4 5]\n  }\n\n  // good\n  // 数组作为函数入参，而不是slice\n  func modify(array [5]int) {\n    array[0] = 10\n  }\n\n  func main() {\n      // 传入数组，注意数组与slice的区别\n      array := [5]int{1, 2, 3, 4, 5}\n  \n      modify(array)\n      fmt.Println(array)\n  }\n  \n```\n\n<a id=\"1.1.2\"></a>\n### 1.2 文件操作\n\n#### 1.2.1【必须】 路径穿越检查\n- 在进行文件操作时，如果对外部传入的文件名未做限制，可能导致任意文件读取或者任意文件写入，严重可能导致代码执行。\n\n```go\n// bad: 任意文件读取\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\tpath := r.URL.Query()[\"path\"][0]\n\n\t// 未过滤文件路径，可能导致任意文件读取\n\tdata, _ := ioutil.ReadFile(path)\n\tw.Write(data)\n\n\t// 对外部传入的文件名变量，还需要验证是否存在../等路径穿越的文件名\n\tdata, _ = ioutil.ReadFile(filepath.Join(\"/home/user/\", path))\n\tw.Write(data)\n}\n\n// bad: 任意文件写入\nfunc unzip(f string) {\n\tr, _ := zip.OpenReader(f)\n\tfor _, f := range r.File {\n\t\tp, _ := filepath.Abs(f.Name)\n\t\t// 未验证压缩文件名，可能导致../等路径穿越，任意文件路径写入\n\t\tioutil.WriteFile(p, []byte(\"present\"), 0640)\n\t}\n}\n\n// good: 检查压缩的文件名是否包含..路径穿越特征字符，防止任意写入\nfunc unzipGood(f string) bool {\n\tr, err := zip.OpenReader(f)\n\tif err != nil {\n\t\tfmt.Println(\"read zip file fail\")\n\t\treturn false\n\t}\n\tfor _, f := range r.File {\n\t\tif !strings.Contains(f.Name, \"..\") {\n\t\t\tp, _ := filepath.Abs(f.Name)\n\t\t\tioutil.WriteFile(p, []byte(\"present\"), 0640)\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n```\n\n#### 1.2.2【必须】 文件访问权限\n\n- 根据创建文件的敏感性设置不同级别的访问权限，以防止敏感数据被任意权限用户读取。例如，设置文件权限为：`-rw-r-----`\n\n```go\nioutil.WriteFile(p, []byte(\"present\"), 0640)\n```\n\n<a id=\"1.1.3\"></a>\n### 1.3 系统接口\n\n**1.3.1【必须】命令执行检查**\n- 使用`exec.Command`、`exec.CommandContext`、`syscall.StartProcess`、`os.StartProcess`等函数时，第一个参数（path）直接取外部输入值时，应使用白名单限定可执行的命令范围，不允许传入`bash`、`cmd`、`sh`等命令；\n- 使用`exec.Command`、`exec.CommandContext`等函数时，通过`bash`、`cmd`、`sh`等创建shell，-c后的参数（arg）拼接外部输入，应过滤\\n  $  &  ;  |  '  \"  ( )  `等潜在恶意字符；\n\n```go\n// bad\nfunc foo() {\n\tuserInputedVal := \"&& echo 'hello'\" // 假设外部传入该变量值\n\tcmdName := \"ping \" + userInputedVal\n\n\t// 未判断外部输入是否存在命令注入字符，结合sh可造成命令注入\n\tcmd := exec.Command(\"sh\", \"-c\", cmdName)\n\toutput, _ := cmd.CombinedOutput()\n\tfmt.Println(string(output))\n\n\tcmdName := \"ls\"\n\t// 未判断外部输入是否是预期命令\n\tcmd := exec.Command(cmdName)\n\toutput, _ := cmd.CombinedOutput()\n\tfmt.Println(string(output))\n}\n\n// good\nfunc checkIllegal(cmdName string) bool {\n\tif strings.Contains(cmdName, \"&\") || strings.Contains(cmdName, \"|\") || strings.Contains(cmdName, \";\") ||\n\t\tstrings.Contains(cmdName, \"$\") || strings.Contains(cmdName, \"'\") || strings.Contains(cmdName, \"`\") ||\n\t\tstrings.Contains(cmdName, \"(\") || strings.Contains(cmdName, \")\") || strings.Contains(cmdName, \"\\\"\") {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc main() {\n\tuserInputedVal := \"&& echo 'hello'\"\n\tcmdName := \"ping \" + userInputedVal\n\n\tif checkIllegal(cmdName) { // 检查传给sh的命令是否有特殊字符\n\t\treturn // 存在特殊字符直接return\n\t}\n\n\tcmd := exec.Command(\"sh\", \"-c\", cmdName)\n\toutput, _ := cmd.CombinedOutput()\n\tfmt.Println(string(output))\n}\n```\n\n<a id=\"1.1.4\"></a>\n### 1.4 通信安全\n\n#### 1.4.1【必须】网络通信采用TLS方式\n\n- 明文传输的通信协议目前已被验证存在较大安全风险，被中间人劫持后可能导致许多安全风险，因此必须采用至少TLS的安全通信方式保证通信安全，例如gRPC/Websocket都使用TLS1.3。\n\n```go\n// good\nfunc main() {\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, req *http.Request) {\n\t\tw.Header().Add(\"Strict-Transport-Security\", \"max-age=63072000; includeSubDomains\")\n\t\tw.Write([]byte(\"This is an example server.\\n\"))\n\t})\n\n\t// 服务器配置证书与私钥\n\tlog.Fatal(http.ListenAndServeTLS(\":443\", \"yourCert.pem\", \"yourKey.pem\", nil))\n}\n```\n\n#### 1.4.2【推荐】TLS启用证书验证\n\n- TLS证书应当是有效的、未过期的，且配置正确的域名，生产环境的服务端应启用证书验证。\n\n```go\n// bad\nimport (\n\t\"crypto/tls\"\n\t\"net/http\"\n)\n\nfunc doAuthReq(authReq *http.Request) *http.Response {\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t}\n\tclient := &http.Client{Transport: tr}\n\tres, _ := client.Do(authReq)\n\treturn res\n}\n\n// good\nimport (\n\t\"crypto/tls\"\n\t\"net/http\"\n)\n\nfunc doAuthReq(authReq *http.Request) *http.Response {\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: false},\n\t}\n\tclient := &http.Client{Transport: tr}\n\tres, _ := client.Do(authReq)\n\treturn res\n}\n```\n\n<a id=\"1.1.5\"></a>\n### 1.5 敏感数据保护\n\n#### 1.5.1【必须】敏感信息访问\n- 禁止将敏感信息硬编码在程序中，既可能会将敏感信息暴露给攻击者，也会增加代码管理和维护的难度\n- 使用配置中心系统统一托管密钥等敏感信息\n\n#### 1.5.2【必须】敏感数据输出\n- 只输出必要的最小数据集，避免多余字段暴露引起敏感信息泄露\n- 不能在日志保存密码（包括明文密码和密文密码）、密钥和其它敏感信息\n- 对于必须输出的敏感信息，必须进行合理脱敏展示\n\n```go\n// bad\nfunc serve() {\n\thttp.HandleFunc(\"/register\", func(w http.ResponseWriter, r *http.Request) {\n\t\tr.ParseForm()\n\t\tuser := r.Form.Get(\"user\")\n\t\tpw := r.Form.Get(\"password\")\n\n\t\tlog.Printf(\"Registering new user %s with password %s.\\n\", user, pw)\n\t})\n\thttp.ListenAndServe(\":80\", nil)\n}\n\n// good\nfunc serve1() {\n\thttp.HandleFunc(\"/register\", func(w http.ResponseWriter, r *http.Request) {\n\t\tr.ParseForm()\n\t\tuser := r.Form.Get(\"user\")\n\t\tpw := r.Form.Get(\"password\")\n\n\t\tlog.Printf(\"Registering new user %s.\\n\", user)\n\n\t\t// ...\n\t\tuse(pw)\n\t})\n\thttp.ListenAndServe(\":80\", nil)\n}\n```\n\n- 避免通过GET方法、代码注释、自动填充、缓存等方式泄露敏感信息\n\n#### 1.5.3【必须】敏感数据存储\n- 敏感数据应使用SHA2、RSA等算法进行加密存储\n- 敏感数据应使用独立的存储层，并在访问层开启访问控制\n- 包含敏感信息的临时文件或缓存一旦不再需要应立刻删除\n\n#### 1.5.4【必须】异常处理和日志记录\n\n- 应合理使用panic、recover、defer处理系统异常，避免出错信息输出到前端\n\n```go\ndefer func () {\n\tif r := recover(); r != nil {\n\t\tfmt.Println(\"Recovered in start()\")\n\t}\n}()\n```\n\n- 对外环境禁止开启debug模式，或将程序运行日志输出到前端\n\n```bash\n// bad\ndlv --listen=:2345 --headless=true --api-version=2 debug test.go\n// good\ndlv debug test.go\n```\n\n<a id=\"1.1.6\"></a>\n### 1.6 加密解密\n\n#### 1.6.1【必须】不得硬编码密码/密钥\n- 在进行用户登陆，加解密算法等操作时，不得在代码里硬编码密钥或密码，可通过变换算法或者配置等方式设置密码或者密钥。\n\n```go\n// bad\nconst (\n\tuser     = \"dbuser\"\n\tpassword = \"s3cretp4ssword\"\n)\n\nfunc connect() *sql.DB {\n\tconnStr := fmt.Sprintf(\"postgres://%s:%s@localhost/pqgotest\", user, password)\n\tdb, err := sql.Open(\"postgres\", connStr)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn db\n}\n\n// bad\nvar (\n\tcommonkey = []byte(\"0123456789abcdef\")\n)\n\nfunc AesEncrypt(plaintext string) (string, error) {\n\tblock, err := aes.NewCipher(commonkey)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n}\n```\n\n#### 1.6.2【必须】密钥存储安全\n\n- 在使用对称密码算法时，需要保护好加密密钥。当算法涉及敏感、业务数据时，可通过非对称算法协商加密密钥。其他较为不敏感的数据加密，可以通过变换算法等方式保护密钥。\n\n#### 1.6.3【推荐】不使用弱密码算法\n\n- 在使用加密算法时，不建议使用加密强度较弱的算法。\n\n```\n// bad\ncrypto/des，crypto/md5，crypto/sha1，crypto/rc4等。\n\n// good\ncrypto/rsa，crypto/aes等。\n```\n\n<a id=\"1.1.7\"></a>\n### 1.7 正则表达式\n\n#### 1.7.1【推荐】使用regexp进行正则表达式匹配\n\n- 正则表达式编写不恰当可被用于DoS攻击，造成服务不可用，推荐使用regexp包进行正则表达式匹配。regexp保证了线性时间性能和优雅的失败：对解析器、编译器和执行引擎都进行了内存限制。但regexp不支持以下正则表达式特性，如业务依赖这些特性，则regexp不适合使用。\n  - 回溯引用[Backreferences](https://www.regular-expressions.info/backref.html)\n  - 查看[Lookaround](https://www.regular-expressions.info/lookaround.html)\n\n```go\n// good\nmatched, err := regexp.MatchString(`a.b`, \"aaxbb\")\nfmt.Println(matched) // true\nfmt.Println(err)     // nil\n```\n\n<a id=\"2\"></a>\n# 后台类\n\n<a id=\"2.1\"></a>\n## 1 代码实现类\n\n<a id=\"2.1.1\"></a>\n### 1.1 输入校验\n\n#### 1.1.1【必须】按类型进行数据校验\n\n- 所有外部输入的参数，应使用`validator`进行白名单校验，校验内容包括但不限于数据长度、数据范围、数据类型与格式，校验不通过的应当拒绝\n\n```go\n// good\nimport (\n\t\"fmt\"\n\t\"github.com/go-playground/validator/v10\"\n)\n\nvar validate *validator.Validate\n\nfunc validateVariable() {\n\tmyEmail := \"abc@tencent.com\"\n\terrs := validate.Var(myEmail, \"required,email\")\n\tif errs != nil {\n\t\tfmt.Println(errs)\n\t\treturn\n\t\t//停止执行\n\t}\n\t// 验证通过，继续执行\n\t...\n}\n\nfunc main() {\n\tvalidate = validator.New()\n\tvalidateVariable()\n}\n```\n\n- 无法通过白名单校验的应使用`html.EscapeString`、`text/template`或`bluemonday`对`<, >, &, ',\"`等字符进行过滤或编码\n\n```go\nimport (\n\t\"text/template\"\n)\n\n// TestHTMLEscapeString HTML特殊字符转义\nfunc main(inputValue string) string {\n\tescapedResult := template.HTMLEscapeString(inputValue)\n\treturn escapedResult\n}\n```\n\n<a id=\"2.1.2\"></a>\n### 1.2 SQL操作\n\n#### 1.2.1【必须】SQL语句默认使用预编译并绑定变量\n\n- 使用`database/sql`的prepare、Query或使用GORM等ORM执行SQL操作\n\n```go\nimport (\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/sqlite\"\n)\n\ntype Product struct {\n\tgorm.Model\n\tCode  string\n\tPrice uint\n}\n\n...\nvar product Product\n...\ndb.First(&product, 1)\n```\n\n- 使用参数化查询，禁止拼接SQL语句，另外对于传入参数用于order by或表名的需要通过校验\n\n```go\n// bad\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\nfunc handler(db *sql.DB, req *http.Request) {\n\tq := fmt.Sprintf(\"SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='%s' ORDER BY PRICE\",\n\t\treq.URL.Query()[\"category\"])\n\tdb.Query(q)\n}\n\n// good\nfunc handlerGood(db *sql.DB, req *http.Request) {\n\t// 使用?占位符\n\tq := \"SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='?' ORDER BY PRICE\"\n\tdb.Query(q, req.URL.Query()[\"category\"])\n}\n```\n\n<a id=\"2.1.3\"></a>\n### 1.3 网络请求\n####  1.3.1【必须】资源请求过滤验证\n\n- 使用`\"net/http\"`下的方法`http.Get(url)`、`http.Post(url, contentType, body)`、`http.Head(url)`、`http.PostForm(url, data)`、`http.Do(req)`时，如变量值外部可控（指从参数中动态获取），应对请求目标进行严格的安全校验。\n\n- 如请求资源域名归属固定的范围，如只允许`a.qq.com`和`b.qq.com`，应做白名单限制。如不适用白名单，则推荐的校验逻辑步骤是：\n\n  - 第 1 步、只允许HTTP或HTTPS协议\n\n  - 第 2 步、解析目标URL，获取其HOST\n\n  - 第 3 步、解析HOST，获取HOST指向的IP地址转换成Long型\n\n  - 第 4 步、检查IP地址是否为内网IP，网段有：\n\n    ```\n    // 以RFC定义的专有网络为例，如有自定义私有网段亦应加入禁止访问列表。\n    10.0.0.0/8\n    172.16.0.0/12\n    192.168.0.0/16\n    127.0.0.0/8\n    ```\n\n  - 第 5 步、请求URL\n\n  - 第 6 步、如有跳转，跳转后执行1，否则绑定经校验的ip和域名，对URL发起请求\n\n- 官方库`encoding/xml`不支持外部实体引用，使用该库可避免xxe漏洞\n\n```go\nimport (\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"os\"\n)\n\nfunc main() {\n\ttype Person struct {\n\t\tXMLName  xml.Name `xml:\"person\"`\n\t\tId       int      `xml:\"id,attr\"`\n\t\tUserName string   `xml:\"name>first\"`\n\t\tComment  string   `xml:\",comment\"`\n\t}\n\n\tv := &Person{Id: 13, UserName: \"John\"}\n\tv.Comment = \" Need more details. \"\n\n\tenc := xml.NewEncoder(os.Stdout)\n\tenc.Indent(\"  \", \"    \")\n\tif err := enc.Encode(v); err != nil {\n\t\tfmt.Printf(\"error: %v\\n\", err)\n\t}\n\n}\n```\n\n<a id=\"2.1.4\"></a>\n### 1.4 服务器端渲染\n#### 1.4.1【必须】模板渲染过滤验证\n\n- 使用`text/template`或者`html/template`渲染模板时禁止将外部输入参数引入模板，或仅允许引入白名单内字符。\n\n```go\n// bad\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\tr.ParseForm()\n\tx := r.Form.Get(\"name\")\n\n\tvar tmpl = `<!DOCTYPE html><html><body>\n    <form action=\"/\" method=\"post\">\n        First name:<br>\n    <input type=\"text\" name=\"name\" value=\"\">\n    <input type=\"submit\" value=\"Submit\">\n    </form><p>` + x + ` </p></body></html>`\n\n\tt := template.New(\"main\")\n\tt, _ = t.Parse(tmpl)\n\tt.Execute(w, \"Hello\")\n}\n\n// good\nimport (\n\t\"fmt\"\n\t\"github.com/go-playground/validator/v10\"\n)\n\nvar validate *validator.Validate\nvalidate = validator.New()\n\nfunc validateVariable(val) {\n\terrs := validate.Var(val, \"gte=1,lte=100\") // 限制必须是1-100的正整数\n\tif errs != nil {\n\t\tfmt.Println(errs)\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\tr.ParseForm()\n\tx := r.Form.Get(\"name\")\n\n\tif validateVariable(x) {\n\t\tvar tmpl = `<!DOCTYPE html><html><body>\n            <form action=\"/\" method=\"post\">\n            First name:<br>\n            <input type=\"text\" name=\"name\" value=\"\">\n            <input type=\"submit\" value=\"Submit\">\n            </form><p>` + x + ` </p></body></html>`\n\t\tt := template.New(\"main\")\n\t\tt, _ = t.Parse(tmpl)\n\t\tt.Execute(w, \"Hello\")\n\t} else {\n\t\t// ...\n\t}\n}\n\n```\n\n<a id=\"2.1.5\"></a>\n### 1.5 Web跨域\n#### 1.5.1【必须】跨域资源共享CORS限制请求来源\n\n- CORS请求保护不当可导致敏感信息泄漏，因此应当严格设置Access-Control-Allow-Origin使用同源策略进行保护。\n\n```go\n// good\nc := cors.New(cors.Options{\n\tAllowedOrigins:   []string{\"http://qq.com\", \"https://qq.com\"},\n\tAllowCredentials: true,\n\tDebug:            false,\n})\n\n// 引入中间件\nhandler = c.Handler(handler)\n```\n\n<a id=\"2.1.6\"></a>\n### 1.6 响应输出\n\n#### 1.6.1 【必须】设置正确的HTTP响应包类型\n\n- 响应头Content-Type与实际响应内容，应保持一致。如：API响应数据类型是json，则响应头使用`application/json`；若为xml，则设置为`text/xml`。\n\n#### 1.6.2 【必须】添加安全响应头\n\n- 所有接口、页面，添加响应头 `X-Content-Type-Options: nosniff`。\n- 所有接口、页面，添加响应头`X-Frame-Options `。按需合理设置其允许范围，包括：`DENY`、`SAMEORIGIN`、`ALLOW-FROM origin`。用法参考：[MDN文档](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options)\n\n#### 1.6.3【必须】外部输入拼接到HTTP响应头中需进行过滤\n\n- 应尽量避免外部可控参数拼接到HTTP响应头中，如业务需要则需要过滤掉`\\r`、`\\n`等换行符，或者拒绝携带换行符号的外部输入。\n\n#### 1.6.4【必须】外部输入拼接到response页面前进行编码处理\n\n- 直出html页面或使用模板生成html页面的，推荐使用`text/template`自动编码，或者使用`html.EscapeString`或`text/template`对`<, >, &, ',\"`等字符进行编码。\n\n```go\nimport (\n\t\"html/template\"\n)\n\nfunc outtemplate(w http.ResponseWriter, r *http.Request) {\n\tparam1 := r.URL.Query().Get(\"param1\")\n\ttmpl := template.New(\"hello\")\n\ttmpl, _ = tmpl.Parse(`{{define \"T\"}}{{.}}{{end}}`)\n\ttmpl.ExecuteTemplate(w, \"T\", param1)\n}\n```\n\n<a id=\"2.1.7\"></a>\n### 1.7 会话管理\n\n#### 1.7.1【必须】安全维护session信息\n\n- 用户登录时应重新生成session，退出登录后应清理session。\n```go\nimport (\n\t\"github.com/gorilla/handlers\"\n\t\"github.com/gorilla/mux\"\n\t\"net/http\"\n)\n\n// 创建cookie\nfunc setToken(res http.ResponseWriter, req *http.Request) {\n\texpireToken := time.Now().Add(time.Minute * 30).Unix()\n\texpireCookie := time.Now().Add(time.Minute * 30)\n\n\t//...\n\n\tcookie := http.Cookie{\n\t\tName:     \"Auth\",\n\t\tValue:    signedToken,\n\t\tExpires:  expireCookie, // 过期失效\n\t\tHttpOnly: true,\n\t\tPath:     \"/\",\n\t\tDomain:   \"127.0.0.1\",\n\t\tSecure:   true,\n\t}\n\n\thttp.SetCookie(res, &cookie)\n\thttp.Redirect(res, req, \"/profile\", 307)\n}\n\n// 删除cookie\nfunc logout(res http.ResponseWriter, req *http.Request) {\n\tdeleteCookie := http.Cookie{\n\t\tName:    \"Auth\",\n\t\tValue:   \"none\",\n\t\tExpires: time.Now(),\n\t}\n\thttp.SetCookie(res, &deleteCookie)\n\treturn\n}\n```\n\n#### 1.7.2【必须】CSRF防护\n\n- 涉及系统敏感操作或可读取敏感信息的接口应校验`Referer`或添加`csrf_token`。\n\n```go\n// good\nimport (\n\t\"github.com/gorilla/csrf\"\n\t\"github.com/gorilla/mux\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/signup\", ShowSignupForm)\n\tr.HandleFunc(\"/signup/post\", SubmitSignupForm)\n\t// 使用csrf_token验证\n\thttp.ListenAndServe(\":8000\",\n\t\tcsrf.Protect([]byte(\"32-byte-long-auth-key\"))(r))\n}\n```\n\n<a id=\"2.1.8\"></a>\n### 1.8 访问控制\n\n#### 1.8.1【必须】默认鉴权\n\n- 除非资源完全可对外开放，否则系统默认进行身份认证，使用白名单的方式放开不需要认证的接口或页面。\n\n- 根据资源的机密程度和用户角色，以最小权限原则，设置不同级别的权限，如完全公开、登录可读、登录可写、特定用户可读、特定用户可写等\n\n- 涉及用户自身相关的数据的读写必须验证登录态用户身份及其权限，避免越权操作\n\n  ```sql\n  -- 伪代码\n  select id from table where id=:id and userid=session.userid\n  ```\n\n- 没有独立账号体系的外网服务使用`QQ`或`微信`登录，内网服务使用`统一登录服务`登录，其他使用账号密码登录的服务需要增加验证码等二次验证\n\n<a id=\"2.1.9\"></a>\n### 1.9 并发保护\n\n#### 1.9.1【必须】禁止在闭包中直接调用循环变量\n\n- 在循环中启动协程，当协程中使用到了循环的索引值，由于多个协程同时使用同一个变量会产生数据竞争，造成执行结果异常。\n\n```go\n// bad\nfunc main() {\n\truntime.GOMAXPROCS(runtime.NumCPU())\n\tvar group sync.WaitGroup\n\n\tfor i := 0; i < 5; i++ {\n\t\tgroup.Add(1)\n\t\tgo func() {\n\t\t\tdefer group.Done()\n\t\t\tfmt.Printf(\"%-2d\", i) // 这里打印的i不是所期望的\n\t\t}()\n\t}\n\tgroup.Wait()\n}\n\n// good\nfunc main() {\n\truntime.GOMAXPROCS(runtime.NumCPU())\n\tvar group sync.WaitGroup\n\n\tfor i := 0; i < 5; i++ {\n\t\tgroup.Add(1)\n\t\tgo func(j int) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tfmt.Println(\"Recovered in start()\")\n\t\t\t\t}\n\t\t\t\tgroup.Done()\n\t\t\t}()\n\t\t\tfmt.Printf(\"%-2d\", j) // 闭包内部使用局部变量\n\t\t}(i) // 把循环变量显式地传给协程\n\t}\n\tgroup.Wait()\n}\n```\n\n#### 1.9.2【必须】禁止并发写map\n\n- 并发写map容易造成程序崩溃并异常退出，建议加锁保护\n```go\n// bad\nfunc main() {\n\tm := make(map[int]int)\n\t// 并发读写\n\tgo func() {\n\t\tfor {\n\t\t\t_ = m[1]\n\t\t}\n\t}()\n\tgo func() {\n\t\tfor {\n\t\t\tm[2] = 1\n\t\t}\n\t}()\n\tselect {}\n}\n```\n#### 1.9.3【必须】确保并发安全\n\n敏感操作如果未作并发安全限制，可导致数据读写异常，造成业务逻辑限制被绕过。可通过同步锁或者原子操作进行防护。\n\n通过同步锁共享内存\n\n```go\n// good\nvar count int\n\nfunc Count(lock *sync.Mutex) {\n\tlock.Lock() // 加写锁\n\tcount++\n\tfmt.Println(count)\n\tlock.Unlock() // 解写锁，任何一个Lock()或RLock()均需要保证对应有Unlock()或RUnlock()\n}\n\nfunc main() {\n\tlock := &sync.Mutex{}\n\tfor i := 0; i < 10; i++ {\n\t\tgo Count(lock) // 传递指针是为了防止函数内的锁和调用锁不一致\n\t}\n\tfor {\n\t\tlock.Lock()\n\t\tc := count\n\t\tlock.Unlock()\n\t\truntime.Gosched() // 交出时间片给协程\n\t\tif c > 10 {\n\t\t\tbreak\n\t\t}\n\t}\n}\n```\n- 使用`sync/atomic`执行原子操作\n\n```go\n// good\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\nfunc main() {\n\ttype Map map[string]string\n\tvar m atomic.Value\n\tm.Store(make(Map))\n\tvar mu sync.Mutex // used only by writers\n\tread := func(key string) (val string) {\n\t\tm1 := m.Load().(Map)\n\t\treturn m1[key]\n\t}\n\tinsert := func(key, val string) {\n\t\tmu.Lock() // 与潜在写入同步\n\t\tdefer mu.Unlock()\n\t\tm1 := m.Load().(Map) // 导入struct当前数据\n\t\tm2 := make(Map)      // 创建新值\n\t\tfor k, v := range m1 {\n\t\t\tm2[k] = v\n\t\t}\n\t\tm2[key] = val\n\t\tm.Store(m2) // 用新的替代当前对象\n\t}\n\t_, _ = read, insert\n}\n```\n"
  },
  {
    "path": "JavaScript安全指南.md",
    "content": "<!-- markdown=\"1\" is required for GitHub Pages to render the TOC properly. -->\n\n<details markdown=\"1\">\n  <summary>目录</summary>\n\n-   [1 JavaScript页面类](#1)\n    *   [I. 代码实现](#1.1)\n\t\t+   [1.1 原生DOM API的安全操作](#1.1.1)\n\t\t+   [1.2 流行框架/库的安全操作](#1.1.2)\n\t\t+   [1.3 页面重定向](#1.1.3)\n\t\t+   [1.4 JSON解析/动态执行](#1.1.4)\n\t\t+   [1.5 跨域通讯](#1.1.5)\n    *   [II. 配置&环境](#1.2)\n\t\t+   [2.1 敏感/配置信息](#1.2.1)\n\t\t+   [2.2 第三方组件/资源](#1.2.2)\n\t\t+   [2.3 纵深安全防护](#1.2.3)\n\n-   [2 Node.js后台类](#2)\n    *   [I. 代码实现](#2.1)\n\t\t+   [1.1 输入验证](#2.1.1)\n\t\t+   [1.2 执行命令](#2.1.2)\n\t\t+   [1.3 文件操作](#2.1.3)\n\t\t+   [1.4 网络请求](#2.1.4)\n\t\t+   [1.5 数据输出](#2.1.5)\n\t\t+   [1.6 响应输出](#2.1.6)\n\t\t+   [1.7 执行代码](#2.1.7)\n\t\t+   [1.8 Web跨域](#2.1.8)\n\t\t+   [1.9 SQL操作](#2.1.9)\n\t\t+   [1.10 NoSQL操作](#2.1.10)\n\t\t+   [1.11 服务器端渲染（SSR）](#2.1.11)\n\t\t+   [1.12 URL跳转](#2.1.12)\n\t\t+   [1.13 Cookie与登录态](#2.1.13)\n    *   [II. 配置&环境](#2.2)\n\t\t+   [2.1 敏感/配置信息](#2.2.1)\n\t\t+   [2.2 第三方组件/资源](#2.2.2)\n\t\t+   [2.3 纵深安全防护](#2.2.3)\n\n</details>\n\n<a id=\"1\"></a>\n## JavaScript页面类\n\n<a id=\"1.1\"></a>\n### I. 代码实现\n\n<a id=\"1.1.1\"></a>\n#### 1.1 原生DOM API的安全操作\n\n**1.1.1【必须】HTML标签操作，限定/过滤传入变量值**\n\n- 使用`innerHTML=`、`outerHTML=`、`document.write()`、`document.writeln()`时，如变量值外部可控，应对特殊字符（`&, <, >, \", '`）做编码转义，或使用安全的DOM API替代，包括：`innerText=`\n\n```javascript\n// 假设 params 为用户输入， text 为 DOM 节点\n// bad：将不可信内容带入HTML标签操作\nconst { user } = params;\n// ...\ntext.innerHTML = `Follow @${user}`;\n\n// good: innerHTML操作前，对特殊字符编码转义\nfunction htmlEncode(iStr) {\n\tlet sStr = iStr;\n\tsStr = sStr.replace(/&/g, \"&amp;\");\n\tsStr = sStr.replace(/>/g, \"&gt;\");\n\tsStr = sStr.replace(/</g, \"&lt;\");\n\tsStr = sStr.replace(/\"/g, \"&quot;\");\n\tsStr = sStr.replace(/'/g, \"&#39;\");\n\treturn sStr;\n}\n\nlet { user } = params;\nuser = htmlEncode(user);\n// ...\ntext.innerHTML = `Follow @${user}`;\n\n// good: 使用安全的DOM API替代innerHTML\nconst { user } = params;\n// ...\ntext.innerText = `Follow @${user}`;\n```\n\n**1.1.2【必须】HTML属性操作，限定/过滤传入变量值**\n\n- 使用`element.setAttribute(name, value);`时，如第一个参数值`name`外部可控，应用白名单限定允许操作的属性范围。\n\n- 使用`element.setAttribute(name, value);`时，操作`a.href`、`ifame.src`、`form.action`、`embed.src`、`object.data`、`link.href`、`area.href`、`input.formaction`、`button.formaction`属性时，如第二个参数值`value`外部可控，应参考*JavaScript页面类规范1.3.1*部分，限定页面重定向或引入资源的目标地址。\n\n```javascript\n// good: setAttribute操作前，限定引入资源的目标地址\nfunction addExternalCss(e) {\n\tconst t = document.createElement('link');\n\tt.setAttribute('href', e),\n\tt.setAttribute('rel', 'stylesheet'),\n\tt.setAttribute('type', 'text/css'),\n\tdocument.head.appendChild(t)\n}\n\nfunction validURL(sUrl) {\n    return !!((/^(https?:\\/\\/)?[\\w\\-.]+\\.(qq|tencent)\\.com($|\\/|\\\\)/i).test(sUrl) || (/^[\\w][\\w/.\\-_%]+$/i).test(sUrl) || (/^[/\\\\][^/\\\\]/i).test(sUrl));\n}\n\nlet sUrl = \"https://evil.com/1.css\"\nif (validURL(sUrl)) {\n\taddExternalCss(sUrl);\n}\n```\n\n<a id=\"1.1.2\"></a>\n#### 1.2 流行框架/库的安全操作\n\n**1.2.1【必须】限定/过滤传入jQuery不安全函数的变量值**\n\n- 使用`.html()`、`.append()`、`.prepend()`、`.wrap()`、`.replaceWith()`、`.wrapAll()`、`.wrapInner()`、`.after()`、`.before()`时，如变量值外部可控，应对特殊字符（`&, <, >, \", '`）做编码转义。\n- 引入`jQuery 1.x（等于或低于1.12）、jQuery2.x（等于或低于2.2）`，且使用`$()`时，应优先考虑替换为最新版本。如一定需要使用，应对传入参数值中的特殊字符（`&, <, >, \", '`）做编码转义。\n\n```javascript\n// bad：将不可信内容，带入jQuery不安全函数.after()操作\nconst { user } = params;\n// ...\n$(\"p\").after(user);\n\n// good: jQuery不安全函数.html()操作前，对特殊字符编码转义\nfunction htmlEncode(iStr) {\n\tlet sStr = iStr;\n\tsStr = sStr.replace(/&/g, \"&amp;\");\n\tsStr = sStr.replace(/>/g, \"&gt;\");\n\tsStr = sStr.replace(/</g, \"&lt;\");\n\tsStr = sStr.replace(/\"/g, \"&quot;\");\n\tsStr = sStr.replace(/'/g, \"&#39;\");\n\treturn sStr;\n}\n\n// const user = params.user;\nuser = htmlEncode(user);\n// ...\n$(\"p\").html(user);\n```\n\n- 使用`.attr()`操作`a.href`、`ifame.src`、`form.action`、`embed.src`、`object.data`、`link.href`、`area.href`、`input.formaction`、`button.formaction`属性时，应参考*JavaScript页面类规范1.3.1*部分，限定重定向的资源目标地址。\n\n- 使用`.attr(attributeName, value)`时，如第一个参数值`attributeName`外部可控，应用白名单限定允许操作的属性范围。\n\n- 使用`$.getScript(url [, success ])`时，如第一个参数值`url`外部可控（如：从URL取值拼接，请求jsonp接口），应限定可控变量值的字符集范围为：`[a-zA-Z0-9_-]+`。\n\n**1.2.2【必须】限定/过滤传入Vue.js不安全函数的变量值**\n\n- 使用`v-html`时，不允许对用户提供的内容使用HTML插值。如业务需要，应先对不可信内容做富文本过滤。\n\n```html\n// bad：直接渲染外部传入的不可信内容\n<div v-html=\"userProvidedHtml\"></div>\n\n// good：使用富文本过滤库处理不可信内容后渲染\n<!-- 使用 -->\n<div v-xss-html=\"{'mode': 'whitelist', dirty: html, options: options}\" ></div>\n\n<!-- 配置 -->\n<script>\n\tnew Vue({\n\tel: \"#app\",\n\tdata: {\n\t\toptions: {\n\t\t\twhiteList: {\n\t\t\t\ta: [\"href\", \"title\", \"target\", \"class\", \"id\"],\n\t\t\t\tdiv: [\"class\", \"id\"],\n\t\t\t\tspan: [\"class\", \"id\"],\n\t\t\t\timg: [\"src\", \"alt\"],\n\t\t\t},\n\t\t},\n\t},\n});\n</script>\n```\n\n\n- 使用`v-bind`操作`a.href`、`ifame.src`、`form.action`、`embed.src`、`object.data`、`link.href`、`area.href`、`input.formaction`、`button.formaction`时，应确保后端已参考*JavaScript页面类规范1.3.1*部分，限定了供前端调用的重定向目标地址。\n\n- 使用`v-bind`操作`style`属性时，应只允许外部控制特定、可控的CSS属性值\n\n```html\n// bad：v-bind允许外部可控值，自定义CSS属性及数值\n<a v-bind:href=\"sanitizedUrl\" v-bind:style=\"userProvidedStyles\">\nclick me\n</a>\n\n// good：v-bind只允许外部提供特性、可控的CSS属性值\n<a v-bind:href=\"sanitizedUrl\" v-bind:style=\"{\ncolor: userProvidedColor,\nbackground: userProvidedBackground\n}\" >\nclick me\n</a>\n```\n\n<a id=\"1.1.3\"></a>\n#### 1.3 页面重定向\n\n**1.3.1【必须】限定跳转目标地址**\n\n- 使用白名单，限定重定向地址的协议前缀（默认只允许HTTP、HTTPS）、域名（默认只允许公司根域），或指定为固定值；\n\n- 适用场景包括，使用函数方法：`location.href`、`window.open()`、`location.assign()`、`location.replace()`；赋值或更新HTML属性：`a.href`、`ifame.src`、`form.action`、`embed.src`、`object.data`、`link.href`、`area.href`、`input.formaction`、`button.formaction`；\n\n```javascript\n// bad: 跳转至外部可控的不可信地址\nconst sTargetUrl = getURLParam(\"target\");\nlocation.replace(sTargetUrl);\n\n// good: 白名单限定重定向地址\nfunction validURL(sUrl) {\n\treturn !!((/^(https?:\\/\\/)?[\\w\\-.]+\\.(qq|tencent)\\.com($|\\/|\\\\)/i).test(sUrl) || (/^[\\w][\\w/.\\-_%]+$/i).test(sUrl) || (/^[/\\\\][^/\\\\]/i).test(sUrl));\n}\n\nconst sTargetUrl = getURLParam(\"target\");\nif (validURL(sTargetUrl)) {\n\tlocation.replace(sTargetUrl);\n}\n\n// good: 制定重定向地址为固定值\nconst sTargetUrl = \"http://www.qq.com\";\nlocation.replace(sTargetUrl);\n```\n\n<a id=\"1.1.4\"></a>\n#### 1.4 JSON解析/动态执行\n\n**1.4.1【必须】使用安全的JSON解析方式**\n\n- 应使用`JSON.parse()`解析JSON字符串。低版本浏览器，应使用安全的[Polyfill封装](https://github.com/douglascrockford/JSON-js/blob/master/json2.js)\n\n```javascript\n// bad: 直接调用eval解析json\nconst sUserInput = getURLParam(\"json_val\");\nconst jsonstr1 = `{\"name\":\"a\",\"company\":\"b\",\"value\":\"${sUserInput}\"}`;\nconst json1 = eval(`(${jsonstr1})`);\n\n// good: 使用JSON.parse解析\nconst sUserInput = getURLParam(\"json_val\");\nJSON.parse(sUserInput, (k, v) => {\n\tif (k === \"\") return v;\n\treturn v * 2;\n});\n\n// good: 低版本浏览器，使用安全的Polyfill封装（基于eval）\n<script src=\"https://github.com/douglascrockford/JSON-js/blob/master/json2.js\"></script>;\nconst sUserInput = getURLParam(\"json_val\");\nJSON.parse(sUserInput);\n```\n\n<a id=\"1.1.5\"></a>\n#### 1.5 跨域通讯\n\n**1.5.1【必须】使用安全的前端跨域通信方式**\n\n- 具有隔离登录态（如：p_skey）、涉及用户高敏感信息的业务（如：微信网页版、QQ空间、QQ邮箱、公众平台），禁止通过`document.domain`降域，实现前端跨域通讯，应使用postMessage替代。\n\n**1.5.2【必须】使用postMessage应限定Origin**\n\n- 在message事件监听回调中，应先使用`event.origin`校验来源，再执行具体操作。\n\n- 校验来源时，应使用`===`判断，禁止使用`indexOf()`\n\n```javascript\n// bad: 使用indexOf校验Origin值\nwindow.addEventListener(\"message\", (e) => {\n\tif (~e.origin.indexOf(\"https://a.qq.com\")) {\n\t// ...\n\t} else {\n\t// ...\n\t}\n});\n\n// good: 使用postMessage时，限定Origin，且使用===判断\nwindow.addEventListener(\"message\", (e) => {\n\tif (e.origin === \"https://a.qq.com\") {\n\t// ...\n\t}\n});\n```\n\n<a id=\"1.2\"></a>\n### II. 配置&环境\n\n<a id=\"1.2.1\"></a>\n#### 2.1 敏感/配置信息\n\n**2.1.1【必须】禁止明文硬编码AK/SK**\n\n- 禁止前端页面的JS明文硬编码AK/SK类密钥，应封装成后台接口，AK/SK保存在后端配置中心或密钥管理系统\n\n<a id=\"1.2.2\"></a>\n#### 2.2 第三方组件/资源\n\n**2.2.1【必须】使用可信范围内的统计组件**\n\n**2.2.2 【必须】禁止引入非可信来源的第三方JS**\n\n<a id=\"1.2.3\"></a>\n#### 2.3 纵深安全防护\n\n**2.3.1【推荐】部署CSP，并启用严格模式**\n\n<a id=\"2\"></a>\n## Node.js后台类\n\n<a id=\"2.1\"></a>\n### I. 代码实现\n\n<a id=\"2.1.1\"></a>\n#### 1.1 输入验证\n\n**1.1.1【必须】按类型进行数据校验**\n\n- 所有程序外部输入的参数值，应进行数据校验。校验内容包括但不限于：数据长度、数据范围、数据类型与格式。校验不通过，应拒绝。\n\n```javascript\n// bad：未进行输入验证\nRouter.get(\"/vulxss\", (req, res) => {\n\tconst { txt } = req.query;\n\tres.set(\"Content-Type\", \"text/html\");\n\tres.send({\n\t\tdata: txt,\n\t});\n});\n\n// good：按数据类型，进行输入验证\nconst Router = require(\"express\").Router();\nconst validator = require(\"validator\");\n\nRouter.get(\"/email_with_validator\", (req, res) => {\n\tconst txt = req.query.txt || \"\";\n\tif (validator.isEmail(txt)) {\n\t\tres.send({\n\t\t\tdata: txt,\n\t\t});\n\t} else {\n\t\tres.send({ err: 1 });\n\t}\n});\n```\n\n*关联漏洞：纵深防护措施 - 安全性增强特性*\n\n<a id=\"2.1.2\"></a>\n#### 1.2 执行命令\n\n**1.2.1 【必须】使用child_process执行系统命令，应限定或校验命令和参数的内容**\n\n- 适用场景包括：`child_process.exec`, `child_process.execSync`, `child_process.spawn`, `child_process.spawnSync`, `child_process.execFile`, `child_process.execFileSync`\n\n- 调用上述函数，应首先考虑限定范围，供用户选择。\n\n- 使用`child_process.exec`或`child_process.execSync`时，如果可枚举输入的参数内容或者格式，则应限定白名单。如果无法枚举命令或参数，则必须过滤或者转义指定符号，包括：```|;&$()><`!```\n\n- 使用`child_process.spawn` 或`child_process.execFile`时，应校验传入的命令和参数在可控列表内。\n\n```js\nconst Router = require(\"express\").Router();\nconst validator = require(\"validator\");\nconst { exec } = require('child_process');\n\n// bad：未限定或过滤，直接执行命令\nRouter.get(\"/vul_cmd_inject\", (req, res) => {\n\tconst txt = req.query.txt || \"echo 1\";\n\texec(txt, (err, stdout, stderr) => {\n\t\tif (err) { res.send({ err: 1 }) }\n\t\tres.send({stdout, stderr});\n\t});\n});\n\n// good：通过白名单，限定外部可执行命令范围\nRouter.get(\"/not_vul_cmd_inject\", (req, res) => {\n\tconst txt = req.query.txt || \"echo 1\";\n  const phone = req.query.phone || \"\";\n\tconst cmdList = {\n    \tsendmsg: \"./sendmsg \"\n\t};\n\tif (txt in cmdList && validator.isMobilePhone(phone)) {\n        exec(cmdList[txt] + phone, (err, stdout, stderr) => {\n          if (err) { res.send({ err: 1 }) };\n          res.send({stdout, stderr});\n        });\n\t} else {\n\t\tres.send({\n\t\t\terr: 1,\n\t\t\ttips: `you can use '${Object.keys(cmdList)}'`,\n\t\t});\n\t}\n});\n\n// good：执行命令前，过滤/转义指定符号\nRouter.get(\"/not_vul_cmd_inject\", (req, res) => {\n\tconst txt = req.query.txt || \"echo 1\";\n  let phone = req.query.phone || \"\";\n\tconst cmdList = {\n    \tsendmsg: \"./sendmsg \"\n\t};\n\tphone = phone.replace(/(\\||;|&|\\$\\(|\\(|\\)|>|<|\\`|!)/gi,\"\");\n\tif (txt in cmdList) {\n        exec(cmdList[txt] + phone, (err, stdout, stderr) => {\n          if (err) { res.send({ err: 1 }) };\n          res.send({stdout, stderr});\n        });\n\t} else {\n\t\tres.send({\n\t\t\terr: 1,\n\t\t\ttips: `you can use '${Object.keys(cmdList)}'`,\n\t\t});\n\t}\n});\n\n```\n\n*关联漏洞：高风险 - 任意命令执行*\n\n<a id=\"2.1.3\"></a>\n#### 1.3 文件操作\n\n**1.3.1 【必须】限定文件操作的后缀范围**\n\n- 按业务需求，使用白名单限定后缀范围。\n\n**1.3.2 【必须】校验并限定文件路径范围**\n\n- 应固定上传、访问文件的路径。若需要拼接外部可控变量值，检查是否包含`..`、`.`路径穿越字符。如存在，应拒绝。\n- 使用`fs`模块下的函数方法时，应对第一个参数即路径部分做校验，检查是否包含路径穿越字符`.`或`..`。涉及方法包括但不限于：`fs.truncate`、`fs.truncateSync`、`fs.chown`、`fs.chownSync`、`fs.lchown`、`fs.lchownSync`、`fs.stat`、`fs.lchmodSync`、`fs.lstat`、`fs.statSync`、`fs.lstatSync`、`fs.readlink`、`fs.unlink`、`fs.unlinkSync`、`fs.rmdir`、`fs.rmdirSync`、`fs.mkdir`、`fs.mkdirSync`、`fs.readdir`、`fs.readdirSync`、`fs.openSync`、`fs.open`、`fs.createReadStream`、`fs.createWriteStream`\n- 使用express框架的`sendFile`方法时，应对第一个参数即路径部分做校验，检查是否包含路径穿越字符`.`或`..`\n- 校验时，应使用`path`模块处理前的路径参数值，或判断处理过后的路径是否穿越出了当前工作目录。涉及方法包括但不限于：`path.resolve`、`path.join`、`path.normalize`等\n\n```javascript\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nlet filename = req.query.ufile;\nlet root = '/data/ufile';\n\n// bad：未检查文件名/路径\nfs.readFile(root + filename, (err, data) => {\n\tif (err) {\n\t\treturn console.error(err);\n\t}\n\tconsole.log(`异步读取: ${data.toString()}`);\n});\n\n// bad：使用path处理过后的路径参数值做校验，仍可能有路径穿越风险\nfilename = path.join(root, filename);\nif (filename.indexOf(\"..\") < 0) {\n\tfs.readFile(filename, (err, data) => {\n\t\tif (err) {\n\t\t\treturn console.error(err);\n\t\t}\n\t\tconsole.log(data.toString());\n\t});\n};\n\n// good：检查了文件名/路径，是否包含路径穿越字符\nif (filename.indexOf(\"..\") < 0) {\n\tfilename = path.join(root, filename);\n\tfs.readFile(filename, (err, data) => {\n\t\tif (err) {\n\t\t\treturn console.error(err);\n\t\t}\n\t\tconsole.log(data.toString());\n\t});\n};\n```\n\n**1.3.3 【必须】安全地处理上传文件名**\n\n- 将上传文件重命名为16位以上的随机字符串保存。\n- 如需原样保留文件名，应检查是否包含`..`、`.`路径穿越字符。如存在，应拒绝。\n\n**1.3.4 【必须】敏感资源文件，应有加密、鉴权和水印等加固措施**\n\n- 用户上传的`身份证`、`银行卡`等图片，属敏感资源文件，应采取安全加固。\n- 指向此类文件的URL，应保证不可预测性；同时，确保无接口会批量展示此类资源的URL。\n- 访问敏感资源文件时，应进行权限控制。默认情况下，仅用户可查看、操作自身敏感资源文件。\n- 图片类文件应添加业务水印，表明该图片仅可用于当前业务使用。\n\n<a id=\"2.1.4\"></a>\n#### 1.4 网络请求\n\n**1.4.1 【必须】限定访问网络资源地址范围**\n\n- 应固定程序访问网络资源地址的`协议`、`域名`、`路径`范围。\n\n- 若业务需要，外部可指定访问网络资源地址，应禁止访问内网私有地址段及域名。\n```\n// 以RFC定义的专有网络为例，如有自定义私有网段亦应加入禁止访问列表。\n10.0.0.0/8\n172.16.0.0/12\n192.168.0.0/16\n127.0.0.0/8\n```\n\n**1.4.2 【推荐】请求网络资源，应加密传输**\n\n- 应优先选用https协议请求网络资源\n\n*关联漏洞：高风险 - SSRF，高风险 - HTTP劫持*\n\n<a id=\"2.1.5\"></a>\n#### 1.5 数据输出\n\n**1.5.1 【必须】高敏感信息禁止存储、展示**\n\n- 口令、密保答案、生理标识等鉴权信息禁止展示\n- 非金融类业务，信用卡cvv码及日志禁止存储\n\n**1.5.2【必须】一般敏感信息脱敏展示**\n\n- 身份证只显示第一位和最后一位字符，如：`3*********************1`\n- 移动电话号码隐藏中间6位字符，如：`134***************48`\n- 工作地址/家庭地址最多显示到`区`一级\n- 银行卡号仅显示最后4位字符，如：`*********************8639`\n\n**1.5.3 【推荐】返回的字段按业务需要输出**\n\n- 按需输出，避免不必要的用户信息泄露\n- 用户敏感数据应在服务器后台处理后输出，不可以先输出到客户端，再通过客户端代码来处理展示\n\n*关联漏洞：高风险 - 用户敏感信息泄露*\n\n<a id=\"2.1.6\"></a>\n#### 1.6 响应输出\n\n**1.6.1 【必须】设置正确的HTTP响应包类型**\n\n- 响应头Content-Type与实际响应内容，应保持一致。如：API响应数据类型是json，则响应头使用`application/json`；若为xml，则设置为`text/xml`。\n\n**1.6.2 【必须】添加安全响应头**\n\n- 所有接口、页面，添加响应头 `X-Content-Type-Options: nosniff`。\n- 所有接口、页面，添加响应头`X-Frame-Options `。按需合理设置其允许范围，包括：`DENY`、`SAMEORIGIN`、`ALLOW-FROM origin`。用法参考：[MDN文档](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options)\n- 推荐使用组件： [helmet](https://www.npmjs.com/package/helmet) \n\n**1.6.3 【必须】外部输入拼接到响应页面前，进行编码处理**\n\n| 场景                                                         | 编码规则                                                     |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| 输出点在HTML标签之间                                         | 需要对以下6个特殊字符进行HTML实体编码(&, <, >, \", ',/)。<br/>示例：<br/>& --> &amp;amp;<br/>< --> &amp;lt;<br/>>--> &amp;gt;<br/>\" --> &amp;quot;<br/>' --> &amp;#x27;  <br/>/ --> &amp;#x2F; |\n| 输出点在HTML标签普通属性内（如href、src、style等，on事件除外） | 要对数据进行HTML属性编码。<br/>编码规则：除了阿拉伯数字和字母，对其他所有的字符进行编码，只要该字符的ASCII码小于256。编码后输出的格式为&#xHH;(以&#x开头，HH则是指该字符对应的十六进制数字，分号作为结束符) |\n| 输出点在JS内的数据中                                         | 需要进行js编码<br/>编码规则：<br/>除了阿拉伯数字和字母，对其他所有的字符进行编码，只要该字符的ASCII码小于256。编码后输出的格式为 \\xHH （以 \\x 开头，HH则是指该字符对应的十六进制数字）<br/>Tips：这种场景仅限于外部数据拼接在js里被引号括起来的变量值中。除此之外禁止直接将代码拼接在js代码中。 |\n| 输出点在CSS中（Style属性）                                   | 需要进行CSS编码<br/>编码规则：<br/>除了阿拉伯数字和字母，对其他所有的字符进行编码，只要该字符的ASCII码小于256。编码后输出的格式为 \\HH （以 \\ 开头，HH则是指该字符对应的十六进制数字） |\n| 输出点在URL属性中                                            | 对这些数据进行URL编码<br/>Tips：除此之外，所有链接类属性应该校验其协议。禁止JavaScript、data和Vb伪协议。 |\n\n**1.6.4 【必须】响应禁止展示物理资源、程序内部代码逻辑等敏感信息**\n\n- 业务生产（正式）环境，应用异常时，响应内容禁止展示敏感信息。包括但不限于：`物理路径`、`程序内部源代码`、`调试日志`、`内部账号名`、`内网ip地址`等。\n\n```\n// bad\nAccess denied for user 'xxx'@'xx.xxx.xxx.162' (using password: NO)\"\n```\n\n**1.6.5 【推荐】添加安全纵深防御措施**\n\n- 部署CSP，规则中应引入最新的严格模式特性`nonce-`\n\n```javascript\n// good：使用helmet组件安全地配置响应头\nconst express = require(\"express\");\nconst helmet = require(\"helmet\");\nconst app = express();\napp.use(helmet());\n\n// good：正确配置Content-Type、添加了安全响应头，引入了CSP\nRouter.get(\"/\", (req, res) => {\n\tres.header(\"Content-Type\", \"application/json\");\n\tres.header(\"X-Content-Type-Options\", \"nosniff\");\n\tres.header(\"X-Frame-Options\", \"SAMEORIGIN\");\n\tres.header(\"Content-Security-Policy\", \"script-src 'self'\");\n});\n```\n\n*关联漏洞：中风险 - XSS、中风险 - 跳转漏洞*\n\n<a id=\"2.1.7\"></a>\n#### 1.7 执行代码\n\n**1.7.1 【必须】安全的代码执行方式**\n\n- 禁止使用 `eval` 函数\n- 禁止使用`new Function(\"input\")()` 来创建函数\n- 使用 `setInteval`，`setTimeout`，应校验传入的参数\n\n*关联漏洞：高风险 - 代码执行漏洞*\n\n<a id=\"2.1.8\"></a>\n#### 1.8 Web跨域\n\n**1.8.1 【必须】限定JSONP接口的callback字符集范围**\n\n- JSONP接口的callback函数名为固定白名单。如callback函数名可用户自定义，应限制函数名仅包含 字母、数字和下划线。如：`[a-zA-Z0-9_-]+`\n\n**1.8.2 【必须】安全的CORS配置**\n\n- 使用CORS，应对请求头Origin值做严格过滤、校验。具体来说，可以使用“全等于”判断，或使用严格的正则进行判断。如：`^https://domain\\.qq\\.com$`\n\n```javascript\n// good：使用全等于，校验请求的Origin\nif (req.headers.origin === 'https://domain.qq.com') {\n    res.setHeader('Access-Control-Allow-Origin', req.headers.origin);\n    res.setHeader('Access-Control-Allow-Credentials', true);\n}\n```\n\n*关联漏洞：中风险 - XSS，中风险 - CSRF，中风险 - CORS配置不当*\n\n<a id=\"2.1.9\"></a>\n#### 1.9 SQL操作\n\n**1.9.1 【必须】SQL语句默认使用预编译并绑定变量**\n\n- 应使用预编译绑定变量的形式编写sql语句，保持查询语句和数据相分离\n\n  ```javascript\n  // bad：拼接SQL语句查询，存在安全风险\n  const mysql  = require(\"mysql\");\n  const connection = mysql.createConnection(options);\n  connection.connect();\n  \n  const sql = util.format(\"SELECT * from some_table WHERE Id = %s and Name = %s\", req.body.id, req.body.name);\n  connection.query(sql, (err, result) => {\n  \t// handle err..\n  });  \n  \n  // good：使用预编译绑定变量构造SQL语句\n  const mysql  = require(\"mysql\");\n  const connection = mysql.createConnection(options);\n  connection.connect();\n  \n  const sql = \"SELECT * from some_table WHERE Id = ? and Name = ?\";\n  const sqlParams = [req.body.id, req.body.name];\n  connection.query(sql, sqlParams, (err, result) => {\n  \t// handle err..\n  });\n  ```\n\n- 对于表名、列名等无法进行预编译的场景，如：`__user_input__` 拼接到比如 `limit`, `order by`, `group by` , `from tablename`语句中。请使用以下方法：\n\n  *方案1：使用白名单校验表名/列名*\n\n  ```javascript\n  // good\n  const tableSuffix = req.body.type;\n  if ([\"expected1\", \"expected2\"].indexOf(tableSuffix) < 0) {\n  \t// 不在表名白名单中，拒绝请求\n  \treturn ;\n  }\n  const sql = `SELECT * from t_business_${tableSuffix}`;\n  connection.query(sql, (err, result) => {\n  \t// handle err..\n  });\n  ```\n\n  *方案2：使用反引号包裹表名/列名，并过滤 `__user_input__` 中的反引号*\n\n  ```javascript\n  // good\n  let { orderType } = req.body;\n  // 过滤掉__user_input__中的反引号\n  orderType = orderType.replace(\"`\", \"\");\n  const sql = util.format(\"SELECT * from t_business_feeds order by `%s`\", orderType);\n  connection.query(sql, (err, result) => {\n  \t// handle err..\n  });\n  ```\n\n  *方案3：将 `__user_input__` 转换为整数*\n\n  ```javascript\n  // good\n  let { orderType } = req.body;\n  // 强制转换为整数\n  orderType = parseInt(orderType, 10);\n  const sql = `SELECT * from t_business_feeds order by ${orderType}`;\n  connection.query(sql, (err, result) => {\n  \t// handle err..\n  });\n  ```\n\n**1.9.2 【必须】安全的ORM操作**\n\n- 使用安全的ORM组件进行数据库操作。如 `sequelize` 等\n\n- 禁止`__user_input__`以拼接的方式直接传入ORM的各类raw方法\n\n```javascript\n//bad: adonisjs ORM\n//参考：https://adonisjs.com/docs/3.2/security-introduction#_sql_injection\nconst username = request.param(\"username\");\nconst users = yield Database\n  .table(\"users\")\n  .where(Database.raw(`username = ${username}`));\n\n//good: adonisjs ORM\nconst username = request.param(\"username\");\nconst users = yield Database\n  .table('users')\n  .where(Database.raw(\"username = ?\", [username]));\n```\n\n- 使用ORM进行Update/Insert操作时，应限制操作字段范围\n\n```javascript\n/*\ngood\n假设该api用于插入用户的基本信息，使用传入的req.body通过Sequelize的create方法实现\n假设User包含字段：username,email,isAdmin，\n其中,isAdmin将会用于是否系统管理员的鉴权，默认值为false\n*/\n// Sequelize: 只允许变更username、email字段值\nUser.create(req.body, { fields: [\"username\", \"email\"] }).then((user) => {\n\t// handle the rest..\n});\n```\n> **为什么要这么做？**\n> 在上述案例中，若不限定fields值，攻击者将可传入`{\"username\":\"boo\",\"email\":\"foo@boo.com\",\"isAdmin\":true}`将自己变为`Admin`，产生垂直越权漏洞。\n\n*关联漏洞：高风险 - SQL注入，中风险 - Mass Assignment 逻辑漏洞*\n\n<a id=\"2.1.10\"></a>\n#### 1.10 NoSQL操作\n\n**1.10.1 【必须】校验参数值类型**\n\n- 将HTTP参数值代入NoSQL操作前，应校验类型。如非功能需要，禁止对象（Object）类型传入。\n\n```javascript\n// bad：执行NOSQL操作前，未作任何判断\napp.post(\"/\", (req, res) => {\n\tdb.users.find({ username: req.body.username, password: req.body.password }, (err, users) => {\n\t// **TODO:** handle the rest\n\t});\n});\n\n// good：在进入nosql前先判断`__USER_INPUT__`是否为字符串。\napp.post(\"/\", (req, res) => {\n\tif (req.body.username && typeof req.body.username !== \"string\") {\n\t\treturn new Error(\"username must be a string\");\n\t}\n\tif (req.body.password && typeof req.body.password !== \"string\") {\n\t\treturn new Error(\"password must be a string\");\n\t}\n\tdb.users.find({ username: req.body.username, password: req.body.password }, (err, users) => {\n\t\t// **TODO:** handle the rest\n\t});\n});\n```\n\n> **为什么要这么做？**\n>\n> JavaScript中，从http或socket接收的数据可能不是单纯的字符串，而是被黑客精心构造的对象(Object)。在本例中：\n>\n> - 期望接收的POST数据：`username=foo&password=bar` \n> - 期望的等价条件查询sql语句：`select * from users where username = 'foo' and password = 'bar'`\n> - 黑客的精心构造的攻击POST数据：`username[$ne]=null&password[$ne]=null`或JSON格式：`{\"username\": {\"$ne\": null},\"password\": {\"$ne\": null}}`\n> - 黑客篡改后的等价条件查询sql语句：`select * from users where username != null & password != null`\n> - 黑客攻击结果：绕过正常逻辑，在不知道他人的username/password的情况登录他人账号。\n\n**1.10.2 【必须】NoSQL操作前，应校验权限/角色**\n\n- 执行NoSQL增、删、改、查逻辑前，应校验权限\n\n```javascript\n// 使用express、mongodb(mongoose)实现的删除文章demo\n// bad：在删除文章前未做权限校验\napp.post(\"/deleteArticle\", (req, res) => {\n\tdb.articles.deleteOne({ article_id: req.body.article_id }, (err, users) => {\n\t\t// TODO: handle the rest\n\t});\n});\n\n// good：进入nosql语句前先进行权限校验\napp.post(\"/deleteArticle\", (req, res) => {\n\tcheckPriviledge(ctx.uin, req.body.article_id);\n\tdb.articles.deleteOne({ article_id: req.body.article_id }, (err, users) => {\n\t\t// TODO: handle the rest\n\t});\n});\n```\n\n*关联漏洞：高风险 - 越权操作，高风险 - NoSQL注入*\n\n<a id=\"2.1.11\"></a>\n#### 1.11 服务器端渲染（SSR）\n\n**1.11.1 【必须】安全的Vue服务器端渲染(Vue SSR)**\n\n- 禁止直接将不受信的外部内容传入`{{{ data }}}`表达式中\n\n- 模板内容禁止被污染\n\n```javascript\n// bad: 将用户输入替换进模板\nconst app = new Vue({\n\ttemplate: appTemplate.replace(\"word\", __USER_INPUT__),\n});\nrenderer.renderToString(app);\n```\n\n- 对已渲染的HTML文本内容（renderToString后的html内容）。如需再拼不受信的外部输入，应先进行安全过滤，具体请参考**1.6.3**\n\n```javascript\n// bad: 渲染后的html再拼接不受信的外部输入\nreturn new Promise(((resolve) => {\n\trenderer.renderToString(component, (err, html) => {\n\t\tlet htmlOutput = html;\n\t\thtmlOutput += `${__USER_INPUT__}`;\n\t\tresolve(htmlOutput);\n\t});\n}));\n```\n\n**1.11.2 【必须】安全地使用EJS、LoDash、UnderScore进行服务器端渲染**\n\n- 使用render函数时，模板内容禁止被污染\n\n  lodash.Template:\n\n  ```js\n  // bad: 将用户输入送进模板\n  const compiled = _.template(`<b>${__USER_INPUT__}<%- value %></b>`);\n  compiled({ value: \"hello\" });\n  ```\n\n  ejs:\n\n  ```javascript\n  // bad: 将用户输入送进模板\n  const ejs = require(\"ejs\");\n  const people = [\"geddy\", \"neil\", \"alex\"];\n  const html = ejs.render(`<%= people.join(\", \"); %>${__USER_INPUT__}`, { people });\n  ```\n\n- Ejs、LoDash、UnderScore提供的HTML插值模板默认形似`<%= data %>`，尽管在默认情况下`<%= data %>`存在过滤，在编写HTML插值模板时需注意:\n\n  1. 用户输入流入html属性值时，必须使用双引号包裹：`<base data-id = \"<%= __USER_INPUT__ %>\">`\n  2. 用户输入流入`<script></script>`标签或on*的html属性中时，如`<script>var id = <%= __USER_INPUT__ %></script>` ，须按照1.6.3中的做法或白名单方法进行过滤，框架/组件的过滤在此处不起作用\n\n**1.11.3 【必须】在自行实现状态存储容器并将其JSON.Stringify序列化后注入到HTML时，必须进行安全过滤**\n\n<a id=\"2.1.12\"></a>\n#### 1.12 URL跳转\n\n**1.12.1【必须】限定跳转目标地址**\n\n- 适用场景包括：\n1. 使用30x返回码并在Header中设置Location进行跳转\n2. 在返回页面中打印`<script>location.href=__Redirection_URL__</script>`\n\n- 使用白名单，限定重定向地址的协议前缀（默认只允许HTTP、HTTPS）、域名（默认只允许公司根域），或指定为固定值；\n\n```javascript\n// 使用express实现的登录成功后的回调跳转页面\n\n// bad: 未校验页面重定向地址\napp.get(\"/login\", (req, res) => {\n\t// 若未登录用户访问其他页面，则让用户导向到该处理函数进行登录\n  // 使用参数loginCallbackUrl记录先前尝试访问的url，在登录成功后跳转回loginCallbackUrl:\n\tconst { loginCallbackUrl } = req.query;\n\tif (loginCallbackUrl) {\n    \tres.redirect(loginCallbackUrl);\n\t}\n});\n\n// good: 白名单限定重定向地址\nfunction isValidURL(sUrl) {\n\treturn !!((/^(https?:\\/\\/)?[\\w\\-.]+\\.(qq|tencent)\\.com($|\\/|\\\\)/i).test(sUrl) || (/^[\\w][\\w/.\\-_%]+$/i).test(sUrl) || (/^[/\\\\][^/\\\\]/i).test(sUrl));\n}\napp.get(\"/login\", (req, res) => {\n\t// 若未登录用户访问其他页面，则让用户导向到该处理函数进行登录\n  // 使用参数loginCallbackUrl记录先前尝试访问的url，在登录成功后跳转回loginCallbackUrl:\n\tconst { loginCallbackUrl } = req.query;\n\tif (loginCallbackUrl && isValidUrl(loginCallbackUrl)) {\n    \tres.redirect(loginCallbackUrl);\n\t}\n});\n\n// good: 白名单限定重定向地址，通过返回html实现\nfunction isValidURL(sUrl) {\n\treturn !!((/^(https?:\\/\\/)?[\\w\\-.]+\\.(qq|tencent)\\.com($|\\/|\\\\)/i).test(sUrl) || (/^[\\w][\\w/.\\-_%]+$/i).test(sUrl) || (/^[/\\\\][^/\\\\]/i).test(sUrl));\n}\napp.get(\"/login\", (req, res) => {\n\t// 若未登录用户访问其他页面，则让用户导向到该处理函数进行登录\n  // 使用参数loginCallbackUrl记录先前尝试访问的url，在登录成功后跳转回loginCallbackUrl:\n\tconst { loginCallbackUrl } = req.query;\n\tif (loginCallbackUrl && isValidUrl(loginCallbackUrl)) {\n\t\t// 使用encodeURI，过滤左右尖括号与双引号，防止逃逸出包裹的双引号\n\t\tconst redirectHtml = `<script>location.href = \"${encodeURI(loginCallbackUrl)}\";</script>`;\n    \tres.end(redirectHtml);\n\t}\n});\n```\n\n*关联漏洞：中风险 - 任意URL跳转漏洞*\n\n<a id=\"2.1.13\"></a>\n#### 1.13 Cookie与登录态\n\n**1.13.1【推荐】为Cookies中存储的关键登录态信息添加http-only保护**\n\n*关联漏洞：纵深防护措施 - 安全性增强特性*\n\n<a id=\"2.2\"></a>\n### II. 配置&环境\n\n<a id=\"2.2.1\"></a>\n#### 2.1 依赖库\n\n**2.1.1【必须】使用安全的依赖库**\n\n- 使用自动工具，检查依赖库是否存在后门/漏洞，保持最新版本\n\n<a id=\"2.2.2\"></a>\n#### 2.2 运行环境\n\n**2.2.1 【必须】使用非root用户运行Node.js**\n\n<a id=\"2.2.3\"></a>\n#### 2.3 配置信息\n\n**2.3.1【必须】禁止硬编码认证凭证**\n\n- 禁止在源码中硬编码`AK/SK`、`数据库账密`、`私钥证书`等配置信息\n- 应使用配置系统或KMS密钥管理系统。\n\n**2.3.2【必须】禁止硬编码IP配置**\n- 禁止在源码中硬编码`IP`信息\n> **为什么要这么做？**\n>\n> 硬编码IP可能会导致后续机器裁撤或变更时产生额外的工作量，影响系统的可靠性。\n\n**2.3.3【必须】禁止硬编码员工敏感信息**\n\n- 禁止在源代码中含员工敏感信息，包括但不限于：`员工ID`、`手机号`、`微信/QQ号`等。\n"
  },
  {
    "path": "Java安全指南.md",
    "content": "<details markdown=\"1\">\r\n  <summary>目录</summary>\r\n\r\n-   [1 安卓类](#1)\r\n    *   [I. 代码实现](#1.1)\r\n\t\t+   [1.1 异常捕获处理](#1.1.1)\r\n\t\t+   [1.2 数据泄露](#1.1.2)\r\n\t\t+   [1.3 webview 组件安全](#1.1.3)\r\n\t\t+   [1.4 传输安全](#1.1.4)\r\n    *   [II. 配置&环境](#1.2)\r\n\t\t+   [2.1 AndroidManifest.xml 配置](#1.2.1)\r\n-   [2 后台类](#2)\r\n    *   [I. 代码实现](#2.1)\r\n\t\t+   [1.1 数据持久化](#2.1.1)\r\n\t\t+   [1.2 文件操作](#2.1.2)\r\n\t\t+   [1.3 文件操作](#2.1.3)\r\n\t\t+   [1.4 XML读写](#2.1.4)\r\n\t\t+   [1.5 响应输出](#2.1.5)\r\n\t\t+   [1.6 OS命令执行](#2.1.6)\r\n\t\t+   [1.7 会话管理](#2.1.7)\r\n\t\t+   [1.8 加解密](#2.1.8)\r\n\t\t+   [1.9 查询业务](#2.1.9)\r\n\t\t+   [1.10 操作业务](#2.1.10)\r\n</details>\r\n\r\n<a id=\"1\"></a>\r\n## 安卓类\r\n<a id=\"1.1\"></a>\r\n### I. 代码实现\r\n<a id=\"1.1.1\"></a>\r\n#### 1.1 异常捕获处理\r\n##### 1.1.1 【必须】序列化异常捕获\r\n对于通过导出组件 intent 传递的序列化对象，必须进行 try...catch 处理，以避免数据非法导致应用崩溃。 \r\n```java\r\npublic class MainActivity extends Activity {\r\n\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        try {\r\n            Intent mIntent = getIntent(); \r\n            //String msg = intent.getStringExtra(\"data\"); \r\n            Person mPerson = (Person)mIntent.getSerializableExtra(ObjectDemo.SER_KEY)\r\n            //textView.setText(msg); \r\n        } catch (ClassNotFoundException exp) {\r\n            // ......\r\n        }\r\n    }\r\n}\r\n```\r\n##### 1.1.2 【必须】NullPointerException 异常捕获\r\n对于通过 intent getAction 方法获取数据时，必须进行 try...catch 处理，以避免空指针异常导致应用崩溃。\r\n```java\r\npublic class MainActivity extends Activity {\r\n    \r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        try {\r\n            Intent mIntent = getIntent(); \r\n            if mIntent.getAction().equals(\"StartNewWorld\") {\r\n                // ......\r\n            }\r\n            // ......\r\n        } catch (NullPointerException exp) {\r\n            // ......\r\n        }\r\n    }\r\n}\r\n```\r\n##### 1.1.3 【必须】ClassCastException 异常捕获\r\n对于通过 intent getSerializableExtra 方法获取数据时，必须进行 try...catch 处理，以避免类型转换异常导致应用崩溃。\r\n```java\r\npublic class MainActivity extends Activity {\r\n\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        try {\r\n            Intent mIntent = getIntent(); \r\n            Person mPerson = (Person)mIntent.getSerializableExtra(ObjectDemo.SER_KEY)\r\n            // ......\r\n        } catch (ClassCastException exp) {\r\n            // ......\r\n        }\r\n    }\r\n}\r\n```\r\n##### 1.1.4 【必须】ClassNotFoundException 异常捕获\r\n同 1.1.3\r\n\r\n<a id=\"1.1.2\"></a>\r\n#### 1.2 数据泄露\r\n##### 1.2.1 【必须】logcat 输出限制\r\nrelease 版本禁止在 logcat 输出信息。\r\n```java\r\npublic class MainActivity extends Activity {\r\n    String DEBUG = \"debug_version\";\r\n\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        // ......\r\n        if (DEBUG == \"debug_version\") {\r\n            Log.d(\"writelog\", \"start activity\");\r\n        }\r\n        // ......\r\n    }\r\n}\r\n```\r\n\r\n<a id=\"1.1.3\"></a>\r\n#### 1.3 webview 组件安全\r\n##### 1.3.1 【必须】addJavaScriptInterface 方法调用\r\n对于设置 minsdk <= 18 的应用，禁止调用 addJavaScriptInterface 方法。\r\n```java\r\npublic class MainActivity extends Activity {\r\n\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        // ......\r\n        mWebView = new WebView(this);\r\n        if (Build.VERSION.SDK_INT > 18) {\r\n            mWebView.addJavascriptInterface(new wPayActivity.InJavaScriptLocalObj(this), \"local_obj\");\r\n        }\r\n        // ......\r\n    }\r\n}\r\n```\r\n##### 1.3.2 【建议】setJavaScriptEnabled 方法调用\r\n如非必要，setJavaScriptEnabled 应设置为 false 。加载本地 html ，应校验 html 页面完整性，以避免 xss 攻击。\r\n```java\r\npublic class MainActivity extends Activity {\r\n\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        // ......\r\n        mWebView = new WebView(this);\r\n        mWebView.getSettings().setJavaScriptEnabled(false);\r\n        // ......\r\n    }\r\n}\r\n```\r\n##### 1.3.3 【建议】setAllowFileAccess 方法调用\r\n建议禁止使用 File 域协议，以避免过滤不当导致敏感信息泄露。\r\n```java\r\npublic class MainActivity extends Activity {\r\n\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        // ......\r\n        mWebView = new WebView(this);\r\n        mWebView.getSettings().setAllowFileAccess(false);\r\n        // ......\r\n    }\r\n}\r\n```\r\n##### 1.3.4 【建议】setSavePassword 方法调用\r\n建议 setSavePassword 的设置为 false ，避免明文保存网站密码。\r\n```java\r\npublic class MainActivity extends Activity {\r\n\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        // ......\r\n        mWebView = new WebView(this);\r\n        mWebView.getSettings().setSavePassword(false);\r\n        // ......\r\n    }\r\n}\r\n```\r\n##### 1.3.5 【必须】onReceivedSslError 方法调用\r\nwebview 组件加载网页发生证书认证错误时，不能直接调用 handler.proceed() 忽略错误，应当处理当前场景是否符合业务预期，以避免中间人攻击劫持。\r\n```java\r\npublic class MainActivity extends Activity {\r\n\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        // ......\r\n        mWebView = new WebView(this);\r\n        mWebView.setWebViewClient(new WebViewClient() {\r\n            @Override\r\n            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {\r\n                // must check error \r\n                check_error();\r\n                handler.proceed();\r\n            }\r\n        }\r\n        // ......\r\n    }\r\n}\r\n```\r\n<a id=\"1.1.4\"></a>\r\n#### 1.4 传输安全\r\n##### 1.4.1 【必须】自定义 HostnameVerifier 类\r\n自定义 HostnameVerifier 类后，必须实现 verify 方法校验域名，以避免中间人攻击劫持。\r\n```java\r\npublic class MainActivity extends Activity {\r\n    \r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        // ......\r\n        HostnameVerifier hnv = new HostnameVerifier() {\r\n            @Override\r\n            public boolean verify(String hostname, SSLSession session) {\r\n                // must to do\r\n                isValid = checkHostName(hostname);\r\n                return isValid;\r\n            }\r\n        };\r\n        // ......\r\n    }\r\n}\r\n```\r\n##### 1.4.2 【必须】自定义 X509TrustManager 类\r\n自定义 X509TrustManager 类后，必须实现 checkServerTrusted 方法校验服务器证书，以避免中间人攻击劫持。\r\n```java\r\npublic class MainActivity extends Activity {\r\n    \r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        // ......\r\n        TrustManager tm = new X509TrustManager() {\r\n            public void checkServerTrusted(X509Certificate[] chain, String authType)\r\n                    throws CertificateException {\r\n                // must to do\r\n                check_server_valid();\r\n            }\r\n        };\r\n        // ......\r\n    }\r\n}\r\n```\r\n##### 1.4.3 【必须】setHostnameVerifier 方法调用\r\n禁止调用 setHostnameVerifier 方法设置 ALLOW_ALL_HOSTNAME_VERIFIER 属性，以避免中间人攻击劫持。\r\n```java\r\npublic class MainActivity extends Activity {\r\n    \r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        // ......\r\n        SchemeRegistry schemeregistry = new SchemeRegistry();\r\n        SSLSocketFactory sslsocketfactory = SSLSocketFactory.getSocketFactory();\r\n        // set STRICT_HOSTNAME_VERIFIER\r\n        sslsocketfactory.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);\r\n        // ......\r\n    }\r\n}\r\n```\r\n\r\n<a id=\"1.2\"></a>\r\n### II. 配置&环境 \r\n<a id=\"1.2.1\"></a>\r\n#### 2.1 AndroidManifest.xml 配置\r\n##### 2.1.1 【必须】PermissionGroup 属性设置\r\n禁止设置 PermissionGroup 属性为空。\r\n##### 2.1.2 【必须】protectionLevel 属性设置\r\n对于自定义权限的 protectionLevel 属性设置，建议设置为 signature 或 signatureOrSystem。\r\n##### 2.1.3 【建议】sharedUserId 权限设置\r\n最小范围和最小权限使用 sharedUserId 设置。\r\n##### 2.1.4 【建议】allowBackup 备份设置\r\n如非产品功能需要，建议设置 allowBackup 为 false。\r\n```java\r\n<application android:allowBackup=\"false\"> \r\n</application>\r\n```\r\n##### 2.1.5 【必须】debuggable 调试设置\r\nrelease 版本禁止设置 debuggable 为 true。\r\n```java\r\n<application android:debuggable=\"false\"> \r\n</application>\r\n```\r\n\r\n\r\n<a id=\"2\"></a>\r\n## 后台类\r\n<a id=\"2.1\"></a>\r\n### I. 代码实现\r\n<a id=\"2.1.1\"></a>\r\n#### 1.1 数据持久化\r\n\r\n##### 1.1.1【必须】SQL语句默认使用预编译并绑定变量\r\n\r\nWeb后台系统应默认使用预编译绑定变量的形式创建sql语句，保持查询语句和数据相分离。以从本质上避免SQL注入风险。\r\n\r\n如使用Mybatis作为持久层框架，应通过\\#{}语法进行参数绑定，MyBatis 会创建 `PreparedStatement` 参数占位符，并通过占位符安全地设置参数。\r\n\r\n示例：JDBC\r\n\r\n```java\r\nString custname = request.getParameter(\"name\"); \r\nString query = \"SELECT * FROM user_data WHERE user_name = ? \";\r\nPreparedStatement pstmt = connection.prepareStatement( query );\r\npstmt.setString( 1, custname); \r\nResultSet results = pstmt.executeQuery( );\r\n```\r\n\r\nMybatis\r\n\r\n```java\r\n<select id=\"queryRuleIdByApplicationId\" parameterType=\"java.lang.String\" resultType=\"java.lang.String\">    \r\n      select rule_id from scan_rule_sqlmap_tab where application_id=#{applicationId} \r\n</select>\r\n\r\n```\r\n\r\n应避免外部输入未经过滤直接拼接到SQL语句中，或者通过Mybatis中的${}传入SQL语句（即使使用PreparedStatement，SQL语句直接拼接外部输入也同样有风险。例如Mybatis中部分参数通过${}传入SQL语句后实际执行时调用的是PreparedStatement.execute()，同样存在注入风险）。\r\n\r\n##### 1.1.2【必须】白名单过滤\r\n\r\n对于表名、列名等无法进行预编译的场景，比如外部数据拼接到order by, group by语句中，需通过白名单的形式对数据进行校验，例如判断传入列名是否存在、升降序仅允许输入“ASC”和“DESC”、表名列名仅允许输入字符、数字、下划线等。参考示例：\r\n\r\n```java\r\npublic String someMethod(boolean sortOrder) {\r\n String SQLquery = \"some SQL ... order by Salary \" + (sortOrder ? \"ASC\" : \"DESC\");`\r\n ...\r\n```\r\n\r\n<a id=\"2.1.2\"></a>\r\n#### 1.2 文件操作\r\n\r\n##### 1.2.1【必须】文件类型限制\r\n\r\n须在服务器端采用白名单方式对上传或下载的文件类型、大小进行严格的限制。仅允许业务所需文件类型上传，避免上传.jsp、.jspx、.class、.java等可执行文件。参考示例：\r\n\r\n```java\r\n       String file_name = file.getOriginalFilename();\r\n        String[] parts = file_name.split(\"\\\\.\");\r\n        String suffix = parts[parts.length - 1];\r\n        switch (suffix){\r\n            case \"jpeg\":\r\n                suffix = \".jpeg\";\r\n                break;\r\n            case \"jpg\":\r\n                suffix = \".jpg\";\r\n                break;\r\n            case \"bmp\":\r\n                suffix = \".bmp\";\r\n                break;\r\n            case \"png\":\r\n                suffix = \".png\";\r\n                break;\r\n            default:\r\n                //handle error\r\n                return \"error\";\r\n        }\r\n```\r\n\r\n##### 1.2.2【必须】禁止外部文件存储于可执行目录\r\n\r\n禁止外部文件存储于WEB容器的可执行目录（appBase）。建议保存在专门的文件服务器中。\r\n\r\n##### 1.2.3【建议】避免路径拼接\r\n\r\n文件目录避免外部参数拼接。保存文件目录建议后台写死并对文件名进行校验（字符类型、长度）。建议文件保存时，将文件名替换为随机字符串。\r\n\r\n##### 1.2.4【必须】避免路径穿越\r\n\r\n如因业务需要不能满足1.2.3的要求，文件路径、文件命中拼接了不可行数据，需判断请求文件名和文件路径参数中是否存在../或..\\\\(仅windows)， 如存在应判定路径非法并拒绝请求。\t\t\r\n\r\n<a id=\"2.1.3\"></a>\r\n#### 1.3 网络访问\r\n\r\n##### 1.3.1【必须】避免直接访问不可信地址\r\n\r\n服务器访问不可信地址时，禁止访问私有地址段及内网域名。\r\n```\r\n// 以RFC定义的专有网络为例，如有自定义私有网段亦应加入禁止访问列表。\r\n10.0.0.0/8\r\n172.16.0.0/12\r\n192.168.0.0/16\r\n127.0.0.0/8\r\n```\r\n\r\n建议通过URL解析函数进行解析，获取host或者domain后通过DNS获取其IP，然后和内网地址进行比较。\r\n\r\n对已校验通过地址进行访问时，应关闭跟进跳转功能。\r\n\r\n参考示例：\r\n\r\n```java\r\n     httpConnection = (HttpURLConnection) Url.openConnection();\r\n\r\n     httpConnection.setFollowRedirects(false);\r\n```\r\n\r\n<a id=\"2.1.4\"></a>\r\n#### 1.4 XML读写\r\n\r\n##### 1.4.1【必须】XML解析器关闭DTD解析\r\n\r\n读取外部传入XML文件时，XML解析器初始化过程中设置关闭DTD解析。\r\n\r\n参考示例：\r\n\r\njavax.xml.parsers.DocumentBuilderFactory\r\n\r\n```java\r\nDocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();\r\ntry {\r\n    dbf.setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true);\r\n    dbf.setFeature(\"http://xml.org/sax/features/external-general-entities\", false);\r\n    dbf.setFeature(\"http://xml.org/sax/features/external-parameter-entities\", false);\r\n    dbf.setFeature(\"http://apache.org/xml/features/nonvalidating/load-external-dtd\", false);\r\n    dbf.setXIncludeAware(false);\r\n    dbf.setExpandEntityReferences(false);\r\n    ……\r\n}\r\n```\r\n\r\norg.dom4j.io.SAXReader\r\n\r\n```java\r\nsaxReader.setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true);\r\nsaxReader.setFeature(\"http://xml.org/sax/features/external-general-entities\", false);\r\nsaxReader.setFeature(\"http://xml.org/sax/features/external-parameter-entities\", false);\r\n```\r\n\r\norg.jdom2.input.SAXBuilder\r\n\r\n```java\r\nSAXBuilder builder = new SAXBuilder();\r\nbuilder.setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\",true);\r\nbuilder.setFeature(\"http://xml.org/sax/features/external-general-entities\", false);\r\nbuilder.setFeature(\"http://xml.org/sax/features/external-parameter-entities\", false);\r\nDocument doc = builder.build(new File(fileName));\r\n```\r\n\r\norg.xml.sax.XMLReader\r\n\r\n```java\r\nXMLReader reader = XMLReaderFactory.createXMLReader();\r\nreader.setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true);\r\nreader.setFeature(\"http://apache.org/xml/features/nonvalidating/load-external-dtd\", false);\r\nreader.setFeature(\"http://xml.org/sax/features/external-general-entities\", false);\r\nreader.setFeature(\"http://xml.org/sax/features/external-parameter-entities\", false);\r\n```\r\n\r\n\r\n<a id=\"2.1.5\"></a>\r\n#### 1.5 响应输出\r\n\r\n##### 1.5.1【必须】设置正确的HTTP响应包类型\r\n\r\n响应包的HTTP头“Content-Type”必须正确配置响应包的类型，禁止非HTML类型的响应包设置为“text/html”。此举会使浏览器在直接访问链接时，将非HTML格式的返回报文当做HTML解析，增加反射型XSS的触发几率。\r\n\r\n##### 1.5.2【建议】设置安全的HTTP响应头\r\n\r\n- X-Content-Type-Options：\r\n\r\n​        建议添加“X-Content-Type-Options”响应头并将其值设置为“nosniff”，可避免部分浏览器根据其“Content-Sniff”特性，将一些非“text/html”类型的响应作为HTML解析，增加反射型XSS的触发几率。\r\n\r\n- HttpOnly：\r\n\r\n​         控制用户登录鉴权的Cookie字段 应当设置HttpOnly属性以防止被XSS漏洞/JavaScript操纵泄漏。\r\n\r\n- X-Frame-Options：\r\n\r\n​        设置X-Frame-Options响应头，并根据需求合理设置其允许范围。该头用于指示浏览器禁止当前页面在frame、iframe、embed等标签中展现。从而避免点击劫持问题。它有三个可选的值：\r\n​        DENY： 浏览器会拒绝当前页面加载任何frame页面；\r\n​\t\tSAMEORIGIN：则frame页面的地址只能为同源域名下的页面\r\n​\t\tALLOW-FROM origin：可以定义允许frame加载的页面地址。\r\n\r\n- Access-Control-Allow-Origin\r\n\r\n  当需要配置CORS跨域时，应对请求头的Origin值做严格过滤。\r\n\r\n  ```java\r\n  ...\r\n  String currentOrigin = request.getHeader(\"Origin\");\r\n  if (currentOrigin.equals(\"https://domain.qq.com\")) {\r\n         response.setHeader(\"Access-Control-Allow-Origin\", currentOrigin);\r\n             }\r\n   ...\r\n  ```\r\n\r\n  \r\n\r\n##### 1.5.3【必须】外部输入拼接到response页面前进行编码处理\r\n\r\n当响应“content-type”为“html”类型时，外部输入拼接到响应包中，需根据输出位置进行编码处理。编码规则：\r\n\r\n| 场景                                                         | 编码规则                                                     |\r\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\r\n| 输出点在HTML标签之间                                         | 需要对以下6个特殊字符进行HTML实体编码(&, <, >, \", ',/)。<br/>示例：<br/>& --> &amp;amp;<br/>< --> &amp;lt;<br/>>--> &amp;gt;<br/>\" --> &amp;quot;<br/>' --> &amp;#x27;  <br/>/ --> &amp;#x2F; |\r\n| 输出点在HTML标签普通属性内（如href、src、style等，on事件除外） | 要对数据进行HTML属性编码。<br/>编码规则：除了阿拉伯数字和字母，对其他所有的字符进行编码，只要该字符的ASCII码小于256。编码后输出的格式为&#xHH;(以&#x开头，HH则是指该字符对应的十六进制数字，分号作为结束符) |\r\n| 输出点在JS内的数据中                                         | 需要进行js编码<br/>编码规则：<br/>除了阿拉伯数字和字母，对其他所有的字符进行编码，只要该字符的ASCII码小于256。编码后输出的格式为 \\xHH （以 \\x 开头，HH则是指该字符对应的十六进制数字）<br/>Tips：这种场景仅限于外部数据拼接在js里被引号括起来的变量值中。除此之外禁止直接将代码拼接在js代码中。 |\r\n| 输出点在CSS中（Style属性）                                   | 需要进行CSS编码<br/>编码规则：<br/>除了阿拉伯数字和字母，对其他所有的字符进行编码，只要该字符的ASCII码小于256。编码后输出的格式为 \\HH （以 \\ 开头，HH则是指该字符对应的十六进制数字） |\r\n| 输出点在URL属性中                                            | 对这些数据进行URL编码<br/>Tips：除此之外，所有链接类属性应该校验其协议。禁止JavaScript、data和Vb伪协议。 |\r\n\r\n\r\n以上编码规则相对较为繁琐，可参考或直接使用业界已有成熟第三方库如ESAPI.其提供以下函数对象上表中的编码规则:\r\n\r\n```java\r\nESAPI.encoder().encodeForHTML();\r\nESAPI.encoder().encodeForHTMLAttribute();\r\nESAPI.encoder().encodeForJavaScript();\r\nESAPI.encoder().encodeForCSS();\r\nESAPI.encoder().encodeForURL();\r\n```\r\n\r\n##### 1.5.4【必须】外部输入拼接到HTTP响应头中需进行过滤\r\n\r\n应尽量避免外部可控参数拼接到HTTP响应头中，如业务需要则需要过滤掉“\\r”、\"\\n\"等换行符，或者拒绝携带换行符号的外部输入。\r\n\r\n##### 1.5.5【必须】避免不可信域名的302跳转\r\n\r\n如果对外部传入域名进行302跳转，必须设置可信域名列表并对传入域名进行校验。\r\n\r\n为避免校验被绕过，应避免直接对URL进行字符串匹配。应通过通过URL解析函数进行解析，获取host或者domain后和白名单进行比较。\r\n\r\n需要注意的是，由于浏览器的容错机制，域名`https://www.qq.com\\www.bbb.com`中的`\\`会被替换成`/`，最终跳转到`www.qq.com`。而Java的域名解析函数则无此特性。为避免解析不一致导致绕过，建议对host中的`/`和`#`进行替换。\r\n\r\n参考代码：\r\n\r\n```java\r\nString host=\"\";\r\n\t\ttry {\r\n\t\t    url = url.replaceAll(\"[\\\\\\\\#]\",\"/\"); //替换掉反斜线和井号\r\n\t\t    host = new URL(url).getHost();  \r\n\t\t} catch (MalformedURLException e) {\r\n\t\t    e.printStackTrace();\r\n\t\t}\r\n\t\tif (host.endsWith(\".qq.com\")){\r\n\t\t\t//跳转操作\r\n\t\t}else{\r\n\t\t\treturn;\r\n\t\t}\r\n```\r\n\r\n\r\n\r\n##### 1.5.6【必须】避免通过Jsonp传输非公开敏感信息\r\n\r\njsonp请求再被CSRF攻击时，其响应包可被攻击方劫持导致信息泄露。应避免通过jsonp传输非公开的敏感信息，例如用户隐私信息、身份凭证等。\r\n\r\n##### 1.5.7【必须】限定JSONP接口的callback字符集范围\r\n\r\nJSONP接口的callback函数名为固定白名单。如callback函数名可用户自定义，应限制函数名仅包含 字母、数字和下划线。如：`[a-zA-Z0-9_-]+`\r\n\r\n#####  1.5.8【必须】屏蔽异常栈\r\n\r\n应用程序出现异常时，禁止将数据库版本、数据库结构、操作系统版本、堆栈跟踪、文件名和路径信息、SQL 查询字符串等对攻击者有用的信息返回给客户端。建议重定向到一个统一、默认的错误提示页面，进行信息过滤。\r\n\r\n##### 1.5.9【必须】模板&表达式\r\n\r\nweb view层通常通过模板技术或者表达式引擎来实现界面与业务数据分离，比如jsp中的EL表达式。这些引擎通常可执行敏感操作，如果外部不可信数据未经过滤拼接到表达式中进行解析。则可能造成严重漏洞。\r\n\r\n下列是基于EL表达式注入漏洞的演示demo： \t\t\r\n\r\n```java\r\n\t@RequestMapping(\"/ELdemo\")\r\n\t@ResponseBody\r\n\tpublic String ELdemo(RepeatDTO repeat) {\r\n\t\tExpressionFactory expressionFactory = new ExpressionFactoryImpl();\r\n        SimpleContext simpleContext = new SimpleContext();\r\n        String exp = \"${\"+repeat.getel()+\"}\";\r\n        ValueExpression valueExpression =       expressionFactory.createValueExpression(simpleContext, exp, String.class);\t\t\r\n\t\treturn valueExpression.getValue(simpleContext).toString();\r\n\t}\r\n```\r\n\r\n外部可通过el参数，将不可信输入拼接到EL表达式中并解析。\r\n\r\n此时外部访问：x.x.x.x/ELdemo?el=”''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'open /Applications/Calculator.app')“ 可执行操作系统命令调出计算器。\r\n\r\n 基于以上风险：\r\n\r\n- 应避免外部输入的内容拼接到EL表达式或其他表达式引起、模板引擎进行解析。\r\n- 白名单过滤外部输入，仅允许字符、数字、下划线等。\r\n\r\n<a id=\"2.1.6\"></a>\r\n#### 1.6 OS命令执行\r\n\r\n##### 1.6.1【建议】避免不可信数据拼接操作系统命令\r\n\r\n当不可信数据存在时，应尽量避免外部数据拼接到操作系统命令使用 `Runtime` 和 `ProcessBuilder` 来执行。优先使用其他同类操作进行代替，比如通过文件系统API进行文件操作而非直接调用操作系统命令。\r\n\r\n##### 1.6.2【必须】避免创建SHELL操作\r\n\r\n如无法避免直接访问操作系统命令，需要严格管理外部传入参数，使不可信数据仅作为执行命令的参数而非命令。\r\n\r\n- 禁止外部数据直接直接作为操作系统命令执行。\r\n\r\n- 避免通过\"cmd\"、“bash”、“sh”等命令创建shell后拼接外部数据来执行操作系统命令。\r\n\r\n- 对外部传入数据进行过滤。可通过白名单限制字符类型，仅允许字符、数字、下划线；或过滤转义以下符号：|;&$><`（反引号）\\!\r\n\r\n  白名单示例：\r\n\r\n  ```java\r\n  private static final Pattern FILTER_PATTERN = Pattern.compile(\"[0-9A-Za-z_]+\");\r\n  if (!FILTER_PATTERN.matcher(input).matches()) {\r\n    // 终止当前请求的处理\r\n  }\r\n  ```\r\n\r\n<a id=\"2.1.7\"></a>\r\n#### 1.7 会话管理\r\n\r\n##### 1.7.1【必须】非一次有效身份凭证禁止在URL中传输\r\n\r\n身份凭证禁止在URL中传输，一次有效的身份凭证除外（如CAS中的st）。\r\n\r\n##### 1.7.2【必须】避免未经校验的数据直接给会话赋值\r\n\r\n防止会话信息被篡改，如恶意用户通过URL篡改手机号码等。\r\n\r\n<a id=\"2.1.8\"></a>\r\n#### 1.8 加解密\r\n\r\n##### 1.8.1【建议】对称加密\r\n\r\n建议使用AES，秘钥长度128位以上。禁止使用DES算法，由于秘钥太短，其为目前已知不安全加密算法。使用AES加密算法请参考以下注意事项：\r\n\r\n- AES算法如果采用CBC模式：每次加密时IV必须采用密码学安全的伪随机发生器（如/dev/urandom）,禁止填充全0等固定值。\r\n- AES算法如采用GCM模式，nonce须采用密码学安全的伪随机数\r\n- AES算法避免使用ECB模式，推荐使用GCM模式。\r\n\r\n##### 1.8.2【建议】非对称加密\r\n\r\n建议使用RSA算法，秘钥2048及以上。\r\n\r\n##### 1.8.3【建议】哈希算法\r\n\r\n哈希算法推荐使用SHA-2及以上。对于签名场景，应使用HMAC算法。如果采用字符串拼接盐值后哈希的方式，禁止将盐值置于字符串开头，以避免哈希长度拓展攻击。\r\n\r\n##### 1.8.4【建议】密码存储策略\r\n\r\n建议采用随机盐+明文密码进行多轮哈希后存储密码。\r\n\r\n<a id=\"2.1.9\"></a>\r\n#### 1.9 查询业务\r\n\r\n##### 1.9.1【必须】返回信息最小化\r\n\r\n返回用户信息应遵循最小化原则，避免将业务需求之外的用户信息返回到前端。\r\n\r\n##### 1.9.2【必须】个人敏感信息脱敏展示\r\n\r\n在满足业务需求的情况下，个人敏感信息需脱敏展示,如：\r\n\r\n- 鉴权信息（如口令、密保答案、生理标识等）不允许展示\r\n- 身份证只显示第一位和最后一位字符，如3****************1。\r\n- 移动电话号码隐藏中间6位字符，如134******48。\r\n- 工作地址/家庭地址最多显示到“区”一级。\r\n- 银行卡号仅显示最后4位字符，如************8639\t\t\r\n\r\n##### 1.9.3【必须】数据权限校验\r\n\r\n查询个人非公开信息时，需要对当前访问账号进行数据权限校验。\r\n\r\n1. 验证当前用户的登录态\r\n2. 从可信结构中获取经过校验的当前请求账号的身份信息（如：session）。禁止从用户请求参数或Cookie中获取外部传入不可信用户身份直接进行查询。\r\n3. 验当前用户是否具备访问数据的权限\r\n\r\n<a id=\"2.1.10\"></a>\r\n#### 1.10 操作业务\r\n\r\n##### 1.10.1【必须】部署CSRF防御机制\r\n\r\nCSRF是指跨站请求伪造（Cross-site request forgery），是web常见的攻击之一。对于可重放的敏感操作请求，需部署CSRF防御机制。可参考以下两种常见的CSRF防御方式\r\n\r\n- 设置CSRF Token\r\n\r\n  服务端给合法的客户颁发CSRF Token，客户端在发送请求时携带该token供服务端校验，服务端拒绝token验证不通过的请求。以此来防止第三方构造合法的恶意操作链接。Token的作用域可以是Request级或者Session级。下面以Session级CSRF Token进行示例\r\n\r\n  1. 登录成功后颁发Token，并同时存储在服务端Session中\r\n\r\n     ```java\r\n     String uuidToken = UUID.randomUUID().toString();\r\n     map.put(\"token\", uuidToken);\r\n     request.getSession().setAttribute(\"token\",uuidToken );\r\n     return map;\r\n     ```\r\n\r\n     \r\n\r\n  2. 创建Filter\r\n\r\n     ```java\r\n     public class CsrfFilter implements Filter {  \r\n       ...\r\n        HttpSession session = req.getSession();\r\n        Object token = session.getAttribute(\"token\");\r\n        String requestToken = req.getParameter(\"token\");\r\n        if(StringUtils.isBlank(requestToken) || !requestToken.equals(token)){\r\n              AjaxResponseWriter.write(req, resp, ServiceStatusEnum.ILLEGAL_TOKEN, \"非法的token\");\r\n                 return;\r\n             }\r\n        ...\r\n     ```\r\n\r\n  ​     CSRF Token应具备随机性，保证其不可预测和枚举。另外由于浏览器会自动对表单所访问的域名添加相应的cookie信息，所以CSRF Token不应该通过Cookie传输。\r\n\r\n  ​    \r\n\r\n- 校验Referer头\r\n\r\n  通过检查HTTP请求的Referer字段是否属于本站域名，非本站域名的请求进行拒绝。\r\n\r\n  这种校验方式需要注意两点：\r\n\r\n  1. 要需要处理Referer为空的情况，当Referer为空则拒绝请求\r\n  2. 注意避免例如qq.com.evil.com 部分匹配的情况。\r\n\r\n##### 1.10.2【必须】权限校验\r\n\r\n对于非公共操作，应当校验当前访问账号进行操作权限（常见于CMS）和数据权限校验。\r\n\r\n1. 验证当前用户的登录态\r\n2. 从可信结构中获取经过校验的当前请求账号的身份信息（如：session）。禁止从用户请求参数或Cookie中获取外部传入不可信用户身份直接进行查询。\r\n3. 校验当前用户是否具备该操作权限\r\n4. 校验当前用户是否具备所操作数据的权限。避免越权。\r\n\r\n##### 1.10.3【建议】加锁操作\r\n\r\n对于有次数限制的操作，比如抽奖。如果操作的过程中资源访问未正确加锁。在高并发的情况下可能造成条件竞争，导致实际操作成功次数多于用户实际操作资格次数。此类操作应加锁处理。\r\n"
  },
  {
    "path": "LICENSE",
    "content": "Tencent (\"Licensor\") is pleased to support the open source community by making Secure Coding Guide available. \nSecure Coding Guide is licensed under the CC-BY-SA-4.0 License, and a copy of the license is included in this file.\n\nCopyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n\n\nTerms of the CC-BY-SA-4.0 License:\n---------------------------------------------------\nCreative Commons Attribution-ShareAlike 4.0 International Public\nLicense\n\nBy exercising the Licensed Rights (defined below), You accept and agree\nto be bound by the terms and conditions of this Creative Commons\nAttribution-ShareAlike 4.0 International Public License (\"Public\nLicense\"). To the extent this Public License may be interpreted as a\ncontract, You are granted the Licensed Rights in consideration of Your\nacceptance of these terms and conditions, and the Licensor grants You\nsuch rights in consideration of benefits the Licensor receives from\nmaking the Licensed Material available under these terms and\nconditions.\n\n\nSection 1 -- Definitions.\n\n  a. Adapted Material means material subject to Copyright and Similar\n     Rights that is derived from or based upon the Licensed Material\n     and in which the Licensed Material is translated, altered,\n     arranged, transformed, or otherwise modified in a manner requiring\n     permission under the Copyright and Similar Rights held by the\n     Licensor. For purposes of this Public License, where the Licensed\n     Material is a musical work, performance, or sound recording,\n     Adapted Material is always produced where the Licensed Material is\n     synched in timed relation with a moving image.\n\n  b. Adapter's License means the license You apply to Your Copyright\n     and Similar Rights in Your contributions to Adapted Material in\n     accordance with the terms and conditions of this Public License.\n\n  c. BY-SA Compatible License means a license listed at\n     creativecommons.org/compatiblelicenses, approved by Creative\n     Commons as essentially the equivalent of this Public License.\n\n  d. Copyright and Similar Rights means copyright and/or similar rights\n     closely related to copyright including, without limitation,\n     performance, broadcast, sound recording, and Sui Generis Database\n     Rights, without regard to how the rights are labeled or\n     categorized. For purposes of this Public License, the rights\n     specified in Section 2(b)(1)-(2) are not Copyright and Similar\n     Rights.\n\n  e. Effective Technological Measures means those measures that, in the\n     absence of proper authority, may not be circumvented under laws\n     fulfilling obligations under Article 11 of the WIPO Copyright\n     Treaty adopted on December 20, 1996, and/or similar international\n     agreements.\n\n  f. Exceptions and Limitations means fair use, fair dealing, and/or\n     any other exception or limitation to Copyright and Similar Rights\n     that applies to Your use of the Licensed Material.\n\n  g. License Elements means the license attributes listed in the name\n     of a Creative Commons Public License. The License Elements of this\n     Public License are Attribution and ShareAlike.\n\n  h. Licensed Material means the artistic or literary work, database,\n     or other material to which the Licensor applied this Public\n     License.\n\n  i. Licensed Rights means the rights granted to You subject to the\n     terms and conditions of this Public License, which are limited to\n     all Copyright and Similar Rights that apply to Your use of the\n     Licensed Material and that the Licensor has authority to license.\n\n  j. Licensor means the individual(s) or entity(ies) granting rights\n     under this Public License.\n\n  k. Share means to provide material to the public by any means or\n     process that requires permission under the Licensed Rights, such\n     as reproduction, public display, public performance, distribution,\n     dissemination, communication, or importation, and to make material\n     available to the public including in ways that members of the\n     public may access the material from a place and at a time\n     individually chosen by them.\n\n  l. Sui Generis Database Rights means rights other than copyright\n     resulting from Directive 96/9/EC of the European Parliament and of\n     the Council of 11 March 1996 on the legal protection of databases,\n     as amended and/or succeeded, as well as other essentially\n     equivalent rights anywhere in the world.\n\n  m. You means the individual or entity exercising the Licensed Rights\n     under this Public License. Your has a corresponding meaning.\n\n\nSection 2 -- Scope.\n\n  a. License grant.\n\n       1. Subject to the terms and conditions of this Public License,\n          the Licensor hereby grants You a worldwide, royalty-free,\n          non-sublicensable, non-exclusive, irrevocable license to\n          exercise the Licensed Rights in the Licensed Material to:\n\n            a. reproduce and Share the Licensed Material, in whole or\n               in part; and\n\n            b. produce, reproduce, and Share Adapted Material.\n\n       2. Exceptions and Limitations. For the avoidance of doubt, where\n          Exceptions and Limitations apply to Your use, this Public\n          License does not apply, and You do not need to comply with\n          its terms and conditions.\n\n       3. Term. The term of this Public License is specified in Section\n          6(a).\n\n       4. Media and formats; technical modifications allowed. The\n          Licensor authorizes You to exercise the Licensed Rights in\n          all media and formats whether now known or hereafter created,\n          and to make technical modifications necessary to do so. The\n          Licensor waives and/or agrees not to assert any right or\n          authority to forbid You from making technical modifications\n          necessary to exercise the Licensed Rights, including\n          technical modifications necessary to circumvent Effective\n          Technological Measures. For purposes of this Public License,\n          simply making modifications authorized by this Section 2(a)\n          (4) never produces Adapted Material.\n\n       5. Downstream recipients.\n\n            a. Offer from the Licensor -- Licensed Material. Every\n               recipient of the Licensed Material automatically\n               receives an offer from the Licensor to exercise the\n               Licensed Rights under the terms and conditions of this\n               Public License.\n\n            b. Additional offer from the Licensor -- Adapted Material.\n               Every recipient of Adapted Material from You\n               automatically receives an offer from the Licensor to\n               exercise the Licensed Rights in the Adapted Material\n               under the conditions of the Adapter's License You apply.\n\n            c. No downstream restrictions. You may not offer or impose\n               any additional or different terms or conditions on, or\n               apply any Effective Technological Measures to, the\n               Licensed Material if doing so restricts exercise of the\n               Licensed Rights by any recipient of the Licensed\n               Material.\n\n       6. No endorsement. Nothing in this Public License constitutes or\n          may be construed as permission to assert or imply that You\n          are, or that Your use of the Licensed Material is, connected\n          with, or sponsored, endorsed, or granted official status by,\n          the Licensor or others designated to receive attribution as\n          provided in Section 3(a)(1)(A)(i).\n\n  b. Other rights.\n\n       1. Moral rights, such as the right of integrity, are not\n          licensed under this Public License, nor are publicity,\n          privacy, and/or other similar personality rights; however, to\n          the extent possible, the Licensor waives and/or agrees not to\n          assert any such rights held by the Licensor to the limited\n          extent necessary to allow You to exercise the Licensed\n          Rights, but not otherwise.\n\n       2. Patent and trademark rights are not licensed under this\n          Public License.\n\n       3. To the extent possible, the Licensor waives any right to\n          collect royalties from You for the exercise of the Licensed\n          Rights, whether directly or through a collecting society\n          under any voluntary or waivable statutory or compulsory\n          licensing scheme. In all other cases the Licensor expressly\n          reserves any right to collect such royalties.\n\n\nSection 3 -- License Conditions.\n\nYour exercise of the Licensed Rights is expressly made subject to the\nfollowing conditions.\n\n  a. Attribution.\n\n       1. If You Share the Licensed Material (including in modified\n          form), You must:\n\n            a. retain the following if it is supplied by the Licensor\n               with the Licensed Material:\n\n                 i. identification of the creator(s) of the Licensed\n                    Material and any others designated to receive\n                    attribution, in any reasonable manner requested by\n                    the Licensor (including by pseudonym if\n                    designated);\n\n                ii. a copyright notice;\n\n               iii. a notice that refers to this Public License;\n\n                iv. a notice that refers to the disclaimer of\n                    warranties;\n\n                 v. a URI or hyperlink to the Licensed Material to the\n                    extent reasonably practicable;\n\n            b. indicate if You modified the Licensed Material and\n               retain an indication of any previous modifications; and\n\n            c. indicate the Licensed Material is licensed under this\n               Public License, and include the text of, or the URI or\n               hyperlink to, this Public License.\n\n       2. You may satisfy the conditions in Section 3(a)(1) in any\n          reasonable manner based on the medium, means, and context in\n          which You Share the Licensed Material. For example, it may be\n          reasonable to satisfy the conditions by providing a URI or\n          hyperlink to a resource that includes the required\n          information.\n\n       3. If requested by the Licensor, You must remove any of the\n          information required by Section 3(a)(1)(A) to the extent\n          reasonably practicable.\n\n  b. ShareAlike.\n\n     In addition to the conditions in Section 3(a), if You Share\n     Adapted Material You produce, the following conditions also apply.\n\n       1. The Adapter's License You apply must be a Creative Commons\n          license with the same License Elements, this version or\n          later, or a BY-SA Compatible License.\n\n       2. You must include the text of, or the URI or hyperlink to, the\n          Adapter's License You apply. You may satisfy this condition\n          in any reasonable manner based on the medium, means, and\n          context in which You Share Adapted Material.\n\n       3. You may not offer or impose any additional or different terms\n          or conditions on, or apply any Effective Technological\n          Measures to, Adapted Material that restrict exercise of the\n          rights granted under the Adapter's License You apply.\n\n\nSection 4 -- Sui Generis Database Rights.\n\nWhere the Licensed Rights include Sui Generis Database Rights that\napply to Your use of the Licensed Material:\n\n  a. for the avoidance of doubt, Section 2(a)(1) grants You the right\n     to extract, reuse, reproduce, and Share all or a substantial\n     portion of the contents of the database;\n\n  b. if You include all or a substantial portion of the database\n     contents in a database in which You have Sui Generis Database\n     Rights, then the database in which You have Sui Generis Database\n     Rights (but not its individual contents) is Adapted Material,\n\n     including for purposes of Section 3(b); and\n  c. You must comply with the conditions in Section 3(a) if You Share\n     all or a substantial portion of the contents of the database.\n\nFor the avoidance of doubt, this Section 4 supplements and does not\nreplace Your obligations under this Public License where the Licensed\nRights include other Copyright and Similar Rights.\n\n\nSection 5 -- Disclaimer of Warranties and Limitation of Liability.\n\n  a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE\n     EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS\n     AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF\n     ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,\n     IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,\n     WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR\n     PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,\n     ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT\n     KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT\n     ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.\n\n  b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE\n     TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,\n     NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,\n     INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,\n     COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR\n     USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN\n     ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR\n     DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR\n     IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.\n\n  c. The disclaimer of warranties and limitation of liability provided\n     above shall be interpreted in a manner that, to the extent\n     possible, most closely approximates an absolute disclaimer and\n     waiver of all liability.\n\n\nSection 6 -- Term and Termination.\n\n  a. This Public License applies for the term of the Copyright and\n     Similar Rights licensed here. However, if You fail to comply with\n     this Public License, then Your rights under this Public License\n     terminate automatically.\n\n  b. Where Your right to use the Licensed Material has terminated under\n     Section 6(a), it reinstates:\n\n       1. automatically as of the date the violation is cured, provided\n          it is cured within 30 days of Your discovery of the\n          violation; or\n\n       2. upon express reinstatement by the Licensor.\n\n     For the avoidance of doubt, this Section 6(b) does not affect any\n     right the Licensor may have to seek remedies for Your violations\n     of this Public License.\n\n  c. For the avoidance of doubt, the Licensor may also offer the\n     Licensed Material under separate terms or conditions or stop\n     distributing the Licensed Material at any time; however, doing so\n     will not terminate this Public License.\n\n  d. Sections 1, 5, 6, 7, and 8 survive termination of this Public\n     License.\n\n\nSection 7 -- Other Terms and Conditions.\n\n  a. The Licensor shall not be bound by any additional or different\n     terms or conditions communicated by You unless expressly agreed.\n\n  b. Any arrangements, understandings, or agreements regarding the\n     Licensed Material not stated herein are separate from and\n     independent of the terms and conditions of this Public License.\n\n\nSection 8 -- Interpretation.\n\n  a. For the avoidance of doubt, this Public License does not, and\n     shall not be interpreted to, reduce, limit, restrict, or impose\n     conditions on any use of the Licensed Material that could lawfully\n     be made without permission under this Public License.\n\n  b. To the extent possible, if any provision of this Public License is\n     deemed unenforceable, it shall be automatically reformed to the\n     minimum extent necessary to make it enforceable. If the provision\n     cannot be reformed, it shall be severed from this Public License\n     without affecting the enforceability of the remaining terms and\n     conditions.\n\n  c. No term or condition of this Public License will be waived and no\n     failure to comply consented to unless expressly agreed to by the\n     Licensor.\n\n  d. Nothing in this Public License constitutes or may be interpreted\n     as a limitation upon, or waiver of, any privileges and immunities\n     that apply to the Licensor or You, including from the legal\n     processes of any jurisdiction or authority.\n\n\n=======================================================================\n\nCreative Commons is not a party to its public\nlicenses. Notwithstanding, Creative Commons may elect to apply one of\nits public licenses to material it publishes and in those instances\nwill be considered the “Licensor.” The text of the Creative Commons\npublic licenses is dedicated to the public domain under the CC0 Public\nDomain Dedication. Except for the limited purpose of indicating that\nmaterial is shared under a Creative Commons public license or as\notherwise permitted by the Creative Commons policies published at\ncreativecommons.org/policies, Creative Commons does not authorize the\nuse of the trademark \"Creative Commons\" or any other trademark or logo\nof Creative Commons without its prior written consent including,\nwithout limitation, in connection with any unauthorized modifications\nto any of its public licenses or any other arrangements,\nunderstandings, or agreements concerning use of licensed material. For\nthe avoidance of doubt, this paragraph does not form part of the\npublic licenses.\n\nCreative Commons may be contacted at creativecommons.org.\n"
  },
  {
    "path": "Python安全指南.md",
    "content": "* [通用类](#通用类)\r\n   * [I. 代码实现](#i-代码实现)\r\n      * [1.1 加密算法](#11-加密算法)\r\n         * [1.1.1 【必须】避免使用不安全的对称加密算法](#111-必须避免使用不安全的对称加密算法)\r\n      * [1.2 程序日志](#12-程序日志)\r\n         * [1.2.1 【建议】对每个重要行为都记录日志](#121-建议对每个重要行为都记录日志)\r\n         * [1.2.2 【建议】禁止将未经验证的用户输入直接记录日志](#122-建议禁止将未经验证的用户输入直接记录日志)\r\n         * [1.2.3 【建议】避免在日志中保存敏感信息](#123-建议避免在日志中保存敏感信息)\r\n      * [1.3 系统口令](#13-系统口令)\r\n         * [1.3.1 【必须】禁止使用空口令、弱口令、已泄露口令](#131-必须禁止使用空口令弱口令已泄露口令)\r\n         * [1.3.2 【必须】口令强度要求](#132-必须口令强度要求)\r\n         * [1.3.3 【必须】口令存储安全](#133-必须口令存储安全)\r\n         * [1.3.4 【必须】禁止传递明文口令](#134-必须禁止传递明文口令)\r\n         * [1.3.5 【必须】禁止在不安全的信道中传输口令](#135-必须禁止在不安全的信道中传输口令)\r\n   * [II. 配置&amp;环境](#ii-配置环境)\r\n      * [2.1 Python版本选择](#21-python版本选择)\r\n         * [2.1.1 【建议】使用Python 3.6+的版本](#211-建议使用python-36的版本)\r\n      * [2.2 第三方包安全](#22-第三方包安全)\r\n         * [2.2.2 【必须】禁止使用不安全的组件](#222-必须禁止使用不安全的组件)\r\n      * [2.3 配置信息](#23-配置信息)\r\n         * [2.3.1 【必须】密钥存储安全](#231-必须密钥存储安全)\r\n         * [2.3.2 【必须】禁止硬编码敏感配置](#232-必须禁止硬编码敏感配置)\r\n* [后台类](#后台类)\r\n   * [I. 代码实现](#i-代码实现-1)\r\n      * [1.1 输入验证](#11-输入验证)\r\n         * [1.1.1 【必须】按类型进行数据校验](#111-必须按类型进行数据校验)\r\n      * [1.2 SQL操作](#12-sql操作)\r\n         * [1.2.1 【必须】使用参数化查询](#121-必须使用参数化查询)\r\n         * [1.2.2 【必须】对参数进行过滤](#122-必须对参数进行过滤)\r\n      * [1.3 执行命令](#13-执行命令)\r\n         * [1.3.1 【建议】避免直接调用函数执行系统命令](#131-建议避免直接调用函数执行系统命令)\r\n         * [1.3.2 【必须】过滤传入命令执行函数的字符](#132-必须过滤传入命令执行函数的字符)\r\n         * [1.3.3 【必须】禁止不安全的代码执行](#133-必须禁止不安全的代码执行)\r\n      * [1.4 文件操作](#14-文件操作)\r\n         * [1.4.1 【必须】文件类型限制](#141-必须文件类型限制)\r\n         * [1.4.2 【必须】禁止外部文件存储于可执行目录](#142-必须禁止外部文件存储于可执行目录)\r\n         * [1.4.3 【必须】避免路径穿越](#143-必须避免路径穿越)\r\n         * [1.4.4 【必须】禁用XML外部实体的方法](#144-必须禁用xml外部实体的方法)\r\n         * [1.4.5 【必须】禁用不安全的反序列化函数](#145-必须禁用不安全的反序列化函数)\r\n         * [1.4.6 【建议】避免路径拼接](#146-建议避免路径拼接)\r\n         * [1.4.7 【建议】文件名hash化处理](#147-建议文件名hash化处理)\r\n      * [1.5 网络请求](#15-网络请求)\r\n         * [1.5.1 【必须】限定访问网络资源地址范围](#151-必须限定访问网络资源地址范围)\r\n      * [1.6 响应输出](#16-响应输出)\r\n         * [1.6.1 【必须】设置正确的HTTP响应包类型](#161-必须设置正确的http响应包类型)\r\n         * [1.6.2 【必须】设置安全的HTTP响应头](#162-必须设置安全的http响应头)\r\n         * [1.6.3 【必须】对外输出页面包含第三方数据时须进行编码处理](#163-必须对外输出页面包含第三方数据时须进行编码处理)\r\n      * [1.7 数据输出](#17-数据输出)\r\n         * [1.7.1 【必须】敏感数据加密存储](#171-必须敏感数据加密存储)\r\n         * [1.7.2 【必须】敏感信息必须由后台进行脱敏处理](#172-必须敏感信息必须由后台进行脱敏处理)\r\n         * [1.7.3 【必须】高敏感信息禁止存储、展示](#173-必须高敏感信息禁止存储展示)\r\n         * [1.7.4 【必须】个人敏感信息脱敏展示](#174-必须个人敏感信息脱敏展示)\r\n         * [1.7.5 【必须】隐藏后台地址](#175-必须隐藏后台地址)\r\n      * [1.8 权限管理](#18-权限管理)\r\n         * [1.8.1 【必须】默认鉴权](#181-必须默认鉴权)\r\n         * [1.8.2 【必须】授权遵循最小权限原则](#182-必须授权遵循最小权限原则)\r\n         * [1.8.3 【必须】避免越权访问](#183-必须避免越权访问)\r\n         * [1.8.4 【建议】及时清理不需要的权限](#184-建议及时清理不需要的权限)\r\n      * [1.9 异常处理](#19-异常处理)\r\n         * [1.9.1 【必须】不向对外错误提示](#191-必须不向对外错误提示)\r\n         * [1.9.2 【必须】禁止异常抛出敏感信息](#192-必须禁止异常抛出敏感信息)\r\n      * [1.10 Flask安全](#110-flask安全)\r\n         * [1.10.1 【必须】生产环境关闭调试模式](#1101-必须生产环境关闭调试模式)\r\n         * [1.10.2 【建议】遵循Flask安全规范](#1102-建议遵循flask安全规范)\r\n      * [1.11 Django安全](#111-django安全)\r\n         * [1.11.1 【必须】生产环境关闭调试模式](#1111-必须生产环境关闭调试模式)\r\n         * [1.11.2 【建议】保持Django自带的安全特性开启](#1112-建议保持django自带的安全特性开启)\r\n\r\n#  通用类\r\n\r\n## I. 代码实现\r\n\r\n### 1.1 加密算法\r\n\r\n#### 1.1.1 【必须】避免使用不安全的对称加密算法\r\n\r\n- DES和3DES已经不再适用于现代应用程序，应改为使用AES。\r\n\r\n### 1.2 程序日志\r\n\r\n#### 1.2.1 【建议】对每个重要行为都记录日志\r\n\r\n- 确保重要行为都记录日志，且可靠保存6个月以上。\r\n\r\n\r\n#### 1.2.2 【建议】禁止将未经验证的用户输入直接记录日志\r\n\r\n- 当日志条目包含未经净化的用户输入时会引发记录注入漏洞。恶意用户会插入伪造的日志数据，从而让系统管理员以为是系统行为。\r\n\r\n\r\n#### 1.2.3 【建议】避免在日志中保存敏感信息\r\n\r\n- 不能在日志保存密码（包括明文密码和密文密码）、密钥和其它敏感信息\r\n\r\n\r\n### 1.3 系统口令\r\n\r\n#### 1.3.1 【必须】禁止使用空口令、弱口令、已泄露口令\r\n\r\n#### 1.3.2 【必须】口令强度要求\r\n\r\n>  口令强度须同时满足：\r\n>  1. 密码长度大于14位\r\n>  2. 必须包含下列元素：大小写英文字母、数字、特殊字符\r\n>  3. 不得使用各系统、程序的默认初始密码\r\n>  4. 不能与最近6次使用过的密码重复\r\n>  5. 不得与其他外部系统使用相同的密码\r\n\r\n#### 1.3.3 【必须】口令存储安全\r\n\r\n* 禁止明文存储口令\r\n* 禁止使用弱密码学算法（如DES和3DES）加密存储口令\r\n* 使用不可逆算法和随机salt对口令进行加密存储\r\n\r\n#### 1.3.4 【必须】禁止传递明文口令\r\n\r\n#### 1.3.5 【必须】禁止在不安全的信道中传输口令\r\n\r\n## II. 配置&环境\r\n\r\n### 2.1 Python版本选择\r\n\r\n#### 2.1.1 【建议】使用Python 3.6+的版本\r\n\r\n- 新增的项目应使用 Python 3.6+\r\n\r\n\r\n> **为什么要这么做？**\r\n> 由于 Python 2 在 [2020 年停止维护](https://www.python.org/doc/sunset-python-2/)，相关组件的漏洞不能得到及时修复与维护\r\n\r\n### 2.2 第三方包安全\r\n\r\n#### 2.2.2 【必须】禁止使用不安全的组件\r\n\r\n### 2.3 配置信息\r\n\r\n#### 2.3.1 【必须】密钥存储安全\r\n\r\n- 在使用对称密码算法时，需要保护好加密密钥。当算法涉及敏感、业务数据时，可通过非对称算法协商加密密钥。其他较为不敏感的数据加密，可以通过变换算法等方式保护密钥。\r\n\r\n\r\n#### 2.3.2 【必须】禁止硬编码敏感配置\r\n- 禁止在源码中硬编码AK/SK、IP、数据库账密等配置信息\r\n- 应使用配置系统或KMS密钥管理系统。\r\n\r\n\r\n\r\n#  后台类\r\n\r\n## I. 代码实现\r\n\r\n### 1.1 输入验证\r\n#### 1.1.1 【必须】按类型进行数据校验\r\n- 所有程序外部输入的参数值，应进行数据校验。校验内容包括但不限于：数据长度、数据范围、数据类型与格式。校验不通过，应拒绝。\r\n\r\n- 推荐使用组件：[Cerberus](https://github.com/pyeve/cerberus)、[jsonschema](https://github.com/Julian/jsonschema)、[Django-Validators](https://docs.djangoproject.com/en/dev/ref/validators/)\r\n\r\n```python\r\n# Cerberus示例\r\nv = Validator({'name': {'type': 'string'}})\r\nv.validate({'name': 'john doe'})\r\n\r\n# jsonschema示例\r\nschema = {\r\n     \"type\" : \"object\",\r\n     \"properties\" : {\r\n         \"price\" : {\"type\" : \"number\"},\r\n         \"name\" : {\"type\" : \"string\"},\r\n     },\r\n}\r\n\r\nvalidate(instance={\"name\" : \"Eggs\", \"price\" : 34.99}, schema=schema)\r\n```\r\n\r\n### 1.2 SQL操作\r\n\r\n#### 1.2.1 【必须】使用参数化查询\r\n\r\n- 使用参数化SQL语句，强制区分数据和命令，避免产生SQL注入漏洞。\r\n\r\n```python\r\n# 错误示例\r\nimport mysql.connector\r\n\r\nmydb = mysql.connector.connect(\r\n... ...\r\n)\r\n\r\ncur = mydb.cursor()\r\nuserid = get_id_from_user()\r\n# 使用%直接格式化字符串拼接SQL语句\r\ncur.execute(\"SELECT `id`, `password` FROM `auth_user` WHERE `id`=%s \" % (userid,)) \r\nmyresult = cur.fetchall()\r\n```\r\n\r\n```python\r\n# 安全示例\r\nimport mysql.connector\r\n\r\nmydb = mysql.connector.connect(\r\n... ...\r\n)\r\ncur = mydb.cursor()\r\nuserid = get_id_from_user()\r\n# 将元组以参数的形式传入\r\ncur.execute(\"SELECT `id`, `password` FROM `auth_user` WHERE `id`=%s \" , (userid,))\r\nmyresult = cur.fetchall()\r\n```\r\n\r\n* 推荐使用ORM框架来操作数据库，如：使用`SQLAlchemy`。\r\n\r\n```python\r\n# 安装sqlalchemy并初始化数据库连接\r\n# pip install sqlalchemy\r\nfrom sqlalchemy import create_engine\r\n# 初始化数据库连接，修改为你的数据库用户名和密码\r\nengine = create_engine('mysql+mysqlconnector://user:password@host:port/DATABASE')\r\n```\r\n\r\n```python\r\n# 引用数据类型\r\nfrom sqlalchemy import Column, String, Integer, Float\r\nfrom sqlalchemy.ext.declarative import declarative_base\r\n\r\nBase = declarative_base()\r\n# 定义 Player 对象:\r\nclass Player(Base):\r\n    # 表的名字:\r\n    __tablename__ = 'player'\r\n\r\n    # 表的结构:\r\n    player_id = Column(Integer, primary_key=True, autoincrement=True)\r\n    team_id = Column(Integer)\r\n    player_name = Column(String(255))\r\n    height = Column(Float(3, 2))\r\n```\r\n\r\n```python\r\n# 增删改查\r\nfrom sqlalchemy.orm import sessionmaker\r\n# 创建 DBSession 类型:\r\nDBSession = sessionmaker(bind=engine)\r\n# 创建 session 对象:\r\nsession = DBSession()\r\n\r\n# 增:\r\nnew_player = Player(team_id=101, player_name=\"Tom\", height=1.98)\r\nsession.add(new_player)\r\n# 删:\r\nrow = session.query(Player).filter(Player.player_name==\"Tom\").first()\r\nsession.delete(row)\r\n# 改:\r\nrow = session.query(Player).filter(Player.player_name==\"Tom\").first()\r\nrow.height = 1.99\r\n# 查:\r\nrows = session.query(Player).filter(Player.height >= 1.88).all()\r\n\r\n# 提交即保存到数据库:\r\nsession.commit()\r\n# 关闭 session:\r\nsession.close()\r\n```\r\n\r\n#### 1.2.2 【必须】对参数进行过滤\r\n\r\n- 将接受到的外部参数动态拼接到SQL语句时，必须对参数进行安全过滤。\r\n\r\n```python\r\ndef sql_filter(sql, max_length=20):\r\n    dirty_stuff = [\"\\\"\", \"\\\\\", \"/\", \"*\", \"'\", \"=\", \"-\", \"#\", \";\", \"<\", \">\", \"+\", \r\n                   \"&\", \"$\", \"(\", \")\", \"%\", \"@\", \",\"]\r\n    for stuff in dirty_stuff:\r\n        sql = sql.replace(stuff, \"x\")\r\n    return sql[:max_length]\r\n```\r\n\r\n### 1.3 执行命令\r\n\r\n#### 1.3.1 【建议】避免直接调用函数执行系统命令\r\n\r\n- 相关功能的实现应避免直接调用系统命令（如`os.system()`、`os.popen()`、`subprocess.call()`等），优先使用其他同类操作进行代替，比如：通过文件系统API进行文件操作而非直接调用操作系统命令\r\n- 如评估无法避免，执行命令应避免拼接外部数据，同时进行执行命令的白名单限制。\r\n\r\n#### 1.3.2 【必须】过滤传入命令执行函数的字符\r\n\r\n- 程序调用各类函数执行系统命令时，如果涉及的命令由外部传入，过滤传入命令执行函数的字符。\r\n\r\n```python\r\nimport os\r\nimport sys\r\nimport shlex\r\n\r\ndomain = sys.argv[1]\r\n# 替换可以用来注入命令的字符为空\r\nbadchars = \"\\n&;|'\\\"$()`-\"\r\nfor char in badchars:\r\n    domain = domain.replace(char, \" \")\r\n\r\nresult = os.system(\"nslookup \" + shlex.quote(domain))\r\n```\r\n\r\n#### 1.3.3 【必须】禁止不安全的代码执行\r\n\r\n* 禁止使用 `eval` 函数处理存在外部输入的数据。\r\n\r\n### 1.4 文件操作\r\n\r\n#### 1.4.1 【必须】文件类型限制\r\n\r\n- 通过白名单对上传或者下载的文件类型、大小进行严格校验。仅允许业务所需文件类型上传，避免上传木马、WebShell等文件。\r\n\r\n```python\r\nimport os\r\n  \r\nALLOWED_EXTENSIONS = ['txt','jpg','png']\r\n  \r\ndef allowed_file(filename):\r\n    if ('.' in filename and \r\n        '..' not in filename and \r\n        os.path.splitext(filename)[1].lower() in ALLOWED_EXTENSIONS):\r\n        \r\n        return filename\r\n    return None\r\n```\r\n\r\n#### 1.4.2 【必须】禁止外部文件存储于可执行目录\r\n\r\n- 禁止外部文件存储于WEB容器的可执行目录（appBase）。建议使用 [tempfile](https://docs.python.org/3/library/tempfile.html) 库处理临时文件和临时目录。\r\n\r\n\r\n#### 1.4.3 【必须】避免路径穿越\r\n\r\n- 保存在本地文件系统时，必须对路径进行合法校验，避免目录穿越漏洞\r\n\r\n```python\r\nimport os\r\n\r\nupload_dir = '/tmp/upload/' # 预期的上传目录\r\nfile_name = '../../etc/hosts' # 用户传入的文件名\r\nabsolute_path = os.path.join(upload_dir, file_name) # /tmp/upload/../../etc/hosts\r\nnormalized_path = os.path.normpath(absolute_path) # /etc/hosts\r\nif not normalized_path.startswith(upload_dir): # 检查最终路径是否在预期的上传目录中\r\n    raise IOError()\r\n```\r\n\r\n#### 1.4.4 【必须】禁用XML外部实体的方法\r\n\r\n* 禁用XML外部实体的方法，来预防XXE攻击。\r\n\r\n    ```python\r\n    from lxml import etree\r\n      \r\n    xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))\r\n    ```\r\n\r\n#### 1.4.5 【必须】禁用不安全的反序列化函数\r\n\r\n* 禁用`yaml.unsafe_load()`函数反序列化YAML数据，来避免反序列化漏洞执行漏洞。\r\n\r\n#### 1.4.6 【建议】避免路径拼接\r\n\r\n- 文件目录避免外部参数拼接。保存文件目录建议后台写死并对文件名进行校验（字符类型、长度）。\r\n\r\n\r\n#### 1.4.7 【建议】文件名hash化处理\r\n\r\n- 建议文件保存时，将文件名替换为随机字符串。\r\n\r\n```python\r\nimport uuid\r\n\r\ndef random_filename(filename):\r\n    ext = os.path.splitext(filename)[1]\r\n    new_filename = uuid.uuid4().hex + ext\r\n    return new_filename\r\n```\r\n\r\n\r\n\r\n### 1.5 网络请求\r\n\r\n#### 1.5.1 【必须】限定访问网络资源地址范围\r\n\r\n当程序需要从用户指定的`URL地址获取网页文本内容`、`加载指定地址的图片`、`进行下载`等操作时，需要对URL地址进行安全校验：\r\n\r\n1. 只允许HTTP或HTTPS协议\r\n\r\n2. 解析目标URL，获取其host\r\n\r\n3. 解析host，获取host指向的IP地址转换成long型\r\n\r\n4. 检查IP地址是否为内网IP\r\n\r\n```python\r\n# 以RFC定义的专有网络为例，如有自定义私有网段亦应加入禁止访问列表。\r\n10.0.0.0/8\r\n172.16.0.0/12\r\n192.168.0.0/16\r\n127.0.0.0/8\r\n```\r\n\r\n5. 请求URL\r\n\r\n6. 如果有跳转，跳转后执行1，否则对URL发起请求\r\n\r\n### 1.6 响应输出\r\n\r\n#### 1.6.1 【必须】设置正确的HTTP响应包类型\r\n响应包的HTTP头“Content-Type”必须正确配置响应包的类型，禁止非HTML类型的响应包设置为“text/html”。\r\n\r\n#### 1.6.2 【必须】设置安全的HTTP响应头\r\n\r\n* X-Content-Type-Options\r\n\r\n  添加“X-Content-Type-Options”响应头并将其值设置为“nosniff ”\r\n\r\n* HttpOnly\r\n   控制用户登鉴权的Cookie字段 应当设置HttpOnly属性以防止被XSS漏洞/JavaScript操纵泄漏。\r\n\r\n* X-Frame-Options\r\n\r\n  设置X-Frame-Options响应头，并根据需求合理设置其允许范围。该头用于指示浏览器禁止当前页面在frame、 iframe、embed等标签中展现。从而避免点击劫持问题。它有三个可选的值: DENY: 浏览器会拒绝当前页面加 载任何frame页面; SAMEORIGIN:则frame页面的地址只能为同源域名下的页面 ALLOW-FROM origin:可以定 义允许frame加载的页面地址。\r\n\r\n#### 1.6.3 【必须】对外输出页面包含第三方数据时须进行编码处理\r\n\r\n- 当响应“Content-Type”为“text/html”类型时，需要对响应体进行编码处理\r\n\r\n```python\r\n# 推荐使用mozilla维护的bleach库来进行过滤\r\nimport bleach\r\nbleach.clean('an <script>evil()</script> example')\r\n# u'an &lt;script&gt;evil()&lt;/script&gt; example'\r\n```\r\n\r\n### 1.7 数据输出\r\n\r\n#### 1.7.1 【必须】敏感数据加密存储\r\n\r\n- 敏感数据应使用SHA2、RSA等算法进行加密存储\r\n- 敏感数据应使用独立的存储层，并在访问层开启访问控制\r\n- 包含敏感信息的临时文件或缓存一旦不再需要应立刻删除\r\n\r\n#### 1.7.2 【必须】敏感信息必须由后台进行脱敏处理\r\n\r\n- 敏感信息须再后台进行脱敏后返回，禁止接口返回敏感信息交由前端/客户端进行脱敏处理。\r\n\r\n\r\n#### 1.7.3 【必须】高敏感信息禁止存储、展示\r\n\r\n- 口令、密保答案、生理标识等鉴权信息禁止展示\r\n- 非金融类业务，信用卡cvv码及日志禁止存储\r\n\r\n#### 1.7.4 【必须】个人敏感信息脱敏展示\r\n\r\n在满足业务需求的情况下，个人敏感信息需脱敏展示。\r\n\r\n- 身份证只显示第一位和最后一位字符，如3****************1。\r\n- 移动电话号码隐藏中间6位字符，如134******48。\r\n- 工作地址/家庭地址最多显示到“区”一级。\r\n- 银行卡号仅显示最后4位字符，如************8639\r\n\r\n#### 1.7.5 【必须】隐藏后台地址\r\n\r\n* 若程序对外提供了登录后台地址，应使用随机字符串隐藏地址。\r\n\r\n```python\r\n# 不要采取这种方式\r\nadmin_login_url = \"xxxx/login\"\r\n```\r\n\r\n```python\r\n# 安全示例\r\nadmin_login_url = \"xxxx/ranD0Str\"\r\n```\r\n\r\n\r\n### 1.8 权限管理\r\n\r\n#### 1.8.1 【必须】默认鉴权\r\n\r\n- 除非资源完全可对外开放，否则系统默认进行身份认证（使用白名单的方式放开不需要认证的接口或页面）。\r\n\r\n\r\n#### 1.8.2 【必须】授权遵循最小权限原则\r\n\r\n- 程序默认用户应不具备任何操作权限。\r\n\r\n\r\n#### 1.8.3 【必须】避免越权访问\r\n\r\n- 对于非公共操作，应当校验当前访问账号进行操作权限（常见于CMS）和数据权限校验。\r\n\r\n\r\n1. 验证当前用户的登录态；\r\n2. 从可信结构中获取经过校验的当前请求账号的身份信息（如：session），禁止从用户请求参数或Cookie中获取外部传入不可信用户身份直接进行查询；\r\n3. 校验当前用户是否具备该操作权限；\r\n4. 校验当前用户是否具备所操作数据的权限；\r\n5. 校验当前操作是否账户是否预期账户。\r\n\r\n#### 1.8.4 【建议】及时清理不需要的权限\r\n\r\n- 程序应定期清理非必需用户的权限。\r\n\r\n\r\n### 1.9 异常处理\r\n\r\n#### 1.9.1 【必须】不向对外错误提示\r\n\r\n* 应合理使用`try/except/finally` 处理系统异常，避免出错信息输出到前端。\r\n* 对外环境禁止开启debug模式，或将程序运行日志输出到前端。\r\n\r\n#### 1.9.2 【必须】禁止异常抛出敏感信息\r\n\r\n### 1.10 Flask安全\r\n\r\n#### 1.10.1 【必须】生产环境关闭调试模式\r\n\r\n#### 1.10.2 【建议】遵循Flask安全规范\r\n\r\n- 参考Flask文档中的安全注意事项 https://flask.palletsprojects.com/en/latest/security/\r\n\r\n\r\n### 1.11 Django安全\r\n\r\n#### 1.11.1 【必须】生产环境关闭调试模式\r\n\r\n#### 1.11.2 【建议】保持Django自带的安全特性开启\r\n\r\n- 保持Django自带的安全特性开启 https://docs.djangoproject.com/en/3.0/topics/security/\r\n\r\n- 在默认配置下，Django自带的安全特性对XSS、CSRF、SQL注入、点击劫持等类型漏洞可以起到较好防护效果。应尽量避免关闭这些安全特性。\r\n\r\n"
  },
  {
    "path": "README.md",
    "content": "# 代码安全指南\n\n面向开发人员梳理的代码安全指南，旨在梳理API层面的风险点并提供详实可行的安全编码方案。\n\n\n\n## 理念\n基于DevSecOps理念，我们希望用开发者更易懂的方式阐述安全编码方案，引导从源头规避漏洞。\n\n\n\n## 索引\n\n| 规范               | 最后修订日期 |\n| ------------------ | ------------ |\n| [C/C++安全指南](./C,C++安全指南.md)      | 2021-05-18   |\n| [JavaScript安全指南](./JavaScript安全指南.md#1) | 2021-05-18   |\n| [Node安全指南](./JavaScript安全指南.md#2)        | 2021-05-18   |\n| [Go安全指南](./Go安全指南.md)         | 2021-05-18   |\n| [Java安全指南](./Java安全指南.md)         | 2021-05-18   |\n| [Python安全指南](./Python安全指南.md)         | 2021-05-18   |\n\n\n## 实践\n代码安全指引可用于以下场景：\n- 开发人员日常参考\n- 编写安全系统扫描策略\n- 安全组件开发\n- 漏洞修复指引\n\n\n## 贡献\n\n盼与社区携手，一道维护完善。欢迎提交修订建议，详参阅[贡献指南](./CONTRIBUTING.md)。\n\n\n\n## 授权许可\n\nSecure Coding Guide by THL A29 Limited, a Tencent company, is licensed under [CC BY 4.0](https://creativecommons.org/licenses/by-sa/4.0/).\n"
  }
]