time 1 2 3 key: 长度为 5\4 线索: 开机时间戳 1630416000 ptdh{dqpfsajpsvjgSVgbVQIFLWXZ}
看到 ptdh{...} 联想到flag 格式flag{...}。 说明 ptdh 对应 flag,这是一种字母替换加密。
又因为提示说”key 长度为 5\4”,说明这不是简单的凯撒密码(因为凯撒只有一个固定偏移量), 而是维吉尼亚密码——用一个多字符的密钥循环加密
关于维吉尼亚密码 字母编号 a=0 到 z=25,公式就一行:
1 密文 = (明文 + 密钥) mod 26 明文 = (密文 - 密钥 + 26) mod 26
推导 key:密文前4个 p t d h 对应明文(flag 格式)f l a g,反推:
1 密钥[0] = (密文 - 明文 + 26) mod 26
密钥 = kidb(长度4,循环使用)
{ 和 } 是特殊符号,不参与加密,直接保留原样
解密
大小写规则:密文是大写字母 → 明文也是大写;密文是小写字母 → 明文也是小写
timeisgoingfINdaLIFEBOUY
flag{timeisgoingfINdaLIFEBOUY}
时间正在流逝,寻找救生圈
p.s.随波逐流7.2对于维吉尼亚密码是大小写不分的 可以使用在线网址
http://moersima.00cha.net/wjly.asp
看了一下讲解视频 实际上是有推导链的
第一步:时间戳转日期 1630416000→2021-08-31 21:12:00(北京时间)
第二步:提示 key 长度为 5\4 的意思是:从日期中挑出5个数字,拼成4个数
从 2021-08-31 中取:
1 2 3 4 5 6 年份 2021 → 取最后一个 "1" 月份 08 → 取 "0" 和 "8" 日期 31 → 取 "3" 和 "1" 5个数字:1, 0, 8, 3, 1 ↓ 拼成4个数(10用了两位,其余各用一位) 10 8 3 1
第三步:数字转字母(a=0 起) 1 2 3 4 5 10 → k 8 → i 3 → d 1 → b 密钥 = kidb
这个大概真想不到
麦填 jpg文件 010打开
jpg文件 文件头是FF D8 FF文件尾是FF D9CTRL+F找到FF D9后面有一段base64 解码后是英文789
保存后面一大段png图片 扫描二维码 获得flag{win
flag{win789}
隐藏的二维码 使用Python遍历图片像素并提取LSB通道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 from PIL import Image import argparse import os def extract_lsb(input_path, output_path): """ 遍历图片像素,提取LSB通道并生成新图片。 新图片中,如果原像素某通道的LSB为1,则设为255(白);如果为0,则设为0(黑)。 这样可以清晰地看到隐藏的数据模式。 """ if not os.path.exists(input_path): print(f"错误: 找不到文件 {input_path}") return try: img = Image.open(input_path) except Exception as e: print(f"错误: 无法打开图片 {e}") return # 确保图片是 RGB 模式,如果不是则转换(处理 RGBA 或灰度图) if img.mode != 'RGB': img = img.convert('RGB') width, height = img.size print(f"正在处理图片: {input_path} ({width}x{height})...") # 创建一个新的空白图片用于存放结果 lsb_img = Image.new('RGB', (width, height)) # 获取像素访问对象 pixels = img.load() lsb_pixels = lsb_img.load() # 遍历每一个像素 for y in range(height): for x in range(width): r, g, b = pixels[x, y] # 提取每个通道的最低有效位 (LSB) # & 1 运算可以获取二进制最后一位 (0 或 1) lsb_r = r & 1 lsb_g = g & 1 lsb_b = b & 1 # 将提取出的位放大到 0 或 255 以便肉眼观察 # 如果位是 1 -> 255 (白色), 如果位是 0 -> 0 (黑色) new_r = 255 if lsb_r else 0 new_g = 255 if lsb_g else 0 new_b = 255 if lsb_b else 0 lsb_pixels[x, y] = (new_r, new_g, new_b) # 保存结果 lsb_img.save(output_path) print(f"LSB 提取完成!结果已保存至: {output_path}") if __name__ == "__main__": # 设置命令行参数解析 parser = argparse.ArgumentParser(description="像素级分析:提取图片的LSB通道") parser.add_argument("input_image", help="输入图片的路径") parser.add_argument("-o", "--output", default="lsb_output.png", help="输出图片的路径 (默认: lsb_output.png)") args = parser.parse_args() extract_lsb(args.input_image, args.output)
flag{qrc0de_1s_h1dden_1n_p1xels}
做日志分析类题目的几个概念:
nginx_access.log:记录所有 HTTP 请求,格式为:IP - - [时间] "请求方法 路径 协议" 状态码 响应大小 "来源页" "浏览器"
mysql_general.log:记录数据库执行的每一条 SQL 语句
opencart_error.log:OpenCart 网站自己写的日志,包含登录成功/失败等应用层事件
auth.log:Linux 系统的认证日志,记录 SSH 登录等
状态码:200 = 成功,302 = 跳转,404 = 找不到,403 = 禁止访问
先看看每个文件有多少行,心里有个底:
1 Get-ChildItem *.log | ForEach-Object { "{0,-30} {1}" -f $_.Name, (Get-Content $_.FullName).Count }
1 2 3 4 5 auth.log 371 mysql_general.log 267 nginx_access.log 1835 nginx_error.log 242 opencart_error.log 544
nginx访问日志最多,是主要分析对象。再看一下README了解背景
1 外部攻击者对 /admin 进行探测与登录尝试,随后通过后台文件管理器(filemanager)进行可疑上传……
就可以知道 攻击方向是/admin后台
attack_log 题目一:完成后台登录成功的攻击IP地址为? 后台登录成功。意味着有人通过POST请求提交账号密码。并且登录成功了
搜索被重复请求最多的路径,就是OpenCart的后台登录接口。
1 2 3 Get-Content nginx_access.log | Select-String "POST" | ForEach-Object { ($_.Line -split '"')[1] } | Group-Object | Sort-Object Count -Descending | Select-Object -First 15 | Select-Object Count, Name | Format-Table -Wrap
1 2 3 4 5 Count Name ----- ---- 27 POST /admin/index.php?route=common/login HTTP/1.1 1 POST /image/catalog/.cache_hidden_218.php HTTP/1.1 1 POST /admin/index.php?route=common/filemanager/upload&user_token=7f2d...ab91 HTTP/1.1
OpenCart后台登录接口是/admin/index.php?route=common/login
搜索route=common/login并保存到文件(此处为什么不用完整的接口 因为?在命令行里是特殊字符 会引起问题 截取一段特殊的字符效果也可以)
1 2 findstr "POST.*route=common/login" nginx_access.log > login.txt notepad login.txt
两个IP在发送请求
39.98.12.44发了大量的POST 应该是在爆破
45.133.12.77只发了一次POST
这两个二选一。现在要判断哪个IP实际上登录成功了
实际上登录成功后,OpenCart会跳转到后台主页。url里会带一个user_token参数
在ngnix_access.log里ctrl+F 45.133.12.77
1 2 3 4 5 45.133.12.77 - - [18/Feb/2026:01:28:52 +0800] "POST /admin/index.php?route=common/login HTTP/1.1" 200 8342 "http://47.95.21.47/admin/index.php?route=common/login" "Mozilla/5.0" 45.133.12.77 - - [18/Feb/2026:01:28:54 +0800] "GET /admin/index.php?route=common/dashboard&user_token=7f2d...ab91 HTTP/1.1" 200 20144 "http://47.95.21.47/admin/index.php?route=common/login" "Mozilla/5.0" 45.133.12.77 - - [18/Feb/2026:01:28:59 +0800] "POST /admin/index.php?route=common/filemanager/upload&user_token=7f2d...ab91 HTTP/1.1" 200 412 "http://47.95.21.47/admin/index.php?route=common/filemanager" "Mozilla/5.0" 45.133.12.77 - - [18/Feb/2026:01:49:20 +0800] "GET /image/catalog/.cache_hidden_218.php HTTP/1.1" 200 87 "-" "curl/7.68.0" 45.133.12.77 - - [18/Feb/2026:01:49:27 +0800] "POST /image/catalog/.cache_hidden_218.php HTTP/1.1" 200 39 "-" "curl/7.68.0"
可以看到
POST登录成功(200)
立刻获得了真实的user_token``7f2d...ab91
随后上传了文件到filemanager也就是文件管理助手那里
最后访问了一个php文件(上传的webshell)
反观39.98.12.44。它的请求里user_token一直都是admin123,是一个固定字符串,不是真实的会话token,说明它一直没登录成功,只是在暴力猜密码
二次验证
搜索"login"忽略大小写
1 findstr /I "login" opencart_error.log
1 2 2026-02-18 00:41:12 - Warning: Login failed for username 'admin' from 103.216.40.77 2026-02-18 01:08:01 - Info: Admin login success for username 'admin' from 45.133.12.77
明确写了45.133.12.77是登录成功
flag{45.133.12.77}
题目二:完成后台登录成功的攻击IP首次访问后台登录入口的时间为? ctrl+F 45.133.12.77
1 IP - - [时间] "请求方法 路径 协议" 状态码 响应大小 "Referer" "User-Agent"
1 2 3 4 5 45.133.12.77 - - [18/Feb/2026:01:28:52 +0800] "POST /admin/index.php?route=common/login HTTP/1.1" 200 8342 "http://47.95.21.47/admin/index.php?route=common/login" "Mozilla/5.0" 45.133.12.77 - - [18/Feb/2026:01:28:54 +0800] "GET /admin/index.php?route=common/dashboard&user_token=7f2d...ab91 HTTP/1.1" 200 20144 "http://47.95.21.47/admin/index.php?route=common/login" "Mozilla/5.0" 45.133.12.77 - - [18/Feb/2026:01:28:59 +0800] "POST /admin/index.php?route=common/filemanager/upload&user_token=7f2d...ab91 HTTP/1.1" 200 412 "http://47.95.21.47/admin/index.php?route=common/filemanager" "Mozilla/5.0" 45.133.12.77 - - [18/Feb/2026:01:49:20 +0800] "GET /image/catalog/.cache_hidden_218.php HTTP/1.1" 200 87 "-" "curl/7.68.0" 45.133.12.77 - - [18/Feb/2026:01:49:27 +0800] "POST /image/catalog/.cache_hidden_218.php HTTP/1.1" 200 39 "-" "curl/7.68.0"
Referer意思是“我是从哪个页面发出这个请求的”
正常用户登录流程:
GET /admin/login → 浏览器加载登录页面
填写账号密码,点提交
POST /admin/login → 浏览器发送表单,Referer 自动填为上一步的页面地址
所以Referer是登录页,说明攻击者在POST之前已经先GET访问过登录页了,只是那次 GET 请求恰好没被日志记录下来
所以nginx日志能找到的最早访问后台登录入口的记录都是这一条
flag{2026-02-18 01:28:52}
题目三:攻击者探测的敏感环境配置文件路径 攻击者在尝试登录之前,通常会先”侦察”目标服务器,看看有没有泄露的配置文件。常见的敏感文件有:
.env:环境配置,常含数据库密码、密钥
.git/config:Git 仓库信息
wp-config.php:WordPress 配置
config.php、database.yml 等
搜索敏感文件关键词
1 findstr /R /I "\.env \.git config phpinfo \.bak" nginx_access.log | findstr /V /I "admin"
发现 IP 103.216.40.77 在 00:56 到 01:00 之间,对 /.env 发出了几十次请求,全部返回 404:
虽然都是404,但这种反复扫同一个路径的行为,就是典型的敏感文件探测
.env 文件是 PHP/Node.js 等项目常用的环境配置文件,一旦泄露,里面可能有数据库账号密码、第三方 API 密钥等,危害极大。
flag{/.env}
题目四:后台登录成功使用的用户名 opencart_error.log是通用应用日志
包含三种级别的记录
1 2 3 Warning: Login failed... ← 警告(登录失败) Info: Admin login success... ← 普通信息(登录成功) Error: xxx ← 真正的错误
开发者偷懒,把所有级别的日志都写进同一个文件,只是用 Warning / Info / Error 前缀区分。所以文件叫error.log,里面却有成功信息。
1 2 3 findstr /I "login" opencart_error.log 2026-02-18 00:41:12 - Warning: Login failed for username 'admin' from 103.216.40.77 2026-02-18 01:08:01 - Info: Admin login success for username 'admin' from 45.133.12.77
日志有不同的层次,打个比方:
层次
日志文件
记录什么
网络层
nginx_access.log
谁发了什么 HTTP 请求,返回什么状态码
应用层
opencart_error.log
网站程序自己记的:登录成功/失败、订单创建等业务事件
数据库层
mysql_general.log
执行了哪些 SQL 语句
系统层
auth.log
SSH 登录、系统用户认证
nginx 只知道”有人 POST 了登录接口,返回了 200”,但它不知道账号密码对不对,因为验证密码是 OpenCart 程序自己做的事。
所以要知道用户名是什么、登录有没有真正成功,必须去问 OpenCart 自己写的日志,也就是 opencart_error.log
1 2 3 findstr /I "login" opencart_error.log 2026-02-18 00:41:12 - Warning: Login failed for username 'admin' from 103.216.40.77 2026-02-18 01:08:01 - Info: Admin login success for username 'admin' from 45.133.12.77
日志里明确写了username='admin'
flag{admin}
题目五:数据库中被查询的订单数据表名称 由题3可以了解到完成后台登录成功的攻击IP首次访问后台登录入口的时间为01:28:52
因此我们在mysql数据库表里搜索01:28
1 findstr "01:28" mysql_general.log
1 2 3 4 5 2026-02-18T01:28:12 Connect oc_user@localhost on opencart 2026-02-18T01:28:13 Query SELECT order_id,total FROM oc_order ORDER BY date_added DESC LIMIT 5; 2026-02-18T01:28:14 Query SELECT product_id FROM oc_product WHERE status=1 LIMIT 20; 2026-02-18T01:28:20 Query SELECT product_id,model,price FROM oc_product ORDER BY date_added DESC LIMIT 5; 2026-02-18T01:28:25 Quit
01:28:12有一个新的数据库连接
紧接着是一系列查询,01:28:25断开连接
1 2 3 4 5 6 SELECT order_id, total FROM oc_order ORDER BY date_added DESC LIMIT 5; 查order_id和total这两列 从oc_order这张表里查 按添加时间倒序添加(最新的在前面) 只取前5条 从订单表中,取最新的5条订单,只看id和总金额
像这样 比如说oc_order这张表可能长这样
1 2 3 4 5 6 7 ┌──────────┬────────┬─────────────────────┬────────┐ │ order_id │ total │ date_added │ status │ ├──────────┼────────┼─────────────────────┼────────┤ │ 10023 │ 299.00 │ 2026-02-17 23:11:00 │ paid │ │ 10022 │ 88.50 │ 2026-02-17 20:33:00 │ paid │ │ 10021 │ 450.00 │ 2026-02-17 18:05:00 │ pending│ └──────────┴────────┴─────────────────────┴────────┘
SQL查出来的结果就是这张表里order_id和total的数据
1 2 3 4 5 SELECT order_id, total FROM oc_order -- order_id=订单号, total=总金额 → 订单表 SELECT product_id, model, price FROM oc_product -- product_id=商品ID, price=价格 → 商品表
所以订单表就是oc_order而商品表就是oc_product
flag{oc_order}
题目六:数据库中被查询的商品数据表名称 同理 商品表就是oc_product
flag{oc_product}
时间线复盘 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 00:41 103.216.40.77 尝试登录后台 admin 账号 → 失败 00:56 103.216.40.77 开始疯狂扫描 /.env 文件(侦察阶段) 同时探测 /admin/ 入口 01:07 39.98.12.44 对 /admin 后台发起暴力破解(几十次 POST) 01:28 45.133.12.77 ← 真正的攻击者出现 :52 POST 登录成功(用户名 admin) :54 GET Dashboard,拿到真实 user_token :59 通过文件管理器上传 Webshell 文件 (伪装成图片 cache_218.jpg,实为 .cache_hidden_218.php) 01:49 45.133.12.77 :20 GET 访问 Webshell → 成功响应(200) :27 POST 向 Webshell 发送命令执行指令(getshell)