Python 基础不是为了背语法,而是为了在渗透测试里把重复动作自动化。
你会遇到很多碎活:整理 URL、处理字典、批量请求、检查端口、拼接路径、读写结果、对扫描输出做二次筛选、把一堆命令回显整理成报告。Python 的价值就在这里:它是安全学习里最趁手的胶水语言。
这篇文章的目标很明确:
- 先掌握 Python 的核心语法。
- 再把语法放进渗透场景里理解。
- 最后写几个小工具:目录扫描器、端口扫描器、Shell/TTY 基础示例。
所有示例都默认只用于自己的靶场、授权环境和本地实验。不要对未授权目标做扫描、爆破或批量请求。
先跑起来
确认 Python 版本:
python --version
python3 --version
Windows 上可能是 python,Linux 和 macOS 上常见是 python3。
新建实验目录:
mkdir python-pentest-basic
cd python-pentest-basic
写一个 main.py:
message = "Python is a pentest glue language"
print(message)
运行:
python main.py
渗透学习里不要一上来追求复杂工具。先让脚本跑起来,再慢慢给它加输入、输出、错误处理和结果保存。
变量和类型
变量就是给数据起名字。
target = "http://127.0.0.1:8000"
port = 8000
is_alive = True
timeout = 3.0
print(target, port, is_alive, timeout)
常见类型:
| 类型 | 示例 | 渗透场景 |
|---|---|---|
str | "admin" | URL、路径、参数、响应文本 |
int | 80 | 端口、状态码、计数 |
float | 2.5 | 超时时间、延迟 |
bool | True | 是否命中、是否可访问 |
None | None | 暂无结果、空值 |
写安全脚本时,你经常会把扫描目标、状态码、响应长度、标题、匹配结果放进变量里。
url = "http://127.0.0.1:8000/admin"
status_code = 200
content_length = 1024
found = status_code == 200 and content_length > 0
print(f"{url=} {status_code=} {found=}")
f-string 是 Python 最舒服的字符串格式化方式。以后输出结果、拼接报告、打印调试信息都会用到。
字符串处理
安全脚本里有大量文本处理:URL、路径、请求参数、字典、命令输出。
raw = " /Admin/Login "
path = raw.strip().lower()
print(path)
常用方法:
text = "GET /admin/login HTTP/1.1"
print(text.startswith("GET"))
print("/admin" in text)
print(text.split(" "))
print(text.replace("HTTP/1.1", "").strip())
把路径拼成 URL:
base_url = "http://127.0.0.1:8000"
path = "/admin"
url = f"{base_url.rstrip('/')}/{path.lstrip('/')}"
print(url)
rstrip('/') 和 lstrip('/') 很实用。它能避免拼出这种路径:
http://127.0.0.1:8000//admin
分支判断
分支用来根据结果做决定。
status_code = 403
if status_code == 200:
print("可能存在可访问页面")
elif status_code in (301, 302):
print("发生跳转,需要继续观察 Location")
elif status_code == 403:
print("存在路径,但当前无权限")
elif status_code == 404:
print("路径不存在")
else:
print("需要人工判断")
在渗透场景里,状态码不能机械理解。
200不一定有漏洞,只代表请求成功。403反而可能说明路径存在。302可能跳到登录页。500可能是错误处理暴露,也可能只是服务异常。
Python 脚本只负责帮你筛线索,真正的判断还要靠上下文。
循环
循环用来批量处理目标。
paths = ["/admin", "/login", "/backup.zip"]
for path in paths:
print(f"checking {path}")
带序号:
paths = ["/admin", "/login", "/backup.zip"]
for index, path in enumerate(paths, start=1):
print(index, path)
过滤空行:
raw_paths = ["/admin", "", " ", "/login"]
for raw_path in raw_paths:
path = raw_path.strip()
if not path:
continue
print(path)
continue 的意思是跳过本轮循环。处理字典文件时特别常用,因为字典里经常会有空行、注释和无效内容。
列表、字典、集合
列表适合存一组有顺序的数据。
ports = [22, 80, 443, 8080]
ports.append(3306)
print(ports[0])
print(ports[-1])
字典适合存结构化结果。
result = {
"url": "http://127.0.0.1:8000/admin",
"status": 403,
"length": 512,
"note": "路径可能存在,但需要权限",
}
print(result["url"])
print(result.get("title", "no title"))
集合适合去重。
raw_hosts = ["example.com", "test.local", "example.com"]
hosts = set(raw_hosts)
print(hosts)
渗透脚本常见的数据结构:
findings = [
{"url": "http://127.0.0.1:8000/admin", "status": 403},
{"url": "http://127.0.0.1:8000/login", "status": 200},
]
for item in findings:
print(item["url"], item["status"])
你可以把它理解成一个轻量版结果表。
函数
函数用来把一段动作命名。
def build_url(base_url: str, path: str) -> str:
return f"{base_url.rstrip('/')}/{path.lstrip('/')}"
print(build_url("http://127.0.0.1:8000/", "/admin"))
为什么要写函数?
- 代码复用。
- 逻辑更清楚。
- 后续更容易测试。
- 出问题时更容易定位。
例如判断状态码:
def classify_status(status_code: int) -> str:
if status_code == 200:
return "open"
if status_code in (301, 302):
return "redirect"
if status_code == 403:
return "forbidden"
if status_code == 404:
return "missing"
return "unknown"
print(classify_status(403))
函数名要写得像一句话。以后你回来看代码,不用每行都读,就知道它大概在干什么。
文件读写
渗透里经常要读字典、读目标列表、写扫描结果。
准备一个 paths.txt:
/admin
/login
/backup.zip
读取:
from pathlib import Path
wordlist = Path("paths.txt")
paths = []
for line in wordlist.read_text(encoding="utf-8").splitlines():
path = line.strip()
if path:
paths.append(path)
print(paths)
写结果:
from pathlib import Path
results = [
"http://127.0.0.1:8000/admin 403",
"http://127.0.0.1:8000/login 200",
]
Path("result.txt").write_text("\n".join(results), encoding="utf-8")
pathlib 比手搓字符串路径舒服很多。写脚本时优先用它。
异常处理
网络请求会失败,文件可能不存在,编码可能不对,目标可能超时。
不要让脚本一遇到错误就崩掉。
from pathlib import Path
try:
content = Path("missing.txt").read_text(encoding="utf-8")
except FileNotFoundError:
print("文件不存在")
else:
print(content)
网络场景里更常见:
import socket
try:
socket.create_connection(("127.0.0.1", 8000), timeout=2)
except TimeoutError:
print("连接超时")
except OSError as error:
print(f"连接失败:{error}")
else:
print("端口可连接")
异常处理不是为了吞掉错误,而是为了让脚本能继续跑,并把失败原因记录下来。
简单匹配
复杂正则可以后面再学。刚开始写渗透脚本,先把简单匹配用熟。
url = "http://127.0.0.1:8000/admin/login"
print("/admin" in url)
print(url.startswith("http://"))
print(url.endswith("/login"))
筛选路径:
paths = ["/", "/admin", "/login", "/api/debug", "/assets/app.js"]
for path in paths:
if "admin" in path or path.startswith("/api"):
print(path)
很多小脚本不需要一开始就上正则。in、startswith、endswith、split、strip 已经能解决大量入门问题。
JSON 和 CSV
脚本输出最好是可继续处理的数据。
JSON 适合结构化结果:
import json
findings = [
{"url": "http://127.0.0.1:8000/admin", "status": 403, "length": 512},
{"url": "http://127.0.0.1:8000/login", "status": 200, "length": 2048},
]
with open("findings.json", "w", encoding="utf-8") as file:
json.dump(findings, file, ensure_ascii=False, indent=2)
CSV 适合表格:
import csv
findings = [
{"url": "http://127.0.0.1:8000/admin", "status": 403, "length": 512},
{"url": "http://127.0.0.1:8000/login", "status": 200, "length": 2048},
]
with open("findings.csv", "w", encoding="utf-8", newline="") as file:
writer = csv.DictWriter(file, fieldnames=["url", "status", "length"])
writer.writeheader()
writer.writerows(findings)
如果你以后要把结果导入表格、报告或其他脚本,JSON 和 CSV 会很省心。
网络请求
Python 标准库能发请求,但安全脚本里常用 requests。
安装:
python -m pip install requests
最小请求:
import requests
url = "http://127.0.0.1:8000"
response = requests.get(url, timeout=5)
print(response.status_code)
print(len(response.text))
print(response.headers.get("Server"))
加上 User-Agent:
import requests
headers = {
"User-Agent": "Neuroblue-Lab/1.0",
}
response = requests.get("http://127.0.0.1:8000", headers=headers, timeout=5)
print(response.status_code)
渗透测试里发请求要克制:
- 设置超时。
- 控制频率。
- 记录目标范围。
- 不要对未授权站点批量跑脚本。
实战案例一:简单目录扫描器
场景:你在本地靶场或授权测试环境里,想快速检查一些常见路径是否存在。
准备 paths.txt:
/
/admin
/login
/backup.zip
/api/debug
安装依赖:
python -m pip install requests
写 dir_scan.py:
import time
from pathlib import Path
import requests
base_url = "http://127.0.0.1:8000"
interesting_status = {200, 301, 302, 401, 403}
def build_url(base_url: str, path: str) -> str:
return f"{base_url.rstrip('/')}/{path.lstrip('/')}"
for line in Path("paths.txt").read_text(encoding="utf-8").splitlines():
path = line.strip()
if not path or path.startswith("#"):
continue
url = build_url(base_url, path)
try:
response = requests.get(url, timeout=3, allow_redirects=False)
except requests.RequestException as error:
print(f"[ERR] {url} {error}")
continue
status = response.status_code
length = len(response.content)
if status in interesting_status:
print(f"[{status}] {url} len={length}")
time.sleep(0.1)
运行:
python dir_scan.py
这个脚本很简单,但已经足够说明 Python 在渗透里的价值:
- 读字典。
- 拼 URL。
- 发请求。
- 过滤有价值的状态码。
- 控制请求频率。
它不是替代 dirsearch、ffuf、gobuster 这类成熟工具,而是帮你理解目录扫描的底层逻辑。
实战案例二:简单端口扫描器
场景:你在自己的靶场或授权内网实验环境里,想快速检查几个端口是否能连通。
写 port_scan.py:
import socket
host = "127.0.0.1"
ports = [22, 80, 443, 8000, 8080]
def is_open(host: str, port: int, timeout: float = 1.5) -> bool:
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return False
for port in ports:
state = "open" if is_open(host, port) else "closed"
print(f"{host}:{port} {state}")
运行:
python port_scan.py
这不是替代 nmap,只是让你理解端口扫描的本质:尝试建立 TCP 连接,成功就是可连,失败就记录失败。
真正做扫描时优先使用成熟工具,并且确认授权范围。
实战案例三:Python Shell 与 TTY 基础
Shell 的本质是一个进程,它接收输入,执行命令,再把输出返回给你。Python 经常出现在两个位置:
- 在本地或靶场里启动一个命令解释器,帮助你理解 Shell 是什么。
- 在已经授权、已经拿到基础命令执行的环境里,改善交互体验。
先看本地示例。Linux / macOS 上可以写:
import subprocess
subprocess.run(["/bin/sh"])
Windows 上可以写:
import subprocess
subprocess.run(["cmd.exe"])
运行:
python shell_demo.py
如果你在授权靶场里已经获得了一个很简陋的 Linux Shell,经常会看到交互不好用:方向键异常、没有补全、不能正常清屏。此时可以尝试用 Python 申请一个伪终端:
python3 -c 'import pty; pty.spawn("/bin/bash")'
这一步不是“黑魔法”,只是让当前交互更像正常终端。这里不展开反连 Shell、免杀、持久化之类内容。初学阶段先理解输入、输出、进程和终端,比背一堆 payload 更重要。
命令行参数
脚本写死变量只能自己临时用。加上命令行参数后,它才像一个工具。
最小 argparse 示例:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("target")
parser.add_argument("-p", "--port", type=int, default=80)
args = parser.parse_args()
print(args.target)
print(args.port)
运行:
python tool.py 127.0.0.1 -p 8080
以后你写任何安全脚本,都建议先想清楚:
- 目标从哪里输入?
- 字典从哪里输入?
- 结果输出到哪里?
- 超时和延迟怎么控制?
- 是否需要安静模式或详细模式?
这比代码本身更像“工具设计”。
子进程调用
有时你需要从 Python 调用系统命令,比如在授权环境里调用 ping 或读取工具输出。
import subprocess
result = subprocess.run(
["ping", "-c", "1", "127.0.0.1"],
text=True,
capture_output=True,
)
print(result.returncode)
print(result.stdout)
print(result.stderr)
注意两点:
- 优先传列表,不要拼接字符串。
- 不要把用户输入直接塞进 shell。
危险写法:
import subprocess
user_input = "127.0.0.1; whoami"
subprocess.run(f"ping -c 1 {user_input}", shell=True)
这类写法本身就是命令注入的风险来源。学 Python 的同时,也是在理解漏洞是怎么被写出来的。
虚拟环境
不同项目依赖不同,建议每个项目一个虚拟环境。
python -m venv .venv
Windows:
.venv\Scripts\activate
Linux / macOS:
source .venv/bin/activate
安装依赖:
python -m pip install requests
python -m pip freeze > requirements.txt
以后别人复现你的脚本:
python -m pip install -r requirements.txt
写安全工具不要把全局 Python 环境弄乱。虚拟环境是最基本的工程卫生。
渗透脚本的基本规范
一个能长期使用的小脚本,至少应该做到:
- 有清楚的授权边界说明。
- 参数不要写死。
- 设置网络超时。
- 失败要记录原因。
- 输出结构化结果。
- 不要默认高并发。
- 不要默认破坏性操作。
- 代码里不要硬编码真实密码、Token、Cookie。
你可以把脚本分成四层:
输入参数 -> 执行动作 -> 整理结果 -> 输出文件
只要这四层清楚,脚本就不会很快变成一团乱麻。
学习路线
建议顺序:
- 变量、类型、分支、循环。
- 列表、字典、集合、字符串。
- 函数、异常、文件读写。
pathlib、json、csv、简单字符串匹配。requests、socket、argparse。- 写三个小工具:目录扫描器、端口扫描器、Shell/TTY 基础示例。
- 把自己的靶场复盘过程自动化一小块。
不要为了学 Python 而学 Python。最好每学一个知识点,就问它能不能解决一个真实小问题。
比如:
- 字符串:清洗 URL 和路径。
- 列表:保存目标集合。
- 字典:保存扫描结果。
- 文件:读取字典和输出报告。
- 简单匹配:筛选路径、URL 和响应片段。
- 网络请求:做授权范围内的批量验证。
- 参数解析:把脚本变成命令行工具。
练习任务
给自己几个小任务:
- 写一个脚本,读取
targets.txt,去重后输出。 - 写一个脚本,读取 URL 列表,只保留
https://开头的。 - 写一个脚本,检查本地靶场的
/admin、/login、/api是否存在。 - 写一个脚本,检查
127.0.0.1的22、80、8000、8080是否可连。 - 给目录扫描器加 CSV 输出。
- 给目录扫描器加
--only-status 200,403参数。 - 把脚本执行结果整理成 Markdown 表格。
这些任务很小,但它们会把 Python 基础和渗透工作流慢慢接起来。
结尾
Python 在渗透学习里的意义不是“我会一门编程语言”,而是“我能把重复劳动变成工具”。
先把基础语法用在真实场景里:读文件、发请求、解析文本、保存结果。等这些动作稳定之后,再去学并发、异步、爬虫框架、漏洞利用脚本和更复杂的安全工具开发,才不会漂在空中。
一开始写得慢没关系。你每写出一个能解决真实问题的小脚本,都会让后面的学习轻一点。