前言 写 CTF EXP 时最常打交道的就是正则提取、HTTP 请求、HTML 解析和并发。这篇文章把 Python 写 EXP 最常用的六个模块整理成速查手册——正则、文件操作、requests、BeautifulSoup、多进程、多线程——每个都有可复制的代码片段。
一、正则表达式(re 模块) 1.1 特殊符号速查
符号
含义
\s
空白符号(空格、制表符、换行等)
\S
除空白符号以外的任意符号
\w
字母、数字、下划线 [a-zA-Z0-9_]
\W
除字母、数字、下划线以外的符号
\d
数字 [0-9]
\D
除数字以外的任意符号
.
除换行符以外的任意字符(re.S 模式下匹配换行)
^
匹配字符串开头
$
匹配字符串结尾
\b
单词边界
1.2 限定符(量词)
限定符
含义
X+
X 至少出现 1 次,最多无限次
X*
X 出现 0 次或无限次
X?
X 出现 0 次或 1 次
X{m}
X 恰好出现 m 次
X{m,}
X 至少出现 m 次
X{m,n}
X 出现 m 到 n 次
1.3 范围符(字符类) 1 2 3 4 5 6 7 8 9 10 [abc] 任选其一(a 或 b 或 c) [^abc] 排除 a、b、c [a-z] 所有小写字母 [A-Z] 所有大写字母 [0-9] 所有数字,等价于 \d [^0-9] 非数字,等价于 \D [0-9a-zA-Z_] 等价于 \w [^0-9a-zA-Z_] 等价于 \W [\t\r\n\f] 空白符集合,等价于 \s [^\t\r\n\f] 非空白符,等价于 \S
1.4 实战示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import retext = "13787782556" pattern = r"^(\+86\-)?1[3-9][0-9]{9}$" print (re.match (pattern, text))text = "http://www.baidu.com" pattern = r"^(((http)|(https)|(ftp))\:\/\/)(w{3}\.)[A-Za-z0-9\-]{2,18}?(\.([a-zA-Z0-9\-]){2,18})$" print (re.match (pattern, text))text = "127.0.0.1" pattern = r"(\d{1,3}\.){3}\d{1,3}" print (re.match (pattern, text))
1.5 常用匹配函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import retext = "hello world 123" result = re.search(r"\d+" , text) print (result.group()) result = re.match (r"hello" , text) print (result.group()) results = re.findall(r"\w+" , text) print (results) for item in re.finditer(r"\w+" , text): print (item.group()) result = re.sub(r"\d+" , "456" , text) print (result)
match vs search: match 必须从文本开头匹配,search 可以在任意位置匹配。写 EXP 时大多数情况用 search。
1.6 flags 参数 1 re.search(pattern, text, flags=re.I)
修饰符
描述
re.I / re.IGNORECASE
不区分大小写
re.M / re.MULTILINE
多行模式,^ 和 $ 匹配每行的开头和结尾
re.S / re.DOTALL
. 匹配包括换行在内的所有字符
re.U / re.UNICODE
Unicode 模式(Python 3 默认)
re.X / re.VERBOSE
允许写带注释和空白的正则,可读性更好
re.A / re.ASCII
ASCII 模式,\w \b 等只匹配 ASCII 字符
1.7 贪婪模式 vs 非贪婪模式 1 2 3 4 5 6 7 8 9 text = "<div>hello</div><div>world</div>" print (re.findall(r"<div>.*</div>" , text))print (re.findall(r"<div>.*?</div>" , text))
记忆: 在 * + ? {m,n} 后面加 ? 变成非贪婪。
1.8 多列表并行遍历(zip) 从页面中提取多个并列字段时的常用写法:
1 2 3 4 5 names = re.findall(r'<td class="name">(.*?)</td>' , html) grades = re.findall(r'<td class="grade">(.*?)</td>' , html) for name, grade in zip (names, grades): print (f"{name} : {grade} " )
二、文件操作 2.1 基本读写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 with open ("./host.txt" , mode="r" , encoding="UTF-8" ) as f: content = f.read() with open ("./host.txt" , mode="r" , encoding="UTF-8" ) as f: for line in f: print (line.strip()) with open ("./output.txt" , mode="w" , encoding="UTF-8" ) as f: f.write("hello world\n" ) with open ("./output.txt" , mode="a" , encoding="UTF-8" ) as f: f.write("append line\n" )
2.2 模式说明
模式
含义
r
只读(默认)
w
写入,清空 原内容再写
a
追加,在原内容末尾 添加
r+
读写,不截断
w+
读写,清空 原内容
a+
读写,追加模式
b
二进制模式(如 rb、wb)
EXP 常用: 读用 r,写用 w 或 a。一般场景不用 w+/a+。
三、requests 模块——HTTP 请求 3.1 GET 请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requestsurl = "http://target.com/api" headers = { "User-Agent" : "Mozilla/5.0" , "Cookie" : "session=xxx" , } resp = requests.get(url, headers=headers, timeout=5 , verify=False ) print (resp.status_code) print (resp.text) print (resp.content) print (resp.headers) print (resp.cookies)
verify=False:忽略 HTTPS 证书验证(自签名证书时使用)。
3.2 POST 请求 1 2 3 4 5 6 7 data = {"key1" : "value1" , "key2" : "value2" } resp = requests.post(url, json=data, headers=headers) data = {"username" : "admin" , "password" : "123456" } resp = requests.post(url, data=data, headers=headers)
3.3 保持会话(Session) 1 2 3 4 5 6 7 sess = requests.Session() sess.post("http://target.com/login" , data={"user" : "admin" , "pass" : "xxx" }) resp = sess.get("http://target.com/admin" )
3.4 代理 1 2 3 4 5 proxies = { "http" : "http://127.0.0.1:8080" , "https" : "http://127.0.0.1:8080" , } resp = requests.get(url, proxies=proxies)
3.5 常见 HTTP 状态码
状态码
含义
200
成功
301
永久重定向
302
临时重定向
401
未授权(需登录)
403
禁止访问
404
不存在
500
服务器内部错误
四、BeautifulSoup 模块——HTML 解析 4.1 初始化 1 2 3 4 5 from bs4 import BeautifulSoupimport requestsresp = requests.get("http://target.com/page" ) soup = BeautifulSoup(resp.text, "html.parser" )
4.2 select —— CSS 选择器(最常用) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 soup.select('a' ) soup.select('div a' ) soup.select('div > a' ) soup.select('div > ul > li > a' ) soup.select('.juejin' ) soup.select('#sp' ) soup.select('a[id]' ) soup.select('a[id="a1"]' ) for item in soup.select('.cn-box > p' ): print (item.get_text())
4.3 find —— 查找单个元素 1 2 3 4 5 6 7 8 9 10 11 12 13 meta = soup.find("meta" , {"name" : "description" }) print (meta["content" ]) link = soup.find('a' , class_='juejin' ) link = soup.find('a' , title='home' ) img = soup.find("img" ) print (img["src" ])
4.4 find_all —— 查找所有元素 1 2 3 4 5 6 7 8 9 10 11 links = soup.find_all('a' , {"name" : "aaa" }) for link in links: print (link["href" ]) container = soup.find_all('div' , {"class" : "container" }) for div in container: inner = div.find('span' , {"class" : "title" }) if inner: print (inner.get_text())
4.5 实战:爬取 HTML 表格数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 from bs4 import BeautifulSoupimport requestsresp = requests.get("http://target.com/table" ) soup = BeautifulSoup(resp.text, "html.parser" ) rows = soup.select('table.data-table tr' ) for row in rows[1 :]: cols = row.select('td' ) if len (cols) >= 3 : name = cols[0 ].get_text().strip() value = cols[1 ].get_text().strip() flag = cols[2 ].get_text().strip() print (f"{name} | {value} | {flag} " )
五、多进程(multiprocessing) 5.1 进程三大状态 1 2 3 就绪 (Ready) ──→ 运行 (Running) ──→ 阻塞 (Blocked) ↑ │ └─────────────────────────────────────┘
就绪: 一切准备就绪,等待 CPU 调度
运行: 正在 CPU 上执行
阻塞: 等待 I/O、sleep、锁等
5.2 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from multiprocessing import Processimport timedef worker (name, num ): print (f"[{name} ] 开始, num={num} " ) time.sleep(2 ) print (f"[{name} ] 完成" ) if __name__ == '__main__' : p1 = Process(target=worker, args=("进程1" , 42 )) p1.start() p1.join() print ("主进程结束" )
__name__ == '__main__' 是必须的! Windows 上不加这个会无限递归创建子进程。
5.3 Process 参数 1 2 3 4 5 6 7 p = Process( target=func, args=(), kwargs={"key" : "value" }, name="my_process" , daemon=True , )
5.4 继承 Process 类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from multiprocessing import Processimport timeclass Test (Process ): def __init__ (self, name ): super (Test, self ).__init__() self .name = name def run (self ): print (f"{self.name} running" ) time.sleep(3 ) if __name__ == '__main__' : t = Test("aaa" ) t.start() t.join()
5.5 进程关键特性
特性
说明
不共享全局变量
子进程复制父进程的内存空间,修改互不影响
能拿到全局变量
子进程启动时会拷贝一份父进程的全局变量
进程间通信
需要 Queue、Pipe、Manager 等机制
5.6 守护进程 vs 孤儿进程
类型
定义
守护进程
子进程跟随父进程——父进程死,子进程自动关 (daemon=True)
孤儿进程
父进程先死,子进程被 init(PID 1)收养,继续运行
六、多线程(threading) 6.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import threadingimport timedef worker (name, num ): print (f"[{name} ] 开始, num={num} " ) time.sleep(2 ) print (f"[{name} ] 完成" ) t1 = threading.Thread(target=worker, args=("线程1" , 42 )) t1.start() t1.join() print ("主线程结束" )
用法与 Process 几乎相同,target args kwargs daemon 等参数通用。
6.2 继承 Thread 类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import threadingimport timeclass MyThread (threading.Thread): def __init__ (self, name ): super ().__init__() self .name = name def run (self ): print (f"{self.name} running" ) time.sleep(3 ) t = MyThread("worker" ) t.start() t.join()
6.3 线程 vs 进程对比
多线程 (threading)
多进程 (multiprocessing)
全局变量共享
共享 (同一内存空间)
不共享(独立内存空间)
GIL 影响
CPU 密集型受限(同一时刻只有一个线程执行 Python 字节码)
不受 GIL 限制
适用场景
I/O 密集型(网络请求、文件读写)
CPU 密集型(计算、爆破)
创建开销
小
大
内存占用
共享进程内存
各自独立内存
6.4 EXP 中多线程的典型用法 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 import threadingimport requestsresults = [] lock = threading.Lock() def brute (uid ): """并发爆破用户 ID""" url = f"http://target.com/user/{uid} " resp = requests.get(url, timeout=5 ) if "flag" in resp.text: with lock: results.append((uid, resp.text)) print (f"[+] Found: {uid} " ) threads = [] for uid in range (1 , 1000 ): t = threading.Thread(target=brute, args=(uid,)) t.start() threads.append(t) for t in threads: t.join() print (f"Results: {len (results)} " )
lock 的作用: 多线程共享 results 列表时,append 虽然是原子操作但你仍需要 lock 保护对共享资源的读写,特别是 print 和写文件的场景。
七、综合实战——一个完整的爬虫 EXP 骨架 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 68 69 70 71 72 73 74 75 76 77 78 79 """CTF 爬虫 EXP 骨架""" import reimport threadingimport requestsfrom bs4 import BeautifulSoupTARGET = "http://target.example.com" THREADS = 10 lock = threading.Lock() results = [] def fetch_page (path ): """抓取页面""" sess = requests.Session() resp = sess.get(f"{TARGET} {path} " , timeout=5 ) return resp.text def parse_data (html ): """解析 HTML,提取数据""" soup = BeautifulSoup(html, "html.parser" ) items = soup.select('.data-item' ) for item in items: name = item.select_one('.name' ).get_text().strip() value = item.select_one('.value' ).get_text().strip() flag_match = re.search(r'flag\{[^}]+\}' , value) if flag_match: with lock: results.append(flag_match.group()) print (f"[+] Flag: {flag_match.group()} " ) return items def brute_param (param ): """并发爆破参数""" url = f"{TARGET} /check?key={param} " try : resp = requests.get(url, timeout=3 ) if resp.status_code == 200 and "exists" in resp.text: with lock: print (f"[+] Valid: {param} " ) except : pass def run (): html = fetch_page("/index" ) items = parse_data(html) threads = [] for i in range (1000 ): t = threading.Thread(target=brute_param, args=(f"key_{i} " ,)) t.start() threads.append(t) if len (threads) >= THREADS: for t in threads: t.join() threads = [] for t in threads: t.join() print (f"\n=== 共找到 {len (results)} 个 flag ===" ) for r in results: print (r) if __name__ == "__main__" : run()
参考