SQL注入 - 手工注入 portswigger 练习
什么是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注入之前需要知道的
查询数据库以确定其类型和版本
主流数据库提供如下方式来确定数据库版本:
MySQL/Microsoft SELECT @@version
Oracle SELECT * FROM v$version
PostgreSQL SELECT version()
列出数据库的内容
查看数据库中都有哪些表:
MySQL/Microsoft/PostgreSQL SELECT * FROM information_schema.tables
Oracle SELECT * FROM all_tables
查看这个表都有哪些列:
MySQL/Microsoft/PostgreSQL SELECT * FROM information_schema.columns WHERE table_name = '表名'
Oracle SELECT * FROM all_tab_columns WHERE table_name = '表名'
SQL语句的注释
注释可以用来截断查询,并去掉原始查询中输入之后的部分
Microsoft/PostgreSQL/Oracle -- 单行注释
MySQL # 单行注释( -- 两个短横线后必须有空格,可以写成--+)
关于MySQL数据库
information_schema.tables 包含了数据库里所有的表
table_name 表名
table_schema 数据库名
column_name 字段名
version() 查看数据库版本(当前连接到MySQL的客户端版本)
database() 查看当前使用的数据库
user() 查看当前用户
length() 回指定对象的长度
ascii() 返回字符串str的最左字符的数值,ASCII()返回数值是从0到255
substr(str,start,len) 从str的start位置开始,提取的子串的长度为len
group_concat() 一次性获取所有的数据库信息
left(str,num) 对字符串str从左开始数起,返回num个字符(与函数right()相反)
靶场训练 portswigger labs
地址:All labs | Web Security Academy (portswigger.net)
工具:火狐浏览器配合hackbar
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查询对于盲注是无效的,因为它依赖于在响应中看到注入查询的结果。
参考文章:
SQL注入备忘单 SQL injection cheat sheet
PortSwigger Web Security Academy lab SQL注入
portswigger labs sql注入之探测数据库信息
portswigger lab Blind SQL injection sql盲注
若有错误,欢迎指正!o( ̄▽ ̄)ブ