修复Redis安全漏洞并新增日志查询接口
- docker-compose.yml: Redis去掉端口公网映射,加密码和强制单机模式,修复未授权访问被攻击为从节点的问题 - 新增 ApiJstCallLogController(/manage/jst/call-log/list)橘视厅调用日志查询接口 - 新增 AuditLogController(/manage/audit-log/list)审计日志查询接口 - 新增 Redis安全防护备忘.md 文档
This commit is contained in:
80
Redis安全防护备忘.md
Normal file
80
Redis安全防护备忘.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Redis 安全防护备忘
|
||||||
|
|
||||||
|
## 事件回顾
|
||||||
|
|
||||||
|
2026-07-31,生产环境第三方接口报 "Redis服务异常",排查发现:
|
||||||
|
|
||||||
|
1. Redis 容器(daycard-redis)被外部设置为 `replicaof 109.244.159.27 29846`(从节点)
|
||||||
|
2. master 不可达,Redis 进入只读状态,写入操作全部失败
|
||||||
|
3. PartnerSignFilter 的 nonce 防重放(`setIfAbsent`)写入被拒,导致所有第三方接口不可用
|
||||||
|
|
||||||
|
## 根因
|
||||||
|
|
||||||
|
Redis 未授权访问被攻击:
|
||||||
|
|
||||||
|
- **端口暴露公网**:docker-compose.yml 中 `ports: "6379:6379"` 将 Redis 暴露到 0.0.0.0:6379
|
||||||
|
- **无密码**:REDIS_PASSWORD 为空,任何人可直连 Redis
|
||||||
|
- 攻击者扫描公网 6379 端口,未授权连接后执行 `CONFIG SET replicaof 109.244.159.27 29846`,将 Redis 变为从节点
|
||||||
|
|
||||||
|
## 防护措施(已落实到 docker-compose.yml)
|
||||||
|
|
||||||
|
### 1. 不暴露端口到公网
|
||||||
|
|
||||||
|
去掉 Redis 的 `ports` 映射,仅在 docker 内网访问。应用容器通过 `redis:6379` 访问,无需宿主机端口映射。
|
||||||
|
|
||||||
|
### 2. 加密码
|
||||||
|
|
||||||
|
```
|
||||||
|
command: redis-server --requirepass AsRedis2026!Safe
|
||||||
|
```
|
||||||
|
|
||||||
|
应用环境变量同步配置 `REDIS_PASSWORD=AsRedis2026!Safe`。
|
||||||
|
|
||||||
|
### 3. 强制单机模式
|
||||||
|
|
||||||
|
```
|
||||||
|
command: redis-server --requirepass AsRedis2026!Safe --replicaof no one
|
||||||
|
```
|
||||||
|
|
||||||
|
`--replicaof no one` 确保即使被连上也无法被设置为从节点。
|
||||||
|
|
||||||
|
## 重新部署步骤
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 拉取最新代码(含修改后的 docker-compose.yml)
|
||||||
|
cd /path/to/as-daycard-openapi
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# 2. 重建 Redis 容器(彻底清除被篡改的配置)
|
||||||
|
docker rm -f daycard-redis
|
||||||
|
|
||||||
|
# 3. 启动新的 Redis 容器
|
||||||
|
docker-compose up -d redis
|
||||||
|
|
||||||
|
# 4. 验证:replicaof 应为空
|
||||||
|
docker exec daycard-redis redis-cli -a 'AsRedis2026!Safe' config get replicaof
|
||||||
|
# 预期输出:
|
||||||
|
# 1) "replicaof"
|
||||||
|
# 2) ""
|
||||||
|
|
||||||
|
# 5. 验证:密码生效(无密码连接应失败)
|
||||||
|
docker exec daycard-redis redis-cli config get replicaof
|
||||||
|
# 预期输出:NOAUTH Authentication required.
|
||||||
|
|
||||||
|
# 6. 重启应用容器(加载新密码)
|
||||||
|
docker-compose up -d daycard-openapi
|
||||||
|
|
||||||
|
# 7. 验证应用正常
|
||||||
|
curl http://localhost:8084/openapi/partner/media/package \
|
||||||
|
-H "appKey: 你的appKey" \
|
||||||
|
-H "timestamp: $(date +%s%3N)" \
|
||||||
|
-H "nonce: test123" \
|
||||||
|
-H "sign: 签名"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 日常运维注意事项
|
||||||
|
|
||||||
|
- **不要恢复 6379 端口映射**:如需宿主机调试 Redis,用 `docker exec -it daycard-redis redis-cli -a 'AsRedis2026!Safe'` 进入
|
||||||
|
- **密码保密**:`AsRedis2026!Safe` 已写在 docker-compose.yml 中,如需修改请同步更新 Redis command 和应用 REDIS_PASSWORD 环境变量
|
||||||
|
- **定期检查**:`docker exec daycard-redis redis-cli -a 'AsRedis2026!Safe' config get replicaof` 确认未被篡改
|
||||||
|
- **服务器安全**:检查 /root/.ssh/authorized_keys 是否有陌生 key,确认未被 SSH 入侵
|
||||||
@@ -18,6 +18,7 @@ services:
|
|||||||
- DB_PASSWORD=MAe8pALGWjE6jCwi
|
- DB_PASSWORD=MAe8pALGWjE6jCwi
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
|
- REDIS_PASSWORD=AsRedis2026!Safe
|
||||||
- REDIS_OPEN=true
|
- REDIS_OPEN=true
|
||||||
- JWT_SECRET=2F064BA10037477FBB3A1BF9D508AE96
|
- JWT_SECRET=2F064BA10037477FBB3A1BF9D508AE96
|
||||||
- JWT_EXPIRE=604800
|
- JWT_EXPIRE=604800
|
||||||
@@ -37,6 +38,6 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
container_name: daycard-redis
|
container_name: daycard-redis
|
||||||
ports:
|
command: redis-server --requirepass AsRedis2026!Safe --replicaof no one
|
||||||
- "6379:6379"
|
# 不映射端口到宿主机,仅在 docker 内网访问,防止公网未授权访问
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package io.as.modules.jst.controller;
|
||||||
|
|
||||||
|
import io.as.common.utils.PageUtils;
|
||||||
|
import io.as.common.utils.R;
|
||||||
|
import io.as.modules.jst.service.ApiJstCallLogService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 橘视厅调用日志管理(管理后台,JWT鉴权)
|
||||||
|
* <p>记录调用橘视厅免密登录、卡密激活、会员查询等接口的出站请求与响应</p>
|
||||||
|
*/
|
||||||
|
@Api(tags = "橘视厅调用日志")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/manage/jst/call-log")
|
||||||
|
public class ApiJstCallLogController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ApiJstCallLogService apiJstCallLogService;
|
||||||
|
|
||||||
|
@ApiOperation("分页查询橘视厅调用日志")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public R list(@RequestParam Map<String, Object> params) {
|
||||||
|
PageUtils page = apiJstCallLogService.queryPage(params);
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("page", page);
|
||||||
|
return R.dataSuccess(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package io.as.modules.sys.controller;
|
||||||
|
|
||||||
|
import io.as.common.utils.PageUtils;
|
||||||
|
import io.as.common.utils.R;
|
||||||
|
import io.as.modules.sys.service.AuditLogService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审计日志管理(管理后台,JWT鉴权)
|
||||||
|
* <p>记录平台运营操作(分配/销售/转合同/绑定/解绑/激活/采购等)的审计轨迹</p>
|
||||||
|
*/
|
||||||
|
@Api(tags = "审计日志")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/manage/audit-log")
|
||||||
|
public class AuditLogController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuditLogService auditLogService;
|
||||||
|
|
||||||
|
@ApiOperation("分页查询审计日志")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public R list(@RequestParam Map<String, Object> params) {
|
||||||
|
PageUtils page = auditLogService.queryPage(params);
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("page", page);
|
||||||
|
return R.dataSuccess(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user