SQL注入入门
SQL注入入门
1azy_fish.你也喜欢SQL注入吗?(MySQL为例
MySQL语句
登录
本地
1 | mysql -u username -p |
远程
1 | mysql -h ip -p port -u username -p |
库
1 | -- 创建数据库 |
数据类型
数值类型
类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 |
---|---|---|---|---|
TINYINT | 1 Bytes | (-128,127) | (0,255) | 小整数值 |
SMALLINT | 2 Bytes | (-32 768,32 767) | (0,65 535) | 大整数值 |
MEDIUMINT | 3 Bytes | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 |
INT或INTEGER | 4 Bytes | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 |
BIGINT | 8 Bytes | (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 |
FLOAT | 4 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) | 单精度 浮点数值 |
DOUBLE | 8 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) | 范围 | 格式 | 用途 |
---|---|---|---|---|
DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 |
TIME | 3 | ‘-838:59:59’/‘838:59:59’ | HH:MM:SS | 时间值或持续时间 |
YEAR | 1 | 1901/2155 | YYYY | 年份值 |
DATETIME | 8 | ‘1000-01-01 00:00:00’ 到 ‘9999-12-31 23:59:59’ | YYYY-MM-DD hh:mm:ss | 混合日期和时间值 |
TIMESTAMP | 4 | ‘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:07 | YYYY-MM-DD hh:mm:ss | 混合日期和时间值,时间戳 |
字符串类型
类型 | 大小 | 用途 |
---|---|---|
CHAR | 0-255 bytes | 定长字符串 |
VARCHAR | 0-65535 bytes | 变长字符串 |
TINYBLOB | 0-255 bytes | 不超过 255 个字符的二进制字符串 |
TINYTEXT | 0-255 bytes | 短文本字符串 |
BLOB | 0-65 535 bytes | 二进制形式的长文本数据 |
TEXT | 0-65 535 bytes | 长文本数据 |
MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 |
MEDIUMTEXT | 0-16 777 215 bytes | 中等长度文本数据 |
LONGBLOB | 0-4 294 967 295 bytes | 二进制形式的极大文本数据 |
LONGTEXT | 0-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 | -- 创建数据表 |
数据处理
1 | -- 插入数据 |
在使用上述语句时,以下是一些需要注意的事项:
插入数据:
- 确保插入语句中的字段和值的数量匹配,并按照正确的顺序提供值。
- 如果某些字段允许为空,可以使用NULL值来表示。
查询数据:
- 在SELECT语句中指定要检索的列名,可以使用通配符(*)来选择所有列。
- 使用WHERE子句来过滤结果集,根据条件检索特定的行。
- 可选地使用LIMIT子句来限制返回的行数,并使用OFFSET子句来指定起始位置。
修改数据:
- 使用UPDATE语句时,确保指定要更新的表名和列名。
- 使用WHERE子句来指定要更新的特定行,否则将更新所有行。
删除数据:
- 使用DELETE语句时,确保指定要删除的表名。
- 使用WHERE子句来指定要删除的特定行,否则将删除所有行。
UNION操作符:
- UNION操作符用于合并两个或多个SELECT语句的结果集。
- 可选择使用ALL关键字来包含重复行,或使用DISTINCT关键字来排除重复行。
数据排序:
- 使用ORDER BY子句按照指定的字段对结果进行排序。
- 可以指定多个字段,以定义排序的优先级。
- 默认情况下,按照升序(ASC)排序,可以使用DESC关键字进行降序排序。
数据分组:
- 使用GROUP BY子句按照指定的列对结果进行分组。
- 可以在SELECT列表中使用聚合函数对每个组进行计算。
- 可以使用WHERE子句过滤分组前的数据。
判断注入点
在GET参数、POST参数、Cookie、Referer、XFF、UA等地方尝试插入代码、符号或语句,尝试是否存在数据库参数读取行为,以及能否对其参数产生影响,如产生影响则说明存在注入点。
sql注入点类型
get注入
在get传参时写入参数,将SQl语句闭合,后面加写入自己的SQL语句。post注入
通过post传参,原理与get一样,重要的是判断我们所输入的信息是否与数据库产生交互,其次判断SQL语句是如何闭合的。有些网站通过查询cookie判断用户是否登录,需要与数据库进行交互,我们可以修改cookie的值,查找我们所需要的东西。或者通过报错注入是网页返回报错信息。
Referer注入
Referer正确写法应该是Referrer,因为http规定时写错只能将错就错,有些网站会记录ip和访问路径,例如百度就是通过Referer来统计网站流量,我们将访问路径进行SQL注入,同样也可以得到想要的信息。XFF注入
在用户登录注册模块在 HTTP 头信息添加 X-Forwarded-for: 9.9.9.9’ ,用户在注册的时候,如果存在安全隐患,会出现错误页面或者报错。从而导致注册或者登录用户失败。
burpsuite 抓包,提交输入检测语句:1
2X-Forwarded-for: 127.0.0.1'and 1=1#
X-Forwarded-for: 127.0.0.1'and 1=2#两次提交返回不一样,存在 SQL 注入漏洞
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的默认库与表
默认库和表的内容如下:
information_schema库:
- TABLES表:提供了关于数据库中的表的信息,包括表名、表类型、表引擎、创建时间等
- COLUMNS表:提供了表中的列信息,包括列名、数据类型、是否为主键等
- STATISTICS表:提供了关于表索引的信息,包括索引名、索引类型、索引字段等
- USER_PRIVILEGES表:给出了关于全局权限的信息,包括用户、权限等
- SCHEMA_PRIVILEGES表:给出了关于数据库权限的信息,包括数据库、权限等
- TABLE_PRIVILEGES表:给出了关于表权限的信息,包括表、权限等
- COLUMN_PRIVILEGES表:给出了关于列权限的信息,包括表、列、权限等
mysql库:
- user表:记录了MySQL的用户信息和全局权限
- db表:记录了数据库级别的权限信息
- tables_priv表:记录了表级别的权限信息
- columns_priv表:记录了列级别的权限信息
- event表:用于事件调度
- plugin表:记录了MySQL的插件信息
performance_schema库:
- tables表:提供了关于数据库中的表的信息,包括表名、表类型、表引擎、创建时间等
- columns表:提供了表中的列信息,包括列名、数据类型、是否为主键等
- events_waits表:提供了关于等待事件的信息,包括等待事件的类型、持续时间等
- mutex_instances表:提供了关于互斥锁的信息,包括互斥锁的名称、等待次数等
- file_instances表:提供了关于文件的信息,包括文件的名称、读写次数等
sys库:
- schema_table_statistics表:提供了关于数据库中表的统计信息,包括表的行数、大小等
- schema_index_statistics表:提供了关于数据库中索引的统计信息,包括索引的大小、使用次数等
- schema_table_lock_waits表:提供了关于表锁等待的信息,包括等待的表、等待时间等
看到上面的库与表,当我们注入之后就可以在其中看到数据库的基本信息,帮助我们进一步注入拿取危险信息。
SQL注入的种类
按照注入方法可以分为以下几类:
基于报错的注入(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;
基于布尔的盲注(Inference Blind Injection):通过观察Web应用的不同反应来推断数据,例如布尔盲注。这种注入方法是通过Web应用的其他改变来推断数据。
基于时间的盲注(Inference Time-based Injection):通过延迟响应时间来推断数据,例如时间盲注。这种注入方法是通过Web应用的响应时间来推断数据。
基于联合查询的注入(Inband Union-based Injection):通过联合查询将多个SELECT语句的结果合并到一个结果集中,例如联合注入。这种注入方法是通过合并多个查询结果来获取数据。
注意:
UNION
操作要求两个SELECT
语句返回的列数相同。基于堆叠查询的注入(Inband Stacked Queries Injection):通过在一个查询中嵌入多个查询语句来执行恶意操作,例如堆叠注入。这种注入方法是通过在一个查询中执行多个查询语句来实现攻击。
基于报表的注入(Inband Inline Query Injection):通过在查询语句中插入恶意代码来执行攻击,例如报表注入。这种注入方法是通过在查询语句中插入恶意代码来执行攻击。
二次注入(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()
函数将字符串 str1
和 str2
的数据连接在一起,中间用 ‘*’ 连接:
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 |
|
关键代码
1 | if ($row['password'] === $password) { |
只有密码正确才输出flag,有两种回显
那么我们尝试爆密码
1 | import requests |
密码eb2d018ac00e7d6dbE8eb7059dF0a4b2
得到flag
或者?quine注入