第四章:Web应用漏洞挖掘(上)——SQL注入、XSS与CSRF

各位同学,欢迎来到实战环节。今天我们要聊的这三个漏洞——SQL注入、XSS、CSRF,可以说是Web安全领域的“老三样”。别觉得它们老,我敢说,现在你去挖一些金融系统,十有八九还能碰到。说白了,基础不牢,地动山摇。

我个人习惯,每次拿到一个目标,先不急着上扫描器。先手动测几个点,心里有个底。今天我们就从最经典的SQL注入开始。

4.1 SQL注入:报错注入与盲注

SQL注入的本质是什么?就是用户输入的数据,被当成了SQL代码来执行。你想想看,这多危险。我在项目中遇到过一家P2P平台,登录框直接拼接字符串,连参数化查询都没用。结果呢?整个用户表被拖走。

4.1.1 报错注入

报错注入,顾名思义,就是让数据库报错,从错误信息里提取数据。它有个前提:应用会把数据库的错误信息直接显示在页面上。

常用的报错函数有这些:

数据库类型 常用报错函数
MySQL updatexml(), extractvalue(), floor()
MSSQL convert(), cast()
Oracle ctxsys.drithsx.sn(), utl_inaddr.get_host_name()

举个例子,在MySQL里,我们可以这样构造:

// 正常URL
http://example.com/user?id=1

// 报错注入payload
http://example.com/user?id=1 and updatexml(1,concat(0x7e,(select database())),1)

这里用了updatexml()函数。第二个参数我们传入了concat(0x7e,(select database()))。当XPath表达式格式错误时,MySQL会报错,并把错误信息显示出来。嗯,这里要注意,0x7e是波浪号~,用来确保格式一定错误。

关键点:报错注入的输出长度有限制,MySQL的updatexml和extractvalue最多只能输出32个字符。如果数据太长,需要用substr()函数分段提取。

4.1.2 盲注

如果页面不显示错误信息呢?那就用盲注。盲注分两种:布尔盲注和时间盲注。

布尔盲注:根据页面返回内容的“真”与“假”来判断。比如:

// 如果数据库版本是5.5,页面返回正常
?id=1 and substr(version(),1,1)=5

// 如果不是,页面返回异常
?id=1 and substr(version(),1,1)=4

我曾经遇到一个系统,登录失败只提示“用户名或密码错误”,不告诉你具体哪个错了。这就是典型的布尔盲注场景。我写了个脚本,逐字符爆破,花了两个小时,把管理员密码给跑出来了。

时间盲注:当页面没有任何差异反馈时,就用时间来判断。比如:

// 如果条件成立,sleep 5秒
?id=1 and if(ascii(substr(database(),1,1))>100,sleep(5),0)

说实话,时间盲注效率很低。一个32位的密码,平均要发几百个请求。我建议你优先用布尔盲注,实在不行再上时间盲注。

避坑指南:我曾经在测试一个银行系统时,用了时间盲注,结果sleep(10)导致数据库连接池爆满,生产环境差点挂了。从那以后,我测试时间盲注,sleep时间绝不超过3秒。

4.2 XSS:反射型与存储型

XSS,全称跨站脚本攻击。说白了,就是攻击者把恶意脚本“塞”进了你的网页里。我见过最离谱的一次,某电商平台的商品评论区,直接渲染HTML,连过滤都没有。

4.2.1 反射型XSS

反射型XSS,数据是“一次性”的。通常出现在搜索框、URL参数里。比如:

// 正常搜索
http://example.com/search?q=手机

// 反射型XSS payload
http://example.com/search?q=<script>alert('xss')</script>

用户点击这个链接,脚本就会在浏览器里执行。攻击者通常会把这个链接伪装成短链接,通过钓鱼邮件发出去。

嗯,这里要注意,反射型XSS虽然是一次性的,但危害不小。攻击者可以窃取用户的Cookie,然后冒充用户登录。

4.2.2 存储型XSS

存储型XSS更危险。恶意脚本被永久保存在服务器上,比如评论区、个人简介、留言板。每个访问该页面的用户都会中招。

我在项目中遇到过这样一个案例:某论坛的“个人签名”功能,允许用户输入任意HTML。攻击者把恶意脚本写进签名里,管理员每次审核帖子,都会触发XSS。结果管理员的Cookie被窃取,整个论坛被拿下。

存储型XSS的典型payload:

<img src=x onerror=alert(document.cookie)>

这个payload利用了图片加载失败时的onerror事件。即使<script>标签被过滤,这种基于事件的XSS往往能绕过。

警告:XSS的防御核心是“输出编码”。永远不要信任用户的输入。在输出到HTML时,对<>&"等特殊字符进行转义。

4.3 CSRF攻击原理与利用

CSRF,中文叫跨站请求伪造。它的逻辑是这样的:用户已经登录了A网站,然后在不经意间访问了B网站。B网站伪造了一个请求,发给A网站。因为浏览器会自动带上A网站的Cookie,所以A网站以为这个请求是用户本人发的。

举个例子:

// 假设银行转账的接口是:
POST /transfer
参数:toAccount=目标账号&amount=金额

// 攻击者构造一个恶意页面:
<img src="http://bank.com/transfer?toAccount=attacker&amount=10000" style="display:none">

用户只要访问了这个恶意页面,浏览器就会自动发起GET请求。如果银行用的是GET方式处理转账,那钱就没了。

我遇到过最经典的CSRF案例,是一个社交平台的“关注”功能。攻击者构造了一个页面,里面藏了100个隐藏的<img>标签,每个标签对应一个明星账号。用户访问后,瞬间关注了100个账号。

CSRF的防御手段主要有三种:

  • Referer验证:检查请求来源是否合法。但Referer可以被伪造,不太可靠。
  • Token验证:在表单里加一个随机Token,服务端验证。这是最常用的方法。
  • SameSite Cookie:设置Cookie的SameSite属性为Strict或Lax,限制跨站请求携带Cookie。

避坑指南:我曾经测试过一个系统,他们用了Token验证,但Token是写在URL里的。结果呢?Referer泄露了Token,攻击者照样能伪造请求。记住,Token一定要放在请求体里,或者用自定义Header传递。

知识体系总览

为了让你更直观地理解这三个漏洞的关系,我画了一张图:

Web应用漏洞挖掘(上)知识体系 SQL注入 XSS CSRF 报错注入 盲注 反射型 存储型 原理 利用 核心防御思路 输入过滤 + 输出编码 + Token验证 + 最小权限原则 永远不要信任用户的输入,永远

这张图把今天的内容串起来了。SQL注入、XSS、CSRF,虽然攻击方式不同,但核心都是“数据与代码未分离”。你想想看,只要用户输入的数据能被当作代码执行,就一定有风险。

好了,今天的内容就到这里。这三个漏洞,你可以在自己的测试环境里多练练。记住,纸上得来终觉浅,绝知此事要躬行。


专注资料整理