SQL注入 - 手工注入 portswigger 练习


什么是SQL注入?

SQL注入(SQL Injection)是指Web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在Web应用程序中事先定义好的查询语句的结尾后添加额外的SQL语句,在管理员不知情的情况下实现非法操作。以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。

简单来讲就是:攻击者通过构造恶意的SQL语句来实现对数据库的操作。

两个条件:

  • 参数用户可控:用户能够控制数据的输入
  • 构造的参数可带入数据库并且被执行:原本要执行的sql语句拼接了用户的输入

SQL注入会发生在哪些地方?

以下是一些常见的SQL注入发生地点:

  1. 用户输入:这是最常见的SQL注入来源,包括一些表单字段如登录表单、搜索框、注册表单等。

  2. URL参数:通过修改URL的查询参数,攻击者可以尝试对数据库进行注入攻击。

  3. Cookie:Web应用程序会使用Cookie来存储用户会话信息,就可能被用来执行SQL注入。

  4. HTTP头部:有些Web应用程序可能会根据HTTP请求头部字段(如User-Agent、Referer等)中的信息来构建SQL查询。

  5. XML输入:有些Web应用程序会使用XML或其他进行数据交换,如果没有正确处理输入数据,就可能被用来执行SQL注入。


SQL注入的类型有哪些?

按照数据类型来分类

  1. 数字型注入

类似结构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
  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'

按照数据提交的方式来分类:

  1. GET 注入

使用GET方式提交数据,注入点的位置在GET参数部分。

  1. POST 注入

使用POST方式提交数据,注入点位置在POST数据部分。

  1. Cookie 注入

HTTP请求的时候会带上客户端的Cookie, 注入点位置在Cookie当中的某个字段中。

  1. HTTP 头部注入

注入点在HTTP请求头部的某个字段中,例如Referrer字段。(严格的讲,Cookie其实应该也是算头部注入的一种形式)

按照执行效果来分类:

  1. 基于布尔的盲注

即可以根据返回页面判断条件真假的注入。

  1. 基于时间的盲注

即用条件语句查看时间延迟语句是否执行(即页面返回时间是否延长)来判断。

  1. 基于报错注入

即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。

  1. 联合查询注入

可以使用union查询的情况下的注入。

  1. 堆查询注入

可以同时执行多条语句的执行时的注入。

  1. 宽字节注入

数据库编码与php编码设置为不同的两个编码,这样就可能会产生宽字节注入。


SQL注入点如何探测

在输入字段中尝试输入特殊的SQL字符,如果应用程序返回了数据库错误信息,或者异常,可能就意味着存在SQL注入漏洞。

只要是带有参数的动态网页并且该网页访问了数据库,那么就有可能存在 SQL 注入:

  • 先加单引号'或双引号"等看是否报错,如果报错就有可能存在SQL注入漏洞。

  • 另外在URL后面加and 1=1and 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 byunion

  • 输入' 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( ̄▽ ̄)ブ

热门相关:妖夏   第一强者   走私大明   楚汉争鼎   秾李夭桃