CTF CSV题目教程:题目想让你干嘛,以及Python脚本怎么写
CSV题在CTF里通常会被归到Misc、数据分析、取证或者脚本题里面。它看起来像一个很普通的表格文件,但题目真正考的不是Excel用得熟不熟,而是:你能不能从大量结构化数据里找到规律,把有效信息提取出来,最后还原出flag。
一句话概括:CSV题就是让你写脚本批量处理数据,而不是用肉眼一行一行看。
1. CSV题目一般想让你干嘛
CSV文件本质上就是“用逗号分隔字段的文本文件”,例如:
id,char,index
1,f,0
2,l,1
3,a,2
4,g,3
CTF题目会把flag拆散、打乱、隐藏或者编码后放进表格里。常见目标有下面几种。
1.1直接搜索flag
最简单的情况是flag被藏在某一行、某一列、某个备注字段里。题目可能给你一个很大的CSV,让你不能靠肉眼快速找到。
你要做的是:
- 读取CSV。
- 遍历每一行。
- 在每个字段里搜索
flag{、ctf{、FLAG{之类的关键词。
1.2按某一列排序后拼接字符
很多题会把flag拆成一个一个字符,然后打乱顺序,例如:
pos,ch
3,g
0,f
2,a
1,l
这时题目想让你根据pos排序,再把ch拼起来:
flag
这种题的关键是找出“顺序列”和“字符列”。
1.3按条件筛选有效行
有些CSV会放大量干扰数据,只有满足条件的行才是真的。例如:
id,value,valid
1,x,0
2,f,1
3,l,1
4,?,0
5,a,1
6,g,1
题目想让你筛选valid=1的行,再拼接value。
这种题的核心是:先过滤,再提取,再拼接。
1.4对字段做编码转换
CSV里面可能存的不是直接字符,而是ASCII、十六进制、二进制、Base64等编码。
例如:
index,ascii
0,102
1,108
2,97
3,103
这里102 108 97 103对应ASCII字符:
flag
所以你需要把数字转成字符:
chr(102) # f
1.5用坐标还原图片或二维码
稍微进阶一点的CSV题会给你坐标和颜色,例如:
x,y,color
0,0,0
1,0,255
0,1,255
1,1,0
这种题可能想让你把CSV还原成一张图片、二维码或者像素图。你要做的是:
- 读取
x、y坐标。 - 根据颜色值画点。
- 保存成图片。
- 扫二维码或者看图得到flag。
2.拿到CSV文件后先看什么
不要一上来就写复杂脚本。先做基本观察。
2.1看表头
表头通常会暴露题目思路,例如:
index, char, order, ascii, hex, x, y, r, g, b, valid
看到不同字段,可以大概判断方向:
| 字段名 | 可能含义 |
|---|---|
index / pos / order |
排序 |
char / ch / value |
拼接字符 |
ascii |
ASCII转字符 |
hex |
十六进制解码 |
bin |
二进制解码 |
x / y |
坐标画图 |
r / g / b |
RGB图片 |
valid / flag / is_true |
筛选有效数据 |
2.2看数据量
如果只有几十行,可以手动观察规律。如果有几千、几万行,就必须写脚本。
import csv
with open("data.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
rows = list(reader)
print("行数:", len(rows))
print("表头:", reader.fieldnames)
print("前 5 行:")
for row in rows[:5]:
print(row)
2.3看有没有异常值
CTF题经常把关键信息藏在异常值里,比如:
- 某一列只有一行特别长。
- 某一行包含
{或}。 - 某些数字明显是ASCII范围:
32到126。 - 某些字段像十六进制:
66 6c 61 67。 - 某些字段像Base64:结尾可能有
=。
3. Python读取CSV的基础写法
推荐优先使用Python标准库里的csv,不需要安装第三方库。
3.1按字典方式读取
如果CSV有表头,用DictReader最方便。
import csv
filename = "data.csv"
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
print(row)
假设CSV是:
index,char
0,f
1,l
读出来的每一行就是:
{"index": "0", "char": "f"}
注意:CSV读出来的内容默认都是字符串。
如果要排序或者计算,需要手动转成int。
4.脚本模板一:直接搜索flag
适用于题目给了一个很大的CSV,你怀疑flag直接藏在里面。
import csv
import re
filename = "data.csv"
pattern = re.compile(r"flag\{.*?\}", re.IGNORECASE)
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.reader(f)
for line_no, row in enumerate(reader, start=1):
for col_no, cell in enumerate(row, start=1):
match = pattern.search(cell)
if match:
print(f"[+] 第 {line_no} 行,第 {col_no} 列找到 flag:")
print(match.group())
如果比赛的flag格式不是flag{},就改这里:
pattern = re.compile(r"你的比赛前缀\{.*?\}", re.IGNORECASE)
5.脚本模板二:排序后拼接字符
适用于CSV里有index和char之类的列。
import csv
filename = "data.csv"
items = []
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
index = int(row["index"])
char = row["char"]
items.append((index, char))
items.sort(key=lambda x: x[0])
flag = "".join(char for index, char in items)
print(flag)
这类题最常见的改动点是列名。比如题目里叫pos和value,就改成:
index = int(row["pos"])
char = row["value"]
6.脚本模板三:筛选有效行后拼接
适用于CSV里有一列用来标记是否有效,例如valid、is_flag、status。
import csv
filename = "data.csv"
result = []
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
if row["valid"] == "1":
result.append(row["value"])
flag = "".join(result)
print(flag)
如果还需要排序,就把筛选和排序结合起来:
import csv
filename = "data.csv"
items = []
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
if row["valid"] == "1":
items.append((int(row["index"]), row["value"]))
items.sort(key=lambda x: x[0])
flag = "".join(value for index, value in items)
print(flag)
7.脚本模板四:ASCII转字符
适用于字段里是一堆数字,例如102,108,97,103。
import csv
filename = "data.csv"
chars = []
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
num = int(row["ascii"])
chars.append(chr(num))
flag = "".join(chars)
print(flag)
如果CSV里面还有顺序列:
import csv
filename = "data.csv"
items = []
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
index = int(row["index"])
char = chr(int(row["ascii"]))
items.append((index, char))
items.sort(key=lambda x: x[0])
flag = "".join(char for index, char in items)
print(flag)
8.脚本模板五:十六进制解码
CSV中可能有类似66、6c、61、67这样的十六进制数据。
import csv
filename = "data.csv"
hex_parts = []
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
hex_parts.append(row["hex"])
hex_string = "".join(hex_parts)
result = bytes.fromhex(hex_string).decode()
print(result)
如果每一行是完整的一段十六进制,也可以逐行解码:
text = bytes.fromhex(row["hex"]).decode()
9.脚本模板六:坐标还原图片
如果CSV里有x、y、value或者r、g、b,大概率是让你画图。
先安装Pillow:
pip install pillow
黑白图还原:
import csv
from PIL import Image
filename = "data.csv"
points = []
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
x = int(row["x"])
y = int(row["y"])
value = int(row["value"])
points.append((x, y, value))
width = max(x for x, y, value in points) + 1
height = max(y for x, y, value in points) + 1
img = Image.new("L", (width, height), 255)
for x, y, value in points:
color = 0 if value == 1 else 255
img.putpixel((x, y), color)
img.save("result.png")
print("[+] 已保存 result.png")
RGB彩色图还原:
import csv
from PIL import Image
filename = "data.csv"
points = []
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
x = int(row["x"])
y = int(row["y"])
r = int(row["r"])
g = int(row["g"])
b = int(row["b"])
points.append((x, y, r, g, b))
width = max(x for x, y, r, g, b in points) + 1
height = max(y for x, y, r, g, b in points) + 1
img = Image.new("RGB", (width, height), (255, 255, 255))
for x, y, r, g, b in points:
img.putpixel((x, y), (r, g, b))
img.save("result.png")
print("[+] 已保存 result.png")
保存出来以后,如果是二维码,可以直接扫码;如果图像方向不对,可以尝试旋转、翻转或者放大。
10.一个完整解题示例
假设题目给了data.csv:
id,pos,value,valid
1,3,g,1
2,0,f,1
3,9,x,0
4,1,l,1
5,2,a,1
题目没有多余提示,只说“find the flag”。
我们先观察字段:
pos:像是顺序。value:像是字符。valid:像是筛选条件。
所以思路就是:
- 读取CSV。
- 只保留
valid=1的行。 - 按
pos从小到大排序。 - 拼接
value。
最终脚本:
import csv
filename = "data.csv"
items = []
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
if row["valid"] != "1":
continue
pos = int(row["pos"])
value = row["value"]
items.append((pos, value))
items.sort(key=lambda item: item[0])
flag = "".join(value for pos, value in items)
print(flag)
运行:
python solve.py
输出:
flag
真实比赛里输出一般会是:
flag{xxxxxxxxxxxxxxxx}
11.解CSV题的通用套路
遇到CSV题,可以按这个流程走:
- 看文件名和题目描述:有没有提示排序、坐标、颜色、编码、日志、流量等关键词。
- 看表头:字段名通常就是题目给你的解题方向。
- 打印前几行:确认每列数据长什么样。
- 统计行数和字段范围:判断是不是图片坐标、ASCII、时间序列。
- 先写最小脚本:能读、能打印、能筛选就行。
- 逐步加功能:排序、拼接、解码、画图。
- 搜索flag格式:最后用正则提取
flag{...}。
12.常见坑
12.1编码错误
如果utf-8报错,可以试试:
open(filename, "r", encoding="gbk", newline="")
或者:
open(filename, "r", encoding="utf-8-sig", newline="")
utf-8-sig可以处理带BOM的CSV文件。
12.2数字排序变成字符串排序
错误示例:
items.sort(key=lambda x: x[0])
如果x[0]是字符串,会出现:
1, 10, 11, 2, 3
所以要转成整数:
index = int(row["index"])
12.3分隔符不是逗号
有些文件虽然叫CSV,但实际用的是分号、Tab或竖线分隔。
reader = csv.DictReader(f, delimiter=";")
Tab分隔:
reader = csv.DictReader(f, delimiter="\t")
12.4表头有空格
有些表头可能是:
index , char
这会导致row["index"]取不到。可以先打印:
print(reader.fieldnames)
或者读取后把key清理一下:
row = {k.strip(): v.strip() for k, v in row.items()}

说些什么吧!