摘要:本文是《DVWA从入门到精通》系列的第二篇,带你全面掌握Brute Force(暴力破解)模块的攻防全流程。从暴力破解的核心原理出发,逐步讲解Low、Medium、High三个级别的攻击手法与源码分析,并深入探讨Impossible级别的终极防御方案。文章包含Burp Suite的详细操作教程、字典制作方法、Token绕过技术以及账户锁定机制的实现原理,让你真正做到“知其然更知其所以然”。
一、什么是暴力破解?
1.1 暴力破解的核心原理
暴力破解(Brute Force)在Web攻击中,是指攻击者使用大量的认证信息在认证接口进行尝试登录,直到得到正确的结果。它的核心原理是通过自动化工具,以极高的速度尝试各种可能的用户名和密码组合,直到找到能成功登录的那一组。
暴力破解不依赖特定的系统缺陷,而是用最直接的方式与系统“正面硬刚”。它的成功与否,取决于以下几个因素:
| 因素 | 说明 |
|---|---|
| 密码复杂度 | 密码越简单、越常见,被猜中的概率越高 |
| 字典质量 | 字典越全面,覆盖的密码组合越多 |
| 防护机制 | 是否有验证码、登录限制、账户锁定等 |
| 攻击速度 | 自动化工具每秒可尝试数百上千次 |
1.2 为什么暴力破解依然有效?
你可能会想:“都什么年代了,还有人用暴力破解?”但现实是,暴力破解在实战中依然成功率极高。原因很简单——它利用了人们设置密码时的“通病”:
为了方便记忆,很多人会使用
123456、password、admin这类简单的弱口令很多人会在多个平台使用相同的密码
企业员工常常使用公司名称、部门名称、生日等可猜测的信息作为密码
攻击者正是将这些高频词汇制成字典,进行有针对性的字典攻击,而不是真的一个一个字符去试。
1.3 暴力破解 vs 字典攻击
在正式开始之前,有必要澄清两个概念:
| 概念 | 含义 | 特点 |
|---|---|---|
| 暴力破解(Brute Force) | 尝试所有可能的字符组合 | 理论上一定能破解,但耗时极长 |
| 字典攻击(Dictionary Attack) | 仅尝试字典中预定义的词汇 | 速度快,但依赖字典质量 |
在实际的Web安全测试中,我们通常说的“暴力破解”其实指的是字典攻击——使用精心编制的用户名和密码字典进行快速尝试。
二、准备工作:环境与工具
在开始攻击之前,需要做好以下准备:
2.1 靶场环境
确保你的DVWA已经成功部署并正常运行:
访问地址:
http://你的服务器IP/dvwa/login.php默认账号:
admin/password
2.2 必备工具:Burp Suite
Burp Suite是本次实验的核心工具,我们将使用其Intruder模块完成攻击演示。
Burp Suite的四种攻击模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| Sniper(狙击手) | 单字典,依次替换标记的参数 | 只有一个爆破点(如仅爆破密码) |
| Battering ram(攻城锤) | 单字典,同时替换所有标记的参数 | 所有参数值相同时使用 |
| Pitchfork(草叉) | 多字典,并行替换 | 多个爆破点,且需一一对应(如High级别的Token爆破) |
| Cluster bomb(集束炸弹) | 多字典,笛卡尔积交叉匹配 | 用户名和密码均未知时使用 |
2.3 字典文件
准备用户名和密码字典。常用的字典来源:
SecLists:GitHub上最全的安全字典集合
rockyou.txt:经典的密码字典(包含1400万个密码)
也可以自行制作针对性的小型字典进行测试
三、Low级别:毫无防护的“裸奔”状态
3.1 安全级别设置
首先将DVWA Security设置为Low级别,然后进入Brute Force模块。
3.2 观察与初步测试
随意输入一个用户名和密码,观察页面的行为:
输入错误时,页面会显示“Username and/or password incorrect.”
没有任何次数限制——你可以无限次尝试
通过浏览器地址栏可以看到,用户名和密码是通过GET请求明文传输的
3.3 源码分析
点击DVWA页面底部的“View Source”按钮,查看Low级别的核心代码:
<?php if( isset( $_GET[ 'Login' ] ) ) { // Get username $user = $_GET[ 'username' ]; // Get password $pass = $_GET[ 'password' ]; $pass = md5( $pass ); // Check the database $query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); if( $result && mysqli_num_rows( $result ) == 1 ) { // Get users details $row = mysqli_fetch_assoc( $result ); $avatar = $row["avatar"]; // Login successful echo "<p>Welcome to the password protected area {$user}</p>"; echo "<img src=\"{$avatar}\" />"; } else { // Login failed echo "<pre><br />Username and/or password incorrect.</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>这段代码存在以下致命缺陷:
无登录次数限制:无论尝试多少次,都不会触发任何保护机制
存在SQL注入漏洞:直接将用户输入拼接到SQL语句中
明文传输:用户名和密码通过GET请求明文传递
无验证码:没有任何人机验证机制
3.4 攻击方法一:SQL注入绕过登录
由于Low级别的代码直接将用户输入拼接到SQL语句中,我们可以使用SQL注入直接绕过登录。
在Username输入框中输入:
admin' or '1'='1在Password输入框中随便输入任何内容(如123)。
原理分析:
当输入admin' or '1'='1时,拼接后的SQL语句变成了:
SELECT * FROM `users` WHERE user = 'admin' or '1'='1' AND password = '123'由于'1'='1'恒为真,整个WHERE条件恒为真,因此会返回users表中的记录,从而成功登录。
3.5 攻击方法二:使用Burp Suite进行暴力破解
第一步:配置Burp Suite代理
打开Burp Suite,进入Proxy→Options
确认代理监听地址为
127.0.0.1:8080在浏览器中设置代理为
127.0.0.1:8080
第二步:抓取登录请求
在Burp Suite中点击Proxy→Intercept,确保Intercept is on
在DVWA的Brute Force页面输入任意用户名和密码(如
admin/123),点击LoginBurp Suite会拦截到登录请求
抓取到的数据包类似这样:
第三步:发送到Intruder模块
右键点击拦截到的请求,选择“Send to Intruder”(或按Ctrl+I)。
第四步:配置爆破位置
进入Intruder→Positions标签
点击“Clear”清除所有默认标记
选中
password=123中的123,点击“Add”将其设为爆破变量也可以将
username=admin中的admin也设为变量(如果用户名未知)
攻击模式选择:
如果仅爆破密码(已知用户名为admin):选择Sniper(狙击手)
如果用户名和密码均未知:选择Cluster bomb(集束炸弹)
第五步:配置字典
进入Intruder→Payloads标签
在Payload Options中加载字典文件(点击Load导入.txt字典)
也可以手动添加几个测试密码:
123456、password、admin、123等
第六步:开始爆破
点击右上角的“Start Attack”按钮,开始爆破。
第七步:分析结果
爆破完成后,通过Length(响应长度)来筛选结果:
登录成功的页面长度通常与失败的不同
找到长度异常的条目,查看响应内容
在DVWA中,默认账号密码为admin / password,爆破成功后页面会显示“Welcome to the password protected area admin”。
3.6 Low级别总结
| 缺陷 | 说明 |
|---|---|
| 无登录次数限制 | 可无限次尝试 |
| SQL注入漏洞 | 可直接绕过登录 |
| 明文传输 | GET请求暴露密码 |
| 无验证码 | 无任何人机验证 |
四、Medium级别:增加了延迟的“缓兵之计”
4.1 安全级别设置
将DVWA Security切换为Medium级别。
4.2 观察变化
在Medium级别下,你会发现登录失败后,页面响应明显变慢了。
4.3 源码分析
查看Medium级别的核心代码:
<?php if( isset( $_GET[ 'Login' ] ) ) { // Sanitise username input $user = $_GET[ 'username' ]; $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Sanitise password input $pass = $_GET[ 'password' ]; $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass = md5( $pass ); // Check the database $query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); if( $result && mysqli_num_rows( $result ) == 1 ) { // Get users details $row = mysqli_fetch_assoc( $result ); $avatar = $row["avatar"]; // Login successful echo "<p>Welcome to the password protected area {$user}</p>"; echo "<img src=\"{$avatar}\" />"; } else { // Login failed sleep( 2 ); echo "<pre><br />Username and/or password incorrect.</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>Medium级别的变化:
增加了输入过滤:使用
mysqli_real_escape_string()对用户输入进行转义处理,有效防御了SQL注入增加了延迟机制:登录失败后执行
sleep(2),程序休眠2秒
4.4 攻击方法
Medium级别的防护本质上是“缓兵之计”:
SQL注入被修复了(输入被转义)
但暴力破解依然可行——只是变慢了
攻击步骤与Low级别完全一致:
使用Burp Suite抓取登录请求
发送到Intruder模块
配置爆破位置和字典
开始爆破
唯一的区别是:由于每次失败都有2秒延迟,爆破速度会显著降低。但只要有足够的时间和耐心,依然可以成功爆破出密码。
4.5 Medium级别总结
| 改进 | 局限性 |
|---|---|
| 输入转义(防SQL注入) | 对暴力破解无效 |
| sleep(2)延迟 | 仅降低速度,不阻断攻击 |
五、High级别:Token机制的“动态防线”
5.1 安全级别设置
将DVWA Security切换为High级别。
5.2 观察变化
在High级别下,再次抓取登录请求,你会发现数据包中多了一个参数——user_token:
5.3 源码分析
查看High级别的核心代码:
<?php if( isset( $_GET[ 'Login' ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Sanitise username input $user = $_GET[ 'username' ]; $user = stripslashes( $user ); $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Sanitise password input $pass = $_GET[ 'password' ]; $pass = stripslashes( $pass ); $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass = md5( $pass ); // Check database $query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); if( $result && mysqli_num_rows( $result ) == 1 ) { // Get users details $row = mysqli_fetch_assoc( $result ); $avatar = $row["avatar"]; // Login successful echo "<p>Welcome to the password protected area {$user}</p>"; echo "<img src=\"{$avatar}\" />"; } else { // Login failed sleep( rand( 0, 3 ) ); echo "<pre><br />Username and/or password incorrect.</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } // Generate Anti-CSRF token generateSessionToken(); ?>High级别的核心变化:
增加了Token验证:服务器要求每一个登录请求都必须携带一个名为
user_token的参数,且这个值必须和服务器Session中存的session_token完全一致Token是动态的:每次请求后,服务器都会生成一个新的Token
保留了延迟机制:依然存在登录失败的延迟
5.4 Token机制的原理
Token机制是如何防御暴力破解的?
服务器生成Token:用户打开登录页面时,服务器生成一个随机的Token,存储在Session中,同时输出到页面的隐藏字段中
客户端提交Token:用户提交登录请求时,必须同时提交这个Token
服务器验证Token:服务器比对提交的Token与Session中的Token是否一致
Token更新:每次验证后,服务器都会生成新的Token
这样一来,攻击者每尝试一个密码,都必须先访问登录页面获取一个新的随机Token,然后把这个Token塞进下一个请求中。传统的字典爆破工具无法直接处理这种动态Token。
5.5 攻击方法:使用Burp Suite绕过Token
虽然Token机制增加了难度,但Burp Suite的Intruder模块提供了绕过Token的能力。
核心思路
从服务器的响应包中提取最新的
user_token将提取到的Token自动填入下一个请求包中
如此循环,实现自动化爆破
详细步骤
第一步:抓取登录请求
在DVWA的Brute Force页面,输入任意用户名和密码,用Burp Suite抓包。
第二步:发送到Intruder
右键 →“Send to Intruder”。
第三步:配置爆破位置
进入Intruder→Positions
点击“Clear”清除所有标记
为
password参数添加变量标记为
user_token参数添加变量标记攻击模式选择Pitchfork(草叉)(因为需要将密码字典与动态Token一一对应)
第四步:配置Payload
进入Intruder→Payloads
Payload set 1(密码):加载密码字典
Payload set 2(Token):选择“Recursive grep”类型
第五步:配置Token提取规则
进入Intruder→Options→Grep - Extract
点击“Add”
在响应包中找到
user_token的值,配置正则表达式提取规则这样Burp Suite就会自动从每个响应中提取新的Token
第六步:配置资源池
由于Token机制要求串行处理(必须等上一个请求完成、获取新Token后才能发下一个请求),需要:
进入Intruder→Resource pool
创建新的资源池,将最大并发数设为1
第七步:设置初始Token值
在Payloads→Payload Options中,将初始Token值填入“Initial payload for first request”。
第八步:开始爆破
点击“Start Attack”,等待爆破完成。
第九步:分析结果
通过响应长度筛选出成功登录的请求。
5.6 High级别总结
| 防御机制 | 作用 | 绕过方法 |
|---|---|---|
| 动态Token | 阻断传统字典爆破 | 使用Burp Suite的Recursive grep自动提取Token |
| 输入过滤 | 防御SQL注入 | 对暴力破解影响不大 |
| 延迟机制 | 降低爆破速度 | 通过串行处理(并发数=1)应对 |
六、Impossible级别:终极防御方案
6.1 安全级别设置
将DVWA Security切换为Impossible级别。
6.2 源码分析
查看Impossible级别的核心代码:
<?php if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Sanitise username input $user = $_POST[ 'username' ]; $user = stripslashes( $user ); $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Sanitise password input $pass = $_POST[ 'password' ]; $pass = stripslashes( $pass ); $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass = md5( $pass ); // Default values $total_failed_login = 3; $lockout_time = 15; $account_locked = false; // Check the database (Check user information) $data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); $row = $data->fetch(); // Check to see if the user has been locked out. if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) { // User locked out. Note, using this method would allow for user enumeration! //echo "<pre><br />This account has been locked due to too many incorrect logins.</pre>"; // Calculate when the user would be allowed to login again $last_login = strtotime( $row[ 'last_login' ] ); $timeout = $last_login + ($lockout_time * 60); $timenow = time(); /* print "The last login was: " . date ("h:i:s", $last_login) . "<br />"; print "The timenow is: " . date ("h:i:s", $timenow) . "<br />"; print "The timeout is: " . date ("h:i:s", $timeout) . "<br />"; */ // Check to see if enough time has passed, if it hasn't locked the account if( $timenow < $timeout ) { $account_locked = true; // print "The account is locked<br />"; } } // Check the database (if username matches the password) $data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR); $data->bindParam( ':password', $pass, PDO::PARAM_STR ); $data->execute(); $row = $data->fetch(); // If its a valid login... if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) { // Get users details $avatar = $row[ 'avatar' ]; $failed_login = $row[ 'failed_login' ]; $last_login = $row[ 'last_login' ]; // Login successful echo "<p>Welcome to the password protected area <em>{$user}</em></p>"; echo "<img src=\"{$avatar}\" />"; // Had the account been locked out since last login? if( $failed_login >= $total_failed_login ) { echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>"; echo "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>{$last_login}</em>.</p>"; } // Reset bad login count $data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); } else { // Login failed sleep( rand( 2, 4 ) ); // Give the user some feedback echo "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>"; // Update bad login count $data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); } // Set the last login time $data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); } // Generate Anti-CSRF token generateSessionToken(); ?>6.3 Impossible级别的防御体系
Impossible级别构建了多层防御体系:
第一层:账户锁定策略
| 参数 | 值 | 说明 |
|---|---|---|
$total_failed_login | 3 | 连续输错3次密码,账号即被冻结 |
$lockout_time | 15 | 锁定后必须等待15分钟才能再次尝试 |
锁定逻辑:
服务器在每次登录前先查询
failed_login计数如果失败次数 ≥ 3,
$account_locked设为true即使后来猜对了密码,只要在锁定时间内,登录依然失败
第二层:PDO预处理(防SQL注入)
使用PDO预处理语句(Prepared Statements):
$data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );SQL指令模板和数据(用户输入的用户名/密码)是分开发送给数据库的。无论用户输入什么特殊字符(如' OR 1=1 #),都会被数据库严格当作普通字符串处理,绝不会被执行。
第三层:Token验证
保留了High级别的Token机制,确保每个请求都必须携带有效的动态Token。
6.4 Impossible级别能否被绕过?
理论上,Impossible级别的防护已经非常完善:
账户锁定阻止了暴力破解的核心——大量尝试
PDO预处理彻底杜绝了SQL注入
Token机制防止了请求重放
除非存在逻辑漏洞或其他漏洞配合(如XSS配合CSRF),否则单纯依靠暴力破解无法突破Impossible级别。
七、防御暴力破解的最佳实践
通过DVWA四个级别的对比,我们可以总结出防御暴力破解的最佳实践:
7.1 必须实施的防御措施
| 措施 | 说明 | 优先级 |
|---|---|---|
| 账户锁定 | 连续失败N次后锁定账户(如3次/15分钟) | ⭐⭐⭐⭐⭐ |
| 验证码 | 登录失败后或一定次数后要求输入验证码 | ⭐⭐⭐⭐⭐ |
| 强密码策略 | 要求密码包含大小写字母、数字、特殊字符 | ⭐⭐⭐⭐ |
| 双因素认证(2FA) | 增加第二层验证(短信、OTP等) | ⭐⭐⭐⭐ |
7.2 推荐的辅助措施
| 措施 | 说明 | 优先级 |
|---|---|---|
| 请求延迟 | 登录失败后增加延迟(如sleep) | ⭐⭐⭐ |
| 日志监控 | 记录所有登录尝试,异常时告警 | ⭐⭐⭐ |
| IP黑名单 | 对异常IP进行临时封禁 | ⭐⭐⭐ |
| Token机制 | 使用动态Token防止重放攻击 | ⭐⭐⭐ |
7.3 常见误区
在实际开发中,以下做法不能有效防御暴力破解:
❌仅使用sleep延迟:只能降低速度,不能阻断攻击
❌仅使用输入过滤:对暴力破解无效
❌仅使用前端验证:攻击者可以绕过前端直接发请求
❌使用简单的验证码:容易被OCR识别或打码平台破解
八、总结
本文围绕暴力破解漏洞展开完整学习,我们先理清其依靠工具批量遍历账号密码组合猜解凭证的核心原理,对比 Low 无防护、Medium 延时限制、High 增加 Token 校验、Impossible 融合账号锁定、PDO 预处理与 Token 三重防护四个等级的代码与防护迭代差异,实操掌握 Burp Suite 针对不同安全等级的各类爆破绕过手段,同时分清 Intruder 模块 Sniper、Battering ram、Pitchfork、Cluster bomb 四种攻击模式的适用场景,并梳理出账号锁定、验证码、强密码、双因素认证等多层防御方案;暴力破解属于入门级 Web 攻击却极易被忽略,借助 DVWA 暴力破解模块我们兼顾攻击思路与防护逻辑,生产环境中搭配账号锁定、验证码、强密码的多重防护策略,可有效抵御大部分暴力破解行为。
重要声明:本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。
如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。