WaniCTF 2023 | Writeup for Web Challenges
Web
IndexedDB
This is an easy question, after you visit the webpage you see this
Simply open DevTools and look at the IndexedDB values and we have the flag
Flag: FLAG{y0u_c4n_u3e_db_1n_br0wser}
Extract Service 1
Trying out with the given sample word document file and looking at the post request
There is a file and a target parameter. The target parameter seems to be the extracted xml file from the word document. We know that the flag is in /flag
, if only there was a way to read the flag file from another directory. Cue symlinks in zip files.
Upload the file, after the docx has been extracted as zip, the document.xml which is a symlink to /flag
will be read instead and we get the flag.
Flag: FLAG{ex7r4c7_1s_br0k3n_by_b4d_p4r4m3t3rs}
Extract Service 2
The source code changes can be seen below where the target is checked to ensure they are not modified. Our solution still works as we use symlinks and we did not modify the target parameter
if targetParam == "docx" {
extractTarget = "word/document.xml"
} else if targetParam == "xlsx" {
extractTarget = "xl/sharedStrings.xml"
} else if targetParam == "pptx" {
extractTarget = "ppt/slides/slide1.xml"
} else {
c.HTML(http.StatusOK, "index.html", gin.H{
"result": "Error : target is invalid",
})
return
}
Flag: FLAG{4x7ract_i3_br0k3n_by_3ymb01ic_1ink_fi1e}
64bps
Looking at the nginx.conf
file, we are rate limted to 8 bytes per second.
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
keepalive_timeout 65;
gzip off;
limit_rate 8; # 8 bytes/s = 64 bps
server {
listen 80;
listen [::]:80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
}
From the dockerfile, we see that a 2GB file is generated and the flag simply appended at the end.
FROM nginx:1.23.3-alpine-slim
COPY nginx.conf /etc/nginx/nginx.conf
COPY flag.txt /usr/share/nginx/html/flag.txt
RUN cd /usr/share/nginx/html && \
dd if=/dev/random of=2gb.txt bs=1M count=2048 && \
cat flag.txt >> 2gb.txt && \
rm flag.txt
With HTTP GET requests, we can use the Range header to get partial content. Making a request for the last 64 Bytes, we get the flag.
curl https://64bps-web.wanictf.org/2gb.txt -i -H "Range: bytes=-64"
Flag: FLAG{m@ke_use_0f_r@n0e_reques7s_f0r_l@r9e_f1les}
screenshot
Seems like we need to try and get a screenshot of the flag in /flag.txt
However there seems to be an unintended solution in the following snippet
if (!req.query.url.includes("http") || req.query.url.includes("file")) {
res.status(400).send("Bad Request");
return;
}
It checks if the url includes http
but not the file
protocol.
However we can bypass that as chromium is not case sensitive and typical file tricks works (it does not matter that the http
folder does not exists as we go back up one level with ../
).
Flag: FLAG{beware_of_parameter_type_confusion!}
Lambda
Downloading the files, we have the Access key ID, Secret access key and the Region. After configuring the awscli
with aws configure
. We can check the attached user policies with aws iam list-attached-user-policies
To get the actual policy document we need to check the current version first with aws iam get-policy --policy-arn
using the WaniLambdaGetFunc
ARN from the previous command. For this case the policy document is v1
, we can then get the actual policy document stating what we can or cannot do for the policy.
Using aws iam get-policy-version
with the arn and version flags
We are allowed to use aws lambda get-function
on wani_function
, this allows us to get the deployment package used for the function
After downloading and extracting the contents. WaniCTF_Lambda.runtimeconfig.json
tells us that the function is a Net Core 6 application
{
"runtimeOptions": {
"tfm": "net6.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false
}
}
}
We can then use a Free .Net Decompiler like DotPeek to view the files, specifically WaniCTF_Lambda.dll
We can see the expected username and password to view the flag, but we do not need to try as we have the actual flag itself.
Flag: FLAG{l4mabd4_1s_s3rverl3ss_s3rv1c3}