Merge pull request '节点所有权问题' (#6) from dev_qing into main
Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
commit
dff9b42bea
8 files changed
+253
-45
No files matched your search
@@ -13,6 +13,10 @@ CREATE TABLE IF NOT EXISTS Users (
|
|||||||
CREATE TABLE IF NOT EXISTS Notes (
|
CREATE TABLE IF NOT EXISTS Notes (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
pid INTEGER NOT NULL,
|
pid INTEGER NOT NULL,
|
||||||
|
owner VARCHAR(128) DEFAULT 'Unknow',
|
||||||
title VARCHAR(128) NOT NULL,
|
title VARCHAR(128) NOT NULL,
|
||||||
content TEXT NOT NULL
|
content TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- 旧版增量
|
||||||
|
-- ALTER TABLE Notes ADD COLUMN owner VARCHAR(128) DEFAULT 'Unknow';
|
||||||
+65
-32
@@ -22,6 +22,7 @@ from flask_cors import CORS
|
|||||||
from waitress import serve
|
from waitress import serve
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import base64
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.error
|
import urllib.error
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
@@ -157,7 +158,18 @@ CORS(app)
|
|||||||
# "pid": 0,
|
# "pid": 0,
|
||||||
# "title": "test",
|
# "title": "test",
|
||||||
# "content": "hello"
|
# "content": "hello"
|
||||||
#}
|
#}
|
||||||
|
|
||||||
|
def decode_token(token: str):
|
||||||
|
idx = token.find('_')
|
||||||
|
if idx != -1:
|
||||||
|
raw = token[:idx]
|
||||||
|
# FIXME: 验证数字签名
|
||||||
|
decoded = base64.b64decode(raw)
|
||||||
|
jj = decoded.decode('utf-8')
|
||||||
|
return json.loads(jj)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/add_note', methods=['POST'])
|
@app.route('/add_note', methods=['POST'])
|
||||||
|
|
||||||
def add_note():
|
def add_note():
|
||||||
@@ -174,14 +186,14 @@ def add_note():
|
|||||||
|
|
||||||
return jsonify({"error": "Pid, title and content are required"}), 400
|
return jsonify({"error": "Pid, title and content are required"}), 400
|
||||||
|
|
||||||
#new_note = Note(pid=int(pid), title=title, content=content)
|
info = None
|
||||||
|
auth: str = request.headers.get('Authorization', '')
|
||||||
#with app.app_context():
|
if auth.startswith('Bearer '):
|
||||||
|
token: str = auth[7:]
|
||||||
# db.session.add(new_note)
|
info = decode_token(token)
|
||||||
|
|
||||||
# db.session.commit()
|
|
||||||
|
|
||||||
|
if info is None:
|
||||||
|
return jsonify({"error": "Warning: no Authorization"}), 401
|
||||||
|
|
||||||
if pid != 0:
|
if pid != 0:
|
||||||
|
|
||||||
@@ -211,6 +223,7 @@ def add_note():
|
|||||||
'table': 'Notes',
|
'table': 'Notes',
|
||||||
'values': {
|
'values': {
|
||||||
"pid": pid,
|
"pid": pid,
|
||||||
|
'owner': info['user'],
|
||||||
"title": title,
|
"title": title,
|
||||||
'content': content
|
'content': content
|
||||||
}
|
}
|
||||||
@@ -247,17 +260,6 @@ def get_notes():
|
|||||||
|
|
||||||
notes = call_db_service(DB_SERVER_URL, select_payload)
|
notes = call_db_service(DB_SERVER_URL, select_payload)
|
||||||
|
|
||||||
#with app.app_context():
|
|
||||||
|
|
||||||
# notes = Note.query.filter(Note.pid == int(pid))
|
|
||||||
|
|
||||||
#result = [{
|
|
||||||
# 'id': note['id'],
|
|
||||||
# 'pid': note['pid'],
|
|
||||||
# 'title': note['title'],
|
|
||||||
# 'content':note['content']
|
|
||||||
#} for note in notes]
|
|
||||||
|
|
||||||
result = notes['data']
|
result = notes['data']
|
||||||
|
|
||||||
return jsonify(result), 200
|
return jsonify(result), 200
|
||||||
@@ -276,6 +278,15 @@ def delete_note():
|
|||||||
|
|
||||||
return jsonify({"error": "Note ID is required"}), 400
|
return jsonify({"error": "Note ID is required"}), 400
|
||||||
|
|
||||||
|
info = None
|
||||||
|
auth: str = request.headers.get('Authorization', '')
|
||||||
|
if auth.startswith('Bearer '):
|
||||||
|
token: str = auth[7:]
|
||||||
|
info = decode_token(token)
|
||||||
|
|
||||||
|
if info is None:
|
||||||
|
return jsonify({"error": "Warning: no Authorization"}), 401
|
||||||
|
|
||||||
#with app.app_context():
|
#with app.app_context():
|
||||||
|
|
||||||
# notes = Note.query.filter(Note.pid == int(note_id))
|
# notes = Note.query.filter(Note.pid == int(note_id))
|
||||||
@@ -295,6 +306,7 @@ def delete_note():
|
|||||||
# return jsonify({"error": "Note not found"}), 404
|
# return jsonify({"error": "Note not found"}), 404
|
||||||
|
|
||||||
|
|
||||||
|
# 检查是否有子节点
|
||||||
select_payload = {
|
select_payload = {
|
||||||
'action': 'select',
|
'action': 'select',
|
||||||
'table': 'Notes',
|
'table': 'Notes',
|
||||||
@@ -310,6 +322,20 @@ def delete_note():
|
|||||||
|
|
||||||
return jsonify({'error': 'this node has a child '}), 400
|
return jsonify({'error': 'this node has a child '}), 400
|
||||||
|
|
||||||
|
# 检查所有权
|
||||||
|
select_payload = {
|
||||||
|
'action': 'select',
|
||||||
|
'table': 'Notes',
|
||||||
|
'where': {
|
||||||
|
'id': note_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = call_db_service(DB_SERVER_URL, select_payload)
|
||||||
|
notes = data['data']
|
||||||
|
if not info['user'] == 'admin':
|
||||||
|
if not len(notes) == 1 or not notes[0]['owner'] == info['user']:
|
||||||
|
return jsonify({"error": 'It\'s not your note'}), 403
|
||||||
|
|
||||||
|
|
||||||
delete_payload = {
|
delete_payload = {
|
||||||
@@ -355,23 +381,30 @@ def update_note():
|
|||||||
|
|
||||||
return jsonify({"error": "Invalid param"}), 400
|
return jsonify({"error": "Invalid param"}), 400
|
||||||
|
|
||||||
|
# 获取用户名
|
||||||
|
info = None
|
||||||
|
auth: str = request.headers.get('Authorization', '')
|
||||||
|
if auth.startswith('Bearer '):
|
||||||
|
token: str = auth[7:]
|
||||||
|
info = decode_token(token)
|
||||||
|
|
||||||
if pid != 0:
|
if info is None:
|
||||||
|
return jsonify({"error": "Warning: no Authorization"}), 401
|
||||||
|
|
||||||
select_payload = {
|
|
||||||
'action': 'select',
|
# 检查所有权
|
||||||
'table': 'Notes',
|
select_payload = {
|
||||||
'where': {
|
'action': 'select',
|
||||||
'id': pid
|
'table': 'Notes',
|
||||||
}
|
'where': {
|
||||||
|
'id': id
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
notes = call_db_service(DB_SERVER_URL, select_payload)
|
data = call_db_service(DB_SERVER_URL, select_payload)
|
||||||
|
notes = data['data']
|
||||||
# 不存在的父节点
|
if not len(notes) == 1 or not notes[0]['owner'] == info['user']:
|
||||||
if len( notes['data']) == 0:
|
return jsonify({"error": 'It\'s not your note'}), 403
|
||||||
return jsonify({'error': 'the new node need a parent'}), 400
|
|
||||||
|
|
||||||
|
|
||||||
# 内容审查
|
# 内容审查
|
||||||
review = review_content(title + '\n' + content, 10)
|
review = review_content(title + '\n' + content, 10)
|
||||||
|
|||||||
@@ -125,6 +125,15 @@ default = http://127.0.0.1:9200
|
|||||||
closed = http://127.0.0.1:8005;http://127.0.0.1:8007;
|
closed = http://127.0.0.1:8005;http://127.0.0.1:8007;
|
||||||
opened = http://127.0.0.1:9200;http://127.0.0.1:8006;
|
opened = http://127.0.0.1:9200;http://127.0.0.1:8006;
|
||||||
|
|
||||||
|
[Token]
|
||||||
|
# Token 有效期(秒),默认 3600 = 1 小时
|
||||||
|
keep_time = 3600
|
||||||
|
|
||||||
|
[CORS]
|
||||||
|
# 允许跨域的 Origin 白名单(分号分隔)
|
||||||
|
# 仅命中的 Origin 才会回填 Access-Control-Allow-Origin;缺省为空(不回填)
|
||||||
|
allowed_origins = https://qsont.me;https://www.qsont.me;http://127.0.0.1:8443;http://localhost:8443
|
||||||
|
|
||||||
[Database]
|
[Database]
|
||||||
path = sqlite3.db
|
path = sqlite3.db
|
||||||
```
|
```
|
||||||
@@ -132,6 +141,8 @@ path = sqlite3.db
|
|||||||
- `Proxy.default`:未带 `Target-Url` header 时的默认转发地址(不需 Token,主要服务前端静态资源)。
|
- `Proxy.default`:未带 `Target-Url` header 时的默认转发地址(不需 Token,主要服务前端静态资源)。
|
||||||
- `Proxy.opened`:允许外部带 Token 访问的内部服务白名单(分号分隔)。
|
- `Proxy.opened`:允许外部带 Token 访问的内部服务白名单(分号分隔)。
|
||||||
- `Proxy.closed`:仅记录用途,实际靠"不在 opened 中即拒绝"实现隔离。
|
- `Proxy.closed`:仅记录用途,实际靠"不在 opened 中即拒绝"实现隔离。
|
||||||
|
- `Token.keep_time`:登录签发 Token 的有效期(秒),缺省 3600。
|
||||||
|
- `CORS.allowed_origins`:OPTIONS 预检允许跨域的 Origin 白名单(分号分隔),缺省为空(不回填 ACAO)。
|
||||||
|
|
||||||
## 部署模式
|
## 部署模式
|
||||||
|
|
||||||
|
|||||||
@@ -11,5 +11,14 @@ default = http://127.0.0.1:9200
|
|||||||
closed = http://127.0.0.1:8005;http://127.0.0.1:8007;
|
closed = http://127.0.0.1:8005;http://127.0.0.1:8007;
|
||||||
opened = http://127.0.0.1:9200;http://127.0.0.1:8006;
|
opened = http://127.0.0.1:9200;http://127.0.0.1:8006;
|
||||||
|
|
||||||
|
[Token]
|
||||||
|
# Token 有效期(秒),默认 3600 = 1 小时
|
||||||
|
keep_time = 3600
|
||||||
|
|
||||||
|
[CORS]
|
||||||
|
# 允许跨域的 Origin 白名单(分号分隔)
|
||||||
|
# 仅命中的 Origin 才会回填 Access-Control-Allow-Origin;缺省为空(不回填)
|
||||||
|
allowed_origins = https://qsont.me;https://www.qsont.me;http://127.0.0.1:8443;http://localhost:8443
|
||||||
|
|
||||||
[Database]
|
[Database]
|
||||||
path = sqlite3.db
|
path = sqlite3.db
|
||||||
+12
-13
@@ -37,7 +37,11 @@ std::vector<std::string> split(const std::string& s, char delim) {
|
|||||||
std::istringstream stream(s);
|
std::istringstream stream(s);
|
||||||
std::string token;
|
std::string token;
|
||||||
while (std::getline(stream, token, delim)) {
|
while (std::getline(stream, token, delim)) {
|
||||||
tokens.push_back(token);
|
// 跳过空串:避免 "a;b;" 末尾分号产生 "" 元素
|
||||||
|
// 防御白名单匹配场景下空串被误判为命中
|
||||||
|
if (!token.empty()) {
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
@@ -291,6 +295,8 @@ static bool _TLS = false;
|
|||||||
static std::string _DEFT_URL;
|
static std::string _DEFT_URL;
|
||||||
static std::vector<std::string> _OPEN_URL;
|
static std::vector<std::string> _OPEN_URL;
|
||||||
static std::string _DB_PATH;
|
static std::string _DB_PATH;
|
||||||
|
static int _TOKEN_KEEP_TIME = 3600; // Token 有效期(秒),可由 [Token] keep_time 配置
|
||||||
|
static std::vector<std::string> _CORS_ORIGINS; // CORS 允许的 Origin 白名单,可由 [CORS] allowed_origins 配置
|
||||||
|
|
||||||
/* instance items */
|
/* instance items */
|
||||||
static std::unique_ptr<HttpServer> _SERVE;
|
static std::unique_ptr<HttpServer> _SERVE;
|
||||||
@@ -399,6 +405,8 @@ int main(int argc, char **argv) {
|
|||||||
_DEFT_URL = cf.get<std::string>("Proxy.default", "");
|
_DEFT_URL = cf.get<std::string>("Proxy.default", "");
|
||||||
_OPEN_URL = split(cf.get<std::string>("Proxy.opened", ""), ';');
|
_OPEN_URL = split(cf.get<std::string>("Proxy.opened", ""), ';');
|
||||||
_DB_PATH = cf.get<std::string>("Database.path", "");
|
_DB_PATH = cf.get<std::string>("Database.path", "");
|
||||||
|
_TOKEN_KEEP_TIME = cf.get<int>("Token.keep_time", 3600);
|
||||||
|
_CORS_ORIGINS = split(cf.get<std::string>("CORS.allowed_origins", ""), ';');
|
||||||
}
|
}
|
||||||
catch (ConfIni::IniConfigureFileParseException &e) {
|
catch (ConfIni::IniConfigureFileParseException &e) {
|
||||||
std::cerr << "ERROR: " << e.what() << std::endl;
|
std::cerr << "ERROR: " << e.what() << std::endl;
|
||||||
@@ -507,19 +515,12 @@ void registerRoutes(HttpServer &server) {
|
|||||||
pmc::net::http::response<pmc::net::http::string_body> res;
|
pmc::net::http::response<pmc::net::http::string_body> res;
|
||||||
res.result(pmc::net::http::status::ok);
|
res.result(pmc::net::http::status::ok);
|
||||||
|
|
||||||
// CORS 白名单(P1-4):仅允许官方域名,防止任意网站跨域调用
|
// CORS 白名单:从 [CORS] allowed_origins 加载,缺省为空(不回填 ACAO)
|
||||||
// 原配置 Access-Control-Allow-Origin: * 过宽,XSS 拿到 token 后可跨域读取数据
|
|
||||||
// 动态回填 Origin:仅当请求 Origin 命中白名单时才返回该值,否则不返回 ACAO 头
|
// 动态回填 Origin:仅当请求 Origin 命中白名单时才返回该值,否则不返回 ACAO 头
|
||||||
static const std::vector<std::string> allowed_origins = {
|
|
||||||
"https://qsont.me",
|
|
||||||
"https://www.qsont.me",
|
|
||||||
"http://127.0.0.1:8443", // 本地开发调试
|
|
||||||
"http://localhost:8443"
|
|
||||||
};
|
|
||||||
auto origin_it = req.base().find("Origin");
|
auto origin_it = req.base().find("Origin");
|
||||||
if (origin_it != req.base().end()) {
|
if (origin_it != req.base().end()) {
|
||||||
std::string origin = std::string(origin_it->value());
|
std::string origin = std::string(origin_it->value());
|
||||||
for (const auto& allowed : allowed_origins) {
|
for (const auto& allowed : _CORS_ORIGINS) {
|
||||||
if (origin == allowed) {
|
if (origin == allowed) {
|
||||||
res.set(pmc::net::http::field::access_control_allow_origin, origin);
|
res.set(pmc::net::http::field::access_control_allow_origin, origin);
|
||||||
res.set(pmc::net::http::field::vary, "Origin");
|
res.set(pmc::net::http::field::vary, "Origin");
|
||||||
@@ -709,12 +710,10 @@ http::response<http::string_body> handleLogin(
|
|||||||
|
|
||||||
auto _PRIV = MyRSA::Private_Key::from_pem(_KEYGEN->get_private_key_pem());
|
auto _PRIV = MyRSA::Private_Key::from_pem(_KEYGEN->get_private_key_pem());
|
||||||
|
|
||||||
int keep_time = 3600;
|
|
||||||
|
|
||||||
json::value jv_t = {
|
json::value jv_t = {
|
||||||
{"user", username},
|
{"user", username},
|
||||||
{"created_at", LocalTime::getCurrent()},
|
{"created_at", LocalTime::getCurrent()},
|
||||||
{"keep_time", keep_time}
|
{"keep_time", _TOKEN_KEEP_TIME}
|
||||||
};
|
};
|
||||||
std::string data = json::serialize(jv_t);
|
std::string data = json::serialize(jv_t);
|
||||||
std::string token = Base64::base64_encode(data);
|
std::string token = Base64::base64_encode(data);
|
||||||
|
|||||||
Whitespace-only changes.
Executable
+144
@@ -0,0 +1,144 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 拉取 OWASP CRS 规则(通过 Go module proxy,不依赖 GitHub 直连)
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# bash _fetch_crs.sh # 拉取最新版
|
||||||
|
# bash _fetch_crs.sh v4.25.0 # 拉取指定版本
|
||||||
|
#
|
||||||
|
# 规则来源:github.com/corazawaf/coraza-coreruleset/v4(Apache-2.0)
|
||||||
|
# 规则文件放置在 rules/crs/rules/,入口配置 rules/crs/coraza.conf
|
||||||
|
# 整个 rules/crs/ 目录已在 .gitignore 中,不入库。
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")" || exit
|
||||||
|
|
||||||
|
VERSION="${1:-latest}"
|
||||||
|
CRS_DIR="rules/crs"
|
||||||
|
RULES_DIR="$CRS_DIR/rules"
|
||||||
|
|
||||||
|
echo "==== 拉取 OWASP CRS (${VERSION}) ===="
|
||||||
|
|
||||||
|
# 1. 通过 Go module proxy 下载 coraza-coreruleset
|
||||||
|
TMPDIR=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMPDIR"' EXIT
|
||||||
|
WORKDIR="$TMPDIR/crs-fetch"
|
||||||
|
mkdir -p "$WORKDIR"
|
||||||
|
|
||||||
|
cd "$WORKDIR"
|
||||||
|
cat > go.mod << 'EOF'
|
||||||
|
module crs-fetch
|
||||||
|
go 1.22.0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "[1/4] 下载 coraza-coreruleset (${VERSION}) ..."
|
||||||
|
if [ "$VERSION" = "latest" ]; then
|
||||||
|
GOPROXY=https://goproxy.cn,direct go mod download github.com/corazawaf/coraza-coreruleset/v4@latest 2>&1
|
||||||
|
else
|
||||||
|
GOPROXY=https://goproxy.cn,direct go mod download "github.com/corazawaf/coraza-coreruleset/v4@${VERSION}" 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. 定位模块缓存
|
||||||
|
GOMODCACHE=$(go env GOMODCACHE)
|
||||||
|
# 找到实际下载的版本目录(可能是 v4@v4.25.0 等形式)
|
||||||
|
MOD_DIR=$(ls -d "$GOMODCACHE"/github.com/corazawaf/coraza-coreruleset/v4@* 2>/dev/null | tail -1)
|
||||||
|
if [ -z "$MOD_DIR" ]; then
|
||||||
|
echo "[ERROR] 未在模块缓存中找到 coraza-coreruleset" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ACTUAL_VER=$(basename "$MOD_DIR" | sed 's/v4@//')
|
||||||
|
echo " 实际版本: ${ACTUAL_VER}"
|
||||||
|
|
||||||
|
SRC_RULES="$MOD_DIR/rules/@owasp_crs"
|
||||||
|
if [ ! -d "$SRC_RULES" ]; then
|
||||||
|
echo "[ERROR] 规则目录不存在: $SRC_RULES" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd - > /dev/null
|
||||||
|
|
||||||
|
# 3. 复制规则文件
|
||||||
|
echo "[2/4] 复制规则文件到 ${RULES_DIR}/ ..."
|
||||||
|
mkdir -p "$RULES_DIR"
|
||||||
|
cp "$SRC_RULES"/*.conf "$SRC_RULES"/*.data "$RULES_DIR/"
|
||||||
|
CONF_COUNT=$(ls "$RULES_DIR"/*.conf 2>/dev/null | wc -l | tr -d ' ')
|
||||||
|
DATA_COUNT=$(ls "$RULES_DIR"/*.data 2>/dev/null | wc -l | tr -d ' ')
|
||||||
|
echo " ${CONF_COUNT} 个规则文件, ${DATA_COUNT} 个数据文件"
|
||||||
|
|
||||||
|
# 4. 复制 LICENSE
|
||||||
|
echo "[3/4] 复制 LICENSE ..."
|
||||||
|
cp "$MOD_DIR/rules/LICENSE" "$CRS_DIR/LICENSE" 2>/dev/null || true
|
||||||
|
|
||||||
|
# 5. 生成入口配置 crs/coraza.conf
|
||||||
|
echo "[4/4] 生成 ${CRS_DIR}/coraza.conf ..."
|
||||||
|
# 从版本号算出 crs_setup_version:v4.25.0 → 4250
|
||||||
|
SETUP_VER=$(echo "$ACTUAL_VER" | sed 's/^v//; s/\.//g; s/\([0-9]*\).*/\1/' | head -c 4)
|
||||||
|
# 更精确:取 major*1000 + minor*10 + patch
|
||||||
|
MAJOR=$(echo "$ACTUAL_VER" | sed 's/^v//; s/\..*//')
|
||||||
|
MINOR=$(echo "$ACTUAL_VER" | sed 's/^v[0-9]*\.//; s/\..*//')
|
||||||
|
PATCH=$(echo "$ACTUAL_VER" | sed 's/^v[0-9]*\.[0-9]*\.//; s/[^0-9].*//')
|
||||||
|
SETUP_VER="${MAJOR}${MINOR}${PATCH}"
|
||||||
|
|
||||||
|
cat > "$CRS_DIR/coraza.conf" << CONFEOF
|
||||||
|
# crs/coraza.conf —— OWASP CRS 入口配置(由 _fetch_crs.sh 自动生成)
|
||||||
|
#
|
||||||
|
# 加载顺序(由 engine.Reload 控制):
|
||||||
|
# 1. rules/waf.conf 基础配置(SecRuleEngine On 等)
|
||||||
|
# 2. rules/local/*.conf 本地自定义规则
|
||||||
|
# 3. rules/crs/coraza.conf ← 本文件(CRS 初始化)
|
||||||
|
# 4. rules/crs/rules/*.conf CRS 规则文件(按文件名字母序)
|
||||||
|
#
|
||||||
|
# 本文件职责:
|
||||||
|
# - 设置 CRS 必需的事务变量(crs_setup_version)
|
||||||
|
# - 初始化 XML/JSON 请求体处理器
|
||||||
|
#
|
||||||
|
# 不在此文件设置的项(由 waf.conf 统一管理):
|
||||||
|
# - SecRuleEngine / SecRequestBodyAccess / SecResponseBodyAccess
|
||||||
|
# - SecRequestBodyLimit
|
||||||
|
# - SecDefaultAction(coraza 不允许同 phase 重复定义)
|
||||||
|
|
||||||
|
# ===== 请求体处理器初始化 =====
|
||||||
|
SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \\
|
||||||
|
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
|
||||||
|
|
||||||
|
SecRule REQUEST_HEADERS:Content-Type "^application/json" \\
|
||||||
|
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
|
||||||
|
|
||||||
|
SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \\
|
||||||
|
"id:'200006',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
|
||||||
|
|
||||||
|
# ===== CRS 必需变量 =====
|
||||||
|
# 规则 901001 检查此变量,未设置则返回 500
|
||||||
|
SecAction \\
|
||||||
|
"id:900990,\\
|
||||||
|
phase:1,\\
|
||||||
|
pass,\\
|
||||||
|
nolog,\\
|
||||||
|
setvar:tx.crs_setup_version=${SETUP_VER}"
|
||||||
|
|
||||||
|
# ===== 可选调优(取消注释以启用) =====
|
||||||
|
# 异常评分阈值(默认 inbound=5, outbound=4)
|
||||||
|
# SecAction \\
|
||||||
|
# "id:900110,\\
|
||||||
|
# phase:1,\\
|
||||||
|
# pass,\\
|
||||||
|
# nolog,\\
|
||||||
|
# setvar:tx.inbound_anomaly_score_threshold=5,\\
|
||||||
|
# setvar:tx.outbound_anomaly_score_threshold=4"
|
||||||
|
|
||||||
|
# 偏执等级(默认 1;等级越高规则越严、误报越多)
|
||||||
|
# SecAction \\
|
||||||
|
# "id:900000,\\
|
||||||
|
# phase:1,\\
|
||||||
|
# pass,\\
|
||||||
|
# nolog,\\
|
||||||
|
# setvar:tx.blocking_paranoia_level=1"
|
||||||
|
CONFEOF
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==== 完成 ===="
|
||||||
|
echo " 版本: OWASP CRS ${ACTUAL_VER}"
|
||||||
|
echo " 规则: ${RULES_DIR}/ (${CONF_COUNT} conf + ${DATA_COUNT} data)"
|
||||||
|
echo " 入口: ${CRS_DIR}/coraza.conf"
|
||||||
|
echo " 许可: Apache-2.0 (${CRS_DIR}/LICENSE)"
|
||||||
|
echo ""
|
||||||
|
echo "验证: ./waf.bin -c config.yaml -list | head"
|
||||||
|
echo "热加载: kill -HUP \$(pidof waf.bin)"
|
||||||
@@ -4,8 +4,15 @@
|
|||||||
# - 修改后执行 `kill -HUP <pid>` 热加载
|
# - 修改后执行 `kill -HUP <pid>` 热加载
|
||||||
# - 校验语法: `waf -test rules/local/xxx.conf`
|
# - 校验语法: `waf -test rules/local/xxx.conf`
|
||||||
#
|
#
|
||||||
|
# 加载顺序(engine.Reload):
|
||||||
|
# 1. waf.conf ← 本文件(引擎基础配置)
|
||||||
|
# 2. local/*.conf 本地自定义规则(按文件名字母序)
|
||||||
|
# 3. crs/coraza.conf OWASP CRS 入口(若存在)
|
||||||
|
# 4. crs/rules/*.conf OWASP CRS 规则(按文件名字母序)
|
||||||
|
#
|
||||||
# 规则 ID 分段约定:
|
# 规则 ID 分段约定:
|
||||||
# 100000-199999 本地自定义规则
|
# 100000-199999 本地自定义规则
|
||||||
|
# 200000-200006 请求体处理器初始化(crs/coraza.conf)
|
||||||
# 900000-999999 OWASP CRS(由 crs/ 提供,不手改)
|
# 900000-999999 OWASP CRS(由 crs/ 提供,不手改)
|
||||||
#
|
#
|
||||||
# 注意:本文件不用 Include 指令(coraza 的 Include 路径相对于 cwd 解析,
|
# 注意:本文件不用 Include 指令(coraza 的 Include 路径相对于 cwd 解析,
|
||||||
@@ -16,6 +23,7 @@ SecRuleEngine On
|
|||||||
SecRequestBodyAccess On
|
SecRequestBodyAccess On
|
||||||
SecResponseBodyAccess On
|
SecResponseBodyAccess On
|
||||||
SecDefaultAction "phase:1,pass,log,auditlog"
|
SecDefaultAction "phase:1,pass,log,auditlog"
|
||||||
|
SecDefaultAction "phase:2,pass,log,auditlog"
|
||||||
|
|
||||||
# 请求体限制 10MB
|
# 请求体限制 10MB
|
||||||
SecRequestBodyLimit 10485760
|
SecRequestBodyLimit 10485760
|
||||||
|
|||||||
Reference in new issue
Block a user