在 AWD 赛事中,时间紧迫且操作频繁,熟练掌握以下 Python 标准库与第三方库,是实现自动化攻击批量化运维防御的基础。

1 Python 标准库:系统交互与底层网络

标准库无需额外安装,是编写高兼容性、高可用性脚本的首选。

1.1 操作系统与环境交互:os & sys

在 AWD 中,通常需要遍历目录读取源码、备份文件,或者根据不同操作系统环境调整脚本逻辑。

  • os 模块:主要用于文件和目录操作。
    • os.system() / os.popen():执行系统命令(在现代 Python 开发中,更推荐使用 subprocess)。
    • os.walk():递归遍历目录树,常用于 批量查找后门(Webshell) 或备份整个 Web 目录。
  • sys 模块:主要用于与 Python 解释器交互。
    • sys.argv:获取命令行参数,用于编写灵活的 CLI 工具(如 python exp.py http://target 80)。
    • sys.exit():在检测到目标不可达或触发防御机制时安全退出脚本。

1.2 进程管理与命令执行:subprocess

完全理解。在深入复杂的攻防脚本之前,打好基础是非常必要的。考虑到内容吸收效率和 Token 的合理分配,我们先聚焦于 subprocess,从最基本的核心用法开始,一步步向上构建。

subprocess 模块的作用非常明确:让 Python 脚本能够像你在终端(Terminal)里敲键盘一样,去执行操作系统的命令,并且还能把执行的结果拿回到 Python 里进行逻辑判断。

1.2.1 为什么抛弃 os.system

在很多老旧的教程中,你会看到使用 os.system('ls')。但在现代 Python 开发和网络安全领域,我们强烈建议抛弃它,原因有两点:

  1. 无法捕获输出os.system 只能在屏幕上打印结果,你的 Python 变量无法直接拿到这些文本内容(它只能返回一个代表成功与否的数字状态码)。

  2. 安全风险:它的底层实现极易受到命令注入(Command Injection)攻击。


1.2.2 核心函数:subprocess.run()

自 Python 3.5 起,官方推荐使用 subprocess.run() 来处理绝大多数的系统命令调用任务。它是一个同步阻塞的函数——即 Python 程序会暂停,等待这条系统命令彻底执行完毕后,才会继续往下执行代码。

1.2.2.1 执行无参命令

我们先来看如何执行一个最简单的命令,例如查看当前系统用户 whoami

1
2
3
4
5
import subprocess

# 直接传入命令字符串的列表
# 注意:强烈建议将命令和参数写成列表形式,而不是一整个字符串 "whoami"
subprocess.run(['whoami'])

运行逻辑:这会在终端屏幕上打印出当前用户名(如 root),但你的 Python 脚本内部并没有截获这个值。

1.2.2.2 执行带参数的命令

当命令带有参数时(例如 Linux 下的 ls -la /var/www/html),列表传参的优势就体现出来了。Python 会自动处理底层的空格和转义问题,避免因路径中含有特殊字符而导致的报错。

1
2
3
4
import subprocess

# 列表的第一个元素是主命令,后面的元素全是参数
subprocess.run(['ls', '-la', '/var/www/html'])

1.2.2.3 捕获输出结果

这是你在 AWD 中最常用的场景。你需要执行命令,并把弹出的结果存到变量里,以便后续使用正则表达式(re)提取 Flag 或 IP 网址。

要实现这一点,需要给 run() 函数添加两个关键参数:

  • capture_output=True:告诉 Python,把命令产生的标准输出(stdout)和错误输出(stderr)都拦截在内存里,不要直接打印到屏幕上。
  • text=True:将捕获到的底层二进制字节流(bytes)自动解码为普通的 Python 字符串(string),方便我们进行 if "root" in result: 这样的字符串匹配。
1
2
3
4
5
6
7
8
import subprocess

# 执行命令并捕获结果
result = subprocess.run(['id'], capture_output=True, text=True)

# result 是一个 CompletedProcess 对象,它包含了执行完毕后的所有状态信息
print("返回状态码:", result.returncode) # 0 表示命令执行成功,非 0 表示出现错误
print("标准输出内容:\n", result.stdout) # 这里就是我们想要的具体命令回显内容

1.2.2.4 错误处理:识别执行失败的命令

在 AWD 的复杂环境中,靶机可能被其他队伍篡改,导致某些命令(如 netstat)不存在或因权限不足而报错。我们需要优雅地处理这些错误。

1
2
3
4
5
6
7
8
9
10
11
12
import subprocess

# 故意执行一个不存在的目录查看命令
result = subprocess.run(['ls', '/non_existent_folder_awd'], capture_output=True, text=True)

# 通过 returncode 判断命令是否报错
if result.returncode != 0:
print("[-] 命令执行失败!")
# stdout 此时通常为空,报错详情会被写入 stderr
print("[-] 错误原因:", result.stderr)
else:
print("[+] 执行成功:\n", result.stdout)

1.3 底层网络通信:socket

socket 是所有网络通信的基石,工作在 TCP/UDP 层面。

  • AWD 场景:编写底层的端口扫描器、服务存活探测脚本,或者在无法使用高级库时构造原始的网络数据包,甚至编写反弹 Shell(Reverse Shell)的监听端。
  • 核心应用:通过三次握手探测端口开放状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import socket

def scan_port(ip, port):
# 创建一个 TCP/IPv4 的 Socket 对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1) # 设置超时时间,避免阻塞
try:
s.connect((ip, port))
print(f"[+] {ip}:{port} is OPEN")
return True
except:
return False
finally:
s.close()

1.4 数据提取神器:re (正则表达式)

在 AWD 中,无论你是获取了网页源码需要提取 Flag,还是在防御阶段分析 Nginx/Apache 日志寻找攻击者的 Payload,正则表达式都是必不可少的工具。

  • AWD 场景Flag 自动化提取(如 flag{...})、日志异常 IP 提取SQL注入报错信息匹配
  • 核心方法re.findall(), re.search(), re.match()
1
2
3
4
5
6
7
import re

log_line = '192.168.1.10 - - [12/Mar/2026:14:00:00 +0000] "GET /index.php?id=1%27 HTTP/1.1" 200'
# 匹配 IP 地址
ip_pattern = r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b'
ips = re.findall(ip_pattern, log_line)
print("提取到的攻击者IP:", ips) # ['192.168.1.10']

2 第三方核心库:Web 攻防与流量控制

第三方库需要通过 pip install 安装,它们封装了复杂的底层逻辑,极大提升了脚本编写的效率。

2.1 requests 模块

在原生 Python 中,自带了 urllib 模块用于网络请求,但它的 API 设计极其反人类且繁琐。requests 作为第三方库,其设计哲学是 “HTTP for Humans”(让人类使用的 HTTP 库),它将底层的 TCP 连接、编码解码、Cookie 处理等复杂逻辑全部完美封装。

前置准备:

由于是第三方库,如果你的靶机或物理机尚未安装,需要先通过终端执行:

pip install requests


2.1.1 GET 请求 (获取资源)

GET 是 HTTP 中最常见的请求方法,主要用于向服务器索取数据(例如在浏览器地址栏输入网址按回车,就是一个标准的 GET 请求)。

在 AWD 中,GET 请求常用于:探测靶机存活状态、读取网页源码以寻找隐藏的注释或后门链接

2.1.1.1 基础发送与状态码判定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

# 目标靶机的 URL
url = "http://192.168.1.100/index.php"

# 发起 GET 请求,并将服务器返回的所有信息赋值给 response 变量
response = requests.get(url)

# 核心属性 1:status_code (状态码)
# 它是判断请求是否成功的最直接标准
print("HTTP 状态码:", response.status_code)

if response.status_code == 200:
print("[+] 靶机存活,页面访问成功!")
elif response.status_code == 404:
print("[-] 页面不存在 (可能木马文件已被防守方删除)")
elif response.status_code == 403:
print("[-] 拒绝访问 (可能触发了目标机器的 WAF 或目录权限被锁定)")

2.1.1.2 携带 URL 参数 (Query String)

很多时候我们需要向 GET 请求传递参数,例如访问 http://target.com/vuln.php?id=1&cmd=whoami

requests 中,绝对不要手动去拼接字符串,这极易出错且不优雅。标准的做法是使用 params 字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

url = "http://192.168.1.100/vuln.php"

# 将参数封装为字典
payload = {
"id": "1",
"cmd": "whoami"
}

# requests 会自动帮你把字典转换为合法的 URL 编码拼接在网址后面
response = requests.get(url, params=payload)

# 打印最终实际发出的完整 URL,用于调试验证
print("实际请求的完整 URL:", response.url)

2.1.2 POST 请求 (提交数据)

POST 请求主要用于向服务器发送数据。它将数据放在 HTTP 报文的请求体(Body)中,而不是直接暴露在 URL 里。

在 AWD 中,POST 请求常用于:爆破后台登录密码、提交比赛 Flag、向一句话木马发送执行指令

2.1.2.1 提交表单数据 (Form Data)

当你模拟网页上的账号密码登录时,使用的是表单提交。在 requests 中,通过 data 参数传入字典即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests

login_url = "http://192.168.1.100/admin/login.php"

# 构造想要爆破或登录的表单数据
login_data = {
"username": "admin",
"password": "password123"
}

# 发起 POST 请求,注意这里用的是 data 参数,不是 params
response = requests.post(login_url, data=login_data)

if "欢迎回来" in response.text:
print("[+] 登录成功!")
else:
print("[-] 密码错误。")

2.1.3 深入解析:Response (响应对象) 的提取方法

无论是 GET 还是 POST,服务器返回的结果都被封装在了我们定义的 response 变量中。针对不同类型的数据,requests 提供了不同的提取属性:

2.1.3.1 response.text:提取文本内容

它是最常用的属性。requests 会自动根据 HTTP 头部信息推测网页的编码(如 utf-8),并将其解码为 Python 字符串

  • 适用场景:读取 HTML 源码、读取纯文本、用正则表达式(re 模块)在其中匹配想要的数据。

2.1.3.2 response.content:提取二进制数据

获取服务器返回的原始字节流(bytes),不进行任何字符解码。

  • 适用场景:当你需要下载靶机上的图片、压缩包源码备份(.zip/.tar.gz)或者可执行文件时,必须用这个属性,否则文件会损坏。
1
2
3
4
5
6
7
8
9
import requests

url = "http://192.168.1.100/backup.zip"
res = requests.get(url)

# 将二进制流写入本地文件
with open("target_backup.zip", "wb") as f: # "wb" 表示以二进制写模式打开
f.write(res.content)
print("[+] 源码备份下载完成!")

2.1.3.3 response.json():提取 JSON 数据

现代 Web 架构和 AWD 比赛平台的 API 几乎全部使用 JSON 格式交互。如果服务器返回的是 JSON 数据,调用此方法会直接将其转换为 Python 的字典(Dict)对象,省去了引入 json 模块手动解析的麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
import requests

# 假设这是向比赛平台查询自己队伍分数的 API
api_url = "http://platform.awd.com/api/get_score"
res = requests.get(api_url)

# 直接解析为字典
data_dict = res.json()

# 像操作普通字典一样提取数据
print("当前队伍分数:", data_dict['score'])
print("当前排名:", data_dict['rank'])

2.1.4 请求头 (Headers) 伪造与身份隐匿

在默认情况下,使用 requests 发起网络请求时,其底层会自动携带一个特征极其明显的 HTTP 请求头:User-Agent: python-requests/2.x.x

在 AWD 赛场上,防守方(甚至比赛平台自带的 WAF)通常会配置一条最基础的防御规则:直接阻断所有 User-Agent 包含 “python” 或 “java” 的流量。因此,伪造请求头是将你的自动化脚本伪装成“正常人类浏览器行为”的核心步骤。

2.1.4.1 伪装 User-Agent (浏览器标识)

我们需要构造一个字典,将正常的浏览器 UA 覆盖掉默认的 Python UA。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

url = "http://192.168.1.100/vuln.php"

# 构造自定义的 Headers 字典
# 建议在实战中准备几个常见的主流浏览器 UA 进行随机切换
headers_payload = {
# 伪装成 Windows 10 下的 Chrome 浏览器
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
# 告诉服务器我们接受的数据类型
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
}

# 发起 GET 请求时,将字典传递给 headers 参数
response = requests.get(url, headers=headers_payload)

if response.status_code == 200:
print("[+] 成功绕过基础 UA 拦截,获取到页面内容!")

2.1.4.2 伪造 Referer 与 X-Forwarded-For (突破特定逻辑漏洞)

除了 UA 之外,某些靶机的后台逻辑漏洞(如 SSRF、越权或部分 SQL 注入)可能会校验请求的来源或客户端 IP。我们同样可以通过 Headers 字典进行伪造。

  • Referer:告诉服务器“我是从哪个页面跳转过来的”。常用于绕过防盗链或极其简陋的 CSRF 防御机制。
  • X-Forwarded-For (XFF):在经过代理或负载均衡时,用于表示客户端的真实 IP。很多靶机的代码中如果使用 $_SERVER['HTTP_X_FORWARDED_FOR'] 来获取 IP 并存入数据库,这里往往是极佳的二次注入伪造本地访问(127.0.0.1)的突破口。
1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

url = "http://192.168.1.100/admin/system_check.php"

# 针对特定漏洞的 Headers 伪造
headers_bypass = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605.1.15",
"Referer": "http://127.0.0.1/admin/index.php", # 伪造是从本地后台主页跳转过来的
"X-Forwarded-For": "127.0.0.1" # 伪装成本地 IP,尝试绕过外部 IP 访问限制
}

response = requests.post(url, headers=headers_bypass, data={"cmd": "whoami"})
print(response.text)

2.1.4.3 在 Session 中全局配置 Headers (实战最优解)

在编写批量扫描或批量攻击脚本时,如果每个 get()post() 都去单独写一遍 headers=... 会导致代码严重冗余。专业的做法是直接修改 Session 对象的全局配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

# 1. 实例化 Session 维持会话
sess = requests.Session()

# 2. 全局更新该会话的 Headers
# 此后通过该 sess 发出的所有请求,都会自动带上这个伪装的 UA
sess.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
})

# 3. 正常发起连贯的攻击/测试流程
res_1 = sess.get("http://192.168.1.100/index.php")
res_2 = sess.post("http://192.168.1.100/login.php", data={"u":"admin", "p":"123456"})

# res_1 和 res_2 在底层抓包时,全部呈现为合法的 Chrome 浏览器流量

2.2 流量分析与报文伪造:Scapy

Scapy 是一个极其强大的交互式数据包处理程序。它能够伪造或者解码大量的网络协议数据包。

  • AWD 场景网络抓包与流量分析(PCAP 审计)发现其他队伍的攻击 Payload,ARP 欺骗拦截流量,或者构造特制的畸形数据包进行拒绝服务攻击(DoS)测试。
  • 注意Scapy 运行通常需要系统的最高权限(Root)。
1
2
3
4
5
6
7
8
9
10
11
12
# 注意:此模块较为庞大,通常采用按需导入
from scapy.all import sniff, IP, TCP

def packet_callback(packet):
# 简单的嗅探逻辑:过滤包含明文密码的 HTTP 流量
if packet.haslayer(TCP) and packet.haslayer(Raw):
payload = packet[Raw].load.decode('utf-8', errors='ignore')
if "password=" in payload or "flag=" in payload:
print(f"[*] 截获敏感流量: {packet[IP].src} -> {packet[IP].dst}")

# 开始嗅探网卡 eth0 上的 TCP 80 端口流量
# sniff(iface="eth0", filter="tcp port 80", prn=packet_callback, store=0)

2.3 远程服务器运维与管理:paramiko

AWD 不仅有攻击,更重要的是防守。当队伍分配到多台靶机时,手动登录服务器修改密码、修补漏洞效率极低,paramiko 提供了 Python 原生的 SSHv2 协议支持。

  • AWD 场景批量修改多台服务器的 SSH 密码批量上传防御脚本(WAF)弱口令爆破测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import paramiko

def ssh_command(ip, user, passwd, cmd):
client = paramiko.SSHClient()
# 自动添加未知主机的 SSH 密钥
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
client.connect(ip, username=user, password=passwd, timeout=5)
stdin, stdout, stderr = client.exec_command(cmd)
print(f"[{ip}] 结果:\n{stdout.read().decode()}")
except Exception as e:
print(f"[{ip}] 连接失败: {e}")
finally:
client.close()

# 示例:批量查看当前系统权限
# ssh_command("192.168.1.100", "root", "toor", "id")

3 数据格式解析:API与配置处理

无论是靶机提供的服务还是比赛平台下发的接口,数据往往以特定的格式进行序列化。Python 提供了极简的内置库来处理这些数据。

  • json 模块:AWD 中最常见的格式。用于向比赛平台的 API 获取靶机状态、获取本轮 Flag 的提交结果等。
    • json.loads(str):字符串转字典。
    • json.dumps(dict):字典转字符串。
  • csv 模块:常用于读取或导出批量靶机信息表(如包含 IP、端口、用户名、密码的表格),方便脚本进行迭代读取。
  • xml 模块:部分老旧系统(如某些 Java RMI 或 SOAP 接口)使用 XML 传输数据,可能会涉及 XML 外部实体注入(XXE)漏洞的构造与解析。