无字母数字webshell算是一个很老生常谈的话题了,但由于利用条件比较苛刻,一般只会在CTF题目中出现,作为一种有趣的绕过的思路,p牛关于这个问题也写过两篇非常出色的博客,通过p牛的博客我也学到了许多有趣的bypass技巧。最近翻看其他大佬博客的时候,对于这个问题我又发现了一个很有趣的小tips,那就是通过linux中的某些特殊变量执行命令,或许这种技巧能在真实的环境或者CTF赛题中出现。
首先介绍一下shell脚本中$的多种用法(参考):
变量名 | 含义 |
---|---|
$0 | 脚本本身的名字 |
$1 | 脚本后所输入的第一串字符 |
$2 | 传递给该shell脚本的第二个参数 |
$* | 脚本后所输入的所有字符’westos’ ‘linux’ ‘lyq’ |
[email protected] | 脚本后所输入的所有字符’westos’ ‘linux’ ‘lyq’ |
$_ | 表示上一个命令的最后一个参数 |
$# | #脚本后所输入的字符串个数 |
$$ | 脚本运行的当前进程ID号 |
$! | 表示最后执行的后台命令的PID |
$? | 显示最后命令的退出状态,0表示没有错误,其他表示由错误 |
我看大佬的博客的时候他注意到: Linux变量$_
,它存储着上次程序传入的参数,比如执行echo can you get the file of tmp
命令后,再执行echo $_
,发现结果是tmp
。
因此给出一个特殊的题目条件,比如:
<?php
if(isset($_GET['code'])){
$code = $_GET['code'];
if(strlen($code)>10){
die("Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
eval("system(\"echo can you get the file of tmp;".$code."\")");
}else{
highlight_file(__FILE__);
}
此时我们使用payload
?code=. /\$_/*
然后配合临时文件上传我们就可以执行命令,甚至get shell。
但大佬在文章的末尾提到:
于是当天我就想到了个出CTF题的有趣点子,其实对于以get flag为目的的ctf题目而言,对于payload的长度可以大大缩减,我们来本地做个测试:
我们先到根目录下放个flag:
然后切换到桌面用那个黑魔法获得flag,我当时本地测试时想到的payload可以只用五个字符:
?code=. /$_
原理很简单,就是在linux里可以用点号+空格+文件名执行一个可执行文件,等效于source可执行文件,然后我们前面echo了一次flag,flag作为了最后一个参数,因此可以用$_代替这个flag,但又因为我们这个flag不是可执行文件,因此linux就会报错,然后打印并输出这个文件里的内容,类似于用date -f越权读文件一样。
然后我就构思一个题目,分享在了学校的信安协会群里:
<?php
if(isset($_GET['command'])){
$command = $_GET['command'];
if(strlen($command)>5){
die("Too Long!");
}
if(preg_match("/[A-Za-z0-9]+/",$command)){
die("No letters or numbers!");
}
eval(system("echo you are not able to get flag;$command 2>&1"));
}else{
highlight_file(__FILE__);
}
?>
因为要靠报错输出flag嘛,所以必须加上2>&1,但这样也导致这个题目的破局点比较明显了,我也没想到什么更好的方法,这个代码也只是一个雏形。
然后预期解当然就是?command=. /$_
放在群里之后没过多久就有人做出来了,而且还只用了三个字符,猜猜是什么?
没错,是直接?command=/$_
这种做法大大的出乎了我的意料,因为这样做我出题的时候本地是失败了的:
后来我一下就想到了,肯定是权限的问题。我当时在宝塔后台随便就创建了一个flag文本,到后台一看,果然不但有阅读权限更有执行权限:
把执行权限关了就只能用我那个预期解了:
原理也很简单,就是直接用/$_代替/flag了,相当于直接用文件名执行脚本,然后打印报错输出,就不用再加个点号了。
不过这次有趣的题目分享也激发了更多我对这种无字母数字ctf题目的思考,在限制和过滤足够多的情况下,只是用$当作给变量命名的工具其实是大大局限了它的作用,因为shell脚本中很多变量名都是有特殊含义的,如果配合一定条件,它能发挥十分神奇的作用,比如我上面出的那个题,就可以在限制足够多的条件用三个字符就拿到flag,而其他的变量同样能发挥神奇的作用:
我们知道$#可以表示#脚本后所输入的字符串个数,在默认情况下就直接表示零了:
而我们可以通过自增运算表示不同的数字:
这也相当于一种绕过限制表达数字的方式了。
同样的,$?可以显示最后命令的退出状态,我们也可以用它来构造出数字来:
其他的变量也有很神奇的作用,不过利用条件其实都挺苛刻的,但或许能通过不同变量打一套组合拳实现get shell,比如像大佬文章里的那样配合临时文件甚至可以直接get shell。但这种利用条件终究有点太难了,感觉只有可能在CTF题目作为一个有趣的考点,很难在实战中发挥作用,我在这里就算抛砖引玉了,希望大家能有更多有趣的见解和想法。
参考:
shell脚本中$的多种用法($* 、 [email protected] 、$_ 、$# 、$$ 、$! 、 $? )