2024年四川省第二届网信行业技能竞赛-数据安全赛项决赛WP
第一部分:数据加解密
Q1 简单的数据解密
1.1 题目描述

1.2 考点分析
考点1:维吉尼亚密码
(1)维吉尼亚密码是什么?
维吉尼亚密码就是用一个循环表(key)来对字符串中的每个字母进行循有规律的平移。在标准密码学中,维吉尼亚密码只处理英文字母,遇到下划线、空格或标点符号时通常直接跳过并保留原样。
(2)维吉尼亚加密、解密过程过程
以下是一个维吉尼亚加密的示例:
以下是一个维吉尼亚解密的示例:
(3)CyberChef中进行维吉尼亚加解密
加密时搜索Vigenère encode:
解密时搜索Vigenère decode:
考点2:MD5加密
(1)什么是MD5哈希
MD5哈希就是将任意长度的输入数据,通过一系列复杂的数学变换,压缩成一个固定长度为128位的哈希值(32字节字符串),过程中无需输入KEY。
(2)CyberChef进行MD5哈希
直接搜索MD5即可。
1.3 扩展练习
题目链接:https://buuoj.cn/challenges,搜索[MRCTF2020]vigenere
知识点:KEY分析、解密算法修改
思路分析:题目附件中给了2个文件,第一个文件cipher.txt中存放的维吉尼亚加密后的密文,第二个文件vigenere.py中介绍了本题采用的维吉尼亚加密逻辑。
对py文件进行进一步解读,发现里面有2个关键信息:一是return(getchar((getdiff(src) + getdiff(key) + 1) % 26))这一行,意味着加密逻辑为 C i = ( P i + K i + 1 ) ( m o d 26 ) C_i = (P_i + K_i + 1) \pmod{26} Ci=(Pi+Ki+1)(mod26),而非传统的 C i = ( P i + K i ) ( m o d 26 ) C_i = (P_i + K_i) \pmod{26} Ci=(Pi+Ki)(mod26),因此对应设计的解密逻辑为 P i = ( C i − K i − 1 + 26 ) ( m o d 26 ) P_i = (C_i - K_i - 1 + 26) \pmod{26} Pi=(Ci−Ki−1+26)(mod26),将原来加密时多加的那个1给减掉;二是assert(len(key_string) > 5 and len(key_string) < 10)这一行,意味着密码长度为6-9,在设计Kasiski 测试法时尝试这几个长度的KEY即可。
操作:
结合加密逻辑以及KEY长度的限制,编写维吉尼亚解密脚本:
# 纯标准库实现,适合离线环境
import string
# 标准英文字母频率表 (期望值)
ENGLISH_FREQS = {
'a': 0.08167, 'b': 0.01492, 'c': 0.02782, 'd': 0.04253, 'e': 0.12702,
'f': 0.02228, 'g': 0.02015, 'h': 0.06094, 'i': 0.06966, 'j': 0.00015,
'k': 0.00772, 'l': 0.04025, 'm': 0.02406, 'n': 0.06749, 'o': 0.07507,
'p': 0.01929, 'q': 0.00095, 'r': 0.05987, 's': 0.06327, 't': 0.09056,
'u': 0.02758, 'v': 0.00978, 'w': 0.02360, 'x': 0.00150, 'y': 0.01974,
'z': 0.00074
}
def get_ic(text):
"""计算文本的重合指数 (IC)"""
n = len(text)
if n <= 1: return 0
freqs = {char: text.count(char) for char in string.ascii_lowercase}
ic = sum(f * (f - 1) for f in freqs.values()) / (n * (n - 1))
return ic
def custom_decrypt_char(c_char, k_char):
"""题目的魔改解密逻辑:P = (C - K - 1 + 26) % 26"""
c_val = ord(c_char) - ord('a')
k_val = ord(k_char) - ord('a')
p_val = (c_val - k_val - 1 + 26) % 26
return chr(p_val + ord('a'))
def solve_vigenere(ciphertext):
# 清洗数据,只保留小写字母用于频率分析
clean_text = "".join([c for c in ciphertext.lower() if c.isalpha()])
print("[*] 第一步:通过重合指数 (IC) 寻找密钥长度 (范围 6-9)")
best_length = 0
best_ic = 0
for length in range(6, 10):
# 将密文按长度分组,计算所有列的平均 IC 值
avg_ic = 0
for i in range(length):
column = clean_text[i::length]
avg_ic += get_ic(column)
avg_ic /= length
print(f" 长度 {length} 的平均 IC 值为: {avg_ic:.5f}")
# 记录最接近 0.065 的那个长度
if avg_ic > best_ic:
best_ic = avg_ic
best_length = length
print(f"[+] 确定最优密钥长度为: {best_length}\n")
print("[*] 第二步:通过卡方检验逐字符爆破密钥")
final_key = ""
for i in range(best_length):
column = clean_text[i::best_length]
best_char = 'a'
lowest_chi_sq = float('inf')
# 尝试 a-z 26个字母
for guess_key in string.ascii_lowercase:
# 1. 尝试解密当前列
decrypted_col = [custom_decrypt_char(c, guess_key) for c in column]
col_len = len(decrypted_col)
# 2. 计算卡方值
chi_sq = 0
for char in string.ascii_lowercase:
observed = decrypted_col.count(char)
expected = ENGLISH_FREQS[char] * col_len
if expected > 0:
chi_sq += ((observed - expected) ** 2) / expected
if chi_sq < lowest_chi_sq:
lowest_chi_sq = chi_sq
best_char = guess_key
final_key += best_char
print(f"[+] 爆破成功!发现完整密钥: {final_key}\n")
print("[*] 第三步:执行最终解密")
plaintext = ""
key_idx = 0
for char in ciphertext:
if char.isalpha():
is_upper = char.isupper()
c_lower = char.lower()
k_char = final_key[key_idx % best_length]
p_char = custom_decrypt_char(c_lower, k_char)
plaintext += p_char.upper() if is_upper else p_char
key_idx += 1
else:
# 保留原文中的标点和下划线
plaintext += char
return plaintext
# ========= 执行区域 =========
# 请将 MRCTF2020 题目的实际密文填入下方
# 读取同目录下的 cipher.txt 文件内容作为密文
with open(r'C:\Users\Lxf\Desktop\cipher.txt', 'r', encoding='utf-8') as f:
target_ciphertext = f.read()
result = solve_vigenere(target_ciphertext)
print("[+] 最终还原的明文 (内含 Flag):\n")
print(result)
程序运行的最后一行中包含了flag,可知最终答案为flag{vigenere_crypto_crack_man}
Q2 用户session 算法解密
第二部分 :数据分析
Q1 API接口分析

# 题目附件:python_api.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @Author: Theon
# @Version: 1.0.0
# @Info:
import socket
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Accounts import accounts
AES_KEY = b'vndchwxkfqatkhnt'
def aes_encrypt(data):
cipher = AES.new(AES_KEY, AES.MODE_CBC, iv=b'rrwynwinvzlogwmy')
ct_bytes = cipher.encrypt(pad(data.encode(), AES.block_size))
ct = base64.b64encode(ct_bytes).decode('utf-8')
return ct
def handle_client(client_socket):
hello_data = 'Connect Success Input name \n'
client_socket.send(hello_data.encode('utf-8'))
account = client_socket.recv(1024).decode('utf-8').strip()
phone_number = accounts.get(account)
if phone_number:
phone_number_b64 = base64.b64encode(phone_number.encode()).decode('utf-8')
encrypted_phone_number = aes_encrypt(phone_number_b64)
response = f'{encrypted_phone_number} \n'
else:
response = 'Account not found.'
client_socket.send(response.encode('utf-8'))
client_socket.close()
def main():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8006))
server.listen(5)
print('Server listening on port 8006...')
while True:
client_socket, addr = server.accept()
print(f'Accepted connection from {addr}')
handle_client(client_socket)
if __name__ == '__main__':
main()
思路:python_api.py中的函数handle_client()解释了对数据的核心加密逻辑。服务器端先发送一个欢迎语,然后接收到客户输入的账户名account,提取出这个账户名对应的手机号,依次进行base64 -> AES-CBC(key为vndchwxkfqatkhnt,初始化向量IV为rrwynwinvzlogwmy) -> base64加密,然后将密文发送给客户端。因此,在本地中向192.168.239.131:8006发送请求获取到账户zhangsan的手机号加密信息后,依次进行base64 -> AES-CBC -> base64解密即可。
操作:
1.用 nc 192.168.239.131 8006 测试连通性,应能收到 Connect Success Input name。(若API挂了用 systemctl restart pythonapi 重启)
2.编写交互脚本拿到密文。
import socket
s = socket.socket()
s.connect(('192.168.239.131', 8006))
print(s.recv(1024)) # Connect Success Input name
s.send(b'zhangsan')
cipher_b64 = s.recv(1024).decode().strip()
print(cipher_b64) # 得到加密后的字符串
s.close()
3.逆向解密
import base64, hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
KEY = b'vndchwxkfqatkhnt'
IV = b'rrwynwinvzlogwmy'
# 外层 Base64 解码
ct = base64.b64decode(cipher_b64)
# AES-CBC 解密
cipher = AES.new(KEY, AES.MODE_CBC, iv=IV)
pt_b64 = unpad(cipher.decrypt(ct), AES.block_size)
# 内层 Base64 解码 → 真正的手机号
phone = base64.b64decode(pt_b64).decode()
print('phone:', phone)
4.转成flag
md5 = hashlib.md5(phone.encode()).hexdigest() # 小写32位,若大写就是.hexdigest().upper()
print(f'flag{{{md5}}}')
- Step1。获取账户zhangsan对应的手机号密文。kali-linux命令行窗口输入
nc 192.168.239.131:8006,会得到zhangshan的手机号密文。 - Step2。base64解码。打开CyberChef,Frombase64即可
- Step3。AES-CBC解密。CyberChef中选择AES Decrypt,配置如下参数

- Step4。base64解码。就能得到手机号信息了!
Q2 数据窃取程序分析
题目描述:
思路:本题考察逆向分析。sendc本质上是一个编译好的C/C++文件,直接把它丢进IDA Pro中,查看main的strings视图即可。
操作:
由于没有原题,本题编写一个c程序进行编译后还原原题环境。
- Step1。将下面这个文件
sendc.c写好后,kali中使用指令gcc sendc.c -o sendc将c文件编译成二进制文件sendc。
#include <stdio.h>
#include <string.h>
int main() {
// 假设这就是那个窃取程序,目标IP被硬编码了
char *target_ip = "192.168.233.66";
char stolen_data[] = "/etc/passwd content...";
printf("Initializing network...\n");
// 模拟建立连接并发送数据
printf("Connecting to %s to send data...\n", target_ip);
return 0;
- Step2。查看反编译结果。在IDA Pro中打开文件sendc,双击左侧栏中的main,然后点击工具栏中的View -> Open Subviews -> Strings以查看原程序中的所有可见文本。也可View -> Open Subviews -> Generate pesuecode直接查看反编译结果

配置的指令:指令idea64用于打开idea pro软件, 切回分析数据文件所在目录后使用指令idaclean清除所有缓存文件
Q3 安全风险数据识别与分析
题目描述:
思路分析:本题考察日志分析。要对日志中访问最多的URL进行归类统计,且不用输出完整URL。只需输出一级目录即可。这题本质上就是个文本分析,可用linux管道符命令来过滤文本,也可用python脚本来实现/
以下几个题目用于对日志分析这个知识点的拓展。
拓展1:统计访问最多的路径
题目描述:下面的代码块中是一个名为access.log的日志文件中的数据,请对这个日志文件中的内容进行分析,统计出访问最多的2个路径(一级目录)
192.168.1.10 - - [26/Mar/2026:10:00:01 +0800] "GET /index.php HTTP/1.1" 200 2326
10.0.0.5 - - [26/Mar/2026:10:00:02 +0800] "GET /api/v1/user HTTP/1.1" 200 512
192.168.1.50 - - [26/Mar/2026:10:01:01 +0800] "GET /services/app.js HTTP/1.1" 200 1024
192.168.1.50 - - [26/Mar/2026:10:01:02 +0800] "GET /services/style.css HTTP/1.1" 200 211
192.168.1.50 - - [26/Mar/2026:10:01:03 +0800] "GET /services/login.php HTTP/1.1" 200 455
192.168.1.100 - - [26/Mar/2026:10:02:01 +0800] "GET /admin/index.php HTTP/1.1" 401 332
192.168.1.100 - - [26/Mar/2026:10:02:02 +0800] "GET /admin/config.php HTTP/1.1" 403 211
192.168.1.100 - - [26/Mar/2026:10:02:03 +0800] "GET /admin/upload.php HTTP/1.1" 200 455
192.168.1.100 - - [26/Mar/2026:10:02:04 +0800] "GET /admin/shell.php?cmd=cat%20/etc/passwd HTTP/1.1" 200 1523
192.168.1.100 - - [26/Mar/2026:10:02:05 +0800] "GET /admin/db_backup.zip HTTP/1.1" 200 50402
192.168.1.50 - - [26/Mar/2026:10:03:01 +0800] "GET /services/auth.php HTTP/1.1" 200 120
192.168.1.50 - - [26/Mar/2026:10:03:02 +0800] "GET /services/dashboard.php HTTP/1.1" 200 890
操作:
直接用下面这个py文件完成分析即可。
from collections import Counter
counts = Counter()
# 逐行读取,避免大日志文件撑爆内存
with open('access.log', 'r', encoding='utf-8') as f:
for line in f:
try:
# 1. 按空格分割,取第7列,内容类似于/index.php HTTP/1.1"
path = line.split()[6]
# 2. 按 '/' 分割路径
first_level = '/' + path.split('/')[1]
counts[first_level] += 1
except IndexError:
# 忽略格式不规范的空行或错误日志
continue
# 3. 按统计次数降序,取前20
for path, count in counts.most_common(20):
print(f"{count:>7} {path}")
对应程序的运行结果如下图,可知排名前2的访问路径为/services和/admin
拓展2:找泄露的源码文件
题目链接:https://ctf.bugku.com/challenges/detail/id/1760.html
拓展2至拓展4共用一个附件,这是附件内容的部分展示
172.17.0.1 - - [07/Aug/2021:01:37:51 +0000] "GET / HTTP/1.1" 200 638 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:51 +0000] "GET /favicon.ico HTTP/1.1" 404 493 "http://192.168.2.197:8081/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:55 +0000] "GET / HTTP/1.1" 200 637 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /index.php HTTP/1.1" 200 601 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2egit HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2egit%2fHEAD HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2egit%2findex HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2egit%2fconfig HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2egit%2fdescription HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /source HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /source%2ephp HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /source%2ephp%2ebak HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2eidea%2fworkspace%2exml HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /source%2ephp%2eswp HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2esource%2ephp%2ebak HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /README%2eMD HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /README%2emd HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /README HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2egitignore HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2esvn HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2ehg HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2esvn%2fwc%2edb HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2esvn%2fentries HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /user%2ephp%2ebak HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /%2eDS_store HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /WEB-INF%2fweb%2exml HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /WEB-INF%2fclasses HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /WEB-INF%2fsrc%2f HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /WEB-INF%2flib HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /WEB-INF%2fdatabase%2epropertie HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /CVS%2fRoot HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
172.17.0.1 - - [07/Aug/2021:01:37:58 +0000] "GET /CVS%2fEntries HTTP/1.1" 404 457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
思路分析:源码泄露说明黑客已经成功下载了源码文件,对应的响应码应该为200,因此找到响应码为200的行然后打印这一行中访问的URL即可(可能存在URL编码,进行URL解码后输出)
操作:
对应的代码以及运行结果如下图,可知答案为flag{www.zip}
import urllib.parse
# 使用 set 存储响应码为 200 的路径,自动去重
success_paths = set()
# 逐行读取日志文件
with open('access.log', 'r', encoding='utf-8') as f:
for line in f:
try:
parts = line.split()
path = parts[6] # 提取请求路径
status_code = parts[8] # 提取 HTTP 状态码
# 只提取响应码为 200 的记录
if status_code == '200':
# 将 URL 编码还原,方便人工阅读
decoded_path = urllib.parse.unquote(path)
success_paths.add(decoded_path)
except IndexError:
# 忽略格式不完整的脏数据
continue
print(f"[*] 日志分析完毕!共找到 {len(success_paths)} 个不同的 200 成功请求记录。\n")
print("-" * 40)
# 使用 sorted() 排序输出,让相似的路径挨在一起,更利于人工排查
for p in sorted(success_paths):
print(p)
print("-" * 40)

拓展3:找/tmp中写入的文件
题目链接:https://ctf.bugku.com/challenges/detail/id/1761.html
思路:要向/tmp中写入文件,那么访问的URL经过解码后一定会包含/tmp字样。因此先逐行读取访问的URL并进行URL解码,若匹配到/tmp则将该行数据存起来,最后把所有匹配的行打印出来人眼扫描其中信息即可。
操作:
本题编写的py文件以及对应的程序运行结果如下图,可知黑客实际的攻击点为第二行,filename=…代码黑客在写入文件,文件名为sess_car。因此本题答案为flag{sess_car}
import urllib.parse
# 用 set 去重,防止同一个 payload 黑客发了上百次导致刷屏
suspicious_urls = set()
print("[*] 开始全量解码并搜索 /tmp 特征...\n")
print("-" * 80)
with open('access.log', 'r', encoding='utf-8') as f:
for line in f:
try:
# 提取请求路径 (第7列)
path = line.split()[6]
# 核心思路 1:直接全量 URL 解码
decoded_path = urllib.parse.unquote(path)
# 核心思路 2:简单粗暴地字符串匹配,命中就抓出来
if '/tmp' in decoded_path:
suspicious_urls.add(decoded_path)
except IndexError:
continue
# 核心思路 3:打印出来,交给“人眼”一行行审视
for url in suspicious_urls:
print(url)
print("-" * 80)
print(f"[*] 搜索完毕!共提取到 {len(suspicious_urls)} 条独立的可疑 URL。")

拓展4:分析黑客读取文件时使用的类名
题目链接:https://ctf.bugku.com/challenges/detail/id/1762.html
思路:黑客要通过GET请求读取秘密文件,那么一定将一段很长的payload发给服务器,而这段很长的payload肯定会以?开始,因此筛选中访问的URL中含有?的请求然后人眼观察即可。
操作:
本题编写的py文件以及对应的程序运行结果如下图(py代码与上一问几乎一样,就改了一个?),观察/?filename=…/这一行的末尾为paths|a:1:{s:5:“/flag”;s:13:“SplFileObject”;},可知使用类SplFileObject读取的秘密文件flag。因此本题答案为flag{SplFileObject}
import urllib.parse
# 用 set 去重,防止同一个 payload 黑客发了上百次导致刷屏
suspicious_urls = set()
print("[*] 开始全量解码并搜索 /? 特征...\n")
print("-" * 80)
with open('access.log', 'r', encoding='utf-8') as f:
for line in f:
try:
# 提取请求路径 (第7列)
path = line.split()[6]
# 核心思路 1:直接全量 URL 解码
decoded_path = urllib.parse.unquote(path)
# 核心思路 2:简单粗暴地字符串匹配,命中就抓出来
if '?' in decoded_path:
suspicious_urls.add(decoded_path)
except IndexError:
continue
# 核心思路 3:打印出来,交给“人眼”一行行审视
for url in suspicious_urls:
print(url)
print("-" * 80)
print(f"[*] 搜索完毕!共提取到 {len(suspicious_urls)} 条独立的可疑 URL。")
[*] 开始全量解码并搜索 /? 特征...
--------------------------------------------------------------------------------
/?.save1
/.?.swp
/?.save
/?~1~
/?.save3
/?.bak
/?~
/?.save2
/?filename=../../../../../../../../../../../../../../../../../tmp/sess_car&content=func|N;files|a:2:{s:8:"filename";s:16:"./files/filename";s:20:"call_user_func_array";s:28:"./files/call_user_func_array";}paths|a:1:{s:5:"/flag";s:13:"SplFileObject";}
/.?.swm
/?file=sess_car
/?~3~
/?~2~
/?
/.?.swn
/.?.swo
/?.bak_Edietplus
/?.back
/.?.swl
--------------------------------------------------------------------------------
[*] 搜索完毕!共提取到 19 条独立的可疑 URL。
Q4 密码安全等级划分
题目描述:
思路分析:本题考察正则化表达式的使用。可调用python中的re模块进行分析,分析代码以及运行结果如下2和下图3。核心代码为regex_level4 = re.compile(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])[A-Za-z\d\W_]+$"),对该句的分析如下图1。
| 字符段 | 作用 |
|---|---|
re.compile |
将一段正则表达式的规则字符串提前编译成正则化对象,加快后面匹配效率 |
^ |
匹配字符串开头 |
(?=.*[a-z]) |
(?=...)是一个正向先行断言,它表示从左到右扫描字符串要求满足某个条件,.*表示任意字符,[a-z]表示小写字母。因此,(?=.*[a-z])的意思是从左到右扫描字符串看是否有任意字符后面跟了小写字母 |
(?=.*[A-Z]) |
从左到右扫描字符串看是否有任意字符后面跟了大写字母 |
(?=.*\d) |
从左到右扫描字符串看是否有任意字符后面跟了数字 |
(?=.*[\W_]) |
\W表示特殊字符(维度不包括下划线,_表示下划线。因此,(?=.*[\W_])的意思是从左到右扫描字符串看是否任意字符后面跟了特殊字符) |
[A-Za-z\d\W_]+ |
[A-Za-z\d\W_]表示前面描述的所有规则,其中各个规则的描写顺序是任意的(例如[a-zA-Z\d\W_]跟原句的作用一样),+表示这些规则每个都至少需要出现一次。因此,[A-Za-z\d\W_]+的意思是前面描述的所有规则全都要出现 |
import re
import hashlib
level_count = {1:0, 2:0, 3:0, 4:0}
regex_level1 = re.compile(r"^\d+$")
regex_level2 = re.compile(r"^(?=.*[a-z])(?=.*\d)[a-z\d]+$")
regex_level3 = re.compile(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]+$")
regex_level4 = re.compile(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])[A-Za-z\d\W_]+$")
with open("C:\\Users\\Administrator\\Downloads\\passwd.dbcd", 'r') as f:
lines = f.readlines()
for line in lines:
passwd = line.strip()
if regex_level1.match(passwd):
level_count[1] += 1
elif regex_level2.match(passwd):
level_count[2] += 1
elif regex_level3.match(passwd):
level_count[3] += 1
elif regex_level4.match(passwd):
level_count[4] += 1
else:
print(passwd)
level_count_result = str(level_count).replace(" ","")
print("[+]: {}".format(level_count_result))
print("[+]: flag{{{}}}".format(hashlib.md5(level_count_result.encode()).hexdigest()))

Q5 敏感数据识别
题目描述:
思路:本题仍然考察正则化表达式。下图1是对核心代码的拆解,下图2是完整代码,下图3是代码运行情况。
| 语句 | 字符段解释 |
|---|---|
regex_phone |
(?<!\d)表示从当前位置向左看<…不能出现数字((?<…)表示向左断言,!表示不能出现与=相反,\为数字);1表示手机号必须以数字 1 开头;[3-9]表示手机号第二位数字必须是3-9(中国大陆手机号规则),\d{9}表示后面紧跟着的9位都必须是数字(\d表示数字,{9}表示重复9次;(?!\d)表示从此处开始向右开始读不能出现数字((?..)表示向右断言,!表示不能出现,\d表示数字) |
regex_ip |
总的来说,该句的意思是让字符串符合num.num.num.num的格式,其中num为0-255之间的一个数字。下图是具体解释:![]() |
regex_email |
![]() |
regex_idcard |
![]() |
import re
import hashlib
datas_count = {"Phone":0,"IPv4":0,"Email":0,"Idcard":0}
regex_phone = re.compile(r"(?<!\d)1[3-9]\d{9}(?!\d)")
regex_ip = re.compile(r"\b(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}\b")
regex_email = re.compile(r"\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b")
regex_idcard = re.compile(r"\b[1-9]\d{5}(?:18|19|20|21)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dxX]\b")
with open("C:\\Users\\Administrator\\Downloads\\datas.dbcd", 'r') as f:
lines = f.readlines()
for line in lines:
data = line.strip()
if regex_phone.match(data):
datas_count["Phone"] += 1
elif regex_ip.match(data):
datas_count["IPv4"] += 1
elif regex_email.match(data):
datas_count["Email"] += 1
elifregex_idcard.match(data):
datas_count["Idcard"] += 1
else:
print(data)
datas_count_result = str(datas_count).replace(" ","").replace("'", "")
print("[+]: {}".format(datas_count_result))
print("[+]: flag{{{}}}".format(hashlib.md5(datas_count_result.encode()).hexdigest()))

第三部分 数据库加固
Q1 redis访问加固
题目描述:
思路分析:本题考察redis配置管理。redis是一个建在内存里的超快数据库,存放用户登录状态、网站头条、手机验证码等变动极快且无需永久保存的数据,redis是一个完全独立的程序,无需依赖 Apache、Nginx 或者任何 Web 网站就能单独运行。
靶场复现:由于没有原题,这里执行一些配置来生成一个与原靶场类似的环境。
- Step1。安装redis服务。先执行指令
sudo apt update,再执行指令sudo apt install redis-server -y。安装完redis服务后,一般会生成一个/etc/redis文件夹(里面包含配置文件redis.conf) + redis-sever主程序(/usr/bin目录下) - Step2。编写判题程序。在bash中先后执行以下两句指令
sudo mkdir -p /root/check创建文件夹,随后使用指令sudo mousepad /root/check/redis创建并打开判题脚本文件redis,输入下面代码块中的代码,保存文件后退出,然后执行指令sudo chmod +x /root/check/redis给判题程序赋予权限。
#!/bin/bash
# 模拟CTF后端的检测逻辑
RESULT=$(redis-cli -a "7XzE56xtemVJYssg" PING 2>/dev/null)
if [[ "$RESULT" == *"PONG"* ]]; then
echo "恭喜!验证通过。flag{R3dis_s3cur1ty_h4rden_m4st3r}"
else
echo "验证失败:Redis未启动或密码设置不正确,请检查 /etc/redis/redis.conf"
fi
做题步骤:
- Step1。修改redis配置文件。使用指令
sudo mousepad /etc/redis/redis.conf打开redis配置文件(实践的靶场中不一定是这个环境),然后搜索requirepass,找到# requirepass foobared这一行,在下面一行填入文本requirepass 7XzE56xtemVJYssg后保存退出,从而设置redis的密码为7XzE56xtemVJYssg。 - Step2。重启redis服务。执行指令
sudo systemctl restart redis-server,从而使得刚刚的配置文件生效 - Step3。判题。执行指令
sudo /root/check/redis从而执行判题程序,可看到终端输出一行文本:恭喜!验证通过。flag{R3dis_s3cur1ty_h4rden_m4st3r}
Q2 数据库密码长度加固
题目描述

思路分析
本题考察mysql的密码复杂度管理。
靶场复现
由于没有原题,需要先在kali中配置好mysql以及对应的判题程序。
复现阶段一:复现判题脚本
# 1. 切换到 root 权限 (如果尚未切换)
sudo su
# 2. 创建判题脚本目录
mkdir -p /root/check
# 3. 编写判题脚本模拟器
cat << 'EOF' > /root/check/mysql
#!/bin/bash
# 模拟比赛的 checker
echo "正在检查 MySQL 密码策略配置..."
# 这里假设你的 root 密码已经按题目要求设为了 A@bbc202411
# 实际比赛中 checker 会直接连库查询 validate_password.length 的值
EXPECTED_LENGTH=$(mysql -h 127.0.0.1 -P 3306 -u root -p'A@bbc202411' -BN -e "SELECT @@validate_password_length;" 2>/dev/null)
if [ "$EXPECTED_LENGTH" == "12" ]; then
echo "恭喜!配置无误。你的 flag 是: flag{Meiya_MySQL_Harden_Done}"
else
echo "配置错误:当前密码最小长度未设置为 12,请继续努力!"
fi
EOF
# 4. 赋予执行权限
chmod +x /root/check/mysql
复现阶段二:mysql的安装
在bash中依次运行以下指令即可完成mysql的安装。
# 1. 更新软件源列表并安装 Docker
sudo apt update
sudo apt install docker.io -y
# 2. 启动 Docker 服务,并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker
# 3. 确保 docker 配置目录存在
sudo mkdir -p /etc/docker
# 4. 写入国内镜像加速源
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://mirror.baidubce.com",
"https://docker.nju.edu.cn",
"https://docker.m.daocloud.io"
]
}
EOF
# 5. 重新加载系统守护进程并重启 Docker,使配置生效
sudo systemctl daemon-reload
sudo systemctl restart docker
# 6.拉取并运行mysql
sudo docker run --name meiya-mysql -p 3306:3306 -m 512m -e MYSQL_ROOT_PASSWORD='A@bbc202411' -d mysql:8.0
做题步骤
思路一:动态修改(SQL命令行实现)
- Step1。运行mysql。将上一步中的bash窗口关掉,再开一个窗口运行以下指令
# 1.启动docker容器
sudo docker start meiya-mysql
# 2.进入mysql容器内部
sudo docker exec -it meiya-mysql bash
# 3.登录mysql(运行后最前面的账户名变成mysql>)
mysql -u root -p'A@bbc202411'
- Step2。修改password_length字段的值。在sql窗口中依次运行下面代码块中的sql指令,到第二步时注意观察字段变量的名字。
# 1.安装validate_password插件(无需联网也能安装)
INSTALL PLUGIN validate_password SONAME 'validate_password.so';
# 2.展示validate_password相关字段情况(可发现length为8)
SHOW VARIABLES LIKE 'validate_password%';
# 3.修改validate_password_length字段的值
SET GLOBAL validate_password_length = 12;
# 4.再次查看字段是否完成修改
SHOW VARIABLES LIKE 'validate_password%';
- Step3。执行判题脚本。再看一个命令行窗口,执行指令
sudo /root/check/mysql以运行判题程序,会看到输出下面的字样:
正在检查 MySQL 密码策略配置...
恭喜!配置无误。你的 flag 是: flag{Meiya_MySQL_Harden_Done}
思路二:静态修改
编辑配置文件(通常在 /etc/my.cnf 或 /etc/mysql/my.cnf),找到[mysqld]字段,在这个字段下面一行添加文本validate_password_length = 12即可,最后再执行一下判题脚本即可!
Q3 数据库网络连接加固
题目链接

思路分析
本题考察数据库最大连接数量的修改。
靶场复现
由于没有原题,需要先在kali中配置好PostgreSQL数据库l以及对应的判题程序。
阶段一:安装 PostgreSQL并创建用户
# 1.安装 PostgreSQL
sudo apt update && sudo apt install postgresql postgresql-contrib
# 2.启动数据库
sudo systemctl start postgresql
# 3.创建用户zwxa
sudo -u postgres createuser zwxa
阶段二:写判题程序
先使用指令sudo mkdir -p /root/check创建文件夹,然后使用指令sudo mousepad /root/check/postgresql创建文件,填入下面代码块中的内容,然后使用指令sudo chmod +x /root/check/postgresql赋予判题程序权限
#!/bin/bash
# 检查 max_connections
MAX_CONN=$(sudo -u postgres psql -t -c "SHOW max_connections;" | tr -d '[:space:]')
if [ "$MAX_CONN" != "2100" ]; then
echo "max_connections incorrect (Current: $MAX_CONN, Expected: 2100)"
exit 1
fi
# 检查用户 zwxa
USER_EXISTS=$(sudo -u postgres psql -t -c "SELECT rolname FROM pg_roles WHERE rolname = 'zwxa';" | tr -d '[:space:]')
if [ "$USER_EXISTS" != "zwxa" ]; then
echo "User zwxa does not exist"
exit 1
fi
# 检查 zwxa 的连接限制
USER_CONN_LIMIT=$(sudo -u postgres psql -t -c "SELECT rolconnlimit FROM pg_roles WHERE rolname = 'zwxa';" | tr -d '[:space:]')
if [ "$USER_CONN_LIMIT" != "1500" ]; then
echo "User zwxa connection limit incorrect (Current: $USER_CONN_LIMIT, Expected: 1500)"
exit 1
fi
# 如果都正确,输出 Flag
echo "网络连接安全"
echo "flag{pg_conn_limits_configured_correctly_4a2b}"
做题步骤
- Step1.查找postgresql数据库的配置文件路径。保证数据库为启动状态下,使用指令
sudo -u postgres psql -c "SHOW config_file;"查询数据库配置文件的路径。(指令sudo -u postgres psql -c "SHOW data_directory;"查看数据目录的路径,指令sudo -u postgres psql -c "SHOW hba_file;"查看网络策略文件的路径)
- Step2.全局连接数修改。使用指令
sudo mousepad /etc/postgresql/15/main/postgresql.conf打开配置文件,然后搜索max_connections,将其值修改为2100,从而实现将全局实例的最大连接数设置成2100.

- Step3。用户连接数修改。使用指令
sudo -u postgres psql登录postgresql数据的命令行,然后使用指令ALTER ROLE zwxa CONNECTION LIMIT 1500;修改用户的连接数为1500. 然后使用指令\q退出数据库 - Step4。重启数据库。使用指令
sudo systemctl restart postgresql重启postgresql数据库,以确保前面修改的2个最大连接数生效!这步一定要做! - Step5。判题。执行指令
sudo /root/check/postgresql,得到最终结果。
小拓展
若本题的数据库类型变成mysql,则核心的解题步骤变成
# 1.查找配置文件路径 (一般在/etc/mysql/my.cnf)
mysql --help | grep "Default options" -A 1
# 2.修改配置文件,找到[mysqld],修改max_connections的值,如果没有就在[mysqld]下面添加这一行
max_connections = 2100
# 3.重启mysql
sudo systemctl restart mysql
# 4.sudo设置普通用户最大连接数量
sudo mysql -e "ALTER USER 'zwxa'@'localhost' WITH MAX_USER_CONNECTIONS 1500;"
Q4 数据库用户安全
题目链接

思路分析
本题考察postgresql数据库的用户权限管理
靶场复现
由于前面已经安装过postgresql了,做这题时只需修改一下判题脚本中的内容即可。以下是这题对应的判题脚本
#!/bin/bash
# 1. 检查 admin 是否为超级用户且具有登录权限
ADMIN_CHECK=$(sudo -u admin psql -d postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='admin' AND rolsuper='t' AND rolcanlogin='t';" 2>/dev/null)
# 2. 检查 admin 是否设置了有效密码
# 我们去 PostgreSQL 最底层的系统表 pg_authid 提取密码哈希值
PWD_HASH=$(sudo -u admin psql -d postgres -tAc "SELECT rolpassword FROM pg_authid WHERE rolname='admin';" 2>/dev/null)
# 判断提取出的密码字段是否以 SCRAM-SHA-256 或 md5 开头(证明密码已成功加密存储)
if [[ "$PWD_HASH" == SCRAM-SHA-256\$* ]] || [[ "$PWD_HASH" == md5* ]]; then
PWD_CHECK="1"
else
PWD_CHECK="0"
fi
# 3. 检查默认的 postgres 用户是否已被成功禁止登录 (NOLOGIN)
POSTGRES_CHECK=$(sudo -u admin psql -d postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='postgres' AND rolcanlogin='f';" 2>/dev/null)
# 4. 综合判断:三个条件必须全部满足
if [ "$ADMIN_CHECK" == "1" ] && [ "$PWD_CHECK" == "1" ] && [ "$POSTGRES_CHECK" == "1" ]; then
echo "【用户安全】flag{postgres_sec_admin_configured_1024}"
else
echo "配置有误,请继续检查超级用户权限、密码设置和NOLOGIN状态!"
fi
做题步骤
- Step1。启动sql服务。使用指令
sudo systemctl start postgresql - Step2。登录sql命令行。使用指令
sudo -u postgres psql - Step3。创建超级用户。sql命令行中使用指令
CREATE ROLE admin WITH LOGIN SUPERUSER PASSWORD 'Admin@202411'; - Step4。禁用系统默认的超级用户postgresql。sql命令行使用指令
ALTER ROLE postgres NOLOGIN;
- Step5。创建新的Linux账户admin。命令行终端执行指令
sudo useradd admin,创建Linux系统中的用户admin。此时Linux系统中有一个无密码的admin账户、一个postgresql账户,PostgreSQL数据库中有一个带密码的admin账户、一个被禁用的postgresql账户。创建这个新的Linux系统中的admin就是为了能够进行对等身份认证来登录PostgreSQL数据库。
要注意的是,一旦禁用了postgresql账户后,后面要登录sql命令行的指令由sudo -u postgres psql 变成 sudo -u admin psql -d postgres。其中sudo -u admin代表在linux系统层面切回刚刚创建的admin用户,psql是启动 PostgreSQL 的自带命令行客户端,-d postgres是指定连接名为postgres的数据库(postgres是配置PostgresSQL时系统自动创建一个库) - Step6。重启数据库、判题。先使用指令
sudo systemctl restart postgresql重启数据库从而让刚刚的配置生效,然后使用指令sudo /root/check/postgresql来判题,输出【用户安全】
Q5 数据库密码安全
题目链接
思路分析
本题考察postgresql数据库的密码加密存储、密码复杂度检验
靶场复现
阶段一:修改判题脚本
#!/bin/bash
# 使用 admin 身份免密登录,检查密码加密方式
ENC_CHECK=$(sudo -u admin psql -d postgres -tAc "SHOW password_encryption;" 2>/dev/null)
# 检查已加载的共享库中是否包含 passwordcheck
LIB_CHECK=$(sudo -u admin psql -d postgres -tAc "SHOW shared_preload_libraries;" 2>/dev/null)
# 综合判断
if [ "$ENC_CHECK" == "scram-sha-256" ] && [[ "$LIB_CHECK" == *"passwordcheck"* ]]; then
echo "【密码安全】flag{pg_scram_passcheck_enabled_2048}"
else
echo "配置有误,请继续检查加密方式和密码复杂度插件状态!"
echo "当前加密方式: $ENC_CHECK"
echo "当前加载库: $LIB_CHECK"
fi
阶段二:更新附件包
sudo apt update
sudo apt install postgresql-contrib -y
操作步骤
- Step1。打开配置文件。使用指令
sudo mousepad /etc/postgresql/15/main/postgresql.conf - Step2。密码加密。搜索password_encryption,去掉前面的#,改成
password_encryption = scram-sha-256 - Step3。密码复杂度检查。搜索 shared_preload_libraries,去掉前面的#,改成
shared_preload_libraries = 'passwordcheck'(如果该字段有赋值,则用英文逗号隔开即可,加上passwordcheck即可) - Step4。重启数据库。使用指令sudo systemctl restart postgresql。重启以后才能让配置生效。
- Step5。执行判题程序。使用指令
sudo /root/check/postgresql,输出【密码安全】
小拓展
# 1.登录数据库。
# 第一步清理在运行的mysql,第二步启动docker引擎以及靶机容器meiya,第三步登录数据库命令行
sudo systemctl stop mysql
sudo systemctl start docker
sudo docker start meiya-mysql
sudo docker exec -it meiya-mysql mysql -uroot -p'A@bbc202411'
# 2.开启密码复杂度检查。在mysql命令行输入下面这句指令,install密码复杂度检查的插件,可使用指令SHOW VARIABLES LIKE 'validate_password%';来查看当前的密码复杂度策略
INSTALL COMPONENT 'file://component_validate_password';
# 3.开启密码加密 (其实mysql中是默认加密的,用下面这句指令可看到caching_sha2_password)
SHOW VARIABLES LIKE 'default_authentication_plugin';
# 也可显式修改配置文件,打开配置文件后找到[mysqld]字段,修改default_authentication_plugin,重启数据库生效
sudo mousepad /etc/mysql/mysql.conf.d/mysqld.cnf
default_authentication_plugin=caching_sha2_password
sudo systemctl restart mysql
Q6 数据库备份
题目链接

思路分析
本题考察数据库备份。
靶场复现
由于已经在kali中配置了mysql,因此只用建一个dbs数据库即可。
阶段一:创建dbs数据库
sudo systemctl start docker
sudo docker start meiya-mysql
sudo docker exec -it meiya-mysql mysql -uroot -p'A@bbc202411'
CREATE DATABASE dbs;
USE dbs;
CREATE TABLE test_data (id INT, content VARCHAR(50));
INSERT INTO test_data VALUES (1, 'Meiya_Pico_Test_Data');
exit;
阶段二:创建判题脚本
进入docker命令行以后,创建的这些目录都是在docker容器内部,与kali-linux中的文件目录是完全隔离的。
# 登录docker命令行 (看到以bash-5.1#开头)
docker exec -it meiya-mysql /bin/bash
# 创建备份目标目录
mkdir -p /var/backup
# 创建检查脚本目录
mkdir -p /root/check
# 编写简单的检查脚本 (检查备份文件是否存在且包含我们的测试数据)
echo '#!/bin/bash
if [ -f "/var/backup/dbs_backup.sql" ] && grep -q "Meiya_Pico_Test_Data" "/var/backup/dbs_backup.sql"; then
echo "flag{db_backup_successful_9982}"
else
echo "Check Failed: dbs_backup.sql not found or incomplete."
fi' > /root/check/mysqlbackup
# 赋予脚本执行权限
chmod +x /root/check/mysqlbackup
操作步骤
下面的指令都需要在docker命令行中运行,因为之前配置的mysql数据库、备份文件夹、判题脚本这些全在docker内部。
# 1.将数据库dbs备份到/var/backup/dbs_backup.sql
mysqldump -u root -p'A@bbc202411' dbs > /var/backup/dbs_backup.sql
# 2.判题
/root/check/mysqlbackup
执行判题程序后可看到flag{db_backup_successful_9982}
Q7 数据文件加密
题目链接

思路分析
本题考察openssl工具的使用
靶场复现
# 1.创建文件夹
sudo mkdir -p /etc/my_config
# 2.创建config.py,打开以后输入 db_password = "super_secret_password_123"
sudo mousepad /etc/my_config/config.py
操作步骤
- Step1。使用指令
sudo openssl enc -aes-256-cbc -salt -S 57393464 -k KqEraC5X7Hhw -in /etc/my_config/config.py -out /etc/my_config/aesconfig.py,enc -aes-256-cbc表示加密算法;-salt -S 57393464表示加盐且盐值为57393464;-k KqEraC5X7Hhw指定加密密钥;-in /etc/my_config/config.py指定加密的输入路径;-out /etc/my_config/aesconfig.py指定加密的输出路径 - Step2。计算文件的md5值。使用指令
md5sum aesconfig.py
Q8 美亚柏科杯运维题部分复现
题目链接

8-1 数据库类型的判断与启动
1.判断数据库类型。根据题目给定的数据库用户为root/root,数据库文件为.sql,可知数据库类型为MYSQL.
2.数据库的启动。对于mysql,数据库服务的启动指令为systemctl start mysql
8-5 数据库用户密码的修改
# 1.登录数据库账户(如果账户的密码是123,则在后面加上-p'123')
sudo mysql -u root
# 2.修改root用户的密码(无论原用户是否带密码都是这句)
ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass@123';
8-6 数据库文件解密并备份
阶段一:环境复现
在/tmp目录下创建一个文件sscms.sql.enc,该文件采用了aes-256-cbc加密,密钥为ctf2023。
# 1. 创建一个正常 SQL 备份
echo "CREATE TABLE test_table (id INT, name VARCHAR(20)); INSERT INTO test_table VALUES (1, 'CTF_FLAG');" > /tmp/sscms.sql
# 2. 用 OpenSSL 模拟勒索病毒进行 AES 加密 (密码设为 ctf2023)
openssl enc -aes-256-cbc -salt -in /tmp/sscms.sql -out /tmp/sscms.sql.enc -k ctf2023
# 3. 删除原文件,只留加密后的文件
rm /tmp/sscms.sql
阶段二:解题
# 1.对sql文件进行解密
openssl enc -d -aes-256-cbc -in /tmp/sscms.sql.enc -out /tmp/sscms.sql -k ctf2023
# 2.创建一个新数据库sscms
sudo systemctl start mysql
mysql -u root -p'root'
CREATE DATABASE sscms;
exit;
# 3.数据导入
mysql -u root -p'root' sscms < /tmp/sscms.sql
查看数据库 SHOW DATABASES;
进入数据库sscms USE sscms;
展示数据库中的表名 SHOW TABLES;
查看表test_table中的具体数据 SELECT * FROM test_table;
数据备份后,sscms由一个空库变成了一张带有test_table表的库,这张表中存放了一个数据CTF_FLAG
8-7 windows防火墙的配置
实验说明:在windows物理机中配置防火墙规则,然后用kali-linux虚拟机对windows物理机进行通信测试。
1.查询物理机、虚拟机的通信IP
在windows物理机的cmd窗口输入ipconfig,得到它与虚拟机通信的IP为192.168.163.1
在虚拟机的命令行窗口输入ifconfig,得到它与物理机的通信IP为192.168.163.130
2.Windows防火墙配置
# 1.拦截策略(拒绝kali的ip访问3306端口)
New-NetFirewallRule -DisplayName "CTF-Block-3306" -Direction Inbound -Action Block -Protocol TCP -LocalPort 3306 -RemoteAddress 192.168.163.130
# 2.放行策略 (允许192.168.10.99这个ip访问3306端口)
New-NetFirewallRule -DisplayName "CTF-Allow-3306" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 3306 -RemoteAddress 192.168.163.130
删除设置的策略的指令为: Remove-NetFirewallRule -DisplayName "CTF-Block-3306" 或 Remove-NetFirewallRule -DisplayName "CTF-Allow-3306"
3.通信测试
先开启Windows物理机开启了防火墙总开关,然后开启虚拟的3306监听端口。然后做下面这些步骤测试:
- Step1。拦截规则测试。
先打开WindowsPowershell,使用指令New-NetFirewallRule -DisplayName "CTF-Block-3306" -Direction Inbound -Action Block -Protocol TCP -LocalPort 3306 -RemoteAddress 192.168.163.130设置拦截规则
kali中使用指令nmap -p 3306 192.168.163.1 -Pn尝试扫描Windows物理机的3306端口,发现发出的通信包都被 filtered,说明防火墙拦截规则生效。
- Step2。放行规则测试。
先删除刚刚的那条拦截规则。删除拦截规则的指令为Remove-NetFirewallRule -DisplayName "CTF-Allow-3306"(Windowspowershell中使用),删除完以后发现在kali中扫描Windows物理机的3306端口发现仍然为 filtered,因为开启了防火墙总开关后不在白名单列表里的一律不许访问。
再添加放行规则。添加放行规则的指令New-NetFirewallRule -DisplayName "CTF-Allow-3306" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 3306 -RemoteAddress 192.168.163.130,加完以后指令nmap -p 3306 192.168.163.1 -Pn扫描3306端口发现状态为open !
注:本题在测试放行规则时,发现自己的Windows物理机的防火墙开启了隐身模式拦截我设置的3306端口放行规则,而永久关闭这个隐身模式又需要电脑重启又可能降低防护性能,于是选择添加3306虚拟监听端口才成功完成了测试。
下面是几条本题实验中需掌握的相关指令
开启3306端口监听:Start-Job -Name L3306 -ScriptBlock { $l = [System.Net.Sockets.TcpListener]::new(0, 3306); $l.Start(); while($true) { Start-Sleep 1 } }
关闭3306端口后台监听:Get-Job L3306 | Stop-Job | Remove-Job
Windows防火墙规则查询指令(按名称查询,假设规则名称是CTF-开头):Get-NetFirewallRule -DisplayName "CTF-*" | Select-Object DisplayName, Action, Direction, Enabled
Windows防火墙规则查询指令(按端口号查询,假设规则与3306端口有关):Get-NetFirewallRule | Get-NetFirewallPortFilter | Where-Object { $_.LocalPort -eq "3306" } | Get-NetFirewallRule | Select-Object DisplayName, Action, Enabled
Windows防火墙规则删除指令(填入查询出来的规则名称即可,假设要删除CTF-Allow-3306这条规则):Remove-NetFirewallRule -DisplayName "CTF-Allow-3306"
如果要进行ping测试,若发现ping不同,可添加一条ping规则,相关指令为New-NetFirewallRule -DisplayName "CTF-Allow-Ping" -Direction Inbound -Action Allow -Protocol ICMPv4 -RemoteAddress 192.168.163.130
第四部分 源码审计
Q1 python_project敏感信息审核
题目链接






思路分析
本题考察代码审计。在filelocator pro中依次检索ob、secretkey,ak,login,ssh即可
更多推荐





所有评论(0)