ホームページ  >  記事  >  バックエンド開発  >  XML インジェクションとコード防御の詳細な紹介

XML インジェクションとコード防御の詳細な紹介

黄舟
黄舟オリジナル
2017-03-18 17:28:592978ブラウズ


0x01 はじめに

一部の Web アプリケーションは、構成から完全なデータベース機能まで、さまざまな目的で XML ファイルを使用します。 ユーザー入力は通常、構成のカスタマイズやアプリケーション データベースの更新のために、これらのファイルに

されます。 ユーザー入力が使用前にサニタイズまたは検証されていない場合、セキュリティ リスクになる可能性があります。 予防策が講じられていない場合、悪意のあるユーザーは設定手順を変更し、新しいユーザーを追加する可能性があります(ユーザーリストが合格した場合) メンテナンス用の XML ファイル)、より高い権限の取得など。 以下は脆弱な J2EE アプリケーションを示しています:

  		Document doc = docBuilder.newDocument();
  		Element rootElement = doc.createElement("root");
  		doc.appendChild(rootElement);
  		Element firstElement = doc.createElement("first");
  		rootElement.appendChild(firstElement);
  		Element childElement = doc.createElement("child");
  		childElement.appendChild(doc.createTextNode(--User_Supplied_Text--)); // un-sanitized text
  		rootElement.appendChild(childElement);

以下は同様の脆弱な .NET アプリケーションを示しています:

  		String nodeText = request.getParameter("node");
  		XmlWriterSettings settings = new XmlWriterSettings();
  		writer = XmlWriter.Create(m_Document, settings);
  		writer.WriteElementString("newNode", nodeText); // un-sanitized text
  		 writer.WriteEndElement();

このコードを例として使用すると、ユーザー入力は適切なクリーンアップなしで XML ファイルに伝播されます。この脆弱性は、アプリケーションによる XML ファイルの使用方法に応じて、さまざまな方法で悪用される可能性があります。

0x02 脅威の影響

ユーザー入力時の危険な文字の不適切なサニタイズが原因で、 この攻撃の最悪のシナリオは、クライアントで変更されているページコンテキストによって異なります。側、アプリケーションのロジックを壊す可能性があります

0x03 修復のアイデア いくつかの問題の解決策は、ユーザー入力をクリーンアップすることです。ユーザー入力に危険な文字が含まれていないことを確認することで、悪意のあるユーザーがアプリケーションに意図しないタスク (任意の SQL

クエリ

の起動、クライアント上で実行される Javascript コードの埋め込み) を実行させることを防ぐことができます。各種オペレーティングシステムの実行 注文など

1 推奨されるフィルター文字

[1] | (縦棒記号)

[2] & (& アンパサンド)

[3] (セミコロン)

; [5] % (パーセント記号)

[6] @ (アットマーク)

[7] ' (一重引用符)

[8] " (引用符)

🎜[9] ' (バックスラッシュ エスケープ シングル引用)🎜

[10] " (バックスラッシュでエスケープされた引用符)

[11] a8093152e673feb7aba1828c43532094 (山括弧)

[12] () (括弧)

[13] + (プラス記号)

[14 ] CR (キャリッジリターン、ASCII 0x0d)

[15] LF (ラインフィード、ASCII 0x0a)

[16] 、(カンマ)

[17] (バックスラッシュ)

以下のセクションでは、さまざまな問題について説明します。問題の改訂に関する提案およびそれらを引き起こす可能性のある危険な文字:

2 SQL インジェクションとブラインド SQL インジェクション

A. ユーザーが入力した値と型 (整数、日付など) が有効であり、期待どおりであることを確認します。アプリケーション

. B. ストアド プロシージャを使用して、ユーザーがテーブルやビューに直接アクセスしないようにします。ストアド プロシージャを使用する場合は、変数の型を強制する

C. コンテキストを除外する。記号は次のとおりです。

[1] ' (一重引用符)

[2] " (引用符)

[3] ' (バックスラッシュでエスケープされた一重引用符)

[4] " (バックスラッシュでエスケープされた一重引用符) )

[5] ) (閉じ括弧)

[6] ; (セミコロン)

3 クロスサイト スクリプティング

A. 次の文字をフィルタリングして除外することをお勧めします。 :

[1] a8093152e673feb7aba1828c43532094 (山括弧)

[2] " (引用符)

[3] ' (一重引用符)

[4] % (パーセント記号)

[5] ; (セミコロン)

[6] () (括弧)

[7] & (アンパサンド)

[8] + (プラス記号)

B。 のバリアントを修正する場合は、MS の記事 821349 を参照してください。

C. UTF-7 攻撃の場合: [-] 可能であれば、特定の

Characterset エンコーディングを実装することをお勧めします (「Content-Type」ヘッダーまたは e8e496c15ba93d81f6ea4fe5f55a2244 タグを使用)。

4 HTTP 応答の分割

ユーザー入力 (少なくとも後で HTTP 応答に埋め込まれる入力) をサニタイズします。入力に次のような悪意のある文字が含まれていないことを確認してください。

[1] CR (Carriage Return、ASCII) 0x0d)

[2] LF (改行、ASCII 0x0a) リモート コマンド実行: 入力をサニタイズして、次のようなオペレーティング システム コマンドの実行に意味のある記号を除外します。 ] & (& Symbol)

[3]; (セミコロン)

5 シェルコマンドを実行します

A. チェックされていないユーザー入力を eval()、open()、sysopen()、system() などに渡さないでください。 Perl

コマンド。

B. 入力に次のような悪意のある文字が含まれていないことを確認してください:

[1] $ (ドル記号) [2] % (パーセント記号)

[3] @ (アットマーク)

XPath

インジェクション: 入力をサニタイズして、次のようなコンテキストを変更するシンボルを除外します。

[1] ' (一重引用符)

[2] " (引用符) など。 6 LDAP インジェクション

A. ポジティブ検証を使用します。英数字フィルタリング (A. .Z,a..z,0..9) ほとんどの LDAP クエリに適しています

B. フィルタリングまたはエスケープする必要がある特殊な LDAP 文字:

[1] 文字列の先頭にあるスペースまたは「#」文字

[2] 文字列の末尾にあるスペース文字

[3] 、(カンマ)

[4] + (プラス記号)

[5] " (引用符)

[6] (バックスラッシュ)

[7] 5cf6cad25bff9887343dcda440407ce3 " ' % ; ) ( & +

以下示例通过将敏感字符转换为其对应的字符实体来过滤指定字符串:

  // Example to filter sensitive data to prevent cross-site scripting
  public Class Validator {
      public static String filter(String value) {
          if (value == null) {
              return null;
          }        
          StringBuffer result = new StringBuffer(value.length());
          for (int i=0; i<value.length(); ++i) {
              switch (value.charAt(i)) {
              case &#39;<&#39;:
                  result.append("&lt;");
                  break;
              case &#39;>&#39;: 
                  result.append("&gt;");
                  break;
              case &#39;"&#39;: 
                  result.append("&quot;");
                  break;
              case &#39;\&#39;&#39;: 
                  result.append("&#39;");
                  break;
              case &#39;%&#39;: 
                  result.append("&#37;");
                  break;
              case &#39;;&#39;: 
                  result.append("&#59;");
                  break;
              case &#39;(&#39;: 
                  result.append("&#40;");
                  break;
              case &#39;)&#39;: 
                  result.append("&#41;");
                  break;
              case &#39;&&#39;: 
                  result.append("&amp;");
                  break;
              case &#39;+&#39;:
                  result.append("&#43;");
                  break;
              default:
                  result.append(value.charAt(i));
                  break;
          }        
          return result;
      }
  }
  // Filter the HTTP response using Validator.filter
  PrintWriter out = response.getWriter();
  // set output response
  out.write(Validator.filter(response));
  out.close();

Java Servlet API 2.3 引进了过滤器,它支持拦截和转换 HTTP 请求或响应。以下示例使用 Validator.filter 来用“Servlet 过滤器”清理响应:

  // Example to filter all sensitive characters in the HTTP response using a Java Filter.
  // This example is for illustration purposes since it will filter all content in the response, including HTML tags!
  public class SensitiveCharsFilter implements Filter {
      ...
      public void doFilter(ServletRequest request,
                      ServletResponse response,
                      FilterChain chain)
              throws IOException, ServletException {
          PrintWriter out = response.getWriter();
          ResponseWrapper wrapper = new ResponseWrapper((HttpServletResponse)response);
          chain.doFilter(request, wrapper);
          CharArrayWriter caw = new CharArrayWriter();
          caw.write(Validator.filter(wrapper.toString())); 
          response.setContentType("text/html");
          response.setContentLength(caw.toString().length());
          out.write(caw.toString());
          out.close();
      }
      public class CharResponseWrapper extends HttpServletResponseWrapper {
          private CharArrayWriter output;
          public String toString() {
              return output.toString();
          }
          public CharResponseWrapper(HttpServletResponse response){
              super(response);
              output = new CharArrayWriter();
          }
          public PrintWriter getWriter(){
              return new PrintWriter(output);
          }
      }
  } 
  }

[8-2] 保护 cookie

在 cookie 中存储敏感数据时,确保使用 Cookie.setSecure(布尔标志)在 HTTP 响应中设置 cookie 的安全标志,以指导浏览器使用安全协议(如 HTTPS 或 SSL)发送 cookie。

保护“用户”cookie 的示例:

  // Example to secure a cookie, i.e. instruct the browser to
  // send the cookie using a secure protocol
  Cookie cookie = new Cookie("user", "sensitive");
  cookie.setSecure(true);
  response.addCookie(cookie);

2 错误处理

      许多 J2EE Web 应用程序体系结构都遵循“模型视图控制器MVC)”模式。在该模式中,Servlet 扮演“控制器”的角色。Servlet 将应用程序处理委派给 EJB 会话 Bean(模型)之类的 JavaBean。然后,Servlet 再将请求转发给 JSP(视图),以呈现处理结果。Servlet 应检查所有的输入、输出、返回码、错误代码和已知的异常,以确保实际处理按预期进行。

数据验证可保护应用程序免遭恶意数据篡改,而有效的错误处理策略则是防止应用程序意外泄露内部错误消息(如异常堆栈跟踪)所不可或缺的。好的错误处理策略会处理以下项:

[1] 定义错误

[2] 报告错误

[3] 呈现错误

[4] 错误映射

[1] 定义错误

         应避免在应用程序层(如 Servlet)中硬编码错误消息。 相反地,应用程序应该使用映射到已知应用程序故障的错误密钥。好的做法是定义错误密钥,且该错误密钥映射到 HTML 表单字段或其他 Bean 属性的验证规则。例如,如果需要“user_name”字段,其内容为字母数字,并且必须在数据库中是唯一的,那么就应定义以下错误密钥:

(a) ERROR_USERNAME_REQUIRED:该错误密钥用于显示消息,以通知用户需要“user_name”字段;

(b) ERROR_USERNAME_ALPHANUMERIC:该错误密钥用于显示消息,以通知用户“user_name”字段应该是字母数字;

(c) ERROR_USERNAME_DUPLICATE:该错误密钥用于显示消息,以通知用户“user_name”值在数据库中重复;

(d) ERROR_USERNAME_INVALID:该错误密钥用于显示一般消息,以通知用户“user_name”值无效;

好的做法是定义用于存储和报告应用程序错误的以下框架 Java 类:

- ErrorKeys:定义所有错误密钥

      // Example: ErrorKeys defining the following error keys:    
      //    - ERROR_USERNAME_REQUIRED
      //    - ERROR_USERNAME_ALPHANUMERIC
      //    - ERROR_USERNAME_DUPLICATE
      //    - ERROR_USERNAME_INVALID
      //    ...
      public Class ErrorKeys {
          public static final String ERROR_USERNAME_REQUIRED = "error.username.required";
          public static final String ERROR_USERNAME_ALPHANUMERIC = "error.username.alphanumeric";
          public static final String ERROR_USERNAME_DUPLICATE = "error.username.duplicate";
          public static final String ERROR_USERNAME_INVALID = "error.username.invalid";
      }


- Error:封装个别错误

      // Example: Error encapsulates an error key.
      // Error is serializable to support code executing in multiple JVMs.
      public Class Error implements Serializable {
          
          // Constructor given a specified error key
          public Error(String key) {
              this(key, null);
          }
          // Constructor given a specified error key and array of placeholder objects
          public Error(String key, Object[] values) {
              this.key = key;
              this.values = values;
          }
          // Returns the error key
          public String getKey() {
              return this.key;
          }
          // Returns the placeholder values
          public Object[] getValues() {
              return this.values;
          }
          private String key = null;
          private Object[] values = null;
      }

- Errors:封装错误的集合

      // Example: Errors encapsulates the Error objects being reported to the presentation layer.
      // Errors are stored in a HashMap where the key is the bean property name and value is an
      // ArrayList of Error objects.
      public Class Errors implements Serializable {
      
          // Adds an Error object to the Collection of errors for the specified bean property.
          public void addError(String property, Error error) {
              ArrayList propertyErrors = (ArrayList)errors.get(property);
              if (propertyErrors == null) {
                  propertyErrors = new ArrayList();
                  errors.put(property, propertyErrors);
              }
              propertyErrors.put(error);            
          }
          // Returns true if there are any errors
          public boolean hasErrors() {
              return (errors.size > 0);
          }
          // Returns the Errors for the specified property
          public ArrayList getErrors(String property) {
              return (ArrayList)errors.get(property);
          }
          private HashMap errors = new HashMap();
      }

以下是使用上述框架类来处理“user_name”字段验证错误的示例:

  // Example to process validation errors of the "user_name" field.
  Errors errors = new Errors();
  String userName = request.getParameter("user_name");
  // (a) Required validation rule
  if (!Validator.validateRequired(userName)) {
      errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_REQUIRED));
  } // (b) Alpha-numeric validation rule
  else if (!Validator.matchPattern(userName, "^[a-zA-Z0-9]*$")) {
      errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_ALPHANUMERIC));
  }
  else
  {
      // (c) Duplicate check validation rule
      // We assume that there is an existing UserValidationEJB session bean that implements
      // a checkIfDuplicate() method to verify if the user already exists in the database.
      try {
          ...        
          if (UserValidationEJB.checkIfDuplicate(userName)) {
              errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_DUPLICATE));
          }
      } catch (RemoteException e) {
          // log the error
          logger.error("Could not validate user for specified userName: " + userName);
          errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_DUPLICATE);
      }
  }
  // set the errors object in a request attribute called "errors"
  request.setAttribute("errors", errors);

[2] 报告错误

有两种方法可报告 web 层应用程序错误:

(a) Servlet 错误机制、(b) JSP 错误机制

[2-a] Servlet 错误机制

Servlet 可通过以下方式报告错误:

- 转发给输入 JSP(已将错误存储在请求属性中),或

- 使用 HTTP 错误代码参数来调用 response.sendError,或

- 抛出异常

          好的做法是处理所有已知应用程序错误(如 [1] 部分所述),将这些错误存储在请求属性中,然后转发给输入 JSP。输入 JSP 应显示错误消息,并提示用户重新输入数据。以下示例阐明转发给输入 JSP(userInput.jsp)的方式:

  // Example to forward to the userInput.jsp following user validation errors
  RequestDispatcher rd = getServletContext().getRequestDispatcher("/user/userInput.jsp");
  if (rd != null) {
      rd.forward(request, response);
  }

    如果 Servlet 无法转发给已知的 JSP 页面,那么第二个选项是使用 response.sendError 方法,将 HttpServletResponse.SC_INTERNAL_SERVER_ERROR(状态码 500)作为参数,来报告错误。 请参阅 javax.servlet.http.HttpServletResponse 的 Javadoc,以获取有关各种 HTTP 状态码的更多详细信息。返回 HTTP 错误的示例:

  // Example to return a HTTP error code
  RequestDispatcher rd = getServletContext().getRequestDispatcher("/user/userInput.jsp");
  if (rd == null) {
      // messages is a resource bundle with all message keys and values
      response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                              messages.getMessage(ErrorKeys.ERROR_USERNAME_INVALID));
  }

作为最后的手段,Servlet 可以抛出异常,且该异常必须是以下其中一类的子类:

- RuntimeException

- ServletException

- IOException

[2-b] JSP 错误机制

JSP 页面通过定义 errorPage 伪指令来提供机制,以处理运行时异常,如以下示例所示:

      <%@ page errorPage="/errors/userValidation.jsp" %>

未捕获的 JSP 异常被转发给指定的 errorPage,并且原始异常设置在名称为 javax.servlet.jsp.jspException 的请求参数中。错误页面必须包括 isErrorPage 伪指令:

      <%@ page isErrorPage="true" %>

isErrorPage 伪指令导致“exception”变量初始化为所抛出的异常对象。

[3] 呈现错误

J2SE Internationalization API 提供使应用程序资源外部化以及将消息格式化的实用程序类,其中包括:

(a) 资源束、(b) 消息格式化

[3-a] 资源束

资源束通过将本地化数据从使用该数据的源代码中分离来支持国际化。每一资源束都会为特定的语言环境存储键/值对的映射。

java.util.PropertyResourceBundle 将内容存储在外部属性文件中,对其进行使用或扩展都很常见,如以下示例所示:

  ################################################
  # ErrorMessages.properties
  ################################################
  # required user name error message
  error.username.required=User name field is required
  # invalid user name format
  error.username.alphanumeric=User name must be alphanumeric
  # duplicate user name error message
  error.username.duplicate=User name {0} already exists, please choose another one

         可定义多种资源,以支持不同的语言环境(因此名为资源束)。例如,可定义 ErrorMessages_fr.properties 以支持该束系列的法语成员。如果请求的语言环境的资源成员不存在,那么会使用缺省成员。在以上示例中,缺省资源是 ErrorMessages.properties。应用程序(JSP 或 Servlet)会根据用户的语言环境从适当的资源检索内容。

[3-b] 消息格式化

        J2SE 标准类 java.util.MessageFormat 提供使用替换占位符来创建消息的常规方法。MessageFormat 对象包含嵌入了格式说明符的模式字符串,如下所示:

  // Example to show how to format a message using placeholder parameters
  String pattern = "User name {0} already exists, please choose another one";
  String userName = request.getParameter("user_name");
  Object[] args = new Object[1];
  args[0] = userName;
  String message = MessageFormat.format(pattern, args);

以下是使用 ResourceBundle 和 MessageFormat 来呈现错误消息的更加全面的示例:

  // Example to render an error message from a localized ErrorMessages resource (properties file)
  // Utility class to retrieve locale-specific error messages
  public Class ErrorMessageResource {   
      // Returns the error message for the specified error key in the environment locale
      public String getErrorMessage(String errorKey) {
          return getErrorMessage(errorKey, defaultLocale);
      }  
      // Returns the error message for the specified error key in the specified locale
      public String getErrorMessage(String errorKey, Locale locale) {
          return getErrorMessage(errorKey, null, locale);
      } 
      // Returns a formatted error message for the specified error key in the specified locale
      public String getErrorMessage(String errorKey, Object[] args, Locale locale) {    
          // Get localized ErrorMessageResource
          ResourceBundle errorMessageResource = ResourceBundle.getBundle("ErrorMessages", locale);
          // Get localized error message
          String errorMessage = errorMessageResource.getString(errorKey);
          if (args != null) {
              // Format the message using the specified placeholders args
              return MessageFormat.format(errorMessage, args);
          } else {
              return errorMessage;
          }
      }
      // default environment locale
      private Locale defaultLocale = Locale.getDefaultLocale();
  }
  // Get the user&#39;s locale
  Locale userLocale = request.getLocale();
  // Check if there were any validation errors
  Errors errors = (Errors)request.getAttribute("errors");
  if (errors != null && errors.hasErrors()) {
      // iterate through errors and output error messages corresponding to the "user_name" property
      ArrayList userNameErrors = errors.getErrors("user_name");
      ListIterator iterator = userNameErrors.iterator();
      while (iterator.hasNext()) {
          // Get the next error object
          Error error = (Error)iterator.next();
          String errorMessage = ErrorMessageResource.getErrorMessage(error.getKey(), userLocale);
          output.write(errorMessage + "\r\n");
      }
  }

建议定义定制 JSP 标记(如 displayErrors),以迭代处理并呈现错误消息,如以上示例所示。

[4] 错误映射

通常情况下,“Servlet 容器”会返回与响应状态码或异常相对应的缺省错误页面。可以使用定制错误页面来指定状态码或异常与 Web 资源之间的映射。好的做法是开发不会泄露内部错误状态的静态错误页面(缺省情况下,大部分 Servlet 容器都会报告内部错误消息)。该映射配置在“Web 部署描述符(web.xml)”中,如以下示例所指定:

  <!-- Mapping of HTTP error codes and application exceptions to error pages -->
  <error-page>
    <exception-type>UserValidationException</exception-type>
    <location>/errors/validationError.html</error-page>
  </error-page>
  <error-page>
    <error-code>500</exception-type>
    <location>/errors/internalError.html</error-page>
  </error-page>
  <error-page>
  ...
  </error-page>


0x06 PHP

1  输入数据验证

      虽然为方便用户而在客户端层上提供数据验证,但仍必须始终在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 Javascript。一份好的设计通常需要 Web 应用程序框架,以提供服务器端实用程序例程,从而验证以下内容:[1] 必需字段[2] 字段数据类型(缺省情况下,所有 HTTP 请求参数都是“字符串”)[3] 字段长度[4] 字段范围[5] 字段选项[6] 字段模式[7] cookie 值[8] HTTP 响应好的做法是实现一个或多个验证每个应用程序参数的函数。以下部分描述一些检查的示例。

[1] 必需字段

    “始终”检查字段不为空,并且其长度要大于零,不包括行距和后面的空格。如何验证必需字段的示例:

  // PHP example to validate required fields
  function validateRequired($input) {
      ...
      $pass = false;
      if (strlen(trim($input))>0){
          $pass = true;
      }
      return $pass;
  }
  if (validateRequired($fieldName)) {
      // fieldName is valid, continue processing request
  }

[2] 字段数据类型

        输入的 Web 应用程序中的字段数据类型和输入参数欠佳。例如,所有 HTTP 请求参数或 cookie 值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确。

[3] 字段长度

       “始终”确保输入参数(HTTP 请求参数或 cookie 值)有最小长度和/或最大长度的限制。

[4] 字段范围

        始终确保输入参数是在由功能需求定义的范围内。

[5] 字段选项

          Web 应用程序通常会为用户显示一组可供选择的选项(例如,使用 SELECT HTML 标记),但不能执行服务器端验证以确保选定的值是其中一个允许的选项。请记住,恶意用户能够轻易修改任何选项值。始终针对由功能需求定义的受允许的选项来验证选定的用户值。

[6] 字段模式

       始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果 userName 字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]+$

[7] cookie 值

       适用于 cookie 值的相同的验证规则(如上所述)取决于应用程序需求(如验证必需值、验证长度等)。

[8] HTTP 响应

[8-1] 过滤用户输入要保护应用程序免遭跨站点脚本编制的攻击,开发者应通过将敏感字符转换为其对应的字符实体来清理 HTML。这些是 HTML 敏感字符:6d267e5fab17ea8bc578f9e7e5e1570b " ' % ; ) ( & +PHP 包含一些自动化清理实用程序函数,如 htmlentities():

  $input = htmlentities($input, ENT_QUOTES, &#39;UTF-8&#39;);

此外,为了避免“跨站点脚本编制”的 UTF-7 变体,您应该显式定义响应的 Content-Type 头,例如:

  <?php
  header(&#39;Content-Type: text/html; charset=UTF-8&#39;);
  ?>

[8-2] 保护 cookie

         在 cookie 中存储敏感数据且通过 SSL 来传输时,请确保先在 HTTP 响应中设置 cookie 的安全标志。这将会指示浏览器仅通过 SSL 连接来使用该 cookie。为了保护 cookie,您可以使用以下代码示例:

  <$php
      $value = "some_value";
      $time = time()+3600;
      $path = "/application/";
      $domain = ".example.com";
      $secure = 1;
      setcookie("CookieName", $value, $time, $path, $domain, $secure, TRUE);
  ?>


以上がXML インジェクションとコード防御の詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。