2024数证杯预选赛 — server1-server2 服务器取证解题全过程
环境信息
| 服务器 | 角色 | IP:端口 | 用户/密码 |
|---|---|---|---|
| server1 | 站点服务器 | 192.168.100.118:22 | root/123456 |
| server2 | 数据库服务器 | 192.168.100.119:22 | root/123456 |
server1 环境概览
- OS: Alibaba Cloud Linux 3 (Soaring Falcon)
- Java Spring Boot 应用: /data/cal-0.0.1-SNAPSHOT.jar
- Active profile: sxj, 配置文件 application-sxj.yaml
- 域名: sxj.elitzoe.cn, sx.tiantianqb.com
- APP名称: 顺心借 (网贷/借款平台)
- 组件: Redis Sentinel + RabbitMQ + Consul 服务发现 + Nginx
- Nginx: 端口82(前端静态), 8082(Java后端), 443(HTTPS→8082)
server2 环境概览
- Docker MySQL 8.0.39, 容器名 mysql8.0.39
- docker compose v2.27.1
- 数据库: sxj_prod, 用户 sxy/Sxy000**
Q1 — 站点服务器从哪个云服务平台上调证过来的?
答案
阿里云
解题思路
通过多个维度确认为阿里云ECS实例:
- 主机名格式:
iZbp1gma2uf9hvsnbu9mdkZ— 这是阿里云ECS实例的标准命名格式(以 iZ 开头) - 内核版本:
Linux 5.10.134-12.2.al8.x86_64— al8 表示 Alibaba Linux 8 系列内核 - 阿里云安骑士:
/usr/local/aegis/目录存在 (aegis_client, AliSecCheck, globalcfg) - cloud-init配置:
datasource_list: [ AliYun ], distro: aliyun - 操作系统: NAME="Alibaba Cloud Linux" VERSION="3 (Soaring Falcon)"
- 阿里云服务:
/usr/sbin/aliyun-service,/usr/sbin/aliyun_installer存在
重点命令
# server1
hostname && cat /etc/hostname && uname -a
ls -la /usr/local/aegis/
cat /etc/os-release | head -5
cat /etc/cloud/cloud.cfg | grep -i "platform\|vendor\|aliyun"
Q2 — 站点服务器中数据库的密码是?
答案
Sxy000****
解题思路
应用为 Java Spring Boot,数据库配置在 application-sxj.yaml 文件中。通过解压 JAR 包读取配置文件获取数据库连接信息。
重点命令
# server1 - 解压JAR读取配置文件
unzip -p /data/cal-0.0.1-SNAPSHOT.jar BOOT-INF/classes/application-sxj.yaml
配置内容包含:
- url: jdbc:mysql://rm-bp18td28bsh13f5jy.mysql.rds.aliyuncs.com:***@2024#
- username: sxy
- password: Sxy000**
Q3 — 站点服务器用于提供服务发现的工具名是?
答案
consul
解题思路
在 /data/ 目录下发现 consul 数据目录,包含标准 Consul 组件:
- raft/ — Raft共识算法数据
- serf/ — Serf服务编排
- services/ — 服务注册信息
- node-id — 节点标识
- checkpoint-signature — 检查点签名
重点命令
# server1
ls -la /data/consul/
Q4 — 站点服务器数据库配置文件名是?
答案
application-sxj.yaml
解题思路
Spring Boot 多 profile 架构:
- application.yaml 设置
spring.profiles.active: 'sxj' - 实际数据库连接配置在 application-sxj.yaml 中
- JAR 内共15个profile配置文件(axj, dev, jdd, liu, mxh, ryj, ssh, sxj, test, xfm, xpt等),实际生效的是 sxj
- jar.sh 启动命令为
java -jar cal-0.0.1-SNAPSHOT.jar,无-Dspring.profiles.active覆盖参数 - JAR 内无 database.php 文件(题目格式示例仅为PHP参考)
重点命令
# server1 - 列出所有配置文件
unzip -l /data/cal-0.0.1-SNAPSHOT.jar | grep -E "\.yaml|\.yml|\.properties"
# 确认 active profile
unzip -p /data/cal-0.0.1-SNAPSHOT.jar BOOT-INF/classes/application.yaml
# 确认启动参数无覆盖
cat /data/jar.sh
# 验证datasource配置在sxj文件中
unzip -p /data/cal-0.0.1-SNAPSHOT.jar BOOT-INF/classes/application-sxj.yaml | grep -A5 datasource
Q5 — 该网站涉及的APP名称是?
答案
顺心借
解题思路
- 配置文件确认: application-sxj.yaml 中
app.name: 顺心借 - 浏览器验证: 访问 http://192.168.100.118:82/#/login,页面 h1 标题明确显示"顺心借"
- 密码登录验证: qc用户(19883213882/123456) POST /admin/index/loginByPassword → token有效
- 页面导航栏:后台管理 | 财务审核 | 数据报表
重点命令
# 浏览器访问登录页 http://192.168.100.118:82/#/login 验证标题
# API密码登录验证
curl -X POST http://localhost:8082/admin/index/loginByPassword \
-H 'Content-Type: application/json' \
-d '{"mobile":"19883213882","password":"123456"}'
Q6 — 存储大量身份证照的OSS中的AccessKeyID后八位是?
答案
EuZJybzD
解题思路
从 application-sxj.yaml 中获取 OSS 配置:
- accessKeyId: LTAI5tBt7rHreKjDEuZJybzD (共24字符)
- 取后8位: EuZJybzD
重点命令
# 从配置文件提取
unzip -p /data/cal-0.0.1-SNAPSHOT.jar BOOT-INF/classes/application-sxj.yaml | grep -i accesskey
Q7 — 站点服务器用于消息转发代理工具所使用的端口号是?
答案
5672
解题思路
application-sxj.yaml 中配置了 RabbitMQ,端口为 5672。通过 netstat 确认端口正在监听。
重点命令
# server1 - 确认RabbitMQ端口
netstat -tlnp | grep 5672
Q8 — 站点服务器用于启动定时任务的代码片段存在于?
答案
MobileStatusTask.class
解题思路
- JAR包中搜索 Task 相关类,发现
com/rh/cal/task/MobileStatusTask.class - javap 反编译确认:
@Scheduled(cron = "0 * * * * ?")— Spring 定时任务注解- 常量池确认
Lorg/springframework/scheduling/annotation/Scheduled;和Lorg/springframework/stereotype/Component; - scheduled() 方法日志输出"执行定时任务"+时间戳,调用 userService.uptInMobileStatus()
- 有 try-catch 异常处理
重点命令
# server1
cd /tmp && unzip -l /data/cal-0.0.1-SNAPSHOT.jar | grep -i "task\|Task"
unzip -o /data/cal-0.0.1-SNAPSHOT.jar BOOT-INF/classes/com/rh/cal/task/MobileStatusTask.class
javap -p MobileStatusTask.class
javap -c -p MobileStatusTask.class
javap -v MobileStatusTask.class | grep -i "scheduled\|cron"
Q9 — 站点服务器用于验证用户输入的验证码是否匹配的代码片段存在于?
答案
AdminIndexConller.class
解题思路
- JAR包中搜索 Index 相关类,发现
AdminIndexConller.class(注意拼写是 Conller 不是 Controller) - javap 反编译 login() 方法完整确认验证码匹配逻辑:
stringRedisTemplate.opsForValue().get(ADMIN_PHONE_CODE + dto.getMobile())— 从Redis获取验证码dto.getCode().equals(redisCode)— 用户输入与Redis验证码对比AssertUtil.isTrue(..., "验证码错误")— 不匹配则抛"验证码错误"
- 额外发现:main方法包含
DigestUtil.md5Hex("admin+123")— 密码加密测试代码
重点命令
# server1
cd /tmp && unzip -l /data/cal-0.0.1-SNAPSHOT.jar | grep "Index"
unzip -o /data/cal-0.0.1-SNAPSHOT.jar BOOT-INF/classes/com/rh/cal/controller/admin/AdminIndexConller.class
javap -p AdminIndexConller.class
javap -c -p AdminIndexConller.class
Q10 — 数据库服务器中Docker容器镜像中mysql的镜像ID号前6位是?
答案
23b013
解题思路
在 server2 上查看 Docker 镜像列表,mysql:8.0.39 镜像 ID 为 23b013c7c67d,取前6位。
重点命令
# server2
docker images
Q11 — 数据库服务器中DockerCompose的版本号是?
答案
2.27.1
解题思路
直接查询 docker compose 版本。
重点命令
# server2
docker compose version # v2.27.1
Q12 — 数据库服务器中用于存储后台登录账号的数据表名是?
答案
sys_user
解题思路
首先通过 hosts 文件发现 RDS 域名指向 server2 (192.168.100.119),然后通过 server2 的 MySQL 容器连接数据库,查看 sxj_prod 库中所有表,确认 sys_user 为后台管理员登录账号表。
重点命令
# 发现RDS域名映射
grep "rds.aliyuncs" /etc/hosts
# 输出: 192.168.100.119 rm-bp18td28bsh13f5jy.mysql.rds.aliyuncs.com
# server2 - 连接数据库查看表
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod -e "SHOW TABLES;"
# 查看表结构
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod -e "DESC sys_user;"
Q13 — 后台管理员"xpt-0"所绑定的手机号码是?
答案
19521510863
解题思路
从 sys_user 表中查询 name='xpt-0' 的记录,获取 phone 字段。
重点命令
# server2
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT id, phone, name FROM sys_user WHERE name='xpt-0';"
Q14 — 用户首次借款初始额度是?
答案
4000
解题思路
global_config 表中 config_key=11 对应"用户初始额度",content=4000。
重点命令
# server2
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT * FROM global_config WHERE config_key=11;"
Q15 — 受害者在平台中一共结款了几次?
答案
1857
解题思路
- 初始查询:
SELECT COUNT(*) FROM order_from WHERE status=5 AND repayment_time IS NOT NULL;→ 1856 (误加条件) - 修正查询:
SELECT COUNT(*) FROM order_from WHERE status=5;→ 1857 - 浏览器后台验证: 使用 qc 用户登录后台,首页显示"总还款订单量 1857"确认
差异分析:有1条记录 status=5 但 repayment_time IS NULL,初始查询漏计。
重点命令
# 正确查询
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT COUNT(*) FROM order_from WHERE status=5;"
# 浏览器后台验证 (需先设置Redis验证码)
redis-cli -a 'Sxy000**' SET ADMIN-PHONE19883213882 1234 EX 600
curl -X POST http://localhost:8082/admin/index/login \
-H 'Content-Type: application/json' \
-d '{"mobile":"19883213882","code":"1234"}'
# 访问 http://192.168.100.118:82/#/home 查看"总还款订单量"
Q16 — 该平台中所有下单用户成功完成订单总金额是?
答案
11408100
解题思路 (经历两次修正)
第一次查询 (错误): SELECT SUM(arrival_amount) FROM order_from WHERE status=5; → 6626555
- 问题:使用的是到账金额而非合同金额,且只统计了已完成(status=5)
第二次修正:
- 浏览器后台验证: 首页"所有下单用户"卡片显示"通过订单总金额" = 11408100
- API验证: GET /admin/statistics/getGlobalConfigList → loanVO.loanMoney = 11408100.0
- 正确查询:
SELECT SUM(quota) FROM order_from WHERE status>=4;→ 11408100
字段关系:
- status=4 (REPAYMENT/待还款): quota = 1213400
- status=5 (SUCCESS/已完成): quota = 10194700
- 总计: 1213400 + 10194700 = 11408100
教训: 题目"所有下单用户成功完成订单总金额"对应后台"通过订单总金额",统计范围是 status>=4 (待还款+已完成) 的合同金额总和。
重点命令
# 正确查询
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT SUM(quota) FROM order_from WHERE status>=4;"
# API验证 (需token)
curl -H "admin-token: <token>" http://localhost:8082/admin/statistics/getGlobalConfigList
Q17 — 该平台中逾期费率是?
答案
0.1
解题思路
global_config 表中 config_key=14 对应"逾期费率",content=0.1。
重点命令
# server2
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT * FROM global_config WHERE config_key=14;"
Q18 — 该平台中累计还款总金额是?
答案
10194700
解题思路 (经历修正)
初始查询 (错误): SELECT SUM(actual_repayment_amount) FROM order_detail WHERE status=5; → 9585392
- 问题:使用了明细表 order_detail 而非业务表 order_from
修正过程:
- 浏览器后台验证: 首页显示"总还款金额" = 10194700
- API验证: GET /admin/statistics/getGlobalConfigList → paidUpLoanVO.sumMoney = 10194700
- 源码确认: 追溯 getStaHkOrderAcountVO SQL =
SELECT SUM(quota) FROM order_from WHERE status=5→ 10194700 - 正确查询:
SELECT SUM(quota) FROM order_from WHERE status=5;→ 10194700
教训: 后台统计使用的是业务表 order_from 的 quota 字段,而非明细表 order_detail 的 actual_repayment_amount。
重点命令
# 正确查询
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT SUM(quota) FROM order_from WHERE status=5;"
# 源码追溯
unzip -p /data/cal-0.0.1-SNAPSHOT.jar "BOOT-INF/classes/mapper/*.xml" | grep -A5 "getStaHkOrderAcountVO"
Q19 — 该平台总共设置了多少种借款额度?
答案
18
解题思路
quota 表存储借款额度配置,is_delete=0 表示未删除的有效记录。
重点命令
# server2
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT COUNT(*) FROM quota WHERE is_delete=0;"
Q20 — 该平台一共有多少个借款渠道?
答案
131
解题思路 (经历修正)
初始查询 (错误): SELECT COUNT(DISTINCT channel) FROM channel_data WHERE status=1; → 129
- 问题:使用了 DISTINCT 去重,但题目问的是渠道记录总数
修正过程:
- 用户确认: 渠道总共140个,开启状态(status=1)131个
- 正确查询:
SELECT COUNT(*) FROM channel_data WHERE status=1;→ 131 - 差异分析: 有2个channel重复(DDD2和某个中文名称各出现2次),DISTINCT后少2条
重点命令
# 统计总记录和开启数
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT COUNT(*) total, SUM(CASE WHEN status=1 THEN 1 ELSE 0 END) active_count FROM channel_data;"
# 查找重复channel
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT channel, COUNT(*) cnt FROM channel_data WHERE status=1 GROUP BY channel HAVING cnt > 1;"
# 正确查询
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT COUNT(*) FROM channel_data WHERE status=1;"
Q21 — 该平台对已完成用户收取了总计多少元服务费?
答案
4051915
解题思路 (经历修正)
初始查询 (错误): SELECT ROUND(SUM(rate_money)) FROM order_from WHERE status=5; → 3568145
- 问题:使用了业务表 order_from 的 rate_money 字段,且仅统计 status=5
修正过程 — 三层交叉验证:
-
Enum字节码分析:
javap -c -v AmountBillTypeEnum.class | grep -E 'ldc|Utf8'确认: type=1(借款金额), type=2(打款金额), type=3(服务费), type=4(展期费用), type=5(逾期金额), type=6(减免金额), type=7(逾期减免金额), type=8(还款金额)
-
API验证:
curl -X POST http://localhost:8082/admin/amount/bill/getTjList \ -H "admin-token: <token>" -H "Content-Type: application/json" -d '{}'返回 type=3: sumMoney=4051915, num=2044
-
源码确认: AmountBillMapper.xml 中 getTjList SQL:
SELECT type, count(amount_id) num, sum(money) sumMoney ... FROM amount_bill(仅可选时间范围过滤,无状态过滤) -
数据库验证:
SELECT SUM(money) FROM amount_bill WHERE type=3;→ 4051915.00
差异分析:
- order_from.rate_money(status=5) = 3568145 (仅已完成订单的初始服务费)
- amount_bill.type=3 join status=5 = 3566745 (关联已完成订单的服务费账单)
- amount_bill.type=3 (全部) = 4051915 (平台全部服务费收入)
后台"交易统计"页面展示的是 amount_bill 表全部记录,而非仅关联已完成订单。
重点命令
# Enum字节码分析
javap -c -v AmountBillTypeEnum.class | grep -E 'ldc|Utf8'
# 正确查询
docker exec mysql8.0.39 mysql -h 192.168.100.119 -P 3306 -u sxy -p'Sxy000**' sxj_prod \
-e "SELECT SUM(money) FROM amount_bill WHERE type=3;"
# API验证
curl -X POST http://localhost:8082/admin/amount/bill/getTjList \
-H "admin-token: <token>" -H "Content-Type: application/json" -d '{}'
# 源码追溯
unzip -p /data/cal-0.0.1-SNAPSHOT.jar "BOOT-INF/classes/mapper/*.xml" | grep -A5 "getTjList"
后台网站恢复过程
RabbitMQ 启动失败
错误: systemctl start rabbitmq-server 启动失败
分析: 下载 erl_crash.dump,发现 {epmd_error,"iZbp1gma2uf9hvsnbu9mdkZ",address}
根因: /etc/hosts 中主机名映射到旧IP 172.20.148.2,当前实际IP为192.168.100.118
修复:
sed -i 's|^172\.20\.148\.2\tiZbp1gma2uf9hvsnbu9mdkZ.*|192.168.100.118\tiZbp1gma2uf9hvsnbu9mdkZ|' /etc/hosts
systemctl start rabbitmq-server # active (running) PID 28999
前台JS地址替换
37个JS文件包含外网IP 47.96.140.186,全部替换为 192.168.100.118:
cd /www/admin/ && find ./ -type f -name '*.js' -exec sed -i 's/47.96.140.186/192.168.100.118/g' {} +
服务启动顺序
- RabbitMQ:
systemctl start rabbitmq-server→ active (running) PID 28999, 端口 5672/15672/25672 - Consul:
cd /root/ && ./consul.sh→ OK - Java Spring Boot:
cd /data && sh jar.sh→ PID 11759, 端口 8082 - Nginx: 端口 82 (前端静态)
- Redis: 端口 6379
后台登录方法
方法一:密码登录 (推荐)
# qc用户(19883213882)密码=123456, MD5=e10adc3949ba59abbe56e057f20f883e
curl -X POST http://localhost:8082/admin/index/loginByPassword \
-H 'Content-Type: application/json' \
-d '{"mobile":"19883213882","password":"123456"}'
# 返回 token, 用于后续API调用
方法二:Redis验证码注入
redis-cli -a 'Sxy000**' SET ADMIN-PHONE19883213882 1234 EX 600
curl -X POST http://localhost:8082/admin/index/login \
-H 'Content-Type: application/json' \
-d '{"mobile":"19883213882","code":"1234"}'
Shiro Realm 用户状态注意:
- sys_user.state=1 (Boolean true) → 已禁用 (LockedAccountException "该用户已被禁用 !")
- sys_user.state=0 (Boolean false) → 正常可登录
- qc 用户 state=0 可登录,xpt-0/xpt-1/tw 用户 state=1 被禁用
答案汇总
| 题号 | 题目要点 | 答案 | 关键验证方法 |
|---|---|---|---|
| Q1 | 云服务平台 | 阿里云 | cloud-init AliYun, aegis, Alibaba Cloud Linux |
| Q2 | 数据库密码 | Sxy000** | application-sxj.yaml |
| Q3 | 服务发现工具 | consul | /data/consul/ 目录结构 |
| Q4 | 数据库配置文件名 | application-sxj.yaml | Spring Boot active profile=sxj |
| Q5 | APP名称 | 顺心借 | app.name + 浏览器登录页标题 |
| Q6 | OSS AccessKeyID后八位 | EuZJybzD | LTAI5tBt7rHreKjDEuZJybzD 后8位 |
| Q7 | 消息代理端口 | 5672 | RabbitMQ |
| Q8 | 定时任务代码文件名 | MobileStatusTask.class | @Scheduled 字节码验证 |
| Q9 | 验证码匹配代码文件名 | AdminIndexConller.class | login()方法字节码 |
| Q10 | MySQL镜像ID前6位 | 23b013 | docker images |
| Q11 | DockerCompose版本 | 2.27.1 | docker compose version |
| Q12 | 后台登录账号表名 | sys_user | SHOW TABLES + DESC |
| Q13 | xpt-0绑定手机 | 19521510863 | sys_user查询 |
| Q14 | 首次借款初始额度 | 4000 | global_config config_key=11 |
| Q15 | 受害者结款次数 | 1857 | COUNT(status=5) + 后台验证 |
| Q16 | 成功完成订单总金额 | 11408100 | SUM(quota) status>=4, 三层验证 |
| Q17 | 逾期费率 | 0.1 | global_config config_key=14 |
| Q18 | 累计还款总金额 | 10194700 | SUM(quota) status=5, 三层验证 |
| Q19 | 借款额度种类数 | 18 | COUNT(is_delete=0) |
| Q20 | 借款渠道数 | 131 | COUNT(status=1)非DISTINCT |
| Q21 | 服务费总计 | 4051915 | amount_bill type=3, 三层验证 |
关键经验总结
1. 统计类题目必须三层交叉验证
- 第一层:浏览器后台UI查看统计数据
- 第二层:curl直接调用后端API获取JSON
- 第三层:源码追溯 Controller → Service → Mapper XML 找到实际SQL
2. 金额字段语义容易混淆
- quota = 合同金额/借款额度
- arrival_amount = 到账金额
- rate_money = 服务费 (初始合同)
- 关系: quota = arrival_amount + rate_money
- 账单表 amount_bill 记录实际发生的每一笔费用 (含展期、补扣等)
3. COUNT vs COUNT DISTINCT 陷阱
- Q20: 题目问"多少个借款渠道"=记录数131,而非去重值129
- 存在重复channel时需通过后台UI确认计数方式
4. Java字节码分析技巧
javap -v classfile | grep -i "Lorg/springframework"查看Spring注解引用- Enum常量池直接暴露业务含义映射:
javap -c -v Enum.class | grep -E 'ldc|Utf8'
5. Shiro用户状态字段反直觉
- state=1 = 已禁用, state=0 = 正常 (Boolean类型)
- 验证前必须确认目标用户 state=0